Add selectable color schemes

This commit is contained in:
Ray Berezowski
2026-08-25 17:31:54 -04:00
parent 8760d96310
commit c5f4fe2d0e
7 changed files with 179 additions and 38 deletions

View File

@@ -10,7 +10,6 @@ public partial class App : System.Windows.Application
{ {
base.OnStartup(e); base.OnStartup(e);
ThemeService.Apply();
controller = new LauncherController(); controller = new LauncherController();
controller.Start(); controller.Start();

View File

@@ -129,6 +129,7 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Text="Hotkey" VerticalAlignment="Center" Margin="0,0,8,8"/> <TextBlock Text="Hotkey" VerticalAlignment="Center" Margin="0,0,8,8"/>
@@ -136,10 +137,14 @@
PreviewKeyDown="HotkeyBox_PreviewKeyDown"/> PreviewKeyDown="HotkeyBox_PreviewKeyDown"/>
<TextBlock Text="Default for New Items" Grid.Row="1" VerticalAlignment="Center" Margin="0,0,8,0"/> <TextBlock Text="Default for New Items" Grid.Row="1" VerticalAlignment="Center" Margin="0,0,8,0"/>
<ComboBox x:Name="DisplayModeBox" Grid.Row="1" Grid.Column="1"/> <ComboBox x:Name="DisplayModeBox" Grid.Row="1" Grid.Column="1" Margin="0,0,0,8"/>
<TextBlock Text="Startup" Grid.Row="2" VerticalAlignment="Center" Margin="0,8,8,0"/> <TextBlock Text="Color Scheme" Grid.Row="2" VerticalAlignment="Center" Margin="0,0,8,8"/>
<CheckBox x:Name="StartWithWindowsBox" Grid.Row="2" Grid.Column="1" <ComboBox x:Name="ThemeSchemeBox" Grid.Row="2" Grid.Column="1" Margin="0,0,0,8"
SelectionChanged="ThemeSchemeBox_SelectionChanged"/>
<TextBlock Text="Startup" Grid.Row="3" VerticalAlignment="Center" Margin="0,8,8,0"/>
<CheckBox x:Name="StartWithWindowsBox" Grid.Row="3" Grid.Column="1"
Content="Start with Windows" Content="Start with Windows"
Margin="0,8,0,0" Margin="0,8,0,0"
Checked="StartWithWindowsBox_Changed" Checked="StartWithWindowsBox_Changed"

View File

@@ -33,6 +33,7 @@ public partial class MainWindow : Window
this.controller = controller; this.controller = controller;
DisplayModeBox.ItemsSource = Enum.GetValues<LauncherDisplayMode>(); DisplayModeBox.ItemsSource = Enum.GetValues<LauncherDisplayMode>();
ItemDisplayModeBox.ItemsSource = Enum.GetValues<LauncherDisplayMode>(); ItemDisplayModeBox.ItemsSource = Enum.GetValues<LauncherDisplayMode>();
ThemeSchemeBox.ItemsSource = Enum.GetValues<LauncherThemeScheme>();
TypeBox.ItemsSource = Enum.GetValues<LauncherItemType>(); TypeBox.ItemsSource = Enum.GetValues<LauncherItemType>();
VersionText.Text = $"Version {AppInfo.Version} Build {AppInfo.Build}"; VersionText.Text = $"Version {AppInfo.Version} Build {AppInfo.Build}";
RefreshView(); RefreshView();
@@ -53,6 +54,7 @@ public partial class MainWindow : Window
HotkeyBox.Text = controller.Config.Hotkey; HotkeyBox.Text = controller.Config.Hotkey;
DisplayModeBox.SelectedItem = controller.Config.DisplayMode; DisplayModeBox.SelectedItem = controller.Config.DisplayMode;
ThemeSchemeBox.SelectedItem = controller.Config.ThemeScheme;
LoadStartupState(); LoadStartupState();
StatusText.Text = "Config file: config.json beside TaskbarLauncher.exe"; StatusText.Text = "Config file: config.json beside TaskbarLauncher.exe";
if (itemToSelect is not null && SelectTreeItem(itemToSelect)) if (itemToSelect is not null && SelectTreeItem(itemToSelect))
@@ -324,6 +326,9 @@ public partial class MainWindow : Window
controller.Config.DisplayMode = DisplayModeBox.SelectedItem is LauncherDisplayMode mode controller.Config.DisplayMode = DisplayModeBox.SelectedItem is LauncherDisplayMode mode
? mode ? mode
: LauncherDisplayMode.CompactList; : LauncherDisplayMode.CompactList;
controller.Config.ThemeScheme = ThemeSchemeBox.SelectedItem is LauncherThemeScheme scheme
? scheme
: LauncherThemeScheme.MarkHaven;
ApplyCurrentItem(); ApplyCurrentItem();
} }
@@ -1007,6 +1012,16 @@ public partial class MainWindow : Window
UpdatePreview(); 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() private void EnsureAutomaticIconPath()
{ {
if (TypeBox.SelectedItem is LauncherItemType.Menu) if (TypeBox.SelectedItem is LauncherItemType.Menu)

View File

@@ -6,6 +6,8 @@ public sealed class LauncherConfig
public LauncherDisplayMode DisplayMode { get; set; } = LauncherDisplayMode.CompactList; public LauncherDisplayMode DisplayMode { get; set; } = LauncherDisplayMode.CompactList;
public LauncherThemeScheme ThemeScheme { get; set; } = LauncherThemeScheme.MarkHaven;
public List<LauncherItem> Items { get; set; } = []; public List<LauncherItem> Items { get; set; } = [];
} }
@@ -42,3 +44,11 @@ public enum LauncherDisplayMode
LargeIconWithText, LargeIconWithText,
CompactList CompactList
} }
public enum LauncherThemeScheme
{
MarkHaven,
MGETOrange,
System,
WindowsClassic
}

View File

@@ -36,7 +36,7 @@ public static class ConfigService
string json = File.ReadAllText(ConfigPath); string json = File.ReadAllText(ConfigPath);
LauncherConfig config = JsonSerializer.Deserialize<LauncherConfig>(json, JsonOptions) ?? CreateDefaultConfig(); LauncherConfig config = JsonSerializer.Deserialize<LauncherConfig>(json, JsonOptions) ?? CreateDefaultConfig();
NormalizeDisplayModes(config); NormalizeConfig(config);
Save(config); Save(config);
return config; return config;
} }
@@ -74,7 +74,7 @@ public static class ConfigService
Directory.CreateDirectory(destinationFolder); Directory.CreateDirectory(destinationFolder);
} }
NormalizeDisplayModes(config); NormalizeConfig(config);
string json = JsonSerializer.Serialize(config, JsonOptions); string json = JsonSerializer.Serialize(config, JsonOptions);
File.WriteAllText(destinationPath, json); File.WriteAllText(destinationPath, json);
return destinationPath; return destinationPath;
@@ -155,7 +155,7 @@ public static class ConfigService
] ]
}; };
NormalizeDisplayModes(config); NormalizeConfig(config);
return config; return config;
} }
@@ -179,7 +179,7 @@ public static class ConfigService
} }
ValidateConfig(config); ValidateConfig(config);
NormalizeDisplayModes(config); NormalizeConfig(config);
return 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) foreach (LauncherItem item in config.Items)
{ {
NormalizeDisplayModes(item); NormalizeDisplayModes(item);

View File

@@ -20,6 +20,7 @@ public sealed class LauncherController : IDisposable
public void Start() public void Start()
{ {
Config = ConfigService.Load(); Config = ConfigService.Load();
ThemeService.Apply(Config.ThemeScheme);
CreateTrayIcon(); CreateTrayIcon();
RegisterHotkey(); RegisterHotkey();
} }
@@ -27,6 +28,7 @@ public sealed class LauncherController : IDisposable
public void ReloadConfig() public void ReloadConfig()
{ {
Config = ConfigService.Load(); Config = ConfigService.Load();
ThemeService.Apply(Config.ThemeScheme);
RegisterHotkey(); RegisterHotkey();
popup?.Close(); popup?.Close();
popup = null; popup = null;

View File

@@ -1,6 +1,7 @@
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using Microsoft.Win32; using Microsoft.Win32;
using TaskbarLauncher.Models;
using MediaColor = System.Windows.Media.Color; using MediaColor = System.Windows.Media.Color;
namespace TaskbarLauncher.Services; namespace TaskbarLauncher.Services;
@@ -10,42 +11,32 @@ public static class ThemeService
private const string PersonalizeKey = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"; private const string PersonalizeKey = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
private const string DwmKey = @"Software\Microsoft\Windows\DWM"; private const string DwmKey = @"Software\Microsoft\Windows\DWM";
public static void Apply() public static void Apply(LauncherThemeScheme scheme = LauncherThemeScheme.MarkHaven)
{ {
bool isLight = IsLightTheme(); bool isLight = IsLightTheme();
MediaColor accent = GetAccentColor(); ThemePalette palette = GetPalette(scheme, isLight);
MediaColor orange = MediaColor.FromRgb(0xD8, 0x78, 0x1D); MediaColor accent = palette.Accent;
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);
MediaColor selectedText = IsDark(accent) ? System.Windows.Media.Colors.White : System.Windows.Media.Colors.Black; MediaColor selectedText = IsDark(accent) ? System.Windows.Media.Colors.White : System.Windows.Media.Colors.Black;
SetBrush("AppWindowBrush", window); SetBrush("AppWindowBrush", palette.Window);
SetBrush("AppSurfaceBrush", surface); SetBrush("AppSurfaceBrush", palette.Surface);
SetBrush("AppPanelBrush", panel); SetBrush("AppPanelBrush", palette.Panel);
SetBrush("AppControlBrush", control); SetBrush("AppControlBrush", palette.Control);
SetBrush("AppControlHoverBrush", hover); SetBrush("AppControlHoverBrush", palette.Hover);
SetBrush("AppTextBrush", text); SetBrush("AppTextBrush", palette.Text);
SetBrush("AppMutedTextBrush", muted); SetBrush("AppMutedTextBrush", palette.Muted);
SetBrush("AppBorderBrush", border); SetBrush("AppBorderBrush", palette.Border);
SetBrush("AppAccentBrush", accent); SetBrush("AppAccentBrush", accent);
SetBrush("AppAccentSoftBrush", accentSoft); SetBrush("AppAccentSoftBrush", palette.AccentSoft);
SetBrush("AppOrangeBrush", orange); SetBrush("AppOrangeBrush", palette.Orange);
SetBrush("AppOrangeSoftBrush", orangeSoft); SetBrush("AppOrangeSoftBrush", palette.OrangeSoft);
SetBrush("AppSelectedTextBrush", selectedText); SetBrush("AppSelectedTextBrush", selectedText);
SetBrush(System.Windows.SystemColors.WindowBrushKey, surface); SetBrush(System.Windows.SystemColors.WindowBrushKey, palette.Surface);
SetBrush(System.Windows.SystemColors.WindowTextBrushKey, text); SetBrush(System.Windows.SystemColors.WindowTextBrushKey, palette.Text);
SetBrush(System.Windows.SystemColors.ControlBrushKey, control); SetBrush(System.Windows.SystemColors.ControlBrushKey, palette.Control);
SetBrush(System.Windows.SystemColors.ControlTextBrushKey, text); SetBrush(System.Windows.SystemColors.ControlTextBrushKey, palette.Text);
SetBrush(System.Windows.SystemColors.ControlDarkBrushKey, border); SetBrush(System.Windows.SystemColors.ControlDarkBrushKey, palette.Border);
SetBrush(System.Windows.SystemColors.HighlightBrushKey, accent); SetBrush(System.Windows.SystemColors.HighlightBrushKey, accent);
SetBrush(System.Windows.SystemColors.HighlightTextBrushKey, selectedText); SetBrush(System.Windows.SystemColors.HighlightTextBrushKey, selectedText);
} }
@@ -70,6 +61,101 @@ public static class ThemeService
return MediaColor.FromRgb(0x00, 0x78, 0xD4); 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) private static MediaColor Blend(MediaColor foreground, MediaColor background, double foregroundAmount)
{ {
double amount = Math.Clamp(foregroundAmount, 0, 1); 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); double luminance = (0.299 * color.R) + (0.587 * color.G) + (0.114 * color.B);
return luminance < 140; 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);
} }