14 changed files with 955 additions and 12 deletions
@ -0,0 +1,134 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Text; |
|||
|
|||
using Avalonia.LogicalTree; |
|||
|
|||
namespace Avalonia.Styling |
|||
{ |
|||
public interface IChildIndexProvider |
|||
{ |
|||
(int Index, int? TotalCount) GetChildIndex(ILogical child); |
|||
} |
|||
|
|||
public class NthLastChildSelector : NthChildSelector |
|||
{ |
|||
public NthLastChildSelector(Selector? previous, int step, int offset) : base(previous, step, offset, true) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
public class NthChildSelector : Selector |
|||
{ |
|||
private const string NthChildSelectorName = "nth-child"; |
|||
private const string NthLastChildSelectorName = "nth-last-child"; |
|||
private readonly Selector? _previous; |
|||
private readonly bool _reversed; |
|||
|
|||
internal protected NthChildSelector(Selector? previous, int step, int offset, bool reversed) |
|||
{ |
|||
_previous = previous; |
|||
Step = step; |
|||
Offset = offset; |
|||
_reversed = reversed; |
|||
} |
|||
|
|||
public NthChildSelector(Selector? previous, int step, int offset) |
|||
: this(previous, step, offset, false) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public override bool InTemplate => _previous?.InTemplate ?? false; |
|||
|
|||
public override bool IsCombinator => false; |
|||
|
|||
public override Type? TargetType => _previous?.TargetType; |
|||
|
|||
public int Step { get; } |
|||
public int Offset { get; } |
|||
|
|||
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) |
|||
{ |
|||
var logical = (ILogical)control; |
|||
var controlParent = logical.LogicalParent; |
|||
|
|||
if (controlParent is IChildIndexProvider childIndexProvider) |
|||
{ |
|||
var (index, totalCount) = childIndexProvider.GetChildIndex(logical); |
|||
if (index < 0) |
|||
{ |
|||
return SelectorMatch.NeverThisInstance; |
|||
} |
|||
|
|||
if (_reversed) |
|||
{ |
|||
if (totalCount is int totalCountValue) |
|||
{ |
|||
index = totalCountValue - index; |
|||
} |
|||
else |
|||
{ |
|||
return SelectorMatch.NeverThisInstance; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// nth child index is 1-based
|
|||
index += 1; |
|||
} |
|||
|
|||
|
|||
var n = Math.Sign(Step); |
|||
|
|||
var diff = index - Offset; |
|||
var match = diff == 0 || (Math.Sign(diff) == n && diff % Step == 0); |
|||
|
|||
return match ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; |
|||
} |
|||
else |
|||
{ |
|||
return SelectorMatch.NeverThisInstance; |
|||
} |
|||
|
|||
} |
|||
|
|||
protected override Selector? MovePrevious() => _previous; |
|||
|
|||
public override string ToString() |
|||
{ |
|||
var expectedCapacity = NthLastChildSelectorName.Length + 8; |
|||
var stringBuilder = new StringBuilder(_previous?.ToString(), expectedCapacity); |
|||
|
|||
stringBuilder.Append(':'); |
|||
stringBuilder.Append(_reversed ? NthLastChildSelectorName : NthChildSelectorName); |
|||
stringBuilder.Append('('); |
|||
|
|||
var hasStep = false; |
|||
if (Step != 0) |
|||
{ |
|||
hasStep = true; |
|||
stringBuilder.Append(Step); |
|||
stringBuilder.Append('n'); |
|||
} |
|||
|
|||
if (Offset > 0) |
|||
{ |
|||
if (hasStep) |
|||
{ |
|||
stringBuilder.Append('+'); |
|||
} |
|||
stringBuilder.Append(Offset); |
|||
} |
|||
else if (Offset < 0) |
|||
{ |
|||
stringBuilder.Append('-'); |
|||
stringBuilder.Append(-Offset); |
|||
} |
|||
|
|||
stringBuilder.Append(')'); |
|||
|
|||
return stringBuilder.ToString(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,217 @@ |
|||
using Avalonia.Controls; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Styling.UnitTests |
|||
{ |
|||
public class SelectorTests_NthChild |
|||
{ |
|||
[Theory] |
|||
[InlineData(2, 0, ":nth-child(2n)")] |
|||
[InlineData(2, 1, ":nth-child(2n+1)")] |
|||
[InlineData(1, 0, ":nth-child(1n)")] |
|||
[InlineData(4, -1, ":nth-child(4n-1)")] |
|||
[InlineData(0, 1, ":nth-child(1)")] |
|||
[InlineData(0, -1, ":nth-child(-1)")] |
|||
[InlineData(int.MaxValue, int.MinValue + 1, ":nth-child(2147483647n-2147483647)")] |
|||
public void Not_Selector_Should_Have_Correct_String_Representation(int step, int offset, string expected) |
|||
{ |
|||
var target = default(Selector).NthChild(step, offset); |
|||
|
|||
Assert.Equal(expected, target.ToString()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthChild(2, 0); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel_With_Offset() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthChild(2, 1); |
|||
|
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel_With_Negative_Offset() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthChild(4, -1); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel_With_Singular_Step() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthChild(1, 2); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel_With_Singular_Step_With_Negative_Offset() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthChild(1, -1); |
|||
|
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel_With_Zero_Step_With_Offset() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthChild(0, 2); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Doesnt_Match_Control_In_Panel_With_Zero_Step_With_Negative_Offset() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthChild(0, -2); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel_With_Previous_Selector() |
|||
{ |
|||
Border b1, b2; |
|||
Button b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new Control[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Button(), |
|||
b4 = new Button() |
|||
}); |
|||
|
|||
var previous = default(Selector).OfType<Border>(); |
|||
var target = previous.NthChild(2, 0); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Doesnt_Match_Control_Out_Of_Panel_Parent() |
|||
{ |
|||
Border b1; |
|||
var contentControl = new ContentControl(); |
|||
contentControl.Content = b1 = new Border(); |
|||
|
|||
var target = default(Selector).NthChild(1, 0); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Returns_Correct_TargetType() |
|||
{ |
|||
var target = new NthChildSelector(default(Selector).OfType<Control1>(), 1, 0); |
|||
|
|||
Assert.Equal(typeof(Control1), target.TargetType); |
|||
} |
|||
|
|||
public class Control1 : Control |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,217 @@ |
|||
using Avalonia.Controls; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Styling.UnitTests |
|||
{ |
|||
public class SelectorTests_NthLastChild |
|||
{ |
|||
[Theory] |
|||
[InlineData(2, 0, ":nth-last-child(2n)")] |
|||
[InlineData(2, 1, ":nth-last-child(2n+1)")] |
|||
[InlineData(1, 0, ":nth-last-child(1n)")] |
|||
[InlineData(4, -1, ":nth-last-child(4n-1)")] |
|||
[InlineData(0, 1, ":nth-last-child(1)")] |
|||
[InlineData(0, -1, ":nth-last-child(-1)")] |
|||
[InlineData(int.MaxValue, int.MinValue + 1, ":nth-last-child(2147483647n-2147483647)")] |
|||
public void Not_Selector_Should_Have_Correct_String_Representation(int step, int offset, string expected) |
|||
{ |
|||
var target = default(Selector).NthLastChild(step, offset); |
|||
|
|||
Assert.Equal(expected, target.ToString()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthLastChild(2, 0); |
|||
|
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel_With_Offset() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthLastChild(2, 1); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel_With_Negative_Offset() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthLastChild(4, -1); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel_With_Singular_Step() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthLastChild(1, 2); |
|||
|
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel_With_Singular_Step_With_Negative_Offset() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthLastChild(1, -2); |
|||
|
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel_With_Zero_Step_With_Offset() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthLastChild(0, 2); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Doesnt_Match_Control_In_Panel_With_Zero_Step_With_Negative_Offset() |
|||
{ |
|||
Border b1, b2, b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Border(), |
|||
b4 = new Border() |
|||
}); |
|||
|
|||
var target = default(Selector).NthLastChild(0, -2); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Match_Control_In_Panel_With_Previous_Selector() |
|||
{ |
|||
Border b1, b2; |
|||
Button b3, b4; |
|||
var panel = new StackPanel(); |
|||
panel.Children.AddRange(new Control[] |
|||
{ |
|||
b1 = new Border(), |
|||
b2 = new Border(), |
|||
b3 = new Button(), |
|||
b4 = new Button() |
|||
}); |
|||
|
|||
var previous = default(Selector).OfType<Border>(); |
|||
var target = previous.NthLastChild(2, 0); |
|||
|
|||
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(b1).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b2).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(b3).Result); |
|||
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(b4).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Nth_Child_Doesnt_Match_Control_Out_Of_Panel_Parent() |
|||
{ |
|||
Border b1; |
|||
var contentControl = new ContentControl(); |
|||
contentControl.Content = b1 = new Border(); |
|||
|
|||
var target = default(Selector).NthLastChild(1, 0); |
|||
|
|||
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Returns_Correct_TargetType() |
|||
{ |
|||
var target = new NthLastChildSelector(default(Selector).OfType<Control1>(), 1, 0); |
|||
|
|||
Assert.Equal(typeof(Control1), target.TargetType); |
|||
} |
|||
|
|||
public class Control1 : Control |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue