Browse Source

Merge branch 'master' into fixes/macos-child-window-key-handling

pull/9716/head
Max Katz 3 years ago
committed by GitHub
parent
commit
232a58809c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  2. 113
      native/Avalonia.Native/src/OSX/PlatformSettings.mm
  3. 2
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  4. 18
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  5. 1
      native/Avalonia.Native/src/OSX/common.h
  6. 10
      native/Avalonia.Native/src/OSX/main.mm
  7. 73
      samples/ControlCatalog/MainView.xaml.cs
  8. 2
      src/Android/Avalonia.Android/AndroidPlatform.cs
  9. 19
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  10. 16
      src/Android/Avalonia.Android/AvaloniaView.cs
  11. 14
      src/Android/Avalonia.Android/IAndroidNavigationService.cs
  12. 94
      src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs
  13. 28
      src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
  14. 13
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  15. 5
      src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs
  16. 27
      src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
  17. 15
      src/Avalonia.Base/Platform/IPlatformSettings.cs
  18. 68
      src/Avalonia.Base/Platform/PlatformColorValues.cs
  19. 18
      src/Avalonia.Base/Platform/SystemNavigationManager.cs
  20. 2
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  21. 6
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  22. 62
      src/Avalonia.Controls/TopLevel.cs
  23. 4
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  24. 77
      src/Avalonia.FreeDesktop/DBusPlatformSettings.cs
  25. 16
      src/Avalonia.FreeDesktop/DBusSettings.cs
  26. 5
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  27. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  28. 77
      src/Avalonia.Native/NativePlatformSettings.cs
  29. 5
      src/Avalonia.Native/WindowImplBase.cs
  30. 18
      src/Avalonia.Native/avn.idl
  31. 2
      src/Avalonia.X11/X11Platform.cs
  32. 2
      src/Avalonia.X11/X11Window.cs
  33. 31
      src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs
  34. 24
      src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs
  35. 10
      src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
  36. 5
      src/Browser/Avalonia.Browser/Interop/DomHelper.cs
  37. 10
      src/Browser/Avalonia.Browser/Interop/NavigationHelper.cs
  38. 2
      src/Browser/Avalonia.Browser/WindowingPlatform.cs
  39. 5
      src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts
  40. 21
      src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts
  41. 14
      src/Browser/Avalonia.Browser/webapp/modules/avalonia/navigationHelper.ts
  42. 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  43. 2
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  44. 40
      src/Windows/Avalonia.Win32/Win32Platform.cs
  45. 89
      src/Windows/Avalonia.Win32/Win32PlatformSettings.cs
  46. 26
      src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs
  47. 3
      src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs
  48. 27
      src/Windows/Avalonia.Win32/WinRT/winrt.idl
  49. 13
      src/Windows/Avalonia.Win32/WindowImpl.cs
  50. 35
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  51. 2
      src/iOS/Avalonia.iOS/Platform.cs
  52. 65
      src/iOS/Avalonia.iOS/PlatformSettings.cs
  53. 2
      tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs
  54. 2665
      tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json

4
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -50,6 +50,7 @@
BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; };
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */; };
EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */ = {isa = PBXBuildFile; fileRef = EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -103,6 +104,7 @@
BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = "<group>"; };
BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = "<group>"; };
ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformSettings.mm; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -163,6 +165,7 @@
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */,
AB7A61F02147C815003C5833 /* Products */,
AB661C1C2148230E00291242 /* Frameworks */,
18391676ECF0E983F4964357 /* WindowBaseImpl.mm */,
@ -299,6 +302,7 @@
1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */,
18391AC16726CBC45856233B /* AvnWindow.mm in Sources */,
18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */,
EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

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

@ -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();
}

2
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@ -92,6 +92,8 @@ BEGIN_INTERFACE_MAP()
virtual HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode) override;
virtual HRESULT SetFrameThemeVariant(AvnPlatformThemeVariant variant) override;
virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
IAvnClipboard *clipboard, IAvnDndResultCallback *cb,
void *sourceHandle) override;

