Browse Source

add regular snap points to virutalizing panel

pull/9961/head
Emmanuel Hansen 4 years ago
parent
commit
e6c60ddfef
  1. 5
      src/Avalonia.Controls/ListBox.cs
  2. 83
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  3. 8
      src/Avalonia.Controls/ScrollViewer.cs
  4. 4
      src/Avalonia.Controls/StackPanel.cs
  5. 190
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  6. 3
      src/Avalonia.Themes.Fluent/Controls/ListBox.xaml

5
src/Avalonia.Controls/ListBox.cs

@ -21,7 +21,10 @@ namespace Avalonia.Controls
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary>
private static readonly FuncTemplate<Panel> DefaultPanel =
new FuncTemplate<Panel>(() => new VirtualizingStackPanel());
new FuncTemplate<Panel>(() => new VirtualizingStackPanel()
{
AreVerticalSnapPointsRegular= true,
});
/// <summary>
/// Defines the <see cref="Scroll"/> property.

83
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -23,43 +23,21 @@ namespace Avalonia.Controls.Presenters
private ILogicalScrollable? _logicalScrollable;
private EventHandler? _scrollInvalidated;
public event EventHandler<RoutedEventArgs> HorizontalSnapPointsChanged
{
add
{
if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
scrollSnapPointsInfo.HorizontalSnapPointsChanged += value;
}
}
remove
{
if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
scrollSnapPointsInfo.HorizontalSnapPointsChanged -= value;
}
}
}
public event EventHandler<RoutedEventArgs> VerticalSnapPointsChanged
{
add
{
if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
scrollSnapPointsInfo.VerticalSnapPointsChanged += value;
}
}
/// <summary>
/// Defines the <see cref="HorizontalSnapPointsChanged"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> HorizontalSnapPointsChangedEvent =
RoutedEvent.Register<StackPanel, RoutedEventArgs>(
nameof(HorizontalSnapPointsChanged),
RoutingStrategies.Bubble);
remove
{
if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
scrollSnapPointsInfo.VerticalSnapPointsChanged -= value;
}
}
}
/// <summary>
/// Defines the <see cref="VerticalSnapPointsChanged"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> VerticalSnapPointsChangedEvent =
RoutedEvent.Register<StackPanel, RoutedEventArgs>(
nameof(VerticalSnapPointsChanged),
RoutingStrategies.Bubble);
static ItemsPresenter()
{
@ -123,6 +101,24 @@ namespace Avalonia.Controls.Presenters
}
}
/// <summary>
/// Occurs when the measurements for horizontal snap points change.
/// </summary>
public event EventHandler<RoutedEventArgs>? HorizontalSnapPointsChanged
{
add => AddHandler(HorizontalSnapPointsChangedEvent, value);
remove => RemoveHandler(HorizontalSnapPointsChangedEvent, value);
}
/// <summary>
/// Occurs when the measurements for vertical snap points change.
/// </summary>
public event EventHandler<RoutedEventArgs>? VerticalSnapPointsChanged
{
add => AddHandler(VerticalSnapPointsChangedEvent, value);
remove => RemoveHandler(VerticalSnapPointsChangedEvent, value);
}
bool ILogicalScrollable.IsLogicalScrollEnabled => _logicalScrollable?.IsLogicalScrollEnabled ?? false;
Size ILogicalScrollable.ScrollSize => _logicalScrollable?.ScrollSize ?? default;
Size ILogicalScrollable.PageScrollSize => _logicalScrollable?.PageScrollSize ?? default;
@ -151,6 +147,21 @@ namespace Avalonia.Controls.Presenters
else
CreateSimplePanelGenerator();
if(Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
scrollSnapPointsInfo.VerticalSnapPointsChanged += (s, e) =>
{
e.RoutedEvent = VerticalSnapPointsChangedEvent;
RaiseEvent(e);
};
scrollSnapPointsInfo.HorizontalSnapPointsChanged += (s, e) =>
{
e.RoutedEvent = HorizontalSnapPointsChangedEvent;
RaiseEvent(e);
};
}
_logicalScrollable = Panel as ILogicalScrollable;
if (_logicalScrollable is not null)

8
src/Avalonia.Controls/ScrollViewer.cs

