mirror of
https://github.com/FroggMaster/CreamInstaller.git
synced 2026-06-12 11:01:23 -07:00
Dark mode WIP
- The start of a dark mode theme - Adds a button to toggle dark mode in the top options panel. (The buttons kind of ugly though and I may change this to a checkbox to align with the other existing options) - Added library uxtheme.dll for handling scrollbar themeing (Dealing with scrollbars sucks) - Added ThemeManager for handling of theme colors (Allows for potential expandability I'll probably never implement)
This commit is contained in:
@@ -42,6 +42,18 @@ internal class CustomForm : Form
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnHandleCreated(EventArgs e)
|
||||
{
|
||||
base.OnHandleCreated(e);
|
||||
ThemeManager.Apply(this); // apply current theme (initial or toggled)
|
||||
}
|
||||
|
||||
protected override void OnShown(EventArgs e)
|
||||
{
|
||||
base.OnShown(e);
|
||||
ThemeManager.Apply(this); // ensure late-added controls also themed
|
||||
}
|
||||
|
||||
private void OnHelpButtonClicked(object sender, EventArgs args)
|
||||
{
|
||||
using DialogForm helpDialog = new(this);
|
||||
|
||||
+20
-3
@@ -46,6 +46,7 @@ namespace CreamInstaller.Forms
|
||||
saveFlowPanel = new FlowLayoutPanel();
|
||||
selectionTreeView = new CustomTreeView();
|
||||
topOptionsTable = new TableLayoutPanel();
|
||||
darkModeButton = new Button();
|
||||
programsGroupBox.SuspendLayout();
|
||||
proxyFlowPanel.SuspendLayout();
|
||||
blockedGamesFlowPanel.SuspendLayout();
|
||||
@@ -248,6 +249,19 @@ namespace CreamInstaller.Forms
|
||||
selectionTreeView.Size = new System.Drawing.Size(604, 230);
|
||||
selectionTreeView.TabIndex = 1001;
|
||||
//
|
||||
// darkModeButton
|
||||
//
|
||||
darkModeButton.AutoSize = true;
|
||||
darkModeButton.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
darkModeButton.Location = new System.Drawing.Point(0, 0);
|
||||
darkModeButton.Margin = new Padding(12, 0, 0, 0);
|
||||
darkModeButton.Name = "darkModeButton";
|
||||
darkModeButton.Size = new System.Drawing.Size(73, 25);
|
||||
darkModeButton.TabIndex = 10010;
|
||||
darkModeButton.Text = "Dark Mode";
|
||||
darkModeButton.UseVisualStyleBackColor = true;
|
||||
darkModeButton.Click += OnDarkModeToggle;
|
||||
//
|
||||
// progressBar
|
||||
//
|
||||
progressBar.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
@@ -391,7 +405,8 @@ namespace CreamInstaller.Forms
|
||||
topOptionsTable.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||
topOptionsTable.AutoSize = true;
|
||||
topOptionsTable.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
topOptionsTable.ColumnCount = 5;
|
||||
topOptionsTable.ColumnCount = 6;
|
||||
topOptionsTable.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
topOptionsTable.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
topOptionsTable.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
topOptionsTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); // spacer
|
||||
@@ -406,8 +421,9 @@ namespace CreamInstaller.Forms
|
||||
topOptionsTable.TabIndex = 10009;
|
||||
topOptionsTable.Controls.Add(blockedGamesFlowPanel, 0, 0);
|
||||
topOptionsTable.Controls.Add(useSmokeAPILayoutPanel, 1, 0);
|
||||
topOptionsTable.Controls.Add(proxyFlowPanel, 3, 0);
|
||||
topOptionsTable.Controls.Add(allCheckBoxLayoutPanel, 4, 0);
|
||||
topOptionsTable.Controls.Add(darkModeButton, 2, 0);
|
||||
topOptionsTable.Controls.Add(proxyFlowPanel, 4, 0);
|
||||
topOptionsTable.Controls.Add(allCheckBoxLayoutPanel, 5, 0);
|
||||
//
|
||||
// SelectForm
|
||||
//
|
||||
@@ -479,6 +495,7 @@ namespace CreamInstaller.Forms
|
||||
private Button resetButton;
|
||||
private FlowLayoutPanel saveFlowPanel;
|
||||
private TableLayoutPanel topOptionsTable;
|
||||
private Button darkModeButton;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1239,4 +1239,16 @@ internal sealed partial class SelectForm : CustomForm
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void OnDarkModeToggle(object sender, EventArgs e)
|
||||
{
|
||||
ThemeManager.ToggleDarkMode(this);
|
||||
darkModeButton.Text = Program.DarkModeEnabled ? "Light Mode" : "Dark Mode";
|
||||
}
|
||||
|
||||
protected override void OnShown(EventArgs e)
|
||||
{
|
||||
base.OnShown(e);
|
||||
ThemeManager.Apply(this); // ensure controls after dynamic operations get themed
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ internal sealed partial class UpdateForm : CustomForm
|
||||
#if DEBUG
|
||||
DebugForm.Current.Attach(form);
|
||||
#endif
|
||||
ThemeManager.Apply(form); // apply current theme when transitioning
|
||||
}
|
||||
|
||||
private async void OnLoad()
|
||||
|
||||
@@ -43,6 +43,9 @@ internal static class Program
|
||||
internal static readonly string[] ProtectedGameDirectories = [@"\EasyAntiCheat", @"\BattlEye"];
|
||||
internal static readonly string[] ProtectedGameDirectoryExceptions = [];
|
||||
|
||||
// Dark mode toggle (default false). Can be toggled via UI button.
|
||||
internal static bool DarkModeEnabled;
|
||||
|
||||
internal static bool IsGameBlocked(string name, string directory = null)
|
||||
=> BlockProtectedGames && (ProtectedGames.Contains(name) || directory is not null &&
|
||||
!ProtectedGameDirectoryExceptions.Contains(name)
|
||||
@@ -70,6 +73,8 @@ internal static class Program
|
||||
#if DEBUG
|
||||
DebugForm.Current.Attach(form);
|
||||
#endif
|
||||
// Apply initial theme
|
||||
Utility.ThemeManager.Apply(form);
|
||||
Application.Run(form);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -21,4 +21,9 @@ internal static partial class NativeImports
|
||||
[LibraryImport("user32.dll", SetLastError = true), DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
internal static partial void SetWindowPos(nint hWnd, nint hWndInsertAfter, int x, int y, int cx, int cy,
|
||||
uint uFlags);
|
||||
|
||||
// Windows theming (scrollbars / dark mode for some controls)
|
||||
[LibraryImport("uxtheme.dll", SetLastError = true)]
|
||||
internal static partial int SetWindowTheme(nint hWnd, [MarshalAs(UnmanagedType.LPWStr)] string pszSubAppName,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string pszSubIdList);
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace CreamInstaller.Utility;
|
||||
|
||||
internal static class ThemeManager
|
||||
{
|
||||
// VS-like dark 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");
|
||||
private static readonly Color DarkFore = ColorTranslator.FromHtml("#D4D4D4");
|
||||
private static readonly Color DarkForeDim = ColorTranslator.FromHtml("#9CA3AF");
|
||||
private static readonly Color Accent = ColorTranslator.FromHtml("#0E639C");
|
||||
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 void ToggleDarkMode(Form anyForm)
|
||||
{
|
||||
Program.DarkModeEnabled = !Program.DarkModeEnabled;
|
||||
ApplyToAllOpenForms();
|
||||
}
|
||||
|
||||
internal static void Apply(Form form)
|
||||
{
|
||||
if (form is null) return;
|
||||
if (!Program.DarkModeEnabled)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
private static void ApplyControlTheme(Control control, bool dark)
|
||||
{
|
||||
if (control is null) return;
|
||||
// recurse first to avoid overwriting multi-level backgrounds incorrectly
|
||||
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 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;
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Light reset per control type
|
||||
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 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Reset(Form form)
|
||||
{
|
||||
form.SuspendLayout();
|
||||
form.BackColor = LightBack;
|
||||
form.ForeColor = LightFore;
|
||||
ApplyTitleBar(form);
|
||||
foreach (Control c in form.Controls)
|
||||
ApplyControlTheme(c, false);
|
||||
form.ResumeLayout(true);
|
||||
}
|
||||
|
||||
private static void ApplyToAllOpenForms()
|
||||
{
|
||||
foreach (Form openForm in Application.OpenForms.Cast<Form>())
|
||||
Apply(openForm);
|
||||
}
|
||||
|
||||
private static void ApplyTitleBar(Form form)
|
||||
{
|
||||
try
|
||||
{
|
||||
int useDark = Program.DarkModeEnabled ?1 :0;
|
||||
NativeMethods.EnableDarkTitleBar(form.Handle, useDark);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private static void TryApplyScrollbarTheme(Control control, bool dark)
|
||||
{
|
||||
// RichTextBox & TreeView host scrollbars internally; use window theme API
|
||||
try
|
||||
{
|
||||
string theme = dark ? "DarkMode_Explorer" : null; // reset with null
|
||||
NativeImports.SetWindowTheme(control.Handle, theme, null);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
internal static class NativeMethods
|
||||
{
|
||||
private const int DWMWA_USE_IMMERSIVE_DARK_MODE =20; // Windows102004+ /11
|
||||
|
||||
[System.Runtime.InteropServices.DllImport("dwmapi.dll")]
|
||||
private static extern int DwmSetWindowAttribute(System.IntPtr hwnd, int attr, ref int attrValue, int attrSize);
|
||||
|
||||
internal static void EnableDarkTitleBar(System.IntPtr handle, int useDark)
|
||||
{
|
||||
_ = DwmSetWindowAttribute(handle, DWMWA_USE_IMMERSIVE_DARK_MODE, ref useDark, sizeof(int));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user