Browse Source

Extract theme related properties to IThemeVariantHost and move property definitions to ThemeVariantScope

pull/10149/head
Max Katz 3 years ago
parent
commit
f2050c868a
  1. 39
      src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
  2. 33
      src/Avalonia.Base/StyledElement.cs
  3. 22
      src/Avalonia.Base/Styling/IGlobalThemeVariantProvider.cs
  4. 26
      src/Avalonia.Base/Styling/IThemeVariantHost.cs
  5. 19
      src/Avalonia.Controls/Application.cs
  6. 17
      src/Avalonia.Controls/Control.cs
  7. 17
      src/Avalonia.Controls/ThemeVariantScope.cs
  8. 14
      src/Avalonia.Controls/TopLevel.cs
  9. 2
      src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
  10. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  11. 4
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs

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

33
src/Avalonia.Base/StyledElement.cs

@ -71,23 +71,6 @@ namespace Avalonia
public static readonly StyledProperty<ControlTheme?> ThemeProperty =
AvaloniaProperty.Register<StyledElement, ControlTheme?>(nameof(Theme));
/// <summary>
/// Defines the <see cref="ActualThemeVariant"/> property.
/// </summary>
public static readonly StyledProperty<ThemeVariant> ActualThemeVariantProperty =
AvaloniaProperty.Register<StyledElement, ThemeVariant>(
nameof(ThemeVariant),
inherits: true,
defaultValue: ThemeVariant.Light);
/// <summary>
/// Defines the RequestedThemeVariant property.
/// </summary>
public static readonly StyledProperty<ThemeVariant?> RequestedThemeVariantProperty =
AvaloniaProperty.Register<StyledElement, ThemeVariant?>(
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);
}
/// <summary>
/// Gets the UI theme that is currently used by the element, which might be different than the <see cref="RequestedThemeVariantProperty"/>.
/// </summary>
/// <returns>
/// 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.
/// </returns>
public ThemeVariant ActualThemeVariant => GetValue(ActualThemeVariantProperty);
/// <summary>
/// Gets the styled element's logical children.
/// </summary>
@ -647,13 +621,6 @@ namespace Avalonia
if (change.Property == ThemeProperty)
OnControlThemeChanged();
else if (change.Property == RequestedThemeVariantProperty)
{
if (change.GetNewValue<ThemeVariant>() is {} themeVariant && themeVariant != ThemeVariant.Default)
SetValue(ActualThemeVariantProperty, themeVariant);
else
ClearValue(ActualThemeVariantProperty);
}
}
private protected virtual void OnControlThemeChanged()

22
src/Avalonia.Base/Styling/IGlobalThemeVariantProvider.cs

@ -1,22 +0,0 @@
using System;
using Avalonia.Controls;
using Avalonia.Metadata;
namespace Avalonia.Styling;
/// <summary>
/// Interface for an application host element with a root theme variant.
/// </summary>
[Unstable]
public interface IGlobalThemeVariantProvider : IResourceHost
{
/// <summary>
/// Gets the UI theme variant that is used by the control (and its child elements) for resource determination.
/// </summary>
ThemeVariant ActualThemeVariant { get; }
/// <summary>
/// Raised when the theme variant is changed on the element or an ancestor of the element.
/// </summary>
event EventHandler? ActualThemeVariantChanged;
}

26
src/Avalonia.Base/Styling/IThemeVariantHost.cs

@ -0,0 +1,26 @@
using System;
using Avalonia.Controls;
using Avalonia.Metadata;
namespace Avalonia.Styling;
/// <summary>
/// Interface for the host element with a theme variant.
/// </summary>
[Unstable]
public interface IThemeVariantHost : IResourceHost
{
/// <summary>
/// Gets the UI theme that is currently used by the element, which might be different than the RequestedThemeVariantProperty.
/// </summary>
/// <returns>
/// 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.
/// </returns>
ThemeVariant ActualThemeVariant { get; }
/// <summary>
/// Raised when the theme variant is changed on the element or an ancestor of the element.
/// </summary>
event EventHandler? ActualThemeVariantChanged;
}

19
src/Avalonia.Controls/Application.cs

@ -29,7 +29,7 @@ namespace Avalonia
/// method.
/// - Tracks the lifetime of the application.
/// </remarks>
public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IGlobalThemeVariantProvider, IApplicationPlatformEvents
public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IThemeVariantHost, IApplicationPlatformEvents
{
/// <summary>
/// The application-global data templates.
@ -50,13 +50,13 @@ namespace Avalonia
public static readonly StyledProperty<object?> DataContextProperty =
StyledElement.DataContextProperty.AddOwner<Application>();
/// <inheritdoc cref="StyledElement.ActualThemeVariantProperty" />
/// <inheritdoc cref="ThemeVariantScope.ActualThemeVariantProperty" />
public static readonly StyledProperty<ThemeVariant> ActualThemeVariantProperty =
StyledElement.ActualThemeVariantProperty.AddOwner<Application>();
ThemeVariantScope.ActualThemeVariantProperty.AddOwner<Application>();
/// <inheritdoc cref="StyledElement.RequestedThemeVariantProperty" />
/// <inheritdoc cref="ThemeVariantScope.RequestedThemeVariantProperty" />
public static readonly StyledProperty<ThemeVariant?> RequestedThemeVariantProperty =
StyledElement.RequestedThemeVariantProperty.AddOwner<Application>();
ThemeVariantScope.RequestedThemeVariantProperty.AddOwner<Application>();
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs>? ResourcesChanged;
@ -95,11 +95,8 @@ namespace Avalonia
set => SetValue(RequestedThemeVariantProperty, value);
}
/// <inheritdoc cref="StyledElement.ActualThemeVariant"/>
public ThemeVariant ActualThemeVariant
{
get => GetValue(ActualThemeVariantProperty);
}
/// <inheritdoc cref="ThemeVariantScope.ActualThemeVariant"/>
public ThemeVariant ActualThemeVariant => GetValue(ActualThemeVariantProperty);
/// <summary>
/// Gets the current instance of the <see cref="Application"/> class.
@ -256,7 +253,7 @@ namespace Avalonia
.Bind<IAccessKeyHandler>().ToTransient<AccessKeyHandler>()
.Bind<IGlobalDataTemplates>().ToConstant(this)
.Bind<IGlobalStyles>().ToConstant(this)
.Bind<IGlobalThemeVariantProvider>().ToConstant(this)
.Bind<IThemeVariantHost>().ToConstant(this)
.Bind<IFocusManager>().ToConstant(FocusManager)
.Bind<IInputManager>().ToConstant(InputManager)
.Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>()

