diff --git a/demo/Ursa.Demo/Pages/DatePickerDemo.axaml b/demo/Ursa.Demo/Pages/DatePickerDemo.axaml index c42e019..28ae7c8 100644 --- a/demo/Ursa.Demo/Pages/DatePickerDemo.axaml +++ b/demo/Ursa.Demo/Pages/DatePickerDemo.axaml @@ -8,6 +8,10 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Ursa.Demo.Pages.DatePickerDemo"> + + + + diff --git a/src/Ursa/AssemblyInfo.cs b/src/Ursa/AssemblyInfo.cs index a45e3de..0fd82bc 100644 --- a/src/Ursa/AssemblyInfo.cs +++ b/src/Ursa/AssemblyInfo.cs @@ -1,5 +1,8 @@ +using System.Runtime.CompilerServices; using Avalonia.Metadata; +[assembly:InternalsVisibleTo("HeadlessTest.Ursa")] +[assembly:InternalsVisibleTo("Test.Ursa")] [assembly:XmlnsPrefix("https://irihi.tech/ursa", "u")] [assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa")] [assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls")] diff --git a/src/Ursa/Controls/DateTimePicker/CalendarContext.cs b/src/Ursa/Controls/DateTimePicker/CalendarContext.cs index f2eb957..73eab1c 100644 --- a/src/Ursa/Controls/DateTimePicker/CalendarContext.cs +++ b/src/Ursa/Controls/DateTimePicker/CalendarContext.cs @@ -1,6 +1,6 @@ namespace Ursa.Controls; -public sealed class CalendarContext(int? year = null, int? month = null, int? startYear = null, int? endYear = null): IComparable +internal 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; diff --git a/src/Ursa/Controls/DateTimePicker/CalendarView.cs b/src/Ursa/Controls/DateTimePicker/CalendarView.cs index 8d37511..6d22319 100644 --- a/src/Ursa/Controls/DateTimePicker/CalendarView.cs +++ b/src/Ursa/Controls/DateTimePicker/CalendarView.cs @@ -95,13 +95,13 @@ public class CalendarView : TemplatedControl private CalendarContext _contextDate = new(); - public static readonly DirectProperty ContextDateProperty = AvaloniaProperty.RegisterDirect( + internal static readonly DirectProperty ContextDateProperty = AvaloniaProperty.RegisterDirect( nameof(ContextDate), o => o.ContextDate, (o, v) => o.ContextDate = v); - public CalendarContext ContextDate + internal CalendarContext ContextDate { get => _contextDate; - internal set => SetAndRaise(ContextDateProperty, ref _contextDate, value); + set => SetAndRaise(ContextDateProperty, ref _contextDate, value); } public bool IsTodayHighlighted @@ -535,7 +535,7 @@ public class CalendarView : TemplatedControl IsEnabledProperty.SetValue(canNext, _nextButton, _fastNextButton); } - public void MarkDates(DateTime? startDate = null, DateTime? endDate = null, DateTime? previewStartDate = null, DateTime? previewEndDate = null) + internal void MarkDates(DateTime? startDate = null, DateTime? endDate = null, DateTime? previewStartDate = null, DateTime? previewEndDate = null) { _start = startDate; _end = endDate; diff --git a/src/Ursa/Controls/DateTimePicker/CalendarYearButton.cs b/src/Ursa/Controls/DateTimePicker/CalendarYearButton.cs index 37428ad..3e3b053 100644 --- a/src/Ursa/Controls/DateTimePicker/CalendarYearButton.cs +++ b/src/Ursa/Controls/DateTimePicker/CalendarYearButton.cs @@ -25,7 +25,7 @@ public class CalendarYearButton : ContentControl internal CalendarViewMode Mode { get; private set; } - public event EventHandler ItemSelected + public event EventHandler ItemSelected { add => AddHandler(ItemSelectedEvent, value); remove => RemoveHandler(ItemSelectedEvent, value); diff --git a/src/Ursa/Controls/DateTimePicker/DatePicker.cs b/src/Ursa/Controls/DateTimePicker/DatePicker.cs index 445ce53..03f673f 100644 --- a/src/Ursa/Controls/DateTimePicker/DatePicker.cs +++ b/src/Ursa/Controls/DateTimePicker/DatePicker.cs @@ -7,8 +7,6 @@ using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.VisualTree; -using Irihi.Avalonia.Shared.Common; using Irihi.Avalonia.Shared.Contracts; using Irihi.Avalonia.Shared.Helpers; @@ -49,6 +47,7 @@ public class DatePicker: DatePickerBase, IClearControl static DatePicker() { + FocusableProperty.OverrideDefaultValue(true); SelectedDateProperty.Changed.AddClassHandler((picker, args) => picker.OnSelectionChanged(args)); } @@ -64,7 +63,6 @@ public class DatePicker: DatePickerBase, IClearControl GotFocusEvent.RemoveHandler(OnTextBoxGetFocus, _textBox); TextBox.TextChangedEvent.RemoveHandler(OnTextChanged, _textBox); - PointerPressedEvent.RemoveHandler(OnTextBoxPointerPressed, _textBox); Button.ClickEvent.RemoveHandler(OnButtonClick, _button); CalendarView.DateSelectedEvent.RemoveHandler(OnDateSelected, _calendar); @@ -73,10 +71,9 @@ public class DatePicker: DatePickerBase, IClearControl _textBox = e.NameScope.Find(PART_TextBox); _calendar = e.NameScope.Find(PART_Calendar); - Button.ClickEvent.AddHandler(OnButtonClick, RoutingStrategies.Bubble, true, _button); + Button.ClickEvent.AddHandler(OnButtonClick, RoutingStrategies.Bubble, false, _button); GotFocusEvent.AddHandler(OnTextBoxGetFocus, _textBox); TextBox.TextChangedEvent.AddHandler(OnTextChanged, _textBox); - PointerPressedEvent.AddHandler(OnTextBoxPointerPressed, RoutingStrategies.Tunnel, false, _textBox); CalendarView.DateSelectedEvent.AddHandler(OnDateSelected, RoutingStrategies.Bubble, true, _calendar); SyncSelectedDateToText(SelectedDate); } @@ -89,19 +86,10 @@ public class DatePicker: DatePickerBase, IClearControl private void OnButtonClick(object? sender, RoutedEventArgs e) { - Focus(NavigationMethod.Pointer); - SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen); - } - - private void OnTextBoxPointerPressed(object? sender, PointerPressedEventArgs e) - { - if (_calendar is not null) + if(IsFocused) { - var date = SelectedDate ?? DateTime.Today; - _calendar.ContextDate = new CalendarContext(date.Year, date.Month); - _calendar.UpdateDayButtons(); + SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen); } - SetCurrentValue(IsDropdownOpenProperty, true); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -186,6 +174,7 @@ public class DatePicker: DatePickerBase, IClearControl e.Handled = true; } } + } protected override void OnKeyDown(KeyEventArgs e) @@ -213,4 +202,40 @@ public class DatePicker: DatePickerBase, IClearControl { SetCurrentValue(SelectedDateProperty, null); } + + protected override void OnGotFocus(GotFocusEventArgs e) + { + base.OnGotFocus(e); + FocusChanged(IsKeyboardFocusWithin); + } + + protected override void OnLostFocus(RoutedEventArgs e) + { + base.OnLostFocus(e); + FocusChanged(IsKeyboardFocusWithin); + var top = TopLevel.GetTopLevel(this); + var element = top?.FocusManager?.GetFocusedElement(); + if (element is Visual v && _popup?.IsInsidePopup(v)==true) + { + return; + } + SetCurrentValue(IsDropdownOpenProperty, false); + } + + private bool _isFocused; + private void FocusChanged(bool hasFocus) + { + bool wasFocused = _isFocused; + _isFocused = hasFocus; + + if (hasFocus) + { + + if (!wasFocused && _textBox != null) + { + _textBox.Focus(); + _textBox.SelectAll(); + } + } + } } \ No newline at end of file diff --git a/src/Ursa/Controls/DateTimePicker/DateTimeHelper.cs b/src/Ursa/Controls/DateTimePicker/DateTimeHelper.cs index 06d9ee2..8e6cd75 100644 --- a/src/Ursa/Controls/DateTimePicker/DateTimeHelper.cs +++ b/src/Ursa/Controls/DateTimePicker/DateTimeHelper.cs @@ -46,7 +46,7 @@ internal static class DateTimeHelper public static (int start, int end) GetDecadeViewRangeByYear(int year) { int start = year / 10 * 10; - return new ValueTuple(start, start + 10); + return new ValueTuple(start, start + 10 - 1); } public static (int start, int end) GetCenturyViewRangeByYear(int year) diff --git a/src/Ursa/Controls/DateTimePicker/DateTimePicker.cs b/src/Ursa/Controls/DateTimePicker/DateTimePicker.cs index 43fafbb..22793ac 100644 --- a/src/Ursa/Controls/DateTimePicker/DateTimePicker.cs +++ b/src/Ursa/Controls/DateTimePicker/DateTimePicker.cs @@ -15,7 +15,7 @@ namespace Ursa.Controls; [TemplatePart(PART_Popup, typeof(Popup))] [TemplatePart(PART_TextBox, typeof(TextBox))] [TemplatePart(PART_Calendar, typeof(CalendarView))] -[TemplatePart(PART_TimePicker, typeof(TimePicker))] +[TemplatePart(PART_TimePicker, typeof(TimePickerPresenter))] public class DateTimePicker : DatePickerBase { public const string PART_Button = "PART_Button"; @@ -76,6 +76,7 @@ public class DateTimePicker : DatePickerBase private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs args) { + if (_fromText) return; SyncSelectedDateToText(args.NewValue.Value); } @@ -85,14 +86,14 @@ public class DateTimePicker : DatePickerBase { _textBox?.SetValue(TextBox.TextProperty, null); _calendar?.ClearSelection(); - _timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, null); + _timePickerPresenter?.SyncTime(null); } else { _textBox?.SetValue(TextBox.TextProperty, date.Value.ToString(DisplayFormat ?? CultureInfo.InvariantCulture.DateTimeFormat.FullDateTimePattern)); _calendar?.MarkDates(date.Value.Date, date.Value.Date); - _timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, date.Value.TimeOfDay); + _timePickerPresenter?.SyncTime(date.Value.TimeOfDay); } } @@ -175,12 +176,14 @@ public class DateTimePicker : DatePickerBase var date = SelectedDate ?? DateTime.Now; _calendar.ContextDate = new CalendarContext(date.Year, date.Month); _calendar.UpdateDayButtons(); - _timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, SelectedDate?.TimeOfDay); + _timePickerPresenter?.SyncTime(SelectedDate?.TimeOfDay); } SetCurrentValue(IsDropdownOpenProperty, true); } + private bool _fromText = false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void OnTextChanged(object? sender, TextChangedEventArgs e) { @@ -189,11 +192,13 @@ public class DateTimePicker : DatePickerBase private void SetSelectedDate(bool fromText = false) { + var temp = _fromText; + _fromText = fromText; if (string.IsNullOrEmpty(_textBox?.Text)) { SetCurrentValue(SelectedDateProperty, null); _calendar?.ClearSelection(); - _timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, null); + _timePickerPresenter?.SyncTime(null); } else if (DisplayFormat is null || DisplayFormat.Length == 0) { @@ -201,7 +206,7 @@ public class DateTimePicker : DatePickerBase { SetCurrentValue(SelectedDateProperty, defaultTime); _calendar?.MarkDates(defaultTime.Date, defaultTime.Date); - _timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, defaultTime.TimeOfDay); + _timePickerPresenter?.SyncTime(defaultTime.TimeOfDay); } } else @@ -217,16 +222,17 @@ public class DateTimePicker : DatePickerBase } _calendar?.MarkDates(date.Date, date.Date); - _timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, date.TimeOfDay); + _timePickerPresenter?.SyncTime(date.TimeOfDay); } else { SetCurrentValue(SelectedDateProperty, null); if (!fromText) _textBox?.SetValue(TextBox.TextProperty, null); _calendar?.ClearSelection(); - _timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, null); + _timePickerPresenter?.SyncTime(null); } } + _fromText = temp; } private void OnTextBoxGetFocus(object? sender, GotFocusEventArgs e) @@ -236,7 +242,7 @@ public class DateTimePicker : DatePickerBase var date = SelectedDate ?? DateTime.Today; _calendar.ContextDate = _calendar.ContextDate.With(date.Year, date.Month); _calendar.UpdateDayButtons(); - _timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, date.TimeOfDay); + _timePickerPresenter?.SyncTime(date.TimeOfDay); } SetCurrentValue(IsDropdownOpenProperty, true); diff --git a/src/Ursa/Controls/DateTimePicker/TimePicker.cs b/src/Ursa/Controls/DateTimePicker/TimePicker.cs index 3eee8f7..ccd11e5 100644 --- a/src/Ursa/Controls/DateTimePicker/TimePicker.cs +++ b/src/Ursa/Controls/DateTimePicker/TimePicker.cs @@ -29,24 +29,22 @@ public class TimePicker : TimePickerBase, IClearControl public static readonly StyledProperty WatermarkProperty = AvaloniaProperty.Register( nameof(Watermark)); - private bool _suppressTextPresenterEvent; - private Button? _button; - private TimePickerPresenter? _presenter; - private TextBox? _textBox; + private bool _isFocused; + private Popup? _popup; + private TimePickerPresenter? _presenter; + + private bool _suppressTextPresenterEvent; + private TextBox? _textBox; static TimePicker() { + FocusableProperty.OverrideDefaultValue(true); SelectedTimeProperty.Changed.AddClassHandler((picker, args) => picker.OnSelectionChanged(args)); - DisplayFormatProperty.Changed.AddClassHandler((picker, args) => picker.OnDisplayFormatChanged(args)); - } - - private void OnDisplayFormatChanged(AvaloniaPropertyChangedEventArgs _) - { - if (_textBox is null) return; - SyncTimeToText(SelectedTime); + DisplayFormatProperty.Changed.AddClassHandler((picker, args) => + picker.OnDisplayFormatChanged(args)); } public string? Watermark @@ -63,58 +61,66 @@ public class TimePicker : TimePickerBase, IClearControl public void Clear() { + SetCurrentValue(SelectedTimeProperty, null); Focus(NavigationMethod.Pointer); - _presenter?.SetValue(TimePickerPresenter.TimeProperty, null); } + private void OnDisplayFormatChanged(AvaloniaPropertyChangedEventArgs _) + { + if (_textBox is null) return; + SyncTimeToText(SelectedTime); + } + + /// + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + base.OnPointerPressed(e); + if (!e.Handled && e.Source is Visual source) + if (_popup?.IsInsidePopup(source) == true) + e.Handled = true; + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); GotFocusEvent.RemoveHandler(OnTextBoxGetFocus, _textBox); TextBox.TextChangedEvent.RemoveHandler(OnTextChanged, _textBox); - PointerPressedEvent.RemoveHandler(OnTextBoxPointerPressed, _textBox); Button.ClickEvent.RemoveHandler(OnButtonClick, _button); TimePickerPresenter.SelectedTimeChangedEvent.RemoveHandler(OnPresenterTimeChanged, _presenter); _textBox = e.NameScope.Find(PART_TextBox); - e.NameScope.Find(PartNames.PART_Popup); + _popup = e.NameScope.Find(PartNames.PART_Popup); _presenter = e.NameScope.Find(PART_Presenter); _button = e.NameScope.Find