Browse Source

Update IChildIndexProvider interface with ChildIndexChanged and implement in on items controls

pull/6381/head
Max Katz 5 years ago
parent
commit
e5ca5c38e8
  1. 19
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  2. 39
      src/Avalonia.Controls/ItemsControl.cs
  3. 21
      src/Avalonia.Controls/Panel.cs
  4. 43
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  5. 29
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  6. 23
      src/Avalonia.Styling/LogicalTree/ChildIndexChangedEventArgs.cs
  7. 14
      src/Avalonia.Styling/LogicalTree/IChildIndexProvider.cs
  8. 56
      src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs
  9. 70
      src/Avalonia.Styling/Styling/NthChildSelector.cs
  10. 11
      src/Avalonia.Styling/Styling/NthLastChildSelector.cs
  11. 119
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  12. 85
      tests/Avalonia.Styling.UnitTests/SelectorTests_NthChild.cs
  13. 84
      tests/Avalonia.Styling.UnitTests/SelectorTests_NthLastChild.cs
  14. 7
      tests/Avalonia.Styling.UnitTests/StyleActivatorExtensions.cs

19
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@ -1,17 +1,28 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ItemsRepeaterPage">
<UserControl.Styles>
<Style Selector="ItemsRepeater TextBlock.oddTemplate">
<Setter Property="Background" Value="Yellow" />
<Setter Property="Foreground" Value="Black" />
</Style>
<Style Selector="ItemsRepeater TextBlock.evenTemplate">
<Setter Property="Background" Value="Wheat" />
<Setter Property="Foreground" Value="Black" />
</Style>
<Style Selector="ItemsRepeater TextBlock:nth-child(5n+3)">
<Setter Property="Foreground" Value="Red" />
</Style>
</UserControl.Styles>
<UserControl.Resources>
<RecyclePool x:Key="RecyclePool" />
<DataTemplate x:Key="odd">
<TextBlock Background="Yellow"
Foreground="Black"
<TextBlock Classes="oddTemplate"
Height="{Binding Height}"
Text="{Binding Text}"/>
</DataTemplate>
<DataTemplate x:Key="even">
<TextBlock Background="Wheat"
Foreground="Black"
<TextBlock Classes="evenTemplate"
Height="{Binding Height}"
Text="{Binding Text}"/>
</DataTemplate>

39
src/Avalonia.Controls/ItemsControl.cs

@ -57,6 +57,7 @@ namespace Avalonia.Controls
private IEnumerable _items = new AvaloniaList<object>();
private int _itemCount;
private IItemContainerGenerator _itemContainerGenerator;
private EventHandler<ChildIndexChangedEventArgs> _childIndexChanged;
/// <summary>
/// Initializes static members of the <see cref="ItemsControl"/> class.
@ -146,11 +147,30 @@ namespace Avalonia.Controls
protected set;
}
int? IChildIndexProvider.TotalCount => (Presenter as IChildIndexProvider)?.TotalCount ?? ItemCount;
event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}
/// <inheritdoc/>
void IItemsPresenterHost.RegisterItemsPresenter(IItemsPresenter presenter)
{
if (Presenter is IChildIndexProvider oldInnerProvider)
{
oldInnerProvider.ChildIndexChanged -= PresenterChildIndexChanged;
}
Presenter = presenter;
ItemContainerGenerator.Clear();
if (Presenter is IChildIndexProvider innerProvider)
{
innerProvider.ChildIndexChanged += PresenterChildIndexChanged;
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs());
}
}
void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
@ -508,20 +528,15 @@ namespace Avalonia.Controls
return null;
}
(int Index, int? TotalCount) IChildIndexProvider.GetChildIndex(ILogical child)
private void PresenterChildIndexChanged(object sender, ChildIndexChangedEventArgs e)
{
if (Presenter is IChildIndexProvider innerProvider)
{
return innerProvider.GetChildIndex(child);
}
if (child is IControl control)
{
var index = ItemContainerGenerator.IndexFromContainer(control);
return (index, ItemCount);
}
_childIndexChanged?.Invoke(this, e);
}
return (-1, ItemCount);
int IChildIndexProvider.GetChildIndex(ILogical child)
{
return Presenter is IChildIndexProvider innerProvider
? innerProvider.GetChildIndex(child) : -1;
}
}
}

21
src/Avalonia.Controls/Panel.cs

@ -34,6 +34,8 @@ namespace Avalonia.Controls
AffectsRender<Panel>(BackgroundProperty);
}
private EventHandler<ChildIndexChangedEventArgs> _childIndexChanged;
/// <summary>
/// Initializes a new instance of the <see cref="Panel"/> class.
/// </summary>
@ -57,6 +59,14 @@ namespace Avalonia.Controls
set { SetValue(BackgroundProperty, value); }
}
int? IChildIndexProvider.TotalCount => Children.Count;
event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}
/// <summary>
/// Renders the visual to a <see cref="DrawingContext"/>.
/// </summary>
@ -141,6 +151,7 @@ namespace Avalonia.Controls
throw new NotSupportedException();
}
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs());
InvalidateMeasureOnChildrenChanged();
}
@ -165,15 +176,9 @@ namespace Avalonia.Controls
panel?.InvalidateMeasure();
}
(int Index, int? TotalCount) IChildIndexProvider.GetChildIndex(ILogical child)
int IChildIndexProvider.GetChildIndex(ILogical child)
{
if (child is IControl control)
{
var index = Children.IndexOf(control);
return (index, Children.Count);
}
return (-1, Children.Count);
return child is IControl control ? Children.IndexOf(control) : -1;
}
}
}

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

@ -1,6 +1,8 @@
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Templates;
@ -37,6 +39,7 @@ namespace Avalonia.Controls.Presenters
private IDisposable _itemsSubscription;
private bool _createdPanel;
private IItemContainerGenerator _generator;
private EventHandler<ChildIndexChangedEventArgs> _childIndexChanged;
/// <summary>
/// Initializes static members of the <see cref="ItemsPresenter"/> class.
@ -130,6 +133,14 @@ namespace Avalonia.Controls.Presenters
protected bool IsHosted => TemplatedParent is IItemsPresenterHost;
int? IChildIndexProvider.TotalCount => Items.TryGetCountFast(out var count) ? count : null;
event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}
/// <inheritdoc/>
public override sealed void ApplyTemplate()
{
@ -170,9 +181,21 @@ namespace Avalonia.Controls.Presenters
result.ItemTemplate = ItemTemplate;
}
result.Materialized += ContainerActionHandler;
result.Dematerialized += ContainerActionHandler;
result.Recycled += ContainerActionHandler;
return result;
}
private void ContainerActionHandler(object sender, ItemContainerEventArgs e)
{
for (var i = 0; i < e.Containers.Count; i++)
{
_childIndexChanged?.Invoke(sender, new ChildIndexChangedEventArgs(e.Containers[i].ContainerControl));
}
}
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
@ -250,26 +273,16 @@ namespace Avalonia.Controls.Presenters
(e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this);
}
(int Index, int? TotalCount) IChildIndexProvider.GetChildIndex(ILogical child)
int IChildIndexProvider.GetChildIndex(ILogical child)
{
int? totalCount = null;
if (Items.TryGetCountFast(out var count))
{
totalCount = count;
}
if (child is IControl control)
if (child is IControl control && ItemContainerGenerator is { } generator)
{
var index = ItemContainerGenerator.IndexFromContainer(control);
if (ItemContainerGenerator is { } generator)
{
var index = ItemContainerGenerator.IndexFromContainer(control);
return (index, totalCount);
}
return index;
}
return (-1, totalCount);
return -1;
}
}
}

