diff --git a/demo/Ursa.Demo/Pages/DatePickerDemo.axaml b/demo/Ursa.Demo/Pages/DatePickerDemo.axaml
new file mode 100644
index 0000000..65094c1
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/DatePickerDemo.axaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/demo/Ursa.Demo/Pages/DatePickerDemo.axaml.cs b/demo/Ursa.Demo/Pages/DatePickerDemo.axaml.cs
new file mode 100644
index 0000000..e38d30d
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/DatePickerDemo.axaml.cs
@@ -0,0 +1,25 @@
+using System.Diagnostics;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Ursa.Controls;
+
+namespace Ursa.Demo.Pages;
+
+public partial class DatePickerDemo : UserControl
+{
+ public DatePickerDemo()
+ {
+ InitializeComponent();
+ }
+
+ private void CalendarView_OnOnDateSelected(object? sender, CalendarDayButtonEventArgs e)
+ {
+ Debug.WriteLine("Pressed: "+ e.Date?.ToLongDateString());
+ }
+
+ private void CalendarView_OnOnDatePreviewed(object? sender, CalendarDayButtonEventArgs e)
+ {
+ Debug.WriteLine("Hovered: "+e.Date?.ToLongDateString());
+ }
+}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/ViewModels/DatePickerDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/DatePickerDemoViewModel.cs
new file mode 100644
index 0000000..f77041c
--- /dev/null
+++ b/demo/Ursa.Demo/ViewModels/DatePickerDemoViewModel.cs
@@ -0,0 +1,6 @@
+namespace Ursa.Demo.ViewModels;
+
+public class DatePickerDemoViewModel
+{
+
+}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
index 1baf568..cabccf6 100644
--- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
@@ -31,6 +31,7 @@ public class MainViewViewModel : ViewModelBase
MenuKeys.MenuKeyBreadcrumb => new BreadcrumbDemoViewModel(),
MenuKeys.MenuKeyClassInput => new ClassInputDemoViewModel(),
MenuKeys.MenuKeyClock => new ClockDemoViewModel(),
+ MenuKeys.MenuKeyDatePicker => new DatePickerDemoViewModel(),
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 e196a85..9734638 100644
--- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
@@ -18,6 +18,7 @@ public class MenuViewModel: ViewModelBase
new() { MenuHeader = "Button Group", Key = MenuKeys.MenuKeyButtonGroup },
new() { MenuHeader = "Class Input", Key = MenuKeys.MenuKeyClassInput },
new() { MenuHeader = "Clock", Key = MenuKeys.MenuKeyClock, Status = "New" },
+ new() { MenuHeader = "Date Picker", Key = MenuKeys.MenuKeyDatePicker },
new() { MenuHeader = "Dialog", Key = MenuKeys.MenuKeyDialog },
new() { MenuHeader = "Disable Container", Key = MenuKeys.MenuKeyDisableContainer },
new() { MenuHeader = "Divider", Key = MenuKeys.MenuKeyDivider },
@@ -65,6 +66,7 @@ public static class MenuKeys
public const string MenuKeyBreadcrumb= "Breadcrumb";
public const string MenuKeyClassInput = "Class Input";
public const string MenuKeyClock = "Clock";
+ public const string MenuKeyDatePicker = "DatePicker";
public const string MenuKeyDialog = "Dialog";
public const string MenuKeyDivider = "Divider";
public const string MenuKeyDisableContainer = "DisableContainer";
diff --git a/src/Ursa.Themes.Semi/Controls/Calendar.axaml b/src/Ursa.Themes.Semi/Controls/Calendar.axaml
new file mode 100644
index 0000000..8cc7683
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Controls/Calendar.axaml
@@ -0,0 +1,291 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Ursa.Themes.Semi/Controls/DatePicker.axaml b/src/Ursa.Themes.Semi/Controls/DatePicker.axaml
new file mode 100644
index 0000000..05c1357
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Controls/DatePicker.axaml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ursa.Themes.Semi/Controls/DateRangePicker.axaml b/src/Ursa.Themes.Semi/Controls/DateRangePicker.axaml
new file mode 100644
index 0000000..019c573
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Controls/DateRangePicker.axaml
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml
index 4d7ae82..95a65ca 100644
--- a/src/Ursa.Themes.Semi/Controls/_index.axaml
+++ b/src/Ursa.Themes.Semi/Controls/_index.axaml
@@ -5,8 +5,11 @@
+
+
+
diff --git a/src/Ursa.Themes.Semi/Themes/Dark/DatePicker.axaml b/src/Ursa.Themes.Semi/Themes/Dark/DatePicker.axaml
new file mode 100644
index 0000000..663edbd
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Themes/Dark/DatePicker.axaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Ursa.Themes.Semi/Themes/Dark/_index.axaml b/src/Ursa.Themes.Semi/Themes/Dark/_index.axaml
index 86727f2..05efaed 100644
--- a/src/Ursa.Themes.Semi/Themes/Dark/_index.axaml
+++ b/src/Ursa.Themes.Semi/Themes/Dark/_index.axaml
@@ -1,9 +1,9 @@
-
+
@@ -16,6 +16,6 @@
-
+
-
+
\ No newline at end of file
diff --git a/src/Ursa.Themes.Semi/Themes/Light/DatePicker.axaml b/src/Ursa.Themes.Semi/Themes/Light/DatePicker.axaml
new file mode 100644
index 0000000..23fad7a
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Themes/Light/DatePicker.axaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Ursa.Themes.Semi/Themes/Light/_index.axaml b/src/Ursa.Themes.Semi/Themes/Light/_index.axaml
index 86727f2..05efaed 100644
--- a/src/Ursa.Themes.Semi/Themes/Light/_index.axaml
+++ b/src/Ursa.Themes.Semi/Themes/Light/_index.axaml
@@ -1,9 +1,9 @@
-
+
@@ -16,6 +16,6 @@
-
+
-
+
\ No newline at end of file
diff --git a/src/Ursa.Themes.Semi/Themes/Shared/DatePicker.axaml b/src/Ursa.Themes.Semi/Themes/Shared/DatePicker.axaml
new file mode 100644
index 0000000..0cfcfe6
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Themes/Shared/DatePicker.axaml
@@ -0,0 +1,30 @@
+
+
+ 32
+ 32
+ 0 2
+ 1
+ 3
+
+ 0
+ 0
+
+ 3 0 0 3
+ 0 3 3 0
+
+
+ 32
+ 32
+ 0 2
+ 1
+ 3
+ 0
+
+
+ 260
+ 260
+ 12
+ 12
+ M12.6185 4.39653C13.1272 4.92524 13.1272 5.78245 12.6185 6.31116L7.14483 12L12.6185 17.6888C13.1272 18.2176 13.1272 19.0748 12.6185 19.6035C12.1098 20.1322 11.285 20.1322 10.7763 19.6035L4.38153 12.9573C3.87282 12.4286 3.87282 11.5714 4.38153 11.0427L10.7763 4.39653C11.285 3.86782 12.1098 3.86782 12.6185 4.39653Z M19.6185 4.39653C20.1272 4.92524 20.1272 5.78245 19.6185 6.31116L14.1448 12L19.6185 17.6888C20.1272 18.2176 20.1272 19.0748 19.6185 19.6035C19.1098 20.1322 18.285 20.1322 17.7763 19.6035L11.3815 12.9573C10.8728 12.4286 10.8728 11.5714 11.3815 11.0427L17.7763 4.39653C18.285 3.86782 19.1098 3.86782 19.6185 4.39653Z
+ M4.38153 4.39653C4.89024 3.86782 5.71502 3.86782 6.22373 4.39653L12.6185 11.0427C13.1272 11.5714 13.1272 12.4286 12.6185 12.9573L6.22373 19.6035C5.71502 20.1322 4.89024 20.1322 4.38153 19.6035C3.87282 19.0748 3.87282 18.2176 4.38153 17.6888L9.85517 12L4.38153 6.31116C3.87282 5.78245 3.87282 4.92524 4.38153 4.39653Z M11.3815 4.39653C11.8902 3.86782 12.715 3.86782 13.2237 4.39653L19.6185 11.0427C20.1272 11.5714 20.1272 12.4286 19.6185 12.9573L13.2237 19.6035C12.715 20.1322 11.8902 20.1322 11.3815 19.6035C10.8728 19.0748 10.8728 18.2176 11.3815 17.6888L16.8552 12L11.3815 6.31116C10.8728 5.78245 10.8728 4.92524 11.3815 4.39653Z
+
\ No newline at end of file
diff --git a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml
index 5350d09..68ac463 100644
--- a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml
+++ b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml
@@ -1,9 +1,9 @@
-
+
@@ -19,6 +19,6 @@
-
+
-
+
\ No newline at end of file
diff --git a/src/Ursa/Controls/DateTimePicker/CalendarContext.cs b/src/Ursa/Controls/DateTimePicker/CalendarContext.cs
new file mode 100644
index 0000000..f2eb957
--- /dev/null
+++ b/src/Ursa/Controls/DateTimePicker/CalendarContext.cs
@@ -0,0 +1,91 @@
+namespace Ursa.Controls;
+
+public sealed class CalendarContext(int? year = null, int? month = null, int? startYear = null, int? endYear = null): IComparable
+{
+ public int? Year { get; } = year;
+ public int? Month { get; } = month;
+ public int? StartYear { get; } = startYear;
+ public int? EndYear { get; } = endYear;
+
+
+ public CalendarContext Clone()
+ {
+ return new CalendarContext(Year, Month, StartYear, EndYear);
+ }
+
+ public static CalendarContext Today()
+ {
+ return new CalendarContext(DateTime.Today.Year, DateTime.Today.Month);
+ }
+
+ public CalendarContext With(int? year = null, int? month = null, int? startYear = null, int? endYear = null)
+ {
+ return new CalendarContext(year ?? Year, month ?? Month, startYear ?? StartYear, endYear ?? EndYear);
+ }
+
+ public CalendarContext NextMonth()
+ {
+ var year = Year;
+ var month = Month;
+ if (month == 12)
+ {
+ year++;
+ month = 1;
+ }
+ else
+ {
+ month++;
+ }
+
+ if (month is null)
+ {
+ month = 1;
+ }
+ return new CalendarContext(year, month, StartYear, EndYear);
+ }
+
+ public CalendarContext PreviousMonth()
+ {
+ var year = Year;
+ var month = Month;
+ if (month == 1)
+ {
+ year--;
+ month = 12;
+ }
+ else
+ {
+ month--;
+ }
+ if (month is null)
+ {
+ month = 1;
+ }
+ return new CalendarContext(year, month, StartYear, EndYear);
+ }
+
+ public CalendarContext NextYear()
+ {
+ return new CalendarContext(Year + 1, Month, StartYear, EndYear);
+ }
+
+ public CalendarContext PreviousYear()
+ {
+ return new CalendarContext(Year - 1, Month, StartYear, EndYear);
+ }
+
+ public int CompareTo(CalendarContext? other)
+ {
+ if (ReferenceEquals(this, other)) return 0;
+ if (ReferenceEquals(null, other)) return 1;
+ var yearComparison = Nullable.Compare(Year, other.Year);
+ if (yearComparison != 0) return yearComparison;
+ return Nullable.Compare(Month, other.Month);
+ }
+
+ public override string ToString()
+ {
+ return
+ $"Start: {StartYear?.ToString() ?? "null"}, End: {EndYear?.ToString() ?? "null"}, Year: {Year?.ToString() ?? "null"}, Month: {Month?.ToString() ?? "null"}";
+ }
+}
\ No newline at end of file
diff --git a/src/Ursa/Controls/DateTimePicker/CalendarDayButton.cs b/src/Ursa/Controls/DateTimePicker/CalendarDayButton.cs
new file mode 100644
index 0000000..cec8c74
--- /dev/null
+++ b/src/Ursa/Controls/DateTimePicker/CalendarDayButton.cs
@@ -0,0 +1,205 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Mixins;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Irihi.Avalonia.Shared.Common;
+
+namespace Ursa.Controls;
+
+[PseudoClasses(PseudoClassName.PC_Pressed, PseudoClassName.PC_Selected,
+ PC_StartDate, PC_EndDate, PC_PreviewStartDate, PC_PreviewEndDate, PC_InRange, PC_Today, PC_Blackout,
+ PC_NotCurrentMonth)]
+public class CalendarDayButton : ContentControl
+{
+ public const string PC_StartDate = ":start-date";
+ public const string PC_EndDate = ":end-date";
+ public const string PC_PreviewStartDate = ":preview-start-date";
+ public const string PC_PreviewEndDate = ":preview-end-date";
+ public const string PC_InRange = ":in-range";
+ public const string PC_Today = ":today";
+ public const string PC_NotCurrentMonth = ":not-current-month";
+ public const string PC_Blackout = ":blackout";
+
+ private static HashSet _pseudoClasses =
+ [
+ PseudoClassName.PC_Selected, PC_StartDate, PC_EndDate, PC_PreviewStartDate, PC_PreviewEndDate, PC_InRange
+ ];
+
+ public static readonly RoutedEvent DateSelectedEvent =
+ RoutedEvent.Register(
+ nameof(DateSelected), RoutingStrategies.Bubble);
+
+ public static readonly RoutedEvent DatePreviewedEvent =
+ RoutedEvent.Register(
+ nameof(DatePreviewed), RoutingStrategies.Bubble);
+
+ private bool _isBlackout;
+
+ private bool _isEndDate;
+
+ private bool _isInRange;
+
+ private bool _isNotCurrentMonth;
+
+ private bool _isPreviewEndDate;
+
+ private bool _isPreviewStartDate;
+
+ private bool _isSelected;
+
+ private bool _isStartDate;
+
+ private bool _isToday;
+
+ static CalendarDayButton()
+ {
+ PressedMixin.Attach();
+ }
+
+ // internal CalendarDisplayControl? Owner { get; set; }
+
+ public bool IsToday
+ {
+ get => _isToday;
+ set
+ {
+ _isToday = value;
+ PseudoClasses.Set(PC_Today, value);
+ }
+ }
+
+ public bool IsStartDate
+ {
+ get => _isStartDate;
+ set
+ {
+ _isStartDate = value;
+ SetPseudoClass(PC_StartDate, value);
+ }
+ }
+
+ public bool IsEndDate
+ {
+ get => _isEndDate;
+ set
+ {
+ _isEndDate = value;
+ SetPseudoClass(PC_EndDate, value);
+ }
+ }
+
+ public bool IsPreviewStartDate
+ {
+ get => _isPreviewStartDate;
+ set
+ {
+ _isPreviewStartDate = value;
+ SetPseudoClass(PC_PreviewStartDate, value);
+ }
+ }
+
+ public bool IsPreviewEndDate
+ {
+ get => _isPreviewEndDate;
+ set
+ {
+ _isPreviewEndDate = value;
+ SetPseudoClass(PC_PreviewEndDate, value);
+ }
+ }
+
+ public bool IsInRange
+ {
+ get => _isInRange;
+ set
+ {
+ _isInRange = value;
+ SetPseudoClass(PC_InRange, value);
+ }
+ }
+
+ public bool IsSelected
+ {
+ get => _isSelected;
+ set
+ {
+ _isSelected = value;
+ SetPseudoClass(PseudoClassName.PC_Selected, value);
+ }
+ }
+
+ ///
+ /// Notice: IsBlackout is not equivalent to not IsEnabled. Blackout dates still react to pointerover actions.
+ ///
+ public bool IsBlackout
+ {
+ get => _isBlackout;
+ set
+ {
+ _isBlackout = value;
+ PseudoClasses.Set(PC_Blackout, value);
+ }
+ }
+
+ ///
+ /// Notice: IsNotCurrentMonth is not equivalent to not IsEnabled. Not current month dates still react to pointerover
+ /// and press action.
+ ///
+ public bool IsNotCurrentMonth
+ {
+ get => _isNotCurrentMonth;
+ set
+ {
+ _isNotCurrentMonth = value;
+ PseudoClasses.Set(PC_NotCurrentMonth, value);
+ }
+ }
+
+ public event EventHandler DateSelected
+ {
+ add => AddHandler(DateSelectedEvent, value);
+ remove => RemoveHandler(DateSelectedEvent, value);
+ }
+
+ public event EventHandler DatePreviewed
+ {
+ add => AddHandler(DateSelectedEvent, value);
+ remove => RemoveHandler(DateSelectedEvent, value);
+ }
+
+ protected override void OnPointerReleased(PointerReleasedEventArgs e)
+ {
+ base.OnPointerReleased(e);
+ if (DataContext is DateTime d)
+ RaiseEvent(new CalendarDayButtonEventArgs(d) { RoutedEvent = DateSelectedEvent, Source = this });
+ }
+
+ protected override void OnPointerEntered(PointerEventArgs e)
+ {
+ base.OnPointerEntered(e);
+ if (DataContext is DateTime d)
+ RaiseEvent(new CalendarDayButtonEventArgs(d) { RoutedEvent = DatePreviewedEvent, Source = this });
+ }
+
+ internal void ResetSelection()
+ {
+ foreach (var pc in _pseudoClasses)
+ {
+ PseudoClasses.Set(pc, false);
+ }
+ }
+
+ private void SetPseudoClass(string s, bool value)
+ {
+ if (_pseudoClasses.Contains(s) && value)
+ {
+ foreach (var pc in _pseudoClasses)
+ {
+ PseudoClasses.Set(pc, false);
+ }
+ }
+ PseudoClasses.Set(s, value);
+ }
+}
\ No newline at end of file
diff --git a/src/Ursa/Controls/DateTimePicker/CalendarDayButtonEventArgs.cs b/src/Ursa/Controls/DateTimePicker/CalendarDayButtonEventArgs.cs
new file mode 100644
index 0000000..cc8fc8d
--- /dev/null
+++ b/src/Ursa/Controls/DateTimePicker/CalendarDayButtonEventArgs.cs
@@ -0,0 +1,8 @@
+using Avalonia.Interactivity;
+
+namespace Ursa.Controls;
+
+public class CalendarDayButtonEventArgs(DateTime? date) : RoutedEventArgs
+{
+ public DateTime? Date { get; private set; } = date;
+}
\ No newline at end of file
diff --git a/src/Ursa/Controls/DateTimePicker/CalendarView.cs b/src/Ursa/Controls/DateTimePicker/CalendarView.cs
new file mode 100644
index 0000000..290fc7f
--- /dev/null
+++ b/src/Ursa/Controls/DateTimePicker/CalendarView.cs
@@ -0,0 +1,617 @@
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Irihi.Avalonia.Shared.Helpers;
+using Calendar = System.Globalization.Calendar;
+
+namespace Ursa.Controls;
+
+[TemplatePart(PART_FastNextButton, typeof(Button))]
+[TemplatePart(PART_FastPreviousButton, typeof(Button))]
+[TemplatePart(PART_NextButton, typeof(Button))]
+[TemplatePart(PART_PreviousButton, typeof(Button))]
+[TemplatePart(PART_YearButton, typeof(Button))]
+[TemplatePart(PART_MonthButton, typeof(Button))]
+[TemplatePart(PART_HeaderButton, typeof(Button))]
+[TemplatePart(PART_MonthGrid, typeof(Grid))]
+[TemplatePart(PART_YearGrid, typeof(Grid))]
+[PseudoClasses(PC_Month)]
+public class CalendarView : TemplatedControl
+{
+ public const string PART_FastNextButton = "PART_FastNextButton";
+ public const string PART_FastPreviousButton = "PART_FastPreviousButton";
+ public const string PART_NextButton = "PART_NextButton";
+ public const string PART_PreviousButton = "PART_PreviousButton";
+ public const string PART_YearButton = "PART_YearButton";
+ public const string PART_MonthButton = "PART_MonthButton";
+ public const string PART_HeaderButton = "PART_HeaderButton";
+ public const string PART_MonthGrid = "PART_MonthGrid";
+ public const string PART_YearGrid = "PART_YearGrid";
+ public const string PC_Month = ":month";
+
+ private const string ShortestDayName = "ShortestDayName";
+
+ internal static readonly DirectProperty ModeProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(Mode), o => o.Mode, (o, v) => o.Mode = v);
+
+ public static readonly StyledProperty IsTodayHighlightedProperty =
+ DatePickerBase.IsTodayHighlightedProperty.AddOwner();
+
+ public static readonly StyledProperty FirstDayOfWeekProperty =
+ DatePickerBase.FirstDayOfWeekProperty.AddOwner();
+
+ private readonly Calendar _calendar = new GregorianCalendar();
+ private Button? _fastNextButton;
+
+ private Button? _fastPreviousButton;
+
+ private Button? _headerButton;
+
+ private CalendarViewMode _mode;
+ private Button? _monthButton;
+ private Grid? _monthGrid;
+ private Button? _nextButton;
+ private Button? _previousButton;
+ private Button? _yearButton;
+ private Grid? _yearGrid;
+
+ private DateTime? _start;
+ private DateTime? _end;
+ private DateTime? _previewStart;
+ private DateTime? _previewEnd;
+
+ static CalendarView()
+ {
+ FirstDayOfWeekProperty.Changed.AddClassHandler((view, args) =>
+ view.OnFirstDayOfWeekChanged(args));
+ ModeProperty.Changed.AddClassHandler((view, args) =>
+ {
+ view.PseudoClasses.Set(PC_Month, args.NewValue.Value == CalendarViewMode.Month);
+ });
+ ContextDateProperty.Changed.AddClassHandler((view, args) =>
+ view.OnContextDateChanged(args));
+ }
+
+ private void OnContextDateChanged(AvaloniaPropertyChangedEventArgs args)
+ {
+ Debug.WriteLine(this.Name + " " + args.NewValue.Value);
+ if (!_dateContextSyncing)
+ {
+ ContextDateChanged?.Invoke(this, args.NewValue.Value);
+ }
+ //UpdateDayButtons();
+ //UpdateYearButtons();
+ }
+
+ internal CalendarViewMode Mode
+ {
+ get => _mode;
+ set => SetAndRaise(ModeProperty, ref _mode, value);
+ }
+
+ private CalendarContext _contextDate = new();
+
+ public static readonly DirectProperty ContextDateProperty = AvaloniaProperty.RegisterDirect(
+ nameof(ContextDate), o => o.ContextDate, (o, v) => o.ContextDate = v);
+
+ public CalendarContext ContextDate
+ {
+ get => _contextDate;
+ internal set => SetAndRaise(ContextDateProperty, ref _contextDate, value);
+ }
+
+ public bool IsTodayHighlighted
+ {
+ get => GetValue(IsTodayHighlightedProperty);
+ set => SetValue(IsTodayHighlightedProperty, value);
+ }
+
+ public DayOfWeek FirstDayOfWeek
+ {
+ get => GetValue(FirstDayOfWeekProperty);
+ set => SetValue(FirstDayOfWeekProperty, value);
+ }
+
+ public event EventHandler? DateSelected;
+ public event EventHandler? DatePreviewed;
+ internal event EventHandler? ContextDateChanged;
+
+ private void OnFirstDayOfWeekChanged(AvaloniaPropertyChangedEventArgs args)
+ {
+ UpdateMonthViewHeader(args.NewValue.Value);
+ UpdateDayButtons();
+ }
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+
+ Button.ClickEvent.RemoveHandler(OnHeaderYearButtonClick, _yearButton);
+ Button.ClickEvent.RemoveHandler(OnHeaderMonthButtonClick, _monthButton);
+ Button.ClickEvent.RemoveHandler(OnHeaderButtonClick, _headerButton);
+ Button.ClickEvent.RemoveHandler(OnFastPrevious, _fastPreviousButton);
+ Button.ClickEvent.RemoveHandler(OnPrevious, _previousButton);
+ Button.ClickEvent.RemoveHandler(OnNext, _nextButton);
+ Button.ClickEvent.RemoveHandler(OnFastNext, _fastNextButton);
+
+ _monthGrid = e.NameScope.Find(PART_MonthGrid);
+ _yearGrid = e.NameScope.Find(PART_YearGrid);
+ _yearButton = e.NameScope.Find