Browse Source

Allow setting ThemeVariant to Default to reset theme to system on android (#16340)

* allow setting ThemeVariant to Default to reset theme to system on android

* update api diff

* Fix theme variant not being applied when switching from Light/Dark to Default

* Use IPlatformSettings as a fallback in macOS SetFrameThemeVariant

* fix nullable check in android avaloniaview

* fix SetFrameThemeVariant api suppression

* fix accessibility helper crash

* fix reseting actual theme variant

* rebased

* update apidiff

* update apidiff

* fix default theme selection on desktop

* add missing changes

* fix wayland default theme detection

* fix browser theme detection

* removed redundant overrides

* Fix macOS default theme detection

* Fix iOS default theme detection

* Avoid async void

---------

Co-authored-by: Max Katz <maxkatz6@outlook.com>
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
pull/21644/head
Emmanuel Hansen 3 months ago
committed by GitHub
parent
commit
7846c5cbab
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 36
      api/Avalonia.nupkg.xml
  2. 2
      samples/SafeAreaDemo/App.xaml
  3. 31
      samples/SafeAreaDemo/ViewModels/MainViewModel.cs
  4. 9
      samples/SafeAreaDemo/Views/MainView.xaml
  5. 6
      src/Android/Avalonia.Android/AvaloniaView.Input.cs
  6. 66
      src/Android/Avalonia.Android/AvaloniaView.cs
  7. 5
      src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
  8. 8
      src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs
  9. 23
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  10. 6
      src/Avalonia.Base/Styling/ThemeVariant.cs
  11. 30
      src/Avalonia.Controls/Application.cs
  12. 2
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  13. 2
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  14. 15
      src/Avalonia.Controls/TopLevel.cs
  15. 2
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  16. 5
      src/Avalonia.FreeDesktop/DBusPlatformSettings.cs
  17. 15
      src/Avalonia.Native/NativePlatformSettings.cs
  18. 2
      src/Avalonia.Native/TopLevelImpl.cs
  19. 10
      src/Avalonia.Native/WindowImplBase.cs
  20. 10
      src/Avalonia.Wayland/WindowImplBase.cs
  21. 8
      src/Avalonia.X11/X11Window.cs
  22. 5
      src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs
  23. 6
      src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
  24. 2
      src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
  25. 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  26. 2
      src/Windows/Avalonia.Win32/SwapChainTopLevelImpl.cs
  27. 16
      src/Windows/Avalonia.Win32/Win32PlatformSettings.cs
  28. 8
      src/Windows/Avalonia.Win32/WindowImpl.cs
  29. 7
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  30. 5
      src/iOS/Avalonia.iOS/PlatformSettings.cs
  31. 2
      tests/Avalonia.UnitTests/CompositorTestServices.cs

36
api/Avalonia.nupkg.xml

@ -43,6 +43,18 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetFrameThemeVariant(Avalonia.Platform.PlatformThemeVariant)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Platform.ITopLevelImpl.SetFrameThemeVariant(Avalonia.Platform.PlatformThemeVariant)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.OpenGL.Egl.EglPlatformSurfaceRenderTargetBase.BeginDraw(Avalonia.OpenGL.Egl.EglSurface,Avalonia.PixelSize,System.Double,System.Action,System.Boolean)</Target> <Target>M:Avalonia.OpenGL.Egl.EglPlatformSurfaceRenderTargetBase.BeginDraw(Avalonia.OpenGL.Egl.EglSurface,Avalonia.PixelSize,System.Double,System.Action,System.Boolean)</Target>
@ -91,6 +103,18 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetFrameThemeVariant(Avalonia.Platform.PlatformThemeVariant)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Platform.ITopLevelImpl.SetFrameThemeVariant(Avalonia.Platform.PlatformThemeVariant)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.OpenGL.Egl.EglPlatformSurfaceRenderTargetBase.BeginDraw(Avalonia.OpenGL.Egl.EglSurface,Avalonia.PixelSize,System.Double,System.Action,System.Boolean)</Target> <Target>M:Avalonia.OpenGL.Egl.EglPlatformSurfaceRenderTargetBase.BeginDraw(Avalonia.OpenGL.Egl.EglSurface,Avalonia.PixelSize,System.Double,System.Action,System.Boolean)</Target>
@ -103,10 +127,22 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Platform.ITopLevelImpl.SetFrameThemeVariant(System.Nullable{Avalonia.Platform.PlatformThemeVariant})</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0006</DiagnosticId> <DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl.SetWindowCornerPreference(Avalonia.Controls.Win32Properties.WindowCornerPreference)</Target> <Target>M:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl.SetWindowCornerPreference(Avalonia.Controls.Win32Properties.WindowCornerPreference)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Platform.ITopLevelImpl.SetFrameThemeVariant(System.Nullable{Avalonia.Platform.PlatformThemeVariant})</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
</Suppressions> </Suppressions>

2
samples/SafeAreaDemo/App.xaml

@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SafeAreaDemo" xmlns:local="using:SafeAreaDemo"
x:Class="SafeAreaDemo.App" x:Class="SafeAreaDemo.App"
RequestedThemeVariant="Light"> RequestedThemeVariant="Default">
<Application.DataTemplates> <Application.DataTemplates>
<local:ViewLocator/> <local:ViewLocator/>
</Application.DataTemplates> </Application.DataTemplates>

31
samples/SafeAreaDemo/ViewModels/MainViewModel.cs

@ -3,6 +3,7 @@ using Avalonia;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Styling;
using MiniMvvm; using MiniMvvm;
namespace SafeAreaDemo.ViewModels namespace SafeAreaDemo.ViewModels
@ -33,7 +34,7 @@ namespace SafeAreaDemo.ViewModels
public Rect InputPaneRect => _inputPane?.OccludedRect ?? default; public Rect InputPaneRect => _inputPane?.OccludedRect ?? default;
public Rect CanvasSize { get; set; } public Rect CanvasSize { get; set; }
public Thickness SafeAreaPadding public Thickness SafeAreaPadding
{ {
get get
@ -105,7 +106,7 @@ namespace SafeAreaDemo.ViewModels
set set
{ {
_autoSafeAreaPadding = value; _autoSafeAreaPadding = value;
RaisePropertyChanged(); RaisePropertyChanged();
RaiseSafeAreaChanged(); RaiseSafeAreaChanged();
} }
@ -159,7 +160,7 @@ namespace SafeAreaDemo.ViewModels
this.RaisePropertyChanged(nameof(ViewPadding)); this.RaisePropertyChanged(nameof(ViewPadding));
this.RaisePropertyChanged(nameof(InputPaneMarkerMargin)); this.RaisePropertyChanged(nameof(InputPaneMarkerMargin));
} }
private void RaiseKeyboardChanged() private void RaiseKeyboardChanged()
{ {
this.RaisePropertyChanged(nameof(InputPaneState)); this.RaisePropertyChanged(nameof(InputPaneState));
@ -168,5 +169,29 @@ namespace SafeAreaDemo.ViewModels
this.RaisePropertyChanged(nameof(InputPaneDuration)); this.RaisePropertyChanged(nameof(InputPaneDuration));
this.RaisePropertyChanged(nameof(InputPaneMarkerMargin)); this.RaisePropertyChanged(nameof(InputPaneMarkerMargin));
} }
public void SetLight()
{
if (Application.Current is { } application)
{
application.RequestedThemeVariant = ThemeVariant.Light;
}
}
public void SetDark()
{
if (Application.Current is { } application)
{
application.RequestedThemeVariant = ThemeVariant.Dark;
}
}
public void SetDefault()
{
if (Application.Current is { } application)
{
application.RequestedThemeVariant = ThemeVariant.Default;
}
}
} }
} }