29
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -6,10 +6,13 @@
using System;
using System.Collections;
using System.Collections.Specialized;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Styling;
using Avalonia.Utilities;
using Avalonia.VisualTree;
@ -19,7 +22,7 @@ namespace Avalonia.Controls
/// Represents a data-driven collection control that incorporates a flexible layout system,
/// custom views, and virtualization.
/// </summary>
public class ItemsRepeater : Panel
public class ItemsRepeater : Panel, IChildIndexProvider
{
/// <summary>
/// Defines the <see cref="HorizontalCacheLength"/> property.
@ -61,8 +64,9 @@ namespace Avalonia.Controls
private readonly ViewportManager _viewportManager;
private IEnumerable _items;
private VirtualizingLayoutContext _layoutContext;
private NotifyCollectionChangedEventArgs _processingItemsSourceChange;
private EventHandler<ChildIndexChangedEventArgs> _childIndexChanged;
private bool _isLayoutInProgress;
private NotifyCollectionChangedEventArgs _processingItemsSourceChange;
private ItemsRepeaterElementPreparedEventArgs _elementPreparedArgs;
private ItemsRepeaterElementClearingEventArgs _elementClearingArgs;
private ItemsRepeaterElementIndexChangedEventArgs _elementIndexChangedArgs;
@ -163,6 +167,21 @@ namespace Avalonia.Controls
}
}
int? IChildIndexProvider.TotalCount => ItemsSourceView.Count;
event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}
int IChildIndexProvider.GetChildIndex(ILogical child)
{
return child is IControl control
? GetElementIndex(control)
: -1;
}
/// <summary>
/// Occurs each time an element is cleared and made available to be re-used.
/// </summary>
@ -545,6 +564,8 @@ namespace Avalonia.Controls
ElementPrepared(this, _elementPreparedArgs);
}
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(element));
}
internal void OnElementClearing(IControl element)
@ -562,6 +583,8 @@ namespace Avalonia.Controls
ElementClearing(this, _elementClearingArgs);
}
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(element));
}
internal void OnElementIndexChanged(IControl element, int oldIndex, int newIndex)
@ -579,6 +602,8 @@ namespace Avalonia.Controls
ElementIndexChanged(this, _elementIndexChangedArgs);
}
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(element));
}
private void OnDataSourcePropertyChanged(ItemsSourceView oldValue, ItemsSourceView newValue)

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

@ -0,0 +1,23 @@
#nullable enable
using System;
namespace Avalonia.LogicalTree
{
public class ChildIndexChangedEventArgs : EventArgs
{
public ChildIndexChangedEventArgs()
{
}
public ChildIndexChangedEventArgs(ILogical child)
{
Child = child;
}
/// <summary>
/// Logical child which index was changed.
/// If null, all children should be reset.
/// </summary>
public ILogical? Child { get; }
}
}

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

@ -0,0 +1,14 @@
#nullable enable
using System;
namespace Avalonia.LogicalTree
{
public interface IChildIndexProvider
{
int GetChildIndex(ILogical child);
int? TotalCount { get; }
event EventHandler<ChildIndexChangedEventArgs>? ChildIndexChanged;
}
}

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

@ -0,0 +1,56 @@
#nullable enable
using System;
using Avalonia.LogicalTree;
namespace Avalonia.Styling.Activators
{
/// <summary>
/// An <see cref="IStyleActivator"/> which is active when control's index was changed.
/// </summary>
internal sealed class NthChildActivator : StyleActivatorBase
{
private readonly ILogical _control;
private readonly IChildIndexProvider _provider;
private readonly int _step;
private readonly int _offset;
private readonly bool _reversed;
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChangedHandler;
public NthChildActivator(
ILogical control,
IChildIndexProvider provider,
int step, int offset, bool reversed)
{
_control = control;
_provider = provider;
_step = step;
_offset = offset;
_reversed = reversed;
}
private EventHandler<ChildIndexChangedEventArgs> ChildIndexChangedHandler => _childIndexChangedHandler ??= ChildIndexChanged;
protected override void Initialize()
{
PublishNext(IsMatching());
_provider.ChildIndexChanged += ChildIndexChangedHandler;
}
protected override void Deinitialize()
{
_provider.ChildIndexChanged -= ChildIndexChangedHandler;
}
private void ChildIndexChanged(object sender, ChildIndexChangedEventArgs e)
{
if (e.Child is null
|| e.Child == _control)
{
PublishNext(IsMatching());
}
}
private bool IsMatching() => NthChildSelector.Evaluate(_control, _provider, _step, _offset, _reversed).IsMatch;
}
}

70
src/Avalonia.Styling/Styling/NthChildSelector.cs

@ -3,21 +3,10 @@ using System;
using System.Text;
using Avalonia.LogicalTree;
using Avalonia.Styling.Activators;
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";
@ -55,42 +44,49 @@ namespace Avalonia.Styling
if (controlParent is IChildIndexProvider childIndexProvider)
{
var (index, totalCount) = childIndexProvider.GetChildIndex(logical);
if (index < 0)
{
return SelectorMatch.NeverThisInstance;
}
return subscribe
? new SelectorMatch(new NthChildActivator(logical, childIndexProvider, Step, Offset, _reversed))
: Evaluate(logical, childIndexProvider, Step, Offset, _reversed);
}
else
{
return SelectorMatch.NeverThisInstance;
}
}
internal static SelectorMatch Evaluate(
ILogical logical, IChildIndexProvider childIndexProvider,
int step, int offset, bool reversed)
{
var index = childIndexProvider.GetChildIndex(logical);
if (index < 0)
{
return SelectorMatch.NeverThisInstance;
}
if (_reversed)
if (reversed)
{
if (childIndexProvider.TotalCount is int totalCountValue)
{
if (totalCount is int totalCountValue)
{
index = totalCountValue - index;
}
else
{
return SelectorMatch.NeverThisInstance;
}
index = totalCountValue - index;
}
else
{
// nth child index is 1-based
index += 1;
return SelectorMatch.NeverThisInstance;
}
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;
// 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;
}
protected override Selector? MovePrevious() => _previous;

11
src/Avalonia.Styling/Styling/NthLastChildSelector.cs

@ -0,0 +1,11 @@
#nullable enable
namespace Avalonia.Styling
{
public class NthLastChildSelector : NthChildSelector
{
public NthLastChildSelector(Selector? previous, int step, int offset) : base(previous, step, offset, true)
{
}
}
}

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

@ -1,3 +1,6 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
using Avalonia.Controls;
using Avalonia.Markup.Data;
@ -289,7 +292,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var b1 = window.FindControl<Border>("b1");
var b2 = window.FindControl<Border>("b2");
Assert.Equal(Colors.Red, ((ISolidColorBrush)b1.Background).Color);
Assert.Equal(Brushes.Red, b1.Background);
Assert.Null(b2.Background);
}
}
@ -303,7 +306,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Border:nth-child(2n+1)'>
<Style Selector='Border:nth-child(2n)'>
<Setter Property='Background' Value='Red'/>
</Style>
</Window.Styles>
@ -318,11 +321,119 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var b1 = window.FindControl<Border>("b1");
var b2 = window.FindControl<Border>("b2");
Assert.Null(b1.Background);
Assert.Equal(Brushes.Red, b2.Background);
parent.Children.Remove(b1);
parent.Children.Add(b1);
Assert.Null(b1.Background);
Assert.Equal(Colors.Red, ((ISolidColorBrush)b2.Background).Color);
Assert.Null(b2.Background);
parent.Children.Add(b1);
Assert.Equal(Brushes.Red, b1.Background);
Assert.Null(b2.Background);
}
}
[Fact]
public void Style_Can_Use_NthChild_Selector_With_ListBox()
{
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='ListBoxItem:nth-child(2n)'>
<Setter Property='Background' Value='{Binding}'/>
</Style>
</Window.Styles>
<ListBox x:Name='list' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var collection = new ObservableCollection<IBrush>()
{
Brushes.Red, Brushes.Green, Brushes.Blue
};
var list = window.FindControl<ListBox>("list");
list.VirtualizationMode = ItemVirtualizationMode.Simple;
list.Items = collection;
window.Show();
var items = list.Presenter.Panel.Children.Cast<ListBoxItem>();
ListBoxItem At(int index) => items.ElementAt(index);
Assert.Equal(Brushes.Transparent, At(0).Background);
Assert.Equal(Brushes.Green, At(1).Background);
Assert.Equal(Brushes.Transparent, At(2).Background);
collection.Remove(Brushes.Green);
Assert.Equal(Brushes.Transparent, At(0).Background);
Assert.Equal(Brushes.Blue, At(1).Background);
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);
}
}
[Fact]
public void Style_Can_Use_NthChild_Selector_With_ItemsRepeater()
{
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='TextBlock'>
<Setter Property='Foreground' Value='Transparent'/>
</Style>
<Style Selector='TextBlock:nth-child(2n)'>
<Setter Property='Foreground' Value='{Binding}'/>
</Style>
</Window.Styles>
<ItemsRepeater x:Name='list' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var collection = new ObservableCollection<IBrush>()
{
Brushes.Red, Brushes.Green, Brushes.Blue
};
var list = window.FindControl<ItemsRepeater>("list");
list.Items = collection;
window.Show();
var items = list.Children;
TextBlock At(int index) => (TextBlock)list.GetOrCreateElement(index);
Assert.Equal(Brushes.Transparent, At(0).Foreground);
Assert.Equal(Brushes.Green, At(1).Foreground);
Assert.Equal(Brushes.Transparent, At(2).Foreground);
collection.Remove(Brushes.Green);
Assert.Equal(Brushes.Transparent, At(0).Foreground);
Assert.Equal(Brushes.Blue, At(1).Foreground);
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);
}
}

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

