10 changed files with 993 additions and 1391 deletions
@ -1,276 +0,0 @@ |
|||
using System.Collections.Specialized; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Animation; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Utils; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
/// <summary>
|
|||
/// Displays pages inside an <see cref="ItemsControl"/>.
|
|||
/// </summary>
|
|||
public class CarouselPresenter : ItemsPresenter |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="IsVirtualized"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> IsVirtualizedProperty = |
|||
Carousel.IsVirtualizedProperty.AddOwner<CarouselPresenter>(); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="SelectedIndex"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<CarouselPresenter, int> SelectedIndexProperty = |
|||
SelectingItemsControl.SelectedIndexProperty.AddOwner<CarouselPresenter>( |
|||
o => o.SelectedIndex, |
|||
(o, v) => o.SelectedIndex = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="PageTransition"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IPageTransition?> PageTransitionProperty = |
|||
Carousel.PageTransitionProperty.AddOwner<CarouselPresenter>(); |
|||
|
|||
private int _selectedIndex = -1; |
|||
private Task? _currentTransition; |
|||
private int _queuedTransitionIndex = -1; |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="CarouselPresenter"/> class.
|
|||
/// </summary>
|
|||
static CarouselPresenter() |
|||
{ |
|||
////IsVirtualizedProperty.Changed.AddClassHandler<CarouselPresenter>((x, e) => x.IsVirtualizedChanged(e));
|
|||
////SelectedIndexProperty.Changed.AddClassHandler<CarouselPresenter>((x, e) => x.SelectedIndexChanged(e));
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the items in the carousel are virtualized.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// When the carousel is virtualized, only the active page is held in memory.
|
|||
/// </remarks>
|
|||
public bool IsVirtualized |
|||
{ |
|||
get { return GetValue(IsVirtualizedProperty); } |
|||
set { SetValue(IsVirtualizedProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the index of the selected page.
|
|||
/// </summary>
|
|||
public int SelectedIndex |
|||
{ |
|||
get |
|||
{ |
|||
return _selectedIndex; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
////var old = SelectedIndex;
|
|||
////var effective = (value >= 0 && value < Items?.Cast<object>().Count()) ? value : -1;
|
|||
|
|||
////if (old != effective)
|
|||
////{
|
|||
//// _selectedIndex = effective;
|
|||
//// RaisePropertyChanged(SelectedIndexProperty, old, effective, BindingPriority.LocalValue);
|
|||
////}
|
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a transition to use when switching pages.
|
|||
/// </summary>
|
|||
public IPageTransition? PageTransition |
|||
{ |
|||
get { return GetValue(PageTransitionProperty); } |
|||
set { SetValue(PageTransitionProperty, value); } |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
////protected override void ItemsChanged(NotifyCollectionChangedEventArgs e)
|
|||
////{
|
|||
//// if (!IsVirtualized)
|
|||
//// {
|
|||
//// base.ItemsChanged(e);
|
|||
|
|||
//// if (Items == null || SelectedIndex >= Items.Count())
|
|||
//// {
|
|||
//// SelectedIndex = Items.Count() - 1;
|
|||
//// }
|
|||
|
|||
//// foreach (var c in ItemContainerGenerator.Containers)
|
|||
//// {
|
|||
//// c.ContainerControl.IsVisible = c.Index == SelectedIndex;
|
|||
//// }
|
|||
//// }
|
|||
//// else if (SelectedIndex != -1 && Panel != null)
|
|||
//// {
|
|||
//// switch (e.Action)
|
|||
//// {
|
|||
//// case NotifyCollectionChangedAction.Add:
|
|||
//// if (e.NewStartingIndex > SelectedIndex)
|
|||
//// {
|
|||
//// return;
|
|||
//// }
|
|||
//// break;
|
|||
//// case NotifyCollectionChangedAction.Remove:
|
|||
//// if (e.OldStartingIndex > SelectedIndex)
|
|||
//// {
|
|||
//// return;
|
|||
//// }
|
|||
//// break;
|
|||
//// case NotifyCollectionChangedAction.Replace:
|
|||
//// if (e.OldStartingIndex > SelectedIndex ||
|
|||
//// e.OldStartingIndex + e.OldItems!.Count - 1 < SelectedIndex)
|
|||
//// {
|
|||
//// return;
|
|||
//// }
|
|||
//// break;
|
|||
//// case NotifyCollectionChangedAction.Move:
|
|||
//// if (e.OldStartingIndex > SelectedIndex &&
|
|||
//// e.NewStartingIndex > SelectedIndex)
|
|||
//// {
|
|||
//// return;
|
|||
//// }
|
|||
//// break;
|
|||
//// }
|
|||
|
|||
//// if (Items == null || SelectedIndex >= Items.Count())
|
|||
//// {
|
|||
//// SelectedIndex = Items.Count() - 1;
|
|||
//// }
|
|||
|
|||
//// Panel.Children.Clear();
|
|||
//// ItemContainerGenerator.Clear();
|
|||
|
|||
//// if (SelectedIndex != -1)
|
|||
//// {
|
|||
//// GetOrCreateContainer(SelectedIndex);
|
|||
//// }
|
|||
//// }
|
|||
////}
|
|||
|
|||
////protected override void PanelCreated(Panel panel)
|
|||
////{
|
|||
//// ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
|||
////}
|
|||
|
|||
/////// <summary>
|
|||
/////// Moves to the selected page, animating if a <see cref="PageTransition"/> is set.
|
|||
/////// </summary>
|
|||
/////// <param name="fromIndex">The index of the old page.</param>
|
|||
/////// <param name="toIndex">The index of the new page.</param>
|
|||
/////// <returns>A task tracking the animation.</returns>
|
|||
////private async Task MoveToPage(int fromIndex, int toIndex)
|
|||
////{
|
|||
//// if (fromIndex != toIndex)
|
|||
//// {
|
|||
//// var generator = ItemContainerGenerator;
|
|||
//// Control? from = null;
|
|||
//// Control? to = null;
|
|||
|
|||
//// if (fromIndex != -1)
|
|||
//// {
|
|||
//// from = generator.ContainerFromIndex(fromIndex);
|
|||
//// }
|
|||
|
|||
//// if (toIndex != -1)
|
|||
//// {
|
|||
//// to = GetOrCreateContainer(toIndex);
|
|||
//// }
|
|||
|
|||
//// if (PageTransition != null && (from != null || to != null))
|
|||
//// {
|
|||
//// await PageTransition.Start((Visual?)from, (Visual?)to, fromIndex < toIndex, default);
|
|||
//// }
|
|||
//// else if (to != null)
|
|||
//// {
|
|||
//// to.IsVisible = true;
|
|||
//// }
|
|||
|
|||
//// if (from != null)
|
|||
//// {
|
|||
//// if (IsVirtualized)
|
|||
//// {
|
|||
//// Panel!.Children.Remove(from);
|
|||
//// generator.Dematerialize(fromIndex, 1);
|
|||
//// }
|
|||
//// else
|
|||
//// {
|
|||
//// from.IsVisible = false;
|
|||
//// }
|
|||
//// }
|
|||
//// }
|
|||
////}
|
|||
|
|||
////private Control? GetOrCreateContainer(int index)
|
|||
////{
|
|||
//// var container = ItemContainerGenerator.ContainerFromIndex(index);
|
|||
|
|||
//// if (container == null && IsVirtualized)
|
|||
//// {
|
|||
//// var item = Items!.Cast<object>().ElementAt(index);
|
|||
//// var materialized = ItemContainerGenerator.Materialize(index, item);
|
|||
//// Panel!.Children.Add(materialized.ContainerControl);
|
|||
//// container = materialized.ContainerControl;
|
|||
//// }
|
|||
|
|||
//// return container;
|
|||
////}
|
|||
|
|||
/////// <summary>
|
|||
/////// Called when the <see cref="IsVirtualized"/> property changes.
|
|||
/////// </summary>
|
|||
/////// <param name="e">The event args.</param>
|
|||
////private void IsVirtualizedChanged(AvaloniaPropertyChangedEventArgs e)
|
|||
////{
|
|||
//// if (Panel != null)
|
|||
//// {
|
|||
//// ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
|||
//// }
|
|||
////}
|
|||
|
|||
/////// <summary>
|
|||
/////// Called when the <see cref="SelectedIndex"/> property changes.
|
|||
/////// </summary>
|
|||
/////// <param name="e">The event args.</param>
|
|||
////private async void SelectedIndexChanged(AvaloniaPropertyChangedEventArgs e)
|
|||
////{
|
|||
//// if (Panel != null)
|
|||
//// {
|
|||
//// if (_currentTransition == null)
|
|||
//// {
|
|||
//// int fromIndex = (int)e.OldValue!;
|
|||
//// int toIndex = (int)e.NewValue!;
|
|||
|
|||
//// for (;;)
|
|||
//// {
|
|||
//// _currentTransition = MoveToPage(fromIndex, toIndex);
|
|||
//// await _currentTransition;
|
|||
|
|||
//// if (_queuedTransitionIndex != -1)
|
|||
//// {
|
|||
//// fromIndex = toIndex;
|
|||
//// toIndex = _queuedTransitionIndex;
|
|||
//// _queuedTransitionIndex = -1;
|
|||
//// }
|
|||
//// else
|
|||
//// {
|
|||
//// _currentTransition = null;
|
|||
//// break;
|
|||
//// }
|
|||
//// }
|
|||
//// }
|
|||
//// else
|
|||
//// {
|
|||
//// _queuedTransitionIndex = (int)e.NewValue!;
|
|||
//// }
|
|||
//// }
|
|||
////}
|
|||
} |
|||
} |
|||
@ -0,0 +1,351 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Specialized; |
|||
using System.Diagnostics; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Animation; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Input; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// A panel used by <see cref="Carousel"/> to display the current item.
|
|||
/// </summary>
|
|||
public class VirtualizingCarouselPanel : VirtualizingPanel, ILogicalScrollable |
|||
{ |
|||
private static readonly AttachedProperty<bool> ItemIsOwnContainerProperty = |
|||
AvaloniaProperty.RegisterAttached<VirtualizingCarouselPanel, Control, bool>("ItemIsOwnContainer"); |
|||
|
|||
private Size _extent; |
|||
private Vector _offset; |
|||
private Size _viewport; |
|||
private Stack<Control>? _recyclePool; |
|||
private Control? _realized; |
|||
private int _realizedIndex = -1; |
|||
private Control? _transitionFrom; |
|||
private int _transitionFromIndex = -1; |
|||
private CancellationTokenSource? _transition; |
|||
private EventHandler? _scrollInvalidated; |
|||
|
|||
bool ILogicalScrollable.CanHorizontallyScroll { get; set; } |
|||
bool ILogicalScrollable.CanVerticallyScroll { get; set; } |
|||
bool ILogicalScrollable.IsLogicalScrollEnabled => true; |
|||
Size ILogicalScrollable.ScrollSize => new(1, 1); |
|||
Size ILogicalScrollable.PageScrollSize => new(1, 1); |
|||
Size IScrollable.Extent => Extent; |
|||
Size IScrollable.Viewport => Viewport; |
|||
|
|||
Vector IScrollable.Offset |
|||
{ |
|||
get => _offset; |
|||
set |
|||
{ |
|||
if ((int)_offset.X != value.X) |
|||
InvalidateMeasure(); |
|||
_offset = value; |
|||
} |
|||
} |
|||
|
|||
private Size Extent |
|||
{ |
|||
get => _extent; |
|||
set |
|||
{ |
|||
if (_extent != value) |
|||
{ |
|||
_extent = value; |
|||
_scrollInvalidated?.Invoke(this, EventArgs.Empty); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private Size Viewport |
|||
{ |
|||
get => _viewport; |
|||
set |
|||
{ |
|||
if (_viewport != value) |
|||
{ |
|||
_viewport = value; |
|||
_scrollInvalidated?.Invoke(this, EventArgs.Empty); |
|||
} |
|||
} |
|||
} |
|||
|
|||
event EventHandler? ILogicalScrollable.ScrollInvalidated |
|||
{ |
|||
add => _scrollInvalidated += value; |
|||
remove => _scrollInvalidated -= value; |
|||
} |
|||
|
|||
bool ILogicalScrollable.BringIntoView(Control target, Rect targetRect) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
Control? ILogicalScrollable.GetControlInDirection(NavigationDirection direction, Control? from) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
void ILogicalScrollable.RaiseScrollInvalidated(EventArgs e) => _scrollInvalidated?.Invoke(this, e); |
|||
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
var items = ItemsControl?.Items as IList ?? Array.Empty<object?>(); |
|||
var index = (int)_offset.X; |
|||
|
|||
if (index != _realizedIndex) |
|||
{ |
|||
if (_realized is not null) |
|||
{ |
|||
var cancelTransition = _transition is not null; |
|||
|
|||
// Cancel any already running transition, and recycle the element we're transitioning from.
|
|||
if (cancelTransition) |
|||
{ |
|||
_transition!.Cancel(); |
|||
_transition = null; |
|||
if (_transitionFrom is not null) |
|||
RecycleElement(_transitionFrom); |
|||
_transitionFrom = null; |
|||
_transitionFromIndex = -1; |
|||
} |
|||
|
|||
if (cancelTransition || GetTransition() is null) |
|||
{ |
|||
// If don't have a transition or we've just canceled a transition then recycle the element
|
|||
// we're moving from.
|
|||
RecycleElement(_realized); |
|||
} |
|||
else |
|||
{ |
|||
// We have a transition to do: record the current element as the element we're transitioning
|
|||
// from and we'll start the transition in the arrange pass.
|
|||
_transitionFrom = _realized; |
|||
_transitionFromIndex = _realizedIndex; |
|||
} |
|||
|
|||
_realized = null; |
|||
_realizedIndex = -1; |
|||
} |
|||
|
|||
// Get or create an element for the new item.
|
|||
if (index >= 0 && index < items.Count) |
|||
{ |
|||
_realized = GetOrCreateElement(items, index); |
|||
_realizedIndex = index; |
|||
} |
|||
} |
|||
|
|||
if (_realized is null) |
|||
{ |
|||
Extent = Viewport = new(0, 0); |
|||
_transitionFrom = null; |
|||
_transitionFromIndex = -1; |
|||
return default; |
|||
} |
|||
|
|||
_realized.Measure(availableSize); |
|||
Extent = new(items.Count, 1); |
|||
Viewport = new(1, 1); |
|||
|
|||
return _realized.DesiredSize; |
|||
} |
|||
|
|||
protected override Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
var result = base.ArrangeOverride(finalSize); |
|||
|
|||
if (_transition is null && |
|||
_transitionFrom is not null && |
|||
_realized is { } to && |
|||
GetTransition() is { } transition) |
|||
{ |
|||
_transition = new CancellationTokenSource(); |
|||
transition.Start(_transitionFrom, to, _realizedIndex > _transitionFromIndex, _transition.Token) |
|||
.ContinueWith(TransitionFinished, TaskScheduler.FromCurrentSynchronizationContext()); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
protected override IInputElement? GetControl(NavigationDirection direction, IInputElement? from, bool wrap) => null; |
|||
|
|||
protected internal override Control? ContainerFromIndex(int index) |
|||
{ |
|||
return index == _realizedIndex ? _realized : null; |
|||
} |
|||
|
|||
protected internal override IEnumerable<Control>? GetRealizedContainers() |
|||
{ |
|||
return _realized is not null ? new[] { _realized } : null; |
|||
} |
|||
|
|||
protected internal override int IndexFromContainer(Control container) |
|||
{ |
|||
return container == _realized ? _realizedIndex : -1; |
|||
} |
|||
|
|||
protected internal override Control? ScrollIntoView(int index) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
protected override void OnItemsChanged(IList items, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
base.OnItemsChanged(items, e); |
|||
|
|||
void Add(int index, int count) |
|||
{ |
|||
if (index <= _realizedIndex) |
|||
_realizedIndex += count; |
|||
} |
|||
|
|||
void Remove(int index, int count) |
|||
{ |
|||
var end = index + (count - 1); |
|||
|
|||
if (_realized is not null && index <= _realizedIndex && end >= _realizedIndex) |
|||
{ |
|||
RecycleElement(_realized); |
|||
_realized = null; |
|||
_realizedIndex = -1; |
|||
} |
|||
else if (index < _realizedIndex) |
|||
{ |
|||
_realizedIndex -= count; |
|||
} |
|||
} |
|||
|
|||
switch (e.Action) |
|||
{ |
|||
case NotifyCollectionChangedAction.Add: |
|||
Add(e.NewStartingIndex, e.NewItems!.Count); |
|||
break; |
|||
case NotifyCollectionChangedAction.Remove: |
|||
Remove(e.OldStartingIndex, e.OldItems!.Count); |
|||
break; |
|||
case NotifyCollectionChangedAction.Replace: |
|||
case NotifyCollectionChangedAction.Move: |
|||
Remove(e.OldStartingIndex, e.OldItems!.Count); |
|||
Add(e.NewStartingIndex, e.NewItems!.Count); |
|||
break; |
|||
case NotifyCollectionChangedAction.Reset: |
|||
if (_realized is not null) |
|||
{ |
|||
RecycleElement(_realized); |
|||
_realized = null; |
|||
_realizedIndex = -1; |
|||
} |
|||
break; |
|||
} |
|||
|
|||
InvalidateMeasure(); |
|||
} |
|||
|
|||
private Control GetOrCreateElement(IList items, int index) |
|||
{ |
|||
return GetRealizedElement(index) ?? |
|||
GetItemIsOwnContainer(items, index) ?? |
|||
GetRecycledElement(items, index) ?? |
|||
CreateElement(items, index); |
|||
} |
|||
|
|||
private Control? GetRealizedElement(int index) |
|||
{ |
|||
return _realizedIndex == index ? _realized : null; |
|||
} |
|||
|
|||
private Control? GetItemIsOwnContainer(IList items, int index) |
|||
{ |
|||
Debug.Assert(ItemsControl is not null); |
|||
|
|||
if (items[index] is Control controlItem) |
|||
{ |
|||
var generator = ItemsControl!.ItemContainerGenerator; |
|||
|
|||
if (controlItem.IsSet(ItemIsOwnContainerProperty)) |
|||
{ |
|||
controlItem.IsVisible = true; |
|||
return controlItem; |
|||
} |
|||
else if (generator.IsItemItsOwnContainer(controlItem)) |
|||
{ |
|||
AddInternalChild(controlItem); |
|||
generator.PrepareItemContainer(controlItem, controlItem, index); |
|||
controlItem.SetValue(ItemIsOwnContainerProperty, true); |
|||
return controlItem; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private Control? GetRecycledElement(IList items, int index) |
|||
{ |
|||
Debug.Assert(ItemsControl is not null); |
|||
|
|||
var generator = ItemsControl!.ItemContainerGenerator; |
|||
var item = items[index]; |
|||
|
|||
if (_recyclePool?.Count > 0) |
|||
{ |
|||
var recycled = _recyclePool.Pop(); |
|||
recycled.IsVisible = true; |
|||
generator.PrepareItemContainer(recycled, item, index); |
|||
return recycled; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private Control CreateElement(IList items, int index) |
|||
{ |
|||
Debug.Assert(ItemsControl is not null); |
|||
|
|||
var generator = ItemsControl!.ItemContainerGenerator; |
|||
var item = items[index]; |
|||
var container = generator.CreateContainer(); |
|||
|
|||
AddInternalChild(container); |
|||
generator.PrepareItemContainer(container, item, index); |
|||
|
|||
return container; |
|||
} |
|||
|
|||
private void RecycleElement(Control element) |
|||
{ |
|||
Debug.Assert(ItemsControl is not null); |
|||
|
|||
if (element.IsSet(ItemIsOwnContainerProperty)) |
|||
{ |
|||
element.IsVisible = false; |
|||
} |
|||
else |
|||
{ |
|||
ItemsControl!.ItemContainerGenerator.ClearItemContainer(element); |
|||
_recyclePool ??= new(); |
|||
_recyclePool.Push(element); |
|||
element.IsVisible = false; |
|||
} |
|||
} |
|||
|
|||
private IPageTransition? GetTransition() => (ItemsControl as Carousel)?.PageTransition; |
|||
|
|||
private void TransitionFinished(Task task) |
|||
{ |
|||
if (task.IsCanceled) |
|||
return; |
|||
|
|||
if (_transitionFrom is not null) |
|||
RecycleElement(_transitionFrom); |
|||
_transition = null; |
|||
_transitionFrom = null; |
|||
_transitionFromIndex = -1; |
|||
} |
|||
} |
|||
} |
|||
@ -1,341 +1,321 @@ |
|||
////using System.Collections.ObjectModel;
|
|||
////using System.Linq;
|
|||
////using System.Reactive.Subjects;
|
|||
////using Avalonia.Controls.Presenters;
|
|||
////using Avalonia.Controls.Templates;
|
|||
////using Avalonia.Data;
|
|||
////using Avalonia.LogicalTree;
|
|||
////using Avalonia.UnitTests;
|
|||
////using Avalonia.VisualTree;
|
|||
////using Xunit;
|
|||
|
|||
////namespace Avalonia.Controls.UnitTests
|
|||
////{
|
|||
//// public class CarouselTests
|
|||
//// {
|
|||
//// [Fact]
|
|||
//// public void First_Item_Should_Be_Selected_By_Default()
|
|||
//// {
|
|||
//// var target = new Carousel
|
|||
//// {
|
|||
//// Template = new FuncControlTemplate<Carousel>(CreateTemplate),
|
|||
//// Items = new[]
|
|||
//// {
|
|||
//// "Foo",
|
|||
//// "Bar"
|
|||
//// }
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// Assert.Equal(0, target.SelectedIndex);
|
|||
//// Assert.Equal("Foo", target.SelectedItem);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void LogicalChild_Should_Be_Selected_Item()
|
|||
//// {
|
|||
//// var target = new Carousel
|
|||
//// {
|
|||
//// Template = new FuncControlTemplate<Carousel>(CreateTemplate),
|
|||
//// Items = new[]
|
|||
//// {
|
|||
//// "Foo",
|
|||
//// "Bar"
|
|||
//// }
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// ((Control)target.Presenter).ApplyTemplate();
|
|||
|
|||
//// Assert.Single(target.GetLogicalChildren());
|
|||
|
|||
//// var child = GetContainerTextBlock(target.GetLogicalChildren().Single());
|
|||
|
|||
//// Assert.Equal("Foo", child.Text);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Remove_NonCurrent_Page_When_IsVirtualized_True()
|
|||
//// {
|
|||
//// var target = new Carousel
|
|||
//// {
|
|||
//// Template = new FuncControlTemplate<Carousel>(CreateTemplate),
|
|||
//// Items = new[] { "foo", "bar" },
|
|||
//// IsVirtualized = true,
|
|||
//// SelectedIndex = 0,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// ((Control)target.Presenter).ApplyTemplate();
|
|||
|
|||
//// Assert.Single(target.ItemContainerGenerator.Containers);
|
|||
//// target.SelectedIndex = 1;
|
|||
//// Assert.Single(target.ItemContainerGenerator.Containers);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "Foo",
|
|||
//// "Bar",
|
|||
//// "FooBar"
|
|||
//// };
|
|||
|
|||
//// var target = new Carousel
|
|||
//// {
|
|||
//// Template = new FuncControlTemplate<Carousel>(CreateTemplate),
|
|||
//// Items = items,
|
|||
//// IsVirtualized = false
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// ((Control)target.Presenter).ApplyTemplate();
|
|||
|
|||
//// Assert.Equal(3, target.GetLogicalChildren().Count());
|
|||
|
|||
//// var child = GetContainerTextBlock(target.GetLogicalChildren().First());
|
|||
|
|||
//// Assert.Equal("Foo", child.Text);
|
|||
|
|||
//// var newItems = items.ToList();
|
|||
//// newItems.RemoveAt(0);
|
|||
|
|||
//// target.Items = newItems;
|
|||
|
|||
//// child = GetContainerTextBlock(target.GetLogicalChildren().First());
|
|||
|
|||
//// Assert.Equal("Bar", child.Text);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes_And_Virtualized()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "Foo",
|
|||
//// "Bar",
|
|||
//// "FooBar"
|
|||
//// };
|
|||
|
|||
//// var target = new Carousel
|
|||
//// {
|
|||
//// Template = new FuncControlTemplate<Carousel>(CreateTemplate),
|
|||
//// Items = items,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// ((Control)target.Presenter).ApplyTemplate();
|
|||
|
|||
//// Assert.Single(target.GetLogicalChildren());
|
|||
|
|||
//// var child = GetContainerTextBlock(target.GetLogicalChildren().Single());
|
|||
|
|||
//// Assert.Equal("Foo", child.Text);
|
|||
|
|||
//// var newItems = items.ToList();
|
|||
//// newItems.RemoveAt(0);
|
|||
|
|||
//// target.Items = newItems;
|
|||
|
|||
//// child = GetContainerTextBlock(target.GetLogicalChildren().Single());
|
|||
|
|||
//// Assert.Equal("Bar", child.Text);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Selected_Item_Changes_To_First_Item_When_Item_Added()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>();
|
|||
//// var target = new Carousel
|
|||
//// {
|
|||
//// Template = new FuncControlTemplate<Carousel>(CreateTemplate),
|
|||
//// Items = items,
|
|||
//// IsVirtualized = false
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// ((Control)target.Presenter).ApplyTemplate();
|
|||
using System; |
|||
using System.Collections.ObjectModel; |
|||
using System.Linq; |
|||
using System.Reactive.Subjects; |
|||
using Avalonia.Controls.Presenters; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Data; |
|||
using Avalonia.Layout; |
|||
using Avalonia.LogicalTree; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests |
|||
{ |
|||
public class CarouselTests |
|||
{ |
|||
[Fact] |
|||
public void First_Item_Should_Be_Selected_By_Default() |
|||
{ |
|||
using var app = Start(); |
|||
var target = new Carousel |
|||
{ |
|||
Template = CarouselTemplate(), |
|||
Items = new[] |
|||
{ |
|||
"Foo", |
|||
"Bar" |
|||
} |
|||
}; |
|||
|
|||
Prepare(target); |
|||
|
|||
Assert.Equal(0, target.SelectedIndex); |
|||
Assert.Equal("Foo", target.SelectedItem); |
|||
} |
|||
|
|||
[Fact] |
|||
public void LogicalChild_Should_Be_Selected_Item() |
|||
{ |
|||
using var app = Start(); |
|||
var target = new Carousel |
|||
{ |
|||
Template = CarouselTemplate(), |
|||
Items = new[] |
|||
{ |
|||
"Foo", |
|||
"Bar" |
|||
} |
|||
}; |
|||
|
|||
Prepare(target); |
|||
|
|||
Assert.Single(target.GetRealizedContainers()); |
|||
|
|||
var child = GetContainerTextBlock(target.GetRealizedContainers().Single()); |
|||
|
|||
Assert.Equal("Foo", child.Text); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new ObservableCollection<string> |
|||
{ |
|||
"Foo", |
|||
"Bar", |
|||
"FooBar" |
|||
}; |
|||
|
|||
var target = new Carousel |
|||
{ |
|||
Template = CarouselTemplate(), |
|||
Items = items, |
|||
}; |
|||
|
|||
Prepare(target); |
|||
|
|||
Assert.Single(target.GetRealizedContainers()); |
|||
|
|||
var child = GetContainerTextBlock(target.GetRealizedContainers().Single()); |
|||
|
|||
Assert.Equal("Foo", child.Text); |
|||
|
|||
var newItems = items.ToList(); |
|||
newItems.RemoveAt(0); |
|||
Layout(target); |
|||
|
|||
target.Items = newItems; |
|||
Layout(target); |
|||
|
|||
child = GetContainerTextBlock(target.GetRealizedContainers().Single()); |
|||
|
|||
Assert.Equal("Bar", child.Text); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Selected_Item_Changes_To_First_Item_When_Item_Added() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new ObservableCollection<string>(); |
|||
var target = new Carousel |
|||
{ |
|||
Template = CarouselTemplate(), |
|||
Items = items, |
|||
}; |
|||
|
|||
Prepare(target); |
|||
|
|||
Assert.Equal(-1, target.SelectedIndex); |
|||
Assert.Empty(target.GetRealizedContainers()); |
|||
|
|||
items.Add("Foo"); |
|||
Layout(target); |
|||
|
|||
Assert.Equal(0, target.SelectedIndex); |
|||
Assert.Single(target.GetRealizedContainers()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Selected_Index_Changes_To_None_When_Items_Assigned_Null() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new ObservableCollection<string> |
|||
{ |
|||
"Foo", |
|||
"Bar", |
|||
"FooBar" |
|||
}; |
|||
|
|||
var target = new Carousel |
|||
{ |
|||
Template = CarouselTemplate(), |
|||
Items = items, |
|||
}; |
|||
|
|||
Prepare(target); |
|||
|
|||
Assert.Equal(1, target.GetRealizedContainers().Count()); |
|||
|
|||
var child = GetContainerTextBlock(target.GetRealizedContainers().First()); |
|||
|
|||
Assert.Equal("Foo", child.Text); |
|||
|
|||
target.Items = null; |
|||
Layout(target); |
|||
|
|||
var numChildren = target.GetRealizedContainers().Count(); |
|||
|
|||
//// Assert.Equal(-1, target.SelectedIndex);
|
|||
//// Assert.Empty(target.GetLogicalChildren());
|
|||
Assert.Equal(-1, target.SelectedIndex); |
|||
} |
|||
|
|||
//// items.Add("Foo");
|
|||
[Fact] |
|||
public void Selected_Index_Is_Maintained_Carousel_Created_With_Non_Zero_SelectedIndex() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new ObservableCollection<string> |
|||
{ |
|||
"Foo", |
|||
"Bar", |
|||
"FooBar" |
|||
}; |
|||
|
|||
var target = new Carousel |
|||
{ |
|||
Template = CarouselTemplate(), |
|||
Items = items, |
|||
SelectedIndex = 2 |
|||
}; |
|||
|
|||
//// Assert.Equal(0, target.SelectedIndex);
|
|||
//// Assert.Single(target.GetLogicalChildren());
|
|||
//// }
|
|||
Prepare(target); |
|||
|
|||
Assert.Equal("FooBar", target.SelectedItem); |
|||
|
|||
//// [Fact]
|
|||
//// public void Selected_Index_Changes_To_None_When_Items_Assigned_Null()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "Foo",
|
|||
//// "Bar",
|
|||
//// "FooBar"
|
|||
//// };
|
|||
var child = GetContainerTextBlock(target.GetRealizedContainers().LastOrDefault()); |
|||
|
|||
//// var target = new Carousel
|
|||
//// {
|
|||
//// Template = new FuncControlTemplate<Carousel>(CreateTemplate),
|
|||
//// Items = items,
|
|||
//// IsVirtualized = false
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// ((Control)target.Presenter).ApplyTemplate();
|
|||
|
|||
//// Assert.Equal(3, target.GetLogicalChildren().Count());
|
|||
|
|||
//// var child = GetContainerTextBlock(target.GetLogicalChildren().First());
|
|||
|
|||
//// Assert.Equal("Foo", child.Text);
|
|||
|
|||
//// target.Items = null;
|
|||
|
|||
//// var numChildren = target.GetLogicalChildren().Count();
|
|||
|
|||
//// Assert.Equal(0, numChildren);
|
|||
//// Assert.Equal(-1, target.SelectedIndex);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Selected_Index_Is_Maintained_Carousel_Created_With_Non_Zero_SelectedIndex()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "Foo",
|
|||
//// "Bar",
|
|||
//// "FooBar"
|
|||
//// };
|
|||
|
|||
//// var target = new Carousel
|
|||
//// {
|
|||
//// Template = new FuncControlTemplate<Carousel>(CreateTemplate),
|
|||
//// Items = items,
|
|||
//// IsVirtualized = false,
|
|||
//// SelectedIndex = 2
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// ((Control)target.Presenter).ApplyTemplate();
|
|||
|
|||
//// Assert.Equal("FooBar", target.SelectedItem);
|
|||
|
|||
//// var child = GetContainerTextBlock(target.GetVisualDescendants().LastOrDefault());
|
|||
|
|||
//// Assert.IsType<TextBlock>(child);
|
|||
//// Assert.Equal("FooBar", ((TextBlock)child).Text);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Selected_Item_Changes_To_Next_First_Item_When_Item_Removed_From_Beggining_Of_List()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "Foo",
|
|||
//// "Bar",
|
|||
//// "FooBar"
|
|||
//// };
|
|||
|
|||
//// var target = new Carousel
|
|||
//// {
|
|||
//// Template = new FuncControlTemplate<Carousel>(CreateTemplate),
|
|||
//// Items = items,
|
|||
//// IsVirtualized = false
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// ((Control)target.Presenter).ApplyTemplate();
|
|||
|
|||
//// Assert.Equal(3, target.GetLogicalChildren().Count());
|
|||
|
|||
//// var child = GetContainerTextBlock(target.GetLogicalChildren().First());
|
|||
|
|||
//// Assert.Equal("Foo", child.Text);
|
|||
|
|||
//// items.RemoveAt(0);
|
|||
|
|||
//// child = GetContainerTextBlock(target.GetLogicalChildren().First());
|
|||
|
|||
//// Assert.IsType<TextBlock>(child);
|
|||
//// Assert.Equal("Bar", ((TextBlock)child).Text);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Selected_Item_Changes_To_First_Item_If_SelectedItem_Is_Removed_From_Middle()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "Foo",
|
|||
//// "Bar",
|
|||
//// "FooBar"
|
|||
//// };
|
|||
|
|||
//// var target = new Carousel
|
|||
//// {
|
|||
//// Template = new FuncControlTemplate<Carousel>(CreateTemplate),
|
|||
//// Items = items,
|
|||
//// IsVirtualized = false
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// ((Control)target.Presenter).ApplyTemplate();
|
|||
|
|||
//// target.SelectedIndex = 1;
|
|||
|
|||
//// items.RemoveAt(1);
|
|||
|
|||
//// Assert.Equal(0, target.SelectedIndex);
|
|||
//// Assert.Equal("Foo", target.SelectedItem);
|
|||
//// }
|
|||
|
|||
//// private Control CreateTemplate(Carousel control, INameScope scope)
|
|||
//// {
|
|||
//// return new CarouselPresenter
|
|||
//// {
|
|||
//// Name = "PART_ItemsPresenter",
|
|||
//// [~CarouselPresenter.IsVirtualizedProperty] = control[~Carousel.IsVirtualizedProperty],
|
|||
//// [~CarouselPresenter.ItemsPanelProperty] = control[~Carousel.ItemsPanelProperty],
|
|||
//// [~CarouselPresenter.SelectedIndexProperty] = control[~Carousel.SelectedIndexProperty],
|
|||
//// [~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty],
|
|||
//// }.RegisterInNameScope(scope);
|
|||
//// }
|
|||
|
|||
//// private static TextBlock GetContainerTextBlock(object control)
|
|||
//// {
|
|||
//// var contentPresenter = Assert.IsType<ContentPresenter>(control);
|
|||
//// contentPresenter.UpdateChild();
|
|||
//// return Assert.IsType<TextBlock>(contentPresenter.Child);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void SelectedItem_Validation()
|
|||
//// {
|
|||
//// using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
|
|||
//// {
|
|||
//// var target = new Carousel
|
|||
//// {
|
|||
//// Template = new FuncControlTemplate<Carousel>(CreateTemplate),
|
|||
//// IsVirtualized = false
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// ((Control)target.Presenter).ApplyTemplate();
|
|||
|
|||
//// var exception = new System.InvalidCastException("failed validation");
|
|||
//// var textObservable =
|
|||
//// new BehaviorSubject<BindingNotification>(new BindingNotification(exception,
|
|||
//// BindingErrorType.DataValidationError));
|
|||
//// target.Bind(ComboBox.SelectedItemProperty, textObservable);
|
|||
|
|||
//// Assert.True(DataValidationErrors.GetHasErrors(target));
|
|||
//// Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));
|
|||
//// }
|
|||
//// }
|
|||
//// }
|
|||
////}
|
|||
Assert.Equal("FooBar", child.Text); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Selected_Item_Changes_To_Next_First_Item_When_Item_Removed_From_Beggining_Of_List() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new ObservableCollection<string> |
|||
{ |
|||
"Foo", |
|||
"Bar", |
|||
"FooBar" |
|||
}; |
|||
|
|||
var target = new Carousel |
|||
{ |
|||
Template = CarouselTemplate(), |
|||
Items = items, |
|||
}; |
|||
|
|||
Prepare(target); |
|||
|
|||
var child = GetContainerTextBlock(target.GetRealizedContainers().First()); |
|||
|
|||
Assert.Equal("Foo", child.Text); |
|||
|
|||
items.RemoveAt(0); |
|||
Layout(target); |
|||
|
|||
child = GetContainerTextBlock(target.GetRealizedContainers().First()); |
|||
|
|||
Assert.IsType<TextBlock>(child); |
|||
Assert.Equal("Bar", child.Text); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Selected_Item_Changes_To_First_Item_If_SelectedItem_Is_Removed_From_Middle() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new ObservableCollection<string> |
|||
{ |
|||
"Foo", |
|||
"Bar", |
|||
"FooBar" |
|||
}; |
|||
|
|||
var target = new Carousel |
|||
{ |
|||
Template = CarouselTemplate(), |
|||
Items = items, |
|||
}; |
|||
|
|||
Prepare(target); |
|||
|
|||
target.SelectedIndex = 1; |
|||
|
|||
items.RemoveAt(1); |
|||
|
|||
Assert.Equal(0, target.SelectedIndex); |
|||
Assert.Equal("Foo", target.SelectedItem); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SelectedItem_Validation() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) |
|||
{ |
|||
var target = new Carousel |
|||
{ |
|||
Template = CarouselTemplate(), |
|||
}; |
|||
|
|||
Prepare(target); |
|||
|
|||
var exception = new System.InvalidCastException("failed validation"); |
|||
var textObservable = |
|||
new BehaviorSubject<BindingNotification>(new BindingNotification(exception, |
|||
BindingErrorType.DataValidationError)); |
|||
target.Bind(ComboBox.SelectedItemProperty, textObservable); |
|||
|
|||
Assert.True(DataValidationErrors.GetHasErrors(target)); |
|||
Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception })); |
|||
} |
|||
} |
|||
|
|||
private static IDisposable Start() => UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); |
|||
|
|||
private static void Prepare(Carousel target) |
|||
{ |
|||
var root = new TestRoot(target); |
|||
root.LayoutManager.ExecuteInitialLayoutPass(); |
|||
} |
|||
|
|||
private static void Layout(Carousel target) |
|||
{ |
|||
((ILayoutRoot)target.GetVisualRoot()).LayoutManager.ExecuteLayoutPass(); |
|||
} |
|||
|
|||
private static IControlTemplate CarouselTemplate() |
|||
{ |
|||
return new FuncControlTemplate((c, ns) => |
|||
new ScrollViewer |
|||
{ |
|||
Name = "PART_ScrollViewer", |
|||
Template = ScrollViewerTemplate(), |
|||
HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden, |
|||
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden, |
|||
Content = new ItemsPresenter |
|||
{ |
|||
Name = "PART_ItemsPresenter", |
|||
[~ItemsPresenter.ItemsPanelProperty] = c[~ItemsControl.ItemsPanelProperty], |
|||
}.RegisterInNameScope(ns) |
|||
}.RegisterInNameScope(ns)); |
|||
} |
|||
|
|||
private static FuncControlTemplate ScrollViewerTemplate() |
|||
{ |
|||
return new FuncControlTemplate<ScrollViewer>((parent, scope) => |
|||
new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
new ScrollContentPresenter |
|||
{ |
|||
Name = "PART_ContentPresenter", |
|||
[~ScrollContentPresenter.ContentProperty] = parent.GetObservable(ScrollViewer.ContentProperty).ToBinding(), |
|||
[~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty], |
|||
[~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty], |
|||
[~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty], |
|||
[~ScrollContentPresenter.CanHorizontallyScrollProperty] = parent[~ScrollViewer.CanHorizontallyScrollProperty], |
|||
[~ScrollContentPresenter.CanVerticallyScrollProperty] = parent[~ScrollViewer.CanVerticallyScrollProperty], |
|||
}.RegisterInNameScope(scope), |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private static TextBlock GetContainerTextBlock(object control) |
|||
{ |
|||
var contentPresenter = Assert.IsType<ContentPresenter>(control); |
|||
return Assert.IsType<TextBlock>(contentPresenter.Child); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,732 +0,0 @@ |
|||
////using System.Linq;
|
|||
////using Moq;
|
|||
////using Avalonia.Controls.Generators;
|
|||
////using Avalonia.Controls.Presenters;
|
|||
////using Avalonia.Controls.Templates;
|
|||
////using Xunit;
|
|||
////using System.Collections.ObjectModel;
|
|||
////using System.Collections;
|
|||
|
|||
////namespace Avalonia.Controls.UnitTests.Presenters
|
|||
////{
|
|||
//// public class CarouselPresenterTests
|
|||
//// {
|
|||
//// [Fact]
|
|||
//// public void Should_Register_With_Host_When_TemplatedParent_Set()
|
|||
//// {
|
|||
//// var host = new Carousel();
|
|||
//// var target = new CarouselPresenter();
|
|||
|
|||
//// Assert.Null(host.Presenter);
|
|||
|
|||
//// target.SetValue(Control.TemplatedParentProperty, host);
|
|||
|
|||
//// Assert.Same(target, host.Presenter);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void ApplyTemplate_Should_Create_Panel()
|
|||
//// {
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// ItemsPanel = new FuncTemplate<Panel>(() => new Panel()),
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// Assert.IsType<Panel>(target.Panel);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void ItemContainerGenerator_Should_Be_Picked_Up_From_TemplatedControl()
|
|||
//// {
|
|||
//// var parent = new TestItemsControl();
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// [StyledElement.TemplatedParentProperty] = parent,
|
|||
//// };
|
|||
|
|||
//// Assert.IsType<ItemContainerGenerator<TestItem>>(target.ItemContainerGenerator);
|
|||
//// }
|
|||
|
|||
//// public class Virtualized
|
|||
//// {
|
|||
//// [Fact]
|
|||
//// public void Should_Initially_Materialize_Selected_Container()
|
|||
//// {
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = new[] { "foo", "bar" },
|
|||
//// SelectedIndex = 0,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// AssertSingle(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Initially_Materialize_Nothing_If_No_Selected_Container()
|
|||
//// {
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = new[] { "foo", "bar" },
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// Assert.Empty(target.Panel.Children);
|
|||
//// Assert.Empty(target.ItemContainerGenerator.Containers);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Switching_To_Virtualized_Should_Reset_Containers()
|
|||
//// {
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = new[] { "foo", "bar" },
|
|||
//// SelectedIndex = 0,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// target.IsVirtualized = true;
|
|||
|
|||
//// AssertSingle(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Changing_SelectedIndex_Should_Show_Page()
|
|||
//// {
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = new[] { "foo", "bar" },
|
|||
//// SelectedIndex = 0,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// AssertSingle(target);
|
|||
|
|||
//// target.SelectedIndex = 1;
|
|||
//// AssertSingle(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Remove_NonCurrent_Page()
|
|||
//// {
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = new[] { "foo", "bar" },
|
|||
//// IsVirtualized = true,
|
|||
//// SelectedIndex = 0,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// AssertSingle(target);
|
|||
|
|||
//// target.SelectedIndex = 1;
|
|||
//// AssertSingle(target);
|
|||
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Inserting_Item_At_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.Insert(1, "item1a");
|
|||
//// AssertSingle(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Inserting_Item_Before_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 2,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.Insert(1, "item1a");
|
|||
//// AssertSingle(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Do_Nothing_When_Inserting_Item_After_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// var child = AssertSingle(target);
|
|||
//// items.Insert(2, "after");
|
|||
//// Assert.Same(child, AssertSingle(target));
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Removing_Item_At_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.RemoveAt(1);
|
|||
//// AssertSingle(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Removing_Item_Before_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.RemoveAt(0);
|
|||
//// AssertSingle(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Do_Nothing_When_Removing_Item_After_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// var child = AssertSingle(target);
|
|||
//// items.RemoveAt(2);
|
|||
//// Assert.Same(child, AssertSingle(target));
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Removing_SelectedItem_When_Its_Last()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 2,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.RemoveAt(2);
|
|||
//// Assert.Equal(1, target.SelectedIndex);
|
|||
//// AssertSingle(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Removing_Last_Item()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 0,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.RemoveAt(0);
|
|||
//// Assert.Empty(target.Panel.Children);
|
|||
//// Assert.Empty(target.ItemContainerGenerator.Containers);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Replacing_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items[1] = "replaced";
|
|||
//// AssertSingle(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Do_Nothing_When_Replacing_Non_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// var child = AssertSingle(target);
|
|||
//// items[0] = "replaced";
|
|||
//// Assert.Same(child, AssertSingle(target));
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Moving_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.Move(1, 0);
|
|||
//// AssertSingle(target);
|
|||
//// }
|
|||
|
|||
//// private static Control AssertSingle(CarouselPresenter target)
|
|||
//// {
|
|||
//// var items = (IList)target.Items;
|
|||
//// var index = target.SelectedIndex;
|
|||
//// var content = items[index];
|
|||
//// var child = Assert.Single(target.Panel.Children);
|
|||
//// var presenter = Assert.IsType<ContentPresenter>(child);
|
|||
//// var container = Assert.Single(target.ItemContainerGenerator.Containers);
|
|||
//// var visible = Assert.Single(target.Panel.Children.Where(x => x.IsVisible));
|
|||
|
|||
//// Assert.Same(child, container.ContainerControl);
|
|||
//// Assert.Same(child, visible);
|
|||
//// Assert.Equal(content, presenter.Content);
|
|||
//// Assert.Equal(content, container.Item);
|
|||
//// Assert.Equal(index, container.Index);
|
|||
|
|||
//// return child;
|
|||
//// }
|
|||
//// }
|
|||
|
|||
//// public class NonVirtualized
|
|||
//// {
|
|||
//// [Fact]
|
|||
//// public void Should_Initially_Materialize_All_Containers()
|
|||
//// {
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = new[] { "foo", "bar" },
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Initially_Show_Selected_Item()
|
|||
//// {
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = new[] { "foo", "bar" },
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Switching_To_Non_Virtualized_Should_Reset_Containers()
|
|||
//// {
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = new[] { "foo", "bar" },
|
|||
//// SelectedIndex = 0,
|
|||
//// IsVirtualized = true,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// target.IsVirtualized = false;
|
|||
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Changing_SelectedIndex_Should_Show_Page()
|
|||
//// {
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = new[] { "foo", "bar" },
|
|||
//// SelectedIndex = 0,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// AssertAll(target);
|
|||
|
|||
//// target.SelectedIndex = 1;
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Inserting_Item_At_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.Insert(1, "item1a");
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Inserting_Item_Before_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 2,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.Insert(1, "item1a");
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Do_Handle_Inserting_Item_After_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// items.Insert(2, "after");
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Removing_Item_At_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.RemoveAt(1);
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Removing_Item_Before_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.RemoveAt(0);
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Removing_Item_After_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// items.RemoveAt(2);
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Removing_SelectedItem_When_Its_Last()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 2,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.RemoveAt(2);
|
|||
//// Assert.Equal(1, target.SelectedIndex);
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Removing_Last_Item()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 0,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.RemoveAt(0);
|
|||
//// Assert.Empty(target.Panel.Children);
|
|||
//// Assert.Empty(target.ItemContainerGenerator.Containers);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Replacing_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items[1] = "replaced";
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Handle_Moving_SelectedItem()
|
|||
//// {
|
|||
//// var items = new ObservableCollection<string>
|
|||
//// {
|
|||
//// "item0",
|
|||
//// "item1",
|
|||
//// "item2",
|
|||
//// };
|
|||
|
|||
//// var target = new CarouselPresenter
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// SelectedIndex = 1,
|
|||
//// IsVirtualized = false,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// items.Move(1, 0);
|
|||
//// AssertAll(target);
|
|||
//// }
|
|||
|
|||
//// private static void AssertAll(CarouselPresenter target)
|
|||
//// {
|
|||
//// var items = (IList)target.Items;
|
|||
|
|||
//// Assert.Equal(items?.Count ?? 0, target.Panel.Children.Count);
|
|||
//// Assert.Equal(items?.Count ?? 0, target.ItemContainerGenerator.Containers.Count());
|
|||
|
|||
//// for (var i = 0; i < items?.Count; ++i)
|
|||
//// {
|
|||
//// var content = items[i];
|
|||
//// var child = target.Panel.Children[i];
|
|||
//// var presenter = Assert.IsType<ContentPresenter>(child);
|
|||
//// var container = target.ItemContainerGenerator.ContainerFromIndex(i);
|
|||
|
|||
//// Assert.Same(child, container);
|
|||
//// Assert.Equal(i == target.SelectedIndex, child.IsVisible);
|
|||
//// Assert.Equal(content, presenter.Content);
|
|||
//// Assert.Equal(i, target.ItemContainerGenerator.IndexFromContainer(container));
|
|||
//// }
|
|||
//// }
|
|||
//// }
|
|||
|
|||
//// private class TestItem : ContentControl
|
|||
//// {
|
|||
//// }
|
|||
|
|||
//// private class TestItemsControl : ItemsControl
|
|||
//// {
|
|||
//// protected override IItemContainerGenerator CreateItemContainerGenerator()
|
|||
//// {
|
|||
//// return new ItemContainerGenerator<TestItem>(this, TestItem.ContentProperty, null);
|
|||
//// }
|
|||
//// }
|
|||
//// }
|
|||
////}
|
|||
@ -0,0 +1,271 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.ObjectModel; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Animation; |
|||
using Avalonia.Controls.Presenters; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Layout; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using Moq; |
|||
using Xunit; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Controls.UnitTests |
|||
{ |
|||
public class VirtualizingCarouselPanelTests |
|||
{ |
|||
[Fact] |
|||
public void Initial_Item_Is_Displayed() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new[] { "foo", "bar" }; |
|||
var (target, _) = CreateTarget(items); |
|||
|
|||
Assert.Single(target.Children); |
|||
var container = Assert.IsType<ContentPresenter>(target.Children[0]); |
|||
Assert.Equal("foo", container.Content); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Displays_Next_Item() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new[] { "foo", "bar" }; |
|||
var (target, carousel) = CreateTarget(items); |
|||
|
|||
carousel.SelectedIndex = 1; |
|||
Layout(target); |
|||
|
|||
Assert.Single(target.Children); |
|||
var container = Assert.IsType<ContentPresenter>(target.Children[0]); |
|||
Assert.Equal("bar", container.Content); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Handles_Inserted_Item() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new ObservableCollection<string> { "foo", "bar" }; |
|||
var (target, carousel) = CreateTarget(items); |
|||
var container = Assert.IsType<ContentPresenter>(target.Children[0]); |
|||
|
|||
items.Insert(0, "baz"); |
|||
Layout(target); |
|||
|
|||
Assert.Single(target.Children); |
|||
Assert.Same(container, target.Children[0]); |
|||
Assert.Equal("foo", container.Content); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Handles_Removed_Item() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new ObservableCollection<string> { "foo", "bar" }; |
|||
var (target, carousel) = CreateTarget(items); |
|||
var container = Assert.IsType<ContentPresenter>(target.Children[0]); |
|||
|
|||
items.RemoveAt(0); |
|||
Layout(target); |
|||
|
|||
Assert.Single(target.Children); |
|||
Assert.Same(container, target.Children[0]); |
|||
Assert.Equal("bar", container.Content); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Handles_Replaced_Item() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new ObservableCollection<string> { "foo", "bar" }; |
|||
var (target, carousel) = CreateTarget(items); |
|||
var container = Assert.IsType<ContentPresenter>(target.Children[0]); |
|||
|
|||
items[0] = "baz"; |
|||
Layout(target); |
|||
|
|||
Assert.Single(target.Children); |
|||
Assert.Same(container, target.Children[0]); |
|||
Assert.Equal("baz", container.Content); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Handles_Moved_Item() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new ObservableCollection<string> { "foo", "bar" }; |
|||
var (target, carousel) = CreateTarget(items); |
|||
var container = Assert.IsType<ContentPresenter>(target.Children[0]); |
|||
|
|||
items.Move(0, 1); |
|||
Layout(target); |
|||
|
|||
Assert.Single(target.Children); |
|||
Assert.Same(container, target.Children[0]); |
|||
Assert.Equal("bar", container.Content); |
|||
} |
|||
|
|||
public class Transitions |
|||
{ |
|||
[Fact] |
|||
public void Initial_Item_Does_Not_Start_Transition() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new Control[] { new Button(), new Canvas() }; |
|||
var transition = new Mock<IPageTransition>(); |
|||
var (target, _) = CreateTarget(items, transition.Object); |
|||
|
|||
transition.Verify(x => x.Start( |
|||
It.IsAny<Visual>(), |
|||
It.IsAny<Visual>(), |
|||
It.IsAny<bool>(), |
|||
It.IsAny<CancellationToken>()), |
|||
Times.Never); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Changing_SelectedIndex_Starts_Transition() |
|||
{ |
|||
using var app = Start(); |
|||
var items = new Control[] { new Button(), new Canvas() }; |
|||
var transition = new Mock<IPageTransition>(); |
|||
var (target, carousel) = CreateTarget(items, transition.Object); |
|||
|
|||
carousel.SelectedIndex = 1; |
|||
Layout(target); |
|||
|
|||
transition.Verify(x => x.Start( |
|||
items[0], |
|||
items[1], |
|||
true, |
|||
It.IsAny<CancellationToken>()), |
|||
Times.Once); |
|||
} |
|||
|
|||
[Fact] |
|||
public void TransitionFrom_Control_Is_Recycled_When_Transition_Completes() |
|||
{ |
|||
using var app = Start(); |
|||
using var sync = UnitTestSynchronizationContext.Begin(); |
|||
var items = new Control[] { new Button(), new Canvas() }; |
|||
var transition = new Mock<IPageTransition>(); |
|||
var (target, carousel) = CreateTarget(items, transition.Object); |
|||
var transitionTask = new TaskCompletionSource(); |
|||
|
|||
transition.Setup(x => x.Start( |
|||
items[0], |
|||
items[1], |
|||
true, |
|||
It.IsAny<CancellationToken>())) |
|||
.Returns(() => transitionTask.Task); |
|||
|
|||
carousel.SelectedIndex = 1; |
|||
Layout(target); |
|||
|
|||
Assert.Equal(items, target.Children); |
|||
Assert.All(items, x => Assert.True(x.IsVisible)); |
|||
|
|||
transitionTask.SetResult(); |
|||
sync.ExecutePostedCallbacks(); |
|||
|
|||
Assert.Equal(items, target.Children); |
|||
Assert.False(items[0].IsVisible); |
|||
Assert.True(items[1].IsVisible); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Existing_Transition_Is_Canceled_If_Interrupted() |
|||
{ |
|||
using var app = Start(); |
|||
using var sync = UnitTestSynchronizationContext.Begin(); |
|||
var items = new Control[] { new Button(), new Canvas() }; |
|||
var transition = new Mock<IPageTransition>(); |
|||
var (target, carousel) = CreateTarget(items, transition.Object); |
|||
var transitionTask = new TaskCompletionSource(); |
|||
CancellationToken? cancelationToken = null; |
|||
|
|||
transition.Setup(x => x.Start( |
|||
items[0], |
|||
items[1], |
|||
true, |
|||
It.IsAny<CancellationToken>())) |
|||
.Callback<Visual, Visual, bool, CancellationToken>((_, _, _, c) => cancelationToken = c) |
|||
.Returns(() => transitionTask.Task); |
|||
|
|||
carousel.SelectedIndex = 1; |
|||
Layout(target); |
|||
|
|||
Assert.NotNull(cancelationToken); |
|||
Assert.False(cancelationToken!.Value.IsCancellationRequested); |
|||
|
|||
carousel.SelectedIndex = 0; |
|||
Layout(target); |
|||
|
|||
Assert.True(cancelationToken!.Value.IsCancellationRequested); |
|||
} |
|||
} |
|||
|
|||
private static IDisposable Start() => UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); |
|||
|
|||
private static (VirtualizingCarouselPanel, Carousel) CreateTarget( |
|||
IEnumerable items, |
|||
IPageTransition? transition = null) |
|||
{ |
|||
var carousel = new Carousel |
|||
{ |
|||
Items = items, |
|||
Template = CarouselTemplate(), |
|||
PageTransition = transition, |
|||
}; |
|||
|
|||
var root = new TestRoot(carousel); |
|||
root.LayoutManager.ExecuteInitialLayoutPass(); |
|||
return ((VirtualizingCarouselPanel)carousel.Presenter!.Panel!, carousel); |
|||
} |
|||
|
|||
private static IControlTemplate CarouselTemplate() |
|||
{ |
|||
return new FuncControlTemplate((c, ns) => |
|||
new ScrollViewer |
|||
{ |
|||
Name = "PART_ScrollViewer", |
|||
Template = ScrollViewerTemplate(), |
|||
HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden, |
|||
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden, |
|||
Content = new ItemsPresenter |
|||
{ |
|||
Name = "PART_ItemsPresenter", |
|||
[~ItemsPresenter.ItemsPanelProperty] = c[~ItemsControl.ItemsPanelProperty], |
|||
}.RegisterInNameScope(ns) |
|||
}.RegisterInNameScope(ns)); |
|||
} |
|||
|
|||
private static FuncControlTemplate ScrollViewerTemplate() |
|||
{ |
|||
return new FuncControlTemplate<ScrollViewer>((parent, scope) => |
|||
new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
new ScrollContentPresenter |
|||
{ |
|||
Name = "PART_ContentPresenter", |
|||
[~ScrollContentPresenter.ContentProperty] = parent.GetObservable(ScrollViewer.ContentProperty).ToBinding(), |
|||
[~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty], |
|||
[~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty], |
|||
[~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty], |
|||
[~ScrollContentPresenter.CanHorizontallyScrollProperty] = parent[~ScrollViewer.CanHorizontallyScrollProperty], |
|||
[~ScrollContentPresenter.CanVerticallyScrollProperty] = parent[~ScrollViewer.CanVerticallyScrollProperty], |
|||
}.RegisterInNameScope(scope), |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private static void Layout(Control c) => ((ILayoutRoot)c.GetVisualRoot()!).LayoutManager.ExecuteLayoutPass(); |
|||
} |
|||
} |
|||
@ -1,11 +1,10 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Disposables; |
|||
using System.Threading; |
|||
|
|||
namespace Avalonia.Base.UnitTests.Data |
|||
namespace Avalonia.UnitTests |
|||
{ |
|||
internal sealed class UnitTestSynchronizationContext : SynchronizationContext |
|||
public sealed class UnitTestSynchronizationContext : SynchronizationContext |
|||
{ |
|||
readonly List<Tuple<SendOrPostCallback, object>> _postedCallbacks = |
|||
new List<Tuple<SendOrPostCallback, object>>(); |
|||
Loading…
Reference in new issue