@ -168,15 +168,15 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="HorizontalSnapPointsAlignment"/> property.
/// </summary>
public static readonly StyledProperty<SnapPointsAlignment> HorizontalSnapPointsAlignmentProperty =
AvaloniaProperty.Register<ScrollViewer, SnapPointsAlignment>(
public static readonly AttachedProperty<SnapPointsAlignment> HorizontalSnapPointsAlignmentProperty =
AvaloniaProperty.RegisterAttached<ScrollViewer, Control, SnapPointsAlignment>(
nameof(HorizontalSnapPointsAlignment));
/// <summary>
/// Defines the <see cref="VerticalSnapPointsAlignment"/> property.
/// </summary>
public static readonly StyledProperty<SnapPointsAlignment> VerticalSnapPointsAlignmentProperty =
AvaloniaProperty.Register<ScrollViewer, SnapPointsAlignment>(
public static readonly AttachedProperty<SnapPointsAlignment> VerticalSnapPointsAlignmentProperty =
AvaloniaProperty.RegisterAttached<ScrollViewer, Control, SnapPointsAlignment>(
nameof(VerticalSnapPointsAlignment));
/// <summary>

4
src/Avalonia.Controls/StackPanel.cs

@ -356,6 +356,7 @@ namespace Avalonia.Controls
child.Arrange(rect);
}
/// <inheritdoc/>
public IReadOnlyList<double> GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
{
var snapPoints = new List<double>();
@ -419,6 +420,7 @@ namespace Avalonia.Controls
return snapPoints;
}
/// <inheritdoc/>
public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset)
{
offset = 0f;
@ -470,7 +472,7 @@ namespace Avalonia.Controls
break;
}
return snapPoint;
return snapPoint + Spacing;
}
}
}