9
samples/SafeAreaDemo/Views/MainView.xaml

@ -8,7 +8,6 @@
d:DesignHeight="450" d:DesignHeight="450"
x:Class="SafeAreaDemo.Views.MainView" x:Class="SafeAreaDemo.Views.MainView"
x:DataType="vm:MainViewModel" x:DataType="vm:MainViewModel"
Background="#ccc"
TopLevel.AutoSafeAreaPadding="{Binding AutoSafeAreaPadding, Mode=TwoWay}"> TopLevel.AutoSafeAreaPadding="{Binding AutoSafeAreaPadding, Mode=TwoWay}">
<Grid HorizontalAlignment="Stretch" <Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
@ -40,6 +39,7 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"
Spacing="5"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center"> VerticalAlignment="Center">
<Label HorizontalAlignment="Left">Options:</Label> <Label HorizontalAlignment="Left">Options:</Label>
@ -47,7 +47,12 @@
<CheckBox IsChecked="{Binding UseSafeArea}" IsEnabled="{Binding !AutoSafeAreaPadding}">Use Safe Area</CheckBox> <CheckBox IsChecked="{Binding UseSafeArea}" IsEnabled="{Binding !AutoSafeAreaPadding}">Use Safe Area</CheckBox>
<CheckBox IsChecked="{Binding AutoSafeAreaPadding}">Automatic Paddings</CheckBox> <CheckBox IsChecked="{Binding AutoSafeAreaPadding}">Automatic Paddings</CheckBox>
<CheckBox IsChecked="{Binding HideSystemBars}">Hide System Bars</CheckBox> <CheckBox IsChecked="{Binding HideSystemBars}">Hide System Bars</CheckBox>
<TextBox Width="200" PlaceholderText="Tap to Show Keyboard"/> <TextBox Width="200" PlaceholderText="Tap to Show Keyboard"/>
<StackPanel Orientation="Horizontal" Spacing="10">
<Button Command="{Binding SetLight}">Light</Button>
<Button Command="{Binding SetDark}">Dark</Button>
<Button Command="{Binding SetDefault}">Default</Button>
</StackPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
</DockPanel> </DockPanel>

6
src/Android/Avalonia.Android/AvaloniaView.Input.cs

