feat: Implement High Contrast Mode Support for Skia#22965
feat: Implement High Contrast Mode Support for Skia#22965morning4coffe-dev wants to merge 2 commits intomasterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds cross-platform High Contrast mode plumbing (detection, notifications, and resource updates) with a focus on Skia and WebAssembly, and wires High Contrast adjustments into Skia text rendering and theme resource resolution.
Changes:
- Introduces platform high-contrast detection + change notifications via
SystemThemeHelper(Skia/Win32/Linux/macOS/WASM + Android/iOS hooks). - Updates theme/resource resolution to support a dedicated
"HighContrast"theme dictionary and to refresh theme bindings when HC state/colors change. - Adds High Contrast text adjustment (opacity + backplate) in Skia text rendering and includes new unit/runtime tests + sample updates.
Reviewed changes
Copilot reviewed 37 out of 40 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Uno.UWP/UI/ViewManagement/UISettings.cs | Returns foreground/background from high-contrast system colors when available. |
| src/Uno.UWP/UI/ViewManagement/AccessibilitySettings.cs | Implements HC/HC scheme for WASM+Skia and adds internal IsHighContrastActive for theme selection. |
| src/Uno.UWP/ts/Windows/Helpers/Theming/SystemThemeHelper.ts | Adds WASM-side high contrast detection + observers via matchMedia. |
| src/Uno.UWP/js/Uno.Wasm.d.ts | Updates typings for new WASM high contrast interop methods. |
| src/Uno.UWP/Helpers/Theming/SystemThemeHelper.wasm.cs | Adds WASM high contrast polling + dispatch callback into .NET. |
| src/Uno.UWP/Helpers/Theming/SystemThemeHelper.UIKit.cs | Adds iOS/tvOS “increase contrast” observation + mapping to HC scheme/colors. |
| src/Uno.UWP/Helpers/Theming/SystemThemeHelper.skia.cs | Adds Skia extension surface for HC state/scheme/colors + events. |
| src/Uno.UWP/Helpers/Theming/SystemThemeHelper.Interop.wasm.cs | Adds JSImport bindings for WASM high contrast query/observer. |
| src/Uno.UWP/Helpers/Theming/SystemThemeHelper.cs | Centralizes HC state/scheme/colors caching and change detection alongside theme detection. |
| src/Uno.UWP/Helpers/Theming/SystemThemeHelper.Android.cs | Adds Android high-contrast detection + observers (secure setting + accessibility listener). |
| src/Uno.UWP/Helpers/Theming/ISystemThemeHelperExtension.skia.cs | Extends Skia theme helper extension contract with HC APIs + event. |
| src/Uno.UWP/Helpers/Theming/HighContrastSystemColors.cs | Adds a struct to carry platform HC system colors. |
| src/Uno.UWP/FeatureConfiguration/WinRTFeatureConfiguration.Accessibility.cs | Adds HC overrides (state/scheme/colors) and notifies UISettings/AccessibilitySettings. |
| src/Uno.UI/UI/Xaml/UIElement.HighContrast.cs | Adds HighContrastAdjustment DP and helpers for HC text adjustment. |
| src/Uno.UI/UI/Xaml/ResourceResolver.cs | Updates theme-binding refresh path and attempts to apply platform HC system colors into resources. |
| src/Uno.UI/UI/Xaml/ResourceDictionary.cs | Adds HC-aware active theme dictionary selection and improves theme-binding update traversal snapshots. |
| src/Uno.UI/UI/Xaml/FrameworkElement.Theming.cs | Ensures theme propagation/push uses "HighContrast" key when HC is active. |
| src/Uno.UI/UI/Xaml/Documents/UnicodeText.skia.cs | Adds HC text adjustment (opacity + backplate + selection foreground) for shaped text path. |
| src/Uno.UI/UI/Xaml/Documents/ParsedText.skia.cs | Adds HC text adjustment (opacity + backplate + selection foreground) for parsed text path. |
| src/Uno.UI/UI/Xaml/Documents/IParsedText.skia.cs | Updates draw signature to pass owning element for HC adjustment logic. |
| src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.skia.cs | Passes this into ParsedText.Draw for HC adjustment. |
| src/Uno.UI/UI/Xaml/ApplicationHelper.cs | Ensures resource theme key is updated before reapplying theme. |
| src/Uno.UI/UI/Xaml/Application.HighContrast.cs | Adds Application.HighContrastAdjustment storage + invalidation hook (platform-limited build). |
| src/Uno.UI/UI/Xaml/Application.cs | Hooks HC change events, updates resource/theme propagation, and invalidates visuals for HC adjustment changes. |
| src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/UIElement.cs | Removes generated HC adjustment members in favor of manual implementation. |
| src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/Application.cs | Removes generated HC adjustment member in favor of manual implementation. |
| src/Uno.UI.Tests/Windows_UI_Xaml/Given_ResourceDictionary.cs | Adds unit tests for HC theme dictionary caching + HC system color reapplication. |
| src/Uno.UI.Tests/Windows_UI_Xaml/Given_HighContrastAdjustment.cs | Adds unit tests for Application/UIElement HC adjustment defaults and inheritance. |
| src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_ResourceDictionary.cs | Adds runtime test ensuring theme binding updates work with lazy theme dictionaries. |
| src/Uno.UI.Runtime.Skia.X11/Helpers/Theming/LinuxSystemThemeHelper.cs | Adds Linux HC detection (portal contrast + fallback heuristics) + change event. |
| src/Uno.UI.Runtime.Skia.Win32/Helpers/Theming/Win32SystemThemeHelperExtension.cs | Adds Win32 HC detection, scheme naming, and system color extraction. |
| src/Uno.UI.Runtime.Skia.Win32.Support/NativeMethods.txt | Adds Win32 APIs needed for HC detection/system colors to the support list. |
| src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.m | Adds macOS “increase contrast” getter + change notification callback. |
| src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.h | Declares macOS HC callback + getter exports. |
| src/Uno.UI.Runtime.Skia.MacOS/Native/NativeUno.cs | Adds P/Invoke for macOS HC getter + callback registration. |
| src/Uno.UI.Runtime.Skia.MacOS/Helpers/Theming/MacOSSystemThemeHelperExtension.cs | Adds macOS HC reporting + event propagation into the Skia extension. |
| src/Uno.UI.Runtime.Skia.AppleUIKit/UI/Xaml/Window/RootViewController.cs | Refreshes HC state on trait changes. |
| src/Uno.UI.Runtime.Skia.Android/ApplicationActivity.cs | Refreshes HC state on configuration changes. |
| src/SamplesApp/UITests.Shared/Windows_UI_Xaml/ThemeResources/BasicThemeResources.xaml.cs | Fixes parent theme toggling logic and null-safety. |
| src/SamplesApp/UITests.Shared/Windows_UI_Xaml/ThemeResources/BasicThemeResources.xaml | Updates sample resources to use SystemColor* in HC + demonstrates HC adjustment opt-out. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private static void ApplyHighContrastSystemColors(ResourceDictionary dictionary, HighContrastSystemColors? highContrastSystemColors) | ||
| { | ||
| if (!dictionary.TryGetThemeDictionary("HighContrast", out var highContrastThemeDictionary)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var baselineColors = GetOrCreateHighContrastSystemColorDefaults(highContrastThemeDictionary); | ||
|
|
||
| foreach (var (resourceKey, selector) in _highContrastSystemColorMappings) | ||
| { | ||
| var targetColor = highContrastSystemColors is { } colors | ||
| ? selector(colors) | ||
| : selector(baselineColors); | ||
|
|
||
| SetColorResource(highContrastThemeDictionary, resourceKey, targetColor); | ||
| } |
There was a problem hiding this comment.
UpdateHighContrastSystemColors() currently applies SystemColor* overrides to any ResourceDictionary that has a "HighContrast" theme dictionary. Some system theme dictionaries (e.g. FluentTheme Common_themeresources.xaml) reference SystemColorWindowColor/etc but do not define those keys locally, so GetColorResource() falls back to Transparent and SetColorResource() then injects Transparent values. This breaks HighContrast rendering on platforms where HighContrastSystemColors is null even while HC is active (e.g. WASM where GetHighContrastSystemColorsPlatform is unimplemented), because ThemeResource lookups will be shadowed by the injected Transparent values. Consider only setting colors for keys that already exist in the target dictionary (skip missing keys), and/or resolve baseline via shouldCheckSystem:true so referenced system defaults are used instead of Transparent when platform colors aren't available.
| /// <summary> | ||
| /// Gets whether the system High Contrast mode is currently enabled. | ||
| /// </summary> | ||
| internal static bool IsHighContrastEnabled => _lastHighContrast ??= GetIsHighContrastEnabled(); | ||
|
|
||
| /// <summary> | ||
| /// Gets the name of the active High Contrast scheme. | ||
| /// </summary> | ||
| internal static string HighContrastSchemeName => _lastHighContrastScheme ??= GetHighContrastSchemeName(); | ||
|
|
||
| /// <summary> | ||
| /// Gets the current High Contrast system colors, if available. | ||
| /// </summary> | ||
| internal static HighContrastSystemColors? HighContrastSystemColors => GetCurrentHighContrastSystemColors(IsHighContrastEnabled); | ||
|
|
There was a problem hiding this comment.
HighContrastSystemColors is recomputed on every access (calls GetCurrentHighContrastSystemColors + potentially platform interop). This property is used during rendering (e.g., UIElement.GetHighContrastTextColors during Skia text draw), so repeated P/Invokes/JS interop can become a hot-path cost. Consider returning the cached _lastHighContrastSystemColors (initialized in ObserveThemeChanges and updated in RefreshHighContrast), with a lazy fallback for cases where observation hasn't started yet.
| /// <summary> | ||
| /// Gets whether High Contrast is currently active (from any source). | ||
| /// This is used internally for theme selection. | ||
| /// </summary> | ||
| internal static bool IsHighContrastActive => | ||
| #if __SKIA__ || __WASM__ | ||
| WinRTFeatureConfiguration.Accessibility.HighContrastOverride ?? SystemThemeHelper.IsHighContrastEnabled; | ||
| #else | ||
| WinRTFeatureConfiguration.Accessibility.HighContrast; | ||
| #endif |
There was a problem hiding this comment.
IsHighContrastActive is used by Uno.UI for theme dictionary selection, but it only consults OS high-contrast state on SKIA/WASM. This means OS high-contrast changes detected by SystemThemeHelper on Android/iOS/macOS/etc (you added observers + RefreshHighContrast calls) won’t actually activate the HighContrast theme dictionaries because IsHighContrastActive remains tied to WinRTFeatureConfiguration.Accessibility.HighContrast on those platforms. Consider folding SystemThemeHelper.IsHighContrastEnabled into IsHighContrastActive for the other platforms that now report HC, while still allowing WinRTFeatureConfiguration overrides to force-enable/disable.
|
🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-22965/wasm-skia-net9/index.html |
|
The build 205560 found UI Test snapshots differences: Details
|
c688332 to
f94fdf0
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 38 out of 42 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private static readonly Logger _log = typeof(ResourceResolver).Log(); | ||
| private static readonly Dictionary<ResourceDictionary, HighContrastSystemColors> _highContrastSystemColorDefaults = new(); | ||
| private static readonly (string ResourceKey, Func<HighContrastSystemColors, Color> Selector)[] _highContrastSystemColorMappings = |
There was a problem hiding this comment.
_highContrastSystemColorDefaults caches baseline colors keyed by ResourceDictionary with strong references and is never cleared. If HighContrast theme dictionaries are created dynamically (e.g., per XamlRoot or during hot reload), this can retain them indefinitely. Consider using a ConditionalWeakTable<ResourceDictionary, HighContrastSystemColors> (or another weak-key approach) so dictionaries can be collected, or explicitly evict entries when dictionaries are no longer reachable.
| { | ||
| ApplyHighContrastSystemColors(dictionary, highContrastSystemColors); | ||
|
|
||
| foreach (var mergedDictionary in dictionary.MergedDictionaries) |
There was a problem hiding this comment.
UpdateHighContrastSystemColors recurses over dictionary.MergedDictionaries using foreach. Since MergedDictionaries is an ObservableCollection, modifying merged dictionaries during this update (e.g., hot reload or runtime dictionary changes) can throw 'Collection was modified' exceptions. Consider iterating a snapshot (e.g., ToArray()) or index-based copy before recursing.
| foreach (var mergedDictionary in dictionary.MergedDictionaries) | |
| foreach (var mergedDictionary in dictionary.MergedDictionaries.ToArray()) |
| /// <summary> | ||
| /// Forces all ResourceDictionary instances to re-evaluate their active theme dictionary. | ||
| /// This is needed when High Contrast state changes but the base theme key remains the same. | ||
| /// </summary> | ||
| internal void InvalidateActiveThemeDictionary() | ||
| { | ||
| _activeTheme = ResourceKey.Empty; |
There was a problem hiding this comment.
The XML doc for InvalidateActiveThemeDictionary says it "Forces all ResourceDictionary instances" to re-evaluate their active theme dictionary, but this is an instance method and only invalidates the current dictionary. Consider rewording the comment to avoid implying global invalidation, or (if global invalidation is intended) route this through a centralized mechanism.
|
🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-22965/wasm-skia-net9/index.html |
|
The build 206490 found UI Test snapshots differences: Details
|
899b92e to
f7430b2
Compare
GitHub Issue: closes https://github.com/unoplatform/private/issues/1073
PR Type:
What is the new behavior? 🚀
This pull request improves theme resource handling and high contrast support across platforms, especially for macOS and Windows. It introduces new theme resource brushes for accent and text colors, updates the sample app to use these resources for better visual clarity, and adds platform-level detection and notification for high contrast mode changes. Additionally, it refines the logic for theme switching in the sample app and removes some unused code.
These changes lay the groundwork for more robust accessibility support and make theme resources more flexible and maintainable across platforms and get closer to WinUI3.
PR Checklist ✅
Please check if your PR fulfills the following requirements:
Screenshots Compare Test Runresults.