From 0b9bc3303d098da4014038e7fe7ddc32beb8311a Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 17 Dec 2024 16:53:53 +0800 Subject: [PATCH] feat: initialize marquee, add demo. --- demo/Ursa.Demo/Pages/MarqueeDemo.axaml | 20 +++ demo/Ursa.Demo/Pages/MarqueeDemo.axaml.cs | 13 ++ .../Ursa.Demo/ViewModels/MainViewViewModel.cs | 1 + .../ViewModels/MarqueeDemoViewModel.cs | 8 + demo/Ursa.Demo/ViewModels/MenuViewModel.cs | 2 + src/Ursa.Themes.Semi/Controls/Marquee.axaml | 19 +++ src/Ursa.Themes.Semi/Controls/_index.axaml | 1 + src/Ursa/Controls/Marquee/Direction.cs | 11 ++ src/Ursa/Controls/Marquee/Marquee.cs | 150 ++++++++++++++++++ 9 files changed, 225 insertions(+) create mode 100644 demo/Ursa.Demo/Pages/MarqueeDemo.axaml create mode 100644 demo/Ursa.Demo/Pages/MarqueeDemo.axaml.cs create mode 100644 demo/Ursa.Demo/ViewModels/MarqueeDemoViewModel.cs create mode 100644 src/Ursa.Themes.Semi/Controls/Marquee.axaml create mode 100644 src/Ursa/Controls/Marquee/Direction.cs create mode 100644 src/Ursa/Controls/Marquee/Marquee.cs diff --git a/demo/Ursa.Demo/Pages/MarqueeDemo.axaml b/demo/Ursa.Demo/Pages/MarqueeDemo.axaml new file mode 100644 index 0000000..63580c2 --- /dev/null +++ b/demo/Ursa.Demo/Pages/MarqueeDemo.axaml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/demo/Ursa.Demo/Pages/MarqueeDemo.axaml.cs b/demo/Ursa.Demo/Pages/MarqueeDemo.axaml.cs new file mode 100644 index 0000000..0f58a44 --- /dev/null +++ b/demo/Ursa.Demo/Pages/MarqueeDemo.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Ursa.Demo.Pages; + +public partial class MarqueeDemo : UserControl +{ + public MarqueeDemo() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs index 3fc9e46..b093413 100644 --- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs @@ -54,6 +54,7 @@ public partial class MainViewViewModel : ViewModelBase MenuKeys.MenuKeyIpBox => new IPv4BoxDemoViewModel(), MenuKeys.MenuKeyKeyGestureInput => new KeyGestureInputDemoViewModel(), MenuKeys.MenuKeyLoading => new LoadingDemoViewModel(), + MenuKeys.MenuKeyMarquee => new MarqueeDemoViewModel(), MenuKeys.MenuKeyMessageBox => new MessageBoxDemoViewModel(), MenuKeys.MenuKeyMultiComboBox => new MultiComboBoxDemoViewModel(), MenuKeys.MenuKeyNavMenu => new NavMenuDemoViewModel(), diff --git a/demo/Ursa.Demo/ViewModels/MarqueeDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/MarqueeDemoViewModel.cs new file mode 100644 index 0000000..cea4d3b --- /dev/null +++ b/demo/Ursa.Demo/ViewModels/MarqueeDemoViewModel.cs @@ -0,0 +1,8 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Ursa.Demo.ViewModels; + +public class MarqueeDemoViewModel: ViewModelBase +{ + +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs index 7559eb7..9b90094 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 = "IPv4Box", Key = MenuKeys.MenuKeyIpBox }, new() { MenuHeader = "KeyGestureInput", Key = MenuKeys.MenuKeyKeyGestureInput }, new() { MenuHeader = "Loading", Key = MenuKeys.MenuKeyLoading }, + new() { MenuHeader = "Marquee", Key = MenuKeys.MenuKeyMarquee }, new() { MenuHeader = "Message Box", Key = MenuKeys.MenuKeyMessageBox }, new() { MenuHeader = "MultiComboBox", Key = MenuKeys.MenuKeyMultiComboBox, Status = "Updated" }, new() { MenuHeader = "Nav Menu", Key = MenuKeys.MenuKeyNavMenu }, @@ -89,6 +90,7 @@ public static class MenuKeys public const string MenuKeyIpBox = "IPv4Box"; public const string MenuKeyKeyGestureInput = "KeyGestureInput"; public const string MenuKeyLoading = "Loading"; + public const string MenuKeyMarquee = "Marquee"; public const string MenuKeyMessageBox = "MessageBox"; public const string MenuKeyMultiComboBox = "MultiComboBox"; public const string MenuKeyNavMenu = "NavMenu"; diff --git a/src/Ursa.Themes.Semi/Controls/Marquee.axaml b/src/Ursa.Themes.Semi/Controls/Marquee.axaml new file mode 100644 index 0000000..cb8d4bd --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/Marquee.axaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index 4129232..0c8421c 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -27,6 +27,7 @@ + diff --git a/src/Ursa/Controls/Marquee/Direction.cs b/src/Ursa/Controls/Marquee/Direction.cs new file mode 100644 index 0000000..bd00e24 --- /dev/null +++ b/src/Ursa/Controls/Marquee/Direction.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace Ursa.Controls; + +public enum Direction +{ + Left, + Right, + Up, + Down, +} \ No newline at end of file diff --git a/src/Ursa/Controls/Marquee/Marquee.cs b/src/Ursa/Controls/Marquee/Marquee.cs new file mode 100644 index 0000000..8ff8acf --- /dev/null +++ b/src/Ursa/Controls/Marquee/Marquee.cs @@ -0,0 +1,150 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Threading; +using Irihi.Avalonia.Shared.Helpers; +using Timer = System.Timers.Timer; + +namespace Ursa.Controls; + +public class Marquee : ContentControl +{ + /// + /// Defines the property. + /// + public static readonly StyledProperty IsRunningProperty = AvaloniaProperty.Register( + nameof(IsRunning)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty DirectionProperty = AvaloniaProperty.Register( + nameof(Direction)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty SpeedProperty = AvaloniaProperty.Register( + nameof(Speed), 60.0); + + private readonly Timer _timer; + + static Marquee() + { + ClipToBoundsProperty.OverrideDefaultValue(true); + } + + public Marquee() + { + _timer = new Timer(); + _timer.Interval = 1000 / 60.0; + _timer.Elapsed += TimerOnTick; + _timer.Start(); + } + + /// + /// Gets or sets a value indicating whether the marquee is running. + /// + public bool IsRunning + { + get => GetValue(IsRunningProperty); + set => SetValue(IsRunningProperty, value); + } + + /// + /// Gets or sets the direction of the marquee. + /// + public Direction Direction + { + get => GetValue(DirectionProperty); + set => SetValue(DirectionProperty, value); + } + + /// + /// Gets or sets the speed of the marquee. + /// + public double Speed + { + get => GetValue(SpeedProperty); + set => SetValue(SpeedProperty, value); + } + + private void TimerOnTick(object sender, System.EventArgs e) + { + Dispatcher.UIThread.Post(UpdateLocation, DispatcherPriority.Background); + } + + protected override Size MeasureOverride(Size availableSize) + { + var result = base.MeasureOverride(availableSize); + var presenter = Presenter; + if (presenter is null) return result; + var size = presenter.DesiredSize; + if (double.IsInfinity(result.Width) || result.Width == 0) + { + result = result.WithWidth(size.Width); + } + if (double.IsInfinity(result.Height) || result.Height == 0) + { + result = result.WithHeight(size.Height); + } + return result; + } + + private void UpdateLocation() + { + if (Presenter is null) return; + var horizontalOffset = Direction switch + { + Direction.Up or Direction.Down => 0, + Direction.Left or Direction.Right => Canvas.GetLeft(Presenter), + }; + var verticalOffset = Direction switch + { + Direction.Up or Direction.Down => Canvas.GetTop(Presenter), + Direction.Left or Direction.Right => 0, + }; + if (horizontalOffset is double.NaN) horizontalOffset = 0.0; + if (verticalOffset is double.NaN) verticalOffset = 0.0; + var speed = Speed / 60.0; + var diff = Direction switch + { + Direction.Up => -speed, + Direction.Down => speed, + Direction.Left => -speed, + Direction.Right => speed, + _ => 0 + }; + switch (Direction) + { + case Direction.Up: + case Direction.Down: + verticalOffset += diff; + break; + case Direction.Left: + case Direction.Right: + horizontalOffset += diff; + break; + } + switch (Direction) + { + case Direction.Down: + if (verticalOffset > Bounds.Height) verticalOffset = -Presenter.Bounds.Height; + verticalOffset = MathHelpers.SafeClamp(verticalOffset, -Presenter.Bounds.Height, Bounds.Height); + break; + case Direction.Up: + if (verticalOffset < -Presenter.Bounds.Height) verticalOffset = Bounds.Height; + verticalOffset = MathHelpers.SafeClamp(verticalOffset, -Presenter.Bounds.Height, Bounds.Height); + break; + case Direction.Right: + if (horizontalOffset > Bounds.Width) horizontalOffset = -Presenter.Bounds.Width; + horizontalOffset = MathHelpers.SafeClamp(horizontalOffset, -Presenter.Bounds.Width, Bounds.Width); + break; + case Direction.Left: + if (horizontalOffset < -Presenter.Bounds.Width) horizontalOffset = Bounds.Width; + horizontalOffset = MathHelpers.SafeClamp(horizontalOffset, -Presenter.Bounds.Width, Bounds.Width); + break; + } + Canvas.SetTop(Presenter, verticalOffset); + Canvas.SetLeft(Presenter, horizontalOffset); + } +} \ No newline at end of file