diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index d66ef8f68d..4b2aa63ef1 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -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,6 +103,18 @@ 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) @@ -103,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 + 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/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 c03ffc1f39..c62410aa83 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}"> @@ -47,7 +47,12 @@ Use Safe Area Automatic Paddings Hide System Bars - + + + + + + diff --git a/src/Android/Avalonia.Android/AvaloniaView.Input.cs b/src/Android/Avalonia.Android/AvaloniaView.Input.cs index 4862daaf0b..413a21ba9c 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.Input.cs +++ b/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) { base.OnFocusChanged(gainFocus, direction, previouslyFocusedRect); - _accessHelper.OnFocusChanged(gainFocus, (int)direction, previouslyFocusedRect); + _accessHelper?.OnFocusChanged(gainFocus, (int)direction, previouslyFocusedRect); } protected override bool DispatchHoverEvent(MotionEvent? e) { 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); @@ -63,7 +63,7 @@ namespace Avalonia.Android { var res = _view.KeyboardHelper.DispatchKeyEvent(e, out var callBase); if (res == false) - callBase = !_accessHelper.DispatchKeyEvent(e!) && callBase; + callBase = _accessHelper?.DispatchKeyEvent(e!) == false && callBase; var baseResult = callBase && base.DispatchKeyEvent(e); diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index f1c3b95aa1..cec520acb7 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -19,11 +19,13 @@ namespace Avalonia.Android { public partial class AvaloniaView : FrameLayout { - private EmbeddableControlRoot _root; + private EmbeddableControlRoot? _root; + private ExploreByTouchHelper? _accessHelper; + private readonly ViewImpl _view; - private readonly ExploreByTouchHelper _accessHelper; private IDisposable? _timerSubscription; + private object? _content; private bool _surfaceCreated; public AvaloniaView(Context context) : base(context) @@ -32,11 +34,13 @@ namespace Avalonia.Android AddView(_view.View); + this.SetBackgroundColor(global::Android.Graphics.Color.Transparent); + _root = new EmbeddableControlRoot(_view); _root.Prepare(); SetBackgroundColor(global::Android.Graphics.Color.Transparent); - OnConfigurationChanged(); + OnConfigurationChanged(null); _view.InternalView!.SurfaceWindowCreated += InternalView_SurfaceWindowCreated; _view.InternalView.SurfaceWindowDestroyed += InternalView_SurfaceWindowDestroyed; @@ -69,16 +73,50 @@ namespace Avalonia.Android public object? Content { - get { return _root.Content; } - set { _root.Content = value; } + get { return _root?.Content; } + set + { + _content = value; + if (_root != null) + _root.Content = value; + } } internal new void Dispose() { + _root?.Dispose(); + _root = null; + _content = null; + } + + protected override void OnDetachedFromWindow() + { + base.OnDetachedFromWindow(); OnVisibilityChanged(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")] @@ -98,6 +136,7 @@ namespace Avalonia.Android { if (_root == null || !_surfaceCreated) return; + if (isVisible && _timerSubscription == null) { if (AndroidPlatform.Timer is { } timer) @@ -119,20 +158,21 @@ namespace Avalonia.Android _timerSubscription = null; } } - + 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); ((AndroidScreens)_view.TryGetFeature()!).OnChanged(); } } @@ -146,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/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs index a8ab825808..2cf2896c7b 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs +++ b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs @@ -187,6 +187,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 1e77b7d880..5da78439e2 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs +++ b/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs @@ -49,14 +49,14 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings 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; } - var systemTheme = (context.Resources.Configuration.UiMode & UiMode.NightMask) switch + PlatformThemeVariant systemTheme = (configuration.UiMode & UiMode.NightMask) switch { UiMode.NightYes => PlatformThemeVariant.Dark, UiMode.NightNo => PlatformThemeVariant.Light, @@ -98,7 +98,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 a7bf5f626d..a732e90689 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -8,6 +8,7 @@ using Android.OS; using Android.Runtime; using Android.Views; using AndroidX.AppCompat.App; +using AndroidX.Core.View; using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.Specific.Helpers; 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) { @@ -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() as AndroidPlatformSettings; + settings?.OnViewConfigurationChanged(context, config); + } } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new(1, 1, 1); @@ -254,8 +271,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform PixelSize EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Size => _view?.Size ?? default; double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => _view?.Scaling ?? default; + internal AndroidInsetsManager? InsetsManager => _insetsManager; internal AndroidKeyboardEventsHelper KeyboardHelper => _keyboardHelper; - internal AndroidMotionEventsHelper PointerHelper => _pointerHelper; public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) diff --git a/src/Avalonia.Base/Styling/ThemeVariant.cs b/src/Avalonia.Base/Styling/ThemeVariant.cs index 5414d340bf..b469ea5b28 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) + _ => throw new ArgumentOutOfRangeException(nameof(themeVariant), themeVariant, null), }; } - 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 90e272c7bd..0b1e20c395 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -53,7 +53,7 @@ namespace Avalonia /// public static readonly StyledProperty ActualThemeVariantProperty = ThemeVariantScope.ActualThemeVariantProperty.AddOwner(); - + /// public static readonly StyledProperty RequestedThemeVariantProperty = ThemeVariantScope.RequestedThemeVariantProperty.AddOwner(); @@ -91,7 +91,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.")] @@ -201,7 +201,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; @@ -218,7 +218,7 @@ namespace Avalonia /// Initializes the application by loading XAML etc. /// public virtual void Initialize() { } - + /// public bool TryGetResource(object key, ThemeVariant? theme, out object? value) { @@ -262,7 +262,7 @@ namespace Avalonia .Bind().ToConstant(this) .Bind().ToConstant(this) .Bind().ToConstant(InputManager) - .Bind< IToolTipService>().ToConstant(new ToolTipService(InputManager)) + .Bind().ToConstant(new ToolTipService(InputManager)) .Bind().ToTransient() .Bind().ToConstant(DragDropDevice.Instance); @@ -330,20 +330,28 @@ namespace Avalonia if (change.Property == RequestedThemeVariantProperty) { - if (change.GetNewValue() is {} themeVariant && themeVariant != ThemeVariant.Default) - SetValue(ActualThemeVariantProperty, themeVariant); - else - ClearValue(ActualThemeVariantProperty); + if (change.GetNewValue() is { } themeVariant) + { + if (themeVariant == ThemeVariant.Default) + { + ClearValue(ActualThemeVariantProperty); + } + else + { + SetValue(ActualThemeVariantProperty, themeVariant); + } + } } 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) + SetValue(ActualThemeVariantProperty, (ThemeVariant)e.ThemeVariant); } } } diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 27f927a3c4..32df6bafd9 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -63,7 +63,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 bab5e4ffe9..548d4989b6 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -137,6 +137,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/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index c30047fe7a..eeb6ae3488 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -239,11 +239,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); @@ -258,6 +253,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; diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 8149e5ad71..b53645c67b 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -196,7 +196,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); 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/Avalonia.Native/TopLevelImpl.cs b/src/Avalonia.Native/TopLevelImpl.cs index 04e1318da8..4bd6e94f7d 100644 --- a/src/Avalonia.Native/TopLevelImpl.cs +++ b/src/Avalonia.Native/TopLevelImpl.cs @@ -158,7 +158,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 852317a793..dda1162f7a 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -68,8 +68,9 @@ namespace Avalonia.Native Native?.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason); } - public override void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public override void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { + themeVariant ??= AvaloniaLocator.Current.GetService()?.GetColorValues().ThemeVariant ?? PlatformThemeVariant.Light; Native?.SetFrameThemeVariant((AvnPlatformThemeVariant)themeVariant); } diff --git a/src/Avalonia.Wayland/WindowImplBase.cs b/src/Avalonia.Wayland/WindowImplBase.cs index 3a95933d9c..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; } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index ef644e2c72..12c3453d75 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1662,7 +1662,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); 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 6b712391e4..67773a6d5c 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -144,9 +144,13 @@ 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. + if (themeVariant == null && AvaloniaLocator.Current.GetService() is BrowserPlatformSettings platformSettings) + { + platformSettings.OnValuesChanged(); + } } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index f2a7bf3575..0036288ac0 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -438,7 +438,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 22965c05fe..0ef7d08677 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/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. 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()); } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index a39fa9c1cb..e07454a8ee 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/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) { - var pvUseBackdropBrush = themeVariant == PlatformThemeVariant.Dark ? 1 : 0; + var pvUseBackdropBrush = _currentThemeVariant == PlatformThemeVariant.Dark ? 1 : 0; DwmSetWindowAttribute( _hwnd, (int)DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE, @@ -964,6 +964,8 @@ namespace Avalonia.Win32 SetTransparencyMica(); } } + + (Win32Platform.Instance.PlatformSettings as Win32PlatformSettings)?.OnColorValuesChanged(); } protected virtual IntPtr CreateWindowOverride(ushort atom) diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 29f848925d..98c4d1ff2f 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -254,7 +254,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 14320ee4da..1ba03f5974 100644 --- a/tests/Avalonia.UnitTests/CompositorTestServices.cs +++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs @@ -205,7 +205,7 @@ public class CompositorTestServices : IDisposable public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { }