diff --git a/CreamInstaller/Components/CustomForm.cs b/CreamInstaller/Components/CustomForm.cs index 06b4d2e..c485f43 100644 --- a/CreamInstaller/Components/CustomForm.cs +++ b/CreamInstaller/Components/CustomForm.cs @@ -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); diff --git a/CreamInstaller/Forms/SelectForm.Designer.cs b/CreamInstaller/Forms/SelectForm.Designer.cs index 59f52df..ebc956b 100644 --- a/CreamInstaller/Forms/SelectForm.Designer.cs +++ b/CreamInstaller/Forms/SelectForm.Designer.cs @@ -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; } } diff --git a/CreamInstaller/Forms/SelectForm.cs b/CreamInstaller/Forms/SelectForm.cs index 2678f58..ad69f41 100644 --- a/CreamInstaller/Forms/SelectForm.cs +++ b/CreamInstaller/Forms/SelectForm.cs @@ -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 + } } \ No newline at end of file diff --git a/CreamInstaller/Forms/UpdateForm.cs b/CreamInstaller/Forms/UpdateForm.cs index 481d0cc..d9809b0 100644 --- a/CreamInstaller/Forms/UpdateForm.cs +++ b/CreamInstaller/Forms/UpdateForm.cs @@ -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() diff --git a/CreamInstaller/Program.cs b/CreamInstaller/Program.cs index 3e80873..8fb6235 100644 --- a/CreamInstaller/Program.cs +++ b/CreamInstaller/Program.cs @@ -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) diff --git a/CreamInstaller/Utility/NativeImports.cs b/CreamInstaller/Utility/NativeImports.cs index 5e102f1..142d263 100644 --- a/CreamInstaller/Utility/NativeImports.cs +++ b/CreamInstaller/Utility/NativeImports.cs @@ -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); } \ No newline at end of file diff --git a/CreamInstaller/Utility/ThemeManager.cs b/CreamInstaller/Utility/ThemeManager.cs new file mode 100644 index 0000000..63d4bf8 --- /dev/null +++ b/CreamInstaller/Utility/ThemeManager.cs @@ -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