190
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -3,8 +3,11 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Utilities;
using Avalonia.VisualTree;
@ -14,7 +17,7 @@ namespace Avalonia.Controls
/// <summary>
/// Arranges and virtualizes content on a single line that is oriented either horizontally or vertically.
/// </summary>
public class VirtualizingStackPanel : VirtualizingPanel
public class VirtualizingStackPanel : VirtualizingPanel, IScrollSnapPointsInfo
{
/// <summary>
/// Defines the <see cref="Orientation"/> property.
@ -22,6 +25,34 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Orientation> OrientationProperty =
StackLayout.OrientationProperty.AddOwner<VirtualizingStackPanel>();
/// <summary>
/// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property.
/// </summary>
public static readonly StyledProperty<bool> AreHorizontalSnapPointsRegularProperty =
AvaloniaProperty.Register<VirtualizingStackPanel, bool>(nameof(AreHorizontalSnapPointsRegular));
/// <summary>
/// Defines the <see cref="AreVerticalSnapPointsRegular"/> property.
/// </summary>
public static readonly StyledProperty<bool> AreVerticalSnapPointsRegularProperty =
AvaloniaProperty.Register<StackPanel, bool>(nameof(AreVerticalSnapPointsRegular));
/// <summary>
/// Defines the <see cref="HorizontalSnapPointsChanged"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> HorizontalSnapPointsChangedEvent =
RoutedEvent.Register<VirtualizingStackPanel, RoutedEventArgs>(
nameof(HorizontalSnapPointsChanged),
RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="VerticalSnapPointsChanged"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> VerticalSnapPointsChangedEvent =
RoutedEvent.Register<VirtualizingStackPanel, RoutedEventArgs>(
nameof(VerticalSnapPointsChanged),
RoutingStrategies.Bubble);
private static readonly AttachedProperty<bool> ItemIsOwnContainerProperty =
AvaloniaProperty.RegisterAttached<VirtualizingStackPanel, Control, bool>("ItemIsOwnContainer");
@ -62,6 +93,42 @@ namespace Avalonia.Controls
set => SetValue(OrientationProperty, value);
}
/// <summary>
/// Occurs when the measurements for horizontal snap points change.
/// </summary>
public event EventHandler<RoutedEventArgs>? HorizontalSnapPointsChanged
{
add => AddHandler(HorizontalSnapPointsChangedEvent, value);
remove => RemoveHandler(HorizontalSnapPointsChangedEvent, value);
}
/// <summary>
/// Occurs when the measurements for vertical snap points change.
/// </summary>
public event EventHandler<RoutedEventArgs>? VerticalSnapPointsChanged
{
add => AddHandler(VerticalSnapPointsChangedEvent, value);
remove => RemoveHandler(VerticalSnapPointsChangedEvent, value);
}
/// <summary>
/// Gets or sets whether the horizontal snap points for the <see cref="VirtualizingStackPanel"/> are equidistant from each other.
/// </summary>
public bool AreHorizontalSnapPointsRegular
{
get { return GetValue(AreHorizontalSnapPointsRegularProperty); }
set { SetValue(AreHorizontalSnapPointsRegularProperty, value); }
}
/// <summary>
/// Gets or sets whether the vertical snap points for the <see cref="VirtualizingStackPanel"/> are equidistant from each other.
/// </summary>
public bool AreVerticalSnapPointsRegular
{
get { return GetValue(AreVerticalSnapPointsRegularProperty); }
set { SetValue(AreVerticalSnapPointsRegularProperty, value); }
}
protected override Size MeasureOverride(Size availableSize)
{
if (!IsEffectivelyVisible)
@ -145,6 +212,8 @@ namespace Avalonia.Controls
finally
{
_isInLayout = false;
RaiseEvent(new RoutedEventArgs(Orientation == Orientation.Horizontal ? HorizontalSnapPointsChangedEvent : VerticalSnapPointsChangedEvent));
}
}
@ -622,6 +691,125 @@ namespace Avalonia.Controls
Invalidate(c);
}
/// <inheritdoc/>
public IReadOnlyList<double> GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
{
var snapPoints = new List<double>();
switch (orientation)
{
case Orientation.Horizontal:
if (AreHorizontalSnapPointsRegular)
throw new InvalidOperationException();
if (Orientation == Orientation.Horizontal)
{
foreach (var child in VisualChildren)
{
double snapPoint = 0;
switch (snapPointsAlignment)
{
case SnapPointsAlignment.Near:
snapPoint = child.Bounds.Left;
break;
case SnapPointsAlignment.Center:
snapPoint = child.Bounds.Center.X;
break;
case SnapPointsAlignment.Far:
snapPoint = child.Bounds.Right;
break;
}
snapPoints.Add(snapPoint);
}
}
break;
case Orientation.Vertical:
if (AreVerticalSnapPointsRegular)
throw new InvalidOperationException();
if (Orientation == Orientation.Vertical)
{
foreach (var child in VisualChildren)
{
double snapPoint = 0;
switch (snapPointsAlignment)
{
case SnapPointsAlignment.Near:
snapPoint = child.Bounds.Top;
break;
case SnapPointsAlignment.Center:
snapPoint = child.Bounds.Center.Y;
break;
case SnapPointsAlignment.Far:
snapPoint = child.Bounds.Bottom;
break;
}
snapPoints.Add(snapPoint);
}
}
break;
}
return snapPoints;
}
/// <inheritdoc/>
public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset)
{
offset = 0f;
var firstRealizedChild = _realizedElements?.Elements.FirstOrDefault();
if (firstRealizedChild == null)
{
return 0;
}
double snapPoint = 0;
switch (Orientation)
{
case Orientation.Horizontal:
if (!AreHorizontalSnapPointsRegular)
throw new InvalidOperationException();
snapPoint = firstRealizedChild.Bounds.Width;
switch (snapPointsAlignment)
{
case SnapPointsAlignment.Near:
offset = 0;
break;
case SnapPointsAlignment.Center:
offset = (firstRealizedChild.Bounds.Right - firstRealizedChild.Bounds.Left) / 2;
break;
case SnapPointsAlignment.Far:
offset = firstRealizedChild.Bounds.Width;
break;
}
break;
case Orientation.Vertical:
if (!AreVerticalSnapPointsRegular)
throw new InvalidOperationException();
snapPoint = firstRealizedChild.Bounds.Height;
switch (snapPointsAlignment)
{
case SnapPointsAlignment.Near:
offset = 0;
break;
case SnapPointsAlignment.Center:
offset = (firstRealizedChild.Bounds.Bottom - firstRealizedChild.Bounds.Top) / 2;
break;
case SnapPointsAlignment.Far:
offset = firstRealizedChild.Bounds.Height;
break;
}
break;
}
return snapPoint;
}
/// <summary>
/// Stores the realized element state for a <see cref="VirtualizingStackPanel"/>.
/// </summary>

3
src/Avalonia.Themes.Fluent/Controls/ListBox.xaml

@ -19,6 +19,7 @@
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.IsScrollChainingEnabled" Value="True" />
<Setter Property="ScrollViewer.VerticalSnapPointsType" Value="Mandatory" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template">
<ControlTemplate>
@ -29,6 +30,8 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ScrollViewer Name="PART_ScrollViewer"
VerticalSnapPointsType="{TemplateBinding (ScrollViewer.VerticalSnapPointsType)}"
HorizontalSnapPointsType="{TemplateBinding (ScrollViewer.HorizontalSnapPointsType)}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"

Loading…
Cancel
Save