diff --git a/demo/Ursa.Demo/Models/MenuKeys.cs b/demo/Ursa.Demo/Models/MenuKeys.cs deleted file mode 100644 index b7b1a76..0000000 --- a/demo/Ursa.Demo/Models/MenuKeys.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Ursa.Demo; - -public static class MenuKeys -{ - public const string MenuKeyIntroduction = "Introduction"; - public const string MenuKeyBadge = "Badge"; - public const string MenuKeyBanner = "Banner"; - public const string MenuKeyButtonGroup = "ButtonGroup"; - public const string MenuKeyBreadcrumb = "Breadcrumb"; - public const string MenuKeyClassInput = "Class Input"; - public const string MenuKeyDialog = "Dialog"; - public const string MenuKeyDivider = "Divider"; - public const string MenuKeyDisableContainer = "DisableContainer"; - public const string MenuKeyDrawer = "Drawer"; - public const string MenuKeyDualBadge = "DualBadge"; - public const string MenuKeyEnumSelector = "EnumSelector"; - public const string MenuKeyForm = "Form"; - public const string MenuKeyImageViewer = "ImageViewer"; - public const string MenuKeyIpBox = "IPv4Box"; - public const string MenuKeyIconButton = "IconButton"; - public const string MenuKeyKeyGestureInput = "KeyGestureInput"; - public const string MenuKeyLoading = "Loading"; - public const string MenuKeyMessageBox = "MessageBox"; - public const string MenuKeyNavMenu = "NavMenu"; - public const string MenuKeyNumberDisplayer = "NumberDisplayer"; - public const string MenuKeyNumericUpDown = "NumericUpDown"; - public const string MenuKeyPagination = "Pagination"; - public const string MenuKeyRangeSlider = "RangeSlider"; - public const string MenuKeySelectionList = "SelectionList"; - public const string MenuKeyTagInput = "TagInput"; - public const string MenuKeySkeleton = "Skeleton"; - public const string MenuKeyTimeline = "Timeline"; - public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon"; - public const string MenuKeyThemeToggler = "ThemeToggler"; - public const string MenuKeyToolBar = "ToolBar"; - -} \ No newline at end of file diff --git a/demo/Ursa.Demo/Pages/BadgeDemo.axaml b/demo/Ursa.Demo/Pages/BadgeDemo.axaml index 012ea37..f46673b 100644 --- a/demo/Ursa.Demo/Pages/BadgeDemo.axaml +++ b/demo/Ursa.Demo/Pages/BadgeDemo.axaml @@ -8,7 +8,7 @@ d:DesignHeight="850" d:DesignWidth="850" mc:Ignorable="d"> - + + + + + + + Scroll To Top + Scroll To Bottom + Scroll To Left + Scroll To Right + Scroll To Top + + + + + + + + + + + + + + + diff --git a/demo/Ursa.Demo/Pages/ScrollToButtonDemo.axaml.cs b/demo/Ursa.Demo/Pages/ScrollToButtonDemo.axaml.cs new file mode 100644 index 0000000..5e7741c --- /dev/null +++ b/demo/Ursa.Demo/Pages/ScrollToButtonDemo.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Ursa.Demo.Pages; + +public partial class ScrollToButtonDemo : UserControl +{ + public ScrollToButtonDemo() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/Ursa.Demo.csproj b/demo/Ursa.Demo/Ursa.Demo.csproj index cdf1e1b..92d7e39 100644 --- a/demo/Ursa.Demo/Ursa.Demo.csproj +++ b/demo/Ursa.Demo/Ursa.Demo.csproj @@ -32,4 +32,8 @@ SkeletonDemo.axaml + + + + diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs index d987128..5a0cd77 100644 --- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs @@ -48,6 +48,7 @@ public class MainViewViewModel : ViewModelBase MenuKeys.MenuKeyNumericUpDown => new NumericUpDownDemoViewModel(), MenuKeys.MenuKeyPagination => new PaginationDemoViewModel(), MenuKeys.MenuKeyRangeSlider => new RangeSliderDemoViewModel(), + MenuKeys.MenuKeyScrollToButton => new ScrollToButtonDemoViewModel(), MenuKeys.MenuKeySelectionList => new SelectionListDemoViewModel(), MenuKeys.MenuKeySkeleton => new SkeletonDemoViewModel(), MenuKeys.MenuKeyTagInput => new TagInputDemoViewModel(), diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs index a4eaa14..a48238e 100644 --- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs @@ -35,6 +35,7 @@ public class MenuViewModel: ViewModelBase new() { MenuHeader = "Numeric UpDown", Key = MenuKeys.MenuKeyNumericUpDown }, new() { MenuHeader = "Pagination", Key = MenuKeys.MenuKeyPagination }, new() { MenuHeader = "RangeSlider", Key = MenuKeys.MenuKeyRangeSlider }, + new() { MenuHeader = "Scroll To", Key = MenuKeys.MenuKeyScrollToButton, Status = "New" }, new() { MenuHeader = "Selection List", Key = MenuKeys.MenuKeySelectionList, Status = "New" }, new() { MenuHeader = "Skeleton", Key = MenuKeys.MenuKeySkeleton, Status = "New" }, new() { MenuHeader = "TagInput", Key = MenuKeys.MenuKeyTagInput }, @@ -44,4 +45,41 @@ public class MenuViewModel: ViewModelBase new() { MenuHeader = "ToolBar", Key = MenuKeys.MenuKeyToolBar, Status = "New" } }; } +} + +public static class MenuKeys +{ + public const string MenuKeyIntroduction = "Introduction"; + public const string MenuKeyBadge = "Badge"; + public const string MenuKeyBanner = "Banner"; + public const string MenuKeyButtonGroup = "ButtonGroup"; + public const string MenuKeyBreadcrumb= "Breadcrumb"; + public const string MenuKeyClassInput = "Class Input"; + public const string MenuKeyDialog = "Dialog"; + public const string MenuKeyDivider = "Divider"; + public const string MenuKeyDisableContainer = "DisableContainer"; + public const string MenuKeyDrawer = "Drawer"; + public const string MenuKeyDualBadge = "DualBadge"; + public const string MenuKeyEnumSelector = "EnumSelector"; + public const string MenuKeyForm = "Form"; + public const string MenuKeyImageViewer = "ImageViewer"; + public const string MenuKeyIpBox = "IPv4Box"; + public const string MenuKeyIconButton = "IconButton"; + public const string MenuKeyKeyGestureInput = "KeyGestureInput"; + public const string MenuKeyLoading = "Loading"; + public const string MenuKeyMessageBox = "MessageBox"; + public const string MenuKeyNavMenu = "NavMenu"; + public const string MenuKeyNumberDisplayer = "NumberDisplayer"; + public const string MenuKeyNumericUpDown = "NumericUpDown"; + public const string MenuKeyPagination = "Pagination"; + public const string MenuKeyRangeSlider = "RangeSlider"; + public const string MenuKeyScrollToButton = "ScrollToButton"; + public const string MenuKeySelectionList = "SelectionList"; + public const string MenuKeyTagInput = "TagInput"; + public const string MenuKeySkeleton = "Skeleton"; + public const string MenuKeyTimeline = "Timeline"; + public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon"; + public const string MenuKeyThemeToggler = "ThemeToggler"; + public const string MenuKeyToolBar = "ToolBar"; + } \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/ScrollToButtonDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/ScrollToButtonDemoViewModel.cs new file mode 100644 index 0000000..833189f --- /dev/null +++ b/demo/Ursa.Demo/ViewModels/ScrollToButtonDemoViewModel.cs @@ -0,0 +1,15 @@ +using System.Collections.ObjectModel; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Ursa.Demo.ViewModels; + +public class ScrollToButtonDemoViewModel: ObservableObject +{ + public ObservableCollection Items { get; set; } + + public ScrollToButtonDemoViewModel() + { + Items = new ObservableCollection(Enumerable.Range(0, 1000).Select(a => "Item " + a)); + } +} \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/ScrollToButton.axaml b/src/Ursa.Themes.Semi/Controls/ScrollToButton.axaml new file mode 100644 index 0000000..dc205d0 --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/ScrollToButton.axaml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index 6be2414..ddb53fd 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -25,6 +25,7 @@ + diff --git a/src/Ursa.Themes.Semi/Themes/Shared/ScrollToButton.axaml b/src/Ursa.Themes.Semi/Themes/Shared/ScrollToButton.axaml new file mode 100644 index 0000000..4e45321 --- /dev/null +++ b/src/Ursa.Themes.Semi/Themes/Shared/ScrollToButton.axaml @@ -0,0 +1,5 @@ + + + M19.637 16.4369C19.0513 17.0227 18.1015 17.0227 17.5157 16.4369L11.8589 10.7801L6.20202 16.4369C5.61623 17.0227 4.66648 17.0227 4.0807 16.4369C3.49491 15.8511 3.49491 14.9014 4.0807 14.3156L10.7982 7.59809C11.384 7.01231 12.3337 7.01231 12.9195 7.59809L19.637 14.3156C20.2228 14.9014 20.2228 15.8511 19.637 16.4369Z + diff --git a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml index 5e43477..b11d979 100644 --- a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml +++ b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml @@ -13,6 +13,7 @@ + diff --git a/src/Ursa/Controls/BackTop/BackTop.cs b/src/Ursa/Controls/BackTop/BackTop.cs deleted file mode 100644 index 3506807..0000000 --- a/src/Ursa/Controls/BackTop/BackTop.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Media; - -namespace Ursa.Controls.BackTop; - -public class BackTop: Control -{ - public static readonly AttachedProperty AttachProperty = - AvaloniaProperty.RegisterAttached("Attach"); - - public static void SetAttach(Control obj, bool value) => obj.SetValue(AttachProperty, value); - public static bool GetAttach(Control obj) => obj.GetValue(AttachProperty); -} \ No newline at end of file diff --git a/src/Ursa/Controls/ScrollTo/ScrollTo.cs b/src/Ursa/Controls/ScrollTo/ScrollTo.cs new file mode 100644 index 0000000..fc1dcfb --- /dev/null +++ b/src/Ursa/Controls/ScrollTo/ScrollTo.cs @@ -0,0 +1,60 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Styling; +using Ursa.Common; + +namespace Ursa.Controls; + +public class ScrollTo +{ + public static readonly AttachedProperty DirectionProperty = + AvaloniaProperty.RegisterAttached("Direction"); + + public static void SetDirection(Control obj, Position value) => obj.SetValue(DirectionProperty, value); + public static Position? GetDirection(Control obj) => obj.GetValue(DirectionProperty); + + public static readonly AttachedProperty ButtonThemeProperty = + AvaloniaProperty.RegisterAttached("ButtonTheme"); + + public static void SetButtonTheme(Control obj, ControlTheme? value) => obj.SetValue(ButtonThemeProperty, value); + public static ControlTheme? GetButtonTheme(Control obj) => obj.GetValue(ButtonThemeProperty); + + static ScrollTo() + { + DirectionProperty.Changed.AddClassHandler(OnDirectionChanged); + ButtonThemeProperty.Changed.AddClassHandler(OnButtonThemeChanged); + } + + private static void OnButtonThemeChanged(Control arg1, AvaloniaPropertyChangedEventArgs arg2) + { + var button = EnsureButtonInAdorner(arg1); + if (button is null) return; + button.SetCurrentValue(StyledElement.ThemeProperty, arg2.NewValue.Value); + } + + private static void OnDirectionChanged(Control control, AvaloniaPropertyChangedEventArgs args) + { + if (args.NewValue.Value is null) return; + var button = EnsureButtonInAdorner(control); + if (button is null) return; + button.SetCurrentValue(ScrollToButton.DirectionProperty, args.NewValue.Value); + } + + private static ScrollToButton? EnsureButtonInAdorner(Control control) + { + var adorner = AdornerLayer.GetAdorner(control); + if (adorner is not ScrollToButton button) + { + button = new ScrollToButton(); + AdornerLayer.SetAdorner(control, button); + } + button.SetCurrentValue(ScrollToButton.TargetProperty, control); + button.SetCurrentValue(ScrollToButton.DirectionProperty, GetDirection(control)); + if ( GetButtonTheme(control) is { } theme) + { + button.SetCurrentValue(StyledElement.ThemeProperty, theme); + } + return button; + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/ScrollTo/ScrollToButton.cs b/src/Ursa/Controls/ScrollTo/ScrollToButton.cs new file mode 100644 index 0000000..51a1df3 --- /dev/null +++ b/src/Ursa/Controls/ScrollTo/ScrollToButton.cs @@ -0,0 +1,125 @@ +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; + } + } +} \ No newline at end of file