From c5f4fe2d0efd33e998e2911b5d764558d3b2d13f Mon Sep 17 00:00:00 2001 From: Ray Berezowski <6616212+rberezowski@users.noreply.github.com> Date: Tue, 25 Aug 2026 17:31:54 -0400 Subject: [PATCH] Add selectable color schemes --- App.xaml.cs | 1 - MainWindow.xaml | 11 ++- MainWindow.xaml.cs | 15 ++++ Models/LauncherConfig.cs | 10 +++ Services/ConfigService.cs | 20 +++-- Services/LauncherController.cs | 2 + Services/ThemeService.cs | 158 +++++++++++++++++++++++++++------ 7 files changed, 179 insertions(+), 38 deletions(-) diff --git a/App.xaml.cs b/App.xaml.cs index 3acd1ea..274c24d 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -10,7 +10,6 @@ public partial class App : System.Windows.Application { base.OnStartup(e); - ThemeService.Apply(); controller = new LauncherController(); controller.Start(); diff --git a/MainWindow.xaml b/MainWindow.xaml index e03eb30..9cabaf2 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -129,6 +129,7 @@ + @@ -136,10 +137,14 @@ PreviewKeyDown="HotkeyBox_PreviewKeyDown"/> - + - - + + + + (); ItemDisplayModeBox.ItemsSource = Enum.GetValues(); + ThemeSchemeBox.ItemsSource = Enum.GetValues(); TypeBox.ItemsSource = Enum.GetValues(); VersionText.Text = $"Version {AppInfo.Version} Build {AppInfo.Build}"; RefreshView(); @@ -53,6 +54,7 @@ public partial class MainWindow : Window HotkeyBox.Text = controller.Config.Hotkey; DisplayModeBox.SelectedItem = controller.Config.DisplayMode; + ThemeSchemeBox.SelectedItem = controller.Config.ThemeScheme; LoadStartupState(); StatusText.Text = "Config file: config.json beside TaskbarLauncher.exe"; if (itemToSelect is not null && SelectTreeItem(itemToSelect)) @@ -324,6 +326,9 @@ public partial class MainWindow : Window controller.Config.DisplayMode = DisplayModeBox.SelectedItem is LauncherDisplayMode mode ? mode : LauncherDisplayMode.CompactList; + controller.Config.ThemeScheme = ThemeSchemeBox.SelectedItem is LauncherThemeScheme scheme + ? scheme + : LauncherThemeScheme.MarkHaven; ApplyCurrentItem(); } @@ -1007,6 +1012,16 @@ public partial class MainWindow : Window UpdatePreview(); } + private void ThemeSchemeBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (controller is null || ThemeSchemeBox.SelectedItem is not LauncherThemeScheme scheme) + { + return; + } + + ThemeService.Apply(scheme); + } + private void EnsureAutomaticIconPath() { if (TypeBox.SelectedItem is LauncherItemType.Menu) diff --git a/Models/LauncherConfig.cs b/Models/LauncherConfig.cs index 49eac7b..19f96d7 100644 --- a/Models/LauncherConfig.cs +++ b/Models/LauncherConfig.cs @@ -6,6 +6,8 @@ public sealed class LauncherConfig public LauncherDisplayMode DisplayMode { get; set; } = LauncherDisplayMode.CompactList; + public LauncherThemeScheme ThemeScheme { get; set; } = LauncherThemeScheme.MarkHaven; + public List Items { get; set; } = []; } @@ -42,3 +44,11 @@ public enum LauncherDisplayMode LargeIconWithText, CompactList } + +public enum LauncherThemeScheme +{ + MarkHaven, + MGETOrange, + System, + WindowsClassic +} diff --git a/Services/ConfigService.cs b/Services/ConfigService.cs index 7b41f36..1f1b8b0 100644 --- a/Services/ConfigService.cs +++ b/Services/ConfigService.cs @@ -36,7 +36,7 @@ public static class ConfigService string json = File.ReadAllText(ConfigPath); LauncherConfig config = JsonSerializer.Deserialize(json, JsonOptions) ?? CreateDefaultConfig(); - NormalizeDisplayModes(config); + NormalizeConfig(config); Save(config); return config; } @@ -74,7 +74,7 @@ public static class ConfigService Directory.CreateDirectory(destinationFolder); } - NormalizeDisplayModes(config); + NormalizeConfig(config); string json = JsonSerializer.Serialize(config, JsonOptions); File.WriteAllText(destinationPath, json); return destinationPath; @@ -155,7 +155,7 @@ public static class ConfigService ] }; - NormalizeDisplayModes(config); + NormalizeConfig(config); return config; } @@ -179,7 +179,7 @@ public static class ConfigService } ValidateConfig(config); - NormalizeDisplayModes(config); + NormalizeConfig(config); return config; } @@ -214,8 +214,18 @@ public static class ConfigService } } - private static void NormalizeDisplayModes(LauncherConfig config) + private static void NormalizeConfig(LauncherConfig config) { + if (!Enum.IsDefined(config.DisplayMode)) + { + config.DisplayMode = LauncherDisplayMode.CompactList; + } + + if (!Enum.IsDefined(config.ThemeScheme)) + { + config.ThemeScheme = LauncherThemeScheme.MarkHaven; + } + foreach (LauncherItem item in config.Items) { NormalizeDisplayModes(item); diff --git a/Services/LauncherController.cs b/Services/LauncherController.cs index 4029094..8976adc 100644 --- a/Services/LauncherController.cs +++ b/Services/LauncherController.cs @@ -20,6 +20,7 @@ public sealed class LauncherController : IDisposable public void Start() { Config = ConfigService.Load(); + ThemeService.Apply(Config.ThemeScheme); CreateTrayIcon(); RegisterHotkey(); } @@ -27,6 +28,7 @@ public sealed class LauncherController : IDisposable public void ReloadConfig() { Config = ConfigService.Load(); + ThemeService.Apply(Config.ThemeScheme); RegisterHotkey(); popup?.Close(); popup = null; diff --git a/Services/ThemeService.cs b/Services/ThemeService.cs index 37cf7b9..3be8958 100644 --- a/Services/ThemeService.cs +++ b/Services/ThemeService.cs @@ -1,6 +1,7 @@ using System.Windows; using System.Windows.Media; using Microsoft.Win32; +using TaskbarLauncher.Models; using MediaColor = System.Windows.Media.Color; namespace TaskbarLauncher.Services; @@ -10,42 +11,32 @@ public static class ThemeService private const string PersonalizeKey = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"; private const string DwmKey = @"Software\Microsoft\Windows\DWM"; - public static void Apply() + public static void Apply(LauncherThemeScheme scheme = LauncherThemeScheme.MarkHaven) { bool isLight = IsLightTheme(); - MediaColor accent = GetAccentColor(); - MediaColor orange = MediaColor.FromRgb(0xD8, 0x78, 0x1D); - MediaColor window = isLight ? MediaColor.FromRgb(0xF6, 0xF1, 0xEA) : MediaColor.FromRgb(0x18, 0x16, 0x14); - MediaColor surface = isLight ? MediaColor.FromRgb(0xFF, 0xFC, 0xF8) : MediaColor.FromRgb(0x22, 0x1F, 0x1B); - MediaColor panel = isLight ? MediaColor.FromRgb(0xFF, 0xF7, 0xEE) : MediaColor.FromRgb(0x2A, 0x24, 0x1E); - MediaColor control = isLight ? MediaColor.FromRgb(0xFA, 0xF1, 0xE7) : MediaColor.FromRgb(0x32, 0x2B, 0x24); - MediaColor hover = isLight ? MediaColor.FromRgb(0xFF, 0xED, 0xDA) : MediaColor.FromRgb(0x42, 0x34, 0x28); - MediaColor text = isLight ? MediaColor.FromRgb(0x24, 0x1D, 0x17) : MediaColor.FromRgb(0xF8, 0xF2, 0xEA); - MediaColor muted = isLight ? MediaColor.FromRgb(0x75, 0x68, 0x5C) : MediaColor.FromRgb(0xC3, 0xB5, 0xA6); - MediaColor border = isLight ? MediaColor.FromRgb(0xE4, 0xD1, 0xBC) : MediaColor.FromRgb(0x5B, 0x4B, 0x3C); - MediaColor accentSoft = Blend(accent, surface, isLight ? 0.14 : 0.24); - MediaColor orangeSoft = Blend(orange, surface, isLight ? 0.16 : 0.30); + ThemePalette palette = GetPalette(scheme, isLight); + MediaColor accent = palette.Accent; MediaColor selectedText = IsDark(accent) ? System.Windows.Media.Colors.White : System.Windows.Media.Colors.Black; - SetBrush("AppWindowBrush", window); - SetBrush("AppSurfaceBrush", surface); - SetBrush("AppPanelBrush", panel); - SetBrush("AppControlBrush", control); - SetBrush("AppControlHoverBrush", hover); - SetBrush("AppTextBrush", text); - SetBrush("AppMutedTextBrush", muted); - SetBrush("AppBorderBrush", border); + SetBrush("AppWindowBrush", palette.Window); + SetBrush("AppSurfaceBrush", palette.Surface); + SetBrush("AppPanelBrush", palette.Panel); + SetBrush("AppControlBrush", palette.Control); + SetBrush("AppControlHoverBrush", palette.Hover); + SetBrush("AppTextBrush", palette.Text); + SetBrush("AppMutedTextBrush", palette.Muted); + SetBrush("AppBorderBrush", palette.Border); SetBrush("AppAccentBrush", accent); - SetBrush("AppAccentSoftBrush", accentSoft); - SetBrush("AppOrangeBrush", orange); - SetBrush("AppOrangeSoftBrush", orangeSoft); + SetBrush("AppAccentSoftBrush", palette.AccentSoft); + SetBrush("AppOrangeBrush", palette.Orange); + SetBrush("AppOrangeSoftBrush", palette.OrangeSoft); SetBrush("AppSelectedTextBrush", selectedText); - SetBrush(System.Windows.SystemColors.WindowBrushKey, surface); - SetBrush(System.Windows.SystemColors.WindowTextBrushKey, text); - SetBrush(System.Windows.SystemColors.ControlBrushKey, control); - SetBrush(System.Windows.SystemColors.ControlTextBrushKey, text); - SetBrush(System.Windows.SystemColors.ControlDarkBrushKey, border); + SetBrush(System.Windows.SystemColors.WindowBrushKey, palette.Surface); + SetBrush(System.Windows.SystemColors.WindowTextBrushKey, palette.Text); + SetBrush(System.Windows.SystemColors.ControlBrushKey, palette.Control); + SetBrush(System.Windows.SystemColors.ControlTextBrushKey, palette.Text); + SetBrush(System.Windows.SystemColors.ControlDarkBrushKey, palette.Border); SetBrush(System.Windows.SystemColors.HighlightBrushKey, accent); SetBrush(System.Windows.SystemColors.HighlightTextBrushKey, selectedText); } @@ -70,6 +61,101 @@ public static class ThemeService return MediaColor.FromRgb(0x00, 0x78, 0xD4); } + private static ThemePalette GetPalette(LauncherThemeScheme scheme, bool isLight) + { + MediaColor windowsAccent = GetAccentColor(); + MediaColor orange = MediaColor.FromRgb(0xD8, 0x78, 0x1D); + + return scheme switch + { + LauncherThemeScheme.System => CreatePalette( + isLight, + windowsAccent, + orange, + window: LightDark(isLight, 0xF7F7F7, 0x202020), + surface: LightDark(isLight, 0xFFFFFF, 0x2B2B2B), + panel: LightDark(isLight, 0xF3F3F3, 0x333333), + control: LightDark(isLight, 0xF3F3F3, 0x333333), + hover: LightDark(isLight, 0xEAF3FF, 0x3B4A5C), + text: LightDark(isLight, 0x1A1A1A, 0xF3F3F3), + muted: LightDark(isLight, 0x666666, 0xB8B8B8), + border: LightDark(isLight, 0xC8C8C8, 0x555555)), + + LauncherThemeScheme.WindowsClassic => CreatePalette( + isLight, + windowsAccent, + orange, + window: LightDark(isLight, 0xF0F0F0, 0x1F1F1F), + surface: LightDark(isLight, 0xFAFAFA, 0x2D2D30), + panel: LightDark(isLight, 0xFFFFFF, 0x252526), + control: LightDark(isLight, 0xF5F5F5, 0x333337), + hover: LightDark(isLight, 0xEDEDED, 0x3E3E42), + text: LightDark(isLight, 0x111111, 0xF1F1F1), + muted: LightDark(isLight, 0x5F5F5F, 0xBDBDBD), + border: LightDark(isLight, 0xD0D0D0, 0x565656)), + + LauncherThemeScheme.MGETOrange => CreatePalette( + isLight, + orange, + orange, + window: LightDark(isLight, 0xF6F1EA, 0x181614), + surface: LightDark(isLight, 0xFFFCF8, 0x221F1B), + panel: LightDark(isLight, 0xFFF7EE, 0x2A241E), + control: LightDark(isLight, 0xFAF1E7, 0x322B24), + hover: LightDark(isLight, 0xFFEDDA, 0x423428), + text: LightDark(isLight, 0x241D17, 0xF8F2EA), + muted: LightDark(isLight, 0x75685C, 0xC3B5A6), + border: LightDark(isLight, 0xE4D1BC, 0x5B4B3C)), + + _ => CreatePalette( + isLight, + LightDark(isLight, 0x146B52, 0x58D0A3), + LightDark(isLight, 0xB56B08, 0xF4BD54), + window: LightDark(isLight, 0xEEF1F0, 0x111816), + surface: LightDark(isLight, 0xFFFFFF, 0x18201E), + panel: LightDark(isLight, 0xFBFCFB, 0x15201D), + control: LightDark(isLight, 0xEEF3F0, 0x1A2A24), + hover: LightDark(isLight, 0xE2F3EC, 0x183B30), + text: LightDark(isLight, 0x1B2824, 0xEDF5F2), + muted: LightDark(isLight, 0x66746F, 0x9CAAA5), + border: LightDark(isLight, 0xDCE3E0, 0x33423D)) + }; + } + + private static ThemePalette CreatePalette( + bool isLight, + MediaColor accent, + MediaColor orange, + MediaColor window, + MediaColor surface, + MediaColor panel, + MediaColor control, + MediaColor hover, + MediaColor text, + MediaColor muted, + MediaColor border) + { + return new ThemePalette( + window, + surface, + panel, + control, + hover, + text, + muted, + border, + accent, + Blend(accent, surface, isLight ? 0.14 : 0.24), + orange, + Blend(orange, surface, isLight ? 0.16 : 0.30)); + } + + private static MediaColor LightDark(bool isLight, int lightRgb, int darkRgb) + { + int rgb = isLight ? lightRgb : darkRgb; + return MediaColor.FromRgb((byte)(rgb >> 16), (byte)(rgb >> 8), (byte)rgb); + } + private static MediaColor Blend(MediaColor foreground, MediaColor background, double foregroundAmount) { double amount = Math.Clamp(foregroundAmount, 0, 1); @@ -98,4 +184,18 @@ public static class ThemeService double luminance = (0.299 * color.R) + (0.587 * color.G) + (0.114 * color.B); return luminance < 140; } + + private sealed record ThemePalette( + MediaColor Window, + MediaColor Surface, + MediaColor Panel, + MediaColor Control, + MediaColor Hover, + MediaColor Text, + MediaColor Muted, + MediaColor Border, + MediaColor Accent, + MediaColor AccentSoft, + MediaColor Orange, + MediaColor OrangeSoft); }