18
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@ -498,6 +498,24 @@ HRESULT WindowBaseImpl::SetTransparencyMode(AvnWindowTransparencyMode mode) {
return S_OK;
}
HRESULT WindowBaseImpl::SetFrameThemeVariant(AvnPlatformThemeVariant variant) {
START_COM_CALL;
NSAppearanceName appearanceName;
if (@available(macOS 10.14, *))
{
appearanceName = variant == AvnPlatformThemeVariant::Dark ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua;
}
else
{
appearanceName = variant == AvnPlatformThemeVariant::Dark ? NSAppearanceNameVibrantDark : NSAppearanceNameAqua;
}
[Window setAppearance: [NSAppearance appearanceNamed: appearanceName]];
return S_OK;
}
HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) {
START_COM_CALL;

1
native/Avalonia.Native/src/OSX/common.h

@ -27,6 +27,7 @@ extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnApplicationCommands* CreateApplicationCommands();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
extern IAvnPlatformSettings* CreatePlatformSettings();
extern void SetAppMenu(IAvnMenu *menu);
extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu ();

10
native/Avalonia.Native/src/OSX/main.mm

@ -398,6 +398,16 @@ public:
}
}
virtual HRESULT CreatePlatformSettings (IAvnPlatformSettings** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
*ppv = ::CreatePlatformSettings();
return S_OK;
}
}
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()

73
samples/ControlCatalog/MainView.xaml.cs

@ -3,9 +3,11 @@ using System.Collections;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.VisualTree;
using ControlCatalog.Models;
using ControlCatalog.Pages;
@ -14,10 +16,14 @@ namespace ControlCatalog
{
public class MainView : UserControl
{
private readonly IPlatformSettings _platformSettings;
public MainView()
{
AvaloniaXamlLoader.Load(this);
_platformSettings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
var sideBar = this.Get<TabControl>("Sidebar");
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
@ -37,6 +43,15 @@ namespace ControlCatalog
if (themes.SelectedItem is CatalogTheme theme)
{
App.SetThemeVariant(theme);
((TopLevel?)this.GetVisualRoot())?.PlatformImpl?.SetFrameThemeVariant(theme switch
{
CatalogTheme.FluentLight => PlatformThemeVariant.Light,
CatalogTheme.FluentDark => PlatformThemeVariant.Dark,
CatalogTheme.SimpleLight => PlatformThemeVariant.Light,
CatalogTheme.SimpleDark => PlatformThemeVariant.Dark,
_ => throw new ArgumentOutOfRangeException()
});
}
};
@ -89,6 +104,62 @@ namespace ControlCatalog
var decorations = this.Get<ComboBox>("Decorations");
if (VisualRoot is Window window)
decorations.SelectedIndex = (int)window.SystemDecorations;
_platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
_platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged;
}
private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e)
{
var themes = this.Get<ComboBox>("Themes");
var currentTheme = (CatalogTheme?)themes.SelectedItem ?? CatalogTheme.FluentLight;
var newTheme = (currentTheme, e.ThemeVariant) switch
{
(CatalogTheme.FluentDark, PlatformThemeVariant.Light) => CatalogTheme.FluentLight,
(CatalogTheme.FluentLight, PlatformThemeVariant.Dark) => CatalogTheme.FluentDark,
(CatalogTheme.SimpleDark, PlatformThemeVariant.Light) => CatalogTheme.SimpleLight,
(CatalogTheme.SimpleLight, PlatformThemeVariant.Dark) => CatalogTheme.SimpleDark,
_ => currentTheme
};
themes.SelectedItem = newTheme;
Application.Current!.Resources["SystemAccentColor"] = e.AccentColor1;
Application.Current.Resources["SystemAccentColorDark1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
Application.Current.Resources["SystemAccentColorDark2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
Application.Current.Resources["SystemAccentColorDark3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
static Color ChangeColorLuminosity(Color color, double luminosityFactor)
{
var red = (double)color.R;
var green = (double)color.G;
var blue = (double)color.B;
if (luminosityFactor < 0)
{
luminosityFactor = 1 + luminosityFactor;
red *= luminosityFactor;
green *= luminosityFactor;
blue *= luminosityFactor;
}
else if (luminosityFactor >= 0)
{
red = (255 - red) * luminosityFactor + red;
green = (255 - green) * luminosityFactor + green;
blue = (255 - blue) * luminosityFactor + blue;
}
return new Color(color.A, (byte)red, (byte)green, (byte)blue);
}
}
}
}

2
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -43,7 +43,7 @@ namespace Avalonia.Android
.Bind<ICursorFactory>().ToTransient<CursorFactory>()
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformSettings>().ToSingleton<AndroidPlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>()
.Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())