17
src/Avalonia.Controls/Control.cs

@ -26,7 +26,7 @@ namespace Avalonia.Controls
/// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control.
/// - <see cref="ContextRequestedEvent"/> and other context menu related members.
/// </remarks>
public class Control : InputElement, IDataTemplateHost, INamed, IVisualBrushInitialize, ISetterValue
public class Control : InputElement, IDataTemplateHost, INamed, IVisualBrushInitialize, ISetterValue, IThemeVariantHost
{
/// <summary>
/// Defines the <see cref="FocusAdorner"/> 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;
/// <summary>
/// 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<ThemeVariant>() is {} themeVariant && themeVariant != ThemeVariant.Default)
SetValue(ThemeVariantScope.ActualThemeVariantProperty, themeVariant);
else
ClearValue(ThemeVariantScope.ActualThemeVariantProperty);
}
else if (change.Property == ThemeVariantScope.ActualThemeVariantProperty)
{
ActualThemeVariantChanged?.Invoke(this, EventArgs.Empty);
}
}
}
}

17
src/Avalonia.Controls/ThemeVariantScope.cs

@ -7,6 +7,23 @@ namespace Avalonia.Controls
/// </summary>
public class ThemeVariantScope : Decorator
{
/// <summary>
/// Defines the <see cref="StyledElement.ActualThemeVariant"/> property.
/// </summary>
public static readonly StyledProperty<ThemeVariant> ActualThemeVariantProperty =
AvaloniaProperty.Register<ThemeVariantScope, ThemeVariant>(
nameof(ThemeVariant),
inherits: true,
defaultValue: ThemeVariant.Light);
/// <summary>
/// Defines the <see cref="RequestedThemeVariant"/> property.
/// </summary>
public static readonly StyledProperty<ThemeVariant?> RequestedThemeVariantProperty =
AvaloniaProperty.Register<ThemeVariantScope, ThemeVariant?>(
nameof(ThemeVariant),
defaultValue: ThemeVariant.Default);
/// <summary>
/// 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.

14
src/Avalonia.Controls/TopLevel.cs

@ -79,6 +79,14 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBrush> TransparencyBackgroundFallbackProperty =
AvaloniaProperty.Register<TopLevel, IBrush>(nameof(TransparencyBackgroundFallback), Brushes.White);
/// <inheritdoc cref="ThemeVariantScope.ActualThemeVariantProperty" />
public static readonly StyledProperty<ThemeVariant> ActualThemeVariantProperty =
ThemeVariantScope.ActualThemeVariantProperty.AddOwner<Application>();
/// <inheritdoc cref="ThemeVariantScope.RequestedThemeVariantProperty" />
public static readonly StyledProperty<ThemeVariant?> RequestedThemeVariantProperty =
ThemeVariantScope.RequestedThemeVariantProperty.AddOwner<Application>();
/// <summary>
/// Defines the <see cref="BackRequested"/> event.
/// </summary>
@ -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<IKeyboardNavigationHandler>(dependencyResolver);
_renderInterface = TryGetService<IPlatformRenderInterface>(dependencyResolver);
_globalStyles = TryGetService<IGlobalStyles>(dependencyResolver);
_applicationThemeHost = TryGetService<IGlobalThemeVariantProvider>(dependencyResolver);
_applicationThemeHost = TryGetService<IThemeVariantHost>(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)

2
src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs

@ -14,7 +14,7 @@ namespace Avalonia.Diagnostics.Controls
public event EventHandler? Closed;
public static readonly StyledProperty<ThemeVariant?> RequestedThemeVariantProperty =
StyledElement.RequestedThemeVariantProperty.AddOwner<Application>();
ThemeVariantScope.RequestedThemeVariantProperty.AddOwner<Application>();
public Application(Avalonia.Application application)
{

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -34,7 +34,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
var stack = serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>();
var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
var themeVariant = (provideTarget.TargetObject as StyledElement)?.ActualThemeVariant;
var themeVariant = (provideTarget.TargetObject as IThemeVariantHost)?.ActualThemeVariant;
var targetType = provideTarget.TargetProperty switch
{

4
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs

@ -185,9 +185,9 @@ public class ThemeDictionariesTests : XamlTestBase
{
using (AvaloniaLocator.EnterScope())
{
var applicationThemeHost = new Mock<IGlobalThemeVariantProvider>();
var applicationThemeHost = new Mock<IThemeVariantHost>();
applicationThemeHost.SetupGet(h => h.ActualThemeVariant).Returns(ThemeVariant.Dark);
AvaloniaLocator.CurrentMutable.Bind<IGlobalThemeVariantProvider>().ToConstant(applicationThemeHost.Object);
AvaloniaLocator.CurrentMutable.Bind<IThemeVariantHost>().ToConstant(applicationThemeHost.Object);
var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@"
<ThemeVariantScope xmlns='https://github.com/avaloniaui'

Loading…
Cancel
Save