From 9c58eea7380ac3be97003d94c57797be627338a3 Mon Sep 17 00:00:00 2001 From: Stuart Meeks Date: Mon, 1 Jun 2026 08:12:12 +0000 Subject: [PATCH] Fix caption buttons invisible in Light theme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The minimise/maximise/close glyphs were always the default white, so they all but vanished against the light title bar. The app never set AppWindow.TitleBar's button colours. ApplyTheme (now public, the single theming entry point — WindowsThemeApplier delegates to it) sets theme-appropriate caption-glyph colours from the content's ActualTheme, keeping backgrounds transparent so Mica shows through. An ActualThemeChanged handler refreshes them when the OS flips light/dark while the preference is System. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 6 ++ src/Snipdeck.App/MainWindow.xaml.cs | 69 ++++++++++++++++--- .../Services/WindowsThemeApplier.cs | 15 +--- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1ed379..10b3f38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 remove icons (the picker reflects the change the next time it opens), and your edits survive app updates. Delete it to restore the default set. +### Fixed +- **Window caption buttons invisible in Light theme.** The minimise, maximise and + close glyphs were always white, leaving them barely visible against the light + title bar. They now take a theme-appropriate colour, update when you switch + theme, and follow the OS when the theme is set to System. + ### Changed - **Home, navigation and shared-parameters polish.** The Home page leads with a full-bleed hero banner (drop `Assets/HomeHero.png` to supply the image), the diff --git a/src/Snipdeck.App/MainWindow.xaml.cs b/src/Snipdeck.App/MainWindow.xaml.cs index a49e386..ad59330 100644 --- a/src/Snipdeck.App/MainWindow.xaml.cs +++ b/src/Snipdeck.App/MainWindow.xaml.cs @@ -35,6 +35,14 @@ public MainWindow(AppConfig config, ShellPage shellPage) ShellHost.Content = shellPage; ApplyTheme(config.Theme); + + // While on System theme, the caption buttons must follow an OS + // light/dark flip too — RequestedTheme stays Default, so only + // ActualTheme changes. + if (Content is FrameworkElement themedRoot) + { + themedRoot.ActualThemeChanged += (sender, _) => UpdateCaptionButtonColours(sender.ActualTheme); + } } // The title-bar switcher and snip search bind to the shell view model. @@ -129,18 +137,61 @@ private void OnSearchQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySu } } - private void ApplyTheme(ThemePreference theme) + /// + /// Applies the chosen theme to the app content and the system caption + /// buttons. Public so the runtime theme applier routes through here, + /// keeping the window content and the min/max/close glyphs in step. + /// + public void ApplyTheme(ThemePreference theme) { - if (Content is FrameworkElement root) + if (Content is not FrameworkElement root) { - root.RequestedTheme = theme switch - { - ThemePreference.Light => ElementTheme.Light, - ThemePreference.Dark => ElementTheme.Dark, - ThemePreference.System => ElementTheme.Default, - _ => ElementTheme.Default, - }; + return; } + + root.RequestedTheme = theme switch + { + ThemePreference.Light => ElementTheme.Light, + ThemePreference.Dark => ElementTheme.Dark, + ThemePreference.System => ElementTheme.Default, + _ => ElementTheme.Default, + }; + + // ActualTheme resolves Default to the OS light/dark choice, so the + // caption glyphs get a concrete theme to contrast against. + UpdateCaptionButtonColours(root.ActualTheme); + } + + // The caption buttons are system-drawn chrome, not styled by the app's + // theme resources — so without this they keep their default (white) + // glyphs and vanish on a light background. Backgrounds stay transparent + // so the Mica backdrop shows through; only the glyph colours change. + private void UpdateCaptionButtonColours(ElementTheme actualTheme) + { + var dark = actualTheme == ElementTheme.Dark; + + var foreground = dark + ? Microsoft.UI.Colors.White + : Windows.UI.Color.FromArgb(0xFF, 0x1A, 0x1A, 0x1A); + var inactive = dark + ? Windows.UI.Color.FromArgb(0xFF, 0x80, 0x80, 0x80) + : Windows.UI.Color.FromArgb(0xFF, 0x8A, 0x8A, 0x8A); + var hoverBackground = dark + ? Windows.UI.Color.FromArgb(0x1F, 0xFF, 0xFF, 0xFF) + : Windows.UI.Color.FromArgb(0x14, 0x00, 0x00, 0x00); + var pressedBackground = dark + ? Windows.UI.Color.FromArgb(0x12, 0xFF, 0xFF, 0xFF) + : Windows.UI.Color.FromArgb(0x0A, 0x00, 0x00, 0x00); + + var titleBar = AppWindow.TitleBar; + titleBar.ButtonBackgroundColor = Microsoft.UI.Colors.Transparent; + titleBar.ButtonInactiveBackgroundColor = Microsoft.UI.Colors.Transparent; + titleBar.ButtonForegroundColor = foreground; + titleBar.ButtonHoverForegroundColor = foreground; + titleBar.ButtonHoverBackgroundColor = hoverBackground; + titleBar.ButtonPressedForegroundColor = foreground; + titleBar.ButtonPressedBackgroundColor = pressedBackground; + titleBar.ButtonInactiveForegroundColor = inactive; } } } diff --git a/src/Snipdeck.App/Services/WindowsThemeApplier.cs b/src/Snipdeck.App/Services/WindowsThemeApplier.cs index f91346f..3051734 100644 --- a/src/Snipdeck.App/Services/WindowsThemeApplier.cs +++ b/src/Snipdeck.App/Services/WindowsThemeApplier.cs @@ -1,5 +1,3 @@ -using Microsoft.UI.Xaml; - using Snipdeck.Core.Abstractions; using Snipdeck.Core.Models; @@ -17,17 +15,10 @@ public WindowsThemeApplier(IServiceProvider services) public void Apply(ThemePreference theme) { + // Route through the window so the content and the system caption + // buttons are themed together (see MainWindow.ApplyTheme). var mainWindow = (MainWindow?)_services.GetService(typeof(MainWindow)); - if (mainWindow?.Content is FrameworkElement root) - { - root.RequestedTheme = theme switch - { - ThemePreference.Light => ElementTheme.Light, - ThemePreference.Dark => ElementTheme.Dark, - ThemePreference.System => ElementTheme.Default, - _ => ElementTheme.Default, - }; - } + mainWindow?.ApplyTheme(theme); } } }