206 lines
6.9 KiB
C#
206 lines
6.9 KiB
C#
|
|
|
|
|
|
using System.Windows.Controls;
|
|
|
|
// ReSharper disable once CheckNamespace
|
|
namespace WPFluent.Controls;
|
|
|
|
/// <summary>
|
|
/// HCWhite <see cref="ScrollViewer"/> with events depending on actions taken by the user.
|
|
/// </summary>
|
|
[DefaultEvent("ScrollChangedEvent")]
|
|
public class DynamicScrollViewer : PassiveScrollViewer
|
|
{
|
|
/// <summary>
|
|
/// Identifies the <see cref="IsScrollingHorizontally"/> dependency property.
|
|
/// </summary>
|
|
public static readonly DependencyProperty IsScrollingHorizontallyProperty = DependencyProperty.Register(
|
|
nameof(IsScrollingHorizontally),
|
|
typeof(bool),
|
|
typeof(DynamicScrollViewer),
|
|
new PropertyMetadata(false, OnIsScrollingHorizontallyChanged));
|
|
|
|
/// <summary>
|
|
/// Identifies the <see cref="IsScrollingVertically"/> dependency property.
|
|
/// </summary>
|
|
public static readonly DependencyProperty IsScrollingVerticallyProperty = DependencyProperty.Register(
|
|
nameof(IsScrollingVertically),
|
|
typeof(bool),
|
|
typeof(DynamicScrollViewer),
|
|
new PropertyMetadata(false, OnIsScrollingVerticallyChanged));
|
|
|
|
/// <summary>
|
|
/// Identifies the <see cref="MinimalChange"/> dependency property.
|
|
/// </summary>
|
|
public static readonly DependencyProperty MinimalChangeProperty = DependencyProperty.Register(
|
|
nameof(MinimalChange),
|
|
typeof(double),
|
|
typeof(DynamicScrollViewer),
|
|
new PropertyMetadata(40d, OnMinimalChangeChanged));
|
|
|
|
/// <summary>
|
|
/// Identifies the <see cref="Timeout"/> dependency property.
|
|
/// </summary>
|
|
public static readonly DependencyProperty TimeoutProperty = DependencyProperty.Register(
|
|
nameof(Timeout),
|
|
typeof(int),
|
|
typeof(DynamicScrollViewer),
|
|
new PropertyMetadata(1200, OnTimeoutChanged));
|
|
|
|
private readonly EventIdentifier _horizontalIdentifier = new();
|
|
|
|
private double _minimalChange = 40d;
|
|
|
|
private bool _scrollingHorizontally = false;
|
|
|
|
// Due to the large number of triggered events, we limit the complex logic of DependencyProperty
|
|
private bool _scrollingVertically = false;
|
|
|
|
private int _timeout = 1200;
|
|
private readonly EventIdentifier _verticalIdentifier = new();
|
|
|
|
private static void OnIsScrollingHorizontallyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
if(d is not DynamicScrollViewer scroll)
|
|
{
|
|
return;
|
|
}
|
|
|
|
scroll._scrollingHorizontally = scroll.IsScrollingHorizontally;
|
|
}
|
|
|
|
private static void OnIsScrollingVerticallyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
if(d is not DynamicScrollViewer scroll)
|
|
{
|
|
return;
|
|
}
|
|
|
|
scroll._scrollingVertically = scroll.IsScrollingVertically;
|
|
}
|
|
|
|
private static void OnMinimalChangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
if(d is not DynamicScrollViewer scroll)
|
|
{
|
|
return;
|
|
}
|
|
|
|
scroll._minimalChange = scroll.MinimalChange;
|
|
}
|
|
|
|
private static void OnTimeoutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
if(d is not DynamicScrollViewer scroll)
|
|
{
|
|
return;
|
|
}
|
|
|
|
scroll._timeout = scroll.Timeout;
|
|
}
|
|
|
|
private async void UpdateHorizontalScrollingState()
|
|
{
|
|
// TODO: Optimize
|
|
// My main assumption here is that each scroll causes a new "event / thread" to be assigned.
|
|
// If more than Timeout has passed since the last event, there is no interaction.
|
|
// We pass this value to the ScrollBar and link it to IsMouseOver.
|
|
// This way we have a dynamic scrollbar that responds to scroll / mouse over.
|
|
long currentEvent = _horizontalIdentifier.GetNext();
|
|
|
|
if(!_scrollingHorizontally)
|
|
{
|
|
SetCurrentValue(IsScrollingHorizontallyProperty, true);
|
|
}
|
|
|
|
await Task.Delay(Timeout < 10000 ? Timeout : 1000);
|
|
|
|
if(_horizontalIdentifier.IsEqual(currentEvent) && _scrollingHorizontally)
|
|
{
|
|
SetCurrentValue(IsScrollingHorizontallyProperty, false);
|
|
}
|
|
}
|
|
|
|
private async void UpdateVerticalScrollingState()
|
|
{
|
|
// TODO: Optimize
|
|
// My main assumption here is that each scroll causes a new "event / thread" to be assigned.
|
|
// If more than Timeout has passed since the last event, there is no interaction.
|
|
// We pass this value to the ScrollBar and link it to IsMouseOver.
|
|
// This way we have a dynamic scrollbar that responds to scroll / mouse over.
|
|
long currentEvent = _verticalIdentifier.GetNext();
|
|
|
|
if(!_scrollingVertically)
|
|
{
|
|
SetCurrentValue(IsScrollingVerticallyProperty, true);
|
|
}
|
|
|
|
if(_timeout > -1)
|
|
{
|
|
await Task.Delay(_timeout < 10000 ? _timeout : 1000);
|
|
}
|
|
|
|
if(_verticalIdentifier.IsEqual(currentEvent) && _scrollingVertically)
|
|
{
|
|
SetCurrentValue(IsScrollingVerticallyProperty, false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// OnScrollChanged is an override called whenever scrolling state changes on this <see
|
|
/// cref="DynamicScrollViewer"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// OnScrollChanged fires the ScrollChangedEvent. Overriders of this method should call base.OnScrollChanged(args)
|
|
/// if they want the event to be fired.
|
|
/// </remarks>
|
|
/// <param name="e">ScrollChangedEventArgs containing information about the change in scrolling state.</param>
|
|
protected override void OnScrollChanged(ScrollChangedEventArgs e)
|
|
{
|
|
base.OnScrollChanged(e);
|
|
|
|
if(e.HorizontalChange > _minimalChange || e.HorizontalChange < -_minimalChange)
|
|
{
|
|
UpdateHorizontalScrollingState();
|
|
}
|
|
|
|
if(e.VerticalChange > _minimalChange || e.VerticalChange < -_minimalChange)
|
|
{
|
|
UpdateVerticalScrollingState();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the user was scrolling horizontally for the last few seconds.
|
|
/// </summary>
|
|
public bool IsScrollingHorizontally
|
|
{
|
|
get => (bool)GetValue(IsScrollingHorizontallyProperty);
|
|
set => SetValue(IsScrollingHorizontallyProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the user was scrolling vertically for the last few seconds.
|
|
/// </summary>
|
|
public bool IsScrollingVertically
|
|
{
|
|
get => (bool)GetValue(IsScrollingVerticallyProperty);
|
|
set => SetValue(IsScrollingVerticallyProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the value required for the scroll to show automatically.
|
|
/// </summary>
|
|
public double MinimalChange
|
|
{
|
|
get => (double)GetValue(MinimalChangeProperty);
|
|
set => SetValue(MinimalChangeProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets time after which the scroll is to be hidden.
|
|
/// </summary>
|
|
public int Timeout { get => (int)GetValue(TimeoutProperty); set => SetValue(TimeoutProperty, value); }
|
|
}
|