From 0b9bc3303d098da4014038e7fe7ddc32beb8311a Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 17 Dec 2024 16:53:53 +0800 Subject: [PATCH 1/7] 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 From 49d410bd4bfa5afba26d4a31937aa43be9265d4b Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 17 Dec 2024 18:01:12 +0800 Subject: [PATCH 2/7] feat: remove calculation from dispatcher call. --- demo/Ursa.Demo/Pages/MarqueeDemo.axaml | 34 +++++- src/Ursa/Controls/Marquee/Marquee.cs | 151 +++++++++++++++++++++---- 2 files changed, 155 insertions(+), 30 deletions(-) diff --git a/demo/Ursa.Demo/Pages/MarqueeDemo.axaml b/demo/Ursa.Demo/Pages/MarqueeDemo.axaml index 63580c2..a12956a 100644 --- a/demo/Ursa.Demo/Pages/MarqueeDemo.axaml +++ b/demo/Ursa.Demo/Pages/MarqueeDemo.axaml @@ -9,12 +9,36 @@ d:DesignWidth="800" mc:Ignorable="d"> - + + + + + + + - + Height="100" + HorizontalContentAlignment="{Binding #horizontal.Value}" + VerticalContentAlignment="{Binding #vertical.Value}" + Background="{DynamicResource SemiBlue1}" + Direction="{Binding #direction.Value}" + IsRunning="{Binding #running.IsChecked}"> + diff --git a/src/Ursa/Controls/Marquee/Marquee.cs b/src/Ursa/Controls/Marquee/Marquee.cs index 8ff8acf..60bc21a 100644 --- a/src/Ursa/Controls/Marquee/Marquee.cs +++ b/src/Ursa/Controls/Marquee/Marquee.cs @@ -1,5 +1,7 @@ +using System.Runtime.CompilerServices; using Avalonia; using Avalonia.Controls; +using Avalonia.Layout; using Avalonia.Threading; using Irihi.Avalonia.Shared.Helpers; using Timer = System.Timers.Timer; @@ -12,7 +14,7 @@ public class Marquee : ContentControl /// Defines the property. /// public static readonly StyledProperty IsRunningProperty = AvaloniaProperty.Register( - nameof(IsRunning)); + nameof(IsRunning), true); /// /// Defines the property. @@ -26,20 +28,54 @@ public class Marquee : ContentControl public static readonly StyledProperty SpeedProperty = AvaloniaProperty.Register( nameof(Speed), 60.0); - private readonly Timer _timer; + private Timer _timer; static Marquee() { ClipToBoundsProperty.OverrideDefaultValue(true); + HorizontalContentAlignmentProperty.OverrideDefaultValue(HorizontalAlignment.Center); + VerticalContentAlignmentProperty.OverrideDefaultValue(VerticalAlignment.Center); + HorizontalContentAlignmentProperty.Changed.AddClassHandler((o,args)=>o.InvalidatePresenterPosition()); + VerticalContentAlignmentProperty.Changed.AddClassHandler((o,args)=>o.InvalidatePresenterPosition()); + IsRunningProperty.Changed.AddClassHandler((o, args) => o.OnIsRunningChanged(args)); + } + + private void OnIsRunningChanged(AvaloniaPropertyChangedEventArgs args) + { + if (args.NewValue.Value) + { + _timer.Start(); + } + else + { + _timer.Stop(); + } } public Marquee() { _timer = new Timer(); _timer.Interval = 1000 / 60.0; + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + _timer.Stop(); + _timer.Dispose(); + _timer = new Timer(); + _timer.Interval = 1000 / 60.0; _timer.Elapsed += TimerOnTick; _timer.Start(); } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + _timer.Elapsed -= TimerOnTick; + _timer.Stop(); + _timer.Dispose(); + } /// /// Gets or sets a value indicating whether the marquee is running. @@ -70,7 +106,25 @@ public class Marquee : ContentControl private void TimerOnTick(object sender, System.EventArgs e) { - Dispatcher.UIThread.Post(UpdateLocation, DispatcherPriority.Background); + var layoutValues = Dispatcher.UIThread.Invoke(GetLayoutValues); + if (Presenter is null) return; + var location = UpdateLocation(layoutValues); + if (location is null) return; + Dispatcher.UIThread.Post(() => + { + Canvas.SetTop(Presenter, location.Value.top); + Canvas.SetLeft(Presenter, location.Value.left); + }, DispatcherPriority.Background); + } + + private void InvalidatePresenterPosition() + { + if (Presenter is null) return; + var layoutValues = GetLayoutValues(); + var location = UpdateLocation(layoutValues); + if (location is null) return; + Canvas.SetTop(Presenter, location.Value.top); + Canvas.SetLeft(Presenter, location.Value.left); } protected override Size MeasureOverride(Size availableSize) @@ -90,23 +144,22 @@ public class Marquee : ContentControl return result; } - private void UpdateLocation() + private (double top, double left)? UpdateLocation(LayoutValues values) { - if (Presenter is null) return; - var horizontalOffset = Direction switch + var horizontalOffset = values.Direction switch { - Direction.Up or Direction.Down => 0, - Direction.Left or Direction.Right => Canvas.GetLeft(Presenter), + Direction.Up or Direction.Down => GetHorizontalOffset(values.Bounds, values.PresenterSize, values.HorizontalAlignment), + Direction.Left or Direction.Right => values.Left, }; - var verticalOffset = Direction switch + var verticalOffset = values.Direction switch { - Direction.Up or Direction.Down => Canvas.GetTop(Presenter), - Direction.Left or Direction.Right => 0, + Direction.Up or Direction.Down => values.Top, + Direction.Left or Direction.Right => GetVerticalOffset(values.Bounds, values.PresenterSize, values.VerticalAlignment), }; 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 + var speed = values.Diff; + var diff = values.Direction switch { Direction.Up => -speed, Direction.Down => speed, @@ -114,7 +167,7 @@ public class Marquee : ContentControl Direction.Right => speed, _ => 0 }; - switch (Direction) + switch (values.Direction) { case Direction.Up: case Direction.Down: @@ -125,26 +178,74 @@ public class Marquee : ContentControl horizontalOffset += diff; break; } - switch (Direction) + switch (values.Direction) { case Direction.Down: - if (verticalOffset > Bounds.Height) verticalOffset = -Presenter.Bounds.Height; - verticalOffset = MathHelpers.SafeClamp(verticalOffset, -Presenter.Bounds.Height, Bounds.Height); + if (verticalOffset > values.Bounds.Height) verticalOffset = -values.PresenterSize.Height; break; case Direction.Up: - if (verticalOffset < -Presenter.Bounds.Height) verticalOffset = Bounds.Height; - verticalOffset = MathHelpers.SafeClamp(verticalOffset, -Presenter.Bounds.Height, Bounds.Height); + if (verticalOffset < -values.PresenterSize.Height) verticalOffset = values.Bounds.Height; break; case Direction.Right: - if (horizontalOffset > Bounds.Width) horizontalOffset = -Presenter.Bounds.Width; - horizontalOffset = MathHelpers.SafeClamp(horizontalOffset, -Presenter.Bounds.Width, Bounds.Width); + if (horizontalOffset > values.Bounds.Width) horizontalOffset = -values.PresenterSize.Width; break; case Direction.Left: - if (horizontalOffset < -Presenter.Bounds.Width) horizontalOffset = Bounds.Width; - horizontalOffset = MathHelpers.SafeClamp(horizontalOffset, -Presenter.Bounds.Width, Bounds.Width); + if (horizontalOffset < -values.PresenterSize.Width) horizontalOffset = values.Bounds.Width; break; } - Canvas.SetTop(Presenter, verticalOffset); - Canvas.SetLeft(Presenter, horizontalOffset); + verticalOffset = MathHelpers.SafeClamp(verticalOffset, -values.PresenterSize.Height, values.Bounds.Height); + horizontalOffset = MathHelpers.SafeClamp(horizontalOffset, -values.PresenterSize.Width, values.Bounds.Width); + return (verticalOffset, horizontalOffset); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private double GetHorizontalOffset(Size bounds, Size presenterBounds, HorizontalAlignment horizontalAlignment) + { + return horizontalAlignment switch + { + HorizontalAlignment.Left => 0, + HorizontalAlignment.Center => (bounds.Width - presenterBounds.Width) / 2, + HorizontalAlignment.Right => bounds.Width - presenterBounds.Width, + _ => 0 + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private double GetVerticalOffset(Size bounds, Size presenterBounds, VerticalAlignment verticalAlignment) + { + return verticalAlignment switch + { + VerticalAlignment.Top => 0, + VerticalAlignment.Center => (bounds.Height - presenterBounds.Height) / 2, + VerticalAlignment.Bottom => bounds.Height - presenterBounds.Height, + _ => 0 + }; + } + + private LayoutValues GetLayoutValues() + { + return new LayoutValues + { + Bounds = Bounds.Size, + PresenterSize = Presenter?.Bounds.Size ?? new Size(), + Left = Presenter is null? 0 : Canvas.GetLeft(Presenter), + Top = Presenter is null? 0 : Canvas.GetTop(Presenter), + Diff = IsRunning ? Speed / 60.0 : 0, + HorizontalAlignment = HorizontalContentAlignment, + VerticalAlignment = VerticalContentAlignment, + Direction = Direction + }; + } +} + +struct LayoutValues +{ + public Size Bounds { get; set; } + public Size PresenterSize { get; set; } + public double Left { get; set; } + public double Top { get; set; } + public double Diff { get; set; } + public Direction Direction { get; set; } + public HorizontalAlignment HorizontalAlignment { get; set; } + public VerticalAlignment VerticalAlignment { get; set; } } \ No newline at end of file From cde9d6725198f3ff6554a11d28439602de14a022 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 17 Dec 2024 18:13:05 +0800 Subject: [PATCH 3/7] feat: add a border to template. --- demo/Ursa.Demo/Pages/MarqueeDemo.axaml | 8 ++++++++ src/Ursa.Themes.Semi/Controls/Marquee.axaml | 19 +++++++++++++------ src/Ursa/Controls/Marquee/Marquee.cs | 2 +- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/demo/Ursa.Demo/Pages/MarqueeDemo.axaml b/demo/Ursa.Demo/Pages/MarqueeDemo.axaml index a12956a..77040f1 100644 --- a/demo/Ursa.Demo/Pages/MarqueeDemo.axaml +++ b/demo/Ursa.Demo/Pages/MarqueeDemo.axaml @@ -25,6 +25,13 @@ u:FormItem.Label="VerticalContentAlignment" EnumType="{x:Type VerticalAlignment}" Value="{x:Static VerticalAlignment.Center}" /> + diff --git a/src/Ursa.Themes.Semi/Controls/Marquee.axaml b/src/Ursa.Themes.Semi/Controls/Marquee.axaml index cb8d4bd..1cf94a2 100644 --- a/src/Ursa.Themes.Semi/Controls/Marquee.axaml +++ b/src/Ursa.Themes.Semi/Controls/Marquee.axaml @@ -7,12 +7,19 @@ - - - + + + + + diff --git a/src/Ursa/Controls/Marquee/Marquee.cs b/src/Ursa/Controls/Marquee/Marquee.cs index 60bc21a..a09d098 100644 --- a/src/Ursa/Controls/Marquee/Marquee.cs +++ b/src/Ursa/Controls/Marquee/Marquee.cs @@ -114,7 +114,7 @@ public class Marquee : ContentControl { Canvas.SetTop(Presenter, location.Value.top); Canvas.SetLeft(Presenter, location.Value.left); - }, DispatcherPriority.Background); + }, DispatcherPriority.Render); } private void InvalidatePresenterPosition() From 31685cda0fd67833a86883df79311d571edf7ffc Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 17 Dec 2024 20:07:19 +0800 Subject: [PATCH 4/7] feat: respect presenter size change. --- demo/Ursa.Demo/Pages/MarqueeDemo.axaml | 4 +++- src/Ursa/Controls/Marquee/Marquee.cs | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/demo/Ursa.Demo/Pages/MarqueeDemo.axaml b/demo/Ursa.Demo/Pages/MarqueeDemo.axaml index 77040f1..244f606 100644 --- a/demo/Ursa.Demo/Pages/MarqueeDemo.axaml +++ b/demo/Ursa.Demo/Pages/MarqueeDemo.axaml @@ -10,6 +10,7 @@ mc:Ignorable="d"> + - + diff --git a/src/Ursa/Controls/Marquee/Marquee.cs b/src/Ursa/Controls/Marquee/Marquee.cs index a09d098..fe3ac51 100644 --- a/src/Ursa/Controls/Marquee/Marquee.cs +++ b/src/Ursa/Controls/Marquee/Marquee.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Primitives; using Avalonia.Layout; using Avalonia.Threading; using Irihi.Avalonia.Shared.Helpers; @@ -75,6 +76,10 @@ public class Marquee : ContentControl _timer.Elapsed -= TimerOnTick; _timer.Stop(); _timer.Dispose(); + if (Presenter is not null) + { + Presenter.SizeChanged -= OnPresenterSizeChanged; + } } /// @@ -104,6 +109,22 @@ public class Marquee : ContentControl set => SetValue(SpeedProperty, value); } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + if (Presenter is not null) + { + Presenter.SizeChanged+= OnPresenterSizeChanged; + } + } + + private void OnPresenterSizeChanged(object sender, SizeChangedEventArgs e) + { + InvalidatePresenterPosition(); + } + + + private void TimerOnTick(object sender, System.EventArgs e) { var layoutValues = Dispatcher.UIThread.Invoke(GetLayoutValues); From 98a900804439cdc2025cee83c9a3e7680547c68e Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 17 Dec 2024 20:13:29 +0800 Subject: [PATCH 5/7] feat: adjust order to reduce error. --- src/Ursa/Controls/Marquee/Marquee.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ursa/Controls/Marquee/Marquee.cs b/src/Ursa/Controls/Marquee/Marquee.cs index fe3ac51..4c6da28 100644 --- a/src/Ursa/Controls/Marquee/Marquee.cs +++ b/src/Ursa/Controls/Marquee/Marquee.cs @@ -127,8 +127,8 @@ public class Marquee : ContentControl private void TimerOnTick(object sender, System.EventArgs e) { - var layoutValues = Dispatcher.UIThread.Invoke(GetLayoutValues); if (Presenter is null) return; + var layoutValues = Dispatcher.UIThread.Invoke(GetLayoutValues); var location = UpdateLocation(layoutValues); if (location is null) return; Dispatcher.UIThread.Post(() => From 7a364ff6b38d78b2d6d038ac0bf9bd8dfbff6611 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 17 Dec 2024 20:29:30 +0800 Subject: [PATCH 6/7] fix: fix a comment. --- src/Ursa/Controls/Marquee/Marquee.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ursa/Controls/Marquee/Marquee.cs b/src/Ursa/Controls/Marquee/Marquee.cs index 4c6da28..b971393 100644 --- a/src/Ursa/Controls/Marquee/Marquee.cs +++ b/src/Ursa/Controls/Marquee/Marquee.cs @@ -18,7 +18,7 @@ public class Marquee : ContentControl nameof(IsRunning), true); /// - /// Defines the property. + /// Defines the property. /// public static readonly StyledProperty DirectionProperty = AvaloniaProperty.Register( nameof(Direction)); From 36f0cf78c0be44de3d81a695f64ddf9ba50e117a Mon Sep 17 00:00:00 2001 From: rabbitism Date: Wed, 18 Dec 2024 14:10:07 +0800 Subject: [PATCH 7/7] feat: coerce speed to be larger than 0. only initialize timer after attached to visual tree. --- demo/Ursa.Demo/Pages/MarqueeDemo.axaml | 2 +- src/Ursa/Controls/Marquee/Marquee.cs | 52 ++++++++++++++------------ 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/demo/Ursa.Demo/Pages/MarqueeDemo.axaml b/demo/Ursa.Demo/Pages/MarqueeDemo.axaml index 244f606..eb79d11 100644 --- a/demo/Ursa.Demo/Pages/MarqueeDemo.axaml +++ b/demo/Ursa.Demo/Pages/MarqueeDemo.axaml @@ -30,7 +30,7 @@ Name="speed" AllowDrag="True" u:FormItem.Label="Speed" - Minimum="1" + Minimum="-100" Maximum="300" Value="60"/> property. /// public static readonly StyledProperty SpeedProperty = AvaloniaProperty.Register( - nameof(Speed), 60.0); + nameof(Speed), 60.0, coerce: OnCoerceSpeed); - private Timer _timer; + private static double OnCoerceSpeed(AvaloniaObject arg1, double arg2) + { + if (arg2 < 0) return 0; + return arg2; + } + + private Timer? _timer; static Marquee() { ClipToBoundsProperty.OverrideDefaultValue(true); HorizontalContentAlignmentProperty.OverrideDefaultValue(HorizontalAlignment.Center); VerticalContentAlignmentProperty.OverrideDefaultValue(VerticalAlignment.Center); - HorizontalContentAlignmentProperty.Changed.AddClassHandler((o,args)=>o.InvalidatePresenterPosition()); - VerticalContentAlignmentProperty.Changed.AddClassHandler((o,args)=>o.InvalidatePresenterPosition()); + HorizontalContentAlignmentProperty.Changed.AddClassHandler((o,_)=>o.InvalidatePresenterPosition()); + VerticalContentAlignmentProperty.Changed.AddClassHandler((o,_)=>o.InvalidatePresenterPosition()); IsRunningProperty.Changed.AddClassHandler((o, args) => o.OnIsRunningChanged(args)); } @@ -45,11 +50,11 @@ public class Marquee : ContentControl { if (args.NewValue.Value) { - _timer.Start(); + _timer?.Start(); } else { - _timer.Stop(); + _timer?.Stop(); } } @@ -62,20 +67,30 @@ public class Marquee : ContentControl protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); - _timer.Stop(); - _timer.Dispose(); + if (Presenter is not null) + { + Presenter.SizeChanged+= OnPresenterSizeChanged; + } + _timer?.Stop(); + _timer?.Dispose(); _timer = new Timer(); _timer.Interval = 1000 / 60.0; _timer.Elapsed += TimerOnTick; - _timer.Start(); + if (IsRunning) + { + _timer.Start(); + } } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); - _timer.Elapsed -= TimerOnTick; - _timer.Stop(); - _timer.Dispose(); + if (_timer is not null) + { + _timer.Elapsed -= TimerOnTick; + _timer.Stop(); + _timer.Dispose(); + } if (Presenter is not null) { Presenter.SizeChanged -= OnPresenterSizeChanged; @@ -101,7 +116,7 @@ public class Marquee : ContentControl } /// - /// Gets or sets the speed of the marquee. + /// Gets or sets the speed of the marquee. Point per second. /// public double Speed { @@ -109,15 +124,6 @@ public class Marquee : ContentControl set => SetValue(SpeedProperty, value); } - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - if (Presenter is not null) - { - Presenter.SizeChanged+= OnPresenterSizeChanged; - } - } - private void OnPresenterSizeChanged(object sender, SizeChangedEventArgs e) { InvalidatePresenterPosition();