Browse Source

Add ColorContrastPreference to the PlatformColorValues

pull/9913/head
Max Katz 3 years ago
parent
commit
13c14e7360
  1. 14
      native/Avalonia.Native/src/OSX/PlatformSettings.mm
  2. 37
      src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs
  3. 5
      src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
  4. 64
      src/Avalonia.Base/Platform/PlatformColorValues.cs
  5. 9
      src/Avalonia.FreeDesktop/DBusPlatformSettings.cs
  6. 22
      src/Avalonia.Native/NativePlatformSettings.cs
  7. 4
      src/Avalonia.Native/avn.idl
  8. 13
      src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs
  9. 6
      src/Browser/Avalonia.Browser/Interop/DomHelper.cs
  10. 18
      src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts
  11. 11
      src/Windows/Avalonia.Win32/Win32Platform.cs
  12. 45
      src/Windows/Avalonia.Win32/Win32PlatformSettings.cs
  13. 26
      src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs
  14. 7
      src/Windows/Avalonia.Win32/WinRT/winrt.idl
  15. 8
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  16. 11
      src/iOS/Avalonia.iOS/PlatformSettings.cs

14
native/Avalonia.Native/src/OSX/PlatformSettings.mm

@ -17,15 +17,17 @@ public:
if (@available(macOS 10.14, *))
{
if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAqua
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantLight
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastAqua
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantLight) {
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantLight) {
return AvnPlatformThemeVariant::Light;
} else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameDarkAqua
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantDark
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastDarkAqua
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantDark) {
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantDark) {
return AvnPlatformThemeVariant::Dark;
} else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastAqua
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantLight) {
return AvnPlatformThemeVariant::HighContrastLight;
} else if (|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastDarkAqua
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantDark) {
return AvnPlatformThemeVariant::HighContrastDark;
}
}
return AvnPlatformThemeVariant::Light;

37
src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs

@ -3,6 +3,8 @@ using Android;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.Provider;
using Android.Views.Accessibility;
using AndroidX.Core.Content.Resources;
using Avalonia.Media;
using Avalonia.Platform;
@ -46,11 +48,14 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings
var accent2 = context.Resources.GetColor(17170507, context.Theme); // Resource.Color.SystemAccent2500
var accent3 = context.Resources.GetColor(17170520, context.Theme); // Resource.Color.SystemAccent3500
_latestValues = new PlatformColorValues(
systemTheme,
new Color(accent1.A, accent1.R, accent1.G, accent1.B),
new Color(accent2.A, accent2.R, accent2.G, accent2.B),
new Color(accent3.A, accent3.R, accent3.G, accent3.B));
_latestValues = new PlatformColorValues
{
ThemeVariant = systemTheme,
ContrastPreference = IsHighContrast(context),
AccentColor1 = new Color(accent1.A, accent1.R, accent1.G, accent1.B),
AccentColor2 = new Color(accent2.A, accent2.R, accent2.G, accent2.B),
AccentColor3 = new Color(accent3.A, accent3.R, accent3.G, accent3.B),
};
}
else if (OperatingSystem.IsAndroidVersionAtLeast(23))
{
@ -58,9 +63,12 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings
var array = context.Theme.ObtainStyledAttributes(new[] { 16843829 }); // Resource.Attribute.ColorAccent
var accent = array.GetColor(0, 0);
_latestValues = new PlatformColorValues(
systemTheme,
new Color(accent.A, accent.R, accent.G, accent.B));
_latestValues = new PlatformColorValues
{
ThemeVariant = systemTheme,
ContrastPreference = IsHighContrast(context),
AccentColor1 = new Color(accent.A, accent.R, accent.G, accent.B)
};
array.Recycle();
}
else
@ -70,4 +78,17 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings
OnColorValuesChanged(_latestValues);
}
private static ColorContrastPreference IsHighContrast(Context context)
{
try
{
return Settings.Secure.GetInt(context.ContentResolver, "high_text_contrast_enabled", 0) == 1
? ColorContrastPreference.High : ColorContrastPreference.NoPreference;
}
catch
{
return ColorContrastPreference.NoPreference;
}
}
}