19
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -1,18 +1,14 @@
using System;
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using Android.Views;
using AndroidX.AppCompat.App;
using AndroidX.Lifecycle;
using AndroidRect = Android.Graphics.Rect;
namespace Avalonia.Android
{
public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler
public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler, IActivityNavigationService
{
internal static object ViewContent;
@ -56,9 +52,18 @@ namespace Avalonia.Android
}
}
public override void OnConfigurationChanged(Configuration newConfig)
public event EventHandler<AndroidBackRequestedEventArgs> BackRequested;
public override void OnBackPressed()
{
base.OnConfigurationChanged(newConfig);
var eventArgs = new AndroidBackRequestedEventArgs();
BackRequested?.Invoke(this, eventArgs);
if (!eventArgs.Handled)
{
base.OnBackPressed();
}
}
protected override void OnDestroy()

16
src/Android/Avalonia.Android/AvaloniaView.cs

@ -1,11 +1,14 @@
using System;
using Android.Content;
using Android.Content.Res;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Avalonia.Android.Platform;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Android
@ -26,6 +29,7 @@ namespace Avalonia.Android
_root.Prepare();
this.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
OnConfigurationChanged();
}
internal TopLevelImpl TopLevelImpl => _view;
@ -70,6 +74,18 @@ namespace Avalonia.Android
_timerSubscription?.Dispose();
}
}
protected override void OnConfigurationChanged(Configuration newConfig)
{
base.OnConfigurationChanged(newConfig);
OnConfigurationChanged();
}
private void OnConfigurationChanged()
{
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as AndroidPlatformSettings;
settings?.OnViewConfigurationChanged(Context);
}
class ViewImpl : TopLevelImpl
{

14
src/Android/Avalonia.Android/IAndroidNavigationService.cs

@ -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; }
}
}

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

@ -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;
}
}
}

28
src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs

@ -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;
}
}
}

13
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -26,12 +26,14 @@ using Avalonia.Rendering.Composition;
using Java.Lang;
using Math = System.Math;
using AndroidRect = Android.Graphics.Rect;
using Window = Android.Views.Window;
using Android.Graphics.Drawables;
namespace Avalonia.Android.Platform.SkiaPlatform
{
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider,
ITopLevelWithSystemNavigationManager
{
private readonly IGlPlatformSurface _gl;
private readonly IFramebufferPlatformSurface _framebuffer;
@ -57,6 +59,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
SystemNavigationManager = new AndroidSystemNavigationManager(avaloniaView.Context as IActivityNavigationService);
}
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@ -286,6 +290,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public WindowTransparencyLevel TransparencyLevel { get; private set; }
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{
// TODO adjust status bar depending on full screen mode.
}
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle;
@ -300,6 +309,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IStorageProvider StorageProvider { get; }
public ISystemNavigationManager SystemNavigationManager { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
if (TransparencyLevel != transparencyLevel)

5
src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs

@ -81,6 +81,10 @@ namespace Avalonia.Input.Platform
{
new KeyGesture(Key.Apps)
};
Back = new List<KeyGesture>
{
new KeyGesture(Key.Left, KeyModifiers.Alt)
};
}
public KeyModifiers CommandModifiers { get; set; }
@ -101,5 +105,6 @@ namespace Avalonia.Input.Platform
public List<KeyGesture> MoveCursorToTheStartOfDocumentWithSelection { get; set; }
public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; }
public List<KeyGesture> OpenContextMenu { get; set; }
public List<KeyGesture> Back { get; set; }
}
}

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

