diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs
index 9dcbd7d2c4..aff6fad990 100644
--- a/src/Avalonia.Base/Styling/ControlTheme.cs
+++ b/src/Avalonia.Base/Styling/ControlTheme.cs
@@ -23,16 +23,32 @@ namespace Avalonia.Styling
///
public Type? TargetType { get; set; }
- internal override bool HasSelector => TargetType is not null;
+ ///
+ /// Gets or sets a control theme that is the basis of the current theme.
+ ///
+ public ControlTheme? BasedOn { get; set; }
- internal override SelectorMatch Match(IStyleable control, object? host, bool subscribe)
+ public override SelectorMatchResult TryAttach(IStyleable target, object? host)
{
+ _ = target ?? throw new ArgumentNullException(nameof(target));
+
if (TargetType is null)
throw new InvalidOperationException("ControlTheme has no TargetType.");
- return control.StyleKey == TargetType ?
- SelectorMatch.AlwaysThisType :
- SelectorMatch.NeverThisType;
+ var result = BasedOn?.TryAttach(target, host) ?? SelectorMatchResult.NeverThisType;
+
+ if (HasSettersOrAnimations && target.StyleKey == TargetType)
+ {
+ Attach(target, null);
+ result = SelectorMatchResult.AlwaysThisType;
+ }
+
+ var childResult = TryAttachChildren(target, host);
+
+ if (childResult > result)
+ result = childResult;
+
+ return result;
}
internal override void SetParent(StyleBase? parent)
diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs
index 6d31f7cb18..c8945a713d 100644
--- a/src/Avalonia.Base/Styling/NestingSelector.cs
+++ b/src/Avalonia.Base/Styling/NestingSelector.cs
@@ -15,9 +15,17 @@ namespace Avalonia.Styling
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{
- if (parent is StyleBase s && s.HasSelector)
+ if (parent is Style s && s.Selector is not null)
{
- return s.Match(control, null, subscribe);
+ return s.Selector.Match(control, s.Parent, subscribe);
+ }
+ else if (parent is ControlTheme theme)
+ {
+ if (theme.TargetType is null)
+ throw new InvalidOperationException("ControlTheme has no TargetType.");
+ return control.StyleKey == theme.TargetType ?
+ SelectorMatch.AlwaysThisType :
+ SelectorMatch.NeverThisType;
}
throw new InvalidOperationException(
diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs
index ca20ff2b4b..7a6b746488 100644
--- a/src/Avalonia.Base/Styling/Style.cs
+++ b/src/Avalonia.Base/Styling/Style.cs
@@ -28,7 +28,32 @@ namespace Avalonia.Styling
///
public Selector? Selector { get; set; }
- internal override bool HasSelector => Selector is not null;
+ public override SelectorMatchResult TryAttach(IStyleable target, object? host)
+ {
+ _ = target ?? throw new ArgumentNullException(nameof(target));
+
+ var result = SelectorMatchResult.NeverThisType;
+
+ if (HasSettersOrAnimations)
+ {
+ var match = Selector?.Match(target, Parent, true) ??
+ (target == host ?
+ SelectorMatch.AlwaysThisInstance :
+ SelectorMatch.NeverThisInstance);
+
+ if (match.IsMatch)
+ Attach(target, match.Activator);
+
+ result = match.Result;
+ }
+
+ var childResult = TryAttachChildren(target, host);
+
+ if (childResult > result)
+ result = childResult;
+
+ return result;
+ }
///
/// Returns a string representation of the style.
@@ -46,14 +71,6 @@ namespace Avalonia.Styling
}
}
- internal override SelectorMatch Match(IStyleable control, object? host, bool subscribe)
- {
- return Selector?.Match(control, Parent, subscribe) ??
- (control == host ?
- SelectorMatch.AlwaysThisInstance :
- SelectorMatch.NeverThisInstance);
- }
-
internal override void SetParent(StyleBase? parent)
{
if (parent is Style parentStyle && parentStyle.Selector is not null)
diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs
index b6bfec62bd..306a4cf010 100644
--- a/src/Avalonia.Base/Styling/StyleBase.cs
+++ b/src/Avalonia.Base/Styling/StyleBase.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Metadata;
+using Avalonia.Styling.Activators;
namespace Avalonia.Styling
{
@@ -64,43 +65,14 @@ namespace Avalonia.Styling
bool IResourceNode.HasResources => _resources?.Count > 0;
IReadOnlyList IStyle.Children => (IReadOnlyList?)_children ?? Array.Empty();
- internal abstract bool HasSelector { get; }
+ internal bool HasSettersOrAnimations => _setters?.Count > 0 || _animations?.Count > 0;
public void Add(ISetter setter) => Setters.Add(setter);
public void Add(IStyle style) => Children.Add(style);
public event EventHandler? OwnerChanged;
- public SelectorMatchResult TryAttach(IStyleable target, object? host)
- {
- target = target ?? throw new ArgumentNullException(nameof(target));
-
- var result = SelectorMatchResult.NeverThisType;
-
- if (_setters?.Count > 0 || _animations?.Count > 0)
- {
- var match = Match(target, host, subscribe: true);
-
- if (match.IsMatch)
- {
- var instance = new StyleInstance(this, target, _setters, _animations, match.Activator);
- target.StyleApplied(instance);
- instance.Start();
- }
-
- result = match.Result;
- }
-
- if (_children is not null)
- {
- _childCache ??= new StyleCache();
- var childResult = _childCache.TryAttach(_children, target, host);
- if (childResult > result)
- result = childResult;
- }
-
- return result;
- }
+ public abstract SelectorMatchResult TryAttach(IStyleable target, object? host);
public bool TryGetResource(object key, out object? result)
{
@@ -108,19 +80,20 @@ namespace Avalonia.Styling
return _resources?.TryGetResource(key, out result) ?? false;
}
- ///
- /// Evaluates the style's selector against the specified target element.
- ///
- /// The control.
- /// The element that hosts the style.
- ///
- /// Whether the match should subscribe to changes in order to track the match over time,
- /// or simply return an immediate result.
- ///
- ///
- /// A describing how the style matches the control.
- ///
- internal abstract SelectorMatch Match(IStyleable control, object? host, bool subscribe);
+ internal void Attach(IStyleable target, IStyleActivator? activator)
+ {
+ var instance = new StyleInstance(this, target, _setters, _animations, activator);
+ target.StyleApplied(instance);
+ instance.Start();
+ }
+
+ internal SelectorMatchResult TryAttachChildren(IStyleable target, object? host)
+ {
+ if (_children is null || _children.Count == 0)
+ return SelectorMatchResult.NeverThisType;
+ _childCache ??= new StyleCache();
+ return _childCache.TryAttach(_children, target, host);
+ }
internal virtual void SetParent(StyleBase? parent) => Parent = parent;
diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs
index 0c0808987a..737cf1e048 100644
--- a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs
+++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs
@@ -28,10 +28,10 @@ public class StyledElementTests_Theming
Assert.NotNull(target.Template);
var border = Assert.IsType(target.VisualChild);
- Assert.Equal(border.Background, Brushes.Red);
+ Assert.Equal(Brushes.Red, border.Background);
target.Classes.Add("foo");
- Assert.Equal(border.Background, Brushes.Green);
+ Assert.Equal(Brushes.Green, border.Background);
}
[Fact]
@@ -73,7 +73,7 @@ public class StyledElementTests_Theming
var root = CreateRoot(target);
var canvas = Assert.IsType