diff --git a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs index 4b0bab0c92..8aed1545a5 100644 --- a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs +++ b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs @@ -138,18 +138,18 @@ namespace Avalonia.Controls protected override void Initialize() { _target.ResourcesChanged += ResourcesChanged; - if (_target is StyledElement themeStyleable) + if (_target is IThemeVariantHost themeVariantHost) { - themeStyleable.PropertyChanged += PropertyChanged; + themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged; } } protected override void Deinitialize() { _target.ResourcesChanged -= ResourcesChanged; - if (_target is StyledElement themeStyleable) + if (_target is IThemeVariantHost themeVariantHost) { - themeStyleable.PropertyChanged -= PropertyChanged; + themeVariantHost.ActualThemeVariantChanged -= ActualThemeVariantChanged; } } @@ -163,18 +163,15 @@ namespace Avalonia.Controls PublishNext(GetValue()); } - private void PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + private void ActualThemeVariantChanged(object? sender, EventArgs e) { - if (e.Property == StyledElement.ActualThemeVariantProperty) - { - PublishNext(GetValue()); - } + PublishNext(GetValue()); } private object? GetValue() { - if (_target is not StyledElement themeStyleable - || !_target.TryFindResource(_key, themeStyleable.ActualThemeVariant, out var value)) + if (_target is not IThemeVariantHost themeVariantHost + || !_target.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value)) { value = _target.FindResource(_key) ?? AvaloniaProperty.UnsetValue; } @@ -236,9 +233,9 @@ namespace Avalonia.Controls { _owner.ResourcesChanged -= ResourcesChanged; } - if (_owner is StyledElement styleable) + if (_owner is IThemeVariantHost themeVariantHost) { - styleable.PropertyChanged += PropertyChanged; + themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged; } _owner = _target.Owner; @@ -247,20 +244,18 @@ namespace Avalonia.Controls { _owner.ResourcesChanged += ResourcesChanged; } - if (_owner is StyledElement styleable2) + if (_owner is IThemeVariantHost themeVariantHost2) { - styleable2.PropertyChanged += PropertyChanged; + themeVariantHost2.ActualThemeVariantChanged -= ActualThemeVariantChanged; } + PublishNext(); } - private void PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + private void ActualThemeVariantChanged(object? sender, EventArgs e) { - if (e.Property == StyledElement.ActualThemeVariantProperty) - { - PublishNext(); - } + PublishNext(); } private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e) @@ -270,8 +265,8 @@ namespace Avalonia.Controls private object? GetValue() { - if (!(_target.Owner is StyledElement themeStyleable) - || !_target.Owner.TryFindResource(_key, themeStyleable.ActualThemeVariant, out var value)) + if (!(_target.Owner is IThemeVariantHost themeVariantHost) + || !_target.Owner.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value)) { value = _target.Owner?.FindResource(_key) ?? AvaloniaProperty.UnsetValue; } diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 5bf022cd51..17d0699336 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -71,23 +71,6 @@ namespace Avalonia public static readonly StyledProperty ThemeProperty = AvaloniaProperty.Register(nameof(Theme)); - /// - /// Defines the property. - /// - public static readonly StyledProperty ActualThemeVariantProperty = - AvaloniaProperty.Register( - nameof(ThemeVariant), - inherits: true, - defaultValue: ThemeVariant.Light); - - /// - /// Defines the RequestedThemeVariant property. - /// - public static readonly StyledProperty RequestedThemeVariantProperty = - AvaloniaProperty.Register( - nameof(ThemeVariant), - defaultValue: ThemeVariant.Default); - private static readonly ControlTheme s_invalidTheme = new ControlTheme(); private int _initCount; private string? _name; @@ -274,15 +257,6 @@ namespace Avalonia set => SetValue(ThemeProperty, value); } - /// - /// Gets the UI theme that is currently used by the element, which might be different than the . - /// - /// - /// If current control is contained in the ThemeVariantScope, TopLevel or Application with non-default RequestedThemeVariant, that value will be returned. - /// Otherwise, current OS theme variant is returned. - /// - public ThemeVariant ActualThemeVariant => GetValue(ActualThemeVariantProperty); - /// /// Gets the styled element's logical children. /// @@ -647,13 +621,6 @@ namespace Avalonia if (change.Property == ThemeProperty) OnControlThemeChanged(); - else if (change.Property == RequestedThemeVariantProperty) - { - if (change.GetNewValue() is {} themeVariant && themeVariant != ThemeVariant.Default) - SetValue(ActualThemeVariantProperty, themeVariant); - else - ClearValue(ActualThemeVariantProperty); - } } private protected virtual void OnControlThemeChanged() diff --git a/src/Avalonia.Base/Styling/IGlobalThemeVariantProvider.cs b/src/Avalonia.Base/Styling/IGlobalThemeVariantProvider.cs deleted file mode 100644 index 2467d99b3b..0000000000 --- a/src/Avalonia.Base/Styling/IGlobalThemeVariantProvider.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Avalonia.Controls; -using Avalonia.Metadata; - -namespace Avalonia.Styling; - -/// -/// Interface for an application host element with a root theme variant. -/// -[Unstable] -public interface IGlobalThemeVariantProvider : IResourceHost -{ - /// - /// Gets the UI theme variant that is used by the control (and its child elements) for resource determination. - /// - ThemeVariant ActualThemeVariant { get; } - - /// - /// Raised when the theme variant is changed on the element or an ancestor of the element. - /// - event EventHandler? ActualThemeVariantChanged; -} diff --git a/src/Avalonia.Base/Styling/IThemeVariantHost.cs b/src/Avalonia.Base/Styling/IThemeVariantHost.cs new file mode 100644 index 0000000000..01583148a8 --- /dev/null +++ b/src/Avalonia.Base/Styling/IThemeVariantHost.cs @@ -0,0 +1,26 @@ +using System; +using Avalonia.Controls; +using Avalonia.Metadata; + +namespace Avalonia.Styling; + +/// +/// Interface for the host element with a theme variant. +/// +[Unstable] +public interface IThemeVariantHost : IResourceHost +{ + /// + /// Gets the UI theme that is currently used by the element, which might be different than the RequestedThemeVariantProperty. + /// + /// + /// If current control is contained in the ThemeVariantScope, TopLevel or Application with non-default RequestedThemeVariant, that value will be returned. + /// Otherwise, current OS theme variant is returned. + /// + ThemeVariant ActualThemeVariant { get; } + + /// + /// Raised when the theme variant is changed on the element or an ancestor of the element. + /// + event EventHandler? ActualThemeVariantChanged; +} diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 6d3ba3cf8a..8456a9a3a9 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -29,7 +29,7 @@ namespace Avalonia /// method. /// - Tracks the lifetime of the application. /// - public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IGlobalThemeVariantProvider, IApplicationPlatformEvents + public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IThemeVariantHost, IApplicationPlatformEvents { /// /// The application-global data templates. @@ -50,13 +50,13 @@ namespace Avalonia public static readonly StyledProperty DataContextProperty = StyledElement.DataContextProperty.AddOwner(); - /// + /// public static readonly StyledProperty ActualThemeVariantProperty = - StyledElement.ActualThemeVariantProperty.AddOwner(); + ThemeVariantScope.ActualThemeVariantProperty.AddOwner(); - /// + /// public static readonly StyledProperty RequestedThemeVariantProperty = - StyledElement.RequestedThemeVariantProperty.AddOwner(); + ThemeVariantScope.RequestedThemeVariantProperty.AddOwner(); /// public event EventHandler? ResourcesChanged; @@ -95,11 +95,8 @@ namespace Avalonia set => SetValue(RequestedThemeVariantProperty, value); } - /// - public ThemeVariant ActualThemeVariant - { - get => GetValue(ActualThemeVariantProperty); - } + /// + public ThemeVariant ActualThemeVariant => GetValue(ActualThemeVariantProperty); /// /// Gets the current instance of the class. @@ -256,7 +253,7 @@ namespace Avalonia .Bind().ToTransient() .Bind().ToConstant(this) .Bind().ToConstant(this) - .Bind().ToConstant(this) + .Bind().ToConstant(this) .Bind().ToConstant(FocusManager) .Bind().ToConstant(InputManager) .Bind().ToTransient() diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index ed24c3c7c2..bfef190dfd 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -26,7 +26,7 @@ namespace Avalonia.Controls /// - A property to allow user-defined data to be attached to the control. /// - and other context menu related members. /// - public class Control : InputElement, IDataTemplateHost, INamed, IVisualBrushInitialize, ISetterValue + public class Control : InputElement, IDataTemplateHost, INamed, IVisualBrushInitialize, ISetterValue, IThemeVariantHost { /// /// Defines the property. @@ -163,6 +163,10 @@ namespace Avalonia.Controls get => GetValue(TagProperty); set => SetValue(TagProperty, value); } + + public ThemeVariant ActualThemeVariant => GetValue(ThemeVariantScope.ActualThemeVariantProperty); + + public event EventHandler? ActualThemeVariantChanged; /// /// Occurs when the user has completed a context input gesture, such as a right-click. @@ -530,6 +534,17 @@ namespace Avalonia.Controls RaiseEvent(sizeChangedEventArgs); } } + else if (change.Property == ThemeVariantScope.RequestedThemeVariantProperty) + { + if (change.GetNewValue() is {} themeVariant && themeVariant != ThemeVariant.Default) + SetValue(ThemeVariantScope.ActualThemeVariantProperty, themeVariant); + else + ClearValue(ThemeVariantScope.ActualThemeVariantProperty); + } + else if (change.Property == ThemeVariantScope.ActualThemeVariantProperty) + { + ActualThemeVariantChanged?.Invoke(this, EventArgs.Empty); + } } } } diff --git a/src/Avalonia.Controls/ThemeVariantScope.cs b/src/Avalonia.Controls/ThemeVariantScope.cs index b9724251c7..2aac1d3e2f 100644 --- a/src/Avalonia.Controls/ThemeVariantScope.cs +++ b/src/Avalonia.Controls/ThemeVariantScope.cs @@ -7,6 +7,23 @@ namespace Avalonia.Controls /// public class ThemeVariantScope : Decorator { + /// + /// Defines the property. + /// + public static readonly StyledProperty ActualThemeVariantProperty = + AvaloniaProperty.Register( + nameof(ThemeVariant), + inherits: true, + defaultValue: ThemeVariant.Light); + + /// + /// Defines the property. + /// + public static readonly StyledProperty RequestedThemeVariantProperty = + AvaloniaProperty.Register( + nameof(ThemeVariant), + defaultValue: 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 676fa1519a..f9f7cfc4c6 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -79,6 +79,14 @@ namespace Avalonia.Controls public static readonly StyledProperty TransparencyBackgroundFallbackProperty = AvaloniaProperty.Register(nameof(TransparencyBackgroundFallback), Brushes.White); + /// + public static readonly StyledProperty ActualThemeVariantProperty = + ThemeVariantScope.ActualThemeVariantProperty.AddOwner(); + + /// + public static readonly StyledProperty RequestedThemeVariantProperty = + ThemeVariantScope.RequestedThemeVariantProperty.AddOwner(); + /// /// Defines the event. /// @@ -96,7 +104,7 @@ namespace Avalonia.Controls private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler; private readonly IPlatformRenderInterface? _renderInterface; private readonly IGlobalStyles? _globalStyles; - private readonly IGlobalThemeVariantProvider? _applicationThemeHost; + private readonly IThemeVariantHost? _applicationThemeHost; private readonly PointerOverPreProcessor? _pointerOverPreProcessor; private readonly IDisposable? _pointerOverPreProcessorSubscription; private readonly IDisposable? _backGestureSubscription; @@ -153,7 +161,7 @@ namespace Avalonia.Controls _keyboardNavigationHandler = TryGetService(dependencyResolver); _renderInterface = TryGetService(dependencyResolver); _globalStyles = TryGetService(dependencyResolver); - _applicationThemeHost = TryGetService(dependencyResolver); + _applicationThemeHost = TryGetService(dependencyResolver); Renderer = impl.CreateRenderer(this); @@ -633,7 +641,7 @@ namespace Avalonia.Controls private void GlobalActualThemeVariantChanged(object? sender, EventArgs e) { - SetValue(ActualThemeVariantProperty, ((IGlobalThemeVariantProvider)sender!).ActualThemeVariant, BindingPriority.Template); + SetValue(ActualThemeVariantProperty, ((IThemeVariantHost)sender!).ActualThemeVariant, BindingPriority.Template); } private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs index 7426c4e2ed..e21983700d 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs @@ -14,7 +14,7 @@ namespace Avalonia.Diagnostics.Controls public event EventHandler? Closed; public static readonly StyledProperty RequestedThemeVariantProperty = - StyledElement.RequestedThemeVariantProperty.AddOwner(); + ThemeVariantScope.RequestedThemeVariantProperty.AddOwner(); public Application(Avalonia.Application application) { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index cdd344becc..bef32766dc 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -34,7 +34,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var stack = serviceProvider.GetService(); var provideTarget = serviceProvider.GetService(); - var themeVariant = (provideTarget.TargetObject as StyledElement)?.ActualThemeVariant; + var themeVariant = (provideTarget.TargetObject as IThemeVariantHost)?.ActualThemeVariant; var targetType = provideTarget.TargetProperty switch { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs index c5b62cdff2..20d76a3cc7 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs @@ -185,9 +185,9 @@ public class ThemeDictionariesTests : XamlTestBase { using (AvaloniaLocator.EnterScope()) { - var applicationThemeHost = new Mock(); + var applicationThemeHost = new Mock(); applicationThemeHost.SetupGet(h => h.ActualThemeVariant).Returns(ThemeVariant.Dark); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(applicationThemeHost.Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(applicationThemeHost.Object); var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@"