Complete DatePicker with time selector

Complete DatePicker with time selector
This commit is contained in:
polarboy 2024-09-15 10:13:58 +08:00
parent ac38916e2e
commit fba1c3117f
15 changed files with 308 additions and 134 deletions

View File

@ -8,22 +8,22 @@
xmlns:atom="https://atomui.net"
mc:Ignorable="d">
<desktop:ShowCasePanel>
<desktop:ShowCaseItem
Title="Basic"
Description="Click DatePicker, and then we could select or input a date in panel.">
<atom:DatePicker Watermark="Select date"/>
</desktop:ShowCaseItem>
<!-- <desktop:ShowCaseItem -->
<!-- Title="Basic" -->
<!-- Description="Click DatePicker, and then we could select or input a date in panel."> -->
<!-- <atom:DatePicker Watermark="Select date"/> -->
<!-- </desktop:ShowCaseItem> -->
<desktop:ShowCaseItem
Title="With Time Picker"
Description="Date selection panel with time selection.">
<atom:DatePicker Watermark="Select date" IsShowTime="True"/>
</desktop:ShowCaseItem>
<desktop:ShowCaseItem
Title="Need Confirm"
Description="DatePicker will automatically determine whether to show a confirm button according to the picker property. You can also set the needConfirm property to determine whether to show a confirm button. When needConfirm is set, the user must click the confirm button to complete the selection. Otherwise, the selection will be submitted when the picker loses focus or selects a date.">
<atom:DatePicker Watermark="Select date" IsNeedConfirm="True"/>
<atom:DatePicker Watermark="Select date" IsShowTime="True" IsNeedConfirm="True" ClockIdentifier="HourClock12"/>
</desktop:ShowCaseItem>
<!-- -->
<!-- <desktop:ShowCaseItem -->
<!-- Title="Need Confirm" -->
<!-- Description="DatePicker will automatically determine whether to show a confirm button according to the picker property. You can also set the needConfirm property to determine whether to show a confirm button. When needConfirm is set, the user must click the confirm button to complete the selection. Otherwise, the selection will be submitted when the picker loses focus or selects a date."> -->
<!-- <atom:DatePicker Watermark="Select date" IsNeedConfirm="True"/> -->
<!-- </desktop:ShowCaseItem> -->
</desktop:ShowCasePanel>
</UserControl>

View File

@ -961,29 +961,13 @@ public class Calendar : TemplatedControl
base.OnPointerWheelChanged(e);
if (!e.Handled)
{
CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out var ctrl, out var shift);
if (!ctrl)
if (e.Delta.Y > 0)
{
if (e.Delta.Y > 0)
{
ProcessPageUpKey(false);
}
else
{
ProcessPageDownKey(false);
}
OnNextMonthClick();
}
else
{
if (e.Delta.Y > 0)
{
ProcessDownKey(ctrl, shift);
}
else
{
ProcessUpKey(ctrl, shift);
}
OnPreviousMonthClick();
}
e.Handled = true;

View File

