committed by
GitHub
54 changed files with 3860 additions and 51 deletions
@ -0,0 +1,113 @@ |
|||
#include "common.h" |
|||
|
|||
@interface CocoaThemeObserver : NSObject |
|||
-(id)initWithCallback:(IAvnActionCallback *)callback; |
|||
@end |
|||
|
|||
class PlatformSettings : public ComSingleObject<IAvnPlatformSettings, &IID_IAvnPlatformSettings> |
|||
{ |
|||
CocoaThemeObserver* observer; |
|||
|
|||
public: |
|||
FORWARD_IUNKNOWN() |
|||
virtual AvnPlatformThemeVariant GetPlatformTheme() override |
|||
{ |
|||
@autoreleasepool |
|||
{ |
|||
if (@available(macOS 10.14, *)) |
|||
{ |
|||
if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAqua |
|||
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantLight) { |
|||
return AvnPlatformThemeVariant::Light; |
|||
} else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameDarkAqua |
|||
|| 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; |
|||
} |
|||
} |
|||
|
|||
virtual unsigned int GetAccentColor() override |
|||
{ |
|||
@autoreleasepool |
|||
{ |
|||
if (@available(macOS 10.14, *)) |
|||
{ |
|||
auto color = [NSColor controlAccentColor]; |
|||
return to_argb(color); |
|||
} |
|||
else |
|||
{ |
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
virtual void RegisterColorsChange(IAvnActionCallback *callback) override |
|||
{ |
|||
if (@available(macOS 10.14, *)) |
|||
{ |
|||
observer = [[CocoaThemeObserver alloc] initWithCallback: callback]; |
|||
[[NSApplication sharedApplication] addObserver:observer forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew context:nil]; |
|||
} |
|||
} |
|||
|
|||
private: |
|||
unsigned int to_argb(NSColor* color) |
|||
{ |
|||
const CGFloat* components = CGColorGetComponents(color.CGColor); |
|||
unsigned int alpha = static_cast<unsigned int>(CGColorGetAlpha(color.CGColor) * 0xFF); |
|||
unsigned int red = static_cast<unsigned int>(components[0] * 0xFF); |
|||
unsigned int green = static_cast<unsigned int>(components[1] * 0xFF); |
|||
unsigned int blue = static_cast<unsigned int>(components[2] * 0xFF); |
|||
return (alpha << 24) + (red << 16) + (green << 8) + blue; |
|||
} |
|||
}; |
|||
|
|||
@implementation CocoaThemeObserver |
|||
{ |
|||
ComPtr<IAvnActionCallback> _callback; |
|||
} |
|||
- (id) initWithCallback:(IAvnActionCallback *)callback{ |
|||
self = [super init]; |
|||
if (self) { |
|||
_callback = callback; |
|||
} |
|||
return self; |
|||
} |
|||
|
|||
/*- (void)didChangeValueForKey:(NSString *)key { |
|||
if([key isEqualToString:@"effectiveAppearance"]) { |
|||
_callback->Run(); |
|||
} |
|||
else { |
|||
[super didChangeValueForKey:key]; |
|||
} |
|||
}*/ |
|||
|
|||
- (void)observeValueForKeyPath:(NSString *)keyPath |
|||
ofObject:(id)object |
|||
change:(NSDictionary *)change |
|||
context:(void *)context { |
|||
if([keyPath isEqualToString:@"effectiveAppearance"]) { |
|||
_callback->Run(); |
|||
} else { |
|||
[super observeValueForKeyPath:keyPath |
|||
ofObject:object |
|||
change:change |
|||
context:context]; |
|||
} |
|||
} |
|||
@end |
|||
|
|||
extern IAvnPlatformSettings* CreatePlatformSettings() |
|||
{ |
|||
return new PlatformSettings(); |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Android |
|||
{ |
|||
public interface IActivityNavigationService |
|||
{ |
|||
event EventHandler<AndroidBackRequestedEventArgs> BackRequested; |
|||
} |
|||
|
|||
public class AndroidBackRequestedEventArgs : EventArgs |
|||
{ |
|||
public bool Handled { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
using System; |
|||
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; |
|||
using Color = Avalonia.Media.Color; |
|||
|
|||
namespace Avalonia.Android.Platform; |
|||
|
|||
// TODO: ideally should be created per view/activity.
|
|||
internal class AndroidPlatformSettings : DefaultPlatformSettings |
|||
{ |
|||
private PlatformColorValues _latestValues; |
|||
|
|||
public AndroidPlatformSettings() |
|||
{ |
|||
_latestValues = base.GetColorValues(); |
|||
} |
|||
|
|||
public override PlatformColorValues GetColorValues() |
|||
{ |
|||
return _latestValues; |
|||
} |
|||
|
|||
internal void OnViewConfigurationChanged(Context context) |
|||
{ |
|||
if (context.Resources?.Configuration is null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var systemTheme = (context.Resources.Configuration.UiMode & UiMode.NightMask) switch |
|||
{ |
|||
UiMode.NightYes => PlatformThemeVariant.Dark, |
|||
UiMode.NightNo => PlatformThemeVariant.Light, |
|||
_ => throw new ArgumentOutOfRangeException() |
|||
}; |
|||
|
|||
if (OperatingSystem.IsAndroidVersionAtLeast(31)) |
|||
{ |
|||
// See https://developer.android.com/reference/android/R.color
|
|||
var accent1 = context.Resources.GetColor(17170494, context.Theme); // Resource.Color.SystemAccent1500
|
|||
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 |
|||
{ |
|||
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)) |
|||
{ |
|||
// See https://developer.android.com/reference/android/R.attr
|
|||
var array = context.Theme.ObtainStyledAttributes(new[] { 16843829 }); // Resource.Attribute.ColorAccent
|
|||
var accent = array.GetColor(0, 0); |
|||
|
|||
_latestValues = new PlatformColorValues |
|||
{ |
|||
ThemeVariant = systemTheme, |
|||
ContrastPreference = IsHighContrast(context), |
|||
AccentColor1 = new Color(accent.A, accent.R, accent.G, accent.B) |
|||
}; |
|||
array.Recycle(); |
|||
} |
|||
else |
|||
{ |
|||
_latestValues = _latestValues with { ThemeVariant = systemTheme }; |
|||
} |
|||
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using System; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Android.Platform |
|||
{ |
|||
internal class AndroidSystemNavigationManager : ISystemNavigationManager |
|||
{ |
|||
public event EventHandler<RoutedEventArgs> BackRequested; |
|||
|
|||
public AndroidSystemNavigationManager(IActivityNavigationService? navigationService) |
|||
{ |
|||
if(navigationService != null) |
|||
{ |
|||
navigationService.BackRequested += OnBackRequested; |
|||
} |
|||
} |
|||
|
|||
private void OnBackRequested(object sender, AndroidBackRequestedEventArgs e) |
|||
{ |
|||
var routedEventArgs = new RoutedEventArgs(); |
|||
|
|||
BackRequested?.Invoke(this, routedEventArgs); |
|||
|
|||
e.Handled = routedEventArgs.Handled; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Platform; |
|||
|
|||
/// <summary>
|
|||
/// System theme variant or mode.
|
|||
/// </summary>
|
|||
public enum PlatformThemeVariant |
|||
{ |
|||
Light, |
|||
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>
|
|||
public record PlatformColorValues |
|||
{ |
|||
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; |
|||
} |
|||
|
|||
/// <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; |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Metadata; |
|||
|
|||
namespace Avalonia.Platform |
|||
{ |
|||
[Unstable] |
|||
public interface ITopLevelWithSystemNavigationManager |
|||
{ |
|||
ISystemNavigationManager SystemNavigationManager { get; } |
|||
} |
|||
|
|||
[Unstable] |
|||
public interface ISystemNavigationManager |
|||
{ |
|||
public event EventHandler<RoutedEventArgs>? BackRequested; |
|||
} |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Logging; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.FreeDesktop; |
|||
|
|||
internal class DBusPlatformSettings : DefaultPlatformSettings |
|||
{ |
|||
private readonly IDBusSettings? _settings; |
|||
private PlatformColorValues? _lastColorValues; |
|||
|
|||
public DBusPlatformSettings() |
|||
{ |
|||
_settings = DBusHelper.TryInitialize()? |
|||
.CreateProxy<IDBusSettings>("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); |
|||
|
|||
if (_settings is not null) |
|||
{ |
|||
_ = _settings.WatchSettingChangedAsync(SettingsChangedHandler); |
|||
|
|||
_ = TryGetInitialValue(); |
|||
} |
|||
} |
|||
|
|||
public override PlatformColorValues GetColorValues() |
|||
{ |
|||
return _lastColorValues ?? base.GetColorValues(); |
|||
} |
|||
|
|||
private async Task TryGetInitialValue() |
|||
{ |
|||
var colorSchemeTask = _settings!.ReadAsync("org.freedesktop.appearance", "color-scheme"); |
|||
if (colorSchemeTask.Status == TaskStatus.RanToCompletion) |
|||
{ |
|||
_lastColorValues = GetColorValuesFromSetting(colorSchemeTask.Result); |
|||
} |
|||
else |
|||
{ |
|||
try |
|||
{ |
|||
var value = await colorSchemeTask; |
|||
_lastColorValues = GetColorValuesFromSetting(value); |
|||
OnColorValuesChanged(_lastColorValues); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_lastColorValues = base.GetColorValues(); |
|||
Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, "Unable to get setting value", ex); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void SettingsChangedHandler((string @namespace, string key, object value) tuple) |
|||
{ |
|||
if (tuple.@namespace == "org.freedesktop.appearance" |
|||
&& tuple.key == "color-scheme") |
|||
{ |
|||
/* |
|||
<member>0: No preference</member> |
|||
<member>1: Prefer dark appearance</member> |
|||
<member>2: Prefer light appearance</member> |
|||
*/ |
|||
_lastColorValues = GetColorValuesFromSetting(tuple.value); |
|||
OnColorValuesChanged(_lastColorValues); |
|||
} |
|||
} |
|||
|
|||
private static PlatformColorValues GetColorValuesFromSetting(object value) |
|||
{ |
|||
var isDark = value?.ToString() == "1"; |
|||
return new PlatformColorValues |
|||
{ |
|||
ThemeVariant = isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Tmds.DBus; |
|||
|
|||
namespace Avalonia.FreeDesktop; |
|||
|
|||
[DBusInterface("org.freedesktop.portal.Settings")] |
|||
internal interface IDBusSettings : IDBusObject |
|||
{ |
|||
Task<(string @namespace, IDictionary<string, object>)> ReadAllAsync(string[] namespaces); |
|||
|
|||
Task<object> ReadAsync(string @namespace, string key); |
|||
|
|||
Task<IDisposable> WatchSettingChangedAsync(Action<(string @namespace, string key, object value)> handler, Action<Exception>? onError = null); |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Native.Interop; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Native; |
|||
|
|||
internal class NativePlatformSettings : DefaultPlatformSettings |
|||
{ |
|||
private readonly IAvnPlatformSettings _platformSettings; |
|||
private PlatformColorValues _lastColorValues; |
|||
|
|||
public NativePlatformSettings(IAvnPlatformSettings platformSettings) |
|||
{ |
|||
_platformSettings = platformSettings; |
|||
platformSettings.RegisterColorsChange(new ColorsChangeCallback(this)); |
|||
} |
|||
|
|||
public override PlatformColorValues GetColorValues() |
|||
{ |
|||
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 |
|||
{ |
|||
ThemeVariant = theme, |
|||
ContrastPreference = contrast, |
|||
AccentColor1 = Color.FromUInt32(color) |
|||
}; |
|||
} |
|||
else |
|||
{ |
|||
_lastColorValues = new PlatformColorValues |
|||
{ |
|||
ThemeVariant = theme, |
|||
ContrastPreference = contrast |
|||
}; |
|||
} |
|||
|
|||
return _lastColorValues; |
|||
} |
|||
|
|||
public void OnColorValuesChanged() |
|||
{ |
|||
var oldColorValues = _lastColorValues; |
|||
var colorValues = GetColorValues(); |
|||
|
|||
if (oldColorValues != colorValues) |
|||
{ |
|||
OnColorValuesChanged(colorValues); |
|||
} |
|||
} |
|||
|
|||
private class ColorsChangeCallback : NativeCallbackBase, IAvnActionCallback |
|||
{ |
|||
private readonly NativePlatformSettings _settings; |
|||
|
|||
public ColorsChangeCallback(NativePlatformSettings settings) |
|||
{ |
|||
_settings = settings; |
|||
} |
|||
|
|||
public void Run() |
|||
{ |
|||
_settings.OnColorValuesChanged(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using Avalonia.Browser.Interop; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Browser; |
|||
|
|||
internal class BrowserPlatformSettings : DefaultPlatformSettings |
|||
{ |
|||
private bool _isDarkMode; |
|||
private bool _isHighContrast; |
|||
|
|||
public BrowserPlatformSettings() |
|||
{ |
|||
var obj = DomHelper.ObserveDarkMode((isDarkMode, isHighContrast) => |
|||
{ |
|||
_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, |
|||
ContrastPreference = _isHighContrast ? ColorContrastPreference.High : ColorContrastPreference.NoPreference |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using Avalonia.Browser.Interop; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Browser |
|||
{ |
|||
internal class BrowserSystemNavigationManager : ISystemNavigationManager |
|||
{ |
|||
public event EventHandler<RoutedEventArgs>? BackRequested; |
|||
|
|||
public BrowserSystemNavigationManager() |
|||
{ |
|||
NavigationHelper.AddBackHandler(() => |
|||
{ |
|||
var routedEventArgs = new RoutedEventArgs(); |
|||
|
|||
BackRequested?.Invoke(this, routedEventArgs); |
|||
|
|||
return routedEventArgs.Handled; |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices.JavaScript; |
|||
|
|||
namespace Avalonia.Browser.Interop; |
|||
|
|||
internal static partial class NavigationHelper |
|||
{ |
|||
[JSImport("NavigationHelper.addBackHandler", AvaloniaModule.MainModuleName)] |
|||
public static partial void AddBackHandler([JSMarshalAs<JSType.Function<JSType.Boolean>>] Func<bool> backHandlerCallback); |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
export class NavigationHelper { |
|||
public static addBackHandler(backHandlerCallback: () => Boolean) { |
|||
history.pushState(null, "", window.location.href); |
|||
window.onpopstate = () => { |
|||
const handled = backHandlerCallback(); |
|||
|
|||
if (!handled) { |
|||
history.back(); |
|||
} else { |
|||
history.forward(); |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Input; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Win32.Interop; |
|||
using Avalonia.Win32.WinRT; |
|||
using static Avalonia.Win32.Interop.UnmanagedMethods; |
|||
|
|||
namespace Avalonia.Win32; |
|||
|
|||
internal class Win32PlatformSettings : DefaultPlatformSettings |
|||
{ |
|||
private PlatformColorValues _lastColorValues; |
|||
|
|||
public override Size GetTapSize(PointerType type) |
|||
{ |
|||
return type switch |
|||
{ |
|||
PointerType.Touch => new(10, 10), |
|||
_ => new(GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDRAG), GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDRAG)), |
|||
}; |
|||
} |
|||
|
|||
public override Size GetDoubleTapSize(PointerType type) |
|||
{ |
|||
return type switch |
|||
{ |
|||
PointerType.Touch => new(16, 16), |
|||
_ => new(GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDOUBLECLK), GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDOUBLECLK)), |
|||
}; |
|||
} |
|||
|
|||
public override TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(GetDoubleClickTime()); |
|||
|
|||
public override PlatformColorValues GetColorValues() |
|||
{ |
|||
if (Win32Platform.WindowsVersion.Major < 10) |
|||
{ |
|||
return base.GetColorValues(); |
|||
} |
|||
|
|||
var uiSettings = NativeWinRTMethods.CreateInstance<IUISettings3>("Windows.UI.ViewManagement.UISettings"); |
|||
var accent = uiSettings.GetColorValue(UIColorType.Accent).ToAvalonia(); |
|||
|
|||
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") == 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() |
|||
{ |
|||
var oldColorValues = _lastColorValues; |
|||
var colorValues = GetColorValues(); |
|||
|
|||
if (oldColorValues != colorValues) |
|||
{ |
|||
OnColorValuesChanged(colorValues); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Foundation; |
|||
using UIKit; |
|||
|
|||
namespace Avalonia.iOS; |
|||
|
|||
// TODO: ideally should be created per view/activity.
|
|||
internal class PlatformSettings : DefaultPlatformSettings |
|||
{ |
|||
private PlatformColorValues _lastColorValues; |
|||
|
|||
public override PlatformColorValues GetColorValues() |
|||
{ |
|||
var themeVariant = UITraitCollection.CurrentTraitCollection.UserInterfaceStyle == UIUserInterfaceStyle.Dark ? |
|||
PlatformThemeVariant.Dark : |
|||
PlatformThemeVariant.Light; |
|||
|
|||
|
|||
var contrastPreference = UITraitCollection.CurrentTraitCollection.AccessibilityContrast == UIAccessibilityContrast.High ? |
|||
ColorContrastPreference.High : |
|||
ColorContrastPreference.NoPreference; |
|||
|
|||
UIColor? tintColor = null; |
|||
if (OperatingSystem.IsIOSVersionAtLeast(14)) |
|||
{ |
|||
tintColor = UIConfigurationColorTransformer.PreferredTint(UIColor.Clear); |
|||
} |
|||
|
|||
if (tintColor is not null) |
|||
{ |
|||
tintColor.GetRGBA(out var red, out var green, out var blue, out var alpha); |
|||
return _lastColorValues = new PlatformColorValues |
|||
{ |
|||
ThemeVariant = themeVariant, |
|||
ContrastPreference = contrastPreference, |
|||
AccentColor1 = new Color( |
|||
(byte)(alpha * 255), |
|||
(byte)(red * 255), |
|||
(byte)(green * 255), |
|||
(byte)(blue * 255)) |
|||
}; |
|||
} |
|||
else |
|||
{ |
|||
return _lastColorValues = new PlatformColorValues |
|||
{ |
|||
ThemeVariant = themeVariant, ContrastPreference = contrastPreference |
|||
}; |
|||
} |
|||
} |
|||
|
|||
public void TraitCollectionDidChange() |
|||
{ |
|||
var oldColorValues = _lastColorValues; |
|||
var colorValues = GetColorValues(); |
|||
|
|||
if (oldColorValues != colorValues) |
|||
{ |
|||
OnColorValuesChanged(colorValues); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue