Browse Source

Converted RangeBase to StyledProperty

Normalised ScrollViewer internal properties and bindings
pull/10803/head
Tom Edwards 3 years ago
parent
commit
74f75b0525
  1. 117
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  2. 165
      src/Avalonia.Controls/Primitives/RangeBase.cs
  3. 70
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  4. 34
      src/Avalonia.Controls/Primitives/Track.cs
  5. 30
      src/Avalonia.Controls/ProgressBar.cs
  6. 309
      src/Avalonia.Controls/ScrollViewer.cs
  7. 2
      src/Avalonia.Controls/Slider.cs
  8. 13
      src/Avalonia.Themes.Fluent/Controls/MenuScrollViewer.xaml
  9. 35
      src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml
  10. 51
      src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml
  11. 14
      tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs
  12. 6
      tests/Avalonia.Controls.UnitTests/CarouselTests.cs
  13. 9
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  14. 112
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  15. 10
      tests/Avalonia.Controls.UnitTests/VirtualizingCarouselPanelTests.cs
  16. 6
      tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

117
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -6,6 +6,7 @@ using Avalonia.Input;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using System.Linq;
namespace Avalonia.Controls.Presenters
{
@ -19,44 +20,34 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Defines the <see cref="CanHorizontallyScroll"/> property.
/// </summary>
public static readonly DirectProperty<ScrollContentPresenter, bool> CanHorizontallyScrollProperty =
AvaloniaProperty.RegisterDirect<ScrollContentPresenter, bool>(
nameof(CanHorizontallyScroll),
o => o.CanHorizontallyScroll,
(o, v) => o.CanHorizontallyScroll = v);
public static readonly StyledProperty<bool> CanHorizontallyScrollProperty =
AvaloniaProperty.Register<ScrollContentPresenter, bool>(nameof(CanHorizontallyScroll));
/// <summary>
/// Defines the <see cref="CanVerticallyScroll"/> property.
/// </summary>
public static readonly DirectProperty<ScrollContentPresenter, bool> CanVerticallyScrollProperty =
AvaloniaProperty.RegisterDirect<ScrollContentPresenter, bool>(
nameof(CanVerticallyScroll),
o => o.CanVerticallyScroll,
(o, v) => o.CanVerticallyScroll = v);
public static readonly StyledProperty<bool> CanVerticallyScrollProperty =
AvaloniaProperty.Register<ScrollContentPresenter, bool>(nameof(CanVerticallyScroll));
/// <summary>
/// Defines the <see cref="Extent"/> property.
/// </summary>
public static readonly DirectProperty<ScrollContentPresenter, Size> ExtentProperty =
ScrollViewer.ExtentProperty.AddOwner<ScrollContentPresenter>(
o => o.Extent,
(o, v) => o.Extent = v);
o => o.Extent);
/// <summary>
/// Defines the <see cref="Offset"/> property.
/// </summary>
public static readonly DirectProperty<ScrollContentPresenter, Vector> OffsetProperty =
ScrollViewer.OffsetProperty.AddOwner<ScrollContentPresenter>(
o => o.Offset,
(o, v) => o.Offset = v);
public static readonly StyledProperty<Vector> OffsetProperty =
ScrollViewer.OffsetProperty.AddOwner<ScrollContentPresenter>(new(coerce: ScrollViewer.CoerceOffset));
/// <summary>
/// Defines the <see cref="Viewport"/> property.
/// </summary>
public static readonly DirectProperty<ScrollContentPresenter, Size> ViewportProperty =
ScrollViewer.ViewportProperty.AddOwner<ScrollContentPresenter>(
o => o.Viewport,
(o, v) => o.Viewport = v);
o => o.Viewport);
/// <summary>
/// Defines the <see cref="HorizontalSnapPointsType"/> property.
@ -88,11 +79,8 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<bool> IsScrollChainingEnabledProperty =
ScrollViewer.IsScrollChainingEnabledProperty.AddOwner<ScrollContentPresenter>();
private bool _canHorizontallyScroll;
private bool _canVerticallyScroll;
private bool _arranging;
private Size _extent;
private Vector _offset;
private IDisposable? _logicalScrollSubscription;
private Size _viewport;
private Dictionary<int, Vector>? _activeLogicalGestureScrolls;
@ -109,6 +97,7 @@ namespace Avalonia.Controls.Presenters
private double _verticalSnapPoint;
private double _verticalSnapPointOffset;
private double _horizontalSnapPointOffset;
private CompositeDisposable? _ownerSubscriptions;
/// <summary>
/// Initializes static members of the <see cref="ScrollContentPresenter"/> class.
@ -116,7 +105,6 @@ namespace Avalonia.Controls.Presenters
static ScrollContentPresenter()
{
ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true);
ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>((x, e) => x.ChildChanged(e));
}
/// <summary>
@ -137,8 +125,8 @@ namespace Avalonia.Controls.Presenters
/// </summary>
public bool CanHorizontallyScroll
{
get { return _canHorizontallyScroll; }
set { SetAndRaise(CanHorizontallyScrollProperty, ref _canHorizontallyScroll, value); }
get => GetValue(CanHorizontallyScrollProperty);
set => SetValue(CanHorizontallyScrollProperty, value);
}
/// <summary>
@ -146,8 +134,8 @@ namespace Avalonia.Controls.Presenters
/// </summary>
public bool CanVerticallyScroll
{
get { return _canVerticallyScroll; }
set { SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value); }
get => GetValue(CanVerticallyScrollProperty);
set => SetValue(CanVerticallyScrollProperty, value);
}
/// <summary>
@ -164,8 +152,8 @@ namespace Avalonia.Controls.Presenters
/// </summary>
public Vector Offset
{
get { return _offset; }
set { SetAndRaise(OffsetProperty, ref _offset, ScrollViewer.CoerceOffset(Extent, Viewport, value)); }
get => GetValue(OffsetProperty);
set => SetValue(OffsetProperty, value);
}
/// <summary>
@ -295,12 +283,64 @@ namespace Avalonia.Controls.Presenters
if (result)
{
Offset = offset;
SetCurrentValue(OffsetProperty, offset);
}
return result;
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
AttachToScrollViewer();
}
/// <summary>
/// Locates the first <see cref="ScrollViewer"/> ancestor and binds to it. Properties which have been set through other means are not bound.
/// </summary>
/// <remarks>
/// This method is automatically called when the control is attached to a visual tree.
/// </remarks>
protected internal virtual void AttachToScrollViewer()
{
_ownerSubscriptions?.Dispose();
var owner = this.FindAncestorOfType<ScrollViewer>();
if (owner == null)
{
return;
}
var subscriptionDisposables = new IDisposable?[]
{
IfUnset(CanHorizontallyScrollProperty, p => Bind(p, owner.GetObservable(ScrollViewer.HorizontalScrollBarVisibilityProperty).Select(NotDisabled), Data.BindingPriority.Template)),
IfUnset(CanVerticallyScrollProperty, p => Bind(p, owner.GetObservable(ScrollViewer.VerticalScrollBarVisibilityProperty).Select(NotDisabled), Data.BindingPriority.Template)),
IfUnset(OffsetProperty, p => new CompositeDisposable(
Bind(p, owner.GetBindingObservable(ScrollViewer.OffsetProperty), Data.BindingPriority.Template),
this.GetObservable(OffsetProperty).Subscribe(v => owner.SetCurrentValue(OffsetProperty, v)))),
IfUnset(IsScrollChainingEnabledProperty, p => Bind(p, owner.GetBindingObservable(ScrollViewer.IsScrollChainingEnabledProperty), Data.BindingPriority.Template)),
IfUnset(ContentProperty, p => Bind(p, owner.GetBindingObservable(ContentProperty), Data.BindingPriority.Template)),
// read-only properties on ScrollViewer with internal setters:
this.GetObservable(ExtentProperty).Subscribe(v => owner.Extent = v),
this.GetObservable(ViewportProperty).Subscribe(v => owner.Viewport = v)
}.Where(d => d != null).Cast<IDisposable>().ToArray();
_ownerSubscriptions = new CompositeDisposable(subscriptionDisposables);
static bool NotDisabled(ScrollBarVisibility v) => v != ScrollBarVisibility.Disabled;
IDisposable? IfUnset<T>(T property, Func<T, IDisposable> func) where T : AvaloniaProperty => GetValueStore().IsSet(property) ? null : func(property);
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_ownerSubscriptions?.Dispose();
base.OnDetachedFromVisualTree(e);
}
/// <inheritdoc/>
void IScrollAnchorProvider.RegisterAnchorCandidate(Control element)
{
@ -410,7 +450,7 @@ namespace Avalonia.Controls.Presenters
try
{
_arranging = true;
Offset = newOffset;
SetCurrentValue(OffsetProperty, newOffset);
}
finally
{
@ -427,7 +467,6 @@ namespace Avalonia.Controls.Presenters
Viewport = finalSize;
Extent = Child!.Bounds.Size.Inflate(Child.Margin);
Offset = ScrollViewer.CoerceOffset(Extent, finalSize, Offset);
_isAnchorElementDirty = true;
return finalSize;
@ -516,7 +555,7 @@ namespace Avalonia.Controls.Presenters
}
bool offsetChanged = newOffset != Offset;
Offset = newOffset;
SetCurrentValue(OffsetProperty, newOffset);
e.Handled = !IsScrollChainingEnabled || offsetChanged;
@ -529,7 +568,7 @@ namespace Avalonia.Controls.Presenters
_activeLogicalGestureScrolls?.Remove(e.Id);
_scrollGestureSnapPoints?.Remove(e.Id);
Offset = SnapOffset(Offset);
SetCurrentValue(OffsetProperty, SnapOffset(Offset));
}
private void OnScrollGestureInertiaStartingEnded(object? sender, ScrollGestureInertiaStartingEventArgs e)
@ -623,7 +662,7 @@ namespace Avalonia.Controls.Presenters
Vector newOffset = SnapOffset(new Vector(x, y));
bool offsetChanged = newOffset != Offset;
Offset = newOffset;
SetCurrentValue(OffsetProperty, newOffset);
e.Handled = !IsScrollChainingEnabled || offsetChanged;
}
@ -651,6 +690,10 @@ namespace Avalonia.Controls.Presenters
UpdateSnapPoints();
}
else if (change.Property == ChildProperty)
{
ChildChanged(change);
}
else if (change.Property == HorizontalSnapPointsAlignmentProperty ||
change.Property == VerticalSnapPointsAlignmentProperty)
{
@ -677,7 +720,7 @@ namespace Avalonia.Controls.Presenters
if (e.OldValue != null)
{
Offset = default;
SetCurrentValue(OffsetProperty, default);
}
}
@ -719,14 +762,14 @@ namespace Avalonia.Controls.Presenters
if (logicalScroll != scrollable.IsLogicalScrollEnabled)
{
UpdateScrollableSubscription(Child);
Offset = default;
SetCurrentValue(OffsetProperty, default);
InvalidateMeasure();
}
else if (scrollable.IsLogicalScrollEnabled)
{
Viewport = scrollable.Viewport;
Extent = scrollable.Extent;
Offset = scrollable.Offset;
SetCurrentValue(OffsetProperty, scrollable.Offset);
}
}

