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