Browse Source

Moved attached layout code to Avalonia.Layout.

Also involved moving `Orientation` enum.
pull/2603/head
Steven Kirk 7 years ago
parent
commit
2210b441a2
  1. 1
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  2. 1
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  3. 1
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  4. 1
      src/Avalonia.Controls/ContextMenu.cs
  5. 1
      src/Avalonia.Controls/GridSplitter.cs
  6. 1
      src/Avalonia.Controls/Menu.cs
  7. 1
      src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
  8. 1
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  9. 1
      src/Avalonia.Controls/Primitives/TabStrip.cs
  10. 1
      src/Avalonia.Controls/Primitives/Track.cs
  11. 7
      src/Avalonia.Controls/ProgressBar.cs
  12. 23
      src/Avalonia.Controls/Repeaters/ItemsRepeater.cs
  13. 5
      src/Avalonia.Controls/Repeaters/RepeaterLayoutContext.cs
  14. 1
      src/Avalonia.Controls/Repeaters/ViewManager.cs
  15. 2
      src/Avalonia.Controls/Repeaters/ViewportManager.cs
  16. 1
      src/Avalonia.Controls/Slider.cs
  17. 4
      src/Avalonia.Controls/StackPanel.cs
  18. 1
      src/Avalonia.Controls/WrapPanel.cs
  19. 12
      src/Avalonia.Layout/AttachedLayout.cs
  20. 45
      src/Avalonia.Layout/ElementManager.cs
  21. 14
      src/Avalonia.Layout/FlowLayoutAlgorithm.cs
  22. 12
      src/Avalonia.Layout/IFlowLayoutAlgorithmDelegates.cs
  23. 2
      src/Avalonia.Layout/LayoutContext.cs
  24. 4
      src/Avalonia.Layout/NonVirtualizingLayout.cs
  25. 2
      src/Avalonia.Layout/Orientation.cs
  26. 40
      src/Avalonia.Layout/OrientationBasedMeasures.cs
  27. 20
      src/Avalonia.Layout/StackLayout.cs
  28. 2
      src/Avalonia.Layout/StackLayoutState.cs
  29. 20
      src/Avalonia.Layout/UniformGridLayout.cs
  30. 8
      src/Avalonia.Layout/UniformGridLayoutState.cs
  31. 2
      src/Avalonia.Layout/Utils/ListUtils.cs
  32. 27
      src/Avalonia.Layout/VirtualizingLayout.cs
  33. 14
      src/Avalonia.Layout/VirtualizingLayoutContext.cs
  34. 1
      tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs
  35. 3
      tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs
  36. 1
      tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs
  37. 3
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  38. 1
      tests/Avalonia.Controls.UnitTests/SliderTests.cs
  39. 3
      tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs
  40. 1
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs
  41. 1
      tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs

1
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@ -5,6 +5,7 @@ using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Repeaters;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages

1
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@ -9,6 +9,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using ReactiveUI.Legacy;
using ReactiveUI;
using Avalonia.Layout;
namespace VirtualizationDemo.ViewModels
{

1
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -24,6 +24,7 @@ using System.Linq;
using Avalonia.Input.Platform;
using System.ComponentModel.DataAnnotations;
using Avalonia.Controls.Utils;
using Avalonia.Layout;
namespace Avalonia.Controls
{

1
src/Avalonia.Controls/ContextMenu.cs

@ -7,6 +7,7 @@ using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.LogicalTree;
namespace Avalonia.Controls

1
src/Avalonia.Controls/GridSplitter.cs

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls

1
src/Avalonia.Controls/Menu.cs

@ -5,6 +5,7 @@ using Avalonia.Controls.Platform;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
namespace Avalonia.Controls
{

1
src/Avalonia.Controls/Presenters/ItemVirtualizer.cs

@ -8,6 +8,7 @@ using System.Reactive.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.Layout;
namespace Avalonia.Controls.Presenters
{

1
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -7,6 +7,7 @@ using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Input;
using Avalonia.Layout;
namespace Avalonia.Controls.Primitives
{

1
src/Avalonia.Controls/Primitives/TabStrip.cs

@ -4,6 +4,7 @@
using Avalonia.Controls.Generators;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
namespace Avalonia.Controls.Primitives
{

1
src/Avalonia.Controls/Primitives/Track.cs

@ -3,6 +3,7 @@
using System;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Metadata;
namespace Avalonia.Controls.Primitives

7
src/Avalonia.Controls/ProgressBar.cs

@ -3,6 +3,7 @@
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
namespace Avalonia.Controls
{
@ -33,8 +34,8 @@ namespace Avalonia.Controls
static ProgressBar()
{
PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical");
PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal");
PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
PseudoClass<ProgressBar>(IsIndeterminateProperty, ":indeterminate");
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.UpdateIndicatorWhenPropChanged);
@ -120,4 +121,4 @@ namespace Avalonia.Controls
UpdateIndicator(Bounds.Size);
}
}
}
}

23
src/Avalonia.Controls/Repeaters/ItemsRepeater.cs

@ -8,6 +8,7 @@ using System.Collections;
using System.Collections.Specialized;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
namespace Avalonia.Controls.Repeaters
{
@ -38,8 +39,8 @@ namespace Avalonia.Controls.Repeaters
/// <summary>
/// Defines the <see cref="Layout"/> property.
/// </summary>
public static readonly AvaloniaProperty<Layout> LayoutProperty =
AvaloniaProperty.Register<ItemsRepeater, Layout>(nameof(Layout), new StackLayout());
public static readonly AvaloniaProperty<AttachedLayout> LayoutProperty =
AvaloniaProperty.Register<ItemsRepeater, AttachedLayout>(nameof(Layout), new StackLayout());
/// <summary>
/// Defines the <see cref="VerticalCacheLength"/> property.
@ -58,7 +59,6 @@ namespace Avalonia.Controls.Repeaters
private IEnumerable _items;
private VirtualizingLayoutContext _layoutContext;
private NotifyCollectionChangedEventArgs _processingItemsSourceChange;
private Size _lastAvailableSize;
private bool _isLayoutInProgress;
private ItemsRepeaterElementPreparedEventArgs _elementPreparedArgs;
private ItemsRepeaterElementClearingEventArgs _elementClearingArgs;
@ -87,7 +87,7 @@ namespace Avalonia.Controls.Repeaters
/// The layout used to size and position elements. The default is a StackLayout with
/// vertical orientation.
/// </value>
public Layout Layout
public AttachedLayout Layout
{
get => GetValue(LayoutProperty);
set => SetValue(LayoutProperty, value);
@ -300,7 +300,6 @@ namespace Avalonia.Controls.Repeaters
}
_viewportManager.SetLayoutExtent(extent);
_lastAvailableSize = availableSize;
return desiredSize;
}
finally
@ -396,7 +395,7 @@ namespace Avalonia.Controls.Repeaters
}
else if (property == LayoutProperty)
{
OnLayoutChanged((Layout)args.OldValue, (Layout)args.NewValue);
OnLayoutChanged((AttachedLayout)args.OldValue, (AttachedLayout)args.NewValue);
}
else if (property == HorizontalCacheLengthProperty)
{
@ -479,7 +478,7 @@ namespace Avalonia.Controls.Repeaters
throw new InvalidOperationException("Cannot make an Anchor when there is no attached layout.");
}
element = GetLayoutContext().GetOrCreateElementAt(index);
element = (IControl)GetLayoutContext().GetOrCreateElementAt(index);
element.Measure(Size.Infinity);
}
@ -566,7 +565,7 @@ namespace Avalonia.Controls.Repeaters
if (Layout is VirtualizingLayout virtualLayout)
{
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
virtualLayout.OnItemsChangedCore(GetLayoutContext(), newValue, args);
virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args);
}
else if (Layout is NonVirtualizingLayout nonVirtualLayout)
{
@ -605,14 +604,14 @@ namespace Avalonia.Controls.Repeaters
try
{
virtualLayout.OnItemsChangedCore(GetLayoutContext(), newValue, args);
virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args);
}
finally
{
_processingItemsSourceChange = null;
}
}
else if (Layout is NonVirtualizingLayout nonVirtualLayout)
else if (Layout is NonVirtualizingLayout)
{
// Walk through all the elements and make sure they are cleared for
// non-virtualizing layouts.
@ -631,7 +630,7 @@ namespace Avalonia.Controls.Repeaters
InvalidateMeasure();
}
private void OnLayoutChanged(Layout oldValue, Layout newValue)
private void OnLayoutChanged(AttachedLayout oldValue, AttachedLayout newValue)
{
if (_isLayoutInProgress)
{
@ -693,7 +692,7 @@ namespace Avalonia.Controls.Repeaters
{
if (Layout is VirtualizingLayout virtualLayout)
{
virtualLayout.OnItemsChangedCore(GetLayoutContext(), sender, args);
virtualLayout.OnItemsChanged(GetLayoutContext(), sender, args);
}
else
{

5
src/Avalonia.Controls/Repeaters/RepeaterLayoutContext.cs

@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Layout;
namespace Avalonia.Controls.Repeaters
{
@ -47,7 +48,7 @@ namespace Avalonia.Controls.Repeaters
protected override int ItemCountCore() => _owner.ItemsSourceView?.Count ?? 0;
protected override IControl GetOrCreateElementAtCore(int index, ElementRealizationOptions options)
protected override ILayoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options)
{
return _owner.GetElementImpl(
index,
@ -57,7 +58,7 @@ namespace Avalonia.Controls.Repeaters
protected override object GetItemAtCore(int index) => _owner.ItemsSourceView.GetAt(index);
protected override void RecycleElementCore(IControl element) => _owner.ClearElementImpl(element);
protected override void RecycleElementCore(ILayoutable element) => _owner.ClearElementImpl((IControl)element);
protected override Rect RealizationRectCore() => _owner.RealizationWindow;
}

1
src/Avalonia.Controls/Repeaters/ViewManager.cs

@ -9,6 +9,7 @@ using System.Collections.Specialized;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Repeaters

2
src/Avalonia.Controls/Repeaters/ViewportManager.cs

@ -234,7 +234,7 @@ namespace Avalonia.Controls.Repeaters
////element.CanBeScrollAnchor(true);
}
public void OnElementCleared(IControl element)
public void OnElementCleared(ILayoutable element)
{
////element.CanBeScrollAnchor(false);
}

1
src/Avalonia.Controls/Slider.cs

@ -5,6 +5,7 @@ using System;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
namespace Avalonia.Controls
{

4
src/Avalonia.Controls/StackPanel.cs

@ -17,13 +17,13 @@ namespace Avalonia.Controls
/// Defines the <see cref="Spacing"/> property.
/// </summary>
public static readonly StyledProperty<double> SpacingProperty =
AvaloniaProperty.Register<StackPanel, double>(nameof(Spacing));
StackLayout.SpacingProperty.AddOwner<StackPanel>();
/// <summary>
/// Defines the <see cref="Orientation"/> property.
/// </summary>
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<StackPanel, Orientation>(nameof(Orientation), Orientation.Vertical);
StackLayout.OrientationProperty.AddOwner<StackPanel>();
/// <summary>
/// Initializes static members of the <see cref="StackPanel"/> class.

1
src/Avalonia.Controls/WrapPanel.cs

@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Linq;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Utilities;
using static System.Math;

12
src/Avalonia.Controls/Repeaters/Layout.cs → src/Avalonia.Layout/AttachedLayout.cs

@ -5,12 +5,12 @@
using System;
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
/// <summary>
/// Represents the base class for an object that sizes and arranges child elements for a host.
/// </summary>
public abstract class Layout : AvaloniaObject
public abstract class AttachedLayout : AvaloniaObject
{
internal string LayoutId { get; set; }
@ -26,7 +26,7 @@ namespace Avalonia.Controls.Repeaters
/// <summary>
/// Initializes any per-container state the layout requires when it is attached to an
/// <see cref="IControl"/> container.
/// <see cref="ILayoutable"/> container.
/// </summary>
/// <param name="context">
/// The context object that facilitates communication between the layout and its host
@ -49,7 +49,7 @@ namespace Avalonia.Controls.Repeaters
public abstract void InitializeForContext(LayoutContext context);
/// <summary>
/// Removes any state the layout previously stored on the IControl container.
/// Removes any state the layout previously stored on the ILayoutable container.
/// </summary>
/// <param name="context">
/// The context object that facilitates communication between the layout and its host
@ -61,7 +61,7 @@ namespace Avalonia.Controls.Repeaters
/// Suggests a DesiredSize for a container element. A container element that supports
/// attached layouts should call this method from their own MeasureOverride implementations
/// to form a recursive layout update. The attached layout is expected to call the Measure
/// for each of the container’s IControl children.
/// for each of the container’s ILayoutable children.
/// </summary>
/// <param name="context">
/// The context object that facilitates communication between the layout and its host
@ -91,7 +91,7 @@ namespace Avalonia.Controls.Repeaters
public abstract Size Arrange(LayoutContext context, Size finalSize);
/// <summary>
/// Invalidates the measurement state (layout) for all IControl containers that reference
/// Invalidates the measurement state (layout) for all ILayoutable containers that reference
/// this layout.
/// </summary>
protected void InvalidateMeasure() => MeasureInvalidated?.Invoke(this, EventArgs.Empty);

45
src/Avalonia.Controls/Repeaters/ElementManager.cs → src/Avalonia.Layout/ElementManager.cs

@ -6,14 +6,13 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using Avalonia.Controls.Utils;
using Avalonia.Layout.Utils;
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
internal class ElementManager
{
private readonly List<IControl> _realizedElements = new List<IControl>();
private readonly List<ILayoutable> _realizedElements = new List<ILayoutable>();
private readonly List<Rect> _realizedElementLayoutBounds = new List<Rect>();
private int _firstRealizedDataIndex;
private VirtualizingLayoutContext _context;
@ -34,7 +33,7 @@ namespace Avalonia.Controls.Repeaters
public void SetContext(VirtualizingLayoutContext virtualContext) => _context = virtualContext;
public void OnBeginMeasure(Orientation orientation)
public void OnBeginMeasure(ScrollOrientation orientation)
{
if (_context != null)
{
@ -69,9 +68,9 @@ namespace Avalonia.Controls.Repeaters
return IsVirtualizingContext ? _realizedElements.Count : _context.ItemCount;
}
public IControl GetAt(int realizedIndex)
public ILayoutable GetAt(int realizedIndex)
{
IControl element;
ILayoutable element;
if (IsVirtualizingContext)
{
@ -100,7 +99,7 @@ namespace Avalonia.Controls.Repeaters
return element;
}
public void Add(IControl element, int dataIndex)
public void Add(ILayoutable element, int dataIndex)
{
if (_realizedElements.Count == 0)
{
@ -111,7 +110,7 @@ namespace Avalonia.Controls.Repeaters
_realizedElementLayoutBounds.Add(default);
}
public void Insert(int realizedIndex, int dataIndex, IControl element)
public void Insert(int realizedIndex, int dataIndex, ILayoutable element)
{
if (realizedIndex == 0)
{
@ -208,7 +207,7 @@ namespace Avalonia.Controls.Repeaters
public bool IsIndexValidInData(int currentIndex) => currentIndex >= 0 && currentIndex < _context.ItemCount;
public IControl GetRealizedElement(int dataIndex)
public ILayoutable GetRealizedElement(int dataIndex)
{
return IsVirtualizingContext ?
GetAt(GetRealizedRangeIndexFromDataIndex(dataIndex)) :
@ -236,7 +235,7 @@ namespace Avalonia.Controls.Repeaters
}
}
public bool IsWindowConnected(in Rect window, Orientation orientation, bool scrollOrientationSameAsFlow)
public bool IsWindowConnected(in Rect window, ScrollOrientation orientation, bool scrollOrientationSameAsFlow)
{
bool intersects = false;
@ -246,14 +245,14 @@ namespace Avalonia.Controls.Repeaters
var lastElementBounds = GetLayoutBoundsForRealizedIndex(GetRealizedElementCount() - 1);
var effectiveOrientation = scrollOrientationSameAsFlow ?
(orientation == Orientation.Vertical ? Orientation.Horizontal : Orientation.Vertical) :
(orientation == ScrollOrientation.Vertical ? ScrollOrientation.Horizontal : ScrollOrientation.Vertical) :
orientation;
var windowStart = effectiveOrientation == Orientation.Vertical ? window.Y : window.X;
var windowEnd = effectiveOrientation == Orientation.Vertical ? window.Y + window.Height : window.X + window.Width;
var firstElementStart = effectiveOrientation == Orientation.Vertical ? firstElementBounds.Y : firstElementBounds.X;
var lastElementEnd = effectiveOrientation == Orientation.Vertical ? lastElementBounds.Y + lastElementBounds.Height : lastElementBounds.X + lastElementBounds.Width;
var windowStart = effectiveOrientation == ScrollOrientation.Vertical ? window.Y : window.X;
var windowEnd = effectiveOrientation == ScrollOrientation.Vertical ? window.Y + window.Height : window.X + window.Width;
var firstElementStart = effectiveOrientation == ScrollOrientation.Vertical ? firstElementBounds.Y : firstElementBounds.X;
var lastElementEnd = effectiveOrientation == ScrollOrientation.Vertical ? lastElementBounds.Y + lastElementBounds.Height : lastElementBounds.X + lastElementBounds.Width;
intersects =
firstElementStart <= windowEnd &&
@ -298,7 +297,7 @@ namespace Avalonia.Controls.Repeaters
}
}
public int GetElementDataIndex(IControl suggestedAnchor)
public int GetElementDataIndex(ILayoutable suggestedAnchor)
{
var it = _realizedElements.IndexOf(suggestedAnchor);
return it != -1 ? GetDataIndexFromRealizedRangeIndex(it) : -1;
@ -314,7 +313,7 @@ namespace Avalonia.Controls.Repeaters
return IsVirtualizingContext ? dataIndex - _firstRealizedDataIndex : dataIndex;
}
private void DiscardElementsOutsideWindow(in Rect window, Orientation orientation)
private void DiscardElementsOutsideWindow(in Rect window, ScrollOrientation orientation)
{
// The following illustration explains the cutoff indices.
// We will clear all the realized elements from both ends
@ -369,12 +368,12 @@ namespace Avalonia.Controls.Repeaters
}
}
private static bool Intersects(in Rect lhs, in Rect rhs, Orientation orientation)
private static bool Intersects(in Rect lhs, in Rect rhs, ScrollOrientation orientation)
{
var lhsStart = orientation == Orientation.Vertical ? lhs.Y : lhs.X;
var lhsEnd = orientation == Orientation.Vertical ? lhs.Y + lhs.Height : lhs.X + lhs.Width;
var rhsStart = orientation == Orientation.Vertical ? rhs.Y : rhs.X;
var rhsEnd = orientation == Orientation.Vertical ? rhs.Y + rhs.Height : rhs.X + rhs.Width;
var lhsStart = orientation == ScrollOrientation.Vertical ? lhs.Y : lhs.X;
var lhsEnd = orientation == ScrollOrientation.Vertical ? lhs.Y + lhs.Height : lhs.X + lhs.Width;
var rhsStart = orientation == ScrollOrientation.Vertical ? rhs.Y : rhs.X;
var rhsEnd = orientation == ScrollOrientation.Vertical ? rhs.Y + rhs.Height : rhs.X + rhs.Width;
return lhsEnd >= rhsStart && lhsStart <= rhsEnd;
}

14
src/Avalonia.Controls/Repeaters/FlowLayoutAlgorithm.cs → src/Avalonia.Layout/FlowLayoutAlgorithm.cs

@ -6,7 +6,7 @@
using System;
using System.Collections.Specialized;
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
internal class FlowLayoutAlgorithm
{
@ -72,7 +72,7 @@ namespace Avalonia.Controls.Repeaters
bool isWrapping,
double minItemSpacing,
double lineSpacing,
Orientation orientation,
ScrollOrientation orientation,
string layoutId)
{
_orientation.ScrollOrientation = orientation;
@ -135,7 +135,7 @@ namespace Avalonia.Controls.Repeaters
}
public Size MeasureElement(
IControl element,
ILayoutable element,
int index,
Size availableSize,
VirtualizingLayoutContext context)
@ -469,9 +469,9 @@ namespace Avalonia.Controls.Repeaters
private Rect EstimateExtent(Size availableSize, string layoutId)
{
IControl firstRealizedElement = null;
ILayoutable firstRealizedElement = null;
Rect firstBounds = new Rect();
IControl lastRealizedElement = null;
ILayoutable lastRealizedElement = null;
Rect lastBounds = new Rect();
int firstDataIndex = -1;
int lastDataIndex = -1;
@ -667,7 +667,7 @@ namespace Avalonia.Controls.Repeaters
}
}
public IControl GetElementIfRealized(int dataIndex)
public ILayoutable GetElementIfRealized(int dataIndex)
{
if (_elementManager.IsDataIndexRealized(dataIndex))
{
@ -677,7 +677,7 @@ namespace Avalonia.Controls.Repeaters
return null;
}
public bool TryAddElement0(IControl element)
public bool TryAddElement0(ILayoutable element)
{
if (_elementManager.GetRealizedElementCount() == 0)
{

12
src/Avalonia.Controls/Repeaters/IFlowLayoutAlgorithmDelegates.cs → src/Avalonia.Layout/IFlowLayoutAlgorithmDelegates.cs

@ -3,11 +3,7 @@
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
internal struct FlowLayoutAnchorInfo
{
@ -25,14 +21,14 @@ namespace Avalonia.Controls.Repeaters
Rect Algorithm_GetExtent(
Size availableSize,
VirtualizingLayoutContext context,
IControl firstRealized,
ILayoutable firstRealized,
int firstRealizedItemIndex,
Rect firstRealizedLayoutBounds,
IControl lastRealized,
ILayoutable lastRealized,
int lastRealizedItemIndex,
Rect lastRealizedLayoutBounds);
void Algorithm_OnElementMeasured(
IControl element,
ILayoutable element,
int index,
Size availableSize,
Size measureSize,

2
src/Avalonia.Controls/Repeaters/LayoutContext.cs → src/Avalonia.Layout/LayoutContext.cs

@ -3,7 +3,7 @@
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
/// <summary>
/// Represents the base class for an object that facilitates communication between an attached

4
src/Avalonia.Controls/Repeaters/NonVirtualizingLayout.cs → src/Avalonia.Layout/NonVirtualizingLayout.cs

@ -3,13 +3,13 @@
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
/// <summary>
/// Represents the base class for an object that sizes and arranges child elements for a host
/// and and does not support virtualization.
/// </summary>
public abstract class NonVirtualizingLayout : Layout
public abstract class NonVirtualizingLayout : AttachedLayout
{
}
}

2
src/Avalonia.Controls/Orientation.cs → src/Avalonia.Layout/Orientation.cs

@ -1,7 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Controls
namespace Avalonia.Layout
{
/// <summary>
/// Defines vertical or horizontal orientation.

40
src/Avalonia.Controls/Repeaters/OrientationBasedMeasures.cs → src/Avalonia.Layout/OrientationBasedMeasures.cs

@ -3,24 +3,30 @@
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
internal enum ScrollOrientation
{
Vertical,
Horizontal,
}
internal class OrientationBasedMeasures
{
public Orientation ScrollOrientation { get; set; } = Orientation.Vertical;
public ScrollOrientation ScrollOrientation { get; set; } = ScrollOrientation.Vertical;
public double Major(in Size size) => ScrollOrientation == Orientation.Vertical ? size.Height : size.Width;
public double Minor(in Size size) => ScrollOrientation == Orientation.Vertical ? size.Width : size.Height;
public double MajorSize(in Rect rect) => ScrollOrientation == Orientation.Vertical ? rect.Height : rect.Width;
public double MinorSize(in Rect rect) => ScrollOrientation == Orientation.Vertical ? rect.Width : rect.Height;
public double MajorStart(in Rect rect) => ScrollOrientation == Orientation.Vertical ? rect.Y : rect.X;
public double MinorStart(in Rect rect) => ScrollOrientation == Orientation.Vertical ? rect.X : rect.Y;
public double MajorEnd(in Rect rect) => ScrollOrientation == Orientation.Vertical ? rect.Bottom : rect.Right;
public double MinorEnd(in Rect rect) => ScrollOrientation == Orientation.Vertical ? rect.Right : rect.Bottom;
public double Major(in Size size) => ScrollOrientation == ScrollOrientation.Vertical ? size.Height : size.Width;
public double Minor(in Size size) => ScrollOrientation == ScrollOrientation.Vertical ? size.Width : size.Height;
public double MajorSize(in Rect rect) => ScrollOrientation == ScrollOrientation.Vertical ? rect.Height : rect.Width;
public double MinorSize(in Rect rect) => ScrollOrientation == ScrollOrientation.Vertical ? rect.Width : rect.Height;
public double MajorStart(in Rect rect) => ScrollOrientation == ScrollOrientation.Vertical ? rect.Y : rect.X;
public double MinorStart(in Rect rect) => ScrollOrientation == ScrollOrientation.Vertical ? rect.X : rect.Y;
public double MajorEnd(in Rect rect) => ScrollOrientation == ScrollOrientation.Vertical ? rect.Bottom : rect.Right;
public double MinorEnd(in Rect rect) => ScrollOrientation == ScrollOrientation.Vertical ? rect.Right : rect.Bottom;
public void SetMajorSize(ref Rect rect, double value)
{
if (ScrollOrientation == Orientation.Vertical)
if (ScrollOrientation == ScrollOrientation.Vertical)
{
rect = rect.WithHeight(value);
}
@ -32,7 +38,7 @@ namespace Avalonia.Controls.Repeaters
public void SetMinorSize(ref Rect rect, double value)
{
if (ScrollOrientation == Orientation.Vertical)
if (ScrollOrientation == ScrollOrientation.Vertical)
{
rect = rect.WithWidth(value);
}
@ -44,7 +50,7 @@ namespace Avalonia.Controls.Repeaters
public void SetMajorStart(ref Rect rect, double value)
{
if (ScrollOrientation == Orientation.Vertical)
if (ScrollOrientation == ScrollOrientation.Vertical)
{
rect = rect.WithY(value);
}
@ -56,7 +62,7 @@ namespace Avalonia.Controls.Repeaters
public void SetMinorStart(ref Rect rect, double value)
{
if (ScrollOrientation == Orientation.Vertical)
if (ScrollOrientation == ScrollOrientation.Vertical)
{
rect = rect.WithX(value);
}
@ -68,21 +74,21 @@ namespace Avalonia.Controls.Repeaters
public Rect MinorMajorRect(double minor, double major, double minorSize, double majorSize)
{
return ScrollOrientation == Orientation.Vertical ?
return ScrollOrientation == ScrollOrientation.Vertical ?
new Rect(minor, major, minorSize, majorSize) :
new Rect(major, minor, majorSize, minorSize);
}
public Point MinorMajorPoint(double minor, double major)
{
return ScrollOrientation == Orientation.Vertical ?
return ScrollOrientation == ScrollOrientation.Vertical ?
new Point(minor, major) :
new Point(major, minor);
}
public Size MinorMajorSize(double minor, double major)
{
return ScrollOrientation == Orientation.Vertical ?
return ScrollOrientation == ScrollOrientation.Vertical ?
new Size(minor, major) :
new Size(major, minor);
}

20
src/Avalonia.Controls/Repeaters/StackLayout.cs → src/Avalonia.Layout/StackLayout.cs

@ -6,7 +6,7 @@
using System;
using System.Collections.Specialized;
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
/// <summary>
/// Arranges elements into a single line (with spacing) that can be oriented horizontally or vertically.
@ -17,13 +17,13 @@ namespace Avalonia.Controls.Repeaters
/// Defines the <see cref="Orientation"/> property.
/// </summary>
public static readonly StyledProperty<Orientation> OrientationProperty =
StackPanel.OrientationProperty.AddOwner<StackLayout>();
AvaloniaProperty.Register<StackLayout, Orientation>(nameof(Orientation), Orientation.Vertical);
/// <summary>
/// Defines the <see cref="Spacing"/> property.
/// </summary>
public static readonly StyledProperty<double> SpacingProperty =
StackPanel.SpacingProperty.AddOwner<StackLayout>();
AvaloniaProperty.Register<StackLayout, double>(nameof(Spacing));
private readonly OrientationBasedMeasures _orientation = new OrientationBasedMeasures();
@ -61,10 +61,10 @@ namespace Avalonia.Controls.Repeaters
internal Rect GetExtent(
Size availableSize,
VirtualizingLayoutContext context,
IControl firstRealized,
ILayoutable firstRealized,
int firstRealizedItemIndex,
Rect firstRealizedLayoutBounds,
IControl lastRealized,
ILayoutable lastRealized,
int lastRealizedItemIndex,
Rect lastRealizedLayoutBounds)
{
@ -97,7 +97,7 @@ namespace Avalonia.Controls.Repeaters
}
internal void OnElementMeasured(
IControl element,
ILayoutable element,
int index,
Size availableSize,
Size measureSize,
@ -164,10 +164,10 @@ namespace Avalonia.Controls.Repeaters
Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent(
Size availableSize,
VirtualizingLayoutContext context,
IControl firstRealized,
ILayoutable firstRealized,
int firstRealizedItemIndex,
Rect firstRealizedLayoutBounds,
IControl lastRealized,
ILayoutable lastRealized,
int lastRealizedItemIndex,
Rect lastRealizedLayoutBounds)
{
@ -182,7 +182,7 @@ namespace Avalonia.Controls.Repeaters
lastRealizedLayoutBounds);
}
void IFlowLayoutAlgorithmDelegates.Algorithm_OnElementMeasured(IControl element, int index, Size availableSize, Size measureSize, Size desiredSize, Size provisionalArrangeSize, VirtualizingLayoutContext context)
void IFlowLayoutAlgorithmDelegates.Algorithm_OnElementMeasured(ILayoutable element, int index, Size availableSize, Size measureSize, Size desiredSize, Size provisionalArrangeSize, VirtualizingLayoutContext context)
{
OnElementMeasured(
element,
@ -295,7 +295,7 @@ namespace Avalonia.Controls.Repeaters
{
//Note: For StackLayout Vertical Orientation means we have a Vertical ScrollOrientation.
//Horizontal Orientation means we have a Horizontal ScrollOrientation.
_orientation.ScrollOrientation = (Orientation)e.NewValue;
_orientation.ScrollOrientation = (ScrollOrientation)e.NewValue;
}
InvalidateLayout();

2
src/Avalonia.Controls/Repeaters/StackLayoutState.cs → src/Avalonia.Layout/StackLayoutState.cs

@ -7,7 +7,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
/// <summary>
/// Represents the state of a StackLayout.

20
src/Avalonia.Controls/Repeaters/UniformGridLayout.cs → src/Avalonia.Layout/UniformGridLayout.cs

@ -6,7 +6,7 @@
using System;
using System.Collections.Specialized;
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
/// <summary>
/// Defines constants that specify how items are aligned on the non-scrolling or non-virtualizing axis.
@ -114,7 +114,7 @@ namespace Avalonia.Controls.Repeaters
/// Defines the <see cref="Orientation"/> property.
/// </summary>
public static readonly StyledProperty<Orientation> OrientationProperty =
StackPanel.OrientationProperty.AddOwner<StackLayout>();
StackLayout.OrientationProperty.AddOwner<UniformGridLayout>();
private readonly OrientationBasedMeasures _orientation = new OrientationBasedMeasures();
private double _minItemWidth = double.NaN;
@ -316,10 +316,10 @@ namespace Avalonia.Controls.Repeaters
Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent(
Size availableSize,
VirtualizingLayoutContext context,
IControl firstRealized,
ILayoutable firstRealized,
int firstRealizedItemIndex,
Rect firstRealizedLayoutBounds,
IControl lastRealized,
ILayoutable lastRealized,
int lastRealizedItemIndex,
Rect lastRealizedLayoutBounds)
{
@ -359,7 +359,7 @@ namespace Avalonia.Controls.Repeaters
return extent;
}
void IFlowLayoutAlgorithmDelegates.Algorithm_OnElementMeasured(IControl element, int index, Size availableSize, Size measureSize, Size desiredSize, Size provisionalArrangeSize, VirtualizingLayoutContext context)
void IFlowLayoutAlgorithmDelegates.Algorithm_OnElementMeasured(ILayoutable element, int index, Size availableSize, Size measureSize, Size desiredSize, Size provisionalArrangeSize, VirtualizingLayoutContext context)
{
}
@ -440,7 +440,7 @@ namespace Avalonia.Controls.Repeaters
//Note: For UniformGridLayout Vertical Orientation means we have a Horizontal ScrollOrientation. Horizontal Orientation means we have a Vertical ScrollOrientation.
//i.e. the properties are the inverse of each other.
var scrollOrientation = (orientation == Orientation.Horizontal) ? Orientation.Vertical : Orientation.Horizontal;
var scrollOrientation = (orientation == Orientation.Horizontal) ? ScrollOrientation.Vertical : ScrollOrientation.Horizontal;
_orientation.ScrollOrientation = scrollOrientation;
}
else if (args.Property == MinColumnSpacingProperty)
@ -475,7 +475,7 @@ namespace Avalonia.Controls.Repeaters
{
var minItemSpacing = MinItemSpacing;
var gridState = (UniformGridLayoutState)context.LayoutState;
return _orientation.ScrollOrientation == Orientation.Vertical?
return _orientation.ScrollOrientation == ScrollOrientation.Vertical?
gridState.EffectiveItemWidth + minItemSpacing :
gridState.EffectiveItemHeight + minItemSpacing;
}
@ -484,7 +484,7 @@ namespace Avalonia.Controls.Repeaters
{
var lineSpacing = LineSpacing;
var gridState = (UniformGridLayoutState)context.LayoutState;
return _orientation.ScrollOrientation == Orientation.Vertical ?
return _orientation.ScrollOrientation == ScrollOrientation.Vertical ?
gridState.EffectiveItemHeight + lineSpacing :
gridState.EffectiveItemWidth + lineSpacing;
}
@ -503,8 +503,8 @@ namespace Avalonia.Controls.Repeaters
Rect bounds = _orientation.MinorMajorRect(
indexInRow * GetMinorSizeWithSpacing(context) + _orientation.MinorStart(lastExtent),
rowIndex * GetMajorSizeWithSpacing(context) + _orientation.MajorStart(lastExtent),
_orientation.ScrollOrientation == Orientation.Vertical ? gridState.EffectiveItemWidth : gridState.EffectiveItemHeight,
_orientation.ScrollOrientation == Orientation.Vertical ? gridState.EffectiveItemHeight : gridState.EffectiveItemWidth);
_orientation.ScrollOrientation == ScrollOrientation.Vertical ? gridState.EffectiveItemWidth : gridState.EffectiveItemHeight,
_orientation.ScrollOrientation == ScrollOrientation.Vertical ? gridState.EffectiveItemHeight : gridState.EffectiveItemWidth);
return bounds;
}

8
src/Avalonia.Controls/Repeaters/UniformGridLayoutState.cs → src/Avalonia.Layout/UniformGridLayoutState.cs

@ -4,11 +4,9 @@
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
/// <summary>
/// Represents the state of a <see cref="UniformGridLayout"/>.
@ -20,7 +18,7 @@ namespace Avalonia.Controls.Repeaters
// If it does not, then we need to do context.GetElement(0) at which point we have requested an element and are on point to clear it.
// If we are responsible for clearing element 0 we keep m_cachedFirstElement valid.
// If we are not (because FlowLayoutAlgorithm is holding it for us) then we just null out this field and use the one from FlowLayoutAlgorithm.
private IControl _cachedFirstElement;
private ILayoutable _cachedFirstElement;
internal FlowLayoutAlgorithm FlowAlgorithm { get; } = new FlowLayoutAlgorithm();
internal double EffectiveItemWidth { get; private set; }
@ -86,7 +84,7 @@ namespace Avalonia.Controls.Repeaters
}
private void SetSize(
IControl element,
ILayoutable element,
double layoutItemWidth,
double LayoutItemHeight,
Size availableSize,

2
src/Avalonia.Controls/Utils/ListUtils.cs → src/Avalonia.Layout/Utils/ListUtils.cs

@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Controls.Utils
namespace Avalonia.Layout.Utils
{
internal static class ListUtils
{

27
src/Avalonia.Controls/Repeaters/VirtualizingLayout.cs → src/Avalonia.Layout/VirtualizingLayout.cs

@ -5,13 +5,13 @@
using System.Collections.Specialized;
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
/// <summary>
/// Represents the base class for an object that sizes and arranges child elements for a host
/// and supports virtualization.
/// </summary>
public abstract class VirtualizingLayout : Layout
public abstract class VirtualizingLayout : AttachedLayout
{
/// <inheritdoc/>
public sealed override void InitializeForContext(LayoutContext context)
@ -37,9 +37,28 @@ namespace Avalonia.Controls.Repeaters
return ArrangeOverride((VirtualizingLayoutContext)context, finalSize);
}
/// <summary>
/// Notifies the layout when the data collection assigned to the container element (Items)
/// has changed.
/// </summary>
/// <param name="context">
/// The context object that facilitates communication between the layout and its host
/// container.
/// </param>
/// <param name="source">The data source.</param>
/// <param name="args">Data about the collection change.</param>
/// <remarks>
/// Override <see cref="OnItemsChangedCore(VirtualizingLayoutContext, object, NotifyCollectionChangedEventArgs)"/>
/// to provide the behavior for this method in a derived class.
/// </remarks>
public void OnItemsChanged(
VirtualizingLayoutContext context,
object source,
NotifyCollectionChangedEventArgs args) => OnItemsChangedCore(context, source, args);
/// <summary>
/// When overridden in a derived class, initializes any per-container state the layout
/// requires when it is attached to an IControl container.
/// requires when it is attached to an ILayoutable container.
/// </summary>
/// <param name="context">
/// The context object that facilitates communication between the layout and its host
@ -51,7 +70,7 @@ namespace Avalonia.Controls.Repeaters
/// <summary>
/// When overridden in a derived class, removes any state the layout previously stored on
/// the IControl container.
/// the ILayoutable container.
/// </summary>
/// <param name="context">
/// The context object that facilitates communication between the layout and its host

14
src/Avalonia.Controls/Repeaters/VirtualizingLayoutContext.cs → src/Avalonia.Layout/VirtualizingLayoutContext.cs

@ -5,7 +5,7 @@
using System;
namespace Avalonia.Controls.Repeaters
namespace Avalonia.Layout
{
/// <summary>
/// Defines constants that specify whether to suppress automatic recycling of the retrieved
@ -114,7 +114,7 @@ namespace Avalonia.Controls.Repeaters
/// This method calls <see cref="GetOrCreateElementAtCore(int, ElementRealizationOptions)"/>
/// with options set to None. GetElementAtCore must be implemented in a derived class.
/// </remarks>
public IControl GetOrCreateElementAt(int index)
public ILayoutable GetOrCreateElementAt(int index)
=> GetOrCreateElementAtCore(index, ElementRealizationOptions.None);
/// <summary>
@ -138,7 +138,7 @@ namespace Avalonia.Controls.Repeaters
/// advanced layouts that choose to explicitly manage the realization and recycling of
/// elements as a performance optimization.
/// </remarks>
public IControl GetOrCreateElementAt(int index, ElementRealizationOptions options)
public ILayoutable GetOrCreateElementAt(int index, ElementRealizationOptions options)
=> GetOrCreateElementAtCore(index, options);
/// <summary>
@ -146,10 +146,10 @@ namespace Avalonia.Controls.Repeaters
/// </summary>
/// <param name="element">The element to clear.</param>
/// <remarks>
/// This method calls <see cref="RecycleElementCore(IControl)"/>, which must be implemented
/// This method calls <see cref="RecycleElementCore(ILayoutable)"/>, which must be implemented
/// in a derived class.
/// </remarks>
public void RecycleElement(IControl element) => RecycleElementCore(element);
public void RecycleElement(ILayoutable element) => RecycleElementCore(element);
/// <summary>
/// When implemented in a derived class, retrieves the number of items in the data.
@ -178,13 +178,13 @@ namespace Avalonia.Controls.Repeaters
/// A value of <see cref="ElementRealizationOptions"/> that specifies whether to suppress
/// automatic recycling of the retrieved element or force creation of a new element.
/// </param>
protected abstract IControl GetOrCreateElementAtCore(int index, ElementRealizationOptions options);
protected abstract ILayoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options);
/// <summary>
/// When implemented in a derived class, clears the specified UIElement and allows it to be
/// either re-used or released.
/// </summary>
/// <param name="element">The element to clear.</param>
protected abstract void RecycleElementCore(IControl element);
protected abstract void RecycleElementCore(ILayoutable element);
}
}

1
tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs

@ -8,6 +8,7 @@ using Xunit;
using System.Windows.Input;
using System;
using Avalonia.Data.Converters;
using Avalonia.Layout;
namespace Avalonia.Base.UnitTests.Data.Converters
{

3
tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs

@ -6,6 +6,7 @@ using System.ComponentModel;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Markup.Data;
using Avalonia.Styling;
using Xunit;
@ -199,4 +200,4 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
}
}
}

1
tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Xunit;

3
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@ -6,6 +6,7 @@ using System.Collections.Generic;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -113,4 +114,4 @@ namespace Avalonia.Controls.UnitTests
};
}
}
}
}

1
tests/Avalonia.Controls.UnitTests/SliderTests.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Layout;
using Xunit;
namespace Avalonia.Controls.UnitTests

3
tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Layout;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -93,4 +94,4 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(100, 0, 100, 50), target.Children[1].Bounds);
}
}
}
}

1
tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs

@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.UnitTests;
using Xunit;

1
tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs

@ -1,6 +1,7 @@
using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.UnitTests;

Loading…
Cancel
Save