diff --git a/CreamInstaller/Components/CustomTreeView.cs b/CreamInstaller/Components/CustomTreeView.cs index 28fcfcd..6e07ee4 100644 --- a/CreamInstaller/Components/CustomTreeView.cs +++ b/CreamInstaller/Components/CustomTreeView.cs @@ -21,6 +21,11 @@ internal sealed class CustomTreeView : TreeView private readonly Dictionary selectionBounds = []; private SolidBrush backBrush; private Color lastBackColor; // Tracks the last background color + + // Selection background brush (used instead of SystemBrushes.Highlight to support dark mode) + private SolidBrush selectionBrush; + private Color lastSelectionBackColor; + private ToolStripDropDown comboBoxDropDown; private Font comboBoxFont; private Form form; @@ -46,6 +51,8 @@ internal sealed class CustomTreeView : TreeView { backBrush?.Dispose(); backBrush = null; + selectionBrush?.Dispose(); + selectionBrush = null; comboBoxFont?.Dispose(); comboBoxFont = null; comboBoxDropDown?.Dispose(); @@ -60,6 +67,10 @@ internal sealed class CustomTreeView : TreeView backBrush?.Dispose(); backBrush = null; lastBackColor = Color.Empty; + + selectionBrush?.Dispose(); + selectionBrush = null; + lastSelectionBackColor = Color.Empty; } private void DrawTreeNode(object sender, DrawTreeNodeEventArgs e) @@ -71,17 +82,29 @@ internal sealed class CustomTreeView : TreeView bool highlighted = (e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected && Focused; Graphics graphics = e.Graphics; - - // Recreate brush if background color changed + + // Recreate back brush if background color changed if (backBrush == null || lastBackColor != BackColor) { backBrush?.Dispose(); backBrush = new(BackColor); lastBackColor = BackColor; } - + + // If highlighted, prepare a selection brush that respects the theme + if (highlighted) + { + Color selColor = ThemeManager.CustomTreeViewSelectionBackColor; + if (selectionBrush == null || lastSelectionBackColor != selColor) + { + selectionBrush?.Dispose(); + selectionBrush = new(selColor); + lastSelectionBackColor = selColor; + } + } + Font font = node.NodeFont ?? Font; - Brush brush = highlighted ? SystemBrushes.Highlight : backBrush; + Brush brush = highlighted ? (Brush)selectionBrush : backBrush; Rectangle bounds = node.Bounds; Rectangle selectionBounds = bounds; @@ -166,8 +189,8 @@ internal sealed class CustomTreeView : TreeView checkBoxBounds = new(checkBoxBounds.Location, checkBoxBounds.Size + bounds.Size with { Height = 0 }); graphics.FillRectangle(backBrush, bounds); point = new(bounds.Location.X - 1 + left, bounds.Location.Y + 1); - TextRenderer.DrawText(graphics, text, font, point, - Enabled ? ThemeManager.CustomTreeViewProxyColor : ThemeManager.CustomTreeViewDisabledProxyColor, + TextRenderer.DrawText(graphics, text, font, point, + Enabled ? ThemeManager.CustomTreeViewProxyColor : ThemeManager.CustomTreeViewDisabledProxyColor, TextFormatFlags.Default); this.checkBoxBounds[selection] = RectangleToClient(checkBoxBounds); @@ -189,16 +212,9 @@ internal sealed class CustomTreeView : TreeView selectionBounds = new(selectionBounds.Location, selectionBounds.Size + bounds.Size with { Height = 0 }); Rectangle comboBoxBounds = bounds; - - // Draw custom themed combobox - using (SolidBrush comboBrush = new(comboBackColor)) - using (Pen borderPen = new(comboBorderColor)) - { - graphics.FillRectangle(comboBrush, bounds); - graphics.DrawRectangle(borderPen, bounds); - point = new(bounds.Location.X + 3, bounds.Location.Y + bounds.Height / 2 - size.Height / 2); - TextRenderer.DrawText(graphics, text, comboBoxFont, point, comboTextColor, TextFormatFlags.Default); - } + + // Themed combobox background + text (centralized in ThemeManager) + ThemeManager.DrawCustomComboBox(graphics, bounds, comboBoxFont, text); size = new(14, 0); left = -1; @@ -207,28 +223,9 @@ internal sealed class CustomTreeView : TreeView selectionBounds.Size + new Size(bounds.Size.Width + left, 0)); comboBoxBounds = new(comboBoxBounds.Location, comboBoxBounds.Size + new Size(bounds.Size.Width + left, 0)); - - // Draw custom themed dropdown button - using (SolidBrush comboBrush = new(comboBackColor)) - using (Pen borderPen = new(comboBorderColor)) - { - graphics.FillRectangle(comboBrush, bounds); - graphics.DrawRectangle(borderPen, bounds); - - // Draw arrow - int arrowSize = 3; - Point arrowTop = new(bounds.X + bounds.Width / 2, bounds.Y + bounds.Height / 2 - 1); - Point[] arrowPoints = new[] - { - arrowTop, - new Point(arrowTop.X - arrowSize, arrowTop.Y - arrowSize), - new Point(arrowTop.X + arrowSize, arrowTop.Y - arrowSize) - }; - using (SolidBrush arrowBrush = new(comboTextColor)) - { - graphics.FillPolygon(arrowBrush, arrowPoints); - } - } + + // Themed combobox dropdown button (centralized in ThemeManager) + ThemeManager.DrawCustomComboBoxButton(graphics, bounds); this.comboBoxBounds[selection] = RectangleToClient(comboBoxBounds); } @@ -270,7 +267,7 @@ internal sealed class CustomTreeView : TreeView comboBoxDropDown ??= new(); comboBoxDropDown.ShowItemToolTips = false; comboBoxDropDown.Items.Clear(); - + foreach (string proxy in proxies) { bool canUse = true; diff --git a/CreamInstaller/Utility/ThemeManager.cs b/CreamInstaller/Utility/ThemeManager.cs index 57f312f..d0424d8 100644 --- a/CreamInstaller/Utility/ThemeManager.cs +++ b/CreamInstaller/Utility/ThemeManager.cs @@ -7,7 +7,13 @@ namespace CreamInstaller.Utility; internal static class ThemeManager { - // VS-like dark colors + // ----------------------------------------------------------------- + // Color definitions (do not change values) + // ----------------------------------------------------------------- + + // ---------------------------- + // Dark mode colors + // ---------------------------- private static readonly Color DarkBack = ColorTranslator.FromHtml("#1E1E1E"); private static readonly Color DarkBackAlt = ColorTranslator.FromHtml("#252525"); private static readonly Color DarkBorder = ColorTranslator.FromHtml("#3F3F46"); @@ -15,171 +21,249 @@ internal static class ThemeManager private static readonly Color DarkForeDim = ColorTranslator.FromHtml("#9CA3AF"); private static readonly Color Accent = ColorTranslator.FromHtml("#0E639C"); private static readonly Color DarkLink = ColorTranslator.FromHtml("#64B5F6"); + + // CustomTreeView dark-mode specific colors + private static readonly Color DarkPlatform = ColorTranslator.FromHtml("#FFFF99"); + private static readonly Color DarkId = ColorTranslator.FromHtml("#99FFFF"); + private static readonly Color DarkProxy = ColorTranslator.FromHtml("#99FF99"); + private static readonly Color DarkSelectionBack = ColorTranslator.FromHtml("#2A2D2E"); + private static readonly Color DarkComboBack = DarkBackAlt; // #252525 + private static readonly Color DarkComboBorder = DarkBorder; // #3F3F46 + private static readonly Color DarkComboText = DarkFore; // #D4D4D4 + + // ---------------------------- + // Light mode colors (system defaults) + // ---------------------------- private static readonly Color LightBack = SystemColors.Control; private static readonly Color LightBackAlt = SystemColors.ControlLightLight; private static readonly Color LightFore = SystemColors.ControlText; private static readonly Color LightBorder = SystemColors.ControlDark; - internal static Color CustomTreeViewPlatformColor => Program.DarkModeEnabled - ? ColorTranslator.FromHtml("#FFFF99") // Light yellow for dark mode - : ColorTranslator.FromHtml("#696900"); // Dark yellow for light mode + // CustomTreeView light-mode specific colors + private static readonly Color LightPlatform = ColorTranslator.FromHtml("#696900"); + private static readonly Color LightId = ColorTranslator.FromHtml("#006969"); + private static readonly Color LightProxy = ColorTranslator.FromHtml("#006900"); + private static readonly Color LightSelectionBack = SystemColors.Highlight; + private static readonly Color LightComboBack = SystemColors.Control; + private static readonly Color LightComboBorder = SystemColors.ControlDark; + private static readonly Color LightComboText = SystemColors.ControlText; - internal static Color CustomTreeViewIdColor => Program.DarkModeEnabled - ? ColorTranslator.FromHtml("#99FFFF") // Light cyan for dark mode - : ColorTranslator.FromHtml("#006969"); // Dark cyan for light mode + // ----------------------------------------------------------------- + // Theme-aware properties used by other components (CustomTreeView etc.) + // ----------------------------------------------------------------- - internal static Color CustomTreeViewProxyColor => Program.DarkModeEnabled - ? ColorTranslator.FromHtml("#99FF99") // Light green for dark mode - : ColorTranslator.FromHtml("#006900"); // Dark green for light mode + internal static bool IsDark => Program.DarkModeEnabled; - internal static Color CustomTreeViewHighlightPlatformColor => ColorTranslator.FromHtml("#FFFF99"); // C1 + internal static Color CustomTreeViewPlatformColor => IsDark ? DarkPlatform : LightPlatform; + + internal static Color CustomTreeViewIdColor => IsDark ? DarkId : LightId; + + internal static Color CustomTreeViewProxyColor => IsDark ? DarkProxy : LightProxy; + + internal static Color CustomTreeViewHighlightPlatformColor => DarkPlatform; // C1 (uses same color for highlight) internal static Color CustomTreeViewDisabledPlatformColor => ColorTranslator.FromHtml("#AAAA69"); // C3 - internal static Color CustomTreeViewHighlightIdColor => ColorTranslator.FromHtml("#99FFFF"); // C4 + internal static Color CustomTreeViewHighlightIdColor => DarkId; // C4 internal static Color CustomTreeViewDisabledIdColor => ColorTranslator.FromHtml("#69AAAA"); // C6 internal static Color CustomTreeViewDisabledProxyColor => ColorTranslator.FromHtml("#69AA69"); // C8 - internal static Color CustomTreeViewComboBackColor => Program.DarkModeEnabled - ? DarkBackAlt // #252525 - : SystemColors.Control; + // Background color used when a tree node is selected. + // Keeps light-mode behavior using the system highlight, but supplies a custom dark color for dark mode + internal static Color CustomTreeViewSelectionBackColor => IsDark ? DarkSelectionBack : LightSelectionBack; - internal static Color CustomTreeViewComboBorderColor => Program.DarkModeEnabled - ? DarkBorder // #3F3F46 - : SystemColors.ControlDark; + internal static Color CustomTreeViewComboBackColor => IsDark ? DarkComboBack : LightComboBack; - internal static Color CustomTreeViewComboTextColor => Program.DarkModeEnabled - ? DarkFore // #D4D4D4 - : SystemColors.ControlText; + internal static Color CustomTreeViewComboBorderColor => IsDark ? DarkComboBorder : LightComboBorder; + internal static Color CustomTreeViewComboTextColor => IsDark ? DarkComboText : LightComboText; + + // ----------------------------------------------------------------- + // Public / Internal API + // ----------------------------------------------------------------- + + /// + /// Toggle dark mode and re-apply theming to all open forms. + /// internal static void ToggleDarkMode(Form anyForm) { Program.DarkModeEnabled = !Program.DarkModeEnabled; ApplyToAllOpenForms(); } + /// + /// Apply current theme to a single form and its child controls. + /// internal static void Apply(Form form) { if (form is null) return; - if (!Program.DarkModeEnabled) + if (!IsDark) { Reset(form); return; } + form.SuspendLayout(); form.BackColor = DarkBack; form.ForeColor = DarkFore; ApplyTitleBar(form); + foreach (Control c in form.Controls) ApplyControlTheme(c, true); + form.ResumeLayout(true); } + /// + /// Apply the theme to all currently open forms. + /// + internal static void ApplyToAllOpenForms() + { + foreach (Form openForm in Application.OpenForms.Cast
()) + Apply(openForm); + } + + // ----------------------------------------------------------------- + // Control theming helpers + // ----------------------------------------------------------------- + + /// + /// Apply theming to a control tree. Entry point which recurses children + /// then applies either the dark or light styling logic. + /// private static void ApplyControlTheme(Control control, bool dark) { if (control is null) return; + + // Recurse first so parent layering still works correctly foreach (Control child in control.Controls) ApplyControlTheme(child, dark); + if (dark) - { - switch (control) - { - case GroupBox gb: - gb.ForeColor = DarkFore; - gb.BackColor = DarkBackAlt; - break; - case Button b: - b.FlatStyle = FlatStyle.Flat; - b.FlatAppearance.BorderColor = DarkBorder; - b.BackColor = DarkBackAlt; - b.ForeColor = DarkFore; - break; - case CheckBox cb: - cb.BackColor = DarkBack; - cb.ForeColor = DarkFore; - break; - case LinkLabel ll: - ll.BackColor = DarkBack; - ll.ForeColor = DarkFore; - ll.LinkColor = DarkLink; - ll.ActiveLinkColor = Color.White; - ll.VisitedLinkColor = DarkLink; - break; - case Label lbl: - lbl.BackColor = DarkBack; - lbl.ForeColor = DarkFore; - break; - case ProgressBar pb: - pb.ForeColor = Accent; - pb.BackColor = DarkBackAlt; - break; - case TreeView tv: - tv.BackColor = DarkBackAlt; - tv.ForeColor = DarkFore; - tv.LineColor = DarkBorder; - tv.Invalidate(); // Forces a redraw - break; - case RichTextBox rtb: - rtb.BackColor = DarkBackAlt; - rtb.ForeColor = DarkFore; - break; - case TableLayoutPanel tlp: - tlp.BackColor = DarkBack; - break; - case FlowLayoutPanel flp: - flp.BackColor = DarkBack; - break; - } - TryApplyScrollbarTheme(control, true); - } + ApplyDarkControl(control); else + ApplyLightControl(control); + + // Try to apply themed scrollbars where applicable + TryApplyScrollbarTheme(control, dark); + } + + // Separated dark/light cases to make the intent clearer and reduce duplication + private static void ApplyDarkControl(Control control) + { + switch (control) { - switch (control) - { - case GroupBox gb: - gb.BackColor = LightBack; - gb.ForeColor = LightFore; - break; - case Button b: - b.FlatStyle = FlatStyle.Standard; - b.BackColor = LightBack; - b.ForeColor = LightFore; - break; - case CheckBox cb: - cb.BackColor = LightBack; - cb.ForeColor = LightFore; - break; - case LinkLabel ll: - ll.BackColor = LightBack; - ll.ForeColor = LightFore; - ll.LinkColor = SystemColors.HotTrack; - ll.ActiveLinkColor = SystemColors.Highlight; - ll.VisitedLinkColor = SystemColors.HotTrack; - break; - case Label lbl: - lbl.BackColor = LightBack; - lbl.ForeColor = LightFore; - break; - case ProgressBar pb: - pb.BackColor = LightBack; - pb.ForeColor = LightFore; - break; - case TreeView tv: - tv.BackColor = LightBack; - tv.ForeColor = LightFore; - tv.LineColor = LightBorder; - tv.Invalidate(); // Forces a redraw - break; - case RichTextBox rtb: - rtb.BackColor = LightBack; - rtb.ForeColor = LightFore; - break; - case TableLayoutPanel tlp: - tlp.BackColor = LightBack; - break; - case FlowLayoutPanel flp: - flp.BackColor = LightBack; - break; - } - TryApplyScrollbarTheme(control, false); + // Group box background/foreground + case GroupBox gb: + gb.ForeColor = DarkFore; + gb.BackColor = DarkBackAlt; + break; + + // Buttons: flat appearance, border and foreground + case Button b: + b.FlatStyle = FlatStyle.Flat; + b.FlatAppearance.BorderColor = DarkBorder; + b.BackColor = DarkBackAlt; + b.ForeColor = DarkFore; + break; + + // Checkboxes: match form background and foreground + case CheckBox cb: + cb.BackColor = DarkBack; + cb.ForeColor = DarkFore; + break; + + // LinkLabel: color and active/visited styling + case LinkLabel ll: + ll.BackColor = DarkBack; + ll.ForeColor = DarkFore; + ll.LinkColor = DarkLink; + ll.ActiveLinkColor = Color.White; + ll.VisitedLinkColor = DarkLink; + break; + + // Labels: dark background, light foreground + case Label lbl: + lbl.BackColor = DarkBack; + lbl.ForeColor = DarkFore; + break; + + // ProgressBar uses accent color for foreground + case ProgressBar pb: + pb.ForeColor = Accent; + pb.BackColor = DarkBackAlt; + break; + + // TreeView: darker alternate background, light text, darker lines + case TreeView tv: + tv.BackColor = DarkBackAlt; + tv.ForeColor = DarkFore; + tv.LineColor = DarkBorder; + tv.Invalidate(); // Forces a redraw + break; + + // RichTextBox follows alternate dark background + case RichTextBox rtb: + rtb.BackColor = DarkBackAlt; + rtb.ForeColor = DarkFore; + break; + + // Layout panels set a consistent background + case TableLayoutPanel tlp: + tlp.BackColor = DarkBack; + break; + case FlowLayoutPanel flp: + flp.BackColor = DarkBack; + break; + } + } + + private static void ApplyLightControl(Control control) + { + switch (control) + { + case GroupBox gb: + gb.BackColor = LightBack; + gb.ForeColor = LightFore; + break; + case Button b: + b.FlatStyle = FlatStyle.Standard; + b.BackColor = LightBack; + b.ForeColor = LightFore; + break; + case CheckBox cb: + cb.BackColor = LightBack; + cb.ForeColor = LightFore; + break; + case LinkLabel ll: + ll.BackColor = LightBack; + ll.ForeColor = LightFore; + ll.LinkColor = SystemColors.HotTrack; + ll.ActiveLinkColor = SystemColors.Highlight; + ll.VisitedLinkColor = SystemColors.HotTrack; + break; + case Label lbl: + lbl.BackColor = LightBack; + lbl.ForeColor = LightFore; + break; + case ProgressBar pb: + pb.BackColor = LightBack; + pb.ForeColor = LightFore; + break; + case TreeView tv: + tv.BackColor = LightBack; + tv.ForeColor = LightFore; + tv.LineColor = LightBorder; + tv.Invalidate(); // Forces a redraw + break; + case RichTextBox rtb: + rtb.BackColor = LightBack; + rtb.ForeColor = LightFore; + break; + case TableLayoutPanel tlp: + tlp.BackColor = LightBack; + break; + case FlowLayoutPanel flp: + flp.BackColor = LightBack; + break; } } @@ -194,17 +278,15 @@ internal static class ThemeManager form.ResumeLayout(true); } - internal static void ApplyToAllOpenForms() - { - foreach (Form openForm in Application.OpenForms.Cast()) - Apply(openForm); - } + // ----------------------------------------------------------------- + // Titlebar / platform-specific helpers + // ----------------------------------------------------------------- private static void ApplyTitleBar(Form form) { try { - int useDark = Program.DarkModeEnabled ? 1 : 0; + int useDark = IsDark ? 1 : 0; NativeMethods.EnableDarkTitleBar(form.Handle, useDark); } catch { } @@ -220,12 +302,18 @@ internal static class ThemeManager catch { } } - // Right Click context menu styling + // ----------------------------------------------------------------- + // Context menu / ToolStrip theming + // ----------------------------------------------------------------- + + /// + /// Apply theme to a context menu (ContextMenuStrip). + /// internal static void ApplyContextMenu(ContextMenuStrip contextMenu) { if (contextMenu is null) return; - bool dark = Program.DarkModeEnabled; + bool dark = IsDark; contextMenu.BackColor = dark ? DarkBackAlt : SystemColors.Menu; contextMenu.ForeColor = dark ? DarkFore : SystemColors.MenuText; @@ -251,7 +339,7 @@ internal static class ThemeManager { if (dropDown is null) return; - bool dark = Program.DarkModeEnabled; + bool dark = IsDark; dropDown.BackColor = dark ? DarkBackAlt : SystemColors.Menu; dropDown.ForeColor = dark ? DarkFore : SystemColors.MenuText; @@ -269,10 +357,14 @@ internal static class ThemeManager item.ForeColor = dark ? DarkFore : SystemColors.MenuText; } + // ----------------------------------------------------------------- + // Themed renderers for menus + // ----------------------------------------------------------------- + private class DarkContextMenuRenderer : ToolStripProfessionalRenderer { public DarkContextMenuRenderer() : base(new DarkMenuColorTable()) { } - + protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) { if (e.Item.Selected) @@ -284,7 +376,7 @@ internal static class ThemeManager private class DarkDropDownRenderer : ToolStripProfessionalRenderer { public DarkDropDownRenderer() : base(new DarkMenuColorTable()) { } - + protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) { // Force text color to stay light even when selected @@ -309,6 +401,53 @@ internal static class ThemeManager public override Color SeparatorDark => ColorTranslator.FromHtml("#3F3F46"); public override Color SeparatorLight => ColorTranslator.FromHtml("#3F3F46"); } + + // ----------------------------------------------------------------- + // Theming helpers for CustomTreeView + // All rendering logic for the CustomTreeView's proxy combo box and dropdown + // button is centralized here so theming resides in ThemeManager. + // ----------------------------------------------------------------- + + /// + /// Draws the themed combobox area (background, border and text) used in CustomTreeView. + /// This centralizes colors and rendering for light/dark modes. + /// + internal static void DrawCustomComboBox(Graphics graphics, Rectangle rect, Font font, string text) + { + if (graphics is null) return; + using SolidBrush comboBrush = new(CustomTreeViewComboBackColor); + using Pen borderPen = new(CustomTreeViewComboBorderColor); + graphics.FillRectangle(comboBrush, rect); + graphics.DrawRectangle(borderPen, rect); + // Draw text inside the combobox + Size textSize = TextRenderer.MeasureText(graphics, text, font); + Point textPoint = new(rect.Left +3, rect.Top + rect.Height /2 - textSize.Height /2); + TextRenderer.DrawText(graphics, text, font, textPoint, CustomTreeViewComboTextColor, TextFormatFlags.Default); + } + + /// + /// Draws the themed dropdown button (right-side arrow) used in CustomTreeView comboboxes. + /// + internal static void DrawCustomComboBoxButton(Graphics graphics, Rectangle rect) + { + if (graphics is null) return; + using SolidBrush comboBrush = new(CustomTreeViewComboBackColor); + using Pen borderPen = new(CustomTreeViewComboBorderColor); + graphics.FillRectangle(comboBrush, rect); + graphics.DrawRectangle(borderPen, rect); + + // Draw the arrow glyph centered in the rect + int arrowSize =3; + Point arrowTop = new(rect.X + rect.Width /2, rect.Y + rect.Height /2 -1); + Point[] arrowPoints = new[] + { + arrowTop, + new Point(arrowTop.X - arrowSize, arrowTop.Y - arrowSize), + new Point(arrowTop.X + arrowSize, arrowTop.Y - arrowSize) + }; + using SolidBrush arrowBrush = new(CustomTreeViewComboTextColor); + graphics.FillPolygon(arrowBrush, arrowPoints); + } } internal static class NativeMethods @@ -322,4 +461,4 @@ internal static class NativeMethods { _ = DwmSetWindowAttribute(handle, DWMWA_USE_IMMERSIVE_DARK_MODE, ref useDark, sizeof(int)); } -} +} \ No newline at end of file