5
src/Avalonia.Base/Platform/DefaultPlatformSettings.cs

@ -31,7 +31,10 @@ namespace Avalonia.Platform
public virtual PlatformColorValues GetColorValues()
{
return new PlatformColorValues(PlatformThemeVariant.Light);
return new PlatformColorValues
{
ThemeVariant = PlatformThemeVariant.Light
};
}
public event EventHandler<PlatformColorValues>? ColorValuesChanged;

64
src/Avalonia.Base/Platform/PlatformColorValues.cs

@ -11,32 +11,58 @@ public enum PlatformThemeVariant
Dark
}
/// <summary>
/// System high contrast preference.
/// </summary>
public enum ColorContrastPreference
{
NoPreference,
High
}
/// <summary>
/// Information about current system color values, including information about dark mode and accent colors.
/// </summary>
/// <param name="ThemeVariant">System theme variant or mode.</param>
/// <param name="AccentColor1">Primary system accent color.</param>
/// <param name="AccentColor2">Secondary system accent color. On some platforms can return the same value as AccentColor1.</param>
/// <param name="AccentColor3">Tertiary system accent color. On some platforms can return the same value as AccentColor1.</param>
public record struct PlatformColorValues(
PlatformThemeVariant ThemeVariant,
Color AccentColor1,
Color AccentColor2,
Color AccentColor3)
public record PlatformColorValues
{
public PlatformColorValues(
PlatformThemeVariant ThemeVariant,
Color AccentColor1)
: this(ThemeVariant, AccentColor1, AccentColor1, AccentColor1)
private static Color DefaultAccent => new(255, 0, 120, 215);
private Color _accentColor2, _accentColor3;
/// <summary>
/// System theme variant or mode.
/// </summary>
public PlatformThemeVariant ThemeVariant { get; init; }
/// <summary>
/// System high contrast preference.
/// </summary>
public ColorContrastPreference ContrastPreference { get; init; }
/// <summary>
/// Primary system accent color.
/// </summary>
public Color AccentColor1 { get; init; }
/// <summary>
/// Secondary system accent color. On some platforms can return the same value as <see cref="AccentColor1"/>.
/// </summary>
public Color AccentColor2
{
get => _accentColor2 != default ? _accentColor2 : AccentColor1;
init => _accentColor2 = value;
}
public PlatformColorValues(PlatformThemeVariant ThemeVariant)
: this(ThemeVariant, DefaultAccent)
/// <summary>
/// Tertiary system accent color. On some platforms can return the same value as <see cref="AccentColor1"/>.
/// </summary>
public Color AccentColor3
{
get => _accentColor3 != default ? _accentColor3 : AccentColor1;
init => _accentColor3 = value;
}
public PlatformColorValues()
{
AccentColor1 = DefaultAccent;
}
private static Color DefaultAccent => new(255, 0, 120, 215);
}

9
src/Avalonia.FreeDesktop/DBusPlatformSettings.cs

@ -41,7 +41,7 @@ internal class DBusPlatformSettings : DefaultPlatformSettings
{
var value = await colorSchemeTask;
_lastColorValues = GetColorValuesFromSetting(value);
OnColorValuesChanged(_lastColorValues.Value);
OnColorValuesChanged(_lastColorValues);
}
catch (Exception ex)
{
@ -62,13 +62,16 @@ internal class DBusPlatformSettings : DefaultPlatformSettings
<member>2: Prefer light appearance</member>
*/
_lastColorValues = GetColorValuesFromSetting(tuple.value);
OnColorValuesChanged(_lastColorValues.Value);
OnColorValuesChanged(_lastColorValues);
}
}
private static PlatformColorValues GetColorValuesFromSetting(object value)
{
var isDark = value?.ToString() == "1";
return new PlatformColorValues(isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light);
return new PlatformColorValues
{
ThemeVariant = isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light
};
}
}

