Skip to content

Commit

Permalink
Fix parent selectors with /template/ at end.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
grokys committed Jun 30, 2022
1 parent 8abeb76 commit e50b416
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Styling/ChildSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
}
}

protected override Selector? MovePrevious() => null;
private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null);
protected override Selector? MovePreviousOrParent() => _parent;
}
}
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Styling/DescendentSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
}
}

protected override Selector? MovePrevious() => null;
private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null);
protected override Selector? MovePreviousOrParent() => _parent;
}
}
8 changes: 6 additions & 2 deletions src/Avalonia.Base/Styling/NestingSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
{
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)
{
Expand All @@ -32,7 +32,11 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
"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;
}
}
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Styling/NotSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
}
}

protected override Selector? MovePrevious() => _previous;
private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent);
protected override Selector? MovePreviousOrParent() => _previous;
}
}
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Styling/NthChildSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ internal static SelectorMatch Evaluate(
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()
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Styling/OrSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
}
}

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)
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Styling/PropertyEqualsSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo

}

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)
Expand Down
12 changes: 9 additions & 3 deletions src/Avalonia.Base/Styling/Selector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,13 @@ public SelectorMatch Match(IStyleable control, IStyle? parent = null, bool subsc
/// <summary>
/// Moves to the previous selector.
/// </summary>
protected abstract Selector? MovePrevious();
/// <param name="nestingParent">
/// The parent style, if the selector is on a nested style.
/// </param>
/// <remarks>
/// The previous selector, and its nesting parent.
/// </remarks>
private protected abstract (Selector?, IStyle?) MovePrevious(IStyle? nestingParent);

/// <summary>
/// Moves to the previous selector or the parent selector.
Expand Down Expand Up @@ -142,14 +148,14 @@ private static SelectorMatchResult Match(
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)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Styling/TemplateSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
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;
}
}
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
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()
Expand Down
80 changes: 80 additions & 0 deletions tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Styling;
using Avalonia.Styling.Activators;
using Xunit;
Expand Down Expand Up @@ -148,6 +149,85 @@ public void Double_Nesting_Class_Matches()
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<Button>().Template())
{
Children =
{
(nested = new Style(x => x.Nesting().OfType<Control1>())),
}
};

// REMOOOOVEEEE
var foo = new Style(x => x.OfType<Button>().Template().OfType<Control1>());
var match2 = foo.Selector.Match(control, parent);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, match2.Result);

var match = nested.Selector.Match(control, parent);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, match.Result);


}

[Fact]
public void Template_Nesting_OfType_Class_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<Button>().Template())
{
Children =
{
(nested = new Style(x => x.Nesting().OfType<Control1>().Class("foo"))),
}
};

var match = nested.Selector.Match(control, parent);
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
}

[Fact]
public void Class_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<Button>().Class("bar").Template())
{
Children =
{
(nested = new Style(x => x.Nesting().OfType<Control1>())),
}
};

var match = nested.Selector.Match(control, parent);
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
}

[Fact]
public void Or_Nesting_Class_Matches()
Expand Down

0 comments on commit e50b416

Please sign in to comment.