165
src/Avalonia.Controls/Primitives/RangeBase.cs

@ -12,30 +12,22 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Defines the <see cref="Minimum"/> property.
/// </summary>
public static readonly DirectProperty<RangeBase, double> MinimumProperty =
AvaloniaProperty.RegisterDirect<RangeBase, double>(
nameof(Minimum),
o => o.Minimum,
(o, v) => o.Minimum = v);
public static readonly StyledProperty<double> MinimumProperty =
AvaloniaProperty.Register<RangeBase, double>(nameof(Minimum), coerce: CoerceMinimum);
/// <summary>
/// Defines the <see cref="Maximum"/> property.
/// </summary>
public static readonly DirectProperty<RangeBase, double> MaximumProperty =
AvaloniaProperty.RegisterDirect<RangeBase, double>(
nameof(Maximum),
o => o.Maximum,
(o, v) => o.Maximum = v);
public static readonly StyledProperty<double> MaximumProperty =
AvaloniaProperty.Register<RangeBase, double>(nameof(Maximum), 100, coerce: CoerceMaximum);
/// <summary>
/// Defines the <see cref="Value"/> property.
/// </summary>
public static readonly DirectProperty<RangeBase, double> ValueProperty =
AvaloniaProperty.RegisterDirect<RangeBase, double>(
nameof(Value),
o => o.Value,
(o, v) => o.Value = v,
defaultBindingMode: BindingMode.TwoWay);
public static readonly StyledProperty<double> ValueProperty =
AvaloniaProperty.Register<RangeBase, double>(nameof(Value),
defaultBindingMode: BindingMode.TwoWay,
coerce: CoerceValue);
/// <summary>
/// Defines the <see cref="SmallChange"/> property.
@ -49,44 +41,26 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<double> LargeChangeProperty =
AvaloniaProperty.Register<RangeBase, double>(nameof(LargeChange), 10);
private double _minimum;
private double _maximum = 100.0;
private double _value;
/// <summary>
/// Initializes a new instance of the <see cref="RangeBase"/> class.
/// Gets or sets the minimum value.
/// </summary>
public RangeBase()
public double Minimum
{
get => GetValue(MinimumProperty);
set => SetValue(MinimumProperty, value);
}
/// <summary>
/// Gets or sets the minimum value.
/// </summary>
public double Minimum
private static double CoerceMinimum(AvaloniaObject sender, double value)
{
get
{
return _minimum;
}
return ValidateDouble(value) ? value : sender.GetValue(MinimumProperty);
}
set
private void OnMinimumChanged()
{
if (IsInitialized)
{
if (!ValidateDouble(value))
{
return;
}
if (IsInitialized)
{
SetAndRaise(MinimumProperty, ref _minimum, value);
Maximum = ValidateMaximum(Maximum);
Value = ValidateValue(Value);
}
else
{
SetAndRaise(MinimumProperty, ref _minimum, value);
}
CoerceValue(MaximumProperty);
CoerceValue(ValueProperty);
}
}
@ -95,28 +69,22 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public double Maximum
{
get
{
return _maximum;
}
get => GetValue(MaximumProperty);
set => SetValue(MaximumProperty, value);
}
private static double CoerceMaximum(AvaloniaObject sender, double value)
{
return ValidateDouble(value)
? Math.Max(value, sender.GetValue(MinimumProperty))
: sender.GetValue(MaximumProperty);
}
set
private void OnMaximumChanged()
{
if (IsInitialized)
{
if (!ValidateDouble(value))
{
return;
}
if (IsInitialized)
{
value = ValidateMaximum(value);
SetAndRaise(MaximumProperty, ref _maximum, value);
Value = ValidateValue(Value);
}
else
{
SetAndRaise(MaximumProperty, ref _maximum, value);
}
CoerceValue(ValueProperty);
}
}
@ -125,28 +93,15 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public double Value
{
get
{
return _value;
}
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
set
{
if (!ValidateDouble(value))
{
return;
}
if (IsInitialized)
{
value = ValidateValue(value);
SetAndRaise(ValueProperty, ref _value, value);
}
else
{
SetAndRaise(ValueProperty, ref _value, value);
}
}
private static double CoerceValue(AvaloniaObject sender, double value)
{
return ValidateDouble(value)
? MathUtilities.Clamp(value, sender.GetValue(MinimumProperty), sender.GetValue(MaximumProperty))
: sender.GetValue(ValueProperty);
}
public double SmallChange
@ -165,37 +120,31 @@ namespace Avalonia.Controls.Primitives
{
base.OnInitialized();
Maximum = ValidateMaximum(Maximum);
Value = ValidateValue(Value);
CoerceValue(MaximumProperty);
CoerceValue(ValueProperty);
}
/// <summary>
/// Checks if the double value is not infinity nor NaN.
/// </summary>
/// <param name="value">The value.</param>
private static bool ValidateDouble(double value)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
return !double.IsInfinity(value) && !double.IsNaN(value);
}
base.OnPropertyChanged(change);
/// <summary>
/// Validates/coerces the <see cref="Maximum"/> property.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The coerced value.</returns>
private double ValidateMaximum(double value)
{
return Math.Max(value, Minimum);
if (change.Property == MinimumProperty)
{
OnMinimumChanged();
}
else if (change.Property == MaximumProperty)
{
OnMaximumChanged();
}
}
/// <summary>
/// Validates/coerces the <see cref="Value"/> property.
/// Checks if the double value is not infinity nor NaN.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The coerced value.</returns>
private double ValidateValue(double value)
private static bool ValidateDouble(double value)
{
return MathUtilities.Clamp(value, Minimum, Maximum);
return !double.IsInfinity(value) && !double.IsNaN(value);
}
}
}

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