@ -1,6 +1,9 @@
using AtomUI.Controls.CalendarView;
using System.Globalization;
using AtomUI.Controls.CalendarView;
using AtomUI.Controls.Internal;
using AtomUI.Controls.TimePickerLang;
using AtomUI.Data;
using AtomUI.Theme.Data;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
@ -30,6 +33,9 @@ public class DatePicker : InfoPickerInput
public static readonly StyledProperty<bool> IsShowNowProperty =
AvaloniaProperty.Register<DatePicker, bool>(nameof(IsShowNow), true);
public static readonly StyledProperty<ClockIdentifierType> ClockIdentifierProperty =
TimePicker.ClockIdentifierProperty.AddOwner<DatePicker>();
public DateTime? SelectedDateTime
{
@ -67,6 +73,12 @@ public class DatePicker : InfoPickerInput
set => SetValue(IsShowNowProperty, value);
}
public ClockIdentifierType ClockIdentifier
{
get => GetValue(ClockIdentifierProperty);
set => SetValue(ClockIdentifierProperty, value);
}
#endregion
private DatePickerPresenter? _pickerPresenter;
@ -88,7 +100,7 @@ public class DatePicker : InfoPickerInput
SelectedDateTime = DefaultDateTime;
}
private string EffectiveFormat()
private string GetEffectiveFormat()
{
if (Format is not null)
{
@ -98,12 +110,39 @@ public class DatePicker : InfoPickerInput
var format = "yyyy-MM-dd";
if (IsShowTime)
{
format = $"{format} HH:mm:ss";
if (ClockIdentifier == ClockIdentifierType.HourClock12)
{
format = $"{format} hh:mm:ss tt";
}
else
{
format = $"{format} HH:mm:ss";
}
}
return format;
}
public string FormatDateTime(DateTime? dateTime)
{
if (dateTime is null)
{
return string.Empty;
}
var format = GetEffectiveFormat();
if (ClockIdentifier == ClockIdentifierType.HourClock12)
{
var formatInfo = new DateTimeFormatInfo();
formatInfo.AMDesignator = LanguageResourceBinder.GetLangResource(TimePickerLangResourceKey.AMText)!;
formatInfo.PMDesignator = LanguageResourceBinder.GetLangResource(TimePickerLangResourceKey.PMText)!;
return dateTime.Value.ToString(format, formatInfo);
}
return dateTime.Value.ToString(format);
}
protected override Flyout CreatePickerFlyout()
{
return new DatePickerFlyout();
@ -132,6 +171,7 @@ public class DatePicker : InfoPickerInput
BindUtils.RelayBind(this, IsNeedConfirmProperty, presenter, DatePickerPresenter.IsNeedConfirmProperty);
BindUtils.RelayBind(this, IsShowNowProperty, presenter, DatePickerPresenter.IsShowNowProperty);
BindUtils.RelayBind(this, IsShowTimeProperty, presenter, DatePickerPresenter.IsShowTimeProperty);
BindUtils.RelayBind(this, ClockIdentifierProperty, presenter, DatePickerPresenter.ClockIdentifierProperty);
}
protected override void NotifyFlyoutOpened()
@ -170,7 +210,7 @@ public class DatePicker : InfoPickerInput
{
if (args.Value.HasValue)
{
Text = args.Value.Value.ToString(EffectiveFormat());
Text = FormatDateTime(args.Value);
}
else
{
@ -188,7 +228,7 @@ public class DatePicker : InfoPickerInput
{
DateTime? targetValue = default;
targetValue = SelectedDateTime;
Text = targetValue?.ToString(EffectiveFormat());
Text = FormatDateTime(targetValue);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@ -198,7 +238,7 @@ public class DatePicker : InfoPickerInput
{
SelectedDateTime = DefaultDateTime;
}
Text = SelectedDateTime?.ToString(EffectiveFormat());
Text = FormatDateTime(SelectedDateTime);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
@ -206,7 +246,7 @@ public class DatePicker : InfoPickerInput
base.OnPropertyChanged(change);
if (change.Property == SelectedDateTimeProperty)
{
Text = SelectedDateTime?.ToString(EffectiveFormat());
Text = FormatDateTime(SelectedDateTime);
}
}

View File

@ -1,4 +1,5 @@
using AtomUI.Controls.CalendarView;
using System.Reactive.Disposables;
using AtomUI.Controls.CalendarView;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
@ -37,6 +38,9 @@ internal class DatePickerPresenter : PickerPresenterBase
public static readonly StyledProperty<DateTime?> SelectedDateTimeProperty =
DatePicker.SelectedDateTimeProperty.AddOwner<DatePickerPresenter>();
public static readonly StyledProperty<ClockIdentifierType> ClockIdentifierProperty =
TimePicker.ClockIdentifierProperty.AddOwner<DatePickerPresenter>();
public bool IsNeedConfirm
{
get => GetValue(IsNeedConfirmProperty);
@ -60,6 +64,12 @@ internal class DatePickerPresenter : PickerPresenterBase
get => GetValue(SelectedDateTimeProperty);
set => SetValue(SelectedDateTimeProperty, value);
}
public ClockIdentifierType ClockIdentifier
{
get => GetValue(ClockIdentifierProperty);
set => SetValue(ClockIdentifierProperty, value);
}
#endregion
@ -69,6 +79,9 @@ internal class DatePickerPresenter : PickerPresenterBase
AvaloniaProperty.RegisterDirect<DatePickerPresenter, bool>(nameof(ButtonsPanelVisible),
o => o.ButtonsPanelVisible,
(o, v) => o.ButtonsPanelVisible = v);
public static readonly StyledProperty<TimeSpan?> TempSelectedTimeProperty =
AvaloniaProperty.Register<DatePickerPresenter, TimeSpan?>(nameof(TempSelectedTime));
private bool _buttonsPanelVisible = true;
internal bool ButtonsPanelVisible
@ -76,6 +89,12 @@ internal class DatePickerPresenter : PickerPresenterBase
get => _buttonsPanelVisible;
set => SetAndRaise(ButtonsPanelVisibleProperty, ref _buttonsPanelVisible, value);
}
public TimeSpan? TempSelectedTime
{
get => GetValue(TempSelectedTimeProperty);
set => SetValue(TempSelectedTimeProperty, value);
}
#endregion
@ -97,27 +116,38 @@ internal class DatePickerPresenter : PickerPresenterBase
private Button? _todayButton;
private Button? _confirmButton;
private PickerCalendar? _calendarView;
private IDisposable? _choosingStateDisposable;
private CompositeDisposable? _compositeDisposable;
private TimeView? _timeView;
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
TokenResourceBinder.CreateGlobalTokenBinding(this, BorderThicknessProperty, GlobalTokenResourceKey.BorderThickness, BindingPriority.Template,
new RenderScaleAwareThicknessConfigure(this, thickness => new Thickness(0, thickness.Top, 0, 0)));
_compositeDisposable = new CompositeDisposable();
if (_calendarView is not null)
{
_choosingStateDisposable = PickerCalendar.IsPointerInMonthViewProperty.Changed.Subscribe(args =>
_compositeDisposable.Add(PickerCalendar.IsPointerInMonthViewProperty.Changed.Subscribe(args =>
{
ChoosingStatueChanged?.Invoke(this, new ChoosingStatusEventArgs(args.GetNewValue<bool>()));
});
}));
}
if (_timeView is not null)
{
_compositeDisposable.Add(TimeView.IsPointerInSelectorProperty.Changed.Subscribe(args =>
{
ChoosingStatueChanged?.Invoke(this, new ChoosingStatusEventArgs(args.GetNewValue<bool>()));
}));
SyncTimeViewTimeValue();
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_choosingStateDisposable?.Dispose();
_choosingStateDisposable = null;
_compositeDisposable?.Dispose();
_compositeDisposable = null;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
@ -145,6 +175,7 @@ internal class DatePickerPresenter : PickerPresenterBase
_todayButton = e.NameScope.Get<Button>(DatePickerPresenterTheme.TodayButtonPart);
_confirmButton = e.NameScope.Get<Button>(DatePickerPresenterTheme.ConfirmButtonPart);
_calendarView = e.NameScope.Get<PickerCalendar>(DatePickerPresenterTheme.CalendarViewPart);
_timeView = e.NameScope.Get<TimeView>(DatePickerPresenterTheme.TimeViewPart);
SetupButtonStatus();
if (_calendarView is not null)
{
@ -152,6 +183,18 @@ internal class DatePickerPresenter : PickerPresenterBase
_calendarView.DateSelected += HandleCalendarViewDateSelected;
}
if (_timeView is not null)
{
if (IsShowTime)
{
SyncTimeViewTimeValue();
}
_timeView.HoverTimeChanged += HandleTimeViewHoverChanged;
_timeView.TimeSelected += HandleTimeViewTimeSelected;
_timeView.TempTimeSelected += HandleTimeViewTempTimeSelected;
}
if (_todayButton is not null)
{
_todayButton.Click += HandleTodayButtonClicked;
@ -170,7 +213,7 @@ internal class DatePickerPresenter : PickerPresenterBase
{
if (_calendarView?.SelectedDate is not null)
{
var hoverDateTime = CollectDateTime(_calendarView?.SelectedDate);
var hoverDateTime = CollectDateTime(_calendarView?.SelectedDate, TempSelectedTime ?? _timeView?.SelectedTime);
HoverDateTimeChanged?.Invoke(this, new DateSelectedEventArgs(hoverDateTime));
}
};
@ -196,11 +239,16 @@ internal class DatePickerPresenter : PickerPresenterBase
private void HandleNowButtonClicked(object? sender, RoutedEventArgs args)
{
SelectedDateTime = DateTime.Now;
if (_calendarView is not null)
{
_calendarView.DisplayDate = DateTime.Now;
_calendarView.SelectedDate = DateTime.Now;
}
if (IsShowTime && _timeView is not null)
{
_timeView.SelectedTime = DateTime.Now.TimeOfDay;
}
if (!IsNeedConfirm)
{
OnConfirmed();
@ -219,7 +267,7 @@ internal class DatePickerPresenter : PickerPresenterBase
{
// 需要组合日期和时间
// 暂时没实现
var hoverDateTime = CollectDateTime(args.Value);
var hoverDateTime = CollectDateTime(args.Value, TempSelectedTime);
HoverDateTimeChanged?.Invoke(this, new DateSelectedEventArgs(hoverDateTime));
}
@ -233,17 +281,14 @@ internal class DatePickerPresenter : PickerPresenterBase
private DateTime? CollectDateTime(DateTime? date, TimeSpan? timeSpan = null)
{
DateTime? hoverDateTime = default;
if (date is not null)
date ??= DateTime.Today;
date = date.Value.Date;
if (IsShowTime && timeSpan is not null)
{
hoverDateTime = date?.Date;
if (timeSpan is not null)
{
hoverDateTime = hoverDateTime?.Add(timeSpan.Value);
}
date = date.Value.Add(timeSpan.Value);
}
return hoverDateTime;
return date;
}
private void SetupButtonStatus()
@ -254,11 +299,18 @@ internal class DatePickerPresenter : PickerPresenterBase
{
return;
}
_confirmButton.IsVisible = IsNeedConfirm;
_nowButton.IsVisible = false;
_todayButton.IsVisible = false;
_nowButton.HorizontalAlignment = HorizontalAlignment.Left;
_todayButton.HorizontalAlignment = HorizontalAlignment.Left;
if (IsShowNow)
{
_nowButton.IsVisible = false;
_todayButton.IsVisible = false;
if (IsShowTime)
{
_nowButton.IsVisible = true;
@ -279,13 +331,6 @@ internal class DatePickerPresenter : PickerPresenterBase
_todayButton.HorizontalAlignment = HorizontalAlignment.Left;
}
}
else
{
_nowButton.IsVisible = false;
_todayButton.IsVisible = false;
_nowButton.HorizontalAlignment = HorizontalAlignment.Left;
_todayButton.HorizontalAlignment = HorizontalAlignment.Left;
}
ButtonsPanelVisible = _nowButton.IsVisible || _todayButton.IsVisible || _confirmButton.IsVisible;
}
@ -293,8 +338,8 @@ internal class DatePickerPresenter : PickerPresenterBase
protected override void OnConfirmed()
{
ChoosingStatueChanged?.Invoke(this, new ChoosingStatusEventArgs(false));
SelectedDateTime = CollectDateTime(_calendarView?.SelectedDate, TempSelectedTime ?? _timeView?.SelectedTime);
base.OnConfirmed();
SelectedDateTime = CollectDateTime(_calendarView?.SelectedDate);
}
protected override void OnDismiss()
@ -302,4 +347,31 @@ internal class DatePickerPresenter : PickerPresenterBase
base.OnDismiss();
SelectedDateTime = null;
}
private void SyncTimeViewTimeValue()
{
if (_timeView is not null)
{
_timeView.SelectedTime = SelectedDateTime?.TimeOfDay ?? TimeSpan.Zero;
}
}
private void HandleTimeViewHoverChanged(object? sender, TimeSelectedEventArgs args)
{
var hoverDateTime = CollectDateTime(SelectedDateTime, args.Time);
HoverDateTimeChanged?.Invoke(this, new DateSelectedEventArgs(hoverDateTime));
}
private void HandleTimeViewTimeSelected(object? sender, TimeSelectedEventArgs args)
{
if (!IsNeedConfirm)
{
OnConfirmed();
}
}
private void HandleTimeViewTempTimeSelected(object? sender, TimeSelectedEventArgs args)
{
TempSelectedTime = args.Time;
}
}

View File

@ -5,6 +5,7 @@ using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Styling;
using PickerCalendar = AtomUI.Controls.CalendarView.Calendar;
@ -21,6 +22,7 @@ internal class DatePickerPresenterTheme : BaseControlTheme
public const string ButtonsLayoutPart = "PART_ButtonsLayout";
public const string ButtonsFramePart = "PART_ButtonsFrame";
public const string CalendarViewPart = "PART_CalendarView";
public const string TimeViewPart = "PART_TimeView";
public DatePickerPresenterTheme() : this(typeof(DatePickerPresenter))
{
@ -60,13 +62,29 @@ internal class DatePickerPresenterTheme : BaseControlTheme
protected virtual Control BuildCalendarView(DatePickerPresenter presenter, INameScope scope)
{
var calendarLayout = new StackPanel()
{
Orientation = Orientation.Horizontal
};
var calendarView = new PickerCalendar()
{
Name = CalendarViewPart,
};
CreateTemplateParentBinding(calendarView, PickerCalendar.SelectedDateProperty, DatePickerPresenter.SelectedDateTimeProperty);
calendarView.RegisterInNameScope(scope);
return calendarView;
calendarLayout.Children.Add(calendarView);
var timeView = new TimeView()
{
Name = TimeViewPart,
VerticalAlignment = VerticalAlignment.Top,
};
CreateTemplateParentBinding(timeView, TimeView.ClockIdentifierProperty, DatePickerPresenter.ClockIdentifierProperty);
CreateTemplateParentBinding(timeView, TimeView.IsVisibleProperty, DatePickerPresenter.IsShowTimeProperty);
timeView.RegisterInNameScope(scope);
calendarLayout.Children.Add(timeView);
return calendarLayout;
}
protected virtual Panel BuildButtons(DatePickerPresenter presenter, INameScope scope)
@ -121,7 +139,9 @@ internal class DatePickerPresenterTheme : BaseControlTheme
var buttonsPanelStyle = new Style(selector => selector.Nesting().Template().Name(ButtonsLayoutPart));
buttonsPanelStyle.Add(Panel.MarginProperty, DatePickerTokenResourceKey.ButtonsPanelMargin);
Add(buttonsPanelStyle);
var timeViewStyle = new Style(selector => selector.Nesting().Template().Name(TimeViewPart));
timeViewStyle.Add(TimePicker.PaddingProperty, DatePickerTokenResourceKey.PanelContentPadding);
Add(timeViewStyle);
}
}

View File

@ -25,6 +25,11 @@ internal class DatePickerTheme : InfoPickerInputTheme
base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(DatePicker.MinWidthProperty, DatePickerTokenResourceKey.PickerInputMinWidth);
var withTimeStyle =
new Style(selector => selector.Nesting().PropertyEquals(DatePicker.IsShowTimeProperty, true));
withTimeStyle.Add(DatePicker.MinWidthProperty, DatePickerTokenResourceKey.PickerInputWithTimeMinWidth);
commonStyle.Add(withTimeStyle);
Add(commonStyle);
}

