Browse Source

Added support for implicit themes.

If no `Theme` property is provided, try to look up a resource keyed with the control's `StyleKey`.
pull/8263/head
Steven Kirk 4 years ago
parent
commit
d21e634ab3
  1. 28
      src/Avalonia.Base/StyledElement.cs
  2. 4
      src/Avalonia.Base/Styling/IStyleable.cs
  3. 4
      src/Avalonia.Base/Styling/Styler.cs
  4. 43
      tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs
  5. 4
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs
  6. 6
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs

28
src/Avalonia.Base/StyledElement.cs

@ -60,6 +60,7 @@ namespace Avalonia
public static readonly StyledProperty<ControlTheme?> ThemeProperty = public static readonly StyledProperty<ControlTheme?> ThemeProperty =
AvaloniaProperty.Register<StyledElement, ControlTheme?>(nameof(Theme)); AvaloniaProperty.Register<StyledElement, ControlTheme?>(nameof(Theme));
private static readonly ControlTheme s_invalidTheme = new ControlTheme();
private int _initCount; private int _initCount;
private string? _name; private string? _name;
private readonly Classes _classes = new Classes(); private readonly Classes _classes = new Classes();
@ -72,6 +73,7 @@ namespace Avalonia
private ITemplatedControl? _templatedParent; private ITemplatedControl? _templatedParent;
private bool _dataContextUpdating; private bool _dataContextUpdating;
private bool _hasPromotedTheme; private bool _hasPromotedTheme;
private ControlTheme? _implicitTheme;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="StyledElement"/> class. /// Initializes static members of the <see cref="StyledElement"/> class.
@ -495,6 +497,31 @@ namespace Avalonia
}; };
} }
ControlTheme? IStyleable.GetEffectiveTheme()
{
var theme = Theme;
// Explitly set Theme property takes precedence.
if (theme is not null)
return theme;
// If the Theme property is not set, try to find a ControlTheme resource with our StyleKey.
if (_implicitTheme is null)
{
var key = ((IStyleable)this).StyleKey;
if (this.TryFindResource(key, out var value) && value is ControlTheme t)
_implicitTheme = t;
else
_implicitTheme = s_invalidTheme;
}
if (_implicitTheme != s_invalidTheme)
return _implicitTheme;
return null;
}
void IStyleable.StyleApplied(IStyleInstance instance) void IStyleable.StyleApplied(IStyleInstance instance)
{ {
instance = instance ?? throw new ArgumentNullException(nameof(instance)); instance = instance ?? throw new ArgumentNullException(nameof(instance));
@ -736,6 +763,7 @@ namespace Avalonia
if (_logicalRoot != null) if (_logicalRoot != null)
{ {
_logicalRoot = null; _logicalRoot = null;
_implicitTheme = null;
DetachStyles(); DetachStyles();
OnDetachedFromLogicalTree(e); OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e); DetachedFromLogicalTree?.Invoke(this, e);

4
src/Avalonia.Base/Styling/IStyleable.cs

@ -27,9 +27,9 @@ namespace Avalonia.Styling
ITemplatedControl? TemplatedParent { get; } ITemplatedControl? TemplatedParent { get; }
/// <summary> /// <summary>
/// Gets the theme to be applied to the control. /// Gets the effective theme for the control as used by the syling system.
/// </summary> /// </summary>
public ControlTheme? Theme { get; } ControlTheme? GetEffectiveTheme();
/// <summary> /// <summary>
/// Notifies the element that a style has been applied. /// Notifies the element that a style has been applied.

4
src/Avalonia.Base/Styling/Styler.cs

@ -11,10 +11,10 @@ namespace Avalonia.Styling
// If the control has a themed templated parent then first apply the styles from // If the control has a themed templated parent then first apply the styles from
// the templated parent theme. // the templated parent theme.
if (target.TemplatedParent is IStyleable styleableParent) if (target.TemplatedParent is IStyleable styleableParent)
styleableParent.Theme?.TryAttach(target, styleableParent); styleableParent.GetEffectiveTheme()?.TryAttach(target, styleableParent);
// Next apply the control theme. // Next apply the control theme.
target.Theme?.TryAttach(target, target); target.GetEffectiveTheme()?.TryAttach(target, target);
// Apply styles from the rest of the tree. // Apply styles from the rest of the tree.
if (target is IStyleHost styleHost) if (target is IStyleHost styleHost)

43
tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs

@ -158,6 +158,49 @@ public class StyledElementTests_Theming
} }
} }
public class ImplicitTheme
{
[Fact]
public void Implicit_Theme_Is_Applied_When_Attached_To_Logical_Tree()
{
using var app = UnitTestApplication.Start(TestServices.RealStyler);
var target = CreateTarget();
var root = CreateRoot(target);
Assert.NotNull(target.Template);
var border = Assert.IsType<Border>(target.VisualChild);
Assert.Equal(Brushes.Red, border.Background);
target.Classes.Add("foo");
Assert.Equal(Brushes.Green, border.Background);
}
[Fact]
public void Implicit_Theme_Is_Cleared_When_Removed_From_Logical_Tree()
{
using var app = UnitTestApplication.Start(TestServices.RealStyler);
var target = CreateTarget();
var root = CreateRoot(target);
Assert.NotNull(((IStyleable)target).GetEffectiveTheme());
root.Child = null;
Assert.Null(((IStyleable)target).GetEffectiveTheme());
}
private static ThemedControl CreateTarget() => new ThemedControl();
private static TestRoot CreateRoot(IControl child)
{
var result = new TestRoot();
result.Resources.Add(typeof(ThemedControl), CreateTheme());
result.Child = child;
result.LayoutManager.ExecuteInitialLayoutPass();
return result;
}
}
public class ThemeFromStyle public class ThemeFromStyle
{ {
[Fact] [Fact]

4
tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs

@ -137,9 +137,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
get { throw new NotImplementedException(); } get { throw new NotImplementedException(); }
} }
public ControlTheme Theme public ControlTheme GetEffectiveTheme()
{ {
get { throw new NotImplementedException(); } throw new NotImplementedException();
} }
public void DetachStyles() public void DetachStyles()

6
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs

@ -845,7 +845,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Equal("bar", border.Tag); Assert.Equal("bar", border.Tag);
var resourceProvider = (TrackingResourceProvider)window.Resources.MergedDictionaries[0]; var resourceProvider = (TrackingResourceProvider)window.Resources.MergedDictionaries[0];
Assert.Equal(new[] { "bar" }, resourceProvider.RequestedResources); Assert.Contains("bar", resourceProvider.RequestedResources);
Assert.DoesNotContain("foo", resourceProvider.RequestedResources);
} }
[Fact] [Fact]
@ -883,7 +884,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Equal("bar", border.Tag); Assert.Equal("bar", border.Tag);
var resourceProvider = (TrackingResourceProvider)window.Resources.MergedDictionaries[0]; var resourceProvider = (TrackingResourceProvider)window.Resources.MergedDictionaries[0];
Assert.Equal(new[] { "bar" }, resourceProvider.RequestedResources); Assert.Contains("bar", resourceProvider.RequestedResources);
Assert.DoesNotContain("foo", resourceProvider.RequestedResources);
} }
private IDisposable StyledWindow(params (string, string)[] assets) private IDisposable StyledWindow(params (string, string)[] assets)

Loading…
Cancel
Save