@ -6,6 +6,9 @@ using Avalonia.Layout;
using Avalonia.Threading;
using Avalonia.Controls.Metadata;
using Avalonia.Automation.Peers;
using Avalonia.VisualTree;
using Avalonia.Reactive;
using System.Linq;
namespace Avalonia.Controls.Primitives
{
@ -80,6 +83,7 @@ namespace Avalonia.Controls.Primitives
private Button? _pageDownButton;
private DispatcherTimer? _timer;
private bool _isExpanded;
private CompositeDisposable? _ownerSubscriptions;
/// <summary>
/// Initializes static members of the <see cref="ScrollBar"/> class.
@ -88,6 +92,8 @@ namespace Avalonia.Controls.Primitives
{
Thumb.DragDeltaEvent.AddClassHandler<ScrollBar>((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<ScrollBar>((x, e) => x.OnThumbDragComplete(e), RoutingStrategies.Bubble);
FocusableProperty.OverrideMetadata<ScrollBar>(new(false));
}
/// <summary>
@ -178,7 +184,60 @@ namespace Avalonia.Controls.Primitives
_ => throw new InvalidOperationException("Invalid value for ScrollBar.Visibility.")
};
SetValue(IsVisibleProperty, isVisible);
SetCurrentValue(IsVisibleProperty, isVisible);
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
AttachToScrollViewer();
}
/// <summary>
/// Locates the first <see cref="ScrollViewer"/> ancestor and binds to its properties. Properties which have been set through other means are not bound.
/// </summary>
/// <remarks>
/// This method is automatically called when the control is attached to a visual tree.
/// </remarks>
protected internal virtual void AttachToScrollViewer()
{
_ownerSubscriptions?.Dispose();
var owner = this.FindAncestorOfType<ScrollViewer>();
if (owner == null)
{
return;
}
var visibilitySource = Orientation == Orientation.Horizontal ? ScrollViewer.HorizontalScrollBarVisibilityProperty : ScrollViewer.VerticalScrollBarVisibilityProperty;
var subscriptionDisposables = new IDisposable?[]
{
IfUnset(MaximumProperty, p => Bind(p, owner.GetObservable(ScrollViewer.ScrollBarMaximumProperty).Select(ExtractOrdinate), BindingPriority.Template)),
IfUnset(ValueProperty, p => new CompositeDisposable(
Bind(p, owner.GetObservable(ScrollViewer.OffsetProperty).Select(ExtractOrdinate), BindingPriority.Template),
this.GetObservable(ValueProperty).Subscribe(v => SetScrollViewerOffset(owner, v)))),
IfUnset(ViewportSizeProperty, p => Bind(p, owner.GetObservable(ScrollViewer.ViewportProperty).Select(ExtractOrdinate), BindingPriority.Template)),
IfUnset(VisibilityProperty, p => Bind(p, owner.GetObservable(visibilitySource), BindingPriority.Template)),
IfUnset(AllowAutoHideProperty, p => Bind(p, owner.GetObservable(ScrollViewer.AllowAutoHideProperty), BindingPriority.Template)),
IfUnset(LargeChangeProperty, p => Bind(p, owner.GetObservable(ScrollViewer.LargeChangeProperty).Select(ExtractOrdinate), BindingPriority.Template)),
IfUnset(SmallChangeProperty, p => Bind(p, owner.GetObservable(ScrollViewer.SmallChangeProperty).Select(ExtractOrdinate), BindingPriority.Template))
}.Where(d => d != null).Cast<IDisposable>().ToArray();
_ownerSubscriptions = new CompositeDisposable(subscriptionDisposables);
IDisposable? IfUnset<T>(T property, Func<T, IDisposable> func) where T : AvaloniaProperty => GetValueStore().IsSet(property) ? null : func(property);
}
private double ExtractOrdinate(Vector v) => Orientation == Orientation.Horizontal ? v.X : v.Y;
private double ExtractOrdinate(Size v) => Orientation == Orientation.Horizontal ? v.Width : v.Height;
private void SetScrollViewerOffset(ScrollViewer viewer, double value) => viewer.SetCurrentValue(ScrollViewer.OffsetProperty, Orientation == Orientation.Horizontal ? viewer.Offset.WithX(value) : viewer.Offset.WithY(value));
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_ownerSubscriptions?.Dispose();
base.OnDetachedFromVisualTree(e);
}
protected override void OnKeyDown(KeyEventArgs e)
@ -202,6 +261,7 @@ namespace Avalonia.Controls.Primitives
if (change.Property == OrientationProperty)
{
UpdatePseudoClasses(change.GetNewValue<Orientation>());
AttachToScrollViewer(); // there's no way to manually refresh bindings, so reapply them
}
else if (change.Property == AllowAutoHideProperty)
{
@ -373,25 +433,25 @@ namespace Avalonia.Controls.Primitives
private void SmallDecrement()
{
Value = Math.Max(Value - SmallChange, Minimum);
SetCurrentValue(ValueProperty, Math.Max(Value - SmallChange, Minimum));
OnScroll(ScrollEventType.SmallDecrement);
}
private void SmallIncrement()
{
Value = Math.Min(Value + SmallChange, Maximum);
SetCurrentValue(ValueProperty, Math.Min(Value + SmallChange, Maximum));
OnScroll(ScrollEventType.SmallIncrement);
}
private void LargeDecrement()
{
Value = Math.Max(Value - LargeChange, Minimum);
SetCurrentValue(ValueProperty, Math.Max(Value - LargeChange, Minimum));
OnScroll(ScrollEventType.LargeDecrement);
}
private void LargeIncrement()
{
Value = Math.Min(Value + LargeChange, Maximum);
SetCurrentValue(ValueProperty, Math.Min(Value + LargeChange, Maximum));
OnScroll(ScrollEventType.LargeIncrement);
}

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

@ -15,14 +15,14 @@ namespace Avalonia.Controls.Primitives
[PseudoClasses(":vertical", ":horizontal")]
public class Track : Control
{
public static readonly DirectProperty<Track, double> MinimumProperty =
RangeBase.MinimumProperty.AddOwner<Track>(o => o.Minimum, (o, v) => o.Minimum = v);
public static readonly StyledProperty<double> MinimumProperty =
RangeBase.MinimumProperty.AddOwner<Track>();
public static readonly DirectProperty<Track, double> MaximumProperty =
RangeBase.MaximumProperty.AddOwner<Track>(o => o.Maximum, (o, v) => o.Maximum = v);
public static readonly StyledProperty<double> MaximumProperty =
RangeBase.MaximumProperty.AddOwner<Track>();
public static readonly DirectProperty<Track, double> ValueProperty =
RangeBase.ValueProperty.AddOwner<Track>(o => o.Value, (o, v) => o.Value = v);
public static readonly StyledProperty<double> ValueProperty =
RangeBase.ValueProperty.AddOwner<Track>();
public static readonly StyledProperty<double> ViewportSizeProperty =
ScrollBar.ViewportSizeProperty.AddOwner<Track>();
@ -45,10 +45,6 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<bool> IgnoreThumbDragProperty =
AvaloniaProperty.Register<Track, bool>(nameof(IgnoreThumbDrag));
private double _minimum;
private double _maximum = 100.0;
private double _value;
static Track()
{
ThumbProperty.Changed.AddClassHandler<Track>((x, e) => x.ThumbChanged(e));
@ -64,20 +60,20 @@ namespace Avalonia.Controls.Primitives
public double Minimum
{
get { return _minimum; }
set { SetAndRaise(MinimumProperty, ref _minimum, value); }
get => GetValue(MinimumProperty);
set => SetValue(MinimumProperty, value);
}
public double Maximum
{
get { return _maximum; }
set { SetAndRaise(MaximumProperty, ref _maximum, value); }
get => GetValue(MaximumProperty);
set => SetValue(MaximumProperty, value);
}
public double Value
{
get { return _value; }
set { SetAndRaise(ValueProperty, ref _value, value); }
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public double ViewportSize
@ -443,11 +439,11 @@ namespace Avalonia.Controls.Primitives
{
if (IgnoreThumbDrag)
return;
Value = MathUtilities.Clamp(
SetCurrentValue(ValueProperty, MathUtilities.Clamp(
Value + ValueFromDistance(e.Vector.X, e.Vector.Y),
Minimum,
Maximum);
Maximum));
}
private void ShowChildren(bool visible)

30
src/Avalonia.Controls/ProgressBar.cs

@ -100,8 +100,6 @@ namespace Avalonia.Controls
}
private double _percentage;
private double _indeterminateStartingOffset;
private double _indeterminateEndingOffset;
private Border? _indicator;
private IDisposable? _trackSizeChangedListener;
@ -122,17 +120,11 @@ namespace Avalonia.Controls
nameof(Percentage),
o => o.Percentage);
public static readonly DirectProperty<ProgressBar, double> IndeterminateStartingOffsetProperty =
AvaloniaProperty.RegisterDirect<ProgressBar, double>(
nameof(IndeterminateStartingOffset),
p => p.IndeterminateStartingOffset,
(p, o) => p.IndeterminateStartingOffset = o);
public static readonly StyledProperty<double> IndeterminateStartingOffsetProperty =
AvaloniaProperty.Register<ProgressBar, double>(nameof(IndeterminateStartingOffset));
public static readonly DirectProperty<ProgressBar, double> IndeterminateEndingOffsetProperty =
AvaloniaProperty.RegisterDirect<ProgressBar, double>(
nameof(IndeterminateEndingOffset),
p => p.IndeterminateEndingOffset,
(p, o) => p.IndeterminateEndingOffset = o);
public static readonly StyledProperty<double> IndeterminateEndingOffsetProperty =
AvaloniaProperty.Register<ProgressBar, double>(nameof(IndeterminateEndingOffset));
public double Percentage
{
@ -142,19 +134,19 @@ namespace Avalonia.Controls
public double IndeterminateStartingOffset
{
get => _indeterminateStartingOffset;
set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
get => GetValue(IndeterminateStartingOffsetProperty);
set => SetValue(IndeterminateStartingOffsetProperty, value);
}
public double IndeterminateEndingOffset
{
get => _indeterminateEndingOffset;
set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
get => GetValue(IndeterminateEndingOffsetProperty);
set => SetValue(IndeterminateEndingOffsetProperty, value);
}
static ProgressBar()
{
ValueProperty.OverrideMetadata<ProgressBar>(new DirectPropertyMetadata<double>(defaultBindingMode: BindingMode.OneWay));
ValueProperty.OverrideMetadata<ProgressBar>(new(defaultBindingMode: BindingMode.OneWay));
ValueProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
MinimumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
MaximumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
@ -261,8 +253,8 @@ namespace Avalonia.Controls
// Remove these properties when we switch to fluent as default and removed the old one.
IndeterminateStartingOffset = -dim;
IndeterminateEndingOffset = dim;
SetCurrentValue(IndeterminateStartingOffsetProperty,-dim);
SetCurrentValue(IndeterminateEndingOffsetProperty,dim);
var padding = Padding;
var rectangle = new RectangleGeometry(

309
src/Avalonia.Controls/ScrollViewer.cs

@ -16,54 +16,25 @@ namespace Avalonia.Controls
[TemplatePart("PART_VerticalScrollBar", typeof(ScrollBar))]
public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider
{
/// <summary>
/// Defines the <see cref="CanHorizontallyScroll"/> property.
/// </summary>
/// <remarks>
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly DirectProperty<ScrollViewer, bool> CanHorizontallyScrollProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, bool>(
nameof(CanHorizontallyScroll),
o => o.CanHorizontallyScroll);
/// <summary>
/// Defines the <see cref="CanVerticallyScroll"/> property.
/// </summary>
/// <remarks>
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly DirectProperty<ScrollViewer, bool> CanVerticallyScrollProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, bool>(
nameof(CanVerticallyScroll),
o => o.CanVerticallyScroll);
/// <summary>
/// Defines the <see cref="Extent"/> property.
/// </summary>
public static readonly DirectProperty<ScrollViewer, Size> ExtentProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, Size>(nameof(Extent),
o => o.Extent,
(o, v) => o.Extent = v);
o => o.Extent);
/// <summary>
/// Defines the <see cref="Offset"/> property.
/// </summary>
public static readonly DirectProperty<ScrollViewer, Vector> OffsetProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, Vector>(
nameof(Offset),
o => o.Offset,
(o, v) => o.Offset = v);
public static readonly StyledProperty<Vector> OffsetProperty =
AvaloniaProperty.Register<ScrollViewer, Vector>(nameof(Offset), coerce: CoerceOffset);
/// <summary>
/// Defines the <see cref="Viewport"/> property.
/// </summary>
public static readonly DirectProperty<ScrollViewer, Size> ViewportProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, Size>(nameof(Viewport),
o => o.Viewport,
(o, v) => o.Viewport = v);
o => o.Viewport);
/// <summary>
/// Defines the <see cref="LargeChange"/> property.
@ -82,41 +53,12 @@ namespace Avalonia.Controls
o => o.SmallChange);
/// <summary>
/// Defines the HorizontalScrollBarMaximum property.
/// </summary>
/// <remarks>
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly DirectProperty<ScrollViewer, double> HorizontalScrollBarMaximumProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, double>(
nameof(HorizontalScrollBarMaximum),
o => o.HorizontalScrollBarMaximum);
/// <summary>
/// Defines the HorizontalScrollBarValue property.
/// Defines the <see cref="ScrollBarMaximum"/> property.
/// </summary>
/// <remarks>
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly DirectProperty<ScrollViewer, double> HorizontalScrollBarValueProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, double>(
nameof(HorizontalScrollBarValue),
o => o.HorizontalScrollBarValue,
(o, v) => o.HorizontalScrollBarValue = v);
/// <summary>
/// Defines the HorizontalScrollBarViewportSize property.
/// </summary>
/// <remarks>
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly DirectProperty<ScrollViewer, double> HorizontalScrollBarViewportSizeProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, double>(
nameof(HorizontalScrollBarViewportSize),
o => o.HorizontalScrollBarViewportSize);
public static readonly DirectProperty<ScrollViewer, Vector> ScrollBarMaximumProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, Vector>(
nameof(ScrollBarMaximum),
o => o.ScrollBarMaximum);
/// <summary>
/// Defines the <see cref="HorizontalScrollBarVisibility"/> property.
@ -126,31 +68,6 @@ namespace Avalonia.Controls
nameof(HorizontalScrollBarVisibility),
ScrollBarVisibility.Disabled);
/// <summary>
/// Defines the VerticalScrollBarMaximum property.
/// </summary>
/// <remarks>
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly DirectProperty<ScrollViewer, double> VerticalScrollBarMaximumProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, double>(
nameof(VerticalScrollBarMaximum),
o => o.VerticalScrollBarMaximum);
/// <summary>
/// Defines the VerticalScrollBarValue property.
/// </summary>
/// <remarks>
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly DirectProperty<ScrollViewer, double> VerticalScrollBarValueProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, double>(
nameof(VerticalScrollBarValue),
o => o.VerticalScrollBarValue,
(o, v) => o.VerticalScrollBarValue = v);
/// <summary>
/// Defines the <see cref="HorizontalSnapPointsType"/> property.
/// </summary>
@ -179,18 +96,6 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterAttached<ScrollViewer, Control, SnapPointsAlignment>(
nameof(VerticalSnapPointsAlignment));
/// <summary>
/// Defines the VerticalScrollBarViewportSize property.
/// </summary>
/// <remarks>
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly DirectProperty<ScrollViewer, double> VerticalScrollBarViewportSizeProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, double>(
nameof(VerticalScrollBarViewportSize),
o => o.VerticalScrollBarViewportSize);
/// <summary>
/// Defines the <see cref="VerticalScrollBarVisibility"/> property.
/// </summary>
@ -242,25 +147,16 @@ namespace Avalonia.Controls
private IDisposable? _childSubscription;
private ILogicalScrollable? _logicalScrollable;
private Size _extent;
private Vector _offset;
private Size _viewport;
private Size _oldExtent;
private Vector _oldOffset;
private Vector _oldMaximum;
private Size _oldViewport;
private Size _largeChange;
private Size _smallChange = new Size(DefaultSmallChange, DefaultSmallChange);
private bool _isExpanded;
private IDisposable? _scrollBarExpandSubscription;
/// <summary>
/// Initializes static members of the <see cref="ScrollViewer"/> class.
/// </summary>
static ScrollViewer()
{
HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer, ScrollBarVisibility>((x, e) => x.ScrollBarVisibilityChanged(e));
VerticalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer, ScrollBarVisibility>((x, e) => x.ScrollBarVisibilityChanged(e));
}
/// <summary>
/// Initializes a new instance of the <see cref="ScrollViewer"/> class.
/// </summary>
@ -288,7 +184,7 @@ namespace Avalonia.Controls
return _extent;
}
private set
internal set
{
if (SetAndRaise(ExtentProperty, ref _extent, value))
{
@ -302,18 +198,8 @@ namespace Avalonia.Controls
/// </summary>
public Vector Offset
{
get
{
return _offset;
}
set
{
if (SetAndRaise(OffsetProperty, ref _offset, CoerceOffset(Extent, Viewport, value)))
{
CalculatedPropertiesChanged();
}
}
get => GetValue(OffsetProperty);
set => SetValue(OffsetProperty, value);
}
/// <summary>
@ -326,7 +212,7 @@ namespace Avalonia.Controls
return _viewport;
}
private set
internal set
{
if (SetAndRaise(ViewportProperty, ref _viewport, value))
{
@ -383,70 +269,9 @@ namespace Avalonia.Controls
public Control? CurrentAnchor => (Presenter as IScrollAnchorProvider)?.CurrentAnchor;
/// <summary>
/// Gets the maximum horizontal scrollbar value.
/// Gets the maximum scrolling distance (which is <see cref="Extent"/> - <see cref="Viewport"/>).
/// </summary>
protected double HorizontalScrollBarMaximum
{
get { return Max(_extent.Width - _viewport.Width, 0); }
}
/// <summary>
/// Gets or sets the horizontal scrollbar value.
/// </summary>
protected double HorizontalScrollBarValue
{
get { return _offset.X; }
set
{
if (_offset.X != value)
{
var old = Offset.X;
Offset = Offset.WithX(value);
RaisePropertyChanged(HorizontalScrollBarValueProperty, old, value);
}
}
}
/// <summary>
/// Gets the size of the horizontal scrollbar viewport.
/// </summary>
protected double HorizontalScrollBarViewportSize
{
get { return _viewport.Width; }
}
/// <summary>
/// Gets the maximum vertical scrollbar value.
/// </summary>
protected double VerticalScrollBarMaximum
{
get { return Max(_extent.Height - _viewport.Height, 0); }
}
/// <summary>
/// Gets or sets the vertical scrollbar value.
/// </summary>
protected double VerticalScrollBarValue
{
get { return _offset.Y; }
set
{
if (_offset.Y != value)
{
var old = Offset.Y;
Offset = Offset.WithY(value);
RaisePropertyChanged(VerticalScrollBarValueProperty, old, value);
}
}
}
/// <summary>
/// Gets the size of the vertical scrollbar viewport.
/// </summary>
protected double VerticalScrollBarViewportSize
{
get { return _viewport.Height; }
}
public Vector ScrollBarMaximum => new(Max(_extent.Width - _viewport.Width, 0), Max(_extent.Height - _viewport.Height, 0));
/// <summary>
/// Gets a value that indicates whether any scrollbar is expanded.
@ -528,82 +353,52 @@ namespace Avalonia.Controls
/// <summary>
/// Scrolls the content up one line.
/// </summary>
public void LineUp()
{
Offset -= new Vector(0, _smallChange.Height);
}
public void LineUp() => SetCurrentValue(OffsetProperty, Offset - new Vector(0, _smallChange.Height));
/// <summary>
/// Scrolls the content down one line.
/// </summary>
public void LineDown()
{
Offset += new Vector(0, _smallChange.Height);
}
public void LineDown() => SetCurrentValue(OffsetProperty, Offset + new Vector(0, _smallChange.Height));
/// <summary>
/// Scrolls the content left one line.
/// </summary>
public void LineLeft()
{
Offset -= new Vector(_smallChange.Width, 0);
}
public void LineLeft() => SetCurrentValue(OffsetProperty, Offset - new Vector(_smallChange.Width, 0));
/// <summary>
/// Scrolls the content right one line.
/// </summary>
public void LineRight()
{
Offset += new Vector(_smallChange.Width, 0);
}
public void LineRight() => SetCurrentValue(OffsetProperty, Offset + new Vector(_smallChange.Width, 0));
/// <summary>
/// Scrolls the content upward by one page.
/// </summary>
public void PageUp()
{
VerticalScrollBarValue = Math.Max(_offset.Y - _viewport.Height, 0);
}
public void PageUp() => SetCurrentValue(OffsetProperty, Offset.WithY(Math.Max(Offset.Y - _viewport.Height, 0)));
/// <summary>
/// Scrolls the content downward by one page.
/// </summary>
public void PageDown()
{
VerticalScrollBarValue = Math.Min(_offset.Y + _viewport.Height, VerticalScrollBarMaximum);
}
public void PageDown() => SetCurrentValue(OffsetProperty, Offset.WithY(Math.Min(Offset.Y + _viewport.Height, ScrollBarMaximum.Y)));
/// <summary>
/// Scrolls the content left by one page.
/// </summary>
public void PageLeft()
{
HorizontalScrollBarValue = Math.Max(_offset.X - _viewport.Width, 0);
}
public void PageLeft() => SetCurrentValue(OffsetProperty, Offset.WithX(Math.Max(Offset.X - _viewport.Width, 0)));
/// <summary>
/// Scrolls the content tight by one page.
/// </summary>
public void PageRight()
{
HorizontalScrollBarValue = Math.Min(_offset.X + _viewport.Width, HorizontalScrollBarMaximum);
}
public void PageRight() => SetCurrentValue(OffsetProperty, Offset.WithX(Math.Min(Offset.X + _viewport.Width, ScrollBarMaximum.X)));
/// <summary>
/// Scrolls to the top-left corner of the content.
/// </summary>
public void ScrollToHome()
{
Offset = new Vector(double.NegativeInfinity, double.NegativeInfinity);
}
public void ScrollToHome() => SetCurrentValue(OffsetProperty, new Vector(double.NegativeInfinity, double.NegativeInfinity));
/// <summary>
/// Scrolls to the bottom-left corner of the content.
/// </summary>
public void ScrollToEnd()
{
Offset = new Vector(double.NegativeInfinity, double.PositiveInfinity);
}
public void ScrollToEnd() => SetCurrentValue(OffsetProperty, new Vector(double.NegativeInfinity, double.PositiveInfinity));
/// <summary>
/// Gets the value of the HorizontalScrollBarVisibility attached property.
@ -819,11 +614,14 @@ namespace Avalonia.Controls
return false;
}
internal static Vector CoerceOffset(Size extent, Size viewport, Vector offset)
internal static Vector CoerceOffset(AvaloniaObject sender, Vector value)
{
var extent = sender.GetValue(ExtentProperty);
var viewport = sender.GetValue(ViewportProperty);
var maxX = Math.Max(extent.Width - viewport.Width, 0);
var maxY = Math.Max(extent.Height - viewport.Height, 0);
return new Vector(Clamp(offset.X, 0, maxX), Clamp(offset.Y, 0, maxY));
return new Vector(Clamp(value.X, 0, maxX), Clamp(value.Y, 0, maxY));
}
private static double Clamp(double value, double min, double max)
@ -859,40 +657,11 @@ namespace Avalonia.Controls
CalculatedPropertiesChanged();
}
private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs<ScrollBarVisibility> e)
{
var wasEnabled = e.OldValue.GetValueOrDefault() != ScrollBarVisibility.Disabled;
var isEnabled = e.NewValue.GetValueOrDefault() != ScrollBarVisibility.Disabled;
if (wasEnabled != isEnabled)
{
if (e.Property == HorizontalScrollBarVisibilityProperty)
{
RaisePropertyChanged(
CanHorizontallyScrollProperty,
wasEnabled,
isEnabled);
}
else if (e.Property == VerticalScrollBarVisibilityProperty)
{
RaisePropertyChanged(
CanVerticallyScrollProperty,
wasEnabled,
isEnabled);
}
}
}
private void CalculatedPropertiesChanged()
{
// Pass old values of 0 here because we don't have the old values at this point,
// and it shouldn't matter as only the template uses these properties.
RaisePropertyChanged(HorizontalScrollBarMaximumProperty, 0, HorizontalScrollBarMaximum);
RaisePropertyChanged(HorizontalScrollBarValueProperty, 0, HorizontalScrollBarValue);
RaisePropertyChanged(HorizontalScrollBarViewportSizeProperty, 0, HorizontalScrollBarViewportSize);
RaisePropertyChanged(VerticalScrollBarMaximumProperty, 0, VerticalScrollBarMaximum);
RaisePropertyChanged(VerticalScrollBarValueProperty, 0, VerticalScrollBarValue);
RaisePropertyChanged(VerticalScrollBarViewportSizeProperty, 0, VerticalScrollBarViewportSize);
var newMaximum = ScrollBarMaximum;
RaisePropertyChanged(ScrollBarMaximumProperty, _oldMaximum, newMaximum);
_oldMaximum = newMaximum;
if (_logicalScrollable?.IsLogicalScrollEnabled == true)
{
@ -906,6 +675,16 @@ namespace Avalonia.Controls
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == OffsetProperty)
{
CalculatedPropertiesChanged();
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.PageUp)

2
src/Avalonia.Controls/Slider.cs

@ -110,7 +110,7 @@ namespace Avalonia.Controls
Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e),
RoutingStrategies.Bubble);
ValueProperty.OverrideMetadata<Slider>(new DirectPropertyMetadata<double>(enableDataValidation: true));
ValueProperty.OverrideMetadata<Slider>(new(enableDataValidation: true));
AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<Slider>(AutomationControlType.Slider);
}

