From 7ed8822bd4867848cd86cf37ef73852dad0cc685 Mon Sep 17 00:00:00 2001 From: Magomed Date: Tue, 14 Jul 2026 19:53:30 +0300 Subject: [PATCH] Add Dark Productivity theme with native title bar and auth dialog chrome. Persist theme choice in settings, fix grid row colors on theme switch, and apply DWM caption styling to the main window and authorization dialog. Co-authored-by: Cursor --- .../Classes/UiThemeHelper.cs | 12 +- src/ElectronicPharmacy/Form1.UiShell.cs | 33 ++++- .../HelpForms/HF_Registration.cs | 5 +- src/ElectronicPharmacy/Program.cs | 2 - .../Properties/Settings.Designer.cs | 12 ++ .../Properties/Settings.settings | 3 + src/Elfisa.UI/Controls/ModernDataGridView.cs | 65 ++++++++-- .../Controls/ModernDocumentTabStrip.cs | 87 ++++++++++++- src/Elfisa.UI/Helpers/WindowChromeHelper.cs | 117 ++++++++++++++++++ .../Theming/DarkProductivityPalette.cs | 83 +++++++++++++ src/Elfisa.UI/Theming/ThemeManager.cs | 24 +++- src/Elfisa.UI/Theming/ThemeRenderer.cs | 62 +++++++++- src/Elfisa.UI/Theming/ThemeVariant.cs | 3 +- 13 files changed, 483 insertions(+), 25 deletions(-) create mode 100644 src/Elfisa.UI/Helpers/WindowChromeHelper.cs create mode 100644 src/Elfisa.UI/Theming/DarkProductivityPalette.cs diff --git a/src/ElectronicPharmacy/Classes/UiThemeHelper.cs b/src/ElectronicPharmacy/Classes/UiThemeHelper.cs index 82b6b4b..22ba007 100644 --- a/src/ElectronicPharmacy/Classes/UiThemeHelper.cs +++ b/src/ElectronicPharmacy/Classes/UiThemeHelper.cs @@ -103,9 +103,19 @@ namespace Электронная_Фармация.Classes } else if (control is Panel || control is UserControl || control is TableLayoutPanel || control is SplitContainer) { - if (control.Tag as string != "keep-bg") + if (control.Tag as string == "keep-bg") + { + // preserve custom background + } + else if (control.Tag as string == "chrome-child") + { + control.BackColor = theme.BackgroundPrimary; + control.ForeColor = theme.TextPrimary; + } + else { control.BackColor = theme.BackgroundSecondary; + control.ForeColor = theme.TextPrimary; } } diff --git a/src/ElectronicPharmacy/Form1.UiShell.cs b/src/ElectronicPharmacy/Form1.UiShell.cs index 7048abb..da3ddd5 100644 --- a/src/ElectronicPharmacy/Form1.UiShell.cs +++ b/src/ElectronicPharmacy/Form1.UiShell.cs @@ -4,6 +4,7 @@ using System.Drawing; using System.Linq; using System.Windows.Forms; using Elfisa.UI.Controls; +using Elfisa.UI.Helpers; using Elfisa.UI.Loading; using Elfisa.UI.Notifications; using Elfisa.UI.Theming; @@ -112,6 +113,7 @@ namespace Электронная_Фармация }); _appHeader.ItemSelected += OnAppHeaderItemSelected; + _documentTabStrip.ThemeToggleClicked += OnThemeToggleClicked; _documentTabStrip.TabSelected += OnDocumentTabSelected; _documentTabStrip.TabClosed += OnDocumentTabClosed; _documentTabStrip.TabCloseAllRequested += OnDocumentTabCloseAll; @@ -133,14 +135,41 @@ namespace Электронная_Фармация _documentTabMenu.Items.Add(_documentCloseTabMenuItem); _documentTabMenu.Items.Add(_documentCloseAllTabsMenuItem); - ThemeManager.ThemeChanged += (_, __) => _themeService.Apply(this); + ThemeManager.ThemeChanged += (_, __) => + { + UiThemeHelper.ApplyToControlTree(this); + UpdateThemeToggleState(); + WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors); + }; + + HandleCreated += (_, __) => WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors); + } + + private void UpdateThemeToggleState() + { + if (_documentTabStrip != null) + { + _documentTabStrip.IsDarkTheme = ThemeManager.IsDarkProductivityActive; + } + } + + private void OnThemeToggleClicked(object sender, EventArgs e) + { + ThemeManager.ToggleProductivityTheme(); + Settings.Default.UseDarkTheme = ThemeManager.IsDarkProductivityActive; + Settings.Default.Save(); + UpdateThemeToggleState(); } partial void FinalizeModernShell() { - ThemeManager.CurrentVariant = ThemeVariant.PharmaTrust; + ThemeManager.CurrentVariant = Settings.Default.UseDarkTheme + ? ThemeVariant.DarkProductivity + : ThemeVariant.PharmaTrust; _themeService.Initialize(this); + UpdateThemeToggleState(); UiThemeHelper.ApplyToControlTree(this); + WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors); RefreshCurrentUserStatus(); } diff --git a/src/ElectronicPharmacy/HelpForms/HF_Registration.cs b/src/ElectronicPharmacy/HelpForms/HF_Registration.cs index 27784e0..15eb8c7 100644 --- a/src/ElectronicPharmacy/HelpForms/HF_Registration.cs +++ b/src/ElectronicPharmacy/HelpForms/HF_Registration.cs @@ -12,6 +12,8 @@ using System.Text.Json; using System.Text.Json.Serialization; using Электронная_Фармация.Classes; using System.Net.Http; +using Elfisa.UI.Helpers; +using Elfisa.UI.Theming; namespace Электронная_Фармация.HelpForms { @@ -20,6 +22,7 @@ namespace Электронная_Фармация.HelpForms public HF_Registration() { InitializeComponent(); + Shown += (_, __) => WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors); } string login= Settings.Default.stringLogin.ToString(); @@ -91,7 +94,7 @@ namespace Электронная_Фармация.HelpForms private void HF_Registration_Load(object sender, EventArgs e) { UiThemeHelper.ApplyToControlTree(this); - panel1.BackColor = Elfisa.UI.Theming.ThemeManager.Colors.TitleBarBottom; + WindowChromeHelper.ApplyDialogChrome(this, panel1, label3); txtLogin.Text = login; txtPassword.Text = password; txtLocationId.Text = Settings.Default.LocationId ?? string.Empty; diff --git a/src/ElectronicPharmacy/Program.cs b/src/ElectronicPharmacy/Program.cs index a8be127..4dcc9e3 100644 --- a/src/ElectronicPharmacy/Program.cs +++ b/src/ElectronicPharmacy/Program.cs @@ -1,6 +1,5 @@ using System; using System.Windows.Forms; -using Elfisa.UI.Theming; namespace Электронная_Фармация { @@ -11,7 +10,6 @@ namespace Электронная_Фармация { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - ThemeManager.CurrentVariant = ThemeVariant.PharmaTrust; Application.Run(new ElectroPharmacy()); } } diff --git a/src/ElectronicPharmacy/Properties/Settings.Designer.cs b/src/ElectronicPharmacy/Properties/Settings.Designer.cs index c9b1cff..e0ea4f7 100644 --- a/src/ElectronicPharmacy/Properties/Settings.Designer.cs +++ b/src/ElectronicPharmacy/Properties/Settings.Designer.cs @@ -107,5 +107,17 @@ namespace Электронная_Фармация.Properties { } } + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool UseDarkTheme { + get { + return ((bool)(this["UseDarkTheme"])); + } + set { + this["UseDarkTheme"] = value; + } + } + } } diff --git a/src/ElectronicPharmacy/Properties/Settings.settings b/src/ElectronicPharmacy/Properties/Settings.settings index 17ec4d6..d747674 100644 --- a/src/ElectronicPharmacy/Properties/Settings.settings +++ b/src/ElectronicPharmacy/Properties/Settings.settings @@ -23,5 +23,8 @@ + + False + \ No newline at end of file diff --git a/src/Elfisa.UI/Controls/ModernDataGridView.cs b/src/Elfisa.UI/Controls/ModernDataGridView.cs index 0cbfc82..2f45d86 100644 --- a/src/Elfisa.UI/Controls/ModernDataGridView.cs +++ b/src/Elfisa.UI/Controls/ModernDataGridView.cs @@ -8,6 +8,7 @@ namespace Elfisa.UI.Controls public class ModernDataGridView : DataGridView { private ThemeColors _theme; + private int? _hoverRowIndex; public ModernDataGridView() { @@ -24,6 +25,7 @@ namespace Elfisa.UI.Controls ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing; ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single; DoubleBuffered = true; + CellFormatting += OnGridCellFormatting; ApplyTheme(ThemeManager.Colors); } @@ -64,30 +66,73 @@ namespace Elfisa.UI.Controls Font = theme.FontRegular13, Padding = new Padding(12, 0, 12, 0) }; - Invalidate(); + Invalidate(true); + } + + private void OnGridCellFormatting(object sender, DataGridViewCellFormattingEventArgs e) + { + if (e.RowIndex < 0 || e.ColumnIndex < 0) + { + return; + } + + var theme = _theme ?? ThemeManager.Colors; + var row = Rows[e.RowIndex]; + + if (row.Selected) + { + e.CellStyle.BackColor = theme.GridRowSelected; + e.CellStyle.ForeColor = theme.GridRowSelectedText; + } + else if (_hoverRowIndex == e.RowIndex) + { + e.CellStyle.BackColor = theme.GridRowHover; + e.CellStyle.ForeColor = theme.TextPrimary; + } + else if (e.RowIndex % 2 == 1) + { + e.CellStyle.BackColor = theme.GridRowAlternate; + e.CellStyle.ForeColor = theme.TextPrimary; + } + else + { + e.CellStyle.BackColor = theme.GridRowBackground; + e.CellStyle.ForeColor = theme.TextPrimary; + } + + e.CellStyle.SelectionBackColor = theme.GridRowSelected; + e.CellStyle.SelectionForeColor = theme.GridRowSelectedText; + e.CellStyle.Font = theme.FontRegular13; } protected override void OnCellMouseEnter(DataGridViewCellEventArgs e) { base.OnCellMouseEnter(e); - if (e.RowIndex >= 0 && e.RowIndex != CurrentCell?.RowIndex) + if (e.RowIndex < 0 || e.RowIndex == _hoverRowIndex) { - var theme = _theme ?? ThemeManager.Colors; - Rows[e.RowIndex].DefaultCellStyle.BackColor = theme.GridRowHover; + return; } + + var previous = _hoverRowIndex; + _hoverRowIndex = e.RowIndex; + if (previous.HasValue) + { + InvalidateRow(previous.Value); + } + + InvalidateRow(e.RowIndex); } protected override void OnCellMouseLeave(DataGridViewCellEventArgs e) { base.OnCellMouseLeave(e); - if (e.RowIndex >= 0) + if (e.RowIndex < 0 || _hoverRowIndex != e.RowIndex) { - var theme = _theme ?? ThemeManager.Colors; - var isAlt = e.RowIndex % 2 == 1; - Rows[e.RowIndex].DefaultCellStyle.BackColor = isAlt - ? theme.GridRowAlternate - : theme.GridRowBackground; + return; } + + _hoverRowIndex = null; + InvalidateRow(e.RowIndex); } } } diff --git a/src/Elfisa.UI/Controls/ModernDocumentTabStrip.cs b/src/Elfisa.UI/Controls/ModernDocumentTabStrip.cs index 132565d..20ed68a 100644 --- a/src/Elfisa.UI/Controls/ModernDocumentTabStrip.cs +++ b/src/Elfisa.UI/Controls/ModernDocumentTabStrip.cs @@ -27,10 +27,29 @@ namespace Elfisa.UI.Controls private ThemeColors _theme; private string _activeTabId; private string _hoverTabId; + private bool _themeToggleHover; + private bool _isDarkTheme; + private Rectangle _themeToggleRect = Rectangle.Empty; public event EventHandler TabSelected; public event EventHandler TabClosed; public event EventHandler TabCloseAllRequested; + public event EventHandler ThemeToggleClicked; + + public bool IsDarkTheme + { + get => _isDarkTheme; + set + { + if (_isDarkTheme == value) + { + return; + } + + _isDarkTheme = value; + Invalidate(_themeToggleRect); + } + } private readonly ContextMenuStrip _contextMenu; private readonly ToolStripMenuItem _closeTabMenuItem; @@ -62,6 +81,13 @@ namespace Elfisa.UI.Controls _contextMenu = new ContextMenuStrip(); _contextMenu.Items.Add(_closeTabMenuItem); _contextMenu.Items.Add(_closeAllTabsMenuItem); + Resize += (_, __) => UpdateThemeToggleRect(); + } + + private void UpdateThemeToggleRect() + { + const int size = 26; + _themeToggleRect = new Rectangle(Width - size - 10, (Height - size) / 2, size, size); } public IReadOnlyList Tabs => _tabs; @@ -140,6 +166,7 @@ namespace Elfisa.UI.Controls public void ApplyTheme(ThemeColors theme) { _theme = theme; + UpdateThemeToggleRect(); Invalidate(); } @@ -168,6 +195,8 @@ namespace Elfisa.UI.Controls const int minTabWidth = 140; const int gap = 8; const int closeSize = 18; + const int toggleReserve = 44; + var maxTabRight = Math.Max(10, Width - toggleReserve); using (var textFont = theme.FontSemibold14 ?? new Font("Segoe UI", 10f, FontStyle.Bold)) using (var closeFont = new Font("Segoe MDL2 Assets", 10f, FontStyle.Regular)) @@ -176,6 +205,11 @@ namespace Elfisa.UI.Controls { var textSize = TextRenderer.MeasureText(tab.Title, textFont); var tabWidth = Math.Max(minTabWidth, textSize.Width + 48); + if (x + tabWidth > maxTabRight) + { + break; + } + var tabRect = new Rectangle(x, tabTop, tabWidth, tabHeight); _hitAreas[tab.Id] = tabRect; @@ -232,6 +266,41 @@ namespace Elfisa.UI.Controls x += tabWidth + gap; } } + + DrawThemeToggle(g, theme); + } + + private void DrawThemeToggle(Graphics g, ThemeColors theme) + { + if (_themeToggleRect.IsEmpty) + { + UpdateThemeToggleRect(); + } + + var fill = _themeToggleHover + ? GraphicsExtensions.Blend(theme.SurfaceElevated, theme.AccentPrimarySoft, 0.45f) + : theme.Surface; + using (var fillBrush = new SolidBrush(fill)) + { + GraphicsExtensions.FillRoundedRectangle(g, fillBrush, _themeToggleRect, theme.CornerRadiusSmall); + } + + using (var pen = new Pen(_themeToggleHover ? theme.AccentPrimaryBorder : theme.Border, 1f)) + { + GraphicsExtensions.DrawRoundedRectangle(g, pen, _themeToggleRect, theme.CornerRadiusSmall); + } + + var glyph = _isDarkTheme ? "\uE706" : "\uE708"; + using (var iconFont = new Font("Segoe MDL2 Assets", 11f)) + { + TextRenderer.DrawText( + g, + glyph, + iconFont, + _themeToggleRect, + theme.TextSecondary, + TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine); + } } protected override void OnPaintBackground(PaintEventArgs e) @@ -245,6 +314,12 @@ namespace Elfisa.UI.Controls return; } + if (_themeToggleRect.Contains(e.Location)) + { + ThemeToggleClicked?.Invoke(this, EventArgs.Empty); + return; + } + var tabId = HitTestClose(e.Location); if (!string.IsNullOrEmpty(tabId)) { @@ -270,13 +345,23 @@ namespace Elfisa.UI.Controls Invalidate(); } + var toggleHover = _themeToggleRect.Contains(e.Location); + if (toggleHover != _themeToggleHover) + { + _themeToggleHover = toggleHover; + Invalidate(_themeToggleRect); + } + + Cursor = toggleHover || !string.IsNullOrEmpty(hovered) ? Cursors.Hand : Cursors.Default; base.OnMouseMove(e); } protected override void OnMouseLeave(EventArgs e) { _hoverTabId = null; - Invalidate(); + _themeToggleHover = false; + Invalidate(_themeToggleRect); + Cursor = Cursors.Default; base.OnMouseLeave(e); } diff --git a/src/Elfisa.UI/Helpers/WindowChromeHelper.cs b/src/Elfisa.UI/Helpers/WindowChromeHelper.cs new file mode 100644 index 0000000..6ca4fb9 --- /dev/null +++ b/src/Elfisa.UI/Helpers/WindowChromeHelper.cs @@ -0,0 +1,117 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using Elfisa.UI.Theming; + +namespace Elfisa.UI.Helpers +{ + public static class WindowChromeHelper + { + private const int DwmwaUseImmersiveDarkMode = 20; + private const int DwmwaUseImmersiveDarkModeLegacy = 19; + private const int DwmwaCaptionColor = 35; + private const int DwmwaTextColor = 36; + + private const uint SwpNomove = 0x0002; + private const uint SwpNosize = 0x0001; + private const uint SwpNozorder = 0x0004; + private const uint SwpFramechanged = 0x0020; + + [DllImport("dwmapi.dll", PreserveSig = true)] + private static extern int DwmSetWindowAttribute(IntPtr hwnd, int dwAttribute, ref int pvAttribute, int cbAttribute); + + [DllImport("dwmapi.dll", PreserveSig = true)] + private static extern int DwmSetWindowAttribute(IntPtr hwnd, int dwAttribute, ref uint pvAttribute, int cbAttribute); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool SetWindowPos( + IntPtr hWnd, + IntPtr hWndInsertAfter, + int x, + int y, + int cx, + int cy, + uint uFlags); + + public static void ApplyTitleBarTheme(Form form, ThemeColors theme) + { + if (form == null || theme == null || !form.IsHandleCreated) + { + return; + } + + var isDark = theme.Mode == ThemeMode.Dark; + var useDarkMode = isDark ? 1 : 0; + TrySetInt(form.Handle, DwmwaUseImmersiveDarkMode, useDarkMode); + TrySetInt(form.Handle, DwmwaUseImmersiveDarkModeLegacy, useDarkMode); + + var caption = isDark ? theme.TitleBarTop : Color.White; + var text = isDark ? Color.White : Color.Black; + + TrySetUInt(form.Handle, DwmwaCaptionColor, ColorToDwm(caption)); + TrySetUInt(form.Handle, DwmwaTextColor, ColorToDwm(text)); + + RefreshNonClientArea(form); + } + + public static void ApplyThemedDialogHeader(Panel headerPanel, Label headerLabel, ThemeColors theme) + { + if (theme == null) + { + return; + } + + var isDark = theme.Mode == ThemeMode.Dark; + if (headerPanel != null) + { + headerPanel.BackColor = isDark ? theme.TitleBarTop : Color.White; + } + + if (headerLabel != null) + { + headerLabel.ForeColor = isDark ? Color.White : Color.Black; + } + } + + public static void ApplyDialogChrome(Form form, Panel headerPanel = null, Label headerLabel = null) + { + if (form == null) + { + return; + } + + var theme = ThemeManager.Colors; + ApplyThemedDialogHeader(headerPanel, headerLabel, theme); + + if (form.IsHandleCreated) + { + ApplyTitleBarTheme(form, theme); + } + else + { + form.HandleCreated += (_, __) => ApplyTitleBarTheme(form, ThemeManager.Colors); + } + } + + private static uint ColorToDwm(Color color) + { + return (uint)(color.R | (color.G << 8) | (color.B << 16)); + } + + private static void RefreshNonClientArea(Form form) + { + SetWindowPos(form.Handle, IntPtr.Zero, 0, 0, 0, 0, SwpNomove | SwpNosize | SwpNozorder | SwpFramechanged); + } + + private static void TrySetInt(IntPtr handle, int attribute, int value) + { + DwmSetWindowAttribute(handle, attribute, ref value, sizeof(int)); + } + + private static void TrySetUInt(IntPtr handle, int attribute, uint value) + { + DwmSetWindowAttribute(handle, attribute, ref value, sizeof(uint)); + } + } +} diff --git a/src/Elfisa.UI/Theming/DarkProductivityPalette.cs b/src/Elfisa.UI/Theming/DarkProductivityPalette.cs new file mode 100644 index 0000000..915b18d --- /dev/null +++ b/src/Elfisa.UI/Theming/DarkProductivityPalette.cs @@ -0,0 +1,83 @@ +using System.Drawing; + +namespace Elfisa.UI.Theming +{ + /// + /// Вариант H — Dark Productivity (ui-ux-pro-max: indigo accents, dark surfaces). + /// Макет: elfisa-design/desktop/variant-h-dark-productivity.html + /// + public sealed class DarkProductivityPalette : IThemePalette + { + public ThemeVariant Variant => ThemeVariant.DarkProductivity; + + public ThemeColors Colors { get; } = new ThemeColors + { + Name = "Dark Productivity", + Mode = ThemeMode.Dark, + BackgroundPrimary = Color.FromArgb(17, 24, 39), + BackgroundSecondary = Color.FromArgb(15, 23, 42), + Surface = Color.FromArgb(31, 41, 55), + SurfaceElevated = Color.FromArgb(37, 47, 63), + Border = Color.FromArgb(55, 65, 81), + BorderSubtle = Color.FromArgb(45, 55, 72), + TextPrimary = Color.FromArgb(243, 244, 246), + TextSecondary = Color.FromArgb(209, 213, 219), + TextMuted = Color.FromArgb(156, 163, 175), + TextOnAccent = Color.White, + AccentPrimary = Color.FromArgb(79, 70, 229), + AccentPrimaryHover = Color.FromArgb(67, 56, 202), + AccentPrimaryPressed = Color.FromArgb(55, 48, 163), + AccentPrimarySoft = Color.FromArgb(30, 27, 75), + AccentPrimaryBorder = Color.FromArgb(129, 140, 248), + AccentSecondary = Color.FromArgb(55, 65, 81), + Success = Color.FromArgb(52, 211, 153), + SuccessSoft = Color.FromArgb(6, 78, 59), + Warning = Color.FromArgb(251, 191, 36), + WarningSoft = Color.FromArgb(66, 32, 6), + Danger = Color.FromArgb(239, 68, 68), + DangerSoft = Color.FromArgb(69, 26, 26), + Info = Color.FromArgb(129, 140, 248), + InfoSoft = Color.FromArgb(30, 27, 75), + TitleBarTop = Color.FromArgb(15, 23, 42), + TitleBarBottom = Color.FromArgb(30, 27, 75), + NavBackground = Color.FromArgb(26, 35, 50), + CartHeaderBackground = Color.FromArgb(37, 47, 63), + ToolbarBackground = Color.FromArgb(26, 35, 50), + SectionHeaderBackground = Color.FromArgb(55, 65, 81), + SidebarBackground = Color.FromArgb(17, 24, 39), + SidebarItem = Color.FromArgb(156, 163, 175), + SidebarItemHover = Color.FromArgb(37, 48, 68), + SidebarItemActive = Color.FromArgb(79, 70, 229), + SidebarItemActiveText = Color.White, + GridHeaderBackground = Color.FromArgb(55, 65, 81), + GridHeaderForeground = Color.FromArgb(243, 244, 246), + GridRowBackground = Color.FromArgb(17, 24, 39), + GridRowAlternate = Color.FromArgb(31, 41, 55), + GridRowHover = Color.FromArgb(37, 48, 68), + GridRowSelected = Color.FromArgb(79, 70, 229), + GridRowSelectedText = Color.White, + GridBorder = Color.FromArgb(45, 55, 72), + InputBackground = Color.FromArgb(17, 24, 39), + InputBorder = Color.FromArgb(55, 65, 81), + InputBorderFocus = Color.FromArgb(129, 140, 248), + OverlayScrim = Color.FromArgb(180, 0, 0, 0), + Shadow = Color.FromArgb(115, 0, 0, 0), + ScrollbarThumb = Color.FromArgb(75, 85, 99), + ScrollbarTrack = Color.FromArgb(26, 35, 50), + CornerRadiusSmall = 6, + CornerRadiusMedium = 8, + GridRowHeight = 36, + GridHeaderHeight = 34 + }; + + public DarkProductivityPalette() + { + Colors.FontRegular12 = new Font("Segoe UI", 12f); + Colors.FontRegular13 = new Font("Segoe UI", 13f); + Colors.FontRegular14 = new Font("Segoe UI", 14f); + Colors.FontSemibold14 = new Font("Segoe UI Semibold", 14f); + Colors.FontSemibold16 = new Font("Segoe UI Semibold", 16f); + Colors.FontBold18 = new Font("Segoe UI", 18f, FontStyle.Bold); + } + } +} diff --git a/src/Elfisa.UI/Theming/ThemeManager.cs b/src/Elfisa.UI/Theming/ThemeManager.cs index 5d6e702..d9edcfb 100644 --- a/src/Elfisa.UI/Theming/ThemeManager.cs +++ b/src/Elfisa.UI/Theming/ThemeManager.cs @@ -13,7 +13,8 @@ namespace Elfisa.UI.Theming { ThemeVariant.LightOffice, new LightOfficePalette() }, { ThemeVariant.DarkRider, new DarkRiderPalette() }, { ThemeVariant.DarkFluent, new DarkFluentPalette() }, - { ThemeVariant.FluentWin11, new FluentWin11Palette() } + { ThemeVariant.FluentWin11, new FluentWin11Palette() }, + { ThemeVariant.DarkProductivity, new DarkProductivityPalette() } }; private static ThemeVariant _currentVariant = ThemeVariant.FluentWin11; @@ -40,7 +41,17 @@ namespace Elfisa.UI.Theming public static ThemeMode CurrentMode => Colors.Mode; public static IReadOnlyList AvailableVariants { get; } = - new[] { ThemeVariant.PharmaTrust, ThemeVariant.Apotheca, ThemeVariant.ClinicalPro, ThemeVariant.LightOffice, ThemeVariant.DarkRider, ThemeVariant.DarkFluent, ThemeVariant.FluentWin11 }; + new[] + { + ThemeVariant.PharmaTrust, + ThemeVariant.Apotheca, + ThemeVariant.ClinicalPro, + ThemeVariant.LightOffice, + ThemeVariant.DarkRider, + ThemeVariant.DarkFluent, + ThemeVariant.FluentWin11, + ThemeVariant.DarkProductivity + }; public static string GetVariantDisplayName(ThemeVariant variant) { @@ -52,5 +63,14 @@ namespace Elfisa.UI.Theming var next = ((int)CurrentVariant + 1) % AvailableVariants.Count; CurrentVariant = AvailableVariants[next]; } + + public static bool IsDarkProductivityActive => CurrentVariant == ThemeVariant.DarkProductivity; + + public static void ToggleProductivityTheme() + { + CurrentVariant = IsDarkProductivityActive + ? ThemeVariant.PharmaTrust + : ThemeVariant.DarkProductivity; + } } } diff --git a/src/Elfisa.UI/Theming/ThemeRenderer.cs b/src/Elfisa.UI/Theming/ThemeRenderer.cs index ff02593..9d4f107 100644 --- a/src/Elfisa.UI/Theming/ThemeRenderer.cs +++ b/src/Elfisa.UI/Theming/ThemeRenderer.cs @@ -1,3 +1,4 @@ +using System.Drawing; using System.Windows.Forms; using Elfisa.UI.Controls; @@ -13,6 +14,20 @@ namespace Elfisa.UI.Theming private static void ApplyRecursive(Control control, ThemeColors colors) { + if (control.Tag as string == "keep-bg") + { + ApplyChildren(control, colors); + return; + } + + if (control.Tag as string == "chrome-child") + { + control.BackColor = colors.BackgroundPrimary; + control.ForeColor = colors.TextPrimary; + ApplyChildren(control, colors); + return; + } + if (control is Form form) { form.BackColor = colors.BackgroundPrimary; @@ -90,6 +105,43 @@ namespace Elfisa.UI.Theming { uploadPanel.ApplyTheme(colors); } + else if (control is DataGridView gridView) + { + gridView.EnableHeadersVisualStyles = false; + gridView.BackgroundColor = colors.GridRowBackground; + gridView.GridColor = colors.GridBorder; + gridView.ColumnHeadersDefaultCellStyle.BackColor = colors.GridHeaderBackground; + gridView.ColumnHeadersDefaultCellStyle.ForeColor = colors.GridHeaderForeground; + gridView.DefaultCellStyle.BackColor = colors.GridRowBackground; + gridView.DefaultCellStyle.ForeColor = colors.TextPrimary; + gridView.DefaultCellStyle.SelectionBackColor = colors.GridRowSelected; + gridView.DefaultCellStyle.SelectionForeColor = colors.GridRowSelectedText; + gridView.AlternatingRowsDefaultCellStyle.BackColor = colors.GridRowAlternate; + } + else if (control is CheckBox checkBox) + { + checkBox.ForeColor = colors.TextPrimary; + checkBox.BackColor = Color.Transparent; + } + else if (control is NumericUpDown numericUpDown) + { + numericUpDown.BackColor = colors.InputBackground; + numericUpDown.ForeColor = colors.TextPrimary; + } + else if (control is TextBox legacyTextBox) + { + legacyTextBox.BackColor = colors.InputBackground; + legacyTextBox.ForeColor = colors.TextPrimary; + } + else if (control is TabControl tabControl) + { + tabControl.BackColor = colors.BackgroundSecondary; + tabControl.ForeColor = colors.TextPrimary; + } + else if (control is SplitContainer splitContainer) + { + splitContainer.BackColor = colors.BackgroundSecondary; + } else if (control.Tag as string == "toolbar") { control.BackColor = colors.ToolbarBackground; @@ -100,11 +152,6 @@ namespace Elfisa.UI.Theming } else if (control is Panel || control is UserControl || control is TableLayoutPanel || control is FlowLayoutPanel) { - if (control.Tag as string == "chrome-child" || control.Tag as string == "keep-bg") - { - return; - } - control.BackColor = colors.BackgroundSecondary; control.ForeColor = colors.TextPrimary; } @@ -117,6 +164,11 @@ namespace Elfisa.UI.Theming } } + ApplyChildren(control, colors); + } + + private static void ApplyChildren(Control control, ThemeColors colors) + { foreach (Control child in control.Controls) { ApplyRecursive(child, colors); diff --git a/src/Elfisa.UI/Theming/ThemeVariant.cs b/src/Elfisa.UI/Theming/ThemeVariant.cs index 01ce683..c171870 100644 --- a/src/Elfisa.UI/Theming/ThemeVariant.cs +++ b/src/Elfisa.UI/Theming/ThemeVariant.cs @@ -8,7 +8,8 @@ namespace Elfisa.UI.Theming LightOffice = 3, DarkRider = 4, DarkFluent = 5, - FluentWin11 = 6 + FluentWin11 = 6, + DarkProductivity = 7 } public enum ThemeMode