diff --git a/src/Avalonia.Base/Styling/ChildSelector.cs b/src/Avalonia.Base/Styling/ChildSelector.cs index ac28d2bc46..b118ea5561 100644 --- a/src/Avalonia.Base/Styling/ChildSelector.cs +++ b/src/Avalonia.Base/Styling/ChildSelector.cs @@ -31,7 +31,7 @@ namespace Avalonia.Styling { if (_selectorString == null) { - _selectorString = _parent.ToString(owner) + " > "; + _selectorString = _parent.ToString(owner, true) + " > "; } return _selectorString; diff --git a/src/Avalonia.Base/Styling/DescendentSelector.cs b/src/Avalonia.Base/Styling/DescendentSelector.cs index 6706eb4441..646f7272a5 100644 --- a/src/Avalonia.Base/Styling/DescendentSelector.cs +++ b/src/Avalonia.Base/Styling/DescendentSelector.cs @@ -29,7 +29,7 @@ namespace Avalonia.Styling { if (_selectorString == null) { - _selectorString = _parent.ToString(owner) + ' '; + _selectorString = _parent.ToString(owner, true) + ' '; } return _selectorString; diff --git a/src/Avalonia.Base/Styling/NotSelector.cs b/src/Avalonia.Base/Styling/NotSelector.cs index 9a541cbba7..dca8a45ef8 100644 --- a/src/Avalonia.Base/Styling/NotSelector.cs +++ b/src/Avalonia.Base/Styling/NotSelector.cs @@ -39,7 +39,7 @@ namespace Avalonia.Styling { if (_selectorString == null) { - _selectorString = $"{_previous?.ToString(owner)}:not({_argument})"; + _selectorString = $"{_previous?.ToString(owner, true)}:not({_argument})"; } return _selectorString; diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs index bf6247aba1..e11b81a088 100644 --- a/src/Avalonia.Base/Styling/NthChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthChildSelector.cs @@ -109,9 +109,9 @@ namespace Avalonia.Styling public override string ToString(Style? owner) { var expectedCapacity = NthLastChildSelectorName.Length + 8; - var stringBuilder = StringBuilderCache.Acquire(expectedCapacity); - stringBuilder.Append(_previous?.ToString(owner)); - + var stringBuilder = StringBuilderCache.Acquire(expectedCapacity); + stringBuilder.Append(_previous?.ToString(owner, true)); + stringBuilder.Append(':'); stringBuilder.Append(_reversed ? NthLastChildSelectorName : NthChildSelectorName); stringBuilder.Append('('); diff --git a/src/Avalonia.Base/Styling/OrSelector.cs b/src/Avalonia.Base/Styling/OrSelector.cs index cc77aa9fcf..a58ced7c65 100644 --- a/src/Avalonia.Base/Styling/OrSelector.cs +++ b/src/Avalonia.Base/Styling/OrSelector.cs @@ -45,11 +45,19 @@ namespace Avalonia.Styling internal override Type? TargetType => _targetType ??= EvaluateTargetType(); /// - public override string ToString(Style? owner) + public override string ToString(Style? owner) => ToString(owner, false); + + /// + internal override string ToString(Style? owner, bool hasNext) { if (_selectorString == null) { - _selectorString = string.Join(", ", _selectors.Select(x => x.ToString(owner))); + _selectorString = string.Join(", ", _selectors.Select(x => x.ToString(owner, true))); + + if (hasNext) + { + _selectorString = $"({_selectorString})"; + } } return _selectorString; @@ -97,13 +105,13 @@ namespace Avalonia.Styling private protected override Selector? MovePrevious() => null; private protected override Selector? MovePreviousOrParent() => null; - internal override void ValidateNestingSelector(bool inControlTheme) + internal override void ValidateNestingSelector(bool inControlTheme, int templateCount = 0) { var count = _selectors.Count; for (var i = 0; i < count; i++) { - _selectors[i].ValidateNestingSelector(inControlTheme); + _selectors[i].ValidateNestingSelector(inControlTheme, templateCount); } } diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs index 1d684eeca3..316b7a2853 100644 --- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs @@ -45,7 +45,7 @@ namespace Avalonia.Styling if (_previous != null) { - builder.Append(_previous.ToString(owner)); + builder.Append(_previous.ToString(owner, true)); } builder.Append('['); @@ -85,7 +85,6 @@ namespace Avalonia.Styling ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; } - } private protected override Selector? MovePrevious() => _previous; diff --git a/src/Avalonia.Base/Styling/Selector.cs b/src/Avalonia.Base/Styling/Selector.cs index cb3ddc343c..9102d2e770 100644 --- a/src/Avalonia.Base/Styling/Selector.cs +++ b/src/Avalonia.Base/Styling/Selector.cs @@ -47,7 +47,7 @@ namespace Avalonia.Styling // right-to-left, so MatchUntilCombinator reverses this order because the type selector // will be on the left. var match = MatchUntilCombinator(control, this, parent, subscribe, out var combinator); - + // If the pre-combinator selector matches, we can now match the combinator, if any. if (match.IsMatch && combinator is object) { @@ -76,6 +76,10 @@ namespace Avalonia.Styling /// The owner style. public abstract string ToString(Style? owner); + /// + /// Whether there is a selector that comes after this one. + internal virtual string ToString(Style? owner, bool hasNext) => ToString(owner); + /// /// Evaluates the selector for a match. /// @@ -100,30 +104,31 @@ namespace Avalonia.Styling /// private protected abstract Selector? MovePreviousOrParent(); - internal virtual void ValidateNestingSelector(bool inControlTheme) + internal virtual void ValidateNestingSelector(bool inControlTheme, int templateCount = 0) { var s = this; - var templateCount = 0; - do + if (inControlTheme) { - if (inControlTheme) - { - if (!s.InTemplate && s.IsCombinator) - throw new InvalidOperationException( - "ControlTheme style may not directly contain a child or descendent selector."); - if (s is TemplateSelector && templateCount++ > 0) - throw new InvalidOperationException( - "ControlTemplate styles cannot contain multiple template selectors."); - } + if (!s.InTemplate && s.IsCombinator) + throw new InvalidOperationException( + "ControlTheme style may not directly contain a child or descendent selector."); + if (s is TemplateSelector && templateCount++ > 0) + throw new InvalidOperationException( + "ControlTemplate styles cannot contain multiple template selectors."); + } - var previous = s.MovePreviousOrParent(); + var previous = s.MovePreviousOrParent(); - if (previous is null && s is not NestingSelector) + if (previous is null) + { + if (s is not NestingSelector) throw new InvalidOperationException("Child styles must have a nesting selector."); - - s = previous; - } while (s is not null); + } + else + { + previous.ValidateNestingSelector(inControlTheme, templateCount); + } } private static SelectorMatch MatchUntilCombinator( diff --git a/src/Avalonia.Base/Styling/Selectors.cs b/src/Avalonia.Base/Styling/Selectors.cs index d7406f2164..a58ed3f11d 100644 --- a/src/Avalonia.Base/Styling/Selectors.cs +++ b/src/Avalonia.Base/Styling/Selectors.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; namespace Avalonia.Styling { @@ -124,7 +123,7 @@ namespace Avalonia.Styling { return new NotSelector(previous, argument(null)); } - + /// /// Returns a selector which inverts the results of selector argument. /// diff --git a/src/Avalonia.Base/Styling/TemplateSelector.cs b/src/Avalonia.Base/Styling/TemplateSelector.cs index 1fa2ca2d0f..4fcccef87e 100644 --- a/src/Avalonia.Base/Styling/TemplateSelector.cs +++ b/src/Avalonia.Base/Styling/TemplateSelector.cs @@ -30,7 +30,7 @@ namespace Avalonia.Styling { if (_selectorString == null) { - _selectorString = _parent.ToString(owner) + " /template/ "; + _selectorString = _parent.ToString(owner, true) + " /template/ "; } return _selectorString; diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 81b204761b..4fba8c02c6 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -143,7 +143,7 @@ namespace Avalonia.Styling if (_previous != null) { - builder.Append(_previous.ToString(owner)); + builder.Append(_previous.ToString(owner, true)); } if (TargetType != null) diff --git a/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Or.cs b/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Or.cs index fb5d54bd1f..c013778128 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Or.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Or.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Controls; using Avalonia.Styling; using Xunit; @@ -89,6 +90,24 @@ namespace Avalonia.Base.UnitTests.Styling Assert.Equal(null, target.TargetType); } + + [Fact] + public void ValidateNestingSelector_Checks_Children_When_Parent_Is_An_OrSelector() + { + var target = Selectors.Or( + default(Selector).Class("foo"), + default(Selector).Class("bar") + ).Name("baz"); + + Assert.Throws(() => target.ValidateNestingSelector(false)); + + target = Selectors.Or( + default(Selector).Nesting().Class("foo"), + default(Selector).Nesting().Class("bar") + ).Name("baz"); + + target.ValidateNestingSelector(false); + } public class Control1 : Control {