13
src/Avalonia.Themes.Fluent/Controls/MenuScrollViewer.xaml

@ -75,17 +75,10 @@
Height="20" />
</Viewbox>
</RepeatButton>
<ScrollContentPresenter Name="PART_ContentPresenter"
CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}"
Content="{TemplateBinding Content}"
Extent="{TemplateBinding Extent, Mode=TwoWay}"
Margin="{TemplateBinding Padding}"
Offset="{TemplateBinding Offset, Mode=TwoWay}"
Viewport="{TemplateBinding Viewport, Mode=TwoWay}">
<ScrollContentPresenter Name="PART_ContentPresenter">
<ScrollContentPresenter.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}" />
<ScrollGestureRecognizer CanHorizontallyScroll="{Binding CanHorizontallyScroll, ElementName=PART_ContentPresenter}"
CanVerticallyScroll="{Binding CanVerticallyScroll, ElementName=PART_ContentPresenter}" />
</ScrollContentPresenter.GestureRecognizers>
</ScrollContentPresenter>
</DockPanel>

35
src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml

@ -29,46 +29,23 @@
Grid.RowSpan="2"
Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}"
Content="{TemplateBinding Content}"
Extent="{TemplateBinding Extent, Mode=TwoWay}"
Padding="{TemplateBinding Padding}"
HorizontalSnapPointsType="{TemplateBinding HorizontalSnapPointsType}"
VerticalSnapPointsType="{TemplateBinding VerticalSnapPointsType}"
HorizontalSnapPointsAlignment="{TemplateBinding HorizontalSnapPointsAlignment}"
VerticalSnapPointsAlignment="{TemplateBinding VerticalSnapPointsAlignment}"
Offset="{TemplateBinding Offset, Mode=TwoWay}"
Viewport="{TemplateBinding Viewport, Mode=TwoWay}"
IsScrollChainingEnabled="{TemplateBinding IsScrollChainingEnabled}">
Padding="{TemplateBinding Padding}">
<ScrollContentPresenter.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}"
IsScrollInertiaEnabled="{TemplateBinding IsScrollInertiaEnabled}"/>
<ScrollGestureRecognizer CanHorizontallyScroll="{Binding CanHorizontallyScroll, ElementName=PART_ContentPresenter}"
CanVerticallyScroll="{Binding CanVerticallyScroll, ElementName=PART_ContentPresenter}"
IsScrollInertiaEnabled="{TemplateBinding IsScrollInertiaEnabled}" />
</ScrollContentPresenter.GestureRecognizers>
</ScrollContentPresenter>
<ScrollBar Name="PART_HorizontalScrollBar"
AllowAutoHide="{TemplateBinding AllowAutoHide}"
Orientation="Horizontal"
LargeChange="{Binding LargeChange.Width, RelativeSource={RelativeSource TemplatedParent}}"
SmallChange="{Binding SmallChange.Width, RelativeSource={RelativeSource TemplatedParent}}"
Maximum="{TemplateBinding HorizontalScrollBarMaximum}"
Value="{TemplateBinding HorizontalScrollBarValue, Mode=TwoWay}"
ViewportSize="{TemplateBinding HorizontalScrollBarViewportSize}"
Visibility="{TemplateBinding HorizontalScrollBarVisibility}"
Grid.Row="1"
Focusable="False" />
Grid.Row="1" />
<ScrollBar Name="PART_VerticalScrollBar"
AllowAutoHide="{TemplateBinding AllowAutoHide}"
Orientation="Vertical"
LargeChange="{Binding LargeChange.Height, RelativeSource={RelativeSource TemplatedParent}}"
SmallChange="{Binding SmallChange.Height, RelativeSource={RelativeSource TemplatedParent}}"
Maximum="{TemplateBinding VerticalScrollBarMaximum}"
Value="{TemplateBinding VerticalScrollBarValue, Mode=TwoWay}"
ViewportSize="{TemplateBinding VerticalScrollBarViewportSize}"
Visibility="{TemplateBinding VerticalScrollBarVisibility}"
Grid.Column="1"
Focusable="False" />
Grid.Column="1" />
<Panel x:Name="PART_ScrollBarsSeparator"
Grid.Row="1"
Grid.Column="1"

