feat: enable smooth second hand movement.
This commit is contained in:
@@ -10,9 +10,9 @@
|
|||||||
x:CompileBindings="True"
|
x:CompileBindings="True"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
|
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<Grid>
|
<Grid RowDefinitions="Auto, *">
|
||||||
<u:Clock HorizontalAlignment="Left" Time="{Binding Time}"></u:Clock>
|
<ToggleSwitch Content="Smooth" Name="smooth"></ToggleSwitch>
|
||||||
|
<u:Clock Grid.Row="1" HorizontalAlignment="Left" IsSmooth="{Binding #smooth.IsChecked}" Time="{Binding Time}"></u:Clock>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -1,131 +1,193 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Animation;
|
||||||
using Avalonia.Controls.Metadata;
|
using Avalonia.Controls.Metadata;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Data;
|
using Avalonia.Data;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
|
||||||
namespace Ursa.Controls;
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
[TemplatePart(PART_ClockTicks, typeof(ClockTicks))]
|
[TemplatePart(PART_ClockTicks, typeof(ClockTicks))]
|
||||||
public class Clock: TemplatedControl
|
public class Clock : TemplatedControl
|
||||||
{
|
{
|
||||||
public const string PART_ClockTicks = "PART_ClockTicks";
|
public const string PART_ClockTicks = "PART_ClockTicks";
|
||||||
|
|
||||||
public static readonly StyledProperty<DateTime> TimeProperty = AvaloniaProperty.Register<Clock, DateTime>(
|
public static readonly StyledProperty<DateTime> TimeProperty = AvaloniaProperty.Register<Clock, DateTime>(
|
||||||
nameof(Time), defaultBindingMode: BindingMode.TwoWay);
|
nameof(Time), defaultBindingMode: BindingMode.TwoWay);
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> ShowHourTicksProperty =
|
||||||
|
ClockTicks.ShowHourTicksProperty.AddOwner<Clock>();
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> ShowMinuteTicksProperty =
|
||||||
|
ClockTicks.ShowMinuteTicksProperty.AddOwner<Clock>();
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush?> HandBrushProperty = AvaloniaProperty.Register<Clock, IBrush?>(
|
||||||
|
nameof(HandBrush));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> ShowHourHandProperty = AvaloniaProperty.Register<Clock, bool>(
|
||||||
|
nameof(ShowHourHand), true);
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> ShowMinuteHandProperty = AvaloniaProperty.Register<Clock, bool>(
|
||||||
|
nameof(ShowMinuteHand), true);
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> ShowSecondHandProperty = AvaloniaProperty.Register<Clock, bool>(
|
||||||
|
nameof(ShowSecondHand), true);
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsSmoothProperty = AvaloniaProperty.Register<Clock, bool>(
|
||||||
|
nameof(IsSmooth));
|
||||||
|
|
||||||
|
|
||||||
|
public static readonly DirectProperty<Clock, double> HourAngleProperty =
|
||||||
|
AvaloniaProperty.RegisterDirect<Clock, double>(
|
||||||
|
nameof(HourAngle), o => o.HourAngle);
|
||||||
|
|
||||||
|
public static readonly DirectProperty<Clock, double> MinuteAngleProperty =
|
||||||
|
AvaloniaProperty.RegisterDirect<Clock, double>(
|
||||||
|
nameof(MinuteAngle), o => o.MinuteAngle);
|
||||||
|
|
||||||
|
public static readonly DirectProperty<Clock, double> SecondAngleProperty =
|
||||||
|
AvaloniaProperty.RegisterDirect<Clock, double>(
|
||||||
|
nameof(SecondAngle), o => o.SecondAngle, (o, v) => o.SecondAngle = v);
|
||||||
|
|
||||||
|
private double _hourAngle;
|
||||||
|
private double _minuteAngle;
|
||||||
|
|
||||||
|
private double _secondAngle;
|
||||||
|
|
||||||
|
static Clock()
|
||||||
|
{
|
||||||
|
TimeProperty.Changed.AddClassHandler<Clock, DateTime>((clock, args) => clock.OnTimeChanged(args));
|
||||||
|
IsSmoothProperty.Changed.AddClassHandler<Clock, bool>((clock, args) => clock.OnIsSmoothChanged(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnIsSmoothChanged(AvaloniaPropertyChangedEventArgs<bool> args)
|
||||||
|
{
|
||||||
|
if (args.NewValue.Value && !_cts.IsCancellationRequested )
|
||||||
|
{
|
||||||
|
_cts.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public DateTime Time
|
public DateTime Time
|
||||||
{
|
{
|
||||||
get => GetValue(TimeProperty);
|
get => GetValue(TimeProperty);
|
||||||
set => SetValue(TimeProperty, value);
|
set => SetValue(TimeProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> ShowHourTicksProperty =
|
|
||||||
ClockTicks.ShowHourTicksProperty.AddOwner<Clock>();
|
|
||||||
|
|
||||||
public bool ShowHourTicks
|
public bool ShowHourTicks
|
||||||
{
|
{
|
||||||
get => GetValue(ShowHourTicksProperty);
|
get => GetValue(ShowHourTicksProperty);
|
||||||
set => SetValue(ShowHourTicksProperty, value);
|
set => SetValue(ShowHourTicksProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> ShowMinuteTicksProperty =
|
|
||||||
ClockTicks.ShowMinuteTicksProperty.AddOwner<Clock>();
|
|
||||||
|
|
||||||
public bool ShowMinuteTicks
|
public bool ShowMinuteTicks
|
||||||
{
|
{
|
||||||
get => GetValue(ShowMinuteTicksProperty);
|
get => GetValue(ShowMinuteTicksProperty);
|
||||||
set => SetValue(ShowMinuteTicksProperty, value);
|
set => SetValue(ShowMinuteTicksProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<IBrush?> HandBrushProperty = AvaloniaProperty.Register<Clock, IBrush?>(
|
|
||||||
nameof(HandBrush));
|
|
||||||
|
|
||||||
public IBrush? HandBrush
|
public IBrush? HandBrush
|
||||||
{
|
{
|
||||||
get => GetValue(HandBrushProperty);
|
get => GetValue(HandBrushProperty);
|
||||||
set => SetValue(HandBrushProperty, value);
|
set => SetValue(HandBrushProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> ShowHourHandProperty = AvaloniaProperty.Register<Clock, bool>(
|
|
||||||
nameof(ShowHourHand), defaultValue: true);
|
|
||||||
|
|
||||||
public bool ShowHourHand
|
public bool ShowHourHand
|
||||||
{
|
{
|
||||||
get => GetValue(ShowHourHandProperty);
|
get => GetValue(ShowHourHandProperty);
|
||||||
set => SetValue(ShowHourHandProperty, value);
|
set => SetValue(ShowHourHandProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> ShowMinuteHandProperty = AvaloniaProperty.Register<Clock, bool>(
|
|
||||||
nameof(ShowMinuteHand), defaultValue: true);
|
|
||||||
|
|
||||||
public bool ShowMinuteHand
|
public bool ShowMinuteHand
|
||||||
{
|
{
|
||||||
get => GetValue(ShowMinuteHandProperty);
|
get => GetValue(ShowMinuteHandProperty);
|
||||||
set => SetValue(ShowMinuteHandProperty, value);
|
set => SetValue(ShowMinuteHandProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> ShowSecondHandProperty = AvaloniaProperty.Register<Clock, bool>(
|
|
||||||
nameof(ShowSecondHand), defaultValue: true);
|
|
||||||
|
|
||||||
public bool ShowSecondHand
|
public bool ShowSecondHand
|
||||||
{
|
{
|
||||||
get => GetValue(ShowSecondHandProperty);
|
get => GetValue(ShowSecondHandProperty);
|
||||||
set => SetValue(ShowSecondHandProperty, value);
|
set => SetValue(ShowSecondHandProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsSmooth
|
||||||
|
{
|
||||||
|
get => GetValue(IsSmoothProperty);
|
||||||
|
set => SetValue(IsSmoothProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static readonly DirectProperty<Clock, double> HourAngleProperty = AvaloniaProperty.RegisterDirect<Clock, double>(
|
|
||||||
nameof(HourAngle), o => o.HourAngle);
|
|
||||||
private double _hourAngle;
|
|
||||||
public double HourAngle
|
public double HourAngle
|
||||||
{
|
{
|
||||||
get => _hourAngle;
|
get => _hourAngle;
|
||||||
private set => SetAndRaise(HourAngleProperty, ref _hourAngle, value);
|
private set => SetAndRaise(HourAngleProperty, ref _hourAngle, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly DirectProperty<Clock, double> MinuteAngleProperty = AvaloniaProperty.RegisterDirect<Clock, double>(
|
|
||||||
nameof(MinuteAngle), o => o.MinuteAngle);
|
|
||||||
private double _minuteAngle;
|
|
||||||
public double MinuteAngle
|
public double MinuteAngle
|
||||||
{
|
{
|
||||||
get => _minuteAngle;
|
get => _minuteAngle;
|
||||||
private set => SetAndRaise(MinuteAngleProperty, ref _minuteAngle, value);
|
private set => SetAndRaise(MinuteAngleProperty, ref _minuteAngle, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly DirectProperty<Clock, double> SecondAngleProperty = AvaloniaProperty.RegisterDirect<Clock, double>(
|
|
||||||
nameof(SecondAngle), o => o.SecondAngle);
|
|
||||||
|
|
||||||
private double _secondAngle;
|
|
||||||
public double SecondAngle
|
public double SecondAngle
|
||||||
{
|
{
|
||||||
get => _secondAngle;
|
get => _secondAngle;
|
||||||
private set => SetAndRaise(SecondAngleProperty, ref _secondAngle, value);
|
private set => SetAndRaise(SecondAngleProperty, ref _secondAngle, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Clock()
|
private Animation _secondsAnimation = new Animation()
|
||||||
{
|
{
|
||||||
TimeProperty.Changed.AddClassHandler<Clock, DateTime>((clock, args)=>clock.OnTimeChanged(args));
|
FillMode = FillMode.Forward,
|
||||||
|
Duration = TimeSpan.FromSeconds(1),
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new KeyFrame
|
||||||
|
{
|
||||||
|
Cue = new Cue(0.0),
|
||||||
|
Setters = { new Setter { Property = SecondAngleProperty } }
|
||||||
|
},
|
||||||
|
new KeyFrame
|
||||||
|
{
|
||||||
|
Cue = new Cue(1.0),
|
||||||
|
Setters = { new Setter { Property = SecondAngleProperty } }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private void OnTimeChanged(AvaloniaPropertyChangedEventArgs<DateTime> args)
|
private CancellationTokenSource _cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
private async void OnTimeChanged(AvaloniaPropertyChangedEventArgs<DateTime> args)
|
||||||
{
|
{
|
||||||
DateTime time = args.NewValue.Value;
|
var oldSeconds = args.OldValue.Value.Second;
|
||||||
|
var time = args.NewValue.Value;
|
||||||
var hour = time.Hour;
|
var hour = time.Hour;
|
||||||
var minute = time.Minute;
|
var minute = time.Minute;
|
||||||
var second = time.Second;
|
var second = time.Second;
|
||||||
var hourAngle = 360.0 / 12 * hour + 360.0 / 12 / 60 * minute;
|
var hourAngle = 360.0 / 12 * hour + 360.0 / 12 / 60 * minute;
|
||||||
var minuteAngle = 360.0 / 60 * minute + 360.0 / 60 / 60 * second;
|
var minuteAngle = 360.0 / 60 * minute + 360.0 / 60 / 60 * second;
|
||||||
|
if (second == 0) second = 60;
|
||||||
|
var oldSecondAngle = 360.0 / 60 * oldSeconds;
|
||||||
var secondAngle = 360.0 / 60 * second;
|
var secondAngle = 360.0 / 60 * second;
|
||||||
HourAngle = hourAngle;
|
HourAngle = hourAngle;
|
||||||
MinuteAngle = minuteAngle;
|
MinuteAngle = minuteAngle;
|
||||||
|
if (!IsLoaded || !IsSmooth)
|
||||||
|
{
|
||||||
SecondAngle = secondAngle;
|
SecondAngle = secondAngle;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_cts.Cancel();
|
||||||
|
_cts.Dispose();
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
(_secondsAnimation.Children[0].Setters[0] as Setter).Value = oldSecondAngle;
|
||||||
|
(_secondsAnimation.Children[1].Setters[0] as Setter).Value = secondAngle;
|
||||||
|
_secondsAnimation.RunAsync(this, _cts.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override Size MeasureOverride(Size availableSize)
|
protected override Size MeasureOverride(Size availableSize)
|
||||||
{
|
{
|
||||||
double min = Math.Min(availableSize.Height, availableSize.Width);
|
var min = Math.Min(availableSize.Height, availableSize.Width);
|
||||||
var newSize = new Size(min, min);
|
var newSize = new Size(min, min);
|
||||||
var size = base.MeasureOverride(newSize);
|
var size = base.MeasureOverride(newSize);
|
||||||
return size;
|
return size;
|
||||||
@@ -133,7 +195,7 @@ public class Clock: TemplatedControl
|
|||||||
|
|
||||||
protected override Size ArrangeOverride(Size finalSize)
|
protected override Size ArrangeOverride(Size finalSize)
|
||||||
{
|
{
|
||||||
double min = Math.Min(finalSize.Height, finalSize.Width);
|
var min = Math.Min(finalSize.Height, finalSize.Width);
|
||||||
var newSize = new Size(min, min);
|
var newSize = new Size(min, min);
|
||||||
var size = base.ArrangeOverride(newSize);
|
var size = base.ArrangeOverride(newSize);
|
||||||
return size;
|
return size;
|
||||||
|
|||||||
Reference in New Issue
Block a user