From 91348e22424aebc21218fbe9483798d37724b219 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 17 Jul 2024 12:07:25 +0000 Subject: [PATCH 01/16] allow setting ThemeVariant to Default to reset theme to system on android --- samples/SafeAreaDemo/App.xaml | 2 +- samples/SafeAreaDemo/Views/MainView.xaml | 1 - src/Android/Avalonia.Android/AvaloniaView.cs | 51 ++++++++++++++----- .../Platform/AndroidInsetsManager.cs | 5 ++ .../Platform/AndroidPlatformSettings.cs | 8 +-- .../Platform/SkiaPlatform/TopLevelImpl.cs | 17 +++++-- src/Avalonia.Base/Styling/ThemeVariant.cs | 6 +-- src/Avalonia.Controls/Application.cs | 14 +++-- .../Offscreen/OffscreenTopLevelImpl.cs | 2 +- .../Platform/ITopLevelImpl.cs | 2 +- src/Avalonia.Controls/ThemeVariantScope.cs | 5 ++ src/Avalonia.Controls/TopLevel.cs | 17 ++++--- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Native/TopLevelImpl.cs | 2 +- src/Avalonia.Native/WindowImplBase.cs | 4 +- src/Avalonia.X11/X11Window.cs | 2 +- .../Avalonia.Browser/BrowserTopLevelImpl.cs | 2 +- .../Avalonia.Headless/HeadlessWindowImpl.cs | 2 +- .../FramebufferToplevelImpl.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 4 +- src/iOS/Avalonia.iOS/AvaloniaView.cs | 2 +- .../CompositorTestServices.cs | 2 +- 22 files changed, 105 insertions(+), 49 deletions(-) diff --git a/samples/SafeAreaDemo/App.xaml b/samples/SafeAreaDemo/App.xaml index f406bfe710..70fd759dad 100644 --- a/samples/SafeAreaDemo/App.xaml +++ b/samples/SafeAreaDemo/App.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:SafeAreaDemo" x:Class="SafeAreaDemo.App" - RequestedThemeVariant="Light"> + RequestedThemeVariant="Default"> diff --git a/samples/SafeAreaDemo/Views/MainView.xaml b/samples/SafeAreaDemo/Views/MainView.xaml index 85163e7dad..a2b336abe0 100644 --- a/samples/SafeAreaDemo/Views/MainView.xaml +++ b/samples/SafeAreaDemo/Views/MainView.xaml @@ -8,7 +8,6 @@ d:DesignHeight="450" x:Class="SafeAreaDemo.Views.MainView" x:DataType="vm:MainViewModel" - Background="#ccc" TopLevel.AutoSafeAreaPadding="{Binding AutoSafeAreaPadding, Mode=TwoWay}"> _view; @@ -39,15 +36,42 @@ namespace Avalonia.Android public object? Content { - get { return _root.Content; } - set { _root.Content = value; } + get { return _root?.Content; } + set + { + _content = null; + if (_root != null) + _root.Content = value; + else + { + _content = value; + } + } } protected override void Dispose(bool disposing) { base.Dispose(disposing); + } + + protected override void OnDetachedFromWindow() + { + base.OnDetachedFromWindow(); _root?.Dispose(); - _root = null!; + _root = null; + } + + protected override void OnAttachedToWindow() + { + _root = new EmbeddableControlRoot(_view); + _root.Prepare(); + if(_content != null) + { + _root.Content = _content; + } + SendConfigurationChanged(Context?.Resources?.Configuration); + + base.OnAttachedToWindow(); } public override bool DispatchKeyEvent(KeyEvent? e) @@ -70,6 +94,8 @@ namespace Avalonia.Android internal void OnVisibilityChanged(bool isVisible) { + if (_root == null) + return; if (isVisible && _timerSubscription == null) { if (AvaloniaLocator.Current.GetService() is ChoreographerTimer timer) @@ -95,16 +121,17 @@ namespace Avalonia.Android protected override void OnConfigurationChanged(Configuration? 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 = AvaloniaLocator.Current.GetRequiredService() as AndroidPlatformSettings; - settings?.OnViewConfigurationChanged(context); + settings?.OnViewConfigurationChanged(context, config); } } diff --git a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs index 992d37ed18..ebabc50e5a 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs +++ b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs @@ -166,6 +166,11 @@ namespace Avalonia.Android.Platform return insets; } + internal void SetDefaultSystemLightMode(bool islightMode) + { + _isDefaultSystemBarLightTheme = islightMode; + } + private void NotifySafeAreaChanged(Thickness safeAreaPadding) { Dispatcher.UIThread.Send(_ => SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding))); diff --git a/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs b/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs index 3963a88203..8ca921c03c 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs +++ b/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs @@ -22,14 +22,14 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings return _latestValues; } - internal void OnViewConfigurationChanged(Context context) + internal void OnViewConfigurationChanged(Context context, Configuration configuration) { - if (context.Resources?.Configuration is null) + if (context.Resources is null) { return; } - var systemTheme = (context.Resources.Configuration.UiMode & UiMode.NightMask) switch + PlatformThemeVariant systemTheme = (configuration.UiMode & UiMode.NightMask) switch { UiMode.NightYes => PlatformThemeVariant.Dark, UiMode.NightNo => PlatformThemeVariant.Light, @@ -71,7 +71,7 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings } finally { - array.Recycle(); + array.Recycle(); } } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 79052bf9bd..8a666ecf63 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -282,9 +282,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform } } - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { - if(_insetsManager != null) + if (_insetsManager != null) { _insetsManager.SystemBarTheme = themeVariant switch { @@ -294,7 +294,16 @@ namespace Avalonia.Android.Platform.SkiaPlatform }; } - AppCompatDelegate.DefaultNightMode = themeVariant == PlatformThemeVariant.Light ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes; + AppCompatDelegate.DefaultNightMode = themeVariant == null ? AppCompatDelegate.ModeNightFollowSystem : + themeVariant == PlatformThemeVariant.Light ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes; + + if (AppCompatDelegate.DefaultNightMode == AppCompatDelegate.ModeNightFollowSystem && _view.Context is { } context + && context.Resources?.Configuration is { } config) + { + var settings = + AvaloniaLocator.Current.GetRequiredService() as AndroidPlatformSettings; + settings?.OnViewConfigurationChanged(context, config); + } } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1); @@ -305,6 +314,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform public double Scaling => RenderScaling; + internal AndroidInsetsManager? InsetsManager => _insetsManager; + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { if (_view.Context is not AvaloniaMainActivity activity) diff --git a/src/Avalonia.Base/Styling/ThemeVariant.cs b/src/Avalonia.Base/Styling/ThemeVariant.cs index 23bc15dfa7..9a1fb5f931 100644 --- a/src/Avalonia.Base/Styling/ThemeVariant.cs +++ b/src/Avalonia.Base/Styling/ThemeVariant.cs @@ -100,11 +100,11 @@ public sealed record ThemeVariant { PlatformThemeVariant.Light => Light, PlatformThemeVariant.Dark => Dark, - _ => throw new ArgumentOutOfRangeException(nameof(themeVariant), themeVariant, null) + _ => Default, }; } - public static explicit operator PlatformThemeVariant?(ThemeVariant themeVariant) + public static explicit operator PlatformThemeVariant?(ThemeVariant? themeVariant) { if (themeVariant == Light) { @@ -114,7 +114,7 @@ public sealed record ThemeVariant { return PlatformThemeVariant.Dark; } - else if (themeVariant.InheritVariant is { } inheritVariant) + else if (themeVariant?.InheritVariant is { } inheritVariant) { return (PlatformThemeVariant?)inheritVariant; } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index e48711efdb..210378b933 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -68,6 +68,11 @@ namespace Avalonia /// public event EventHandler? ActualThemeVariantChanged; + static Application() + { + RequestedThemeVariantProperty.OverrideDefaultValue(ThemeVariant.Default); + } + /// /// Creates an instance of the class. /// @@ -364,20 +369,19 @@ namespace Avalonia if (change.Property == RequestedThemeVariantProperty) { - if (change.GetNewValue() is {} themeVariant && themeVariant != ThemeVariant.Default) + if (change.GetNewValue() is { } themeVariant) SetValue(ActualThemeVariantProperty, themeVariant); - else - ClearValue(ActualThemeVariantProperty); } else if (change.Property == ActualThemeVariantProperty) { ActualThemeVariantChanged?.Invoke(this, EventArgs.Empty); } } - + private void OnColorValuesChanged(object? sender, PlatformColorValues e) { - SetValue(ActualThemeVariantProperty, (ThemeVariant)e.ThemeVariant, BindingPriority.Template); + if ((RequestedThemeVariant ?? ThemeVariant.Default) == ThemeVariant.Default) + SetCurrentValue(ActualThemeVariantProperty, (ThemeVariant)e.ThemeVariant); } } } diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 14bb65f0df..5668c017b4 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -61,7 +61,7 @@ namespace Avalonia.Controls.Embedding.Offscreen public Action? TransparencyLevelChanged { get; set; } - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 9b847d71d3..37bcc7a2b2 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -136,6 +136,6 @@ namespace Avalonia.Platform /// Sets the on the frame if it should be dark or light. /// Also applies for the mobile status bar. /// - void SetFrameThemeVariant(PlatformThemeVariant themeVariant); + void SetFrameThemeVariant(PlatformThemeVariant? themeVariant); } } diff --git a/src/Avalonia.Controls/ThemeVariantScope.cs b/src/Avalonia.Controls/ThemeVariantScope.cs index f5ad4b2f94..c88dfa7dd2 100644 --- a/src/Avalonia.Controls/ThemeVariantScope.cs +++ b/src/Avalonia.Controls/ThemeVariantScope.cs @@ -15,6 +15,11 @@ namespace Avalonia.Controls public static readonly StyledProperty RequestedThemeVariantProperty = ThemeVariant.RequestedThemeVariantProperty.AddOwner(); + static ThemeVariantScope() + { + RequestedThemeVariantProperty.OverrideDefaultValue(ThemeVariant.Default); + } + /// /// Gets or sets the UI theme variant that is used by the control (and its child elements) for resource determination. /// The UI theme you specify with ThemeVariant can override the app-level ThemeVariant. diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index b4b76085d9..2bc9270dfa 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -233,11 +233,6 @@ namespace Avalonia.Controls impl.TransparencyLevelChanged = HandleTransparencyLevelChanged; CreatePlatformImplBinding(TransparencyLevelHintProperty, hint => PlatformImpl.SetTransparencyLevelHint(hint ?? Array.Empty())); - CreatePlatformImplBinding(ActualThemeVariantProperty, variant => - { - variant ??= ThemeVariant.Default; - PlatformImpl?.SetFrameThemeVariant((PlatformThemeVariant?)variant ?? PlatformThemeVariant.Light); - }); _keyboardNavigationHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this); @@ -252,6 +247,16 @@ namespace Avalonia.Controls SetValue(ActualThemeVariantProperty, _applicationThemeHost.ActualThemeVariant, BindingPriority.Template); _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; @@ -878,7 +883,7 @@ namespace Avalonia.Controls private void GlobalActualThemeVariantChanged(object? sender, EventArgs e) { - SetValue(ActualThemeVariantProperty, ((IThemeVariantHost)sender!).ActualThemeVariant, BindingPriority.Template); + SetCurrentValue(ActualThemeVariantProperty, ((IThemeVariantHost)sender!).ActualThemeVariant); } private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e) diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index e21f4dc843..65045553e0 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -194,7 +194,7 @@ namespace Avalonia.DesignerSupport.Remote public bool NeedsManagedDecorations => false; - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); public object TryGetFeature(Type featureType) => null; diff --git a/src/Avalonia.Native/TopLevelImpl.cs b/src/Avalonia.Native/TopLevelImpl.cs index c7d3305efd..c8fa6254fa 100644 --- a/src/Avalonia.Native/TopLevelImpl.cs +++ b/src/Avalonia.Native/TopLevelImpl.cs @@ -148,7 +148,7 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0); - public virtual void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public virtual void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { //noop } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 29f6547c9f..ba84d1ad0a 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -70,9 +70,9 @@ namespace Avalonia.Native Native?.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason); } - public override void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public override void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { - Native?.SetFrameThemeVariant((AvnPlatformThemeVariant)themeVariant); + Native?.SetFrameThemeVariant((AvnPlatformThemeVariant)(themeVariant ?? PlatformThemeVariant.Light)); } public override void Dispose() diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index eb2c8dea65..963d05e161 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1440,7 +1440,7 @@ namespace Avalonia.X11 public WindowTransparencyLevel TransparencyLevel => _transparencyHelper?.CurrentLevel ?? WindowTransparencyLevel.None; - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8); diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index 9c15d8d1a5..7785b447e1 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -141,7 +141,7 @@ namespace Avalonia.Browser public Action? LostFocus { get; set; } 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. } diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index 11d62d11c6..fc2feb7f74 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -429,7 +429,7 @@ namespace Avalonia.Headless } - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 2dc7b36b11..c027960cde 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -78,7 +78,7 @@ using Avalonia.Rendering.Composition; 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 object TryGetFeature(Type featureType) => null; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5e85b2f3e7..94a4faa908 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -894,9 +894,9 @@ namespace Avalonia.Win32 } } - public unsafe void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public unsafe void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { - _currentThemeVariant = themeVariant; + _currentThemeVariant = themeVariant ?? PlatformThemeVariant.Light; if (Win32Platform.WindowsVersion.Build >= 22000) { var pvUseBackdropBrush = themeVariant == PlatformThemeVariant.Dark ? 1 : 0; diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index ff7098251b..cc91d67836 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -238,7 +238,7 @@ namespace Avalonia.iOS public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { #if !TVOS // TODO adjust status bar depending on full screen mode. diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs index 4898604182..709b585dad 100644 --- a/tests/Avalonia.UnitTests/CompositorTestServices.cs +++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs @@ -211,7 +211,7 @@ public class CompositorTestServices : IDisposable public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } From 5f26382ffa08a60870b223c84501aecefae14c54 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 17 Jul 2024 16:56:25 +0000 Subject: [PATCH 02/16] update api diff --- api/Avalonia.nupkg.xml | 6 ++++++ src/Tizen/Avalonia.Tizen/TopLevelImpl.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 409cfa9e75..cb79f22059 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -1093,6 +1093,12 @@ baseline/netstandard2.0/Avalonia.Base.dll target/netstandard2.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetFrameThemeVariant(Avalonia.Platform.PlatformThemeVariant) + baseline/netstandard2.0/Avalonia.Controls.dll + target/netstandard2.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Screens.#ctor(Avalonia.Platform.IScreenImpl) diff --git a/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs b/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs index 862d0d7e78..d7b9728003 100644 --- a/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs +++ b/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs @@ -70,7 +70,7 @@ internal class TopLevelImpl : ITopLevelImpl // } - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { // } From 37d4fca2302ffa89f21e04648c7234d2fb32cdd7 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 17 Jul 2024 14:29:15 -0700 Subject: [PATCH 03/16] Fix theme variant not being applied when switching from Light/Dark to Default --- src/Avalonia.Controls/Application.cs | 11 ++++++++++- src/Windows/Avalonia.Win32/WindowImpl.cs | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 210378b933..fd068a3231 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -370,7 +370,16 @@ namespace Avalonia if (change.Property == RequestedThemeVariantProperty) { if (change.GetNewValue() is { } themeVariant) - SetValue(ActualThemeVariantProperty, themeVariant); + { + if (themeVariant == ThemeVariant.Default && PlatformSettings?.GetColorValues() is { } currentColors) + { + SetValue(ActualThemeVariantProperty, (ThemeVariant)currentColors.ThemeVariant); + } + else + { + SetValue(ActualThemeVariantProperty, themeVariant); + } + } } else if (change.Property == ActualThemeVariantProperty) { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 94a4faa908..fb750dd80e 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -896,10 +896,10 @@ namespace Avalonia.Win32 public unsafe void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { - _currentThemeVariant = themeVariant ?? PlatformThemeVariant.Light; + _currentThemeVariant = themeVariant ?? Win32Platform.Instance.PlatformSettings.GetColorValues().ThemeVariant; if (Win32Platform.WindowsVersion.Build >= 22000) { - var pvUseBackdropBrush = themeVariant == PlatformThemeVariant.Dark ? 1 : 0; + var pvUseBackdropBrush = _currentThemeVariant == PlatformThemeVariant.Dark ? 1 : 0; DwmSetWindowAttribute( _hwnd, (int)DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE, From 4c64635ba2bb27d0da61fe106e449970300632cf Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 17 Jul 2024 14:44:50 -0700 Subject: [PATCH 04/16] Use IPlatformSettings as a fallback in macOS SetFrameThemeVariant --- src/Avalonia.Native/WindowImplBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index ba84d1ad0a..40ad4d8497 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -72,7 +72,8 @@ namespace Avalonia.Native public override void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { - Native?.SetFrameThemeVariant((AvnPlatformThemeVariant)(themeVariant ?? PlatformThemeVariant.Light)); + themeVariant ??= AvaloniaLocator.Current.GetService()?.GetColorValues().ThemeVariant ?? PlatformThemeVariant.Light; + Native?.SetFrameThemeVariant((AvnPlatformThemeVariant)themeVariant); } public override void Dispose() From c3daac9f9ac31b8538cd2f563e580eddec207e89 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 2 Sep 2024 13:23:54 +0000 Subject: [PATCH 05/16] fix nullable check in android avaloniaview --- src/Android/Avalonia.Android/AvaloniaView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index 2073f81ac6..4c3772b4fc 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -141,7 +141,7 @@ namespace Avalonia.Android private void SendConfigurationChanged(Configuration? newConfig) { - _view?.InsetsManager?.SetDefaultSystemLightMode(!(newConfig?.UiMode.HasFlag(UiMode.NightYes) ?? false)); + _view.InsetsManager?.SetDefaultSystemLightMode(!(newConfig?.UiMode.HasFlag(UiMode.NightYes) ?? false)); if (Context is { } context && newConfig is { } config) { var settings = From 40500db6289c99ce2714d4817e4be1d930e0bb2e Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 2 Sep 2024 14:13:51 +0000 Subject: [PATCH 06/16] fix SetFrameThemeVariant api suppression --- api/Avalonia.nupkg.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index d97ba48daa..c2e5947c36 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -33,15 +33,15 @@ CP0002 - M:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetFrameThemeVariant(Avalonia.Platform.PlatformThemeVariant) + M:Avalonia.Diagnostics.StyleDiagnostics.get_AppliedStyles baseline/netstandard2.0/Avalonia.Base.dll target/netstandard2.0/Avalonia.Base.dll CP0002 - M:Avalonia.Diagnostics.StyleDiagnostics.get_AppliedStyles - baseline/netstandard2.0/Avalonia.Base.dll - target/netstandard2.0/Avalonia.Base.dll + M:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetFrameThemeVariant(Avalonia.Platform.PlatformThemeVariant) + baseline/netstandard2.0/Avalonia.Controls.dll + target/netstandard2.0/Avalonia.Controls.dll CP0002 From 738fca729b9cdfc884bf259d545d8a46cd079571 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 31 Jul 2025 15:19:36 +0000 Subject: [PATCH 07/16] fix accessibility helper crash --- src/Android/Avalonia.Android/AvaloniaView.cs | 21 +++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index 50dcb79bc3..a97d0425e6 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -20,8 +20,9 @@ namespace Avalonia.Android public class AvaloniaView : FrameLayout { private EmbeddableControlRoot? _root; + private ExploreByTouchHelper? _accessHelper; + private readonly ViewImpl _view; - private readonly ExploreByTouchHelper _accessHelper; private IDisposable? _timerSubscription; private object? _content; @@ -36,9 +37,6 @@ namespace Avalonia.Android this.SetBackgroundColor(global::Android.Graphics.Color.Transparent); _view.InternalView.SurfaceWindowCreated += InternalView_SurfaceWindowCreated; - - _accessHelper = new AvaloniaAccessHelper(this); - ViewCompat.SetAccessibilityDelegate(this, _accessHelper); } private void InternalView_SurfaceWindowCreated(object? sender, EventArgs e) @@ -80,6 +78,12 @@ namespace Avalonia.Android base.OnDetachedFromWindow(); OnVisibilityChanged(false); _surfaceCreated = false; + + if(_accessHelper is { } accessHelper) + { + ViewCompat.SetAccessibilityDelegate(this, null); + _accessHelper = null; + } } protected override void OnAttachedToWindow() @@ -90,6 +94,9 @@ namespace Avalonia.Android { _root.Content = _content; } + + _accessHelper = new AvaloniaAccessHelper(this); + ViewCompat.SetAccessibilityDelegate(this, _accessHelper); SendConfigurationChanged(Context?.Resources?.Configuration); base.OnAttachedToWindow(); @@ -98,18 +105,18 @@ namespace Avalonia.Android protected override void OnFocusChanged(bool gainFocus, FocusSearchDirection direction, global::Android.Graphics.Rect? previouslyFocusedRect) { base.OnFocusChanged(gainFocus, direction, previouslyFocusedRect); - _accessHelper.OnFocusChanged(gainFocus, (int)direction, previouslyFocusedRect); + _accessHelper?.OnFocusChanged(gainFocus, (int)direction, previouslyFocusedRect); } protected override bool DispatchHoverEvent(MotionEvent? e) { - return _accessHelper.DispatchHoverEvent(e!) || base.DispatchHoverEvent(e); + return _accessHelper?.DispatchHoverEvent(e!) == true || base.DispatchHoverEvent(e); } public override bool DispatchKeyEvent(KeyEvent? e) { if (!_view.View.DispatchKeyEvent(e)) - return _accessHelper.DispatchKeyEvent(e!) || base.DispatchKeyEvent(e); + return _accessHelper?.DispatchKeyEvent(e!) == true || base.DispatchKeyEvent(e); return true; } From ce22e0824c12b6a6b80ea3699631a6293e69d5a0 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 1 Aug 2025 07:08:48 +0000 Subject: [PATCH 08/16] fix reseting actual theme variant --- .../SafeAreaDemo/ViewModels/MainViewModel.cs | 31 +++++++++++++++++-- samples/SafeAreaDemo/Views/MainView.xaml | 6 ++++ .../Platform/SkiaPlatform/TopLevelImpl.cs | 12 +++++-- src/Avalonia.Base/Styling/ThemeVariant.cs | 2 +- src/Avalonia.Controls/Application.cs | 22 ++++++------- src/Avalonia.Controls/TopLevel.cs | 2 +- 6 files changed, 57 insertions(+), 18 deletions(-) diff --git a/samples/SafeAreaDemo/ViewModels/MainViewModel.cs b/samples/SafeAreaDemo/ViewModels/MainViewModel.cs index 4679948a6a..ef7fd35651 100644 --- a/samples/SafeAreaDemo/ViewModels/MainViewModel.cs +++ b/samples/SafeAreaDemo/ViewModels/MainViewModel.cs @@ -3,6 +3,7 @@ using Avalonia; using Avalonia.Animation.Easings; using Avalonia.Controls; using Avalonia.Controls.Platform; +using Avalonia.Styling; using MiniMvvm; namespace SafeAreaDemo.ViewModels @@ -33,7 +34,7 @@ namespace SafeAreaDemo.ViewModels public Rect InputPaneRect => _inputPane?.OccludedRect ?? default; public Rect CanvasSize { get; set; } - + public Thickness SafeAreaPadding { get @@ -105,7 +106,7 @@ namespace SafeAreaDemo.ViewModels set { _autoSafeAreaPadding = value; - + RaisePropertyChanged(); RaiseSafeAreaChanged(); } @@ -159,7 +160,7 @@ namespace SafeAreaDemo.ViewModels this.RaisePropertyChanged(nameof(ViewPadding)); this.RaisePropertyChanged(nameof(InputPaneMarkerMargin)); } - + private void RaiseKeyboardChanged() { this.RaisePropertyChanged(nameof(InputPaneState)); @@ -168,5 +169,29 @@ namespace SafeAreaDemo.ViewModels this.RaisePropertyChanged(nameof(InputPaneDuration)); 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; + } + } } } diff --git a/samples/SafeAreaDemo/Views/MainView.xaml b/samples/SafeAreaDemo/Views/MainView.xaml index a2b336abe0..6832bf3ed6 100644 --- a/samples/SafeAreaDemo/Views/MainView.xaml +++ b/samples/SafeAreaDemo/Views/MainView.xaml @@ -39,6 +39,7 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> @@ -47,6 +48,11 @@ Automatic Paddings Hide System Bars + + + + + diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 9b4588c4f0..337f20c160 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -9,6 +9,7 @@ using Android.Text; using Android.Views; using Android.Views.InputMethods; using AndroidX.AppCompat.App; +using AndroidX.Core.View; using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.Specific; using Avalonia.Android.Platform.Specific.Helpers; @@ -281,10 +282,17 @@ namespace Avalonia.Android.Platform.SkiaPlatform }; } - AppCompatDelegate.DefaultNightMode = themeVariant == null ? AppCompatDelegate.ModeNightFollowSystem : + // 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; - if (AppCompatDelegate.DefaultNightMode == AppCompatDelegate.ModeNightFollowSystem && _view.Context is { } context + AppCompatDelegate.DefaultNightMode = nightMode; + + if (nightMode == AppCompatDelegate.ModeNightFollowSystem && _view.Context is { } context && context.Resources?.Configuration is { } config) { var settings = diff --git a/src/Avalonia.Base/Styling/ThemeVariant.cs b/src/Avalonia.Base/Styling/ThemeVariant.cs index 519861cd34..2831baf489 100644 --- a/src/Avalonia.Base/Styling/ThemeVariant.cs +++ b/src/Avalonia.Base/Styling/ThemeVariant.cs @@ -100,7 +100,7 @@ public sealed record ThemeVariant { PlatformThemeVariant.Light => Light, PlatformThemeVariant.Dark => Dark, - _ => Default, + _ => throw new ArgumentOutOfRangeException(nameof(themeVariant), themeVariant, null), }; } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 3e5255c806..bbec08cd81 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -54,7 +54,7 @@ namespace Avalonia /// public static readonly StyledProperty ActualThemeVariantProperty = ThemeVariantScope.ActualThemeVariantProperty.AddOwner(); - + /// public static readonly StyledProperty RequestedThemeVariantProperty = ThemeVariantScope.RequestedThemeVariantProperty.AddOwner(); @@ -106,7 +106,7 @@ namespace Avalonia get => GetValue(RequestedThemeVariantProperty); set => SetValue(RequestedThemeVariantProperty, value); } - + /// [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.")] @@ -216,7 +216,7 @@ namespace Avalonia /// as specific top levels might have different settings set-up. /// public IPlatformSettings? PlatformSettings => this.TryGetFeature(); - + event Action>? IGlobalStyles.GlobalStylesAdded { add => _stylesAdded += value; @@ -233,7 +233,7 @@ namespace Avalonia /// Initializes the application by loading XAML etc. /// public virtual void Initialize() { } - + /// public bool TryGetResource(object key, ThemeVariant? theme, out object? value) { @@ -286,7 +286,7 @@ namespace Avalonia .Bind().ToConstant(this) .Bind().ToConstant(focusManager) .Bind().ToConstant(InputManager) - .Bind< IToolTipService>().ToConstant(new ToolTipService(InputManager)) + .Bind().ToConstant(new ToolTipService(InputManager)) .Bind().ToTransient() .Bind().ToConstant(DragDropDevice.Instance); @@ -304,10 +304,10 @@ namespace Avalonia public virtual void OnFrameworkInitializationCompleted() { } - + void IApplicationPlatformEvents.RaiseUrlsOpened(string[] urls) { - UrlsOpened?.Invoke(this, new UrlOpenedEventArgs (urls)); + UrlsOpened?.Invoke(this, new UrlOpenedEventArgs(urls)); } private string? _name; @@ -361,13 +361,13 @@ namespace Avalonia { if (change.GetNewValue() is { } themeVariant) { - if (themeVariant == ThemeVariant.Default && PlatformSettings?.GetColorValues() is { } currentColors) + if (themeVariant == ThemeVariant.Default) { - SetValue(ActualThemeVariantProperty, (ThemeVariant)currentColors.ThemeVariant); + ClearValue(ActualThemeVariantProperty); } else { - SetValue(ActualThemeVariantProperty, themeVariant); + SetValue(ActualThemeVariantProperty, themeVariant); } } } @@ -380,7 +380,7 @@ namespace Avalonia private void OnColorValuesChanged(object? sender, PlatformColorValues e) { if ((RequestedThemeVariant ?? ThemeVariant.Default) == ThemeVariant.Default) - SetCurrentValue(ActualThemeVariantProperty, (ThemeVariant)e.ThemeVariant); + SetValue(ActualThemeVariantProperty, (ThemeVariant)e.ThemeVariant); } } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index e55af7ef78..0e5087e3f3 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -929,7 +929,7 @@ namespace Avalonia.Controls private void GlobalActualThemeVariantChanged(object? sender, EventArgs e) { - SetCurrentValue(ActualThemeVariantProperty, ((IThemeVariantHost)sender!).ActualThemeVariant); + SetValue(ActualThemeVariantProperty, ((IThemeVariantHost)sender!).ActualThemeVariant, BindingPriority.Template); } private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e) From ff2f02ee9e48be36cfb3b1abb1e0dd9cb9d331c8 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 19 Jun 2026 06:54:18 +0000 Subject: [PATCH 09/16] rebased --- src/Android/Avalonia.Android/AvaloniaView.cs | 18 ++++++++---------- src/Avalonia.Wayland/WindowImplBase.cs | 2 +- .../Avalonia.Win32/SwapChainTopLevelImpl.cs | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index 819dcf5c85..cec520acb7 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -40,7 +40,7 @@ namespace Avalonia.Android _root.Prepare(); SetBackgroundColor(global::Android.Graphics.Color.Transparent); - OnConfigurationChanged(); + OnConfigurationChanged(null); _view.InternalView!.SurfaceWindowCreated += InternalView_SurfaceWindowCreated; _view.InternalView.SurfaceWindowDestroyed += InternalView_SurfaceWindowDestroyed; @@ -76,13 +76,9 @@ namespace Avalonia.Android get { return _root?.Content; } set { - _content = null; + _content = value; if (_root != null) _root.Content = value; - else - { - _content = value; - } } } @@ -90,6 +86,7 @@ namespace Avalonia.Android { _root?.Dispose(); _root = null; + _content = null; } protected override void OnDetachedFromWindow() @@ -98,7 +95,7 @@ namespace Avalonia.Android OnVisibilityChanged(false); _surfaceCreated = false; - if(_accessHelper is { } accessHelper) + if (_accessHelper is { } accessHelper) { ViewCompat.SetAccessibilityDelegate(this, null); _accessHelper = null; @@ -107,9 +104,10 @@ namespace Avalonia.Android protected override void OnAttachedToWindow() { + _root?.Content = null; _root = new EmbeddableControlRoot(_view); _root.Prepare(); - if(_content != null) + if (_content != null) { _root.Content = _content; } @@ -160,7 +158,7 @@ namespace Avalonia.Android _timerSubscription = null; } } - + protected override void OnConfigurationChanged(Configuration? newConfig) { base.OnConfigurationChanged(newConfig); @@ -188,7 +186,7 @@ namespace Avalonia.Android private void ViewImpl_FocusChange(object? sender, FocusChangeEventArgs e) { - if(!e.HasFocus) + if (!e.HasFocus) LostFocus?.Invoke(); } } diff --git a/src/Avalonia.Wayland/WindowImplBase.cs b/src/Avalonia.Wayland/WindowImplBase.cs index 3a95933d9c..f8462417ea 100644 --- a/src/Avalonia.Wayland/WindowImplBase.cs +++ b/src/Avalonia.Wayland/WindowImplBase.cs @@ -76,7 +76,7 @@ internal abstract partial class WindowBaseImpl : IWindowBaseImpl public WindowTransparencyLevel TransparencyLevel { get; } = WindowTransparencyLevel.Transparent; public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = default; public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { } - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } public Action? Closed { get; set; } public Action? LostFocus { get; set; } diff --git a/src/Windows/Avalonia.Win32/SwapChainTopLevelImpl.cs b/src/Windows/Avalonia.Win32/SwapChainTopLevelImpl.cs index 15ea304709..f59d39a75d 100644 --- a/src/Windows/Avalonia.Win32/SwapChainTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32/SwapChainTopLevelImpl.cs @@ -99,7 +99,7 @@ internal class SwapChainTopLevelImpl : ITopLevelImpl public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { } - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } /// /// Optional IME implementation provided by the host. From e5d20f14e4d8640d9fd3ea6ad93917083c775783 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 19 Jun 2026 07:15:53 +0000 Subject: [PATCH 10/16] update apidiff --- api/Avalonia.nupkg.xml | 38 ++++++++++++++++++- .../Avalonia.Android/AvaloniaView.Input.cs | 2 +- .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 +- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 0303d0702b..01861302b0 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -1,4 +1,4 @@ - + @@ -43,6 +43,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetFrameThemeVariant(Avalonia.Platform.PlatformThemeVariant) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Platform.ITopLevelImpl.SetFrameThemeVariant(Avalonia.Platform.PlatformThemeVariant) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.OpenGL.Egl.EglPlatformSurfaceRenderTargetBase.BeginDraw(Avalonia.OpenGL.Egl.EglSurface,Avalonia.PixelSize,System.Double,System.Action,System.Boolean) @@ -91,10 +103,34 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetFrameThemeVariant(Avalonia.Platform.PlatformThemeVariant) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Platform.ITopLevelImpl.SetFrameThemeVariant(Avalonia.Platform.PlatformThemeVariant) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.OpenGL.Egl.EglPlatformSurfaceRenderTargetBase.BeginDraw(Avalonia.OpenGL.Egl.EglSurface,Avalonia.PixelSize,System.Double,System.Action,System.Boolean) baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + + CP0006 + M:Avalonia.Platform.ITopLevelImpl.SetFrameThemeVariant(System.Nullable{Avalonia.Platform.PlatformThemeVariant}) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0006 + M:Avalonia.Platform.ITopLevelImpl.SetFrameThemeVariant(System.Nullable{Avalonia.Platform.PlatformThemeVariant}) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + diff --git a/src/Android/Avalonia.Android/AvaloniaView.Input.cs b/src/Android/Avalonia.Android/AvaloniaView.Input.cs index d72966a82a..413a21ba9c 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.Input.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.Input.cs @@ -29,7 +29,7 @@ namespace Avalonia.Android { var res = _view.PointerHelper.DispatchMotionEvent(e, out var callBase); if (res == false) - callBase = !_accessHelper.DispatchHoverEvent(e!) && callBase; + callBase = (_accessHelper?.DispatchHoverEvent(e!) == true) && callBase; var baseResult = callBase && base.DispatchHoverEvent(e); diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 6e04253eb7..a732e90689 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -255,7 +255,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform AppCompatDelegate.DefaultNightMode = nightMode; - if (nightMode == AppCompatDelegate.ModeNightFollowSystem && _view.Context is { } context + if (nightMode == AppCompatDelegate.ModeNightFollowSystem && _view?.Context is { } context && context.Resources?.Configuration is { } config) { var settings = From f985f02308a628c9344a2f9e52061155f410f02d Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 19 Jun 2026 07:26:12 +0000 Subject: [PATCH 11/16] update apidiff --- api/Avalonia.nupkg.xml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index bc51ba9976..4b2aa63ef1 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -1,4 +1,4 @@ - + @@ -127,10 +127,22 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0006 + M:Avalonia.Platform.ITopLevelImpl.SetFrameThemeVariant(System.Nullable{Avalonia.Platform.PlatformThemeVariant}) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0006 M:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl.SetWindowCornerPreference(Avalonia.Controls.Win32Properties.WindowCornerPreference) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0006 + M:Avalonia.Platform.ITopLevelImpl.SetFrameThemeVariant(System.Nullable{Avalonia.Platform.PlatformThemeVariant}) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + From 22067ae92b2ff60b400e016ba841abd61bc187fa Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 23 Jun 2026 12:03:05 +0000 Subject: [PATCH 12/16] fix default theme selection on desktop --- src/Avalonia.X11/X11Window.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 7926f60ffe..b8858e8742 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1649,7 +1649,13 @@ namespace Avalonia.X11 public WindowTransparencyLevel TransparencyLevel => _transparencyHelper?.CurrentLevel ?? WindowTransparencyLevel.None; - public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } + public async void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) + { + if (themeVariant == null && AvaloniaLocator.Current.GetService() is DBusPlatformSettings platformSettings) + { + platformSettings.OnRequestDefaultThemeVariant(); + } + } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8); From c43aabff9123aaa8fcf923ff442463a4ef697c62 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 23 Jun 2026 12:13:53 +0000 Subject: [PATCH 13/16] add missing changes --- src/Avalonia.FreeDesktop/DBusPlatformSettings.cs | 5 +++++ src/Avalonia.Native/NativePlatformSettings.cs | 15 +++------------ .../Avalonia.Win32/Win32PlatformSettings.cs | 16 ++++------------ 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs index 882219f9a1..1e505d77fb 100644 --- a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs +++ b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs @@ -37,6 +37,11 @@ namespace Avalonia.FreeDesktop Dispatcher.UIThread.Post(() => OnColorValuesChanged(_lastColorValues)); } + internal async void OnRequestDefaultThemeVariant() + { + await TryGetInitialValuesAsync(); + } + private async Task TryGetThemeVariantAsync() { try diff --git a/src/Avalonia.Native/NativePlatformSettings.cs b/src/Avalonia.Native/NativePlatformSettings.cs index 5e3ee0cfc2..b95d9a9439 100644 --- a/src/Avalonia.Native/NativePlatformSettings.cs +++ b/src/Avalonia.Native/NativePlatformSettings.cs @@ -8,7 +8,6 @@ namespace Avalonia.Native; internal class NativePlatformSettings : DefaultPlatformSettings { private readonly IAvnPlatformSettings _platformSettings; - private PlatformColorValues? _lastColorValues; public NativePlatformSettings(IAvnPlatformSettings platformSettings) { @@ -30,7 +29,7 @@ internal class NativePlatformSettings : DefaultPlatformSettings if (color > 0) { - _lastColorValues = new PlatformColorValues + return new PlatformColorValues { ThemeVariant = theme, ContrastPreference = contrast, @@ -39,25 +38,17 @@ internal class NativePlatformSettings : DefaultPlatformSettings } else { - _lastColorValues = new PlatformColorValues + return new PlatformColorValues { ThemeVariant = theme, ContrastPreference = contrast }; } - - return _lastColorValues; } public void OnColorValuesChanged() { - var oldColorValues = _lastColorValues; - var colorValues = GetColorValues(); - - if (oldColorValues != colorValues) - { - OnColorValuesChanged(colorValues); - } + OnColorValuesChanged(GetColorValues()); } private class ColorsChangeCallback : NativeCallbackBase, IAvnActionCallback diff --git a/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs b/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs index c4b016c1b9..efcdb32458 100644 --- a/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs +++ b/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs @@ -10,9 +10,7 @@ internal class Win32PlatformSettings : DefaultPlatformSettings { private static readonly Lazy s_uiSettingsSupported = new(() => WinRTApiInformation.IsTypePresent("Windows.UI.ViewManagement.UISettings") - && WinRTApiInformation.IsTypePresent("Windows.UI.ViewManagement.AccessibilitySettings")); - - private PlatformColorValues? _lastColorValues; + && WinRTApiInformation.IsTypePresent("Windows.UI.ViewManagement.AccessibilitySettings")); public override Size GetTapSize(PointerType type) { @@ -54,7 +52,7 @@ internal class Win32PlatformSettings : DefaultPlatformSettings // - 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 + return new PlatformColorValues { ThemeVariant = highContrastScheme.Value?.Contains("White") == true ? PlatformThemeVariant.Light : @@ -67,7 +65,7 @@ internal class Win32PlatformSettings : DefaultPlatformSettings else { 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) ? PlatformThemeVariant.Dark : @@ -80,12 +78,6 @@ internal class Win32PlatformSettings : DefaultPlatformSettings internal void OnColorValuesChanged() { - var oldColorValues = _lastColorValues; - var colorValues = GetColorValues(); - - if (oldColorValues != colorValues) - { - OnColorValuesChanged(colorValues); - } + OnColorValuesChanged(GetColorValues()); } } From 3df12faff4ab3fd51cb023d42deb2a6fe0c357d6 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 23 Jun 2026 12:26:32 +0000 Subject: [PATCH 14/16] fix wayland default theme detection --- src/Avalonia.Wayland/WindowImplBase.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Wayland/WindowImplBase.cs b/src/Avalonia.Wayland/WindowImplBase.cs index f8462417ea..8fe854feb5 100644 --- a/src/Avalonia.Wayland/WindowImplBase.cs +++ b/src/Avalonia.Wayland/WindowImplBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Platform; +using Avalonia.FreeDesktop; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Input.Raw; @@ -76,7 +77,14 @@ internal abstract partial class WindowBaseImpl : IWindowBaseImpl public WindowTransparencyLevel TransparencyLevel { get; } = WindowTransparencyLevel.Transparent; public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = default; public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { } - public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } + + public async void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) + { + if (themeVariant == null && AvaloniaLocator.Current.GetService() is DBusPlatformSettings platformSettings) + { + platformSettings.OnRequestDefaultThemeVariant(); + } + } public Action? Closed { get; set; } public Action? LostFocus { get; set; } From 7fb11c59bfa9c25d37dd370ed1b6f0754dcd0493 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 23 Jun 2026 12:55:19 +0000 Subject: [PATCH 15/16] fix browser theme detection --- src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs | 5 +++++ src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs b/src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs index 2a3cef4334..5d9f18d993 100644 --- a/src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs +++ b/src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs @@ -37,6 +37,11 @@ internal class BrowserPlatformSettings : DefaultPlatformSettings _isHighContrast = isHighContrast; OnColorValuesChanged(GetColorValues()); } + + public void OnValuesChanged() + { + OnColorValuesChanged(GetColorValues()); + } private void EnsureBackend() { diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index 97c4192d29..67773a6d5c 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -147,6 +147,10 @@ namespace Avalonia.Browser 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. + if (themeVariant == null && AvaloniaLocator.Current.GetService() is BrowserPlatformSettings platformSettings) + { + platformSettings.OnValuesChanged(); + } } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } From 70da60b23b7f7d2933d400a1b5fda256494dfb28 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 23 Jun 2026 13:14:29 +0000 Subject: [PATCH 16/16] removed redundant overrides --- src/Avalonia.Controls/Application.cs | 5 ----- src/Avalonia.Controls/ThemeVariantScope.cs | 5 ----- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 ++ 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index b9deb56871..0b1e20c395 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -64,11 +64,6 @@ namespace Avalonia /// public event EventHandler? ActualThemeVariantChanged; - static Application() - { - RequestedThemeVariantProperty.OverrideDefaultValue(ThemeVariant.Default); - } - /// /// Creates an instance of the class. /// diff --git a/src/Avalonia.Controls/ThemeVariantScope.cs b/src/Avalonia.Controls/ThemeVariantScope.cs index c88dfa7dd2..f5ad4b2f94 100644 --- a/src/Avalonia.Controls/ThemeVariantScope.cs +++ b/src/Avalonia.Controls/ThemeVariantScope.cs @@ -15,11 +15,6 @@ namespace Avalonia.Controls public static readonly StyledProperty RequestedThemeVariantProperty = ThemeVariant.RequestedThemeVariantProperty.AddOwner(); - static ThemeVariantScope() - { - RequestedThemeVariantProperty.OverrideDefaultValue(ThemeVariant.Default); - } - /// /// Gets or sets the UI theme variant that is used by the control (and its child elements) for resource determination. /// The UI theme you specify with ThemeVariant can override the app-level ThemeVariant. diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 31be8012cf..e07454a8ee 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -964,6 +964,8 @@ namespace Avalonia.Win32 SetTransparencyMica(); } } + + (Win32Platform.Instance.PlatformSettings as Win32PlatformSettings)?.OnColorValuesChanged(); } protected virtual IntPtr CreateWindowOverride(ushort atom)