@ -22,14 +22,14 @@ namespace Avalonia.Android
protected override void OnFocusChanged(bool gainFocus, FocusSearchDirection direction, global::Android.Graphics.Rect? previouslyFocusedRect) protected override void OnFocusChanged(bool gainFocus, FocusSearchDirection direction, global::Android.Graphics.Rect? previouslyFocusedRect)
{ {
base.OnFocusChanged(gainFocus, direction, previouslyFocusedRect); base.OnFocusChanged(gainFocus, direction, previouslyFocusedRect);
_accessHelper.OnFocusChanged(gainFocus, (int)direction, previouslyFocusedRect); _accessHelper?.OnFocusChanged(gainFocus, (int)direction, previouslyFocusedRect);
} }
protected override bool DispatchHoverEvent(MotionEvent? e) protected override bool DispatchHoverEvent(MotionEvent? e)
{ {
var res = _view.PointerHelper.DispatchMotionEvent(e, out var callBase); var res = _view.PointerHelper.DispatchMotionEvent(e, out var callBase);
if (res == false) if (res == false)
callBase = !_accessHelper.DispatchHoverEvent(e!) && callBase; callBase = (_accessHelper?.DispatchHoverEvent(e!) == true) && callBase;
var baseResult = callBase && base.DispatchHoverEvent(e); var baseResult = callBase && base.DispatchHoverEvent(e);
@ -63,7 +63,7 @@ namespace Avalonia.Android
{ {
var res = _view.KeyboardHelper.DispatchKeyEvent(e, out var callBase); var res = _view.KeyboardHelper.DispatchKeyEvent(e, out var callBase);
if (res == false) if (res == false)
callBase = !_accessHelper.DispatchKeyEvent(e!) && callBase; callBase = _accessHelper?.DispatchKeyEvent(e!) == false && callBase;
var baseResult = callBase && base.DispatchKeyEvent(e); var baseResult = callBase && base.DispatchKeyEvent(e);

66
src/Android/Avalonia.Android/AvaloniaView.cs

@ -19,11 +19,13 @@ namespace Avalonia.Android
{ {
public partial class AvaloniaView : FrameLayout public partial class AvaloniaView : FrameLayout
{ {
private EmbeddableControlRoot _root; private EmbeddableControlRoot? _root;
private ExploreByTouchHelper? _accessHelper;
private readonly ViewImpl _view; private readonly ViewImpl _view;
private readonly ExploreByTouchHelper _accessHelper;
private IDisposable? _timerSubscription; private IDisposable? _timerSubscription;
private object? _content;
private bool _surfaceCreated; private bool _surfaceCreated;
public AvaloniaView(Context context) : base(context) public AvaloniaView(Context context) : base(context)
@ -32,11 +34,13 @@ namespace Avalonia.Android
AddView(_view.View); AddView(_view.View);
this.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
_root = new EmbeddableControlRoot(_view); _root = new EmbeddableControlRoot(_view);
_root.Prepare(); _root.Prepare();
SetBackgroundColor(global::Android.Graphics.Color.Transparent); SetBackgroundColor(global::Android.Graphics.Color.Transparent);
OnConfigurationChanged(); OnConfigurationChanged(null);
_view.InternalView!.SurfaceWindowCreated += InternalView_SurfaceWindowCreated; _view.InternalView!.SurfaceWindowCreated += InternalView_SurfaceWindowCreated;
_view.InternalView.SurfaceWindowDestroyed += InternalView_SurfaceWindowDestroyed; _view.InternalView.SurfaceWindowDestroyed += InternalView_SurfaceWindowDestroyed;
@ -69,16 +73,50 @@ namespace Avalonia.Android
public object? Content public object? Content
{ {
get { return _root.Content; } get { return _root?.Content; }
set { _root.Content = value; } set
{
_content = value;
if (_root != null)
_root.Content = value;
}
} }
internal new void Dispose() internal new void Dispose()
{ {
_root?.Dispose();
_root = null;
_content = null;
}
protected override void OnDetachedFromWindow()
{
base.OnDetachedFromWindow();
OnVisibilityChanged(false); OnVisibilityChanged(false);
_surfaceCreated = false; _surfaceCreated = false;
_root?.Dispose();
_root = null!; if (_accessHelper is { } accessHelper)
{
ViewCompat.SetAccessibilityDelegate(this, null);
_accessHelper = null;
}
}
protected override void OnAttachedToWindow()
{
_root?.Content = null;
_root = new EmbeddableControlRoot(_view);
_root.Prepare();
if (_content != null)
{
_root.Content = _content;
}
_accessHelper = new AvaloniaAccessHelper(this);
ViewCompat.SetAccessibilityDelegate(this, _accessHelper);
SendConfigurationChanged(Context?.Resources?.Configuration);
base.OnAttachedToWindow();
} }
[SupportedOSPlatform("android24.0")] [SupportedOSPlatform("android24.0")]
@ -98,6 +136,7 @@ namespace Avalonia.Android
{ {
if (_root == null || !_surfaceCreated) if (_root == null || !_surfaceCreated)
return; return;
if (isVisible && _timerSubscription == null) if (isVisible && _timerSubscription == null)
{ {
if (AndroidPlatform.Timer is { } timer) if (AndroidPlatform.Timer is { } timer)
@ -119,20 +158,21 @@ namespace Avalonia.Android
_timerSubscription = null; _timerSubscription = null;
} }
} }
protected override void OnConfigurationChanged(Configuration? newConfig) protected override void OnConfigurationChanged(Configuration? newConfig)
{ {
base.OnConfigurationChanged(newConfig); base.OnConfigurationChanged(newConfig);
OnConfigurationChanged(); SendConfigurationChanged(newConfig ?? Context?.Resources?.Configuration);
} }
private void OnConfigurationChanged() private void SendConfigurationChanged(Configuration? newConfig)
{ {
if (Context is { } context) _view.InsetsManager?.SetDefaultSystemLightMode(!(newConfig?.UiMode.HasFlag(UiMode.NightYes) ?? false));
if (Context is { } context && newConfig is { } config)
{ {
var settings = var settings =
AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as AndroidPlatformSettings; AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as AndroidPlatformSettings;
settings?.OnViewConfigurationChanged(context); settings?.OnViewConfigurationChanged(context, config);
((AndroidScreens)_view.TryGetFeature<IScreenImpl>()!).OnChanged(); ((AndroidScreens)_view.TryGetFeature<IScreenImpl>()!).OnChanged();
} }
} }
@ -146,7 +186,7 @@ namespace Avalonia.Android
private void ViewImpl_FocusChange(object? sender, FocusChangeEventArgs e) private void ViewImpl_FocusChange(object? sender, FocusChangeEventArgs e)
{ {
if(!e.HasFocus) if (!e.HasFocus)
LostFocus?.Invoke(); LostFocus?.Invoke();
} }
} }

5
src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs

@ -187,6 +187,11 @@ namespace Avalonia.Android.Platform
return insets; return insets;
} }
internal void SetDefaultSystemLightMode(bool islightMode)
{
_isDefaultSystemBarLightTheme = islightMode;
}
private void NotifySafeAreaChanged(Thickness safeAreaPadding) private void NotifySafeAreaChanged(Thickness safeAreaPadding)
{ {
Dispatcher.UIThread.Send(_ => SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding))); Dispatcher.UIThread.Send(_ => SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding)));

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

@ -49,14 +49,14 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings
public override TimeSpan HoldWaitDuration => _holdWaitDuration; public override TimeSpan HoldWaitDuration => _holdWaitDuration;
internal void OnViewConfigurationChanged(Context context) internal void OnViewConfigurationChanged(Context context, Configuration configuration)
{ {
if (context.Resources?.Configuration is null) if (context.Resources is null)
{ {
return; return;
} }
var systemTheme = (context.Resources.Configuration.UiMode & UiMode.NightMask) switch PlatformThemeVariant systemTheme = (configuration.UiMode & UiMode.NightMask) switch
{ {
UiMode.NightYes => PlatformThemeVariant.Dark, UiMode.NightYes => PlatformThemeVariant.Dark,
UiMode.NightNo => PlatformThemeVariant.Light, UiMode.NightNo => PlatformThemeVariant.Light,
@ -98,7 +98,7 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings
} }
finally finally
{ {
array.Recycle(); array.Recycle();
} }
} }
} }

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

