diff --git a/src/settings-ui/Settings.UI/Helpers/TitleBarHelper.cs b/src/settings-ui/Settings.UI/Helpers/TitleBarHelper.cs
new file mode 100644
index 000000000000..24a3047594d3
--- /dev/null
+++ b/src/settings-ui/Settings.UI/Helpers/TitleBarHelper.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.UI;
+using Microsoft.UI.Xaml;
+using Windows.UI;
+
+namespace Microsoft.PowerToys.Settings.UI.Helpers
+{
+ ///
+ /// Helpers for theming the system caption buttons (minimize/maximize/close) of a window.
+ ///
+ public static class TitleBarHelper
+ {
+ ///
+ /// Applies the given element theme to a window's system caption buttons.
+ ///
+ ///
+ /// Workaround for the AppWindow TitleBar not updating caption button colors to match the
+ /// app theme when the OS theme differs from the app theme or the theme changes at runtime.
+ /// Mirrors the helper used by the WinUI Gallery (https://github.com/microsoft/WinUI-Gallery).
+ ///
+ public static void ApplySystemThemeToCaptionButtons(Window window, ElementTheme theme)
+ {
+ if (window?.AppWindow is null)
+ {
+ return;
+ }
+
+ var titleBar = window.AppWindow.TitleBar;
+ var foregroundColor = theme == ElementTheme.Dark ? Colors.White : Colors.Black;
+
+ titleBar.ButtonBackgroundColor = Colors.Transparent;
+ titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
+ titleBar.ButtonForegroundColor = foregroundColor;
+ titleBar.ButtonHoverForegroundColor = foregroundColor;
+ titleBar.ButtonInactiveForegroundColor = Colors.DarkGray;
+ titleBar.ButtonHoverBackgroundColor = theme == ElementTheme.Dark
+ ? Color.FromArgb(24, 255, 255, 255)
+ : Color.FromArgb(24, 0, 0, 0);
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml
index 2adb6b3a7ea8..f7efb4ea6603 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml
@@ -52,11 +52,18 @@
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml.cs
index a26d15759c1d..d1e5007a7187 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml.cs
@@ -7,10 +7,12 @@
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
+using CommunityToolkit.WinUI.Controls;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Microsoft.UI.Xaml.Navigation;
@@ -19,6 +21,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
public sealed partial class ScoobeReleaseNotesPage : Page
{
private IList _currentReleases;
+ private string _releaseNotesMarkdownText;
///
/// Initializes a new instance of the class.
@@ -26,6 +29,37 @@ public sealed partial class ScoobeReleaseNotesPage : Page
public ScoobeReleaseNotesPage()
{
this.InitializeComponent();
+
+ // Re-apply the markdown theme workaround when the theme changes at runtime so the
+ // headings/links stay readable after the user switches between light and dark.
+ this.ActualThemeChanged += OnActualThemeChanged;
+ this.Unloaded += OnUnloaded;
+ }
+
+ private void OnUnloaded(object sender, RoutedEventArgs e)
+ {
+ this.ActualThemeChanged -= OnActualThemeChanged;
+ this.Unloaded -= OnUnloaded;
+ }
+
+ private void OnActualThemeChanged(FrameworkElement sender, object args)
+ {
+ RefreshMarkdownTheme();
+ }
+
+ private void RefreshMarkdownTheme()
+ {
+ if (string.IsNullOrEmpty(_releaseNotesMarkdownText))
+ {
+ return;
+ }
+
+ ApplyMarkdownThemeWorkaround();
+
+ // The MarkdownTextBlock captures heading/link brushes when it renders, so re-set the
+ // text to force it to rebuild with the brushes for the now-active theme.
+ ReleaseNotesMarkdown.Text = string.Empty;
+ ReleaseNotesMarkdown.Text = _releaseNotesMarkdownText;
}
///
@@ -128,7 +162,18 @@ private void DisplayReleaseNotes()
{
LoadingProgressRing.Visibility = Visibility.Collapsed;
+ // Workaround: the MarkdownTextBlock control captures its heading foreground
+ // brushes from Application.Current.Resources when its theme config is created,
+ // which resolves against the OS (application) theme rather than the app's
+ // selected theme. When the OS is Light but PowerToys is Dark (or vice versa),
+ // headings render with an unreadable color. Force the control's theme and
+ // reapply correctly-themed heading brushes before the markdown is rendered.
+ // TODO: Remove once the upstream control resolves brushes against the element theme.
+ // Upstream fix: https://github.com/CommunityToolkit/Labs-Windows/pull/785
+ ApplyMarkdownThemeWorkaround();
+
var (releaseNotesMarkdown, heroImageUrl) = ProcessReleaseNotesMarkdown(_currentReleases);
+ _releaseNotesMarkdownText = releaseNotesMarkdown;
// Set the Hero image if found
if (!string.IsNullOrEmpty(heroImageUrl))
@@ -150,6 +195,46 @@ private void DisplayReleaseNotes()
}
}
+ ///
+ /// Works around the control pinning its heading and link
+ /// brushes to the OS (application) theme instead of the element's selected theme, which makes
+ /// titles/links unreadable when the OS and PowerToys themes differ. Pins the control's theme and
+ /// reassigns the heading/link brushes resolved for the selected theme before the markdown renders.
+ /// TODO: Remove once the upstream control resolves brushes against the element theme.
+ /// Upstream fix: https://github.com/CommunityToolkit/Labs-Windows/pull/785
+ ///
+ private void ApplyMarkdownThemeWorkaround()
+ {
+ var elementTheme = App.IsDarkTheme() ? ElementTheme.Dark : ElementTheme.Light;
+ ReleaseNotesMarkdown.RequestedTheme = elementTheme;
+ LinkBrushProvider.RequestedTheme = elementTheme;
+
+ if (Resources["ReleaseNotesMarkdownConfig"] is MarkdownConfig config
+ && config.Themes is MarkdownThemes themes)
+ {
+ // The control's Foreground is bound to TextFillColorPrimaryBrush via ThemeResource,
+ // so after setting RequestedTheme it resolves to the brush for the selected theme.
+ // Reuse it for the heading brushes, which the control would otherwise pin to the OS theme.
+ if (ReleaseNotesMarkdown.Foreground is Brush headingForeground)
+ {
+ themes.H1Foreground = headingForeground;
+ themes.H2Foreground = headingForeground;
+ themes.H3Foreground = headingForeground;
+ themes.H4Foreground = headingForeground;
+ themes.H5Foreground = headingForeground;
+ themes.H6Foreground = headingForeground;
+ }
+
+ // The link brush is likewise pinned to the OS theme's accent color, which can be
+ // unreadable when the app theme differs from the OS theme. Reapply the accent brush
+ // resolved for the selected theme using the hidden helper element.
+ if (LinkBrushProvider.Foreground is Brush linkForeground)
+ {
+ themes.LinkForeground = linkForeground;
+ }
+ }
+ }
+
private void Page_Loaded(object sender, RoutedEventArgs e)
{
DisplayReleaseNotes();
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs
index 0bebb825bfe8..67001d2c25a0 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs
@@ -57,6 +57,16 @@ private void SetTitleBar()
this.ExtendsContentIntoTitleBar = true;
this.SetTitleBar(AppTitleBar);
Title = ResourceLoaderInstance.ResourceLoader.GetString("ScoobeWindow_Title");
+
+ // The built-in WinUI TitleBar does not tint the system caption buttons (min/max/close)
+ // to match the app's selected theme, so they can be unreadable when the OS theme differs
+ // from the PowerToys theme. Drive their colors from the window content's actual theme.
+ if (this.Content is FrameworkElement rootElement)
+ {
+ TitleBarHelper.ApplySystemThemeToCaptionButtons(this, rootElement.ActualTheme);
+ rootElement.ActualThemeChanged += (s, e) =>
+ TitleBarHelper.ApplySystemThemeToCaptionButtons(this, s.ActualTheme);
+ }
}
private void Window_Activated(object sender, WindowActivatedEventArgs args)