@ -1,15 +1,15 @@
using System;
using Avalonia.Input;
using Avalonia.Media;
namespace Avalonia.Platform
{
/// <summary>
/// A default implementation of <see cref="IPlatformSettings"/> for platforms which don't have
/// an OS-specific implementation.
/// A default implementation of <see cref="IPlatformSettings"/> for platforms.
/// </summary>
public class DefaultPlatformSettings : IPlatformSettings
{
public Size GetTapSize(PointerType type)
public virtual Size GetTapSize(PointerType type)
{
return type switch
{
@ -17,7 +17,7 @@ namespace Avalonia.Platform
_ => new(4, 4),
};
}
public Size GetDoubleTapSize(PointerType type)
public virtual Size GetDoubleTapSize(PointerType type)
{
return type switch
{
@ -25,8 +25,23 @@ namespace Avalonia.Platform
_ => new(4, 4),
};
}
public TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500);
public virtual TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500);
public TimeSpan HoldWaitDuration { get; set; } = TimeSpan.FromMilliseconds(300);
public virtual TimeSpan HoldWaitDuration => TimeSpan.FromMilliseconds(300);
public virtual PlatformColorValues GetColorValues()
{
return new PlatformColorValues
{
ThemeVariant = PlatformThemeVariant.Light
};
}
public event EventHandler<PlatformColorValues>? ColorValuesChanged;
protected void OnColorValuesChanged(PlatformColorValues colorValues)
{
ColorValuesChanged?.Invoke(this, colorValues);
}
}
}

15
src/Avalonia.Base/Platform/IPlatformSettings.cs

@ -28,6 +28,19 @@ namespace Avalonia.Platform
/// </summary>
TimeSpan GetDoubleTapTime(PointerType type);
TimeSpan HoldWaitDuration { get; set; }
/// <summary>
/// Holding duration between pointer press and when event is fired.
/// </summary>
TimeSpan HoldWaitDuration { get; }
/// <summary>
/// Gets current system color values including dark mode and accent colors.
/// </summary>
PlatformColorValues GetColorValues();
/// <summary>
/// Raises when current system color values are changed. Including changing of a dark mode and accent colors.
/// </summary>
event EventHandler<PlatformColorValues>? ColorValuesChanged;
}
}

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

@ -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;
}
}

18
src/Avalonia.Base/Platform/SystemNavigationManager.cs

@ -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;
}
}

2
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -58,6 +58,8 @@ namespace Avalonia.Controls.Embedding.Offscreen
public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; }
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
/// <inheritdoc/>
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);

6
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -163,6 +163,12 @@ namespace Avalonia.Platform
/// </summary>
WindowTransparencyLevel TransparencyLevel { get; }
/// <summary>
/// Sets the <see cref="PlatformThemeVariant"/> on the frame if it should be dark or light.
/// Also applies for the mobile status bar.
/// </summary>
void SetFrameThemeVariant(PlatformThemeVariant themeVariant);
/// <summary>
/// Gets the <see cref="AcrylicPlatformCompensationLevels"/> for the platform.
/// </summary>

62
src/Avalonia.Controls/TopLevel.cs