View File

@ -115,6 +115,11 @@ internal class DatePickerToken : AbstractControlDesignToken
/// </summary>
public double PickerInputMinWidth { get; set; }
/// <summary>
/// 日期选择器最小的宽度 (ShowTime 为 True)
/// </summary>
public double PickerInputWithTimeMinWidth { get; set; }
/// <summary>
/// 按钮区域面板外间距
/// </summary>
@ -126,26 +131,27 @@ internal class DatePickerToken : AbstractControlDesignToken
var colorPrimary = _globalToken.ColorToken.ColorPrimaryToken.ColorPrimary;
CellHoverBg = _globalToken.ControlItemBgHover;
CellActiveWithRangeBg = _globalToken.ControlItemBgActive;
CellHoverWithRangeBg = colorPrimary.Lighten(35);
CellRangeBorderColor = colorPrimary.Lighten(20);
CellBgDisabled = _globalToken.ColorBgContainerDisabled;
CellWidth = _globalToken.HeightToken.ControlHeightSM;
CellHeight = _globalToken.HeightToken.ControlHeightSM;
TextHeight = _globalToken.HeightToken.ControlHeightLG;
WithoutTimeCellHeight = _globalToken.HeightToken.ControlHeightLG * 1.65;
CellMargin = new Thickness(_globalToken.MarginXXS);
PanelContentPadding = new Thickness(_globalToken.PaddingSM);
ItemPanelMinWidth = 225;
ItemPanelMinHeight = 270;
RangeItemPanelMinWidth = 260;
DayTitleHeight = _globalToken.HeightToken.ControlHeightSM;
HeaderMargin = new Thickness(0, 0, 0, _globalToken.MarginSM);
HeaderPadding = new Thickness(0, 0, 0, _globalToken.PaddingSM);
CellLineHeight = CellHeight - 2; // 不知道为啥设置成一样,或者不设置文字有些靠下
RangeCalendarSpacing = 20;
PickerInputMinWidth = 150;
ButtonsPanelMargin = new Thickness(0, _globalToken.MarginXS, 0, 0);
CellHoverBg = _globalToken.ControlItemBgHover;
CellActiveWithRangeBg = _globalToken.ControlItemBgActive;
CellHoverWithRangeBg = colorPrimary.Lighten(35);
CellRangeBorderColor = colorPrimary.Lighten(20);
CellBgDisabled = _globalToken.ColorBgContainerDisabled;
CellWidth = _globalToken.HeightToken.ControlHeightSM;
CellHeight = _globalToken.HeightToken.ControlHeightSM;
TextHeight = _globalToken.HeightToken.ControlHeightLG;
WithoutTimeCellHeight = _globalToken.HeightToken.ControlHeightLG * 1.65;
CellMargin = new Thickness(_globalToken.MarginXXS);
PanelContentPadding = new Thickness(_globalToken.PaddingSM);
ItemPanelMinWidth = 225;
ItemPanelMinHeight = 270;
RangeItemPanelMinWidth = 260;
DayTitleHeight = _globalToken.HeightToken.ControlHeightSM;
HeaderMargin = new Thickness(0, 0, 0, _globalToken.MarginSM);
HeaderPadding = new Thickness(0, 0, 0, _globalToken.PaddingSM);
CellLineHeight = CellHeight - 2; // 不知道为啥设置成一样,或者不设置文字有些靠下
RangeCalendarSpacing = 20;
PickerInputMinWidth = 150;
PickerInputWithTimeMinWidth = 200;
ButtonsPanelMargin = new Thickness(0, _globalToken.MarginXS, 0, 0);
}
}

