using Avalonia; using Avalonia.Animation; using Avalonia.Animation.Easings; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.VisualTree; using Irihi.Avalonia.Shared.Helpers; using Ursa.Common; namespace Ursa.Controls; public class ScrollToButton: Button { private ScrollViewer? _scroll; private IDisposable? _disposable; public static readonly StyledProperty TargetProperty = AvaloniaProperty.Register( nameof(Target)); public Control Target { get => GetValue(TargetProperty); set => SetValue(TargetProperty, value); } public static readonly StyledProperty DirectionProperty = AvaloniaProperty.Register( nameof(Direction)); public Position Direction { get => GetValue(DirectionProperty); set => SetValue(DirectionProperty, value); } static ScrollToButton() { TargetProperty.Changed.AddClassHandler((o,e)=>o.OnTargetChanged(e)); DirectionProperty.Changed.AddClassHandler((o,e)=>o.OnDirectionChanged(e)); } private void OnDirectionChanged(AvaloniaPropertyChangedEventArgs avaloniaPropertyChangedEventArgs) { if (_scroll is null) return; SetVisibility(avaloniaPropertyChangedEventArgs.NewValue.Value, _scroll.Offset); } private void OnTargetChanged(AvaloniaPropertyChangedEventArgs arg2) { _disposable?.Dispose(); if (arg2.NewValue.Value is { } newValue) { var scroll = newValue.GetSelfAndVisualDescendants().OfType().FirstOrDefault(); if (_scroll is not null) { _disposable?.Dispose(); _scroll = null; } _scroll = scroll; _disposable = ScrollViewer.OffsetProperty.Changed.AddClassHandler(OnScrollChanged); SetVisibility(Direction, _scroll?.Offset); } } protected override async void OnClick() { if (_scroll is null) return; var vector = Direction switch { Position.Top => new Vector(0, double.NegativeInfinity), Position.Bottom => new Vector(0, double.PositiveInfinity), Position.Left => new Vector(double.NegativeInfinity, 0), Position.Right => new Vector(double.PositiveInfinity, 0), _ => new Vector(0, 0) }; _scroll.SetCurrentValue(ScrollViewer.OffsetProperty, vector); } protected override void OnLoaded(RoutedEventArgs e) { base.OnLoaded(e); var scroll = Target.GetSelfAndVisualDescendants().OfType().FirstOrDefault(); if (_scroll is not null) { _disposable?.Dispose(); _scroll = null; } _scroll = scroll; _disposable = ScrollViewer.OffsetProperty.Changed.AddClassHandler(OnScrollChanged); SetVisibility(Direction, _scroll?.Offset); } private void OnScrollChanged(ScrollViewer arg1, AvaloniaPropertyChangedEventArgs arg2) { if (arg1 != _scroll) return; SetVisibility(Direction, arg2.NewValue.Value); } private void SetVisibility(Position direction, Vector? vector) { if (vector is null || _scroll is null) return; if (direction == Position.Bottom && vector.Value.Y < _scroll.Extent.Height - _scroll.Bounds.Height) { IsVisible = true; } else if (direction == Position.Top && vector.Value.Y > 0) { IsVisible = true; } else if (direction == Position.Left && vector.Value.X > 0) { IsVisible = true; } else if (direction == Position.Right && vector.Value.X < _scroll.Extent.Width - _scroll.Bounds.Width) { IsVisible = true; } else { IsVisible = false; } } }