// 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 System; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Input; namespace Avalonia.Controls { /// /// A control scrolls its content if the content is bigger than the space available. /// public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider { /// /// Defines the property. /// /// /// There is no public C# accessor for this property as it is intended to be bound to by a /// in the control's template. /// public static readonly DirectProperty CanHorizontallyScrollProperty = AvaloniaProperty.RegisterDirect( nameof(CanHorizontallyScroll), o => o.CanHorizontallyScroll); /// /// Defines the property. /// /// /// There is no public C# accessor for this property as it is intended to be bound to by a /// in the control's template. /// public static readonly DirectProperty CanVerticallyScrollProperty = AvaloniaProperty.RegisterDirect( nameof(CanVerticallyScroll), o => o.CanVerticallyScroll); /// /// Defines the property. /// public static readonly DirectProperty ExtentProperty = AvaloniaProperty.RegisterDirect(nameof(Extent), o => o.Extent, (o, v) => o.Extent = v); /// /// Defines the property. /// public static readonly DirectProperty OffsetProperty = AvaloniaProperty.RegisterDirect( nameof(Offset), o => o.Offset, (o, v) => o.Offset = v); /// /// Defines the property. /// public static readonly DirectProperty ViewportProperty = AvaloniaProperty.RegisterDirect(nameof(Viewport), o => o.Viewport, (o, v) => o.Viewport = v); /// /// Defines the HorizontalScrollBarMaximum property. /// /// /// There is no public C# accessor for this property as it is intended to be bound to by a /// in the control's template. /// public static readonly DirectProperty HorizontalScrollBarMaximumProperty = AvaloniaProperty.RegisterDirect( nameof(HorizontalScrollBarMaximum), o => o.HorizontalScrollBarMaximum); /// /// Defines the HorizontalScrollBarValue property. /// /// /// There is no public C# accessor for this property as it is intended to be bound to by a /// in the control's template. /// public static readonly DirectProperty HorizontalScrollBarValueProperty = AvaloniaProperty.RegisterDirect( nameof(HorizontalScrollBarValue), o => o.HorizontalScrollBarValue, (o, v) => o.HorizontalScrollBarValue = v); /// /// Defines the HorizontalScrollBarViewportSize property. /// /// /// There is no public C# accessor for this property as it is intended to be bound to by a /// in the control's template. /// public static readonly DirectProperty HorizontalScrollBarViewportSizeProperty = AvaloniaProperty.RegisterDirect( nameof(HorizontalScrollBarViewportSize), o => o.HorizontalScrollBarViewportSize); /// /// Defines the property. /// public static readonly AttachedProperty HorizontalScrollBarVisibilityProperty = AvaloniaProperty.RegisterAttached( nameof(HorizontalScrollBarVisibility), ScrollBarVisibility.Hidden); /// /// Defines the VerticalScrollBarMaximum property. /// /// /// There is no public C# accessor for this property as it is intended to be bound to by a /// in the control's template. /// public static readonly DirectProperty VerticalScrollBarMaximumProperty = AvaloniaProperty.RegisterDirect( nameof(VerticalScrollBarMaximum), o => o.VerticalScrollBarMaximum); /// /// Defines the VerticalScrollBarValue property. /// /// /// There is no public C# accessor for this property as it is intended to be bound to by a /// in the control's template. /// public static readonly DirectProperty VerticalScrollBarValueProperty = AvaloniaProperty.RegisterDirect( nameof(VerticalScrollBarValue), o => o.VerticalScrollBarValue, (o, v) => o.VerticalScrollBarValue = v); /// /// Defines the VerticalScrollBarViewportSize property. /// /// /// There is no public C# accessor for this property as it is intended to be bound to by a /// in the control's template. /// public static readonly DirectProperty VerticalScrollBarViewportSizeProperty = AvaloniaProperty.RegisterDirect( nameof(VerticalScrollBarViewportSize), o => o.VerticalScrollBarViewportSize); /// /// Defines the property. /// public static readonly AttachedProperty VerticalScrollBarVisibilityProperty = AvaloniaProperty.RegisterAttached( nameof(VerticalScrollBarVisibility), ScrollBarVisibility.Auto); private Size _extent; private Vector _offset; private Size _viewport; /// /// Initializes static members of the class. /// static ScrollViewer() { HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler((x, e) => x.ScrollBarVisibilityChanged(e)); VerticalScrollBarVisibilityProperty.Changed.AddClassHandler((x, e) => x.ScrollBarVisibilityChanged(e)); } /// /// Initializes a new instance of the class. /// public ScrollViewer() { } /// /// Gets the extent of the scrollable content. /// public Size Extent { get { return _extent; } private set { if (SetAndRaise(ExtentProperty, ref _extent, value)) { CalculatedPropertiesChanged(); } } } /// /// Gets or sets the current scroll offset. /// public Vector Offset { get { return _offset; } set { value = ValidateOffset(this, value); if (SetAndRaise(OffsetProperty, ref _offset, value)) { CalculatedPropertiesChanged(); } } } /// /// Gets the size of the viewport on the scrollable content. /// public Size Viewport { get { return _viewport; } private set { if (SetAndRaise(ViewportProperty, ref _viewport, value)) { CalculatedPropertiesChanged(); } } } /// /// Gets or sets the horizontal scrollbar visibility. /// public ScrollBarVisibility HorizontalScrollBarVisibility { get { return GetValue(HorizontalScrollBarVisibilityProperty); } set { SetValue(HorizontalScrollBarVisibilityProperty, value); } } /// /// Gets or sets the vertical scrollbar visibility. /// public ScrollBarVisibility VerticalScrollBarVisibility { get { return GetValue(VerticalScrollBarVisibilityProperty); } set { SetValue(VerticalScrollBarVisibilityProperty, value); } } /// /// Scrolls to the top-left corner of the content. /// public void ScrollToHome() { Offset = new Vector(double.NegativeInfinity, double.NegativeInfinity); } /// /// Scrolls to the bottom-left corner of the content. /// public void ScrollToEnd() { Offset = new Vector(double.NegativeInfinity, double.PositiveInfinity); } /// /// Gets a value indicating whether the viewer can scroll horizontally. /// protected bool CanHorizontallyScroll { get { return HorizontalScrollBarVisibility != ScrollBarVisibility.Disabled; } } /// /// Gets a value indicating whether the viewer can scroll vertically. /// protected bool CanVerticallyScroll { get { return VerticalScrollBarVisibility != ScrollBarVisibility.Disabled; } } /// /// Gets the maximum horizontal scrollbar value. /// protected double HorizontalScrollBarMaximum { get { return Max(_extent.Width - _viewport.Width, 0); } } /// /// Gets or sets the horizontal scrollbar value. /// protected double HorizontalScrollBarValue { get { return _offset.X; } set { if (_offset.X != value) { var old = Offset.X; Offset = Offset.WithX(value); RaisePropertyChanged(HorizontalScrollBarValueProperty, old, value); } } } /// /// Gets the size of the horizontal scrollbar viewport. /// protected double HorizontalScrollBarViewportSize { get { return _viewport.Width; } } /// /// Gets the maximum vertical scrollbar value. /// protected double VerticalScrollBarMaximum { get { return Max(_extent.Height - _viewport.Height, 0); } } /// /// Gets or sets the vertical scrollbar value. /// protected double VerticalScrollBarValue { get { return _offset.Y; } set { if (_offset.Y != value) { var old = Offset.Y; Offset = Offset.WithY(value); RaisePropertyChanged(VerticalScrollBarValueProperty, old, value); } } } /// /// Gets the size of the vertical scrollbar viewport. /// protected double VerticalScrollBarViewportSize { get { return _viewport.Height; } } /// IControl IScrollAnchorProvider.CurrentAnchor => null; // TODO: Implement /// /// Gets the value of the HorizontalScrollBarVisibility attached property. /// /// The control to read the value from. /// The value of the property. public static ScrollBarVisibility GetHorizontalScrollBarVisibility(Control control) { return control.GetValue(HorizontalScrollBarVisibilityProperty); } /// /// Gets the value of the HorizontalScrollBarVisibility attached property. /// /// The control to set the value on. /// The value of the property. public static void SetHorizontalScrollBarVisibility(Control control, ScrollBarVisibility value) { control.SetValue(HorizontalScrollBarVisibilityProperty, value); } /// /// Gets the value of the VerticalScrollBarVisibility attached property. /// /// The control to read the value from. /// The value of the property. public static ScrollBarVisibility GetVerticalScrollBarVisibility(Control control) { return control.GetValue(VerticalScrollBarVisibilityProperty); } /// /// Gets the value of the VerticalScrollBarVisibility attached property. /// /// The control to set the value on. /// The value of the property. public static void SetVerticalScrollBarVisibility(Control control, ScrollBarVisibility value) { control.SetValue(VerticalScrollBarVisibilityProperty, value); } void IScrollAnchorProvider.RegisterAnchorCandidate(IControl element) { // TODO: Implement } void IScrollAnchorProvider.UnregisterAnchorCandidate(IControl element) { // TODO: Implement } internal static Vector CoerceOffset(Size extent, Size viewport, Vector offset) { 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)); } private static double Clamp(double value, double min, double max) { return (value < min) ? min : (value > max) ? max : value; } private static double Max(double x, double y) { var result = Math.Max(x, y); return double.IsNaN(result) ? 0 : result; } private static Vector ValidateOffset(AvaloniaObject o, Vector value) { ScrollViewer scrollViewer = o as ScrollViewer; if (scrollViewer != null) { var extent = scrollViewer.Extent; var viewport = scrollViewer.Viewport; return CoerceOffset(extent, viewport, value); } else { return value; } } private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs e) { var wasEnabled = !ScrollBarVisibility.Disabled.Equals(e.OldValue); var isEnabled = !ScrollBarVisibility.Disabled.Equals(e.NewValue); 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 properies. RaisePropertyChanged(HorizontalScrollBarMaximumProperty, 0, HorizontalScrollBarMaximum); RaisePropertyChanged(HorizontalScrollBarValueProperty, 0, HorizontalScrollBarValue); RaisePropertyChanged(HorizontalScrollBarViewportSizeProperty, 0, HorizontalScrollBarViewportSize); RaisePropertyChanged(VerticalScrollBarMaximumProperty, 0, VerticalScrollBarMaximum); RaisePropertyChanged(VerticalScrollBarValueProperty, 0, VerticalScrollBarValue); RaisePropertyChanged(VerticalScrollBarViewportSizeProperty, 0, VerticalScrollBarViewportSize); } protected override void OnKeyDown(KeyEventArgs e) { if (e.Key == Key.PageUp) { VerticalScrollBarValue = Math.Max(_offset.Y - _viewport.Height, 0); e.Handled = true; } else if (e.Key == Key.PageDown) { VerticalScrollBarValue = Math.Min(_offset.Y + _viewport.Height, VerticalScrollBarMaximum); e.Handled = true; } } } }