diff --git a/src/Avalonia/HandyControlDemo_Avalonia/Data/DemoInfo.json b/src/Avalonia/HandyControlDemo_Avalonia/Data/DemoInfo.json
index d2c0eb21..cc757c13 100644
--- a/src/Avalonia/HandyControlDemo_Avalonia/Data/DemoInfo.json
+++ b/src/Avalonia/HandyControlDemo_Avalonia/Data/DemoInfo.json
@@ -46,6 +46,7 @@
[ "ButtonGroup", "ButtonGroupDemo", "Brush.ButtonGroup", "", "Button" ],
[ "ProgressButton", "ProgressButtonDemo", "Brush.ProgressBarStatusBar8", "", "Button" ],
[ "Card", "CardDemo", "Brush.ContactCard", "", "DataDisplay" ],
+ [ "GotoTop", "GotoTopDemo", "Brush.GoToTop", "", "Interacting" ],
[ "PinBox", "PinBoxDemo", "Brush.Ellipsis", "", "DataEntry" ],
[ "SearchBar", "SearchBarDemo", "Brush.Search", "", "DataEntry" ],
[ "ElementGroup", "ElementGroupDemo", "Brush.ButtonGroup", "", "Layout" ],
diff --git a/src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/GotoTopDemo.axaml b/src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/GotoTopDemo.axaml
new file mode 100644
index 00000000..5af2e959
--- /dev/null
+++ b/src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/GotoTopDemo.axaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/GotoTopDemo.axaml.cs b/src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/GotoTopDemo.axaml.cs
new file mode 100644
index 00000000..bbeb8ebd
--- /dev/null
+++ b/src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/GotoTopDemo.axaml.cs
@@ -0,0 +1,9 @@
+namespace HandyControlDemo.UserControl;
+
+public partial class GotoTopDemo : Avalonia.Controls.UserControl
+{
+ public GotoTopDemo()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/Avalonia/HandyControl_Avalonia/Controls/Other/GotoTop.cs b/src/Avalonia/HandyControl_Avalonia/Controls/Other/GotoTop.cs
new file mode 100644
index 00000000..41d82af8
--- /dev/null
+++ b/src/Avalonia/HandyControl_Avalonia/Controls/Other/GotoTop.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace HandyControl.Controls;
+
+public class GotoTop : Button
+{
+ public static readonly StyledProperty TargetProperty =
+ AvaloniaProperty.Register(nameof(Target));
+
+ public static readonly StyledProperty AnimatedProperty =
+ AvaloniaProperty.Register(nameof(Animated), true);
+
+ public static readonly StyledProperty AnimationTimeProperty =
+ AvaloniaProperty.Register(nameof(AnimationTime), 200d);
+
+ public static readonly StyledProperty HidingHeightProperty =
+ AvaloniaProperty.Register(nameof(HidingHeight));
+
+ public static readonly StyledProperty AutoHidingProperty =
+ AvaloniaProperty.Register(nameof(AutoHiding), true);
+
+ private ScrollViewer? _scrollViewer;
+ private DispatcherTimer? _animationTimer;
+ private DateTime _animationStartTime;
+ private double _animationStartOffset;
+
+ static GotoTop()
+ {
+ TargetProperty.Changed.AddClassHandler((gotoTop, e) => gotoTop.CreateGotoAction(e.GetNewValue()));
+ AutoHidingProperty.Changed.AddClassHandler((gotoTop, _) => gotoTop.UpdateVisibility());
+ HidingHeightProperty.Changed.AddClassHandler((gotoTop, _) => gotoTop.UpdateVisibility());
+ }
+
+ public GotoTop()
+ {
+ Loaded += (_, _) => CreateGotoAction(Target);
+ }
+
+ public Control? Target
+ {
+ get => GetValue(TargetProperty);
+ set => SetValue(TargetProperty, value);
+ }
+
+ public bool Animated
+ {
+ get => GetValue(AnimatedProperty);
+ set => SetValue(AnimatedProperty, value);
+ }
+
+ public double AnimationTime
+ {
+ get => GetValue(AnimationTimeProperty);
+ set => SetValue(AnimationTimeProperty, value);
+ }
+
+ public double HidingHeight
+ {
+ get => GetValue(HidingHeightProperty);
+ set => SetValue(HidingHeightProperty, value);
+ }
+
+ public bool AutoHiding
+ {
+ get => GetValue(AutoHidingProperty);
+ set => SetValue(AutoHidingProperty, value);
+ }
+
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnDetachedFromVisualTree(e);
+
+ if (_scrollViewer != null)
+ {
+ _scrollViewer.ScrollChanged -= ScrollViewerOnScrollChanged;
+ }
+
+ if (_animationTimer != null)
+ {
+ _animationTimer.Stop();
+ }
+ }
+
+ public virtual void CreateGotoAction(Control? obj)
+ {
+ if (_scrollViewer != null)
+ {
+ _scrollViewer.ScrollChanged -= ScrollViewerOnScrollChanged;
+ }
+
+ _scrollViewer = null;
+ var target = obj ?? this;
+
+ if (target is ScrollViewer directScrollViewer)
+ {
+ _scrollViewer = directScrollViewer;
+ }
+ else
+ {
+ _scrollViewer = target.GetVisualDescendants().OfType().FirstOrDefault();
+ }
+
+ if (_scrollViewer != null)
+ {
+ _scrollViewer.ScrollChanged += ScrollViewerOnScrollChanged;
+ }
+
+ UpdateVisibility();
+ }
+
+ private void ScrollViewerOnScrollChanged(object? sender, ScrollChangedEventArgs e)
+ {
+ UpdateVisibility();
+ }
+
+ private void UpdateVisibility()
+ {
+ if (!AutoHiding)
+ {
+ IsVisible = true;
+ return;
+ }
+
+ IsVisible = (_scrollViewer?.Offset.Y ?? 0d) >= HidingHeight;
+ }
+
+ protected override void OnClick()
+ {
+ base.OnClick();
+
+ if (_scrollViewer == null)
+ {
+ return;
+ }
+
+ if (!Animated || AnimationTime <= 0)
+ {
+ _scrollViewer.Offset = new Vector(_scrollViewer.Offset.X, 0);
+ return;
+ }
+
+ StartAnimatedScroll();
+ }
+
+ private void StartAnimatedScroll()
+ {
+ if (_scrollViewer == null)
+ {
+ return;
+ }
+
+ _animationStartOffset = _scrollViewer.Offset.Y;
+ if (_animationStartOffset <= 0)
+ {
+ return;
+ }
+
+ _animationStartTime = DateTime.Now;
+
+ _animationTimer ??= new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(16) };
+ _animationTimer.Tick -= AnimationTimerOnTick;
+ _animationTimer.Tick += AnimationTimerOnTick;
+ _animationTimer.Start();
+ }
+
+ private void AnimationTimerOnTick(object? sender, EventArgs e)
+ {
+ if (_scrollViewer == null)
+ {
+ _animationTimer?.Stop();
+ return;
+ }
+
+ var total = Math.Max(AnimationTime, 1d);
+ var elapsed = (DateTime.Now - _animationStartTime).TotalMilliseconds;
+ var progress = Math.Clamp(elapsed / total, 0d, 1d);
+
+ var current = _animationStartOffset * (1d - progress);
+ _scrollViewer.Offset = new Vector(_scrollViewer.Offset.X, current);
+
+ if (progress >= 1d)
+ {
+ _animationTimer?.Stop();
+ _scrollViewer.Offset = new Vector(_scrollViewer.Offset.X, 0d);
+ }
+ }
+}
diff --git a/src/Avalonia/HandyControl_Avalonia/Themes/Styles/GotoTop.axaml b/src/Avalonia/HandyControl_Avalonia/Themes/Styles/GotoTop.axaml
new file mode 100644
index 00000000..b040f557
--- /dev/null
+++ b/src/Avalonia/HandyControl_Avalonia/Themes/Styles/GotoTop.axaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia/HandyControl_Avalonia/Themes/Styles/MergedStyles.axaml b/src/Avalonia/HandyControl_Avalonia/Themes/Styles/MergedStyles.axaml
index 1dab63d2..478293bd 100644
--- a/src/Avalonia/HandyControl_Avalonia/Themes/Styles/MergedStyles.axaml
+++ b/src/Avalonia/HandyControl_Avalonia/Themes/Styles/MergedStyles.axaml
@@ -75,6 +75,7 @@
+