@ -7,6 +7,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.LogicalTree;
@ -17,6 +18,8 @@ using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using Avalonia.Input.Platform;
using System.Linq;
namespace Avalonia.Controls
{
@ -76,6 +79,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBrush> TransparencyBackgroundFallbackProperty =
AvaloniaProperty.Register<TopLevel, IBrush>(nameof(TransparencyBackgroundFallback), Brushes.White);
/// <summary>
/// Defines the <see cref="BackRequested"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> BackRequestedEvent =
RoutedEvent.Register<TopLevel, RoutedEventArgs>(nameof(BackRequested), RoutingStrategies.Bubble);
private static readonly WeakEvent<IResourceHost, ResourcesChangedEventArgs>
ResourcesChangedWeakEvent = WeakEvent.Register<IResourceHost, ResourcesChangedEventArgs>(
(s, h) => s.ResourcesChanged += h,
@ -89,6 +98,7 @@ namespace Avalonia.Controls
private readonly IGlobalStyles? _globalStyles;
private readonly PointerOverPreProcessor? _pointerOverPreProcessor;
private readonly IDisposable? _pointerOverPreProcessorSubscription;
private readonly IDisposable? _backGestureSubscription;
private Size _clientSize;
private Size? _frameSize;
private WindowTransparencyLevel _actualTransparencyLevel;
@ -205,6 +215,48 @@ namespace Avalonia.Controls
_pointerOverPreProcessor = new PointerOverPreProcessor(this);
_pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor);
if(impl is ITopLevelWithSystemNavigationManager topLevelWithSystemNavigation)
{
topLevelWithSystemNavigation.SystemNavigationManager.BackRequested += (s, e) =>
{
e.RoutedEvent = BackRequestedEvent;
RaiseEvent(e);
};
}
_backGestureSubscription = _inputManager?.PreProcess.Subscribe(e =>
{
bool backRequested = false;
if (e is RawKeyEventArgs rawKeyEventArgs && rawKeyEventArgs.Type == RawKeyEventType.KeyDown)
{
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>()?.Back;
if (keymap != null)
{
var keyEvent = new KeyEventArgs()
{
KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers,
Key = rawKeyEventArgs.Key
};
backRequested = keymap.Any( key => key.Matches(keyEvent));
}
}
else if(e is RawPointerEventArgs pointerEventArgs)
{
backRequested = pointerEventArgs.Type == RawPointerEventType.XButton1Down;
}
if (backRequested)
{
var backRequestedEventArgs = new RoutedEventArgs(BackRequestedEvent);
RaiseEvent(backRequestedEventArgs);
e.Handled = backRequestedEventArgs.Handled;
}
});
}
/// <summary>
@ -263,6 +315,15 @@ namespace Avalonia.Controls
set => SetValue(TransparencyBackgroundFallbackProperty, value);
}
/// <summary>
/// Occurs when physical Back Button is pressed or a back navigation has been requested.
/// </summary>
public event EventHandler<RoutedEvent> BackRequested
{
add { AddHandler(BackRequestedEvent, value); }
remove { RemoveHandler(BackRequestedEvent, value); }
}
public ILayoutManager LayoutManager
{
get
@ -382,6 +443,7 @@ namespace Avalonia.Controls
_pointerOverPreProcessor?.OnCompleted();
_pointerOverPreProcessorSubscription?.Dispose();
_backGestureSubscription?.Dispose();
PlatformImpl = null;

4
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -182,7 +182,9 @@ namespace Avalonia.DesignerSupport.Remote
public bool IsClientAreaExtendedToDecorations { get; }
public bool NeedsManagedDecorations => false;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
}

77
src/Avalonia.FreeDesktop/DBusPlatformSettings.cs

@ -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
};
}
}

16
src/Avalonia.FreeDesktop/DBusSettings.cs

@ -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);
}

5
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -345,5 +345,10 @@ namespace Avalonia.Headless
{
}
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{
}
}
}

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -104,7 +104,7 @@ namespace Avalonia.Native
.Bind<ICursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
.Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformSettings>().ToConstant(new NativePlatformSettings(_factory.CreatePlatformSettings()))
.Bind<IWindowingPlatform>().ToConstant(this)
.Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard()))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))

77
src/Avalonia.Native/NativePlatformSettings.cs

@ -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();
}
}
}

5
src/Avalonia.Native/WindowImplBase.cs

@ -519,6 +519,11 @@ namespace Avalonia.Native
public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.Transparent;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{
_native.SetFrameThemeVariant((AvnPlatformThemeVariant)themeVariant);
}
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0);
public IPlatformHandle Handle { get; private set; }