@ -8,6 +8,7 @@ using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
using AndroidX.AppCompat.App; using AndroidX.AppCompat.App;
using AndroidX.Core.View;
using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Android.Platform.Storage; using Avalonia.Android.Platform.Storage;
@ -232,7 +233,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
} }
} }
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant)
{ {
if (_insetsManager != null) if (_insetsManager != null)
{ {
@ -244,7 +245,23 @@ namespace Avalonia.Android.Platform.SkiaPlatform
}; };
} }
AppCompatDelegate.DefaultNightMode = themeVariant == PlatformThemeVariant.Light ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes; // Sets the default app NightMode to AppCompatDelegate.ModeNightFollowSystem when themeVariant is null. This
// allows app to follow the current OS night mode. Using either ModeNightNo or ModeNightYes will force the app
// to use one night mode, ignoring the system's configuration and preventing us from detecting system theme changes
// in View.OnConfigurationChanged. In this case, only Activity.OnConfigurationChanged is called when system theme is
// changed, but we don't have access to that method if the toplevel view is embedded in a custom activity
var nightMode = themeVariant == null ? AppCompatDelegate.ModeNightFollowSystem :
themeVariant == PlatformThemeVariant.Light ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes;
AppCompatDelegate.DefaultNightMode = nightMode;
if (nightMode == AppCompatDelegate.ModeNightFollowSystem && _view?.Context is { } context
&& context.Resources?.Configuration is { } config)
{
var settings =
AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as AndroidPlatformSettings;
settings?.OnViewConfigurationChanged(context, config);
}
} }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new(1, 1, 1); public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new(1, 1, 1);
@ -254,8 +271,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
PixelSize EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Size => _view?.Size ?? default; PixelSize EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Size => _view?.Size ?? default;
double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => _view?.Scaling ?? default; double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => _view?.Scaling ?? default;
internal AndroidInsetsManager? InsetsManager => _insetsManager;
internal AndroidKeyboardEventsHelper<TopLevelImpl> KeyboardHelper => _keyboardHelper; internal AndroidKeyboardEventsHelper<TopLevelImpl> KeyboardHelper => _keyboardHelper;
internal AndroidMotionEventsHelper PointerHelper => _pointerHelper; internal AndroidMotionEventsHelper PointerHelper => _pointerHelper;
public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels) public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels)

