diff --git a/CreamInstaller/Components/CustomTreeView.cs b/CreamInstaller/Components/CustomTreeView.cs index 6e07ee4..2ec2971 100644 --- a/CreamInstaller/Components/CustomTreeView.cs +++ b/CreamInstaller/Components/CustomTreeView.cs @@ -75,11 +75,14 @@ internal sealed class CustomTreeView : TreeView private void DrawTreeNode(object sender, DrawTreeNodeEventArgs e) { - e.DrawDefault = true; TreeNode node = e.Node; if (node is not { IsVisible: true }) + { + e.DrawDefault = true; return; + } + bool dark = Program.DarkModeEnabled; bool highlighted = (e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected && Focused; Graphics graphics = e.Graphics; @@ -103,12 +106,43 @@ internal sealed class CustomTreeView : TreeView } } + Form form = FindForm(); + + if (dark && CheckBoxes) + { + // In dark mode we take full ownership of the row so the system never + // gets a chance to paint a light-background checkbox. + e.DrawDefault = false; + + // Row background + Rectangle rowRect = new(0, node.Bounds.Top, ClientSize.Width, node.Bounds.Height); + graphics.FillRectangle(highlighted ? selectionBrush : backBrush, rowRect); + + // Node text + Font nodeFont = node.NodeFont ?? Font; + Color textColor = Enabled ? ForeColor : SystemColors.GrayText; + TextRenderer.DrawText(graphics, node.Text, nodeFont, + new Point(node.Bounds.Left, node.Bounds.Top + 1), textColor, TextFormatFlags.Default); + + // Checkbox glyph – pure GDI so it matches the dark-themed CheckBox controls + CheckBoxState cbState = node.Checked + ? (Enabled ? CheckBoxState.CheckedNormal : CheckBoxState.CheckedDisabled) + : (Enabled ? CheckBoxState.UncheckedNormal : CheckBoxState.UncheckedDisabled); + Size cbSize = CheckBoxRenderer.GetGlyphSize(graphics, cbState); + int cbX = node.Bounds.Left - cbSize.Width - 2; + int cbY = node.Bounds.Top + node.Bounds.Height / 2 - cbSize.Height / 2; + ThemeManager.DrawDarkCheckBox(graphics, new Point(cbX, cbY), cbSize, node.Checked, Enabled); + } + else + { + e.DrawDefault = true; + } + Font font = node.NodeFont ?? Font; Brush brush = highlighted ? (Brush)selectionBrush : backBrush; Rectangle bounds = node.Bounds; Rectangle selectionBounds = bounds; - Form form = FindForm(); if (form is not SelectForm and not SelectDialogForm) return; @@ -168,18 +202,19 @@ internal sealed class CustomTreeView : TreeView graphics.FillRectangle(brush, bounds); } - CheckBoxState checkBoxState = selection.UseProxy - ? Enabled ? CheckBoxState.CheckedPressed : CheckBoxState.CheckedDisabled - : Enabled - ? CheckBoxState.UncheckedPressed - : CheckBoxState.UncheckedDisabled; - size = CheckBoxRenderer.GetGlyphSize(graphics, checkBoxState); + CheckBoxState proxyState = selection.UseProxy + ? (Enabled ? CheckBoxState.CheckedNormal : CheckBoxState.CheckedDisabled) + : (Enabled ? CheckBoxState.UncheckedNormal : CheckBoxState.UncheckedDisabled); + size = CheckBoxRenderer.GetGlyphSize(graphics, proxyState); bounds = bounds with { X = bounds.X + bounds.Width, Width = size.Width }; selectionBounds = new(selectionBounds.Location, selectionBounds.Size + bounds.Size with { Height = 0 }); Rectangle checkBoxBounds = bounds; graphics.FillRectangle(backBrush, bounds); point = new(bounds.Left, bounds.Top + bounds.Height / 2 - size.Height / 2 - 1); - CheckBoxRenderer.DrawCheckBox(graphics, point, checkBoxState); + if (dark) + ThemeManager.DrawDarkCheckBox(graphics, point, size, selection.UseProxy, Enabled); + else + CheckBoxRenderer.DrawCheckBox(graphics, point, proxyState); text = ProxyToggleString; size = TextRenderer.MeasureText(graphics, text, font); diff --git a/CreamInstaller/Utility/ThemeManager.cs b/CreamInstaller/Utility/ThemeManager.cs index eef74be..60f3809 100644 --- a/CreamInstaller/Utility/ThemeManager.cs +++ b/CreamInstaller/Utility/ThemeManager.cs @@ -422,6 +422,70 @@ internal static class ThemeManager // button is centralized here so theming resides in ThemeManager. // ----------------------------------------------------------------- + // Dark checkbox colors – matched to how the system renders the "All" CheckBox control + // in dark mode: dark fill, mid-gray border, light foreground tick. + private static readonly Color DarkCbBorder = ColorTranslator.FromHtml("#6B6B6B"); + private static readonly Color DarkCbDisabledBorder = ColorTranslator.FromHtml("#454545"); + + /// + /// Draws a checkbox glyph in pure GDI that matches the appearance of a dark-themed + /// WinForms CheckBox control (same background, border, tick colors, and rounded corners). + /// Use this in owner-draw contexts where CheckBoxRenderer always paints a white background. + /// + internal static void DrawDarkCheckBox(Graphics g, Point point, Size glyphSize, bool isChecked, bool enabled = true) + { + if (g is null) return; + int w = glyphSize.Width; + int h = glyphSize.Height; + Rectangle box = new(point.X, point.Y, w - 1, h - 1); + int radius = Math.Max(2, w / 5); // rounded corner radius proportional to glyph size + + // Build rounded rectangle path + using System.Drawing.Drawing2D.GraphicsPath path = RoundedRect(box, radius); + + // Fill + using SolidBrush fillBrush = new(DarkBackAlt); + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + g.FillPath(fillBrush, path); + + // Border + using Pen borderPen = new(enabled ? DarkCbBorder : DarkCbDisabledBorder); + g.DrawPath(borderPen, path); + + if (isChecked) + { + Color tickColor = enabled ? DarkFore : DarkForeDim; + using Pen tickPen = new(tickColor, 1.7f) + { + StartCap = System.Drawing.Drawing2D.LineCap.Round, + EndCap = System.Drawing.Drawing2D.LineCap.Round, + LineJoin = System.Drawing.Drawing2D.LineJoin.Round, + }; + // Scale tick proportionally to the glyph size + float scaleX = w / 13f; + float scaleY = h / 13f; + g.DrawLines(tickPen, new PointF[] + { + new(point.X + 2 * scaleX, point.Y + 6 * scaleY), + new(point.X + 5 * scaleX, point.Y + 9 * scaleY), + new(point.X + 10 * scaleX, point.Y + 3 * scaleY), + }); + } + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.Default; + } + + private static System.Drawing.Drawing2D.GraphicsPath RoundedRect(Rectangle r, int radius) + { + int d = radius * 2; + System.Drawing.Drawing2D.GraphicsPath path = new(); + path.AddArc(r.Left, r.Top, d, d, 180, 90); + path.AddArc(r.Right - d, r.Top, d, d, 270, 90); + path.AddArc(r.Right - d, r.Bottom - d, d, d, 0, 90); + path.AddArc(r.Left, r.Bottom - d, d, d, 90, 90); + path.CloseFigure(); + return path; + } + /// /// Draws the themed combobox area (background, border and text) used in CustomTreeView. /// This centralizes colors and rendering for light/dark modes.