51
src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml

@ -10,48 +10,22 @@
RowDefinitions="*,Auto">
<ScrollContentPresenter Name="PART_ContentPresenter"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}"
Content="{TemplateBinding Content}"
HorizontalSnapPointsType="{TemplateBinding HorizontalSnapPointsType}"
VerticalSnapPointsType="{TemplateBinding VerticalSnapPointsType}"
HorizontalSnapPointsAlignment="{TemplateBinding HorizontalSnapPointsAlignment}"
VerticalSnapPointsAlignment="{TemplateBinding VerticalSnapPointsAlignment}"
Extent="{TemplateBinding Extent,
Mode=TwoWay}"
IsScrollChainingEnabled="{TemplateBinding IsScrollChainingEnabled}"
Viewport="{TemplateBinding Viewport,
Mode=TwoWay}"
Offset="{TemplateBinding Offset,
Mode=TwoWay}">
Background="{TemplateBinding Background}">
<ScrollContentPresenter.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}" />
<ScrollGestureRecognizer CanHorizontallyScroll="{Binding CanHorizontallyScroll, ElementName=PART_ContentPresenter}"
CanVerticallyScroll="{Binding CanVerticallyScroll, ElementName=PART_ContentPresenter}" />
</ScrollContentPresenter.GestureRecognizers>
</ScrollContentPresenter>
<ScrollBar Name="horizontalScrollBar"
<ScrollBar Name="PART_HorizontalScrollBar"
Grid.Row="1"
Focusable="False"
LargeChange="{Binding LargeChange.Width, RelativeSource={RelativeSource TemplatedParent}}"
Maximum="{TemplateBinding HorizontalScrollBarMaximum}"
Orientation="Horizontal"
SmallChange="{Binding SmallChange.Width, RelativeSource={RelativeSource TemplatedParent}}"
ViewportSize="{TemplateBinding HorizontalScrollBarViewportSize}"
Visibility="{TemplateBinding HorizontalScrollBarVisibility}"
Value="{TemplateBinding HorizontalScrollBarValue,
Mode=TwoWay}" />
<ScrollBar Name="verticalScrollBar"
Orientation="Horizontal"/>
<ScrollBar Name="PART_VerticalScrollBar"
Grid.Column="1"
Focusable="False"
LargeChange="{Binding LargeChange.Height, RelativeSource={RelativeSource TemplatedParent}}"
Maximum="{TemplateBinding VerticalScrollBarMaximum}"
Orientation="Vertical"
SmallChange="{Binding SmallChange.Height, RelativeSource={RelativeSource TemplatedParent}}"
ViewportSize="{TemplateBinding VerticalScrollBarViewportSize}"
Visibility="{TemplateBinding VerticalScrollBarVisibility}"
Value="{TemplateBinding VerticalScrollBarValue,
Mode=TwoWay}" />
Orientation="Vertical"/>
<Panel Grid.Row="1"
Grid.Column="1"
Background="{DynamicResource ThemeControlMidBrush}" />
@ -105,16 +79,7 @@
<Path Data="M 0 0 L 4 4 L 8 0 Z" />
</RepeatButton>
<ScrollContentPresenter Name="PART_ContentPresenter"
Margin="{TemplateBinding Padding}"
CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}"
Content="{TemplateBinding Content}"
Extent="{TemplateBinding Extent,
Mode=TwoWay}"
Viewport="{TemplateBinding Viewport,
Mode=TwoWay}"
Offset="{TemplateBinding Offset,
Mode=TwoWay}" />
Margin="{TemplateBinding Padding}" />
</DockPanel>
</ControlTemplate>
</Setter>

14
tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs

@ -384,31 +384,17 @@ namespace Avalonia.Base.UnitTests.Layout
new ScrollContentPresenter
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
[~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty],
[~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty],
[~~ScrollContentPresenter.ViewportProperty] = control[~~ScrollViewer.ViewportProperty],
[~ScrollContentPresenter.CanHorizontallyScrollProperty] = control[~ScrollViewer.CanHorizontallyScrollProperty],
[~ScrollContentPresenter.CanVerticallyScrollProperty] = control[~ScrollViewer.CanVerticallyScrollProperty],
}.RegisterInNameScope(scope),
new ScrollBar
{
Name = "horizontalScrollBar",
Orientation = Orientation.Horizontal,
[~RangeBase.MaximumProperty] = control[~ScrollViewer.HorizontalScrollBarMaximumProperty],
[~~RangeBase.ValueProperty] = control[~~ScrollViewer.HorizontalScrollBarValueProperty],
[~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.HorizontalScrollBarViewportSizeProperty],
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty],
[Grid.RowProperty] = 1,
}.RegisterInNameScope(scope),
new ScrollBar
{
Name = "verticalScrollBar",
Orientation = Orientation.Vertical,
[~RangeBase.MaximumProperty] = control[~ScrollViewer.VerticalScrollBarMaximumProperty],
[~~RangeBase.ValueProperty] = control[~~ScrollViewer.VerticalScrollBarValueProperty],
[~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.VerticalScrollBarViewportSizeProperty],
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty],
[Grid.ColumnProperty] = 1,
}.RegisterInNameScope(scope),
},

6
tests/Avalonia.Controls.UnitTests/CarouselTests.cs

@ -301,12 +301,6 @@ namespace Avalonia.Controls.UnitTests
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),
}
});

9
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -590,18 +590,11 @@ namespace Avalonia.Controls.UnitTests
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),
new ScrollBar
{
Name = "verticalScrollBar",
[~ScrollBar.MaximumProperty] = parent[~ScrollViewer.VerticalScrollBarMaximumProperty],
[~~ScrollBar.ValueProperty] = parent[~~ScrollViewer.VerticalScrollBarValueProperty],
Orientation = Orientation.Vertical,
}
}
});

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

@ -21,45 +21,20 @@ namespace Avalonia.Controls.UnitTests
Content = "Foo",
};
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
InitializeScrollViewer(target);
Assert.IsType<TextBlock>(target.Presenter.Child);
}
[Fact]
public void CanHorizontallyScroll_Should_Track_HorizontalScrollBarVisibility()
{
var target = new ScrollViewer();
var values = new List<bool>();
target.GetObservable(ScrollViewer.CanHorizontallyScrollProperty).Subscribe(x => values.Add(x));
target.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
target.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
Assert.Equal(new[] { false, true }, values);
}
[Fact]
public void CanVerticallyScroll_Should_Track_VerticalScrollBarVisibility()
{
var target = new ScrollViewer();
var values = new List<bool>();
target.GetObservable(ScrollViewer.CanVerticallyScrollProperty).Subscribe(x => values.Add(x));
target.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
target.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
Assert.Equal(new[] { true, false, true }, values);
}
[Fact]
public void Offset_Should_Be_Coerced_To_Viewport()
{
var target = new ScrollViewer();
target.SetValue(ScrollViewer.ExtentProperty, new Size(20, 20));
target.SetValue(ScrollViewer.ViewportProperty, new Size(10, 10));
target.Offset = new Vector(12, 12);
var target = new ScrollViewer
{
Extent = new Size(20, 20),
Viewport = new Size(10, 10),
Offset = new Vector(12, 12)
};
Assert.Equal(new Vector(10, 10), target.Offset);
}
@ -67,10 +42,12 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Test_ScrollToHome()
{
var target = new ScrollViewer();
target.SetValue(ScrollViewer.ExtentProperty, new Size(50, 50));
target.SetValue(ScrollViewer.ViewportProperty, new Size(10, 10));
target.Offset = new Vector(25, 25);
var target = new ScrollViewer
{
Extent = new Size(50, 50),
Viewport = new Size(10, 10),
Offset = new Vector(25, 25)
};
target.ScrollToHome();
Assert.Equal(new Vector(0, 0), target.Offset);
@ -79,10 +56,12 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Test_ScrollToEnd()
{
var target = new ScrollViewer();
target.SetValue(ScrollViewer.ExtentProperty, new Size(50, 50));
target.SetValue(ScrollViewer.ViewportProperty, new Size(10, 10));
target.Offset = new Vector(25, 25);
var target = new ScrollViewer
{
Extent = new Size(50, 50),
Viewport = new Size(10, 10),
Offset = new Vector(25, 25)
};
target.ScrollToEnd();
Assert.Equal(new Vector(0, 40), target.Offset);
@ -99,9 +78,10 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void LargeChange_Should_Be_Viewport()
{
var target = new ScrollViewer();
target.SetValue(ScrollViewer.ViewportProperty, new Size(104, 143));
var target = new ScrollViewer
{
Viewport = new Size(104, 143)
};
Assert.Equal(new Size(104, 143), target.LargeChange);
}
@ -120,8 +100,7 @@ namespace Avalonia.Controls.UnitTests
Content = child.Object,
};
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
InitializeScrollViewer(target);
Assert.Equal(new Size(12, 43), target.SmallChange);
}
@ -141,8 +120,7 @@ namespace Avalonia.Controls.UnitTests
Content = child.Object,
};
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
InitializeScrollViewer(target);
Assert.Equal(new Size(45, 67), target.LargeChange);
}
@ -154,8 +132,8 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
var raised = 0;
target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100));
target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
target.Extent = new Size(100, 100);
target.Viewport = new Size(50, 50);
target.Offset = new Vector(10, 10);
root.LayoutManager.ExecuteInitialLayoutPass();
@ -168,7 +146,7 @@ namespace Avalonia.Controls.UnitTests
++raised;
};
target.SetValue(ScrollViewer.ExtentProperty, new Size(111, 112));
target.Extent = new Size(111, 112);
Assert.Equal(0, raised);
@ -184,8 +162,8 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
var raised = 0;
target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100));
target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
target.Extent = new Size(100, 100);
target.Viewport = new Size(50, 50);
target.Offset = new Vector(10, 10);
root.LayoutManager.ExecuteInitialLayoutPass();
@ -214,8 +192,8 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
var raised = 0;
target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100));
target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
target.Extent = new Size(100, 100);
target.Viewport = new Size(50, 50);
target.Offset = new Vector(10, 10);
root.LayoutManager.ExecuteInitialLayoutPass();
@ -228,7 +206,7 @@ namespace Avalonia.Controls.UnitTests
++raised;
};
target.SetValue(ScrollViewer.ViewportProperty, new Size(56, 58));
target.Viewport = new Size(56, 58);
Assert.Equal(0, raised);
@ -290,34 +268,32 @@ namespace Avalonia.Controls.UnitTests
new ScrollContentPresenter
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
[~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty],
[~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty],
[~~ScrollContentPresenter.ViewportProperty] = control[~~ScrollViewer.ViewportProperty],
[~ScrollContentPresenter.CanHorizontallyScrollProperty] = control[~ScrollViewer.CanHorizontallyScrollProperty],
}.RegisterInNameScope(scope),
new ScrollBar
{
Name = "horizontalScrollBar",
Name = "PART_HorizontalScrollBar",
Orientation = Orientation.Horizontal,
[~RangeBase.MaximumProperty] = control[~ScrollViewer.HorizontalScrollBarMaximumProperty],
[~~RangeBase.ValueProperty] = control[~~ScrollViewer.HorizontalScrollBarValueProperty],
[~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.HorizontalScrollBarViewportSizeProperty],
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty],
[Grid.RowProperty] = 1,
}.RegisterInNameScope(scope),
new ScrollBar
{
Name = "verticalScrollBar",
Name = "PART_VerticalScrollBar",
Orientation = Orientation.Vertical,
[~RangeBase.MaximumProperty] = control[~ScrollViewer.VerticalScrollBarMaximumProperty],
[~~RangeBase.ValueProperty] = control[~~ScrollViewer.VerticalScrollBarValueProperty],
[~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.VerticalScrollBarViewportSizeProperty],
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty],
[Grid.ColumnProperty] = 1,
}.RegisterInNameScope(scope),
},
};
}
private static void InitializeScrollViewer(ScrollViewer target)
{
target.ApplyTemplate();
var presenter = (ScrollContentPresenter)target.Presenter;
presenter.AttachToScrollViewer();
presenter.UpdateChild();
}
}
}

10
tests/Avalonia.Controls.UnitTests/VirtualizingCarouselPanelTests.cs

@ -163,13 +163,13 @@ namespace Avalonia.Controls.UnitTests
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();
@ -255,12 +255,6 @@ namespace Avalonia.Controls.UnitTests
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),
}
});

6
tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

@ -565,12 +565,6 @@ namespace Avalonia.Controls.UnitTests
new ScrollContentPresenter
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = x[~ContentControl.ContentProperty],
[~~ScrollContentPresenter.ExtentProperty] = x[~~ScrollViewer.ExtentProperty],
[~~ScrollContentPresenter.OffsetProperty] = x[~~ScrollViewer.OffsetProperty],
[~~ScrollContentPresenter.ViewportProperty] = x[~~ScrollViewer.ViewportProperty],
[~ScrollContentPresenter.CanHorizontallyScrollProperty] = x[~ScrollViewer.CanHorizontallyScrollProperty],
[~ScrollContentPresenter.CanVerticallyScrollProperty] = x[~ScrollViewer.CanVerticallyScrollProperty],
}.RegisterInNameScope(ns));
}

Loading…
Cancel
Save