6
src/Avalonia.Base/Styling/ThemeVariant.cs

@ -100,11 +100,11 @@ public sealed record ThemeVariant
{ {
PlatformThemeVariant.Light => Light, PlatformThemeVariant.Light => Light,
PlatformThemeVariant.Dark => Dark, PlatformThemeVariant.Dark => Dark,
_ => throw new ArgumentOutOfRangeException(nameof(themeVariant), themeVariant, null) _ => throw new ArgumentOutOfRangeException(nameof(themeVariant), themeVariant, null),
}; };
} }
public static explicit operator PlatformThemeVariant?(ThemeVariant themeVariant) public static explicit operator PlatformThemeVariant?(ThemeVariant? themeVariant)
{ {
if (themeVariant == Light) if (themeVariant == Light)
{ {
@ -114,7 +114,7 @@ public sealed record ThemeVariant
{ {
return PlatformThemeVariant.Dark; return PlatformThemeVariant.Dark;
} }
else if (themeVariant.InheritVariant is { } inheritVariant) else if (themeVariant?.InheritVariant is { } inheritVariant)
{ {
return (PlatformThemeVariant?)inheritVariant; return (PlatformThemeVariant?)inheritVariant;
} }

30
src/Avalonia.Controls/Application.cs

@ -53,7 +53,7 @@ namespace Avalonia
/// <inheritdoc cref="ThemeVariantScope.ActualThemeVariantProperty" /> /// <inheritdoc cref="ThemeVariantScope.ActualThemeVariantProperty" />
public static readonly StyledProperty<ThemeVariant> ActualThemeVariantProperty = public static readonly StyledProperty<ThemeVariant> ActualThemeVariantProperty =
ThemeVariantScope.ActualThemeVariantProperty.AddOwner<Application>(); ThemeVariantScope.ActualThemeVariantProperty.AddOwner<Application>();
/// <inheritdoc cref="ThemeVariantScope.RequestedThemeVariantProperty" /> /// <inheritdoc cref="ThemeVariantScope.RequestedThemeVariantProperty" />
public static readonly StyledProperty<ThemeVariant?> RequestedThemeVariantProperty = public static readonly StyledProperty<ThemeVariant?> RequestedThemeVariantProperty =
ThemeVariantScope.RequestedThemeVariantProperty.AddOwner<Application>(); ThemeVariantScope.RequestedThemeVariantProperty.AddOwner<Application>();
@ -91,7 +91,7 @@ namespace Avalonia
get => GetValue(RequestedThemeVariantProperty); get => GetValue(RequestedThemeVariantProperty);
set => SetValue(RequestedThemeVariantProperty, value); set => SetValue(RequestedThemeVariantProperty, value);
} }
/// <inheritdoc /> /// <inheritdoc />
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1031", Justification = "This property is supposed to be a styled readonly property.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1031", Justification = "This property is supposed to be a styled readonly property.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1030", Justification = "False positive.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1030", Justification = "False positive.")]
@ -201,7 +201,7 @@ namespace Avalonia
/// as specific top levels might have different settings set-up. /// as specific top levels might have different settings set-up.
/// </remarks> /// </remarks>
public IPlatformSettings? PlatformSettings => this.TryGetFeature<IPlatformSettings>(); public IPlatformSettings? PlatformSettings => this.TryGetFeature<IPlatformSettings>();
event Action<IReadOnlyList<IStyle>>? IGlobalStyles.GlobalStylesAdded event Action<IReadOnlyList<IStyle>>? IGlobalStyles.GlobalStylesAdded
{ {
add => _stylesAdded += value; add => _stylesAdded += value;
@ -218,7 +218,7 @@ namespace Avalonia
/// Initializes the application by loading XAML etc. /// Initializes the application by loading XAML etc.
/// </summary> /// </summary>
public virtual void Initialize() { } public virtual void Initialize() { }
/// <inheritdoc/> /// <inheritdoc/>
public bool TryGetResource(object key, ThemeVariant? theme, out object? value) public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
{ {
@ -262,7 +262,7 @@ namespace Avalonia
.Bind<IGlobalStyles>().ToConstant(this) .Bind<IGlobalStyles>().ToConstant(this)
.Bind<IThemeVariantHost>().ToConstant(this) .Bind<IThemeVariantHost>().ToConstant(this)
.Bind<IInputManager>().ToConstant(InputManager) .Bind<IInputManager>().ToConstant(InputManager)
.Bind< IToolTipService>().ToConstant(new ToolTipService(InputManager)) .Bind<IToolTipService>().ToConstant(new ToolTipService(InputManager))
.Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>() .Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>()
.Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance); .Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance);
@ -330,20 +330,28 @@ namespace Avalonia
if (change.Property == RequestedThemeVariantProperty) if (change.Property == RequestedThemeVariantProperty)
{ {
if (change.GetNewValue<ThemeVariant>() is {} themeVariant && themeVariant != ThemeVariant.Default) if (change.GetNewValue<ThemeVariant>() is { } themeVariant)
SetValue(ActualThemeVariantProperty, themeVariant); {
else if (themeVariant == ThemeVariant.Default)
ClearValue(ActualThemeVariantProperty); {
ClearValue(ActualThemeVariantProperty);
}
else
{
SetValue(ActualThemeVariantProperty, themeVariant);
}
}
} }
else if (change.Property == ActualThemeVariantProperty) else if (change.Property == ActualThemeVariantProperty)
{ {
ActualThemeVariantChanged?.Invoke(this, EventArgs.Empty); ActualThemeVariantChanged?.Invoke(this, EventArgs.Empty);
} }
} }
private void OnColorValuesChanged(object? sender, PlatformColorValues e) private void OnColorValuesChanged(object? sender, PlatformColorValues e)
{ {
SetValue(ActualThemeVariantProperty, (ThemeVariant)e.ThemeVariant, BindingPriority.Template); if ((RequestedThemeVariant ?? ThemeVariant.Default) == ThemeVariant.Default)
SetValue(ActualThemeVariantProperty, (ThemeVariant)e.ThemeVariant);
} }
} }
} }

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

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

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

