feat: remove calculation from dispatcher call.

This commit is contained in:
rabbitism
2024-12-17 18:01:12 +08:00
parent 0b9bc3303d
commit 49d410bd4b
2 changed files with 155 additions and 30 deletions

View File

@@ -9,12 +9,36 @@
d:DesignWidth="800" d:DesignWidth="800"
mc:Ignorable="d"> mc:Ignorable="d">
<StackPanel> <StackPanel>
<u:EnumSelector Name="direction" EnumType="{x:Type u:Direction}" /> <u:Form>
<u:EnumSelector
u:FormItem.Label="Direction"
Name="direction"
EnumType="{x:Type u:Direction}"
Value="{x:Static u:Direction.Left}" />
<u:EnumSelector
Name="horizontal"
u:FormItem.Label="HorizontalContentAlignment"
EnumType="{x:Type HorizontalAlignment}"
Value="{x:Static HorizontalAlignment.Center}" />
<u:EnumSelector
Name="vertical"
u:FormItem.Label="VerticalContentAlignment"
EnumType="{x:Type VerticalAlignment}"
Value="{x:Static VerticalAlignment.Center}" />
<ToggleSwitch
Name="running"
u:FormItem.Label="IsRunning"
IsChecked="True" />
</u:Form>
<u:Marquee <u:Marquee
Background="Red"
Height="100" Height="100"
Direction="{Binding #direction.Value}" > HorizontalContentAlignment="{Binding #horizontal.Value}"
<TextBlock Text="Hello World" VerticalAlignment="Center"/> VerticalContentAlignment="{Binding #vertical.Value}"
Background="{DynamicResource SemiBlue1}"
Direction="{Binding #direction.Value}"
IsRunning="{Binding #running.IsChecked}">
<TextBlock VerticalAlignment="Center" Text="Hello World" />
</u:Marquee> </u:Marquee>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@@ -1,5 +1,7 @@
using System.Runtime.CompilerServices;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Threading; using Avalonia.Threading;
using Irihi.Avalonia.Shared.Helpers; using Irihi.Avalonia.Shared.Helpers;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
@@ -12,7 +14,7 @@ public class Marquee : ContentControl
/// Defines the <see cref="IsRunning" /> property. /// Defines the <see cref="IsRunning" /> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> IsRunningProperty = AvaloniaProperty.Register<Marquee, bool>( public static readonly StyledProperty<bool> IsRunningProperty = AvaloniaProperty.Register<Marquee, bool>(
nameof(IsRunning)); nameof(IsRunning), true);
/// <summary> /// <summary>
/// Defines the <see cref="MarqueeMode" /> property. /// Defines the <see cref="MarqueeMode" /> property.
@@ -26,21 +28,55 @@ public class Marquee : ContentControl
public static readonly StyledProperty<double> SpeedProperty = AvaloniaProperty.Register<Marquee, double>( public static readonly StyledProperty<double> SpeedProperty = AvaloniaProperty.Register<Marquee, double>(
nameof(Speed), 60.0); nameof(Speed), 60.0);
private readonly Timer _timer; private Timer _timer;
static Marquee() static Marquee()
{ {
ClipToBoundsProperty.OverrideDefaultValue<Marquee>(true); ClipToBoundsProperty.OverrideDefaultValue<Marquee>(true);
HorizontalContentAlignmentProperty.OverrideDefaultValue<Marquee>(HorizontalAlignment.Center);
VerticalContentAlignmentProperty.OverrideDefaultValue<Marquee>(VerticalAlignment.Center);
HorizontalContentAlignmentProperty.Changed.AddClassHandler<Marquee>((o,args)=>o.InvalidatePresenterPosition());
VerticalContentAlignmentProperty.Changed.AddClassHandler<Marquee>((o,args)=>o.InvalidatePresenterPosition());
IsRunningProperty.Changed.AddClassHandler<Marquee, bool>((o, args) => o.OnIsRunningChanged(args));
}
private void OnIsRunningChanged(AvaloniaPropertyChangedEventArgs<bool> args)
{
if (args.NewValue.Value)
{
_timer.Start();
}
else
{
_timer.Stop();
}
} }
public Marquee() public Marquee()
{ {
_timer = new Timer(); _timer = new Timer();
_timer.Interval = 1000 / 60.0; _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.Elapsed += TimerOnTick;
_timer.Start(); _timer.Start();
} }
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_timer.Elapsed -= TimerOnTick;
_timer.Stop();
_timer.Dispose();
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the marquee is running. /// Gets or sets a value indicating whether the marquee is running.
/// </summary> /// </summary>
@@ -70,7 +106,25 @@ public class Marquee : ContentControl
private void TimerOnTick(object sender, System.EventArgs e) 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) protected override Size MeasureOverride(Size availableSize)
@@ -90,23 +144,22 @@ public class Marquee : ContentControl
return result; return result;
} }
private void UpdateLocation() private (double top, double left)? UpdateLocation(LayoutValues values)
{ {
if (Presenter is null) return; var horizontalOffset = values.Direction switch
var horizontalOffset = Direction switch
{ {
Direction.Up or Direction.Down => 0, Direction.Up or Direction.Down => GetHorizontalOffset(values.Bounds, values.PresenterSize, values.HorizontalAlignment),
Direction.Left or Direction.Right => Canvas.GetLeft(Presenter), 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.Up or Direction.Down => values.Top,
Direction.Left or Direction.Right => 0, Direction.Left or Direction.Right => GetVerticalOffset(values.Bounds, values.PresenterSize, values.VerticalAlignment),
}; };
if (horizontalOffset is double.NaN) horizontalOffset = 0.0; if (horizontalOffset is double.NaN) horizontalOffset = 0.0;
if (verticalOffset is double.NaN) verticalOffset = 0.0; if (verticalOffset is double.NaN) verticalOffset = 0.0;
var speed = Speed / 60.0; var speed = values.Diff;
var diff = Direction switch var diff = values.Direction switch
{ {
Direction.Up => -speed, Direction.Up => -speed,
Direction.Down => speed, Direction.Down => speed,
@@ -114,7 +167,7 @@ public class Marquee : ContentControl
Direction.Right => speed, Direction.Right => speed,
_ => 0 _ => 0
}; };
switch (Direction) switch (values.Direction)
{ {
case Direction.Up: case Direction.Up:
case Direction.Down: case Direction.Down:
@@ -125,26 +178,74 @@ public class Marquee : ContentControl
horizontalOffset += diff; horizontalOffset += diff;
break; break;
} }
switch (Direction) switch (values.Direction)
{ {
case Direction.Down: case Direction.Down:
if (verticalOffset > Bounds.Height) verticalOffset = -Presenter.Bounds.Height; if (verticalOffset > values.Bounds.Height) verticalOffset = -values.PresenterSize.Height;
verticalOffset = MathHelpers.SafeClamp(verticalOffset, -Presenter.Bounds.Height, Bounds.Height);
break; break;
case Direction.Up: case Direction.Up:
if (verticalOffset < -Presenter.Bounds.Height) verticalOffset = Bounds.Height; if (verticalOffset < -values.PresenterSize.Height) verticalOffset = values.Bounds.Height;
verticalOffset = MathHelpers.SafeClamp(verticalOffset, -Presenter.Bounds.Height, Bounds.Height);
break; break;
case Direction.Right: case Direction.Right:
if (horizontalOffset > Bounds.Width) horizontalOffset = -Presenter.Bounds.Width; if (horizontalOffset > values.Bounds.Width) horizontalOffset = -values.PresenterSize.Width;
horizontalOffset = MathHelpers.SafeClamp(horizontalOffset, -Presenter.Bounds.Width, Bounds.Width);
break; break;
case Direction.Left: case Direction.Left:
if (horizontalOffset < -Presenter.Bounds.Width) horizontalOffset = Bounds.Width; if (horizontalOffset < -values.PresenterSize.Width) horizontalOffset = values.Bounds.Width;
horizontalOffset = MathHelpers.SafeClamp(horizontalOffset, -Presenter.Bounds.Width, Bounds.Width);
break; break;
} }
Canvas.SetTop(Presenter, verticalOffset); verticalOffset = MathHelpers.SafeClamp(verticalOffset, -values.PresenterSize.Height, values.Bounds.Height);
Canvas.SetLeft(Presenter, horizontalOffset); 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; }
}