From 1e5da1869cc7afd93e6e454f677d24dd7ad29170 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Fri, 26 Apr 2024 20:07:55 +0800 Subject: [PATCH] feat: implement timepicker functions. --- demo/Ursa.Demo/Pages/TimePickerDemo.axaml | 2 + .../Controls/TimePicker.axaml | 84 +++++++++-- .../Controls/DateTimePicker/TimePicker.cs | 138 ++++++++++++++++-- .../DateTimePicker/TimePickerPresenter.cs | 23 ++- 4 files changed, 221 insertions(+), 26 deletions(-) diff --git a/demo/Ursa.Demo/Pages/TimePickerDemo.axaml b/demo/Ursa.Demo/Pages/TimePickerDemo.axaml index ecdb17f..7c024c6 100644 --- a/demo/Ursa.Demo/Pages/TimePickerDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimePickerDemo.axaml @@ -8,5 +8,7 @@ + + diff --git a/src/Ursa.Themes.Semi/Controls/TimePicker.axaml b/src/Ursa.Themes.Semi/Controls/TimePicker.axaml index b6dd161..f294ab8 100644 --- a/src/Ursa.Themes.Semi/Controls/TimePicker.axaml +++ b/src/Ursa.Themes.Semi/Controls/TimePicker.axaml @@ -1,6 +1,7 @@  @@ -34,12 +35,12 @@ ShouldLoop="True" /> + VerticalAlignment="Stretch" + Fill="{DynamicResource DateTimePickerSeparatorBackground}" /> + VerticalAlignment="Stretch" + Fill="{DynamicResource DateTimePickerSeparatorBackground}" /> + VerticalAlignment="Stretch" + Fill="{DynamicResource DateTimePickerSeparatorBackground}" /> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ursa/Controls/DateTimePicker/TimePicker.cs b/src/Ursa/Controls/DateTimePicker/TimePicker.cs index 9cacca4..83632a4 100644 --- a/src/Ursa/Controls/DateTimePicker/TimePicker.cs +++ b/src/Ursa/Controls/DateTimePicker/TimePicker.cs @@ -1,14 +1,32 @@ -using Avalonia; +using System.Globalization; +using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Data; +using Avalonia.Input; +using Avalonia.Interactivity; using Irihi.Avalonia.Shared.Contracts; +using Irihi.Avalonia.Shared.Helpers; namespace Ursa.Controls; -public class TimePicker : TemplatedControl, IClearControl +[TemplatePart( PART_TextBox, typeof(TextBox))] +[TemplatePart( PART_Popup, typeof(Popup))] +[TemplatePart( PART_Presenter, typeof(TimePickerPresenter))] +public class TimePicker : TemplatedControl, IClearControl, IInnerContentControl, IPopupInnerContent { - public static readonly StyledProperty DisplayFormatProperty = AvaloniaProperty.Register( + public const string PART_TextBox = "PART_TextBox"; + public const string PART_Popup = "PART_Popup"; + public const string PART_Presenter = "PART_Presenter"; + + private TextBox? _textBox; + private Popup? _popup; + private TimePickerPresenter? _presenter; + + private bool _updateFromPresenter; + + public static readonly StyledProperty DisplayFormatProperty = AvaloniaProperty.Register( nameof(DisplayFormat), "HH:mm:ss"); public static readonly StyledProperty PanelFormatProperty = AvaloniaProperty.Register( @@ -21,8 +39,40 @@ public class TimePicker : TemplatedControl, IClearControl public static readonly StyledProperty NeedConfirmationProperty = AvaloniaProperty.Register( nameof(NeedConfirmation)); - public static readonly StyledProperty IsLoopingProperty = AvaloniaProperty.Register( - nameof(IsLooping)); + public static readonly StyledProperty InnerLeftContentProperty = + AvaloniaProperty.Register( + nameof(InnerLeftContent)); + + public static readonly StyledProperty InnerRightContentProperty = + AvaloniaProperty.Register( + nameof(InnerRightContent)); + + + public static readonly StyledProperty PopupInnerTopContentProperty = + AvaloniaProperty.Register( + nameof(PopupInnerTopContent)); + + public static readonly StyledProperty PopupInnerBottomContentProperty = + AvaloniaProperty.Register( + nameof(PopupInnerBottomContent)); + + public static readonly StyledProperty WatermarkProperty = AvaloniaProperty.Register( + nameof(Watermark)); + + public static readonly StyledProperty IsDropdownOpenProperty = AvaloniaProperty.Register( + nameof(IsDropdownOpen), defaultBindingMode: BindingMode.TwoWay); + + public bool IsDropdownOpen + { + get => GetValue(IsDropdownOpenProperty); + set => SetValue(IsDropdownOpenProperty, value); + } + + public string? Watermark + { + get => GetValue(WatermarkProperty); + set => SetValue(WatermarkProperty, value); + } private TimeSpan? _selectedTimeHolder; @@ -30,9 +80,11 @@ public class TimePicker : TemplatedControl, IClearControl { PanelFormatProperty.Changed.AddClassHandler((picker, args) => picker.OnPanelFormatChanged(args)); + SelectedTimeProperty.Changed.AddClassHandler((picker, args) => + picker.OnSelectionChanged(args)); } - public string DisplayFormat + public string? DisplayFormat { get => GetValue(DisplayFormatProperty); set => SetValue(DisplayFormatProperty, value); @@ -56,17 +108,35 @@ public class TimePicker : TemplatedControl, IClearControl set => SetValue(NeedConfirmationProperty, value); } - public bool IsLooping - { - get => GetValue(IsLoopingProperty); - set => SetValue(IsLoopingProperty, value); - } - public void Clear() { SetCurrentValue(SelectedTimeProperty, null); } + public object? InnerLeftContent + { + get => GetValue(InnerLeftContentProperty); + set => SetValue(InnerLeftContentProperty, value); + } + + public object? InnerRightContent + { + get => GetValue(InnerRightContentProperty); + set => SetValue(InnerRightContentProperty, value); + } + + public object? PopupInnerTopContent + { + get => GetValue(PopupInnerTopContentProperty); + set => SetValue(PopupInnerTopContentProperty, value); + } + + public object? PopupInnerBottomContent + { + get => GetValue(PopupInnerBottomContentProperty); + set => SetValue(PopupInnerBottomContentProperty, value); + } + private void OnPanelFormatChanged(AvaloniaPropertyChangedEventArgs args) { var format = args.NewValue.Value; @@ -77,14 +147,50 @@ public class TimePicker : TemplatedControl, IClearControl protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); + + _textBox = e.NameScope.Find(PART_TextBox); + _popup = e.NameScope.Find(PART_Popup); + _presenter = e.NameScope.Find(PART_Presenter); + TextBox.GotFocusEvent.AddHandler(OnTextBoxGetFocus, _textBox); + TextBox.TextChangedEvent.AddDisposableHandler(OnTextChanged, _textBox); + TextBox.PointerPressedEvent.AddHandler(OnTextBoxPointerPressed, RoutingStrategies.Tunnel, false, _textBox); } - private void OnSelectionChanged() + private void OnTextBoxPointerPressed(object sender, PointerPressedEventArgs e) { - if (NeedConfirmation) - _selectedTimeHolder = new TimeSpan(); + SetCurrentValue(IsDropdownOpenProperty, true); + } + + private void OnTextBoxGetFocus(object sender, GotFocusEventArgs e) + { + IsDropdownOpen = true; + } + + + private void OnTextChanged(object sender, TextChangedEventArgs e) + { + if (DisplayFormat is null || DisplayFormat.Length == 0) + { + if (TimeSpan.TryParse(_textBox?.Text, out var defaultTime)) + { + TimePickerPresenter.TimeProperty.SetValue(defaultTime, _presenter); + } + } else - SelectedTime = new TimeSpan(); + { + if(DateTime.TryParseExact(_textBox?.Text, DisplayFormat, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var time)) + { + TimePickerPresenter.TimeProperty.SetValue(time.TimeOfDay, _presenter); + } + } + } + + private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs args) + { + if (_textBox is null) return; + var time = args.NewValue.Value; + var text = new DateTime(1,1,1, time?.Hours ?? 0, time?.Minutes ?? 0, time?.Seconds ?? 0).ToString(DisplayFormat); + _textBox.Text = text; } public void Confirm() diff --git a/src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs b/src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs index a447685..c93930a 100644 --- a/src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs +++ b/src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs @@ -70,6 +70,15 @@ public class TimePickerPresenter: TemplatedControl set => SetValue(MinuteIncrementProperty, value); } + public static readonly StyledProperty SecondIncrementProperty = AvaloniaProperty.Register( + nameof(SecondIncrement)); + + public int SecondIncrement + { + get => GetValue(SecondIncrementProperty); + set => SetValue(SecondIncrementProperty, value); + } + public static readonly StyledProperty TimeProperty = AvaloniaProperty.Register( nameof(Time)); @@ -88,6 +97,8 @@ public class TimePickerPresenter: TemplatedControl set => SetValue(PanelFormatProperty, value); } + public event EventHandler? SelectedTimeChanged; + static TimePickerPresenter() { PanelFormatProperty.Changed.AddClassHandler((presenter, args) => presenter.OnPanelFormatChanged(args)); @@ -99,6 +110,8 @@ public class TimePickerPresenter: TemplatedControl _updateFromTimeChange = true; UpdatePanelsFromSelectedTime(); _updateFromTimeChange = false; + SelectedTimeChanged?.Invoke(this, + new TimePickerSelectedValueChangedEventArgs(args.OldValue.Value, args.NewValue.Value)); } private void OnPanelFormatChanged(AvaloniaPropertyChangedEventArgs args) @@ -110,7 +123,7 @@ public class TimePickerPresenter: TemplatedControl private void UpdatePanelLayout(string panelFormat) { - var parts = panelFormat.Split(' ', '-', ':'); + var parts = panelFormat.Split(new[] { ' ', '-', ':' }, StringSplitOptions.RemoveEmptyEntries); var panels = new List(); foreach (var part in parts) { @@ -305,4 +318,12 @@ public class TimePickerPresenter: TemplatedControl _ampmSelector.MinimumValue = 0; } } + + public void Confirm() + { + if (NeedsConfirmation) + { + SetCurrentValue(TimeProperty, _timeHolder); + } + } } \ No newline at end of file