@ -137,6 +137,6 @@ namespace Avalonia.Platform
/// Sets the <see cref="PlatformThemeVariant"/> on the frame if it should be dark or light. /// Sets the <see cref="PlatformThemeVariant"/> on the frame if it should be dark or light.
/// Also applies for the mobile status bar. /// Also applies for the mobile status bar.
/// </summary> /// </summary>
void SetFrameThemeVariant(PlatformThemeVariant themeVariant); void SetFrameThemeVariant(PlatformThemeVariant? themeVariant);
} }
} }

15
src/Avalonia.Controls/TopLevel.cs

@ -239,11 +239,6 @@ namespace Avalonia.Controls
impl.TransparencyLevelChanged = HandleTransparencyLevelChanged; impl.TransparencyLevelChanged = HandleTransparencyLevelChanged;
CreatePlatformImplBinding(TransparencyLevelHintProperty, hint => PlatformImpl.SetTransparencyLevelHint(hint ?? Array.Empty<WindowTransparencyLevel>())); CreatePlatformImplBinding(TransparencyLevelHintProperty, hint => PlatformImpl.SetTransparencyLevelHint(hint ?? Array.Empty<WindowTransparencyLevel>()));
CreatePlatformImplBinding(ActualThemeVariantProperty, variant =>
{
variant ??= ThemeVariant.Default;
PlatformImpl?.SetFrameThemeVariant((PlatformThemeVariant?)variant ?? PlatformThemeVariant.Light);
});
_keyboardNavigationHandler?.SetOwner(this); _keyboardNavigationHandler?.SetOwner(this);
_accessKeyHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this);
@ -258,6 +253,16 @@ namespace Avalonia.Controls
SetValue(ActualThemeVariantProperty, _applicationThemeHost.ActualThemeVariant, BindingPriority.Template); SetValue(ActualThemeVariantProperty, _applicationThemeHost.ActualThemeVariant, BindingPriority.Template);
_applicationThemeHost.ActualThemeVariantChanged += GlobalActualThemeVariantChanged; _applicationThemeHost.ActualThemeVariantChanged += GlobalActualThemeVariantChanged;
} }
CreatePlatformImplBinding(ActualThemeVariantProperty, variant =>
{
if(_applicationThemeHost is AvaloniaObject element)
{
if (element.GetValue(ThemeVariantScope.RequestedThemeVariantProperty) is { } requestedThemeVariant)
variant = requestedThemeVariant == ThemeVariant.Default ? ThemeVariant.Default : variant;
}
variant ??= ThemeVariant.Default;
PlatformImpl?.SetFrameThemeVariant((PlatformThemeVariant?)variant);
});
ClientSize = impl.ClientSize; ClientSize = impl.ClientSize;

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

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

5
src/Avalonia.FreeDesktop/DBusPlatformSettings.cs

