From e50b416d5bf1529544830d5a291a1fb2f1b0da64 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 30 Jun 2022 12:50:52 +0200 Subject: [PATCH] Fix parent selectors with /template/ at end. Previously the combinator state was lost when traversing nested selectors. To fix it, instead of running the parent selector in `NestingSelector.Evaluate`, return the parent selector with `MovePrevious`. This required `MovePrevious` to be aware of parent styles and because I had to change its signature, I also made it internal as it doesn't need to be a public API. --- src/Avalonia.Base/Styling/ChildSelector.cs | 2 +- .../Styling/DescendentSelector.cs | 2 +- src/Avalonia.Base/Styling/NestingSelector.cs | 8 +- src/Avalonia.Base/Styling/NotSelector.cs | 2 +- src/Avalonia.Base/Styling/NthChildSelector.cs | 2 +- src/Avalonia.Base/Styling/OrSelector.cs | 2 +- .../Styling/PropertyEqualsSelector.cs | 2 +- src/Avalonia.Base/Styling/Selector.cs | 12 ++- src/Avalonia.Base/Styling/TemplateSelector.cs | 2 +- .../Styling/TypeNameAndClassSelector.cs | 2 +- .../Styling/SelectorTests_Nesting.cs | 80 +++++++++++++++++++ 11 files changed, 103 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Base/Styling/ChildSelector.cs b/src/Avalonia.Base/Styling/ChildSelector.cs index 9512dc34df..bc1d257ce6 100644 --- a/src/Avalonia.Base/Styling/ChildSelector.cs +++ b/src/Avalonia.Base/Styling/ChildSelector.cs @@ -64,7 +64,7 @@ namespace Avalonia.Styling } } - protected override Selector? MovePrevious() => null; + private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null); protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/DescendentSelector.cs b/src/Avalonia.Base/Styling/DescendentSelector.cs index 677a924189..3a16574e04 100644 --- a/src/Avalonia.Base/Styling/DescendentSelector.cs +++ b/src/Avalonia.Base/Styling/DescendentSelector.cs @@ -69,7 +69,7 @@ namespace Avalonia.Styling } } - protected override Selector? MovePrevious() => null; + private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null); protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs index 77c5b719c6..dd0bac31c6 100644 --- a/src/Avalonia.Base/Styling/NestingSelector.cs +++ b/src/Avalonia.Base/Styling/NestingSelector.cs @@ -17,7 +17,7 @@ namespace Avalonia.Styling { if (parent is Style s && s.Selector is not null) { - return s.Selector.Match(control, s.Parent, subscribe); + return SelectorMatch.AlwaysThisType; } else if (parent is ControlTheme theme) { @@ -32,7 +32,11 @@ namespace Avalonia.Styling "Nesting selector was specified but cannot determine parent selector."); } - protected override Selector? MovePrevious() => null; + private protected override (Selector?, IStyle?) MovePrevious(IStyle? parent) + { + return parent is Style parentStyle ? (parentStyle.Selector, parentStyle.Parent) : (null, null); + } + protected override Selector? MovePreviousOrParent() => null; } } diff --git a/src/Avalonia.Base/Styling/NotSelector.cs b/src/Avalonia.Base/Styling/NotSelector.cs index c7727bb6b8..ebde392a3b 100644 --- a/src/Avalonia.Base/Styling/NotSelector.cs +++ b/src/Avalonia.Base/Styling/NotSelector.cs @@ -66,7 +66,7 @@ namespace Avalonia.Styling } } - protected override Selector? MovePrevious() => _previous; + private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent); protected override Selector? MovePreviousOrParent() => _previous; } } diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs index f473791664..e6d9cf58a9 100644 --- a/src/Avalonia.Base/Styling/NthChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthChildSelector.cs @@ -104,7 +104,7 @@ namespace Avalonia.Styling return match ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; } - protected override Selector? MovePrevious() => _previous; + private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent); protected override Selector? MovePreviousOrParent() => _previous; public override string ToString() diff --git a/src/Avalonia.Base/Styling/OrSelector.cs b/src/Avalonia.Base/Styling/OrSelector.cs index af9249864f..115e0aeb95 100644 --- a/src/Avalonia.Base/Styling/OrSelector.cs +++ b/src/Avalonia.Base/Styling/OrSelector.cs @@ -102,7 +102,7 @@ namespace Avalonia.Styling } } - protected override Selector? MovePrevious() => null; + private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null); protected override Selector? MovePreviousOrParent() => null; internal override void ValidateNestingSelector(bool inControlTheme) diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs index 48136ba2de..96f8c8dfeb 100644 --- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs @@ -89,7 +89,7 @@ namespace Avalonia.Styling } - protected override Selector? MovePrevious() => _previous; + private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent); protected override Selector? MovePreviousOrParent() => _previous; internal static bool Compare(Type propertyType, object? propertyValue, object? value) diff --git a/src/Avalonia.Base/Styling/Selector.cs b/src/Avalonia.Base/Styling/Selector.cs index 7ce17518dd..cc8598c5e3 100644 --- a/src/Avalonia.Base/Styling/Selector.cs +++ b/src/Avalonia.Base/Styling/Selector.cs @@ -84,7 +84,13 @@ namespace Avalonia.Styling /// /// Moves to the previous selector. /// - protected abstract Selector? MovePrevious(); + /// + /// The parent style, if the selector is on a nested style. + /// + /// + /// The previous selector, and its nesting parent. + /// + private protected abstract (Selector?, IStyle?) MovePrevious(IStyle? nestingParent); /// /// Moves to the previous selector or the parent selector. @@ -142,14 +148,14 @@ namespace Avalonia.Styling ref AndActivatorBuilder activators, ref Selector? combinator) { - var previous = selector.MovePrevious(); + var (previous, previousParent) = selector.MovePrevious(parent); // Selectors are stored from right-to-left, so we recurse into the selector in order to // reverse this order, because the type selector will be on the left and is our best // opportunity to exit early. if (previous != null && !previous.IsCombinator) { - var previousMatch = Match(control, previous, parent, subscribe, ref activators, ref combinator); + var previousMatch = Match(control, previous, previousParent, subscribe, ref activators, ref combinator); if (previousMatch < SelectorMatchResult.Sometimes) { diff --git a/src/Avalonia.Base/Styling/TemplateSelector.cs b/src/Avalonia.Base/Styling/TemplateSelector.cs index 278e24a203..a68b7003b8 100644 --- a/src/Avalonia.Base/Styling/TemplateSelector.cs +++ b/src/Avalonia.Base/Styling/TemplateSelector.cs @@ -48,7 +48,7 @@ namespace Avalonia.Styling return _parent.Match(templatedParent, parent, subscribe); } - protected override Selector? MovePrevious() => null; + private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null); protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 6681a7da36..3a2150b1e9 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -139,7 +139,7 @@ namespace Avalonia.Styling return Name == null ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance; } - protected override Selector? MovePrevious() => _previous; + private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent); protected override Selector? MovePreviousOrParent() => _previous; private string BuildSelectorString() diff --git a/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs b/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs index 1520dc329d..bd7f0338e6 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Controls; +using Avalonia.Controls.Templates; using Avalonia.Styling; using Avalonia.Styling.Activators; using Xunit; @@ -148,6 +149,85 @@ namespace Avalonia.Base.UnitTests.Styling control.Classes.Remove("foo"); Assert.False(sink.Active); } + + [Fact] + public void Template_Nesting_OfType_Matches() + { + var control = new Control1 { Classes = { "foo" } }; + var button = new Button + { + Template = new FuncControlTemplate((x, _) => control), + }; + + button.ApplyTemplate(); + + Style nested; + var parent = new Style(x => x.OfType