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 <cursoragent@cursor.com>
This commit is contained in:
Magomed 2026-07-14 19:53:30 +03:00
parent 2e1b13fde6
commit 7ed8822bd4
13 changed files with 483 additions and 25 deletions

View File

@ -103,9 +103,19 @@ namespace Электронная_Фармация.Classes
} }
else if (control is Panel || control is UserControl || control is TableLayoutPanel || control is SplitContainer) 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.BackColor = theme.BackgroundSecondary;
control.ForeColor = theme.TextPrimary;
} }
} }

View File

@ -4,6 +4,7 @@ using System.Drawing;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using Elfisa.UI.Controls; using Elfisa.UI.Controls;
using Elfisa.UI.Helpers;
using Elfisa.UI.Loading; using Elfisa.UI.Loading;
using Elfisa.UI.Notifications; using Elfisa.UI.Notifications;
using Elfisa.UI.Theming; using Elfisa.UI.Theming;
@ -112,6 +113,7 @@ namespace Электронная_Фармация
}); });
_appHeader.ItemSelected += OnAppHeaderItemSelected; _appHeader.ItemSelected += OnAppHeaderItemSelected;
_documentTabStrip.ThemeToggleClicked += OnThemeToggleClicked;
_documentTabStrip.TabSelected += OnDocumentTabSelected; _documentTabStrip.TabSelected += OnDocumentTabSelected;
_documentTabStrip.TabClosed += OnDocumentTabClosed; _documentTabStrip.TabClosed += OnDocumentTabClosed;
_documentTabStrip.TabCloseAllRequested += OnDocumentTabCloseAll; _documentTabStrip.TabCloseAllRequested += OnDocumentTabCloseAll;
@ -133,14 +135,41 @@ namespace Электронная_Фармация
_documentTabMenu.Items.Add(_documentCloseTabMenuItem); _documentTabMenu.Items.Add(_documentCloseTabMenuItem);
_documentTabMenu.Items.Add(_documentCloseAllTabsMenuItem); _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() partial void FinalizeModernShell()
{ {
ThemeManager.CurrentVariant = ThemeVariant.PharmaTrust; ThemeManager.CurrentVariant = Settings.Default.UseDarkTheme
? ThemeVariant.DarkProductivity
: ThemeVariant.PharmaTrust;
_themeService.Initialize(this); _themeService.Initialize(this);
UpdateThemeToggleState();
UiThemeHelper.ApplyToControlTree(this); UiThemeHelper.ApplyToControlTree(this);
WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
RefreshCurrentUserStatus(); RefreshCurrentUserStatus();
} }

View File

@ -12,6 +12,8 @@ using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Электроннаяармация.Classes; using Электроннаяармация.Classes;
using System.Net.Http; using System.Net.Http;
using Elfisa.UI.Helpers;
using Elfisa.UI.Theming;
namespace Электроннаяармация.HelpForms namespace Электроннаяармация.HelpForms
{ {
@ -20,6 +22,7 @@ namespace Электронная_Фармация.HelpForms
public HF_Registration() public HF_Registration()
{ {
InitializeComponent(); InitializeComponent();
Shown += (_, __) => WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
} }
string login= Settings.Default.stringLogin.ToString(); string login= Settings.Default.stringLogin.ToString();
@ -91,7 +94,7 @@ namespace Электронная_Фармация.HelpForms
private void HF_Registration_Load(object sender, EventArgs e) private void HF_Registration_Load(object sender, EventArgs e)
{ {
UiThemeHelper.ApplyToControlTree(this); UiThemeHelper.ApplyToControlTree(this);
panel1.BackColor = Elfisa.UI.Theming.ThemeManager.Colors.TitleBarBottom; WindowChromeHelper.ApplyDialogChrome(this, panel1, label3);
txtLogin.Text = login; txtLogin.Text = login;
txtPassword.Text = password; txtPassword.Text = password;
txtLocationId.Text = Settings.Default.LocationId ?? string.Empty; txtLocationId.Text = Settings.Default.LocationId ?? string.Empty;

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using Elfisa.UI.Theming;
namespace Электроннаяармация namespace Электроннаяармация
{ {
@ -11,7 +10,6 @@ namespace Электронная_Фармация
{ {
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
ThemeManager.CurrentVariant = ThemeVariant.PharmaTrust;
Application.Run(new ElectroPharmacy()); Application.Run(new ElectroPharmacy());
} }
} }

View File

@ -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;
}
}
} }
} }

View File

@ -23,5 +23,8 @@
<Setting Name="RegionId" Type="System.String" Scope="User"> <Setting Name="RegionId" Type="System.String" Scope="User">
<Value Profile="(Default)" /> <Value Profile="(Default)" />
</Setting> </Setting>
<Setting Name="UseDarkTheme" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
</Settings> </Settings>
</SettingsFile> </SettingsFile>