@ -37,6 +37,11 @@ namespace Avalonia.FreeDesktop
Dispatcher.UIThread.Post(() => OnColorValuesChanged(_lastColorValues)); Dispatcher.UIThread.Post(() => OnColorValuesChanged(_lastColorValues));
} }
internal void OnRequestDefaultThemeVariant()
{
_ = TryGetInitialValuesAsync();
}
private async Task<PlatformThemeVariant?> TryGetThemeVariantAsync() private async Task<PlatformThemeVariant?> TryGetThemeVariantAsync()
{ {
try try

15
src/Avalonia.Native/NativePlatformSettings.cs

@ -8,7 +8,6 @@ namespace Avalonia.Native;
internal class NativePlatformSettings : DefaultPlatformSettings internal class NativePlatformSettings : DefaultPlatformSettings
{ {
private readonly IAvnPlatformSettings _platformSettings; private readonly IAvnPlatformSettings _platformSettings;
private PlatformColorValues? _lastColorValues;
public NativePlatformSettings(IAvnPlatformSettings platformSettings) public NativePlatformSettings(IAvnPlatformSettings platformSettings)
{ {
@ -30,7 +29,7 @@ internal class NativePlatformSettings : DefaultPlatformSettings
if (color > 0) if (color > 0)
{ {
_lastColorValues = new PlatformColorValues return new PlatformColorValues
{ {
ThemeVariant = theme, ThemeVariant = theme,
ContrastPreference = contrast, ContrastPreference = contrast,
@ -39,25 +38,17 @@ internal class NativePlatformSettings : DefaultPlatformSettings
} }
else else
{ {
_lastColorValues = new PlatformColorValues return new PlatformColorValues
{ {
ThemeVariant = theme, ThemeVariant = theme,
ContrastPreference = contrast ContrastPreference = contrast
}; };
} }
return _lastColorValues;
} }
public void OnColorValuesChanged() public void OnColorValuesChanged()
{ {
var oldColorValues = _lastColorValues; OnColorValuesChanged(GetColorValues());
var colorValues = GetColorValues();
if (oldColorValues != colorValues)
{
OnColorValuesChanged(colorValues);
}
} }
private class ColorsChangeCallback : NativeCallbackBase, IAvnActionCallback private class ColorsChangeCallback : NativeCallbackBase, IAvnActionCallback

2
src/Avalonia.Native/TopLevelImpl.cs

@ -158,7 +158,7 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface
} }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0); public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0);
public virtual void SetFrameThemeVariant(PlatformThemeVariant themeVariant) public virtual void SetFrameThemeVariant(PlatformThemeVariant? themeVariant)
{ {
//noop //noop
} }

10
src/Avalonia.Native/WindowImplBase.cs

@ -68,8 +68,16 @@ namespace Avalonia.Native
Native?.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason); Native?.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason);
} }
public override void SetFrameThemeVariant(PlatformThemeVariant themeVariant) public override void SetFrameThemeVariant(PlatformThemeVariant? themeVariant)
{ {
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
if (themeVariant is null && settings is NativePlatformSettings typedSettings)
{
typedSettings.OnColorValuesChanged();
}
themeVariant ??= settings?.GetColorValues().ThemeVariant ?? PlatformThemeVariant.Light;
Native?.SetFrameThemeVariant((AvnPlatformThemeVariant)themeVariant); Native?.SetFrameThemeVariant((AvnPlatformThemeVariant)themeVariant);
} }

10
src/Avalonia.Wayland/WindowImplBase.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.FreeDesktop;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
@ -76,7 +77,14 @@ internal abstract partial class WindowBaseImpl : IWindowBaseImpl
public WindowTransparencyLevel TransparencyLevel { get; } = WindowTransparencyLevel.Transparent; public WindowTransparencyLevel TransparencyLevel { get; } = WindowTransparencyLevel.Transparent;
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = default; public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = default;
public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels) { } public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels) { }
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
public async void SetFrameThemeVariant(PlatformThemeVariant? themeVariant)
{
if (themeVariant == null && AvaloniaLocator.Current.GetService<IPlatformSettings>() is DBusPlatformSettings platformSettings)
{
platformSettings.OnRequestDefaultThemeVariant();
}
}
public Action? Closed { get; set; } public Action? Closed { get; set; }
public Action? LostFocus { get; set; } public Action? LostFocus { get; set; }

8
src/Avalonia.X11/X11Window.cs

@ -1662,7 +1662,13 @@ namespace Avalonia.X11
public WindowTransparencyLevel TransparencyLevel => public WindowTransparencyLevel TransparencyLevel =>
_transparencyHelper?.CurrentLevel ?? WindowTransparencyLevel.None; _transparencyHelper?.CurrentLevel ?? WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } public async void SetFrameThemeVariant(PlatformThemeVariant? themeVariant)
{
if (themeVariant == null && AvaloniaLocator.Current.GetService<IPlatformSettings>() is DBusPlatformSettings platformSettings)
{
platformSettings.OnRequestDefaultThemeVariant();
}
}
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8); public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8);

5
src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs

@ -37,6 +37,11 @@ internal class BrowserPlatformSettings : DefaultPlatformSettings
_isHighContrast = isHighContrast; _isHighContrast = isHighContrast;
OnColorValuesChanged(GetColorValues()); OnColorValuesChanged(GetColorValues());
} }
public void OnValuesChanged()
{
OnColorValuesChanged(GetColorValues());
}
private void EnsureBackend() private void EnsureBackend()
{ {

6
src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs

@ -144,9 +144,13 @@ namespace Avalonia.Browser
public Action? LostFocus { get; set; } public Action? LostFocus { get; set; }
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) 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. // not in the standard, but we potentially can use "apple-mobile-web-app-status-bar-style" for iOS and "theme-color" for android.
if (themeVariant == null && AvaloniaLocator.Current.GetService<IPlatformSettings>() is BrowserPlatformSettings platformSettings)
{
platformSettings.OnValuesChanged();
}
} }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }

2
src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs

@ -438,7 +438,7 @@ namespace Avalonia.Headless
} }
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant)
{ {
} }

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

@ -78,7 +78,7 @@ using Avalonia.Rendering.Composition;
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
public object? TryGetFeature(Type featureType) => null; public object? TryGetFeature(Type featureType) => null;

2
src/Windows/Avalonia.Win32/SwapChainTopLevelImpl.cs

@ -99,7 +99,7 @@ internal class SwapChainTopLevelImpl : ITopLevelImpl
public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels) { } public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels) { }
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { }
/// <summary> /// <summary>
/// Optional IME implementation provided by the host. /// Optional IME implementation provided by the host.

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

@ -10,9 +10,7 @@ internal class Win32PlatformSettings : DefaultPlatformSettings
{ {
private static readonly Lazy<bool> s_uiSettingsSupported = new(() => private static readonly Lazy<bool> s_uiSettingsSupported = new(() =>
WinRTApiInformation.IsTypePresent("Windows.UI.ViewManagement.UISettings") WinRTApiInformation.IsTypePresent("Windows.UI.ViewManagement.UISettings")
&& WinRTApiInformation.IsTypePresent("Windows.UI.ViewManagement.AccessibilitySettings")); && WinRTApiInformation.IsTypePresent("Windows.UI.ViewManagement.AccessibilitySettings"));
private PlatformColorValues? _lastColorValues;
public override Size GetTapSize(PointerType type) public override Size GetTapSize(PointerType type)
{ {
@ -54,7 +52,7 @@ internal class Win32PlatformSettings : DefaultPlatformSettings
// - Night sky - High Contrast #2 // - Night sky - High Contrast #2
// Only "Desert" one can be considered a "light" preference. // Only "Desert" one can be considered a "light" preference.
using var highContrastScheme = new HStringInterop(accessibilitySettings.HighContrastScheme); using var highContrastScheme = new HStringInterop(accessibilitySettings.HighContrastScheme);
return _lastColorValues = new PlatformColorValues return new PlatformColorValues
{ {
ThemeVariant = highContrastScheme.Value?.Contains("White") == true ? ThemeVariant = highContrastScheme.Value?.Contains("White") == true ?
PlatformThemeVariant.Light : PlatformThemeVariant.Light :
@ -67,7 +65,7 @@ internal class Win32PlatformSettings : DefaultPlatformSettings
else else
{ {
var background = uiSettings.GetColorValue(UIColorType.Background).ToAvalonia(); var background = uiSettings.GetColorValue(UIColorType.Background).ToAvalonia();
return _lastColorValues = new PlatformColorValues return new PlatformColorValues
{ {
ThemeVariant = background.R + background.G + background.B < (255 * 3 - background.R - background.G - background.B) ? ThemeVariant = background.R + background.G + background.B < (255 * 3 - background.R - background.G - background.B) ?
PlatformThemeVariant.Dark : PlatformThemeVariant.Dark :
@ -80,12 +78,6 @@ internal class Win32PlatformSettings : DefaultPlatformSettings
internal void OnColorValuesChanged() internal void OnColorValuesChanged()
{ {
var oldColorValues = _lastColorValues; OnColorValuesChanged(GetColorValues());
var colorValues = GetColorValues();
if (oldColorValues != colorValues)
{
OnColorValuesChanged(colorValues);
}
} }
} }

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

@ -948,12 +948,12 @@ namespace Avalonia.Win32
} }
} }
public unsafe void SetFrameThemeVariant(PlatformThemeVariant themeVariant) public unsafe void SetFrameThemeVariant(PlatformThemeVariant? themeVariant)
{ {
_currentThemeVariant = themeVariant; _currentThemeVariant = themeVariant ?? Win32Platform.Instance.PlatformSettings.GetColorValues().ThemeVariant;
if (Win32Platform.WindowsVersion.Build >= 22000) if (Win32Platform.WindowsVersion.Build >= 22000)
{ {
var pvUseBackdropBrush = themeVariant == PlatformThemeVariant.Dark ? 1 : 0; var pvUseBackdropBrush = _currentThemeVariant == PlatformThemeVariant.Dark ? 1 : 0;
DwmSetWindowAttribute( DwmSetWindowAttribute(
_hwnd, _hwnd,
(int)DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE, (int)DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE,
@ -964,6 +964,8 @@ namespace Avalonia.Win32
SetTransparencyMica(); SetTransparencyMica();
} }
} }
(Win32Platform.Instance.PlatformSettings as Win32PlatformSettings)?.OnColorValuesChanged();
} }
protected virtual IntPtr CreateWindowOverride(ushort atom) protected virtual IntPtr CreateWindowOverride(ushort atom)

7
src/iOS/Avalonia.iOS/AvaloniaView.cs

@ -254,8 +254,13 @@ namespace Avalonia.iOS
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant)
{ {
if (themeVariant is null && AvaloniaLocator.Current.GetService<IPlatformSettings>() is PlatformSettings settings)
{
settings.OnColorValuesChanged();
}
#if !TVOS #if !TVOS
// TODO adjust status bar depending on full screen mode. // TODO adjust status bar depending on full screen mode.
if ((OperatingSystem.IsIOSVersionAtLeast(13) if ((OperatingSystem.IsIOSVersionAtLeast(13)

5
src/iOS/Avalonia.iOS/PlatformSettings.cs

@ -61,4 +61,9 @@ internal class PlatformSettings : DefaultPlatformSettings
OnColorValuesChanged(colorValues); OnColorValuesChanged(colorValues);
} }
} }
internal void OnColorValuesChanged()
{
OnColorValuesChanged(GetColorValues());
}
} }

2
tests/Avalonia.UnitTests/CompositorTestServices.cs

@ -205,7 +205,7 @@ public class CompositorTestServices : IDisposable
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant)
{ {
} }

Loading…
Cancel
Save