View File

@ -210,6 +210,7 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey HeaderPadding = new TokenResourceKey("DatePicker.HeaderPadding", "AtomUI.Token");
public static readonly TokenResourceKey RangeCalendarSpacing = new TokenResourceKey("DatePicker.RangeCalendarSpacing", "AtomUI.Token");
public static readonly TokenResourceKey PickerInputMinWidth = new TokenResourceKey("DatePicker.PickerInputMinWidth", "AtomUI.Token");
public static readonly TokenResourceKey PickerInputWithTimeMinWidth = new TokenResourceKey("DatePicker.PickerInputWithTimeMinWidth", "AtomUI.Token");
public static readonly TokenResourceKey ButtonsPanelMargin = new TokenResourceKey("DatePicker.ButtonsPanelMargin", "AtomUI.Token");
}
@ -578,10 +579,10 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey ItemWidth = new TokenResourceKey("TimePicker.ItemWidth", "AtomUI.Token");
public static readonly TokenResourceKey ItemPadding = new TokenResourceKey("TimePicker.ItemPadding", "AtomUI.Token");
public static readonly TokenResourceKey ButtonsMargin = new TokenResourceKey("TimePicker.ButtonsMargin", "AtomUI.Token");
public static readonly TokenResourceKey PickerPopupHeight = new TokenResourceKey("TimePicker.PickerPopupHeight", "AtomUI.Token");
public static readonly TokenResourceKey RangePickerArrowMargin = new TokenResourceKey("TimePicker.RangePickerArrowMargin", "AtomUI.Token");
public static readonly TokenResourceKey RangePickerIndicatorThickness = new TokenResourceKey("TimePicker.RangePickerIndicatorThickness", "AtomUI.Token");
public static readonly TokenResourceKey PickerInputMinWidth = new TokenResourceKey("TimePicker.PickerInputMinWidth", "AtomUI.Token");
public static readonly TokenResourceKey HeaderMargin = new TokenResourceKey("TimePicker.HeaderMargin", "AtomUI.Token");
}
public static class ToolTipTokenResourceKey