View File

@ -8,6 +8,7 @@ namespace Elfisa.UI.Controls
public class ModernDataGridView : DataGridView public class ModernDataGridView : DataGridView
{ {
private ThemeColors _theme; private ThemeColors _theme;
private int? _hoverRowIndex;
public ModernDataGridView() public ModernDataGridView()
{ {
@ -24,6 +25,7 @@ namespace Elfisa.UI.Controls
ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing; ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single; ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single;
DoubleBuffered = true; DoubleBuffered = true;
CellFormatting += OnGridCellFormatting;
ApplyTheme(ThemeManager.Colors); ApplyTheme(ThemeManager.Colors);
} }
@ -64,30 +66,73 @@ namespace Elfisa.UI.Controls
Font = theme.FontRegular13, Font = theme.FontRegular13,
Padding = new Padding(12, 0, 12, 0) 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) protected override void OnCellMouseEnter(DataGridViewCellEventArgs e)
{ {
base.OnCellMouseEnter(e); base.OnCellMouseEnter(e);
if (e.RowIndex >= 0 && e.RowIndex != CurrentCell?.RowIndex) if (e.RowIndex < 0 || e.RowIndex == _hoverRowIndex)
{ {
var theme = _theme ?? ThemeManager.Colors; return;
Rows[e.RowIndex].DefaultCellStyle.BackColor = theme.GridRowHover;
} }
var previous = _hoverRowIndex;
_hoverRowIndex = e.RowIndex;
if (previous.HasValue)
{
InvalidateRow(previous.Value);
}
InvalidateRow(e.RowIndex);
} }
protected override void OnCellMouseLeave(DataGridViewCellEventArgs e) protected override void OnCellMouseLeave(DataGridViewCellEventArgs e)
{ {
base.OnCellMouseLeave(e); base.OnCellMouseLeave(e);
if (e.RowIndex >= 0) if (e.RowIndex < 0 || _hoverRowIndex != e.RowIndex)
{ {
var theme = _theme ?? ThemeManager.Colors; return;
var isAlt = e.RowIndex % 2 == 1;
Rows[e.RowIndex].DefaultCellStyle.BackColor = isAlt
? theme.GridRowAlternate
: theme.GridRowBackground;
} }
_hoverRowIndex = null;
InvalidateRow(e.RowIndex);
} }
} }
} }

View File

@ -27,10 +27,29 @@ namespace Elfisa.UI.Controls
private ThemeColors _theme; private ThemeColors _theme;
private string _activeTabId; private string _activeTabId;
private string _hoverTabId; private string _hoverTabId;
private bool _themeToggleHover;
private bool _isDarkTheme;
private Rectangle _themeToggleRect = Rectangle.Empty;
public event EventHandler<string> TabSelected; public event EventHandler<string> TabSelected;
public event EventHandler<string> TabClosed; public event EventHandler<string> TabClosed;
public event EventHandler TabCloseAllRequested; 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 ContextMenuStrip _contextMenu;
private readonly ToolStripMenuItem _closeTabMenuItem; private readonly ToolStripMenuItem _closeTabMenuItem;
@ -62,6 +81,13 @@ namespace Elfisa.UI.Controls
_contextMenu = new ContextMenuStrip(); _contextMenu = new ContextMenuStrip();
_contextMenu.Items.Add(_closeTabMenuItem); _contextMenu.Items.Add(_closeTabMenuItem);
_contextMenu.Items.Add(_closeAllTabsMenuItem); _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<DocumentTabInfo> Tabs => _tabs; public IReadOnlyList<DocumentTabInfo> Tabs => _tabs;
@ -140,6 +166,7 @@ namespace Elfisa.UI.Controls
public void ApplyTheme(ThemeColors theme) public void ApplyTheme(ThemeColors theme)
{ {
_theme = theme; _theme = theme;
UpdateThemeToggleRect();
Invalidate(); Invalidate();
} }
@ -168,6 +195,8 @@ namespace Elfisa.UI.Controls
const int minTabWidth = 140; const int minTabWidth = 140;
const int gap = 8; const int gap = 8;
const int closeSize = 18; 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 textFont = theme.FontSemibold14 ?? new Font("Segoe UI", 10f, FontStyle.Bold))
using (var closeFont = new Font("Segoe MDL2 Assets", 10f, FontStyle.Regular)) 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 textSize = TextRenderer.MeasureText(tab.Title, textFont);
var tabWidth = Math.Max(minTabWidth, textSize.Width + 48); var tabWidth = Math.Max(minTabWidth, textSize.Width + 48);
if (x + tabWidth > maxTabRight)
{
break;
}
var tabRect = new Rectangle(x, tabTop, tabWidth, tabHeight); var tabRect = new Rectangle(x, tabTop, tabWidth, tabHeight);
_hitAreas[tab.Id] = tabRect; _hitAreas[tab.Id] = tabRect;
@ -232,6 +266,41 @@ namespace Elfisa.UI.Controls
x += tabWidth + gap; 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) protected override void OnPaintBackground(PaintEventArgs e)
@ -245,6 +314,12 @@ namespace Elfisa.UI.Controls
return; return;
} }
if (_themeToggleRect.Contains(e.Location))
{
ThemeToggleClicked?.Invoke(this, EventArgs.Empty);
return;
}
var tabId = HitTestClose(e.Location); var tabId = HitTestClose(e.Location);
if (!string.IsNullOrEmpty(tabId)) if (!string.IsNullOrEmpty(tabId))
{ {
@ -270,13 +345,23 @@ namespace Elfisa.UI.Controls
Invalidate(); 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); base.OnMouseMove(e);
} }
protected override void OnMouseLeave(EventArgs e) protected override void OnMouseLeave(EventArgs e)
{ {
_hoverTabId = null; _hoverTabId = null;
Invalidate(); _themeToggleHover = false;
Invalidate(_themeToggleRect);
Cursor = Cursors.Default;
base.OnMouseLeave(e); base.OnMouseLeave(e);
} }