@ -1,4 +1,7 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Xunit;
namespace Avalonia.Styling.UnitTests
@ -21,7 +24,7 @@ namespace Avalonia.Styling.UnitTests
}
[Fact]
public void Nth_Child_Match_Control_In_Panel()
public async Task Nth_Child_Match_Control_In_Panel()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -35,14 +38,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.False(await target.Match(b1).Activator!.Take(1));
Assert.True(await target.Match(b2).Activator!.Take(1));
Assert.False(await target.Match(b3).Activator!.Take(1));
Assert.True(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Match_Control_In_Panel_With_Offset()
public async Task Nth_Child_Match_Control_In_Panel_With_Offset()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -56,14 +59,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.True(await target.Match(b1).Activator!.Take(1));
Assert.False(await target.Match(b2).Activator!.Take(1));
Assert.True(await target.Match(b3).Activator!.Take(1));
Assert.False(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Match_Control_In_Panel_With_Negative_Offset()
public async Task Nth_Child_Match_Control_In_Panel_With_Negative_Offset()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -77,14 +80,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.False(await target.Match(b1).Activator!.Take(1));
Assert.False(await target.Match(b2).Activator!.Take(1));
Assert.True(await target.Match(b3).Activator!.Take(1));
Assert.False(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Match_Control_In_Panel_With_Singular_Step()
public async Task Nth_Child_Match_Control_In_Panel_With_Singular_Step()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -98,14 +101,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.False(await target.Match(b1).Activator!.Take(1));
Assert.True(await target.Match(b2).Activator!.Take(1));
Assert.True(await target.Match(b3).Activator!.Take(1));
Assert.True(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Match_Control_In_Panel_With_Singular_Step_With_Negative_Offset()
public async Task Nth_Child_Match_Control_In_Panel_With_Singular_Step_With_Negative_Offset()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -119,14 +122,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.True(await target.Match(b1).Activator!.Take(1));
Assert.True(await target.Match(b2).Activator!.Take(1));
Assert.True(await target.Match(b3).Activator!.Take(1));
Assert.True(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Match_Control_In_Panel_With_Zero_Step_With_Offset()
public async Task Nth_Child_Match_Control_In_Panel_With_Zero_Step_With_Offset()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -140,14 +143,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.False(await target.Match(b1).Activator!.Take(1));
Assert.True(await target.Match(b2).Activator!.Take(1));
Assert.False(await target.Match(b3).Activator!.Take(1));
Assert.False(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Doesnt_Match_Control_In_Panel_With_Zero_Step_With_Negative_Offset()
public async Task Nth_Child_Doesnt_Match_Control_In_Panel_With_Zero_Step_With_Negative_Offset()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -161,14 +164,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.False(await target.Match(b1).Activator!.Take(1));
Assert.False(await target.Match(b2).Activator!.Take(1));
Assert.False(await target.Match(b3).Activator!.Take(1));
Assert.False(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Match_Control_In_Panel_With_Previous_Selector()
public async Task Nth_Child_Match_Control_In_Panel_With_Previous_Selector()
{
Border b1, b2;
Button b3, b4;
@ -184,14 +187,16 @@ namespace Avalonia.Styling.UnitTests
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.False(await target.Match(b1).Activator!.Take(1));
Assert.True(await target.Match(b2).Activator!.Take(1));
Assert.Null(target.Match(b3).Activator);
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(b3).Result);
Assert.Null(target.Match(b4).Activator);
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(b4).Result);
}
[Fact]
public void Nth_Child_Doesnt_Match_Control_Out_Of_Panel_Parent()
public async Task Nth_Child_Doesnt_Match_Control_Out_Of_Panel_Parent()
{
Border b1;
var contentControl = new ContentControl();
@ -199,7 +204,7 @@ namespace Avalonia.Styling.UnitTests
var target = default(Selector).NthChild(1, 0);
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result);
Assert.False(await target.Match(b1).Activator!.Take(1));
}
[Fact]

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

@ -1,3 +1,5 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Xunit;
@ -21,7 +23,7 @@ namespace Avalonia.Styling.UnitTests
}
[Fact]
public void Nth_Child_Match_Control_In_Panel()
public async Task Nth_Child_Match_Control_In_Panel()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -35,14 +37,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.True(await target.Match(b1).Activator!.Take(1));
Assert.False(await target.Match(b2).Activator!.Take(1));
Assert.True(await target.Match(b3).Activator!.Take(1));
Assert.False(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Match_Control_In_Panel_With_Offset()
public async Task Nth_Child_Match_Control_In_Panel_With_Offset()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -56,14 +58,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.False(await target.Match(b1).Activator!.Take(1));
Assert.True(await target.Match(b2).Activator!.Take(1));
Assert.False(await target.Match(b3).Activator!.Take(1));
Assert.True(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Match_Control_In_Panel_With_Negative_Offset()
public async Task Nth_Child_Match_Control_In_Panel_With_Negative_Offset()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -77,14 +79,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.False(await target.Match(b1).Activator!.Take(1));
Assert.True(await target.Match(b2).Activator!.Take(1));
Assert.False(await target.Match(b3).Activator!.Take(1));
Assert.False(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Match_Control_In_Panel_With_Singular_Step()
public async Task Nth_Child_Match_Control_In_Panel_With_Singular_Step()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -98,14 +100,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.True(await target.Match(b1).Activator!.Take(1));
Assert.True(await target.Match(b2).Activator!.Take(1));
Assert.True(await target.Match(b3).Activator!.Take(1));
Assert.False(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Match_Control_In_Panel_With_Singular_Step_With_Negative_Offset()
public async Task Nth_Child_Match_Control_In_Panel_With_Singular_Step_With_Negative_Offset()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -119,14 +121,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.True(await target.Match(b1).Activator!.Take(1));
Assert.True(await target.Match(b2).Activator!.Take(1));
Assert.True(await target.Match(b3).Activator!.Take(1));
Assert.True(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Match_Control_In_Panel_With_Zero_Step_With_Offset()
public async Task Nth_Child_Match_Control_In_Panel_With_Zero_Step_With_Offset()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -140,14 +142,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.False(await target.Match(b1).Activator!.Take(1));
Assert.False(await target.Match(b2).Activator!.Take(1));
Assert.True(await target.Match(b3).Activator!.Take(1));
Assert.False(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Doesnt_Match_Control_In_Panel_With_Zero_Step_With_Negative_Offset()
public async Task Nth_Child_Doesnt_Match_Control_In_Panel_With_Zero_Step_With_Negative_Offset()
{
Border b1, b2, b3, b4;
var panel = new StackPanel();
@ -161,14 +163,14 @@ namespace Avalonia.Styling.UnitTests
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);
Assert.False(await target.Match(b1).Activator!.Take(1));
Assert.False(await target.Match(b2).Activator!.Take(1));
Assert.False(await target.Match(b3).Activator!.Take(1));
Assert.False(await target.Match(b4).Activator!.Take(1));
}
[Fact]
public void Nth_Child_Match_Control_In_Panel_With_Previous_Selector()
public async Task Nth_Child_Match_Control_In_Panel_With_Previous_Selector()
{
Border b1, b2;
Button b3, b4;
@ -184,14 +186,16 @@ namespace Avalonia.Styling.UnitTests
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.True(await target.Match(b1).Activator!.Take(1));
Assert.False(await target.Match(b2).Activator!.Take(1));
Assert.Null(target.Match(b3).Activator);
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(b3).Result);
Assert.Null(target.Match(b4).Activator);
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(b4).Result);
}
[Fact]
public void Nth_Child_Doesnt_Match_Control_Out_Of_Panel_Parent()
public async Task Nth_Child_Doesnt_Match_Control_Out_Of_Panel_Parent()
{
Border b1;
var contentControl = new ContentControl();
@ -199,7 +203,7 @@ namespace Avalonia.Styling.UnitTests
var target = default(Selector).NthLastChild(1, 0);
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(b1).Result);
Assert.False(await target.Match(b1).Activator!.Take(1));
}
[Fact]

7
tests/Avalonia.Styling.UnitTests/StyleActivatorExtensions.cs

@ -20,13 +20,17 @@ namespace Avalonia.Styling.UnitTests
public static IObservable<bool> ToObservable(this IStyleActivator activator)
{
if (activator == null)
{
throw new ArgumentNullException(nameof(activator));
}
return new ObservableAdapter(activator);
}
private class ObservableAdapter : LightweightObservableBase<bool>, IStyleActivatorSink
{
private readonly IStyleActivator _source;
private bool _value;
public ObservableAdapter(IStyleActivator source) => _source = source;
protected override void Initialize() => _source.Subscribe(this);
@ -34,7 +38,6 @@ namespace Avalonia.Styling.UnitTests
void IStyleActivatorSink.OnNext(bool value, int tag)
{
_value = value;
PublishNext(value);
}
}

Loading…
Cancel
Save