diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TimePickerShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TimePickerShowCase.axaml index 89a15ff95..1d8eea718 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TimePickerShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TimePickerShowCase.axaml @@ -66,18 +66,31 @@ Title="Variants" Description="Bordered-less style component."> - - - + + + + + + + + + + + + @@ -85,16 +98,36 @@ Title="Status" Description="Add status to TimePicker with status, which could be error or warning."> - - - + + + + + + + + + + + + diff --git a/src/AtomUI.Controls/TimePicker/DateTimePickerPanel.cs b/src/AtomUI.Controls/TimePicker/DateTimePickerPanel.cs index 08594b371..c3d06d2af 100644 --- a/src/AtomUI.Controls/TimePicker/DateTimePickerPanel.cs +++ b/src/AtomUI.Controls/TimePicker/DateTimePickerPanel.cs @@ -11,6 +11,7 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Utilities; using Avalonia.VisualTree; namespace AtomUI.Controls; @@ -581,7 +582,7 @@ public class DateTimePickerPanel : Panel, ILogicalScrollable { var snapY = Math.Round(Offset.Y / ItemHeight) * ItemHeight; - if (snapY != Offset.Y) { + if (!MathUtilities.AreClose(snapY, Offset.Y)) { Offset = Offset.WithY(snapY); } } diff --git a/src/AtomUI.Controls/TimePicker/RangeTimePicker.cs b/src/AtomUI.Controls/TimePicker/RangeTimePicker.cs index b2a6c792d..42940e2ea 100644 --- a/src/AtomUI.Controls/TimePicker/RangeTimePicker.cs +++ b/src/AtomUI.Controls/TimePicker/RangeTimePicker.cs @@ -1,4 +1,5 @@ -using AtomUI.Data; +using AtomUI.Controls.Utils; +using AtomUI.Data; using AtomUI.Theme.Utils; using Avalonia; using Avalonia.Animation; @@ -292,16 +293,15 @@ public class RangeTimePicker : TemplatedControl if (!IsEnabled) { return false; } - - return PositionInEditKernel(position); - } - private bool PositionInEditKernel(Point position) - { - if (_rangeStartTextBox is not null && _rangeEndTextBox is not null) { - return PositionInRangeStartTextBox(position) || - PositionInRangeEndTextBox(position); + if (PositionInRangeStartTextBox(position)) { + RangeActivatedPart = RangeActivatedPart.Start; + return true; + } else if (PositionInRangeEndTextBox(position)) { + RangeActivatedPart = RangeActivatedPart.End; + return true; } + return false; } @@ -329,7 +329,6 @@ public class RangeTimePicker : TemplatedControl targetWidth = endOffsetX - startOffsetX; var bounds = new Rect(new Point(startOffsetX, offsetY), new Size(targetWidth, targetHeight)); if (bounds.Contains(position)) { - RangeActivatedPart = RangeActivatedPart.Start; return true; } @@ -361,7 +360,6 @@ public class RangeTimePicker : TemplatedControl targetWidth = endOffsetX - startOffsetX; var bounds = new Rect(new Point(startOffsetX, offsetY), new Size(targetWidth, targetHeight)); if (bounds.Contains(position)) { - RangeActivatedPart = RangeActivatedPart.End; return true; } @@ -371,7 +369,17 @@ public class RangeTimePicker : TemplatedControl private bool ClickHideFlyoutPredicate(IPopupHostProvider hostProvider, RawPointerEventArgs args) { if (hostProvider.PopupHost != args.Root) { - if (!PositionInEditKernel(args.Position)) { + var inRangeStart = PositionInRangeStartTextBox(args.Position); + var inRangeEnd = PositionInRangeEndTextBox(args.Position); + if (inRangeStart) { + RangeActivatedPart = RangeActivatedPart.Start; + } + + if (inRangeEnd) { + RangeActivatedPart = RangeActivatedPart.End; + } + if (!inRangeStart && + !inRangeEnd) { return true; } } @@ -381,20 +389,27 @@ public class RangeTimePicker : TemplatedControl private void HandleFlyoutAboutToShow(object? sender, EventArgs args) { - _rangeStartTextBox?.Focus(); _currentValidSelected = false; } private void HandleFlyoutAboutToClose(object? sender, EventArgs args) { + if (!_currentValidSelected) { + if (RangeActivatedPart == RangeActivatedPart.Start) { + if (RangeStartSelectedTime.HasValue) { + _rangeStartTextBox!.Text = DateTimeUtils.FormatTimeSpan(RangeStartSelectedTime.Value, ClockIdentifier == ClockIdentifierType.HourClock12); + } else { + ResetRangeStartTimeValue(); + } + } else if (RangeActivatedPart == RangeActivatedPart.End) { + if (RangeEndSelectedTime.HasValue) { + _rangeEndTextBox!.Text = DateTimeUtils.FormatTimeSpan(RangeEndSelectedTime.Value, ClockIdentifier == ClockIdentifierType.HourClock12); + } else { + ResetRangeEndTimeValue(); + } + } + } RangeActivatedPart = RangeActivatedPart.None; - // if (!_currentValidSelected) { - // if (SelectedTime.HasValue) { - // Text = DateTimeUtils.FormatTimeSpan(SelectedTime.Value, ClockIdentifier == ClockIdentifierType.HourClock12); - // } else { - // ResetTimeValue(); - // } - // } } protected override Size ArrangeOverride(Size finalSize) @@ -422,8 +437,10 @@ public class RangeTimePicker : TemplatedControl _pickerClearUpButton = new PickerClearUpButton(); _pickerClearUpButton.ClearRequest += (sender, args) => { - // ResetTimeValue(); - // SelectedTime = null; + ResetRangeStartTimeValue(); + ResetRangeEndTimeValue(); + RangeStartSelectedTime = null; + RangeEndSelectedTime = null; }; InnerRightContent = _pickerClearUpButton; } @@ -491,9 +508,12 @@ public class RangeTimePicker : TemplatedControl base.OnAttachedToLogicalTree(e); BindUtils.RelayBind(this, MouseEnterDelayProperty, _flyoutStateHelper, FlyoutStateHelper.MouseEnterDelayProperty); BindUtils.RelayBind(this, MouseLeaveDelayProperty, _flyoutStateHelper, FlyoutStateHelper.MouseLeaveDelayProperty); - // if (DefaultTime is not null) { - // SelectedTime = DefaultTime; - // } + if (RangeStartDefaultTime is not null) { + RangeStartSelectedTime = RangeStartDefaultTime; + } + if (RangeEndDefaultTime is not null) { + RangeEndDefaultTime = RangeEndDefaultTime; + } } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) @@ -506,25 +526,33 @@ public class RangeTimePicker : TemplatedControl } } + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + _flyoutStateHelper.NotifyDetachedFromVisualTree(); + _clearUpButtonDetectDisposable?.Dispose(); + _clearUpButtonDetectDisposable = null; + } + private void DetectClearUpButtonState(RawInputEventArgs args) { if (IsEnabled) { if (args is RawPointerEventArgs pointerEventArgs) { - // if (_textBoxInnerBox is not null) { - // var pos = _textBoxInnerBox.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(this)!); - // if (!pos.HasValue) { - // return; - // } - // - // var bounds = new Rect(pos.Value, _textBoxInnerBox.Bounds.Size); - // if (bounds.Contains(pointerEventArgs.Position)) { - // if (SelectedTime is not null) { - // _pickerClearUpButton!.IsInClearMode = true; - // } - // } else { - // _pickerClearUpButton!.IsInClearMode = false; - // } - // } + if (_rangePickerInner is not null) { + var pos = _rangePickerInner.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(this)!); + if (!pos.HasValue) { + return; + } + + var bounds = new Rect(pos.Value, _rangePickerInner.Bounds.Size); + if (bounds.Contains(pointerEventArgs.Position)) { + if (RangeStartSelectedTime is not null || RangeEndSelectedTime is not null) { + _pickerClearUpButton!.IsInClearMode = true; + } + } else { + _pickerClearUpButton!.IsInClearMode = false; + } + } } } } @@ -535,6 +563,41 @@ public class RangeTimePicker : TemplatedControl if (change.Property == RangeActivatedPartProperty) { HandleRangeActivatedPartChanged(); } + if (VisualRoot is not null) { + if (change.Property == RangeStartSelectedTimeProperty) { + if (RangeStartSelectedTime.HasValue) { + _rangeStartTextBox!.Text = DateTimeUtils.FormatTimeSpan(RangeStartSelectedTime.Value, ClockIdentifier == ClockIdentifierType.HourClock12); + } else { + ResetRangeStartTimeValue(); + } + } else if (change.Property == RangeEndSelectedTimeProperty) { + if (RangeEndSelectedTime.HasValue) { + _rangeEndTextBox!.Text = DateTimeUtils.FormatTimeSpan(RangeEndSelectedTime.Value, ClockIdentifier == ClockIdentifierType.HourClock12); + } else { + ResetRangeEndTimeValue(); + } + } + } + } + + protected void ResetRangeStartTimeValue() + { + var targetTextBox = _rangeStartTextBox!; + if (RangeStartDefaultTime is not null) { + targetTextBox.Text = DateTimeUtils.FormatTimeSpan(RangeStartDefaultTime.Value, ClockIdentifier == ClockIdentifierType.HourClock12); + } else { + targetTextBox.Clear(); + } + } + + protected void ResetRangeEndTimeValue() + { + var targetTextBox = _rangeEndTextBox!; + if (RangeEndDefaultTime is not null) { + targetTextBox.Text = DateTimeUtils.FormatTimeSpan(RangeEndDefaultTime.Value, ClockIdentifier == ClockIdentifierType.HourClock12); + } else { + targetTextBox.Clear(); + } } protected override Size MeasureOverride(Size availableSize) @@ -557,19 +620,45 @@ public class RangeTimePicker : TemplatedControl { if (_rangeActivatedPart == RangeActivatedPart.Start) { PickerPlacement = PlacementMode.BottomEdgeAlignedLeft; + if (RangeEndSelectedTime is null) { + ResetRangeEndTimeValue(); + } + + _rangeStartTextBox!.Focus(); } else if (_rangeActivatedPart == RangeActivatedPart.End) { PickerPlacement = PlacementMode.BottomEdgeAlignedRight; + if (RangeStartSelectedTime is null) { + ResetRangeStartTimeValue(); + } + _rangeEndTextBox!.Focus(); + } else { + if (RangeStartSelectedTime is null) { + ResetRangeStartTimeValue(); + } + if (RangeEndSelectedTime is null) { + ResetRangeEndTimeValue(); + } } - SetupPickerIndicatorPosition(); } internal void NotifyConfirmed(TimeSpan value) { + _currentValidSelected = true; + if (RangeActivatedPart == RangeActivatedPart.Start) { + RangeStartSelectedTime = value; + } else if (RangeActivatedPart == RangeActivatedPart.End) { + RangeEndSelectedTime = value; + } } - internal void NotifyTemporaryTimeSelected(TimeSpan selected) + internal void NotifyTemporaryTimeSelected(TimeSpan value) { + if (RangeActivatedPart == RangeActivatedPart.Start) { + _rangeStartTextBox!.Text = DateTimeUtils.FormatTimeSpan(value, ClockIdentifier == ClockIdentifierType.HourClock12); + } else if (RangeActivatedPart == RangeActivatedPart.End) { + _rangeEndTextBox!.Text = DateTimeUtils.FormatTimeSpan(value, ClockIdentifier == ClockIdentifierType.HourClock12); + } } internal void ClosePickerFlyout() diff --git a/src/AtomUI.Controls/TimePicker/RangeTimePickerFlyoutPresenter.cs b/src/AtomUI.Controls/TimePicker/RangeTimePickerFlyoutPresenter.cs index c75d99ffc..9fc613bae 100644 --- a/src/AtomUI.Controls/TimePicker/RangeTimePickerFlyoutPresenter.cs +++ b/src/AtomUI.Controls/TimePicker/RangeTimePickerFlyoutPresenter.cs @@ -1,4 +1,5 @@ -using Avalonia; +using System.Reactive.Disposables; +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; @@ -49,7 +50,7 @@ internal class RangeTimePickerFlyoutPresenter : FlyoutPresenter internal RangeTimePicker TimePickerRef { get; set; } private TimePickerPresenter? _timePickerPresenter; - private IDisposable? _disposable; + private CompositeDisposable? _compositeDisposable; private Button? _confirmButton; private Button? _nowButton; @@ -71,9 +72,8 @@ internal class RangeTimePickerFlyoutPresenter : FlyoutPresenter { TimePickerRef.NotifyConfirmed(_timePickerPresenter.Time); }; - // if (TimePickerRef.DefaultTime is not null) { - // _timePickerPresenter.Time = TimePickerRef.DefaultTime.Value; - // } + + SetupTime(); } if (_confirmButton is not null) { @@ -84,6 +84,20 @@ internal class RangeTimePickerFlyoutPresenter : FlyoutPresenter _nowButton.Click += HandleNowButtonClicked; } } + + private void SetupTime() + { + if (TimePickerRef.RangeActivatedPart == RangeActivatedPart.Start) { + if (TimePickerRef.RangeStartSelectedTime is not null) { + _timePickerPresenter!.Time = TimePickerRef.RangeStartSelectedTime.Value; + + } + } else if (TimePickerRef.RangeActivatedPart == RangeActivatedPart.End) { + if (TimePickerRef.RangeEndSelectedTime is not null) { + _timePickerPresenter!.Time = TimePickerRef.RangeEndSelectedTime.Value; + } + } + } private void HandleNowButtonClicked(object? sender, RoutedEventArgs args) { @@ -94,24 +108,43 @@ internal class RangeTimePickerFlyoutPresenter : FlyoutPresenter private void HandleConfirmButtonClicked(object? sender, RoutedEventArgs args) { _timePickerPresenter?.Confirm(); - TimePickerRef.PickerPlacement = PlacementMode.BottomEdgeAlignedRight; - // TimePickerRef.ClosePickerFlyout(); + if (TimePickerRef.RangeActivatedPart == RangeActivatedPart.Start) { + if (TimePickerRef.RangeEndSelectedTime is null) { + TimePickerRef.RangeActivatedPart = RangeActivatedPart.End; + return; + } + } else if (TimePickerRef.RangeActivatedPart == RangeActivatedPart.End) { + if (TimePickerRef.RangeStartSelectedTime is null) { + TimePickerRef.RangeActivatedPart = RangeActivatedPart.Start; + return; + } + } + TimePickerRef.ClosePickerFlyout(); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); + _compositeDisposable = new CompositeDisposable(); if (_timePickerPresenter is not null) { - _disposable = TimePickerPresenter.TemporaryTimeProperty.Changed.Subscribe(args => + _compositeDisposable.Add(TimePickerPresenter.TemporaryTimeProperty.Changed.Subscribe(args => { TimePickerRef.NotifyTemporaryTimeSelected(args.GetNewValue()); - }); + })); } + _compositeDisposable.Add(RangeTimePicker.RangeActivatedPartProperty.Changed.Subscribe(HandleRangeActivatedPartChanged)); + SetupTime(); + } + + private void HandleRangeActivatedPartChanged(AvaloniaPropertyChangedEventArgs args) + { + SetupTime(); } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); - _disposable?.Dispose(); + _compositeDisposable?.Dispose(); + _compositeDisposable = null; } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TimePicker/TimePicker.cs b/src/AtomUI.Controls/TimePicker/TimePicker.cs index c2c44baab..95e9b41ee 100644 --- a/src/AtomUI.Controls/TimePicker/TimePicker.cs +++ b/src/AtomUI.Controls/TimePicker/TimePicker.cs @@ -28,36 +28,21 @@ public class TimePicker : LineEdit public static readonly StyledProperty PickerPlacementProperty = AvaloniaProperty.Register(nameof(PickerPlacement), defaultValue: PlacementMode.BottomEdgeAlignedLeft); - /// - /// 是否显示指示箭头 - /// public static readonly StyledProperty IsShowArrowProperty = ArrowDecoratedBox.IsShowArrowProperty.AddOwner(); - - /// - /// 箭头是否始终指向中心 - /// + public static readonly StyledProperty IsPointAtCenterProperty = Flyout.IsPointAtCenterProperty.AddOwner(); - /// - /// Defines the property - /// public static readonly StyledProperty MinuteIncrementProperty = AvaloniaProperty.Register(nameof(MinuteIncrement), 1, coerce: CoerceMinuteIncrement); public static readonly StyledProperty SecondIncrementProperty = AvaloniaProperty.Register(nameof(SecondIncrement), 1, coerce: CoerceSecondIncrement); - - /// - /// Defines the property - /// + public static readonly StyledProperty ClockIdentifierProperty = AvaloniaProperty.Register(nameof(ClockIdentifier), ClockIdentifierType.HourClock12); - /// - /// Defines the property - /// public static readonly StyledProperty SelectedTimeProperty = AvaloniaProperty.Register(nameof(SelectedTime), defaultBindingMode: BindingMode.TwoWay, @@ -76,9 +61,6 @@ public class TimePicker : LineEdit public static readonly StyledProperty MouseLeaveDelayProperty = FlyoutStateHelper.MouseLeaveDelayProperty.AddOwner(); - /// - /// Gets or sets the desired placement of the popup in relation to the . - /// public PlacementMode PickerPlacement { get => GetValue(PickerPlacementProperty); @@ -97,9 +79,6 @@ public class TimePicker : LineEdit set => SetValue(IsPointAtCenterProperty, value); } - /// - /// Gets or sets the minute increment in the picker - /// public int MinuteIncrement { get => GetValue(MinuteIncrementProperty); @@ -111,19 +90,13 @@ public class TimePicker : LineEdit get => GetValue(SecondIncrementProperty); set => SetValue(SecondIncrementProperty, value); } - - /// - /// Gets or sets the clock identifier, either 12HourClock or 24HourClock - /// + public ClockIdentifierType ClockIdentifier { get => GetValue(ClockIdentifierProperty); set => SetValue(ClockIdentifierProperty, value); } - - /// - /// Gets or sets the selected time. Can be null. - /// + public TimeSpan? SelectedTime { get => GetValue(SelectedTimeProperty); @@ -310,6 +283,14 @@ public class TimePicker : LineEdit _clearUpButtonDetectDisposable = inputManager.Process.Subscribe(DetectClearUpButtonState); } } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + _flyoutStateHelper.NotifyDetachedFromVisualTree(); + _clearUpButtonDetectDisposable?.Dispose(); + _clearUpButtonDetectDisposable = null; + } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { @@ -356,14 +337,6 @@ public class TimePicker : LineEdit } } } - - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnDetachedFromVisualTree(e); - _flyoutStateHelper.NotifyDetachedFromVisualTree(); - _clearUpButtonDetectDisposable?.Dispose(); - _clearUpButtonDetectDisposable = null; - } private static int CoerceMinuteIncrement(AvaloniaObject sender, int value) { @@ -382,9 +355,9 @@ public class TimePicker : LineEdit return value; } - internal void NotifyTemporaryTimeSelected(TimeSpan selected) + internal void NotifyTemporaryTimeSelected(TimeSpan value) { - Text = DateTimeUtils.FormatTimeSpan(selected, ClockIdentifier == ClockIdentifierType.HourClock12); + Text = DateTimeUtils.FormatTimeSpan(value, ClockIdentifier == ClockIdentifierType.HourClock12); } internal void NotifyConfirmed(TimeSpan value) diff --git a/src/AtomUI.Controls/TimePicker/TimePickerPresenter.cs b/src/AtomUI.Controls/TimePicker/TimePickerPresenter.cs index c54dc127c..36bec5ddd 100644 --- a/src/AtomUI.Controls/TimePicker/TimePickerPresenter.cs +++ b/src/AtomUI.Controls/TimePicker/TimePickerPresenter.cs @@ -144,6 +144,7 @@ public class TimePickerPresenter : PickerPresenterBase TokenResourceBinder.CreateGlobalTokenBinding(this, SpacerThicknessProperty, GlobalTokenResourceKey.LineWidth, BindingPriority.Template, new RenderScaleAwareDoubleConfigure(this)); + InitPicker(); } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) @@ -176,8 +177,6 @@ public class TimePickerPresenter : PickerPresenterBase } _spacer3 = e.NameScope.Get(TimePickerPresenterTheme.ThirdSpacerPart); - - InitPicker(); } private void HandleSelectionChanged(object? sender, EventArgs args) @@ -295,7 +294,5 @@ public class TimePickerPresenter : PickerPresenterBase _spacer3!.IsVisible = !use24HourClock; _periodHost!.IsVisible = !use24HourClock; - - _hourSelector?.Focus(NavigationMethod.Pointer); } } \ No newline at end of file