18
src/Avalonia.Native/avn.idl

@ -473,6 +473,14 @@ enum AvnWindowTransparencyMode
Blur
}
enum AvnPlatformThemeVariant
{
Light,
Dark,
HighContrastLight,
HighContrastDark,
}
[uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)]
interface IAvaloniaNativeFactory : IUnknown
{
@ -494,6 +502,7 @@ interface IAvaloniaNativeFactory : IUnknown
HRESULT CreateMenuItemSeparator(IAvnMenuItem** ppv);
HRESULT CreateTrayIcon(IAvnTrayIcon** ppv);
HRESULT CreateApplicationCommands(IAvnApplicationCommands** ppv);
HRESULT CreatePlatformSettings(IAvnPlatformSettings** ppv);
}
[uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)]
@ -535,6 +544,7 @@ interface IAvnWindowBase : IUnknown
HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
IAvnClipboard* clipboard, IAvnDndResultCallback* cb, [intptr]void* sourceHandle);
HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode);
HRESULT SetFrameThemeVariant(AvnPlatformThemeVariant mode);
}
[uuid(83e588f3-6981-4e48-9ea0-e1e569f79a91), cpp-virtual-inherits]
@ -906,3 +916,11 @@ interface IAvnAutomationNode : IUnknown
void PropertyChanged(AvnAutomationProperty property);
void FocusChanged();
}
[uuid(d1f009cc-9d2d-493b-845d-90d2c104baae)]
interface IAvnPlatformSettings : IUnknown
{
AvnPlatformThemeVariant GetPlatformTheme();
uint GetAccentColor();
void RegisterColorsChange(IAvnActionCallback* callback);
}

2
src/Avalonia.X11/X11Platform.cs

@ -80,7 +80,7 @@ namespace Avalonia.X11
.Bind<IKeyboardDevice>().ToFunc(() => KeyboardDevice)
.Bind<ICursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformSettings>().ToSingleton<DBusPlatformSettings>()
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader())
.Bind<IMountedVolumeInfoProvider>().ToConstant(new LinuxMountedVolumeInfoProvider())
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(new X11PlatformLifetimeEvents(this));

2
src/Avalonia.X11/X11Window.cs

@ -1226,6 +1226,8 @@ namespace Avalonia.X11
public WindowTransparencyLevel TransparencyLevel =>
_transparencyHelper?.CurrentLevel ?? WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8);
public bool NeedsManagedDecorations => false;

31
src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs

@ -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
};
}
}

24
src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs

@ -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;
});
}
}
}

10
src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs

@ -19,7 +19,8 @@ using Avalonia.Rendering.Composition;
namespace Avalonia.Browser
{
internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider,
ITopLevelWithSystemNavigationManager
{
private Size _clientSize;
private IInputRoot? _inputRoot;
@ -228,11 +229,18 @@ namespace Avalonia.Browser
public IKeyboardDevice KeyboardDevice { get; } = BrowserWindowingPlatform.Keyboard;
public WindowTransparencyLevel TransparencyLevel { get; private set; }
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{
// not in the standard, but we potentially can use "apple-mobile-web-app-status-bar-style" for iOS and "theme-color" for android.
}
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
public ITextInputMethodImpl TextInputMethod => _avaloniaView;
public INativeControlHostImpl? NativeControlHost { get; }
public IStorageProvider StorageProvider { get; } = new BrowserStorageProvider();
public ISystemNavigationManager SystemNavigationManager { get; } = new BrowserSystemNavigationManager();
}
}

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

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

10
src/Browser/Avalonia.Browser/Interop/NavigationHelper.cs

@ -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);
}

2
src/Browser/Avalonia.Browser/WindowingPlatform.cs

@ -39,7 +39,7 @@ namespace Avalonia.Browser
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<ICursorFactory>().ToSingleton<CssCursorFactory>()
.Bind<IKeyboardDevice>().ToConstant(s_keyboard)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformSettings>().ToSingleton<BrowserPlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(instance)
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(ManualTriggerRenderTimer.Instance)