22
src/Avalonia.Native/NativePlatformSettings.cs

@ -18,16 +18,32 @@ internal class NativePlatformSettings : DefaultPlatformSettings
public override PlatformColorValues GetColorValues()
{
var theme = (PlatformThemeVariant)_platformSettings.PlatformTheme;
var (theme, contrast) = _platformSettings.PlatformTheme switch
{
AvnPlatformThemeVariant.Dark => (PlatformThemeVariant.Dark, ColorContrastPreference.NoPreference),
AvnPlatformThemeVariant.Light => (PlatformThemeVariant.Light, ColorContrastPreference.NoPreference),
AvnPlatformThemeVariant.HighContrastDark => (PlatformThemeVariant.Dark, ColorContrastPreference.High),
AvnPlatformThemeVariant.HighContrastLight => (PlatformThemeVariant.Dark, ColorContrastPreference.High),
_ => throw new ArgumentOutOfRangeException()
};
var color = _platformSettings.AccentColor;
if (color > 0)
{
_lastColorValues = new PlatformColorValues(theme, Color.FromUInt32(color));
_lastColorValues = new PlatformColorValues
{
ThemeVariant = theme,
ContrastPreference = contrast,
AccentColor1 = Color.FromUInt32(color)
};
}
else
{
_lastColorValues = new PlatformColorValues(theme);
_lastColorValues = new PlatformColorValues
{
ThemeVariant = theme,
ContrastPreference = contrast
};
}
return _lastColorValues;

4
src/Avalonia.Native/avn.idl

@ -476,7 +476,9 @@ enum AvnWindowTransparencyMode
enum AvnPlatformThemeVariant
{
Light,
Dark
Dark,
HighContrastLight,
HighContrastDark,
}
[uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)]

13
src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs

@ -6,21 +6,26 @@ namespace Avalonia.Browser;
internal class BrowserPlatformSettings : DefaultPlatformSettings
{
private bool _isDarkMode;
private bool _isHighContrast;
public BrowserPlatformSettings()
{
_isDarkMode = DomHelper.ObserveDarkMode(m =>
var obj = DomHelper.ObserveDarkMode((isDarkMode, isHighContrast) =>
{
_isDarkMode = m;
_isDarkMode = isDarkMode;
_isHighContrast = isHighContrast;
OnColorValuesChanged(GetColorValues());
});
_isDarkMode = obj.GetPropertyAsBoolean("isDarkMode");
_isHighContrast = obj.GetPropertyAsBoolean("isHighContrast");
}
public override PlatformColorValues GetColorValues()
{
return base.GetColorValues() with
{
ThemeVariant = _isDarkMode ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light
ThemeVariant = _isDarkMode ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light,
ContrastPreference = _isHighContrast ? ColorContrastPreference.High : ColorContrastPreference.NoPreference
};
}
}

6
src/Browser/Avalonia.Browser/Interop/DomHelper.cs

@ -27,7 +27,7 @@ internal static partial class DomHelper
Action<double, double> onDpiChanged);
[JSImport("AvaloniaDOM.observeDarkMode", AvaloniaModule.MainModuleName)]
public static partial bool ObserveDarkMode(
[JSMarshalAs<JSType.Function<JSType.Boolean>>]
Action<bool> onDpiChanged);
public static partial JSObject ObserveDarkMode(
[JSMarshalAs<JSType.Function<JSType.Boolean, JSType.Boolean>>]
Action<bool, bool> onDpiChanged);
}

18
src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts

@ -3,17 +3,25 @@ export class AvaloniaDOM {
element.classList.add(className);
}
static observeDarkMode(observer: (isDarkMode: boolean) => boolean): boolean {
static observeDarkMode(observer: (isDarkMode: boolean, isHighContrast: boolean) => boolean) {
if (globalThis.matchMedia === undefined) {
return false;
}
const media = globalThis.matchMedia("(prefers-color-scheme: dark)");
media.addEventListener("change", (args: MediaQueryListEvent) => {
observer(args.matches);
const colorShemeMedia = globalThis.matchMedia("(prefers-color-scheme: dark)");
const prefersContrastMedia = globalThis.matchMedia("(prefers-contrast: more)");
colorShemeMedia.addEventListener("change", (args: MediaQueryListEvent) => {
observer(args.matches, prefersContrastMedia.matches);
});
prefersContrastMedia.addEventListener("change", (args: MediaQueryListEvent) => {
observer(colorShemeMedia.matches, args.matches);
});
return media.matches;
return {
isDarkMode: colorShemeMedia.matches,
isHighContrast: prefersContrastMedia.matches
};
}
static createAvaloniaHost(host: HTMLElement) {

11
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -287,6 +287,17 @@ namespace Avalonia.Win32
}
}
}
if (msg == (uint)WindowsMessage.WM_SETTINGCHANGE
&& PlatformSettings is Win32PlatformSettings win32PlatformSettings)
{
var changedSetting = Marshal.PtrToStringAuto(lParam);
if (changedSetting == "ImmersiveColorSet" // dark/light mode
|| changedSetting == "WindowsThemeElement") // high contrast mode
{
win32PlatformSettings.OnColorValuesChanged();
}
}
TrayIconImpl.ProcWnd(hWnd, msg, wParam, lParam);

45
src/Windows/Avalonia.Win32/Win32PlatformSettings.cs

@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
@ -38,18 +39,44 @@ internal class Win32PlatformSettings : DefaultPlatformSettings
return base.GetColorValues();
}
var settings = NativeWinRTMethods.CreateInstance<IUISettings3>("Windows.UI.ViewManagement.UISettings");
var accent = settings.GetColorValue(UIColorType.Accent).ToAvalonia();
var background = settings.GetColorValue(UIColorType.Background).ToAvalonia();
var uiSettings = NativeWinRTMethods.CreateInstance<IUISettings3>("Windows.UI.ViewManagement.UISettings");
var accent = uiSettings.GetColorValue(UIColorType.Accent).ToAvalonia();
return _lastColorValues = new PlatformColorValues(
background.R + background.G + background.B < (255 * 3 - background.R - background.G - background.B)
? PlatformThemeVariant.Dark
: PlatformThemeVariant.Light,
accent, accent, accent);
var accessibilitySettings = NativeWinRTMethods.CreateInstance<IAccessibilitySettings>("Windows.UI.ViewManagement.AccessibilitySettings");
if (accessibilitySettings.HighContrast == 1)
{
// Windows 11 has 4 different high contrast schemes:
// - Aquatic - High Contrast Black
// - Desert - High Contrast White
// - Dusk - High Contrast #1
// - Night sky - High Contrast #2
// Only "Desert" one can be considered a "light" preference.
using var highContrastScheme = new HStringInterop(accessibilitySettings.HighContrastScheme);
return _lastColorValues = new PlatformColorValues
{
ThemeVariant = highContrastScheme.Value?.Contains("White", StringComparison.OrdinalIgnoreCase) == true ?
PlatformThemeVariant.Light :
PlatformThemeVariant.Dark,
ContrastPreference = ColorContrastPreference.High,
// Windows provides more than one accent color for the HighContrast themes, but with no API for that (at least not in the WinRT)
AccentColor1 = accent
};
}
else
{
var background = uiSettings.GetColorValue(UIColorType.Background).ToAvalonia();
return _lastColorValues = new PlatformColorValues
{
ThemeVariant = background.R + background.G + background.B < (255 * 3 - background.R - background.G - background.B) ?
PlatformThemeVariant.Dark :
PlatformThemeVariant.Light,
ContrastPreference = ColorContrastPreference.NoPreference,
AccentColor1 = accent
};
}
}
internal void OnColorValuesChanged(IntPtr handle)
internal void OnColorValuesChanged()
{
var oldColorValues = _lastColorValues;
var colorValues = GetColorValues();

26
src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs

@ -22,6 +22,9 @@ namespace Avalonia.Win32.WinRT
internal static IntPtr WindowsCreateString(string sourceString)
=> WindowsCreateString(sourceString, sourceString.Length);
[DllImport("api-ms-win-core-winrt-string-l1-1-0.dll", CallingConvention = CallingConvention.StdCall)]
internal static extern unsafe char* WindowsGetStringRawBuffer(IntPtr hstring, uint* length);
[DllImport("api-ms-win-core-winrt-string-l1-1-0.dll",
CallingConvention = CallingConvention.StdCall, PreserveSig = false)]
internal static extern unsafe void WindowsDeleteString(IntPtr hString);
@ -120,17 +123,38 @@ namespace Avalonia.Win32.WinRT
class HStringInterop : IDisposable
{
private IntPtr _s;
private bool _owns;
public HStringInterop(string s)
{
_s = s == null ? IntPtr.Zero : NativeWinRTMethods.WindowsCreateString(s);
_owns = true;
}
public HStringInterop(IntPtr str, bool owns = false)
{
_s = str;
_owns = owns;
}
public IntPtr Handle => _s;
public unsafe string Value
{
get
{
if (_s == IntPtr.Zero)
return null;
uint length;
var buffer = NativeWinRTMethods.WindowsGetStringRawBuffer(_s, &length);
return new string(buffer, 0, (int) length);
}
}
public void Dispose()
{
if (_s != IntPtr.Zero)
if (_s != IntPtr.Zero && _owns)
{
NativeWinRTMethods.WindowsDeleteString(_s);
_s = IntPtr.Zero;

7
src/Windows/Avalonia.Win32/WinRT/winrt.idl

@ -864,3 +864,10 @@ interface IUISettings3 : IInspectable
{
HRESULT GetColorValue([in] UIColorType desiredColor, [out][retval] Color* value);
}
[uuid(FE0E8147-C4C0-4562-B962-1327B52AD5B9)]
interface IAccessibilitySettings : IInspectable
{
[propget] HRESULT HighContrast([out] [retval] boolean* value);
[propget] HRESULT HighContrastScheme([out] [retval] HSTRING* value);
}

8
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -732,14 +732,6 @@ namespace Avalonia.Win32
var node = AutomationNode.GetOrCreate(peer);
return UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, node);
}
break;
case WindowsMessage.WM_SETTINGCHANGE:
if (Marshal.PtrToStringAuto(lParam) == "ImmersiveColorSet"
&& Win32Platform.PlatformSettings is Win32PlatformSettings win32PlatformSettings)
{
win32PlatformSettings.OnColorValuesChanged(_hwnd);
}
break;
}

11
src/iOS/Avalonia.iOS/PlatformSettings.cs

@ -17,8 +17,17 @@ internal class PlatformSettings : DefaultPlatformSettings
var themeVariant = UITraitCollection.CurrentTraitCollection.UserInterfaceStyle == UIUserInterfaceStyle.Dark ?
PlatformThemeVariant.Dark :
PlatformThemeVariant.Light;
var contrastPreference = UITraitCollection.CurrentTraitCollection.AccessibilityContrast == UIAccessibilityContrast.High ?
ColorContrastPreference.High :
ColorContrastPreference.NoPreference;
return _lastColorValues = new PlatformColorValues(themeVariant);
return _lastColorValues = new PlatformColorValues
{
ThemeVariant = themeVariant,
ContrastPreference = contrastPreference
};
}
public void TraitCollectionDidChange()

Loading…
Cancel
Save