From 14d3958873149b1b8f206b68f01210ad7cb893bb Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sun, 21 Apr 2024 22:14:41 +0800 Subject: [PATCH 1/4] feat: add clock tick. --- demo/Ursa.Demo/Pages/ClockDemo.axaml | 20 +++ demo/Ursa.Demo/Pages/ClockDemo.axaml.cs | 13 ++ .../ViewModels/ClockDemoViewModel.cs | 6 + .../Ursa.Demo/ViewModels/MainViewViewModel.cs | 1 + demo/Ursa.Demo/ViewModels/MenuViewModel.cs | 2 + src/Ursa/Controls/Clock/Clock.cs | 39 ++++++ src/Ursa/Controls/Clock/ClockTicks.cs | 127 ++++++++++++++++++ 7 files changed, 208 insertions(+) create mode 100644 demo/Ursa.Demo/Pages/ClockDemo.axaml create mode 100644 demo/Ursa.Demo/Pages/ClockDemo.axaml.cs create mode 100644 demo/Ursa.Demo/ViewModels/ClockDemoViewModel.cs create mode 100644 src/Ursa/Controls/Clock/Clock.cs create mode 100644 src/Ursa/Controls/Clock/ClockTicks.cs diff --git a/demo/Ursa.Demo/Pages/ClockDemo.axaml b/demo/Ursa.Demo/Pages/ClockDemo.axaml new file mode 100644 index 0000000..c86f20e --- /dev/null +++ b/demo/Ursa.Demo/Pages/ClockDemo.axaml @@ -0,0 +1,20 @@ + + + + + diff --git a/demo/Ursa.Demo/Pages/ClockDemo.axaml.cs b/demo/Ursa.Demo/Pages/ClockDemo.axaml.cs new file mode 100644 index 0000000..058136a --- /dev/null +++ b/demo/Ursa.Demo/Pages/ClockDemo.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Ursa.Demo.Pages; + +public partial class ClockDemo : UserControl +{ + public ClockDemo() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/ClockDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/ClockDemoViewModel.cs new file mode 100644 index 0000000..4670003 --- /dev/null +++ b/demo/Ursa.Demo/ViewModels/ClockDemoViewModel.cs @@ -0,0 +1,6 @@ +namespace Ursa.Demo.ViewModels; + +public class ClockDemoViewModel +{ + +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs index 47eec8a..1ac7c29 100644 --- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs @@ -30,6 +30,7 @@ public class MainViewViewModel : ViewModelBase MenuKeys.MenuKeyButtonGroup => new ButtonGroupDemoViewModel(), MenuKeys.MenuKeyBreadcrumb => new BreadcrumbDemoViewModel(), MenuKeys.MenuKeyClassInput => new ClassInputDemoViewModel(), + MenuKeys.MenuKeyClock => new ClockDemoViewModel(), MenuKeys.MenuKeyDialog => new DialogDemoViewModel(), MenuKeys.MenuKeyDivider => new DividerDemoViewModel(), MenuKeys.MenuKeyDisableContainer => new DisableContainerDemoViewModel(), diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs index 62d76a7..c58784d 100644 --- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs @@ -17,6 +17,7 @@ public class MenuViewModel: ViewModelBase new() { MenuHeader = "Breadcrumb", Key = MenuKeys.MenuKeyBreadcrumb, Status = "New" }, new() { MenuHeader = "Button Group", Key = MenuKeys.MenuKeyButtonGroup, Status = "Updated" }, new() { MenuHeader = "Class Input", Key = MenuKeys.MenuKeyClassInput }, + new() { MenuHeader = "Clock", Key = MenuKeys.MenuKeyClock, Status = "New" }, new() { MenuHeader = "Dialog", Key = MenuKeys.MenuKeyDialog }, new() { MenuHeader = "Disable Container", Key = MenuKeys.MenuKeyDisableContainer }, new() { MenuHeader = "Divider", Key = MenuKeys.MenuKeyDivider }, @@ -59,6 +60,7 @@ public static class MenuKeys public const string MenuKeyButtonGroup = "ButtonGroup"; public const string MenuKeyBreadcrumb= "Breadcrumb"; public const string MenuKeyClassInput = "Class Input"; + public const string MenuKeyClock = "Clock"; public const string MenuKeyDialog = "Dialog"; public const string MenuKeyDivider = "Divider"; public const string MenuKeyDisableContainer = "DisableContainer"; diff --git a/src/Ursa/Controls/Clock/Clock.cs b/src/Ursa/Controls/Clock/Clock.cs new file mode 100644 index 0000000..4036044 --- /dev/null +++ b/src/Ursa/Controls/Clock/Clock.cs @@ -0,0 +1,39 @@ +using Avalonia; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; + +namespace Ursa.Controls; + +[TemplatePart(PART_ClockTicks, typeof(ClockTicks))] +public class Clock: TemplatedControl +{ + public const string PART_ClockTicks = "PART_ClockTicks"; + + public static readonly StyledProperty TimeProperty = AvaloniaProperty.Register( + nameof(Time)); + + public TimeSpan 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); + } + +} \ No newline at end of file diff --git a/src/Ursa/Controls/Clock/ClockTicks.cs b/src/Ursa/Controls/Clock/ClockTicks.cs new file mode 100644 index 0000000..62360a6 --- /dev/null +++ b/src/Ursa/Controls/Clock/ClockTicks.cs @@ -0,0 +1,127 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; + +namespace Ursa.Controls; + +public class ClockTicks: Control +{ + private Matrix _hourRotationMatrix = Matrix.CreateRotation(Math.PI / 6); + private Matrix _minuteRotationMatrix = Matrix.CreateRotation(Math.PI / 30); + + public static readonly StyledProperty ShowHourTicksProperty = AvaloniaProperty.Register( + nameof(ShowHourTicks), true); + + public bool ShowHourTicks + { + get => GetValue(ShowHourTicksProperty); + set => SetValue(ShowHourTicksProperty, value); + } + + public static readonly StyledProperty ShowMinuteTicksProperty = AvaloniaProperty.Register( + nameof(ShowMinuteTicks), true); + + public bool ShowMinuteTicks + { + get => GetValue(ShowMinuteTicksProperty); + set => SetValue(ShowMinuteTicksProperty, value); + } + + public static readonly StyledProperty HourTickForegroundProperty = AvaloniaProperty.Register( + nameof(HourTickForeground)); + + public IBrush? HourTickForeground + { + get => GetValue(HourTickForegroundProperty); + set => SetValue(HourTickForegroundProperty, value); + } + + public static readonly StyledProperty MinuteTickForegroundProperty = AvaloniaProperty.Register( + nameof(MinuteTickForeground)); + + public IBrush? MinuteTickForeground + { + get => GetValue(MinuteTickForegroundProperty); + set => SetValue(MinuteTickForegroundProperty, value); + } + + public static readonly StyledProperty HourTickLengthProperty = AvaloniaProperty.Register( + nameof(HourTickLength), 10); + + public double HourTickLength + { + get => GetValue(HourTickLengthProperty); + set => SetValue(HourTickLengthProperty, value); + } + + public static readonly StyledProperty MinuteTickLengthProperty = AvaloniaProperty.Register( + nameof(MinuteTickLength), 5); + + public double MinuteTickLength + { + get => GetValue(MinuteTickLengthProperty); + set => SetValue(MinuteTickLengthProperty, value); + } + + public static readonly StyledProperty HourTickWidthProperty = AvaloniaProperty.Register( + nameof(HourTickWidth), 2); + + public double HourTickWidth + { + get => GetValue(HourTickWidthProperty); + set => SetValue(HourTickWidthProperty, value); + } + + public static readonly StyledProperty MinuteTickWidthProperty = AvaloniaProperty.Register( + nameof(MinuteTickWidth), 1); + + public double MinuteTickWidth + { + get => GetValue(MinuteTickWidthProperty); + set => SetValue(MinuteTickWidthProperty, value); + } + + static ClockTicks() + { + AffectsRender(ShowHourTicksProperty); + } + + public override void Render(DrawingContext context) + { + base.Render(context); + var size = Math.Min(Bounds.Width, Bounds.Height); + var center = size / 2; + IPen hourTickPen = new Pen(HourTickForeground, HourTickWidth); + IPen minuteTickPen = new Pen(MinuteTickForeground, MinuteTickWidth); + double hourTickLength = Math.Min(center, HourTickLength); + double minuteTickLength = Math.Min(center, MinuteTickLength); + context.PushTransform(Matrix.CreateTranslation(center, center)); + if (ShowHourTicks) + { + for (int i = 0; i < 12; i++) + { + DrawTick(context, hourTickPen, center, hourTickLength); + context.PushTransform(_hourRotationMatrix); + } + } + + if (ShowMinuteTicks) + { + for (int i = 0; i < 60; i++) + { + if (i % 5 != 0) + { + DrawTick(context, minuteTickPen, center, minuteTickLength); + } + context.PushTransform(_minuteRotationMatrix); + } + } + } + + private void DrawTick(DrawingContext context, IPen pen, double center, double length) + { + var start = new Point(0, -center); + var end = new Point(0, length-center); + context.DrawLine(pen, start, end); + } +} \ No newline at end of file From 3815a5114caba5c0cba95f07d18b1fbf12573787 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sun, 21 Apr 2024 22:58:31 +0800 Subject: [PATCH 2/4] feat: add clock template. --- demo/Ursa.Demo/Pages/ClockDemo.axaml | 8 +----- src/Ursa.Themes.Semi/Controls/Clock.axaml | 21 ++++++++++++++ src/Ursa.Themes.Semi/Controls/_index.axaml | 1 + src/Ursa/Controls/Clock/Clock.cs | 33 ++++++++++++++++++++++ src/Ursa/Controls/Clock/ClockTicks.cs | 12 ++++++++ 5 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 src/Ursa.Themes.Semi/Controls/Clock.axaml diff --git a/demo/Ursa.Demo/Pages/ClockDemo.axaml b/demo/Ursa.Demo/Pages/ClockDemo.axaml index c86f20e..4dcde2d 100644 --- a/demo/Ursa.Demo/Pages/ClockDemo.axaml +++ b/demo/Ursa.Demo/Pages/ClockDemo.axaml @@ -9,12 +9,6 @@ d:DesignWidth="800" mc:Ignorable="d"> - + diff --git a/src/Ursa.Themes.Semi/Controls/Clock.axaml b/src/Ursa.Themes.Semi/Controls/Clock.axaml new file mode 100644 index 0000000..a62b9f9 --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/Clock.axaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index d05cb2e..0e6694b 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -6,6 +6,7 @@ + diff --git a/src/Ursa/Controls/Clock/Clock.cs b/src/Ursa/Controls/Clock/Clock.cs index 4036044..92bb777 100644 --- a/src/Ursa/Controls/Clock/Clock.cs +++ b/src/Ursa/Controls/Clock/Clock.cs @@ -35,5 +35,38 @@ public class Clock: TemplatedControl get => GetValue(ShowMinuteTicksProperty); set => SetValue(ShowMinuteTicksProperty, value); } + + public static readonly StyledProperty HourHandMarginProperty = AvaloniaProperty.Register( + nameof(HourHandMargin)); + + public double HourHandMargin + { + get => GetValue(HourHandMarginProperty); + set => SetValue(HourHandMarginProperty, value); + } + public static readonly StyledProperty MinuteHandMarginProperty = AvaloniaProperty.Register( + nameof(MinuteHandMargin)); + + public double MinuteHandMargin + { + get => GetValue(MinuteHandMarginProperty); + set => SetValue(MinuteHandMarginProperty, value); + } + + protected override Size MeasureOverride(Size availableSize) + { + double min = Math.Min(availableSize.Height, availableSize.Width); + var newSize = new Size(min, min); + var size = base.MeasureOverride(newSize); + return size; + } + + protected override Size ArrangeOverride(Size finalSize) + { + double min = Math.Min(finalSize.Height, finalSize.Width); + var newSize = new Size(min, min); + var size = base.ArrangeOverride(newSize); + return size; + } } \ No newline at end of file diff --git a/src/Ursa/Controls/Clock/ClockTicks.cs b/src/Ursa/Controls/Clock/ClockTicks.cs index 62360a6..eab1455 100644 --- a/src/Ursa/Controls/Clock/ClockTicks.cs +++ b/src/Ursa/Controls/Clock/ClockTicks.cs @@ -86,6 +86,18 @@ public class ClockTicks: Control AffectsRender(ShowHourTicksProperty); } + protected override Size MeasureOverride(Size availableSize) + { + double minSize= Math.Min(availableSize.Width, availableSize.Height); + return new Size(minSize, minSize); + } + + protected override Size ArrangeOverride(Size finalSize) + { + var minSize = Math.Min(finalSize.Width, finalSize.Height); + return new Size(minSize, minSize); + } + public override void Render(DrawingContext context) { base.Render(context); From db6ba785ddf3d13bae67aa3d2bb7229b905d01bc Mon Sep 17 00:00:00 2001 From: rabbitism Date: Mon, 22 Apr 2024 02:05:29 +0800 Subject: [PATCH 3/4] feat: add auto tick sample. --- demo/Ursa.Demo/Pages/ClockDemo.axaml | 6 +- .../ViewModels/ClockDemoViewModel.cs | 29 +++++- src/Ursa.Themes.Semi/Controls/Clock.axaml | 52 ++++++++-- src/Ursa/Controls/Clock/Clock.cs | 97 ++++++++++++++++--- 4 files changed, 161 insertions(+), 23 deletions(-) diff --git a/demo/Ursa.Demo/Pages/ClockDemo.axaml b/demo/Ursa.Demo/Pages/ClockDemo.axaml index 4dcde2d..892aa17 100644 --- a/demo/Ursa.Demo/Pages/ClockDemo.axaml +++ b/demo/Ursa.Demo/Pages/ClockDemo.axaml @@ -5,10 +5,14 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:u="https://irihi.tech/ursa" + xmlns:vm="clr-namespace:Ursa.Demo.ViewModels" + x:DataType="vm:ClockDemoViewModel" + x:CompileBindings="True" d:DesignHeight="450" d:DesignWidth="800" + mc:Ignorable="d"> - + diff --git a/demo/Ursa.Demo/ViewModels/ClockDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/ClockDemoViewModel.cs index 4670003..48cda91 100644 --- a/demo/Ursa.Demo/ViewModels/ClockDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/ClockDemoViewModel.cs @@ -1,6 +1,31 @@ -namespace Ursa.Demo.ViewModels; +using System; +using System.Timers; +using CommunityToolkit.Mvvm.ComponentModel; -public class ClockDemoViewModel +namespace Ursa.Demo.ViewModels; + +public partial class ClockDemoViewModel: ObservableObject, IDisposable { + private Timer _timer; + [ObservableProperty] private DateTime _time; + public ClockDemoViewModel() + { + Time = DateTime.Now; + _timer = new Timer(1000); + _timer.Elapsed += TimerOnElapsed; + _timer.Start(); + } + + private void TimerOnElapsed(object? sender, ElapsedEventArgs e) + { + Time = DateTime.Now; + } + + public void Dispose() + { + _timer.Stop(); + _timer.Elapsed -= TimerOnElapsed; + _timer.Dispose(); + } } \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/Clock.axaml b/src/Ursa.Themes.Semi/Controls/Clock.axaml index a62b9f9..14b554a 100644 --- a/src/Ursa.Themes.Semi/Controls/Clock.axaml +++ b/src/Ursa.Themes.Semi/Controls/Clock.axaml @@ -5,15 +5,55 @@ xmlns:u="https://irihi.tech/ursa"> + - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ursa/Controls/Clock/Clock.cs b/src/Ursa/Controls/Clock/Clock.cs index 92bb777..f73b0fd 100644 --- a/src/Ursa/Controls/Clock/Clock.cs +++ b/src/Ursa/Controls/Clock/Clock.cs @@ -1,6 +1,8 @@ using Avalonia; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Media; namespace Ursa.Controls; @@ -9,10 +11,10 @@ public class Clock: TemplatedControl { public const string PART_ClockTicks = "PART_ClockTicks"; - public static readonly StyledProperty TimeProperty = AvaloniaProperty.Register( - nameof(Time)); + public static readonly StyledProperty TimeProperty = AvaloniaProperty.Register( + nameof(Time), defaultBindingMode: BindingMode.TwoWay); - public TimeSpan Time + public DateTime Time { get => GetValue(TimeProperty); set => SetValue(TimeProperty, value); @@ -36,22 +38,89 @@ public class Clock: TemplatedControl set => SetValue(ShowMinuteTicksProperty, value); } - public static readonly StyledProperty HourHandMarginProperty = AvaloniaProperty.Register( - nameof(HourHandMargin)); + public static readonly StyledProperty HandBrushProperty = AvaloniaProperty.Register( + nameof(HandBrush)); - public double HourHandMargin + public IBrush? HandBrush { - get => GetValue(HourHandMarginProperty); - set => SetValue(HourHandMarginProperty, value); + 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 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 StyledProperty MinuteHandMarginProperty = AvaloniaProperty.Register( - nameof(MinuteHandMargin)); - - public double MinuteHandMargin + public static readonly DirectProperty MinuteAngleProperty = AvaloniaProperty.RegisterDirect( + nameof(MinuteAngle), o => o.MinuteAngle); + private double _minuteAngle; + public double MinuteAngle { - get => GetValue(MinuteHandMarginProperty); - set => SetValue(MinuteHandMarginProperty, value); + 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() + { + TimeProperty.Changed.AddClassHandler((clock, args)=>clock.OnTimeChanged(args)); + } + + private void OnTimeChanged(AvaloniaPropertyChangedEventArgs args) + { + DateTime 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; + var secondAngle = 360.0 / 60 * second; + HourAngle = hourAngle; + MinuteAngle = minuteAngle; + SecondAngle = secondAngle; } protected override Size MeasureOverride(Size availableSize) From 823bb2ee8c6489db8e94ccc2d2a3adfb821f48df Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 23 Apr 2024 22:02:17 +0800 Subject: [PATCH 4/4] feat: change hand length. --- src/Ursa.Themes.Semi/Controls/Clock.axaml | 14 +++++----- .../Converters/ClockHandLengthConverter.cs | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 src/Ursa.Themes.Semi/Converters/ClockHandLengthConverter.cs diff --git a/src/Ursa.Themes.Semi/Controls/Clock.axaml b/src/Ursa.Themes.Semi/Controls/Clock.axaml index 14b554a..58809b8 100644 --- a/src/Ursa.Themes.Semi/Controls/Clock.axaml +++ b/src/Ursa.Themes.Semi/Controls/Clock.axaml @@ -1,7 +1,7 @@  @@ -18,8 +18,8 @@ @@ -29,8 +29,8 @@ @@ -40,8 +40,8 @@ diff --git a/src/Ursa.Themes.Semi/Converters/ClockHandLengthConverter.cs b/src/Ursa.Themes.Semi/Converters/ClockHandLengthConverter.cs new file mode 100644 index 0000000..6715eef --- /dev/null +++ b/src/Ursa.Themes.Semi/Converters/ClockHandLengthConverter.cs @@ -0,0 +1,27 @@ +using System.Globalization; +using Avalonia.Data.Converters; + +namespace Ursa.Themes.Semi.Converters; + +public class ClockHandLengthConverter(double ratio) : IValueConverter +{ + public static ClockHandLengthConverter Hour { get; } = new(1-0.618); + public static ClockHandLengthConverter Minute { get; } = new(0.618); + public static ClockHandLengthConverter Second { get; } = new(1); + + private double _ratio = ratio; + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is double d) + { + return d * ratio / 2; + } + return 0.0; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} \ No newline at end of file