Browse Source

Complete

pull/6381/head
Max Katz 5 years ago
parent
commit
031e8ac2f0
  1. 5
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  2. 7
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  3. 7
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  4. 3
      src/Avalonia.Styling/LogicalTree/ChildIndexChangedEventArgs.cs
  5. 18
      src/Avalonia.Styling/LogicalTree/IChildIndexProvider.cs
  6. 7
      src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs
  7. 12
      src/Avalonia.Styling/Styling/NthChildSelector.cs
  8. 12
      src/Avalonia.Styling/Styling/NthLastChildSelector.cs
  9. 6
      src/Avalonia.Styling/Styling/Selectors.cs
  10. 71
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  11. 4
      tests/Avalonia.Styling.UnitTests/SelectorTests_NthChild.cs
  12. 4
      tests/Avalonia.Styling.UnitTests/SelectorTests_NthLastChild.cs

5
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@ -12,6 +12,11 @@
</Style>
<Style Selector="ItemsRepeater TextBlock:nth-child(5n+3)">
<Setter Property="Foreground" Value="Red" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="ItemsRepeater TextBlock:nth-last-child(5n+4)">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</UserControl.Styles>
<UserControl.Resources>

7
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -3,8 +3,13 @@
x:Class="ControlCatalog.Pages.ListBoxPage">
<DockPanel>
<DockPanel.Styles>
<Style Selector="ListBox ListBoxItem:nth-child(2n)">
<Style Selector="ListBox ListBoxItem:nth-child(5n+3)">
<Setter Property="Foreground" Value="Red" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="ListBox ListBoxItem:nth-last-child(5n+4)">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</DockPanel.Styles>
<StackPanel DockPanel.Dock="Top" Margin="4">

7
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
@ -133,7 +132,7 @@ namespace Avalonia.Controls.Presenters
protected bool IsHosted => TemplatedParent is IItemsPresenterHost;
int? IChildIndexProvider.TotalCount => Items.TryGetCountFast(out var count) ? count : null;
int? IChildIndexProvider.TotalCount => Items.TryGetCountFast(out var count) ? count : (int?)null;
event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
@ -161,6 +160,8 @@ namespace Avalonia.Controls.Presenters
if (Panel != null)
{
ItemsChanged(e);
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs());
}
}
@ -192,7 +193,7 @@ namespace Avalonia.Controls.Presenters
{
for (var i = 0; i < e.Containers.Count; i++)
{
_childIndexChanged?.Invoke(sender, new ChildIndexChangedEventArgs(e.Containers[i].ContainerControl));
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(e.Containers[i].ContainerControl));
}
}

3
src/Avalonia.Styling/LogicalTree/ChildIndexChangedEventArgs.cs

