diff --git a/demo/Ursa.Demo/Pages/ClockDemo.axaml b/demo/Ursa.Demo/Pages/ClockDemo.axaml index 892aa17..f29eff8 100644 --- a/demo/Ursa.Demo/Pages/ClockDemo.axaml +++ b/demo/Ursa.Demo/Pages/ClockDemo.axaml @@ -10,9 +10,9 @@ x:CompileBindings="True" d:DesignHeight="450" d:DesignWidth="800" - mc:Ignorable="d"> - - + + + diff --git a/src/Ursa/Controls/Clock/Clock.cs b/src/Ursa/Controls/Clock/Clock.cs index f73b0fd..c2d921a 100644 --- a/src/Ursa/Controls/Clock/Clock.cs +++ b/src/Ursa/Controls/Clock/Clock.cs @@ -1,131 +1,193 @@ using Avalonia; +using Avalonia.Animation; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Media; +using Avalonia.Styling; namespace Ursa.Controls; [TemplatePart(PART_ClockTicks, typeof(ClockTicks))] -public class Clock: TemplatedControl +public class Clock : TemplatedControl { public const string PART_ClockTicks = "PART_ClockTicks"; public static readonly StyledProperty TimeProperty = AvaloniaProperty.Register( nameof(Time), defaultBindingMode: BindingMode.TwoWay); + public static readonly StyledProperty ShowHourTicksProperty = + ClockTicks.ShowHourTicksProperty.AddOwner(); + + public static readonly StyledProperty ShowMinuteTicksProperty = + ClockTicks.ShowMinuteTicksProperty.AddOwner(); + + public static readonly StyledProperty HandBrushProperty = AvaloniaProperty.Register( + nameof(HandBrush)); + + public static readonly StyledProperty ShowHourHandProperty = AvaloniaProperty.Register( + nameof(ShowHourHand), true); + + public static readonly StyledProperty ShowMinuteHandProperty = AvaloniaProperty.Register( + nameof(ShowMinuteHand), true); + + public static readonly StyledProperty ShowSecondHandProperty = AvaloniaProperty.Register( + nameof(ShowSecondHand), true); + + public static readonly StyledProperty IsSmoothProperty = AvaloniaProperty.Register( + nameof(IsSmooth)); + + + public static readonly DirectProperty HourAngleProperty = + AvaloniaProperty.RegisterDirect( + nameof(HourAngle), o => o.HourAngle); + + public static readonly DirectProperty MinuteAngleProperty = + AvaloniaProperty.RegisterDirect( + nameof(MinuteAngle), o => o.MinuteAngle); + + public static readonly DirectProperty SecondAngleProperty = + AvaloniaProperty.RegisterDirect( + 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, args) => clock.OnTimeChanged(args)); + IsSmoothProperty.Changed.AddClassHandler((clock, args) => clock.OnIsSmoothChanged(args)); + } + + private void OnIsSmoothChanged(AvaloniaPropertyChangedEventArgs args) + { + if (args.NewValue.Value && !_cts.IsCancellationRequested ) + { + _cts.Cancel(); + } + } + public DateTime Time { get => GetValue(TimeProperty); set => SetValue(TimeProperty, value); } - public static readonly StyledProperty ShowHourTicksProperty = - ClockTicks.ShowHourTicksProperty.AddOwner(); - public bool ShowHourTicks { get => GetValue(ShowHourTicksProperty); set => SetValue(ShowHourTicksProperty, value); } - public static readonly StyledProperty ShowMinuteTicksProperty = - ClockTicks.ShowMinuteTicksProperty.AddOwner(); - public bool ShowMinuteTicks { get => GetValue(ShowMinuteTicksProperty); set => SetValue(ShowMinuteTicksProperty, value); } - public static readonly StyledProperty HandBrushProperty = AvaloniaProperty.Register( - nameof(HandBrush)); - public IBrush? HandBrush { get => GetValue(HandBrushProperty); set => SetValue(HandBrushProperty, value); } - public static readonly StyledProperty ShowHourHandProperty = AvaloniaProperty.Register( - nameof(ShowHourHand), defaultValue: true); - public bool ShowHourHand { get => GetValue(ShowHourHandProperty); set => SetValue(ShowHourHandProperty, value); } - public static readonly StyledProperty ShowMinuteHandProperty = AvaloniaProperty.Register( - nameof(ShowMinuteHand), defaultValue: true); - public bool ShowMinuteHand { get => GetValue(ShowMinuteHandProperty); set => SetValue(ShowMinuteHandProperty, value); } - public static readonly StyledProperty ShowSecondHandProperty = AvaloniaProperty.Register( - nameof(ShowSecondHand), defaultValue: true); - public bool ShowSecondHand { get => GetValue(ShowSecondHandProperty); set => SetValue(ShowSecondHandProperty, value); } - + public bool IsSmooth + { + get => GetValue(IsSmoothProperty); + set => SetValue(IsSmoothProperty, value); + } - public static readonly DirectProperty HourAngleProperty = AvaloniaProperty.RegisterDirect( - nameof(HourAngle), o => o.HourAngle); - private double _hourAngle; public double HourAngle { get => _hourAngle; private set => SetAndRaise(HourAngleProperty, ref _hourAngle, value); } - - public static readonly DirectProperty MinuteAngleProperty = AvaloniaProperty.RegisterDirect( - nameof(MinuteAngle), o => o.MinuteAngle); - private double _minuteAngle; + public double MinuteAngle { get => _minuteAngle; private set => SetAndRaise(MinuteAngleProperty, ref _minuteAngle, value); } - - public static readonly DirectProperty SecondAngleProperty = AvaloniaProperty.RegisterDirect( - nameof(SecondAngle), o => o.SecondAngle); - - private double _secondAngle; + public double SecondAngle { get => _secondAngle; private set => SetAndRaise(SecondAngleProperty, ref _secondAngle, value); } - static Clock() + private Animation _secondsAnimation = new Animation() { - TimeProperty.Changed.AddClassHandler((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 args) + private CancellationTokenSource _cts = new CancellationTokenSource(); + + private async void OnTimeChanged(AvaloniaPropertyChangedEventArgs args) { - DateTime time = args.NewValue.Value; + var oldSeconds = args.OldValue.Value.Second; + var time = args.NewValue.Value; var hour = time.Hour; var minute = time.Minute; var second = time.Second; var hourAngle = 360.0 / 12 * hour + 360.0 / 12 / 60 * minute; 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; HourAngle = hourAngle; MinuteAngle = minuteAngle; - SecondAngle = secondAngle; + if (!IsLoaded || !IsSmooth) + { + 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) { - double min = Math.Min(availableSize.Height, availableSize.Width); + var min = Math.Min(availableSize.Height, availableSize.Width); var newSize = new Size(min, min); var size = base.MeasureOverride(newSize); return size; @@ -133,7 +195,7 @@ public class Clock: TemplatedControl 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 size = base.ArrangeOverride(newSize); return size;