View File

@ -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));
}
}
}

View File

@ -0,0 +1,83 @@
using System.Drawing;
namespace Elfisa.UI.Theming
{
/// <summary>
/// Вариант H — Dark Productivity (ui-ux-pro-max: indigo accents, dark surfaces).
/// Макет: elfisa-design/desktop/variant-h-dark-productivity.html
/// </summary>
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);
}
}
}

View File

@ -13,7 +13,8 @@ namespace Elfisa.UI.Theming
{ ThemeVariant.LightOffice, new LightOfficePalette() }, { ThemeVariant.LightOffice, new LightOfficePalette() },
{ ThemeVariant.DarkRider, new DarkRiderPalette() }, { ThemeVariant.DarkRider, new DarkRiderPalette() },
{ ThemeVariant.DarkFluent, new DarkFluentPalette() }, { ThemeVariant.DarkFluent, new DarkFluentPalette() },
{ ThemeVariant.FluentWin11, new FluentWin11Palette() } { ThemeVariant.FluentWin11, new FluentWin11Palette() },
{ ThemeVariant.DarkProductivity, new DarkProductivityPalette() }
}; };
private static ThemeVariant _currentVariant = ThemeVariant.FluentWin11; private static ThemeVariant _currentVariant = ThemeVariant.FluentWin11;
@ -40,7 +41,17 @@ namespace Elfisa.UI.Theming
public static ThemeMode CurrentMode => Colors.Mode; public static ThemeMode CurrentMode => Colors.Mode;
public static IReadOnlyList<ThemeVariant> AvailableVariants { get; } = public static IReadOnlyList<ThemeVariant> 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) public static string GetVariantDisplayName(ThemeVariant variant)
{ {
@ -52,5 +63,14 @@ namespace Elfisa.UI.Theming
var next = ((int)CurrentVariant + 1) % AvailableVariants.Count; var next = ((int)CurrentVariant + 1) % AvailableVariants.Count;
CurrentVariant = AvailableVariants[next]; CurrentVariant = AvailableVariants[next];
} }
public static bool IsDarkProductivityActive => CurrentVariant == ThemeVariant.DarkProductivity;
public static void ToggleProductivityTheme()
{
CurrentVariant = IsDarkProductivityActive
? ThemeVariant.PharmaTrust
: ThemeVariant.DarkProductivity;
}
} }
} }

View File

@ -1,3 +1,4 @@
using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using Elfisa.UI.Controls; using Elfisa.UI.Controls;
@ -13,6 +14,20 @@ namespace Elfisa.UI.Theming
private static void ApplyRecursive(Control control, ThemeColors colors) 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) if (control is Form form)
{ {
form.BackColor = colors.BackgroundPrimary; form.BackColor = colors.BackgroundPrimary;
@ -90,6 +105,43 @@ namespace Elfisa.UI.Theming
{ {
uploadPanel.ApplyTheme(colors); 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") else if (control.Tag as string == "toolbar")
{ {
control.BackColor = colors.ToolbarBackground; 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) 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.BackColor = colors.BackgroundSecondary;
control.ForeColor = colors.TextPrimary; 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) foreach (Control child in control.Controls)
{ {
ApplyRecursive(child, colors); ApplyRecursive(child, colors);

View File

@ -8,7 +8,8 @@ namespace Elfisa.UI.Theming
LightOffice = 3, LightOffice = 3,
DarkRider = 4, DarkRider = 4,
DarkFluent = 5, DarkFluent = 5,
FluentWin11 = 6 FluentWin11 = 6,
DarkProductivity = 7
} }
public enum ThemeMode public enum ThemeMode