Migrate Legacy Theme Code into Theme Manager / Shitty Fix for Selection in Dark Mode / Shitty Comments

- Moved as much of the legacy theme code as I could find into ThemeManager
- Some shitty comments added VIA AI (probably better than I'd write anyways)
- Some adjustments to how the highlight is being rendered, for some fucking reason the system highlight refuses to match on the left/right (probably some dumb shit I'm doing.) > This version makes things clearer/easier to read in dark mode.
This commit is contained in:
Frog
2026-01-31 03:00:38 -08:00
parent d2a5549878
commit f20ca0d833
2 changed files with 308 additions and 172 deletions
+36 -39
View File
@@ -21,6 +21,11 @@ internal sealed class CustomTreeView : TreeView
private readonly Dictionary<TreeNode, Rectangle> 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;
+272 -133
View File
@@ -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
// -----------------------------------------------------------------
/// <summary>
/// Toggle dark mode and re-apply theming to all open forms.
/// </summary>
internal static void ToggleDarkMode(Form anyForm)
{
Program.DarkModeEnabled = !Program.DarkModeEnabled;
ApplyToAllOpenForms();
}
/// <summary>
/// Apply current theme to a single form and its child controls.
/// </summary>
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);
}
/// <summary>
/// Apply the theme to all currently open forms.
/// </summary>
internal static void ApplyToAllOpenForms()
{
foreach (Form openForm in Application.OpenForms.Cast<Form>())
Apply(openForm);
}
// -----------------------------------------------------------------
// Control theming helpers
// -----------------------------------------------------------------
/// <summary>
/// Apply theming to a control tree. Entry point which recurses children
/// then applies either the dark or light styling logic.
/// </summary>
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<Form>())
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
// -----------------------------------------------------------------
/// <summary>
/// Apply theme to a context menu (ContextMenuStrip).
/// </summary>
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.
// -----------------------------------------------------------------
/// <summary>
/// Draws the themed combobox area (background, border and text) used in CustomTreeView.
/// This centralizes colors and rendering for light/dark modes.
/// </summary>
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);
}
/// <summary>
/// Draws the themed dropdown button (right-side arrow) used in CustomTreeView comboboxes.
/// </summary>
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));
}
}
}