feat: Add GotoTop control, style and demo.
This commit is contained in:
1 parent
550b980f54
commit
2c91527181
6 files changed
+283
No files matched your search
@@ -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" ],
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
x:Class="HandyControlDemo.UserControl.GotoTopDemo"
|
||||
Background="{DynamicResource RegionBrush}">
|
||||
<TransitioningContentControl>
|
||||
<Panel Height="400">
|
||||
<ScrollViewer x:Name="ScrollViewerDemo"
|
||||
Height="390"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,10,0,0">
|
||||
<Border Height="2000"
|
||||
Margin="8,0">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="50%,0%"
|
||||
EndPoint="50%,100%">
|
||||
<GradientStop Offset="0"
|
||||
Color="White" />
|
||||
<GradientStop Offset="1"
|
||||
Color="Black" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<hc:GotoTop Animated="True"
|
||||
AutoHiding="True"
|
||||
AnimationTime="500"
|
||||
Target="{Binding #ScrollViewerDemo}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="0,0,20,20" />
|
||||
</Panel>
|
||||
</TransitioningContentControl>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace HandyControlDemo.UserControl;
|
||||
|
||||
public partial class GotoTopDemo : Avalonia.Controls.UserControl
|
||||
{
|
||||
public GotoTopDemo()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -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<Control?> TargetProperty =
|
||||
AvaloniaProperty.Register<GotoTop, Control?>(nameof(Target));
|
||||
|
||||
public static readonly StyledProperty<bool> AnimatedProperty =
|
||||
AvaloniaProperty.Register<GotoTop, bool>(nameof(Animated), true);
|
||||
|
||||
public static readonly StyledProperty<double> AnimationTimeProperty =
|
||||
AvaloniaProperty.Register<GotoTop, double>(nameof(AnimationTime), 200d);
|
||||
|
||||
public static readonly StyledProperty<double> HidingHeightProperty =
|
||||
AvaloniaProperty.Register<GotoTop, double>(nameof(HidingHeight));
|
||||
|
||||
public static readonly StyledProperty<bool> AutoHidingProperty =
|
||||
AvaloniaProperty.Register<GotoTop, bool>(nameof(AutoHiding), true);
|
||||
|
||||
private ScrollViewer? _scrollViewer;
|
||||
private DispatcherTimer? _animationTimer;
|
||||
private DateTime _animationStartTime;
|
||||
private double _animationStartOffset;
|
||||
|
||||
static GotoTop()
|
||||
{
|
||||
TargetProperty.Changed.AddClassHandler<GotoTop>((gotoTop, e) => gotoTop.CreateGotoAction(e.GetNewValue<Control?>()));
|
||||
AutoHidingProperty.Changed.AddClassHandler<GotoTop>((gotoTop, _) => gotoTop.UpdateVisibility());
|
||||
HidingHeightProperty.Changed.AddClassHandler<GotoTop>((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<ScrollViewer>().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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:ClassModifier="internal"
|
||||
xmlns:hc="clr-namespace:HandyControl.Controls">
|
||||
|
||||
<ControlTheme x:Key="GotoTopBaseStyle"
|
||||
BasedOn="{StaticResource ButtonBaseStyle}"
|
||||
TargetType="hc:GotoTop">
|
||||
<Setter Property="Width"
|
||||
Value="50" />
|
||||
<Setter Property="Height"
|
||||
Value="50" />
|
||||
<Setter Property="hc:BorderElement.CornerRadius"
|
||||
Value="25" />
|
||||
<Setter Property="CornerRadius"
|
||||
Value="{Binding $self.(hc:BorderElement.CornerRadius)}" />
|
||||
<Setter Property="hc:IconElement.Geometry"
|
||||
Value="{StaticResource UpGeometry}" />
|
||||
<Setter Property="hc:IconElement.Width"
|
||||
Value="14" />
|
||||
<Setter Property="hc:IconElement.Height"
|
||||
Value="14" />
|
||||
<Setter Property="Foreground"
|
||||
Value="{DynamicResource PrimaryTextBrush}" />
|
||||
<Setter Property="Padding"
|
||||
Value="16" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="1" />
|
||||
|
||||
<Style Selector="^:pointerover">
|
||||
<Setter Property="Background"
|
||||
Value="{DynamicResource SecondaryRegionBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^:pressed">
|
||||
<Setter Property="Background"
|
||||
Value="{DynamicResource BorderBrush}" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="{x:Type hc:GotoTop}"
|
||||
BasedOn="{StaticResource GotoTopBaseStyle}"
|
||||
TargetType="hc:GotoTop" />
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -75,6 +75,7 @@
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/Gravatar.axaml" />
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/Tag.axaml" />
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/Badge.axaml" />
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/GotoTop.axaml" />
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/Shield.axaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
Reference in new issue
Block a user