完成 RangeTimePicker 控件

完成 RangeTimePicker 控件
This commit is contained in:
polarboy
2024-09-05 14:28:48 +08:00
parent adf578ca0a
commit 94301fc71d
6 changed files with 246 additions and 120 deletions
@@ -66,18 +66,31 @@
Title="Variants"
Description="Bordered-less style component.">
<StackPanel Orientation="Vertical" Spacing="10">
<atom:TimePicker Watermark="Filled"
Width="160"
StyleVariant="Filled"
/>
<atom:TimePicker Watermark="Select time"
Width="160"
StyleVariant="Outline"
/>
<atom:TimePicker Watermark="Select time"
Width="160"
StyleVariant="Borderless"
/>
<StackPanel Orientation="Horizontal" Spacing="5">
<atom:TimePicker Watermark="Outline"
Width="160"
StyleVariant="Outline"
/>
<atom:RangeTimePicker Width="320"
StyleVariant="Outline"
RangeStartWatermark="Outline"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<atom:TimePicker Watermark="Filled"
Width="160"
StyleVariant="Filled" />
<atom:RangeTimePicker Width="320"
StyleVariant="Outline"
RangeStartWatermark="Filled"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<atom:TimePicker Watermark="Borderless"
Width="160"
StyleVariant="Borderless" />
<atom:RangeTimePicker Width="320"
StyleVariant="Borderless"
RangeStartWatermark="Borderless"/>
</StackPanel>
</StackPanel>
</showcase:ShowCaseItem>
@@ -85,16 +98,36 @@
Title="Status"
Description="Add status to TimePicker with status, which could be error or warning.">
<StackPanel Orientation="Vertical" Spacing="10">
<atom:TimePicker Width="160"
Status="Default"
Watermark="Select time"
/>
<atom:TimePicker Width="160"
Status="Warning"
Watermark="Select time"/>
<atom:TimePicker Width="160"
Status="Error"
Watermark="Select time"/>
<StackPanel Orientation="Horizontal" Spacing="5">
<atom:TimePicker Width="160"
Status="Default"
Watermark="Select time" />
<atom:RangeTimePicker Width="320"
StyleVariant="Outline"
Status="Default"
RangeStartWatermark="Start time"
RangeEndWatermark="End time" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<atom:TimePicker Width="160"
Status="Warning"
Watermark="Select time" />
<atom:RangeTimePicker Width="320"
StyleVariant="Outline"
Status="Warning"
RangeStartWatermark="Start time"
RangeEndWatermark="End time" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<atom:TimePicker Width="160"
Status="Error"
Watermark="Select time" />
<atom:RangeTimePicker Width="320"
StyleVariant="Outline"
Status="Error"
RangeStartWatermark="Start time"
RangeEndWatermark="End time"/>
</StackPanel>
</StackPanel>
</showcase:ShowCaseItem>
@@ -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);
}
}
+131 -42
View File
@@ -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()
@@ -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<TimeSpan>());
});
}));
}
_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;
}
}
+14 -41
View File
@@ -28,36 +28,21 @@ public class TimePicker : LineEdit
public static readonly StyledProperty<PlacementMode> PickerPlacementProperty =
AvaloniaProperty.Register<TimePicker, PlacementMode>(nameof(PickerPlacement), defaultValue: PlacementMode.BottomEdgeAlignedLeft);
/// <summary>
/// 是否显示指示箭头
/// </summary>
public static readonly StyledProperty<bool> IsShowArrowProperty =
ArrowDecoratedBox.IsShowArrowProperty.AddOwner<TimePicker>();
/// <summary>
/// 箭头是否始终指向中心
/// </summary>
public static readonly StyledProperty<bool> IsPointAtCenterProperty =
Flyout.IsPointAtCenterProperty.AddOwner<TimePicker>();
/// <summary>
/// Defines the <see cref="MinuteIncrement"/> property
/// </summary>
public static readonly StyledProperty<int> MinuteIncrementProperty =
AvaloniaProperty.Register<TimePicker, int>(nameof(MinuteIncrement), 1, coerce: CoerceMinuteIncrement);
public static readonly StyledProperty<int> SecondIncrementProperty =
AvaloniaProperty.Register<TimePicker, int>(nameof(SecondIncrement), 1, coerce: CoerceSecondIncrement);
/// <summary>
/// Defines the <see cref="ClockIdentifier"/> property
/// </summary>
public static readonly StyledProperty<ClockIdentifierType> ClockIdentifierProperty =
AvaloniaProperty.Register<TimePicker, ClockIdentifierType>(nameof(ClockIdentifier), ClockIdentifierType.HourClock12);
/// <summary>
/// Defines the <see cref="SelectedTime"/> property
/// </summary>
public static readonly StyledProperty<TimeSpan?> SelectedTimeProperty =
AvaloniaProperty.Register<TimePicker, TimeSpan?>(nameof(SelectedTime),
defaultBindingMode: BindingMode.TwoWay,
@@ -76,9 +61,6 @@ public class TimePicker : LineEdit
public static readonly StyledProperty<int> MouseLeaveDelayProperty =
FlyoutStateHelper.MouseLeaveDelayProperty.AddOwner<TimePicker>();
/// <summary>
/// Gets or sets the desired placement of the popup in relation to the <see cref="PlacementTarget"/>.
/// </summary>
public PlacementMode PickerPlacement
{
get => GetValue(PickerPlacementProperty);
@@ -97,9 +79,6 @@ public class TimePicker : LineEdit
set => SetValue(IsPointAtCenterProperty, value);
}
/// <summary>
/// Gets or sets the minute increment in the picker
/// </summary>
public int MinuteIncrement
{
get => GetValue(MinuteIncrementProperty);
@@ -111,19 +90,13 @@ public class TimePicker : LineEdit
get => GetValue(SecondIncrementProperty);
set => SetValue(SecondIncrementProperty, value);
}
/// <summary>
/// Gets or sets the clock identifier, either 12HourClock or 24HourClock
/// </summary>
public ClockIdentifierType ClockIdentifier
{
get => GetValue(ClockIdentifierProperty);
set => SetValue(ClockIdentifierProperty, value);
}
/// <summary>
/// Gets or sets the selected time. Can be null.
/// </summary>
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)
@@ -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<Rectangle>(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);
}
}