5
src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts

@ -5,6 +5,7 @@ import { AvaloniaDOM } from "./avalonia/dom";
import { Caniuse } from "./avalonia/caniuse";
import { StreamHelper } from "./avalonia/stream";
import { NativeControlHost } from "./avalonia/nativeControlHost";
import { NavigationHelper } from "./avalonia/navigationHelper";
async function registerAvaloniaModule(api: RuntimeAPI): Promise<void> {
api.setModuleImports("avalonia", {
@ -15,7 +16,8 @@ async function registerAvaloniaModule(api: RuntimeAPI): Promise<void> {
DpiWatcher,
AvaloniaDOM,
StreamHelper,
NativeControlHost
NativeControlHost,
NavigationHelper
});
}
export {
@ -27,6 +29,7 @@ export {
AvaloniaDOM,
StreamHelper,
NativeControlHost,
NavigationHelper,
registerAvaloniaModule
};

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

@ -3,6 +3,27 @@ export class AvaloniaDOM {
element.classList.add(className);
}
static observeDarkMode(observer: (isDarkMode: boolean, isHighContrast: boolean) => boolean) {
if (globalThis.matchMedia === undefined) {
return false;
}
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 {
isDarkMode: colorShemeMedia.matches,
isHighContrast: prefersContrastMedia.matches
};
}
static createAvaloniaHost(host: HTMLElement) {
const randomIdPart = Math.random().toString(36).replace(/[^a-z]+/g, "").substr(2, 10);

14
src/Browser/Avalonia.Browser/webapp/modules/avalonia/navigationHelper.ts

@ -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();
}
};
}
}

2
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -83,6 +83,8 @@ namespace Avalonia.LinuxFramebuffer
public WindowTransparencyLevel TransparencyLevel { get; private set; }
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
}
}

2
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -258,6 +258,8 @@ namespace Avalonia.Win32.Interop.Wpf
public WindowTransparencyLevel TransparencyLevel { get; private set; }
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
}
}

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

@ -11,6 +11,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Media;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
@ -19,6 +20,7 @@ using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using Avalonia.Win32.WinRT;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia
@ -111,7 +113,7 @@ namespace Avalonia
namespace Avalonia.Win32
{
public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl
public class Win32Platform : IPlatformThreadingInterface, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl
{
private static readonly Win32Platform s_instance = new Win32Platform();
private static Thread _uiThread;
@ -126,6 +128,7 @@ namespace Avalonia.Win32
}
internal static Win32Platform Instance => s_instance;
internal static IPlatformSettings PlatformSettings => AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
internal IntPtr Handle => _hwnd;
@ -153,7 +156,7 @@ namespace Avalonia.Win32
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<ICursorFactory>().ToConstant(CursorFactory.Instance)
.Bind<IKeyboardDevice>().ToConstant(WindowsKeyboardDevice.Instance)
.Bind<IPlatformSettings>().ToConstant(s_instance)
.Bind<IPlatformSettings>().ToSingleton<Win32PlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(s_instance)
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
@ -258,8 +261,6 @@ namespace Avalonia.Win32
public bool CurrentThreadIsLoopThread => _uiThread == Thread.CurrentThread;
public TimeSpan HoldWaitDuration { get; set; } = TimeSpan.FromMilliseconds(300);
public event Action<DispatcherPriority?> Signaled;
public event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested;
@ -286,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);
@ -403,25 +415,5 @@ namespace Avalonia.Win32
SetProcessDPIAware();
}
Size IPlatformSettings.GetTapSize(PointerType type)
{
return type switch
{
PointerType.Touch => new(10, 10),
_ => new(GetSystemMetrics(SystemMetric.SM_CXDRAG), GetSystemMetrics(SystemMetric.SM_CYDRAG)),
};
}
Size IPlatformSettings.GetDoubleTapSize(PointerType type)
{
return type switch
{
PointerType.Touch => new(16, 16),
_ => new(GetSystemMetrics(SystemMetric.SM_CXDOUBLECLK), GetSystemMetrics(SystemMetric.SM_CYDOUBLECLK)),
};
}
TimeSpan IPlatformSettings.GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(GetDoubleClickTime());
}
}

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