View File

@ -575,7 +575,8 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
Focusable = false,
CornerRadius = new CornerRadius(0),
SizeType = SizeType.Middle,
DisabledItemHoverAnimation = true
DisabledItemHoverAnimation = true,
Cursor = new Cursor(StandardCursorType.Hand)
};
item.PointerEntered += (sender, args) =>
{

View File

@ -15,7 +15,7 @@ internal class RangeTimePickerTheme : RangeInfoPickerInputTheme
{
return new PathIcon()
{
Kind = "CalendarOutlined"
Kind = "ClockCircleOutlined"
};
}
}

View File

@ -30,7 +30,7 @@ public class TimePicker : InfoPickerInput
AvaloniaProperty.Register<TimePicker, int>(nameof(SecondIncrement), 1, coerce: CoerceSecondIncrement);
public static readonly StyledProperty<ClockIdentifierType> ClockIdentifierProperty =
AvaloniaProperty.Register<TimePicker, ClockIdentifierType>(nameof(ClockIdentifier));
AvaloniaProperty.Register<TimePicker, ClockIdentifierType>(nameof(ClockIdentifier), ClockIdentifierType.HourClock12);
public static readonly StyledProperty<TimeSpan?> SelectedTimeProperty =
AvaloniaProperty.Register<TimePicker, TimeSpan?>(nameof(SelectedTime),

View File

@ -104,8 +104,6 @@ internal class TimePickerPresenterTheme : BaseControlTheme
{
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(TimePickerPresenter.HeightProperty, TimePickerTokenResourceKey.PickerPopupHeight);
var buttonsFrameStyle = new Style(selector => selector.Nesting().Template().Name(ButtonsFramePart));
buttonsFrameStyle.Add(Border.BorderBrushProperty, GlobalTokenResourceKey.ColorBorderSecondary);
commonStyle.Add(buttonsFrameStyle);

View File

@ -33,11 +33,6 @@ internal class TimePickerToken : AbstractControlDesignToken
/// </summary>
public Thickness ButtonsMargin { get; set; }
/// <summary>
/// 日期选择弹出框高度
/// </summary>
public double PickerPopupHeight { get; set; }
/// <summary>
/// 范围选择箭头外间距
/// </summary>
@ -53,6 +48,11 @@ internal class TimePickerToken : AbstractControlDesignToken
/// </summary>
public double PickerInputMinWidth { get; set; }
/// <summary>
/// Header 头内间距
/// </summary>
public Thickness HeaderMargin { get; set; }
internal override void CalculateFromAlias()
{
base.CalculateFromAlias();
@ -60,9 +60,9 @@ internal class TimePickerToken : AbstractControlDesignToken
ItemHeight = _globalToken.SeedToken.ControlHeight - 4;
ItemPadding = new Thickness(0, _globalToken.PaddingXXS);
ButtonsMargin = new Thickness(0, _globalToken.MarginXS, 0, 0);
PickerPopupHeight = ItemHeight * 8;
RangePickerArrowMargin = new Thickness(_globalToken.MarginXS, 0);
RangePickerIndicatorThickness = _globalToken.LineWidthFocus;
PickerInputMinWidth = 120;
HeaderMargin = new Thickness(0, 0, 0, 3);
}
}

View File

@ -28,7 +28,7 @@ internal class TimeSelectedEventArgs : EventArgs
[TemplatePart(TimeViewTheme.SecondSelectorPart, typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart(TimeViewTheme.PeriodHostPart, typeof(Panel), IsRequired = true)]
[TemplatePart(TimeViewTheme.PeriodSelectorPart, typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart(TimeViewTheme.PickerContainerPart, typeof(Grid), IsRequired = true)]
[TemplatePart(TimeViewTheme.PickerSelectorContainerPart, typeof(Grid), IsRequired = true)]
[TemplatePart(TimeViewTheme.SecondSpacerPart, typeof(Rectangle), IsRequired = true)]
internal class TimeView : TemplatedControl
{
@ -48,6 +48,9 @@ internal class TimeView : TemplatedControl
public static readonly StyledProperty<bool> IsShowHeaderProperty =
AvaloniaProperty.Register<TimeView, bool>(nameof(IsShowHeader), true);
public static readonly StyledProperty<int> SelectorRowCountProperty =
AvaloniaProperty.Register<TimeView, int>(nameof(SelectorRowCount), 7);
/// <summary>
/// Gets or sets the minute increment in the selector
@ -85,6 +88,12 @@ internal class TimeView : TemplatedControl
set => SetValue(IsShowHeaderProperty, value);
}
public int SelectorRowCount
{
get => GetValue(SelectorRowCountProperty);
set => SetValue(SelectorRowCountProperty, value);
}
#endregion
#region
@ -94,24 +103,35 @@ internal class TimeView : TemplatedControl
o => o.SpacerWidth,
(o, v) => o.SpacerWidth = v);
internal static readonly DirectProperty<TimeView, double> ItemHeightProperty =
AvaloniaProperty.RegisterDirect<TimeView, double>(nameof(ItemHeight),
o => o.ItemHeight,
(o, v) => o.ItemHeight = v);
internal static readonly StyledProperty<bool> IsPointerInSelectorProperty =
AvaloniaProperty.Register<Calendar, bool>(nameof(IsPointerInSelector), false);
AvaloniaProperty.Register<TimeView, bool>(nameof(IsPointerInSelector), false);
private double _spacerWidth;
public double SpacerWidth
internal double SpacerWidth
{
get => _spacerWidth;
set => SetAndRaise(SpacerThicknessProperty, ref _spacerWidth, value);
}
internal bool IsPointerInSelector
{
get => GetValue(IsPointerInSelectorProperty);
set => SetValue(IsPointerInSelectorProperty, value);
}
private double _itemHeight;
internal double ItemHeight
{
get => _itemHeight;
set => SetAndRaise(ItemHeightProperty, ref _itemHeight, value);
}
#endregion
#region
@ -129,7 +149,7 @@ internal class TimeView : TemplatedControl
}
// TemplateItems
private Grid? _pickerContainer;
private Grid? _pickerSelectorContainer;
private Rectangle? _spacer3;
private Panel? _periodHost;
private TextBlock? _headerText;
@ -207,14 +227,17 @@ internal class TimeView : TemplatedControl
{
base.OnApplyTemplate(e);
_pickerContainer = e.NameScope.Get<Grid>(TimeViewTheme.PickerContainerPart);
_periodHost = e.NameScope.Get<Panel>(TimeViewTheme.PeriodHostPart);
_headerText = e.NameScope.Get<TextBlock>(TimeViewTheme.HeaderTextPart);
_pickerSelectorContainer = e.NameScope.Get<Grid>(TimeViewTheme.PickerSelectorContainerPart);
_periodHost = e.NameScope.Get<Panel>(TimeViewTheme.PeriodHostPart);
_headerText = e.NameScope.Get<TextBlock>(TimeViewTheme.HeaderTextPart);
_hourSelector = e.NameScope.Get<DateTimePickerPanel>(TimeViewTheme.HourSelectorPart);
_minuteSelector = e.NameScope.Get<DateTimePickerPanel>(TimeViewTheme.MinuteSelectorPart);
_secondSelector = e.NameScope.Get<DateTimePickerPanel>(TimeViewTheme.SecondSelectorPart);
_periodSelector = e.NameScope.Get<DateTimePickerPanel>(TimeViewTheme.PeriodSelectorPart);
SetupPickerSelectorContainerHeight();
TokenResourceBinder.CreateGlobalTokenBinding(this, ItemHeightProperty, TimePickerTokenResourceKey.ItemHeight);
if (_hourSelector is not null)
{
@ -354,6 +377,12 @@ internal class TimeView : TemplatedControl
}
}
if (change.Property == ItemHeightProperty ||
change.Property == SelectorRowCountProperty)
{
SetupPickerSelectorContainerHeight();
}
}
private void SyncTimeValueToPanel(TimeSpan time)
@ -385,7 +414,7 @@ internal class TimeView : TemplatedControl
private void InitPicker()
{
if (_pickerContainer == null)
if (_pickerSelectorContainer == null)
{
return;
}
@ -414,5 +443,14 @@ internal class TimeView : TemplatedControl
_spacer3!.IsVisible = !use24HourClock;
_periodHost!.IsVisible = !use24HourClock;
}
private void SetupPickerSelectorContainerHeight()
{
if (_pickerSelectorContainer is null)
{
return;
}
_pickerSelectorContainer.Height = ItemHeight * SelectorRowCount;
}
}

View File

@ -15,8 +15,9 @@ namespace AtomUI.Controls;
[ControlThemeProvider]
internal class TimeViewTheme : BaseControlTheme
{
public const string MainContainerPart = "PART_MainContainer";
public const string PickerContainerPart = "PART_PickerContainer";
public const string RootLayoutPart = "PART_RootLayout";
public const string MainFramePart = "PART_MainFrame";
public const string PickerSelectorContainerPart = "PART_PickerContainer";
public const string HeaderTextPart = "PART_HeaderText";
public const string HourHostPart = "PART_HourHost";
@ -42,9 +43,15 @@ internal class TimeViewTheme : BaseControlTheme
{
return new FuncControlTemplate<TimeView>((presenter, scope) =>
{
var mainContainer = new Grid
var frame = new Border()
{
Name = MainContainerPart,
Name = MainFramePart,
};
CreateTemplateParentBinding(frame, Border.PaddingProperty, TimeView.PaddingProperty);
CreateTemplateParentBinding(frame, Border.BackgroundProperty, TimeView.BackgroundProperty);
var rootLayout = new Grid
{
Name = RootLayoutPart,
RowDefinitions = new RowDefinitions
{
new(GridLength.Auto),
@ -52,13 +59,14 @@ internal class TimeViewTheme : BaseControlTheme
new(GridLength.Star)
}
};
BuildHeader(mainContainer, scope);
BuildHosts(mainContainer, scope);
return mainContainer;
BuildHeader(rootLayout, scope);
BuildHosts(rootLayout, scope);
frame.Child = rootLayout;
return frame;
});
}
private void BuildHeader(Grid mainContainer, INameScope scope)
private void BuildHeader(Grid rootLayout, INameScope scope)
{
var headerText = new TextBlock
{
@ -66,26 +74,26 @@ internal class TimeViewTheme : BaseControlTheme
};
headerText.RegisterInNameScope(scope);
CreateTemplateParentBinding(headerText, Visual.IsVisibleProperty, TimeView.IsShowHeaderProperty);
mainContainer.Children.Add(headerText);
rootLayout.Children.Add(headerText);
Grid.SetRow(headerText, 0);
var separator = new Rectangle
{
HorizontalAlignment = HorizontalAlignment.Stretch
HorizontalAlignment = HorizontalAlignment.Stretch,
};
CreateTemplateParentBinding(separator, Layoutable.HeightProperty, TimeView.SpacerThicknessProperty);
CreateTemplateParentBinding(separator, Visual.IsVisibleProperty, TimeView.IsShowHeaderProperty);
TokenResourceBinder.CreateGlobalTokenBinding(separator, Shape.FillProperty,
GlobalTokenResourceKey.ColorBorder);
mainContainer.Children.Add(separator);
GlobalTokenResourceKey.ColorBorderSecondary);
rootLayout.Children.Add(separator);
Grid.SetRow(separator, 1);
}
private void BuildHosts(Grid mainContainer, INameScope scope)
private void BuildHosts(Grid rootLayout, INameScope scope)
{
var pickerContainer = new Grid
{
Name = PickerContainerPart,
Name = PickerSelectorContainerPart,
ColumnDefinitions = new ColumnDefinitions
{
new(GridLength.Star),
@ -133,7 +141,7 @@ internal class TimeViewTheme : BaseControlTheme
firstSpacer.RegisterInNameScope(scope);
CreateTemplateParentBinding(firstSpacer, Layoutable.WidthProperty, TimeView.SpacerThicknessProperty);
TokenResourceBinder.CreateGlobalTokenBinding(firstSpacer, Shape.FillProperty,
GlobalTokenResourceKey.ColorBorder);
GlobalTokenResourceKey.ColorBorderSecondary);
Grid.SetColumn(firstSpacer, 1);
pickerContainer.Children.Add(firstSpacer);
@ -173,7 +181,7 @@ internal class TimeViewTheme : BaseControlTheme
TimeView.SpacerThicknessProperty);
secondSpacer.RegisterInNameScope(scope);
TokenResourceBinder.CreateGlobalTokenBinding(secondSpacer, Shape.FillProperty,
GlobalTokenResourceKey.ColorBorder);
GlobalTokenResourceKey.ColorBorderSecondary);
Grid.SetColumn(secondSpacer, 3);
pickerContainer.Children.Add(secondSpacer);
@ -214,7 +222,7 @@ internal class TimeViewTheme : BaseControlTheme
Grid.SetColumn(thirdSpacer, 5);
pickerContainer.Children.Add(thirdSpacer);
TokenResourceBinder.CreateGlobalTokenBinding(thirdSpacer, Shape.FillProperty,
GlobalTokenResourceKey.ColorBorder);
GlobalTokenResourceKey.ColorBorderSecondary);
var periodHost = new Panel
{
@ -243,8 +251,8 @@ internal class TimeViewTheme : BaseControlTheme
periodHost.Children.Add(scrollViewer);
}
mainContainer.Children.Add(pickerContainer);
mainContainer.RegisterInNameScope(scope);
rootLayout.Children.Add(pickerContainer);
rootLayout.RegisterInNameScope(scope);
Grid.SetRow(pickerContainer, 2);
}
@ -252,10 +260,11 @@ internal class TimeViewTheme : BaseControlTheme
{
var commonStyle = new Style(selector => selector.Nesting());
var headerTextStyle = new Style(selector => selector.Nesting().Template().Name(HeaderTextPart));
headerTextStyle.Add(Layoutable.HeightProperty, TimePickerTokenResourceKey.ItemHeight);
headerTextStyle.Add(Layoutable.HorizontalAlignmentProperty, HorizontalAlignment.Center);
headerTextStyle.Add(Layoutable.VerticalAlignmentProperty, VerticalAlignment.Center);
headerTextStyle.Add(TextBlock.HeightProperty, TimePickerTokenResourceKey.ItemHeight);
headerTextStyle.Add(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Center);
headerTextStyle.Add(TextBlock.VerticalAlignmentProperty, VerticalAlignment.Center);
headerTextStyle.Add(TextBlock.FontWeightProperty, FontWeight.SemiBold);
headerTextStyle.Add(TextBlock.MarginProperty, TimePickerTokenResourceKey.HeaderMargin);
var hourHostStyle = new Style(selector => selector.Nesting().Template().Name(HourHostPart));
hourHostStyle.Add(Panel.WidthProperty, TimePickerTokenResourceKey.ItemWidth);