@ -3,6 +3,9 @@ using System;
namespace Avalonia.LogicalTree
{
/// <summary>
/// Event args for <see cref="IChildIndexProvider.ChildIndexChanged"/> event.
/// </summary>
public class ChildIndexChangedEventArgs : EventArgs
{
public ChildIndexChangedEventArgs()

18
src/Avalonia.Styling/LogicalTree/IChildIndexProvider.cs

@ -3,12 +3,30 @@ using System;
namespace Avalonia.LogicalTree
{
/// <summary>
/// Child's index and total count information provider used by list-controls (ListBox, StackPanel, etc.)
/// </summary>
/// <remarks>
/// Used by nth-child and nth-last-child selectors.
/// </remarks>
public interface IChildIndexProvider
{
/// <summary>
/// Gets child's actual index in order of the original source.
/// </summary>
/// <param name="child">Logical child.</param>
/// <returns>Index or -1 if child was not found.</returns>
int GetChildIndex(ILogical child);
/// <summary>
/// Total children count or null if source is infinite.
/// Some Avalonia features might not work if <see cref="TotalCount"/> is null, for instance: nth-last-child selector.
/// </summary>
int? TotalCount { get; }
/// <summary>
/// Notifies subscriber when child's index or total count was changed.
/// </summary>
event EventHandler<ChildIndexChangedEventArgs>? ChildIndexChanged;
}
}

7
src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs

@ -44,7 +44,12 @@ namespace Avalonia.Styling.Activators
private void ChildIndexChanged(object sender, ChildIndexChangedEventArgs e)
{
if (e.Child is null
// Run matching again if:
// 1. Selector is reversed, so other item insertion/deletion might affect total count without changing subscribed item index.
// 2. e.Child is null, when all children indeces were changed.
// 3. Subscribed child index was changed.
if (_reversed
|| e.Child is null
|| e.Child == _control)
{
PublishNext(IsMatching());

12
src/Avalonia.Styling/Styling/NthChildSelector.cs

@ -7,6 +7,12 @@ using Avalonia.Styling.Activators;
namespace Avalonia.Styling
{
/// <summary>
/// The :nth-child() pseudo-class matches elements based on their position in a group of siblings.
/// </summary>
/// <remarks>
/// Element indices are 1-based.
/// </remarks>
public class NthChildSelector : Selector
{
private const string NthChildSelectorName = "nth-child";
@ -22,6 +28,12 @@ namespace Avalonia.Styling
_reversed = reversed;
}
/// <summary>
/// Creates an instance of <see cref="NthChildSelector"/>
/// </summary>
/// <param name="previous">Previous selector.</param>
/// <param name="step">Position step.</param>
/// <param name="offset">Initial index offset.</param>
public NthChildSelector(Selector? previous, int step, int offset)
: this(previous, step, offset, false)
{

12
src/Avalonia.Styling/Styling/NthLastChildSelector.cs

@ -2,8 +2,20 @@
namespace Avalonia.Styling
{
/// <summary>
/// The :nth-child() pseudo-class matches elements based on their position among a group of siblings, counting from the end.
/// </summary>
/// <remarks>
/// Element indices are 1-based.
/// </remarks>
public class NthLastChildSelector : NthChildSelector
{
/// <summary>
/// Creates an instance of <see cref="NthLastChildSelector"/>
/// </summary>
/// <param name="previous">Previous selector.</param>
/// <param name="step">Position step.</param>
/// <param name="offset">Initial index offset, counting from the end.</param>
public NthLastChildSelector(Selector? previous, int step, int offset) : base(previous, step, offset, true)
{
}

6
src/Avalonia.Styling/Styling/Selectors.cs

@ -123,11 +123,17 @@ namespace Avalonia.Styling
return new NotSelector(previous, argument);
}
/// <inheritdoc cref="NthChildSelector"/>
/// <inheritdoc cref="NthChildSelector(Selector?, int, int)"/>
/// <returns>The selector.</returns>
public static Selector NthChild(this Selector previous, int step, int offset)
{
return new NthChildSelector(previous, step, offset);
}
/// <inheritdoc cref="NthLastChildSelector"/>
/// <inheritdoc cref="NthLastChildSelector(Selector?, int, int)"/>
/// <returns>The selector.</returns>
public static Selector NthLastChild(this Selector previous, int step, int offset)
{
return new NthLastChildSelector(previous, step, offset);

71
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
@ -336,6 +337,45 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
}
}
[Fact]
public void Style_Can_Use_NthLastChild_Selector_After_Reoder()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Border:nth-last-child(2n)'>
<Setter Property='Background' Value='Red'/>
</Style>
</Window.Styles>
<StackPanel x:Name='parent'>
<Border x:Name='b1' />
<Border x:Name='b2' />
</StackPanel>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var parent = window.FindControl<StackPanel>("parent");
var b1 = window.FindControl<Border>("b1");
var b2 = window.FindControl<Border>("b2");
Assert.Equal(Brushes.Red, b1.Background);
Assert.Null(b2.Background);
parent.Children.Remove(b1);
Assert.Null(b1.Background);
Assert.Null(b2.Background);
parent.Children.Add(b1);
Assert.Null(b1.Background);
Assert.Equal(Brushes.Red, b2.Background);
}
}
[Fact]
public void Style_Can_Use_NthChild_Selector_With_ListBox()
@ -364,25 +404,18 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
window.Show();
var items = list.Presenter.Panel.Children.Cast<ListBoxItem>();
ListBoxItem At(int index) => items.ElementAt(index);
IEnumerable<IBrush> GetColors() => list.Presenter.Panel.Children.Cast<ListBoxItem>().Select(t => t.Background);
Assert.Equal(Brushes.Transparent, At(0).Background);
Assert.Equal(Brushes.Green, At(1).Background);
Assert.Equal(Brushes.Transparent, At(2).Background);
Assert.Equal(new[] { Brushes.Transparent, Brushes.Green, Brushes.Transparent }, GetColors());
collection.Remove(Brushes.Green);
Assert.Equal(Brushes.Transparent, At(0).Background);
Assert.Equal(Brushes.Blue, At(1).Background);
Assert.Equal(new[] { Brushes.Transparent, Brushes.Blue }, GetColors());
collection.Add(Brushes.Violet);
collection.Add(Brushes.Black);
Assert.Equal(Brushes.Transparent, At(0).Background);
Assert.Equal(Brushes.Blue, At(1).Background);
Assert.Equal(Brushes.Transparent, At(2).Background);
Assert.Equal(Brushes.Black, At(3).Background);
Assert.Equal(new[] { Brushes.Transparent, Brushes.Blue, Brushes.Transparent, Brushes.Black }, GetColors());
}
}
@ -415,25 +448,19 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
window.Show();
var items = list.Children;
TextBlock At(int index) => (TextBlock)list.GetOrCreateElement(index);
IEnumerable<IBrush> GetColors() => Enumerable.Range(0, list.ItemsSourceView.Count)
.Select(t => (list.GetOrCreateElement(t) as TextBlock)!.Foreground);
Assert.Equal(Brushes.Transparent, At(0).Foreground);
Assert.Equal(Brushes.Green, At(1).Foreground);
Assert.Equal(Brushes.Transparent, At(2).Foreground);
Assert.Equal(new[] { Brushes.Transparent, Brushes.Green, Brushes.Transparent }, GetColors());
collection.Remove(Brushes.Green);
Assert.Equal(Brushes.Transparent, At(0).Foreground);
Assert.Equal(Brushes.Blue, At(1).Foreground);
Assert.Equal(new[] { Brushes.Transparent, Brushes.Blue }, GetColors());
collection.Add(Brushes.Violet);
collection.Add(Brushes.Black);
Assert.Equal(Brushes.Transparent, At(0).Foreground);
Assert.Equal(Brushes.Blue, At(1).Foreground);
Assert.Equal(Brushes.Transparent, At(2).Foreground);
Assert.Equal(Brushes.Black, At(3).Foreground);
Assert.Equal(new[] { Brushes.Transparent, Brushes.Blue, Brushes.Transparent, Brushes.Black }, GetColors());
}
}

4
tests/Avalonia.Styling.UnitTests/SelectorTests_NthChild.cs

@ -196,7 +196,7 @@ namespace Avalonia.Styling.UnitTests
}
[Fact]
public async Task Nth_Child_Doesnt_Match_Control_Out_Of_Panel_Parent()
public void Nth_Child_Doesnt_Match_Control_Out_Of_Panel_Parent()
{
Border b1;
var contentControl = new ContentControl();
@ -204,7 +204,7 @@ namespace Avalonia.Styling.UnitTests
var target = default(Selector).NthChild(1, 0);
Assert.False(await target.Match(b1).Activator!.Take(1));
Assert.Equal(SelectorMatch.NeverThisInstance, target.Match(b1));
}
[Fact]

4
tests/Avalonia.Styling.UnitTests/SelectorTests_NthLastChild.cs

@ -195,7 +195,7 @@ namespace Avalonia.Styling.UnitTests
}
[Fact]
public async Task Nth_Child_Doesnt_Match_Control_Out_Of_Panel_Parent()
public void Nth_Child_Doesnt_Match_Control_Out_Of_Panel_Parent()
{
Border b1;
var contentControl = new ContentControl();
@ -203,7 +203,7 @@ namespace Avalonia.Styling.UnitTests
var target = default(Selector).NthLastChild(1, 0);
Assert.False(await target.Match(b1).Activator!.Take(1));
Assert.Equal(SelectorMatch.NeverThisInstance, target.Match(b1));
}
[Fact]

Loading…
Cancel
Save