// 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;
}
}
}
}