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