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)