@ -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);
}
}
}

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;

3
src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs

@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using Avalonia.Media;
namespace Avalonia.Win32.WinRT
{
@ -14,5 +15,7 @@ namespace Avalonia.Win32.WinRT
{
A = a, R = r, G = g, B = b
};
public Color ToAvalonia() => new(A, R, G, B);
}
}

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

@ -844,3 +844,30 @@ interface ICompositor6 : IInspectable
[overload("CreateGeometricClip")] HRESULT CreateGeometricClip([out] [retval] ICompositionGeometricClip** result);
[overload("CreateGeometricClip")] HRESULT CreateGeometricClipWithGeometry([in] ICompositionGeometry* geometry, [out] [retval] ICompositionGeometricClip** result);
}
enum UIColorType
{
Background = 0,
Foreground = 1,
AccentDark3 = 2,
AccentDark2 = 3,
AccentDark1 = 4,
Accent = 5,
AccentLight1 = 6,
AccentLight2 = 7,
AccentLight3 = 8,
Complement = 9
}
[uuid(03021BE4-5254-4781-8194-5168F7D06D7B)]
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);
}

13
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -805,6 +805,19 @@ namespace Avalonia.Win32
_topmost = value;
}
public unsafe void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{
if (Win32Platform.WindowsVersion.Build >= 22000)
{
var pvUseBackdropBrush = themeVariant == PlatformThemeVariant.Dark ? 1 : 0;
DwmSetWindowAttribute(
_hwnd,
(int)DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE,
&pvUseBackdropBrush,
sizeof(int));
}
}
protected virtual IntPtr CreateWindowOverride(ushort atom)
{
return CreateWindowEx(

35
src/iOS/Avalonia.iOS/AvaloniaView.cs

@ -52,6 +52,22 @@ namespace Avalonia.iOS
public override bool CanResignFirstResponder => true;
public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
{
base.TraitCollectionDidChange(previousTraitCollection);
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as PlatformSettings;
settings?.TraitCollectionDidChange();
}
public override void TintColorDidChange()
{
base.TintColorDidChange();
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as PlatformSettings;
settings?.TraitCollectionDidChange();
}
internal class TopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost,
ITopLevelImplWithStorageProvider
{
@ -120,7 +136,24 @@ namespace Avalonia.iOS
// legacy no-op
public IMouseDevice MouseDevice { get; } = new MouseDevice();
public WindowTransparencyLevel TransparencyLevel { get; }
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{
// TODO adjust status bar depending on full screen mode.
if (OperatingSystem.IsIOSVersionAtLeast(13))
{
var uiStatusBarStyle = themeVariant switch
{
PlatformThemeVariant.Light => UIStatusBarStyle.DarkContent,
PlatformThemeVariant.Dark => UIStatusBarStyle.LightContent,
_ => throw new ArgumentOutOfRangeException(nameof(themeVariant), themeVariant, null)
};
// Consider using UIViewController.PreferredStatusBarStyle in the future.
UIApplication.SharedApplication.SetStatusBarStyle(uiStatusBarStyle, true);
}
}
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } =
new AcrylicPlatformCompensationLevels();

2
src/iOS/Avalonia.iOS/Platform.cs

@ -40,7 +40,7 @@ namespace Avalonia.iOS
.Bind<ICursorFactory>().ToConstant(new CursorFactoryStub())
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IClipboard>().ToConstant(new ClipboardImpl())
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoaderStub())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IRenderLoop>().ToSingleton<RenderLoop>()

65
src/iOS/Avalonia.iOS/PlatformSettings.cs

@ -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);
}
}
}

2
tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs

@ -104,6 +104,8 @@ public class CompositorTestsBase
}
public WindowTransparencyLevel TransparencyLevel { get; }
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
}

2665
tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json

File diff suppressed because it is too large
Loading…
Cancel
Save