Refactor TimePicker

Use the new Hover selection event mechanism
This commit is contained in:
polarboy 2024-09-14 12:52:36 +08:00
parent 79e1c510ed
commit 561aaddcd5
29 changed files with 1247 additions and 1087 deletions

View File

@ -51,8 +51,20 @@ Avalonia 11.1.1 及其以上<br>
PS: AtomUI 目前仅在 Windows 11 平台测试<br> PS: AtomUI 目前仅在 Windows 11 平台测试<br>
#### 中文社区 #### 中文社区
目前我们暂时只创建微信开发者群的交流方式,下面是二维码,有兴趣的同学可以扫码加入: 目前我们暂时只创建 QQ 和微信开发者群的交流方式,下面是二维码,有兴趣的同学可以扫码加入:
<img src="./docs/images/wechat.jpg" width="200" height="200"/>
<table border="0">
<tbody>
<tr>
<td align="center" valign="middle">
<img src="./docs/images/wechat.jpg" width="200" height="200"/>
</td>
<td align="center" valign="middle">
<img src="./docs/images/QQ.jpg" width="200" height="200"/>
</td>
</tr>
</tbody>
<table>
> PS扫码请注明来意比如学习`AtomUI`或者`Avalonia`爱好者 > PS扫码请注明来意比如学习`AtomUI`或者`Avalonia`爱好者

BIN
docs/images/QQ.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

View File

@ -5,20 +5,25 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:desktop="clr-namespace:AtomUI.Demo.Desktop" xmlns:desktop="clr-namespace:AtomUI.Demo.Desktop"
xmlns:calendarpresenter="clr-namespace:AtomUI.Controls.CalendarView;assembly=AtomUI.Controls"
xmlns:atom="https://atomui.net" xmlns:atom="https://atomui.net"
mc:Ignorable="d"> mc:Ignorable="d">
<desktop:ShowCasePanel> <desktop:ShowCasePanel>
<desktop:ShowCaseItem <desktop:ShowCaseItem
Title="Basic" Title="Basic"
Description="Click DatePicker, and then we could select or input a date in panel."> Description="Click DatePicker, and then we could select or input a date in panel.">
<atom:DatePicker Watermark="Select date" DefaultDateTime="2029-9-3" IsNeedConfirm="True"/> <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"/>
</desktop:ShowCaseItem> </desktop:ShowCaseItem>
<!-- -->
<!-- <desktop:ShowCaseItem -->
<!-- Title="Basic" -->
<!-- Description="Click DatePicker, and then we could select or input a date in panel."> -->
<!-- <calendarpresenter:Calendar SelectedDate="2008-9-3"/> -->
<!-- </desktop:ShowCaseItem> -->
</desktop:ShowCasePanel> </desktop:ShowCasePanel>
</UserControl> </UserControl>

View File

@ -11,7 +11,7 @@ using Avalonia.Media;
namespace AtomUI.Controls.CalendarView; namespace AtomUI.Controls.CalendarView;
public class DateSelectedEventArgs : RoutedEventArgs public class DateSelectedEventArgs : EventArgs
{ {
public DateTime? Value { get; } public DateTime? Value { get; }
public DateSelectedEventArgs(DateTime? value) public DateSelectedEventArgs(DateTime? value)

View File

@ -4,7 +4,6 @@ using AtomUI.Data;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Threading;
namespace AtomUI.Controls; namespace AtomUI.Controls;

View File

@ -98,7 +98,6 @@ internal class DatePickerPresenter : PickerPresenterBase
private Button? _confirmButton; private Button? _confirmButton;
private PickerCalendar? _calendarView; private PickerCalendar? _calendarView;
private IDisposable? _choosingStateDisposable; private IDisposable? _choosingStateDisposable;
private bool _isConfirmed;
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
@ -112,13 +111,12 @@ internal class DatePickerPresenter : PickerPresenterBase
ChoosingStatueChanged?.Invoke(this, new ChoosingStatusEventArgs(args.GetNewValue<bool>())); ChoosingStatueChanged?.Invoke(this, new ChoosingStatusEventArgs(args.GetNewValue<bool>()));
}); });
} }
_isConfirmed = false;
} }
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTree(e); base.OnDetachedFromVisualTree(e);
_choosingStateDisposable?.Dispose();
_choosingStateDisposable = null; _choosingStateDisposable = null;
} }

View File

@ -18,8 +18,8 @@ internal class DatePickerPresenterTheme : BaseControlTheme
public const string NowButtonPart = "PART_NowButton"; public const string NowButtonPart = "PART_NowButton";
public const string TodayButtonPart = "PART_TodayButton"; public const string TodayButtonPart = "PART_TodayButton";
public const string ConfirmButtonPart = "PART_ConfirmButton"; public const string ConfirmButtonPart = "PART_ConfirmButton";
public const string ButtonsContainerPart = "PART_ButtonsContainer"; public const string ButtonsLayoutPart = "PART_ButtonsLayout";
public const string ButtonsContainerFramePart = "PART_ButtonsContainerFrame"; public const string ButtonsFramePart = "PART_ButtonsFrame";
public const string CalendarViewPart = "PART_CalendarView"; public const string CalendarViewPart = "PART_CalendarView";
public DatePickerPresenterTheme() : this(typeof(DatePickerPresenter)) public DatePickerPresenterTheme() : this(typeof(DatePickerPresenter))
@ -44,7 +44,7 @@ internal class DatePickerPresenterTheme : BaseControlTheme
var buttonsContainerFrame = new Border() var buttonsContainerFrame = new Border()
{ {
Name = ButtonsContainerFramePart Name = ButtonsFramePart
}; };
CreateTemplateParentBinding(buttonsContainerFrame, Border.BorderThicknessProperty, DatePickerPresenter.BorderThicknessProperty); CreateTemplateParentBinding(buttonsContainerFrame, Border.BorderThicknessProperty, DatePickerPresenter.BorderThicknessProperty);
var buttonsPanel = BuildButtons(presenter, scope); var buttonsPanel = BuildButtons(presenter, scope);
@ -73,7 +73,7 @@ internal class DatePickerPresenterTheme : BaseControlTheme
{ {
var buttonsPanel = new Panel() var buttonsPanel = new Panel()
{ {
Name = ButtonsContainerPart Name = ButtonsLayoutPart
}; };
var nowButton = new Button var nowButton = new Button
@ -114,11 +114,11 @@ internal class DatePickerPresenterTheme : BaseControlTheme
protected override void BuildStyles() protected override void BuildStyles()
{ {
var buttonsFrameStyle = new Style(selector => selector.Nesting().Template().Name(ButtonsContainerFramePart)); var buttonsFrameStyle = new Style(selector => selector.Nesting().Template().Name(ButtonsFramePart));
buttonsFrameStyle.Add(Border.BorderBrushProperty, GlobalTokenResourceKey.ColorBorderSecondary); buttonsFrameStyle.Add(Border.BorderBrushProperty, GlobalTokenResourceKey.ColorBorderSecondary);
Add(buttonsFrameStyle); Add(buttonsFrameStyle);
var buttonsPanelStyle = new Style(selector => selector.Nesting().Template().Name(ButtonsContainerPart)); var buttonsPanelStyle = new Style(selector => selector.Nesting().Template().Name(ButtonsLayoutPart));
buttonsPanelStyle.Add(Panel.MarginProperty, DatePickerTokenResourceKey.ButtonsPanelMargin); buttonsPanelStyle.Add(Panel.MarginProperty, DatePickerTokenResourceKey.ButtonsPanelMargin);
Add(buttonsPanelStyle); Add(buttonsPanelStyle);

View File

@ -26,10 +26,6 @@ internal class DatePickerTheme : InfoPickerInputTheme
var commonStyle = new Style(selector => selector.Nesting()); var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(DatePicker.MinWidthProperty, DatePickerTokenResourceKey.PickerInputMinWidth); commonStyle.Add(DatePicker.MinWidthProperty, DatePickerTokenResourceKey.PickerInputMinWidth);
var choosingStyle = new Style(selector => selector.Nesting().Class(DatePicker.ChoosingPC));
choosingStyle.Add(DatePicker.InputTextBrushProperty, GlobalTokenResourceKey.ColorTextTertiary);
commonStyle.Add(choosingStyle);
Add(commonStyle); Add(commonStyle);
} }
} }

View File

@ -89,11 +89,11 @@
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TabStripItemTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TabStripItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TabStripTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TabStripTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TagTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TagTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.RangeTimePickerFlyoutPresenterTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.RangeTimePickerTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.RangeTimePickerTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TimePickerFlyoutPresenterTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TimePickerFlyoutPresenterTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TimePickerPresenterTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TimePickerPresenterTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TimePickerTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TimePickerTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TimeViewTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ToolTipTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ToolTipTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.NodeSwitcherButtonTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.NodeSwitcherButtonTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TreeViewItemTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TreeViewItemTheme());

View File

@ -581,6 +581,7 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey PickerPopupHeight = new TokenResourceKey("TimePicker.PickerPopupHeight", "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 RangePickerArrowMargin = new TokenResourceKey("TimePicker.RangePickerArrowMargin", "AtomUI.Token");
public static readonly TokenResourceKey RangePickerIndicatorThickness = new TokenResourceKey("TimePicker.RangePickerIndicatorThickness", "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 class ToolTipTokenResourceKey public static class ToolTipTokenResourceKey

View File

@ -140,6 +140,11 @@ internal class InfoPickerInputTheme : BaseControlTheme
base.BuildStyles(); base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting()); var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(InfoPickerInput.InputTextBrushProperty, GlobalTokenResourceKey.ColorText); commonStyle.Add(InfoPickerInput.InputTextBrushProperty, GlobalTokenResourceKey.ColorText);
var choosingStyle = new Style(selector => selector.Nesting().Class(InfoPickerInput.ChoosingPC));
choosingStyle.Add(InfoPickerInput.InputTextBrushProperty, GlobalTokenResourceKey.ColorTextTertiary);
commonStyle.Add(choosingStyle);
Add(commonStyle); Add(commonStyle);
} }
} }

View File

@ -1,4 +1,8 @@
using Avalonia; using AtomUI.Media;
using AtomUI.Theme.Utils;
using Avalonia;
using Avalonia.Animation;
using Avalonia.LogicalTree;
namespace AtomUI.Controls; namespace AtomUI.Controls;
@ -34,5 +38,52 @@ public class ListBoxItem : AvaloniaListBoxItem
set => SetAndRaise(DisabledItemHoverEffectProperty, ref _disabledItemHoverEffect, value); set => SetAndRaise(DisabledItemHoverEffectProperty, ref _disabledItemHoverEffect, value);
} }
internal static readonly DirectProperty<ListBoxItem, bool> DisabledItemHoverAnimationProperty =
AvaloniaProperty.RegisterDirect<ListBoxItem, bool>(nameof(DisabledItemHoverAnimation),
o => o.DisabledItemHoverAnimation,
(o, v) => o.DisabledItemHoverAnimation = v);
private bool _disabledItemHoverAnimation = false;
internal bool DisabledItemHoverAnimation
{
get => _disabledItemHoverAnimation;
set => SetAndRaise(DisabledItemHoverAnimationProperty, ref _disabledItemHoverAnimation, value);
}
#endregion #endregion
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
if (!DisabledItemHoverAnimation)
{
Transitions ??= new Transitions
{
AnimationUtils.CreateTransition<SolidColorBrushTransition>(BackgroundProperty)
};
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (VisualRoot is not null)
{
if (change.Property == DisabledItemHoverAnimationProperty)
{
if (DisabledItemHoverAnimation)
{
Transitions?.Clear();
}
else
{
Transitions = new Transitions
{
AnimationUtils.CreateTransition<SolidColorBrushTransition>(BackgroundProperty)
};
}
}
}
}
} }

View File

@ -1,8 +1,5 @@
using AtomUI.Media; using AtomUI.Theme;
using AtomUI.Theme;
using AtomUI.Theme.Styling; using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
using Avalonia.Animation;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
@ -34,11 +31,10 @@ internal class ListBoxItemTheme : BaseControlTheme
Name = ContentPresenterPart Name = ContentPresenterPart
}; };
contentPresenter.Transitions = new Transitions CreateTemplateParentBinding(contentPresenter, ContentPresenter.ForegroundProperty,
{ TemplatedControl.ForegroundProperty);
AnimationUtils.CreateTransition<SolidColorBrushTransition>(ContentPresenter.BackgroundProperty) CreateTemplateParentBinding(contentPresenter, ContentPresenter.BackgroundProperty,
}; TemplatedControl.BackgroundProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.CornerRadiusProperty, CreateTemplateParentBinding(contentPresenter, ContentPresenter.CornerRadiusProperty,
TemplatedControl.CornerRadiusProperty); TemplatedControl.CornerRadiusProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty, CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty,
@ -66,32 +62,20 @@ internal class ListBoxItemTheme : BaseControlTheme
private void BuildCommonStyle() private void BuildCommonStyle()
{ {
var commonStyle = new Style(selector => selector.Nesting()); var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(Layoutable.MarginProperty, ListBoxTokenResourceKey.ItemMargin); commonStyle.Add(ListBoxItem.MarginProperty, ListBoxTokenResourceKey.ItemMargin);
{ commonStyle.Add(ListBoxItem.ForegroundProperty, ListBoxTokenResourceKey.ItemColor);
var contentPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ContentPresenterPart)); commonStyle.Add(ListBoxItem.BackgroundProperty, ListBoxTokenResourceKey.ItemBgColor);
contentPresenterStyle.Add(ContentPresenter.ForegroundProperty, ListBoxTokenResourceKey.ItemColor);
contentPresenterStyle.Add(ContentPresenter.BackgroundProperty, ListBoxTokenResourceKey.ItemBgColor);
commonStyle.Add(contentPresenterStyle);
}
var disabledItemHoverStyle = new Style(selector => var disabledItemHoverStyle = new Style(selector =>
selector.Nesting().PropertyEquals(ListBoxItem.DisabledItemHoverEffectProperty, false)); selector.Nesting().PropertyEquals(ListBoxItem.DisabledItemHoverEffectProperty, false).Class(StdPseudoClass.PointerOver));
{ disabledItemHoverStyle.Add(ListBoxItem.ForegroundProperty, ListBoxTokenResourceKey.ItemHoverColor);
var contentPresenterStyle = new Style(selector => disabledItemHoverStyle.Add(ListBoxItem.BackgroundProperty, ListBoxTokenResourceKey.ItemHoverBgColor);
selector.Nesting().Template().Name(ContentPresenterPart).Class(StdPseudoClass.PointerOver));
contentPresenterStyle.Add(ContentPresenter.ForegroundProperty, ListBoxTokenResourceKey.ItemHoverColor);
contentPresenterStyle.Add(ContentPresenter.BackgroundProperty, ListBoxTokenResourceKey.ItemHoverBgColor);
disabledItemHoverStyle.Add(contentPresenterStyle);
}
commonStyle.Add(disabledItemHoverStyle); commonStyle.Add(disabledItemHoverStyle);
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected)); var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
{ selectedStyle.Add(ListBoxItem.ForegroundProperty, ListBoxTokenResourceKey.ItemSelectedColor);
var contentPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ContentPresenterPart)); selectedStyle.Add(ListBoxItem.BackgroundProperty, ListBoxTokenResourceKey.ItemSelectedBgColor);
contentPresenterStyle.Add(ContentPresenter.ForegroundProperty, ListBoxTokenResourceKey.ItemSelectedColor);
contentPresenterStyle.Add(ContentPresenter.BackgroundProperty, ListBoxTokenResourceKey.ItemSelectedBgColor);
selectedStyle.Add(contentPresenterStyle);
}
commonStyle.Add(selectedStyle); commonStyle.Add(selectedStyle);
Add(commonStyle); Add(commonStyle);
} }

View File

@ -1,6 +1,5 @@
using System.Diagnostics; using System.Diagnostics;
using AtomUI.Controls.TimePickerLang; using AtomUI.Controls.TimePickerLang;
using AtomUI.Data;
using AtomUI.Theme.Data; using AtomUI.Theme.Data;
using AtomUI.Theme.Styling; using AtomUI.Theme.Styling;
using AtomUI.Utils; using AtomUI.Utils;
@ -29,8 +28,31 @@ public enum DateTimePickerPanelType
TimePeriod //AM or PM TimePeriod //AM or PM
} }
public struct CellHoverInfo
{
public DateTimePickerPanelType PanelType { get; }
public int CellValue { get; }
public CellHoverInfo(DateTimePickerPanelType panelType, int cellValue)
{
PanelType = panelType;
CellValue = cellValue;
}
}
internal class CellHoverEventArgs : EventArgs
{
public CellHoverInfo? CellHoverInfo { get; }
public CellHoverEventArgs(CellHoverInfo? hoverInfo)
{
CellHoverInfo = hoverInfo;
}
}
internal class DateTimePickerPanel : Panel, ILogicalScrollable internal class DateTimePickerPanel : Panel, ILogicalScrollable
{ {
#region
/// <summary> /// <summary>
/// Defines the <see cref="ItemHeight" /> property /// Defines the <see cref="ItemHeight" /> property
/// </summary> /// </summary>
@ -55,6 +77,51 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
public static readonly StyledProperty<bool> ShouldLoopProperty = public static readonly StyledProperty<bool> ShouldLoopProperty =
AvaloniaProperty.Register<DateTimePickerPanel, bool>(nameof(ShouldLoop)); AvaloniaProperty.Register<DateTimePickerPanel, bool>(nameof(ShouldLoop));
/// <summary>
/// Gets or sets the height of each item
/// </summary>
public double ItemHeight
{
get => GetValue(ItemHeightProperty);
set => SetValue(ItemHeightProperty, value);
}
/// <summary>
/// Gets or sets what this panel displays in date or time units
/// </summary>
public DateTimePickerPanelType PanelType
{
get => GetValue(PanelTypeProperty);
set => SetValue(PanelTypeProperty, value);
}
/// <summary>
/// Gets or sets the string format for the items, using standard
/// .net DateTime or TimeSpan formatting. Format must match panel type
/// </summary>
public string ItemFormat
{
get => GetValue(ItemFormatProperty);
set => SetValue(ItemFormatProperty, value);
}
/// <summary>
/// Gets or sets whether the panel should loop
/// </summary>
public bool ShouldLoop
{
get => GetValue(ShouldLoopProperty);
set => SetValue(ShouldLoopProperty, value);
}
#endregion
#region
internal event EventHandler<CellHoverEventArgs>? CellHovered;
#endregion
//Backing fields for properties //Backing fields for properties
private int _minimumValue = 1; private int _minimumValue = 1;
private int _maximumValue = 2; private int _maximumValue = 2;
@ -76,7 +143,7 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
public DateTimePickerPanel() public DateTimePickerPanel()
{ {
FormatDate = DateTime.Now; FormatDate = DateTime.Now;
AddHandler(TappedEvent, OnItemTapped, RoutingStrategies.Bubble); AddHandler(TappedEvent, HandleItemTapped, RoutingStrategies.Bubble);
} }
static DateTimePickerPanel() static DateTimePickerPanel()
@ -86,43 +153,6 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
AffectsMeasure<DateTimePickerPanel>(ItemHeightProperty); AffectsMeasure<DateTimePickerPanel>(ItemHeightProperty);
} }
/// <summary>
/// Gets or sets what this panel displays in date or time units
/// </summary>
public DateTimePickerPanelType PanelType
{
get => GetValue(PanelTypeProperty);
set => SetValue(PanelTypeProperty, value);
}
/// <summary>
/// Gets or sets the height of each item
/// </summary>
public double ItemHeight
{
get => GetValue(ItemHeightProperty);
set => SetValue(ItemHeightProperty, value);
}
/// <summary>
/// Gets or sets the string format for the items, using standard
/// .net DateTime or TimeSpan formatting. Format must match panel type
/// </summary>
public string ItemFormat
{
get => GetValue(ItemFormatProperty);
set => SetValue(ItemFormatProperty, value);
}
/// <summary>
/// Gets or sets whether the panel should loop
/// </summary>
public bool ShouldLoop
{
get => GetValue(ShouldLoopProperty);
set => SetValue(ShouldLoopProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets the minimum value /// Gets or sets the minimum value
/// </summary> /// </summary>
@ -370,6 +400,7 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
{ {
UpdateItems(); UpdateItems();
RaiseScrollInvalidated(EventArgs.Empty); RaiseScrollInvalidated(EventArgs.Empty);
EnableCellHoverAnimation();
_hasInit = true; _hasInit = true;
} }
@ -532,7 +563,16 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
HorizontalContentAlignment = HorizontalAlignment.Center, HorizontalContentAlignment = HorizontalAlignment.Center,
Focusable = false, Focusable = false,
CornerRadius = new CornerRadius(0), CornerRadius = new CornerRadius(0),
SizeType = SizeType.Middle SizeType = SizeType.Middle,
DisabledItemHoverAnimation = true
};
item.PointerEntered += (sender, args) =>
{
if (sender is ListBoxItem target)
{
var cellValue = (int)target.Tag!;
CellHovered?.Invoke(this, new CellHoverEventArgs(new CellHoverInfo(PanelType, cellValue)));
}
}; };
TokenResourceBinder.CreateTokenBinding(item, TemplatedControl.PaddingProperty, TokenResourceBinder.CreateTokenBinding(item, TemplatedControl.PaddingProperty,
TimePickerTokenResourceKey.ItemPadding, BindingPriority.LocalValue); TimePickerTokenResourceKey.ItemPadding, BindingPriority.LocalValue);
@ -640,7 +680,7 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
return newValue; return newValue;
} }
private void OnItemTapped(object? sender, TappedEventArgs e) private void HandleItemTapped(object? sender, TappedEventArgs e)
{ {
if (e.Source is Visual source && if (e.Source is Visual source &&
GetItemFromSource(source) is ListBoxItem listBoxItem && GetItemFromSource(source) is ListBoxItem listBoxItem &&
@ -687,4 +727,14 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
Offset = Offset.WithY(snapY); Offset = Offset.WithY(snapY);
} }
} }
private void EnableCellHoverAnimation()
{
var children = Children;
for (var i = 0; i < children.Count; i++)
{
var item = (ListBoxItem)children[i];
item.DisabledItemHoverAnimation = false;
}
}
} }

View File

@ -104,7 +104,7 @@ public class RangeTimePicker : RangeInfoPickerInput
protected override Flyout CreatePickerFlyout() protected override Flyout CreatePickerFlyout()
{ {
return new RangeTimePickerFlyout(this); return new TimePickerFlyout();
} }
protected override void NotifyFlyoutAboutToClose(bool selectedIsValid) protected override void NotifyFlyoutAboutToClose(bool selectedIsValid)

View File

@ -1,31 +0,0 @@
using AtomUI.Data;
using Avalonia.Controls;
namespace AtomUI.Controls;
internal class RangeTimePickerFlyout : Flyout
{
internal RangeTimePicker RangeTimePickerRef { get; set; }
public RangeTimePickerFlyout(RangeTimePicker timePicker)
{
RangeTimePickerRef = timePicker;
}
protected override Control CreatePresenter()
{
var presenter = new RangeTimePickerFlyoutPresenter(RangeTimePickerRef);
BindUtils.RelayBind(this, IsShowArrowEffectiveProperty, presenter, IsShowArrowProperty);
BindUtils.RelayBind(RangeTimePickerRef, RangeTimePicker.MinuteIncrementProperty, presenter,
RangeTimePickerFlyoutPresenter.MinuteIncrementProperty);
BindUtils.RelayBind(RangeTimePickerRef, RangeTimePicker.SecondIncrementProperty, presenter,
RangeTimePickerFlyoutPresenter.SecondIncrementProperty);
BindUtils.RelayBind(RangeTimePickerRef, RangeTimePicker.ClockIdentifierProperty, presenter,
RangeTimePickerFlyoutPresenter.ClockIdentifierProperty);
CalculateShowArrowEffective();
SetupArrowPosition(Popup, presenter);
return presenter;
}
}

View File

@ -1,164 +0,0 @@
using System.Reactive.Disposables;
using AtomUI.Controls.Internal;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Layout;
namespace AtomUI.Controls;
internal class RangeTimePickerFlyoutPresenter : FlyoutPresenter
{
public static readonly StyledProperty<int> MinuteIncrementProperty =
TimePicker.MinuteIncrementProperty.AddOwner<RangeTimePickerFlyoutPresenter>();
public static readonly StyledProperty<int> SecondIncrementProperty =
TimePicker.SecondIncrementProperty.AddOwner<RangeTimePickerFlyoutPresenter>();
public static readonly StyledProperty<TimeSpan> TimeProperty =
AvaloniaProperty.Register<RangeTimePickerFlyoutPresenter, TimeSpan>(nameof(Time));
public static readonly StyledProperty<ClockIdentifierType> ClockIdentifierProperty =
TimePicker.ClockIdentifierProperty.AddOwner<RangeTimePickerFlyoutPresenter>();
public int MinuteIncrement
{
get => GetValue(MinuteIncrementProperty);
set => SetValue(MinuteIncrementProperty, value);
}
public int SecondIncrement
{
get => GetValue(SecondIncrementProperty);
set => SetValue(SecondIncrementProperty, value);
}
public TimeSpan Time
{
get => GetValue(TimeProperty);
set => SetValue(TimeProperty, value);
}
public ClockIdentifierType ClockIdentifier
{
get => GetValue(ClockIdentifierProperty);
set => SetValue(ClockIdentifierProperty, value);
}
protected override Type StyleKeyOverride => typeof(TimePickerFlyoutPresenter);
internal RangeTimePicker TimePickerRef { get; set; }
private TimePickerPresenter? _timePickerPresenter;
private CompositeDisposable? _compositeDisposable;
private Button? _confirmButton;
private Button? _nowButton;
public RangeTimePickerFlyoutPresenter(RangeTimePicker timePicker)
{
TimePickerRef = timePicker;
HorizontalAlignment = HorizontalAlignment.Left;
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_timePickerPresenter = e.NameScope.Get<TimePickerPresenter>(ArrowDecoratedBoxTheme.ContentPresenterPart);
_confirmButton = e.NameScope.Get<Button>(TimePickerFlyoutPresenterTheme.ConfirmButtonPart);
_nowButton = e.NameScope.Get<Button>(TimePickerFlyoutPresenterTheme.NowButtonPart);
if (_timePickerPresenter is not null)
{
_timePickerPresenter.Confirmed += (sender, args) =>
{
TimePickerRef.NotifyConfirmed(_timePickerPresenter.Time);
};
}
if (_confirmButton is not null)
{
_confirmButton.Click += HandleConfirmButtonClicked;
}
if (_nowButton is not null)
{
_nowButton.Click += HandleNowButtonClicked;
}
}
private void SetupTime()
{
if (TimePickerRef.RangeActivatedPart == RangeActivatedPart.Start)
{
if (TimePickerRef.RangeStartSelectedTime is not null)
{
Time = TimePickerRef.RangeStartSelectedTime.Value;
}
}
else if (TimePickerRef.RangeActivatedPart == RangeActivatedPart.End)
{
if (TimePickerRef.RangeEndSelectedTime is not null)
{
Time = TimePickerRef.RangeEndSelectedTime.Value;
}
}
}
private void HandleNowButtonClicked(object? sender, RoutedEventArgs args)
{
_timePickerPresenter?.NowConfirm();
TimePickerRef.ClosePickerFlyout();
}
private void HandleConfirmButtonClicked(object? sender, RoutedEventArgs args)
{
_timePickerPresenter?.Confirm();
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)
{
_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);
_compositeDisposable?.Dispose();
_compositeDisposable = null;
_timePickerPresenter!.Time = TimeSpan.Zero;
}
}

View File

@ -1,93 +0,0 @@
using AtomUI.Controls.Localization;
using AtomUI.Controls.TimePickerLang;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class RangeTimePickerFlyoutPresenterTheme : ArrowDecoratedBoxTheme
{
public const string ContentLayoutPart = "PART_ContentLayout";
public const string NowButtonPart = "PART_NowButton";
public const string ConfirmButtonPart = "PART_ConfirmButton";
public const string ButtonsContainerPart = "PART_ButtonsContainer";
public RangeTimePickerFlyoutPresenterTheme()
: base(typeof(RangeTimePickerFlyoutPresenter))
{
}
protected override Control BuildContent(INameScope scope)
{
var contentLayout = new DockPanel
{
Name = ContentLayoutPart
};
var contentPresenter = new TimePickerPresenter
{
Name = ContentPresenterPart,
IsShowHeader = false
};
CreateTemplateParentBinding(contentPresenter, TimePickerPresenter.MinuteIncrementProperty,
TimePickerFlyoutPresenter.MinuteIncrementProperty);
CreateTemplateParentBinding(contentPresenter, TimePickerPresenter.SecondIncrementProperty,
TimePickerFlyoutPresenter.SecondIncrementProperty);
CreateTemplateParentBinding(contentPresenter, TimePickerPresenter.ClockIdentifierProperty,
TimePickerFlyoutPresenter.ClockIdentifierProperty);
CreateTemplateParentBinding(contentPresenter, TimePickerPresenter.TimeProperty,
TimePickerFlyoutPresenter.TimeProperty);
contentPresenter.RegisterInNameScope(scope);
var buttons = new Panel
{
Name = ButtonsContainerPart
};
DockPanel.SetDock(buttons, Dock.Bottom);
var nowButton = new Button
{
ButtonType = ButtonType.Link,
Name = NowButtonPart,
HorizontalAlignment = HorizontalAlignment.Left,
SizeType = SizeType.Small
};
LanguageResourceBinder.CreateBinding(nowButton, Button.TextProperty, TimePickerLangResourceKey.Now);
nowButton.RegisterInNameScope(scope);
buttons.Children.Add(nowButton);
var confirmButton = new Button
{
Name = ConfirmButtonPart,
ButtonType = ButtonType.Primary,
SizeType = SizeType.Small,
HorizontalAlignment = HorizontalAlignment.Right
};
LanguageResourceBinder.CreateBinding(confirmButton, Button.TextProperty, CommonLangResourceKey.OkText);
confirmButton.RegisterInNameScope(scope);
buttons.Children.Add(confirmButton);
contentLayout.Children.Add(buttons);
contentLayout.Children.Add(contentPresenter);
return contentLayout;
}
protected override void BuildStyles()
{
base.BuildStyles();
var buttonsContainerStyle = new Style(selector => selector.Nesting().Template().Name(ButtonsContainerPart));
buttonsContainerStyle.Add(Layoutable.MarginProperty, TimePickerTokenResourceKey.ButtonsMargin);
Add(buttonsContainerStyle);
var contentPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ContentPresenterPart));
contentPresenterStyle.Add(Layoutable.MaxWidthProperty, TimePickerTokenResourceKey.PickerPopupWidth);
contentPresenterStyle.Add(Layoutable.HeightProperty, TimePickerTokenResourceKey.PickerPopupHeight);
Add(contentPresenterStyle);
}
}

View File

@ -1,8 +1,9 @@
using AtomUI.Controls.Internal; using AtomUI.Controls.Internal;
using AtomUI.Controls.Utils; using AtomUI.Controls.Utils;
using AtomUI.Data;
using Avalonia; using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data;
namespace AtomUI.Controls; namespace AtomUI.Controls;
@ -16,6 +17,12 @@ public class TimePicker : InfoPickerInput
{ {
#region #region
public static readonly StyledProperty<bool> IsNeedConfirmProperty =
AvaloniaProperty.Register<TimePicker, bool>(nameof(IsNeedConfirm));
public static readonly StyledProperty<bool> IsShowNowProperty =
AvaloniaProperty.Register<TimePicker, bool>(nameof(IsShowNow), true);
public static readonly StyledProperty<int> MinuteIncrementProperty = public static readonly StyledProperty<int> MinuteIncrementProperty =
AvaloniaProperty.Register<TimePicker, int>(nameof(MinuteIncrement), 1, coerce: CoerceMinuteIncrement); AvaloniaProperty.Register<TimePicker, int>(nameof(MinuteIncrement), 1, coerce: CoerceMinuteIncrement);
@ -27,13 +34,24 @@ public class TimePicker : InfoPickerInput
public static readonly StyledProperty<TimeSpan?> SelectedTimeProperty = public static readonly StyledProperty<TimeSpan?> SelectedTimeProperty =
AvaloniaProperty.Register<TimePicker, TimeSpan?>(nameof(SelectedTime), AvaloniaProperty.Register<TimePicker, TimeSpan?>(nameof(SelectedTime),
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true); enableDataValidation: true);
public static readonly StyledProperty<TimeSpan?> DefaultTimeProperty = public static readonly StyledProperty<TimeSpan?> DefaultTimeProperty =
AvaloniaProperty.Register<TimePicker, TimeSpan?>(nameof(DefaultTime), AvaloniaProperty.Register<TimePicker, TimeSpan?>(nameof(DefaultTime),
enableDataValidation: true); enableDataValidation: true);
public bool IsNeedConfirm
{
get => GetValue(IsNeedConfirmProperty);
set => SetValue(IsNeedConfirmProperty, value);
}
public bool IsShowNow
{
get => GetValue(IsShowNowProperty);
set => SetValue(IsShowNowProperty, value);
}
public int MinuteIncrement public int MinuteIncrement
{ {
get => GetValue(MinuteIncrementProperty); get => GetValue(MinuteIncrementProperty);
@ -66,37 +84,95 @@ public class TimePicker : InfoPickerInput
#endregion #endregion
internal void NotifyTemporaryTimeSelected(TimeSpan value) private TimePickerPresenter? _pickerPresenter;
{
Text = DateTimeUtils.FormatTimeSpan(value, ClockIdentifier == ClockIdentifierType.HourClock12);
}
internal void NotifyConfirmed(TimeSpan value)
{
_currentValidSelected = true;
SelectedTime = value;
}
protected override Flyout CreatePickerFlyout() protected override Flyout CreatePickerFlyout()
{ {
return new TimePickerFlyout(this); return new TimePickerFlyout();
}
protected override void NotifyFlyoutPresenterCreated(Control flyoutPresenter)
{
if (flyoutPresenter is TimePickerFlyoutPresenter timePickerFlyoutPresenter)
{
timePickerFlyoutPresenter.AttachedToVisualTree += (sender, args) =>
{
_pickerPresenter = timePickerFlyoutPresenter.TimePickerPresenter;
ConfigurePickerPresenter(_pickerPresenter);
};
}
}
private void ConfigurePickerPresenter(TimePickerPresenter? presenter)
{
if (presenter is null)
{
return;
}
BindUtils.RelayBind(this, MinuteIncrementProperty, presenter, TimePickerPresenter.MinuteIncrementProperty);
BindUtils.RelayBind(this, SecondIncrementProperty, presenter, TimePickerPresenter.SecondIncrementProperty);
BindUtils.RelayBind(this, ClockIdentifierProperty, presenter, TimePickerPresenter.ClockIdentifierProperty);
BindUtils.RelayBind(this, SelectedTimeProperty, presenter, TimePickerPresenter.SelectedTimeProperty);
BindUtils.RelayBind(this, IsNeedConfirmProperty, presenter, TimePickerPresenter.IsNeedConfirmProperty);
BindUtils.RelayBind(this, IsShowNowProperty, presenter, TimePickerPresenter.IsShowNowProperty);
}
protected override void NotifyFlyoutOpened()
{
base.NotifyFlyoutOpened();
if (_pickerPresenter is not null)
{
_pickerPresenter.ChoosingStatueChanged += HandleChoosingStatueChanged;
_pickerPresenter.HoverTimeChanged += HandleHoverTimeChanged;
_pickerPresenter.Confirmed += HandleConfirmed;
}
} }
protected override void NotifyFlyoutAboutToClose(bool selectedIsValid) protected override void NotifyFlyoutAboutToClose(bool selectedIsValid)
{ {
base.NotifyFlyoutAboutToClose(selectedIsValid); base.NotifyFlyoutAboutToClose(selectedIsValid);
if (!selectedIsValid) if (_pickerPresenter is not null)
{ {
if (SelectedTime.HasValue) _pickerPresenter.ChoosingStatueChanged -= HandleChoosingStatueChanged;
_pickerPresenter.HoverTimeChanged -= HandleHoverTimeChanged;
_pickerPresenter.Confirmed -= HandleConfirmed;
}
}
private void HandleChoosingStatueChanged(object? sender, ChoosingStatusEventArgs args)
{ {
Text = DateTimeUtils.FormatTimeSpan(SelectedTime.Value, _isChoosing = args.IsChoosing;
UpdatePseudoClasses();
if (!args.IsChoosing)
{
ClearHoverSelectedInfo();
}
}
private void ClearHoverSelectedInfo()
{
Text = DateTimeUtils.FormatTimeSpan(SelectedTime,
ClockIdentifier == ClockIdentifierType.HourClock12);
}
private void HandleHoverTimeChanged(object? sender, TimeSelectedEventArgs args)
{
if (args.Time.HasValue)
{
Text = DateTimeUtils.FormatTimeSpan(args.Time.Value,
ClockIdentifier == ClockIdentifierType.HourClock12); ClockIdentifier == ClockIdentifierType.HourClock12);
} }
else else
{ {
Clear(); Text = null;
} }
} }
private void HandleConfirmed(object? sender, EventArgs args)
{
SelectedTime = _pickerPresenter?.SelectedTime;
ClosePickerFlyout();
} }
/// <summary> /// <summary>
@ -124,21 +200,11 @@ public class TimePicker : InfoPickerInput
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);
if (VisualRoot is not null)
{
if (change.Property == SelectedTimeProperty) if (change.Property == SelectedTimeProperty)
{ {
if (SelectedTime.HasValue) Text = DateTimeUtils.FormatTimeSpan(SelectedTime,
{
Text = DateTimeUtils.FormatTimeSpan(SelectedTime.Value,
ClockIdentifier == ClockIdentifierType.HourClock12); ClockIdentifier == ClockIdentifierType.HourClock12);
} }
else
{
Clear();
}
}
}
} }
private static int CoerceMinuteIncrement(AvaloniaObject sender, int value) private static int CoerceMinuteIncrement(AvaloniaObject sender, int value)

View File

@ -5,28 +5,12 @@ namespace AtomUI.Controls;
internal class TimePickerFlyout : Flyout internal class TimePickerFlyout : Flyout
{ {
internal TimePicker TimePickerRef { get; set; }
public TimePickerFlyout(TimePicker timePicker)
{
TimePickerRef = timePicker;
}
protected override Control CreatePresenter() protected override Control CreatePresenter()
{ {
var presenter = new TimePickerFlyoutPresenter(TimePickerRef); var presenter = new TimePickerFlyoutPresenter();
BindUtils.RelayBind(this, IsShowArrowEffectiveProperty, presenter, IsShowArrowProperty); BindUtils.RelayBind(this, IsShowArrowEffectiveProperty, presenter, IsShowArrowProperty);
BindUtils.RelayBind(TimePickerRef, TimePicker.MinuteIncrementProperty, presenter,
TimePickerFlyoutPresenter.MinuteIncrementProperty);
BindUtils.RelayBind(TimePickerRef, TimePicker.SecondIncrementProperty, presenter,
TimePickerFlyoutPresenter.SecondIncrementProperty);
BindUtils.RelayBind(TimePickerRef, TimePicker.ClockIdentifierProperty, presenter,
TimePickerFlyoutPresenter.ClockIdentifierProperty);
BindUtils.RelayBind(TimePickerRef, TimePicker.SelectedTimeProperty, presenter,
TimePickerFlyoutPresenter.TimeProperty);
CalculateShowArrowEffective(); CalculateShowArrowEffective();
SetupArrowPosition(Popup, presenter); SetupArrowPosition(Popup, presenter);
return presenter; return presenter;

View File

@ -1,116 +1,16 @@
using Avalonia; using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Layout;
namespace AtomUI.Controls; namespace AtomUI.Controls;
internal class TimePickerFlyoutPresenter : FlyoutPresenter internal class TimePickerFlyoutPresenter : FlyoutPresenter
{ {
public static readonly StyledProperty<int> MinuteIncrementProperty =
TimePicker.MinuteIncrementProperty.AddOwner<TimePickerFlyoutPresenter>();
public static readonly StyledProperty<int> SecondIncrementProperty =
TimePicker.SecondIncrementProperty.AddOwner<TimePickerFlyoutPresenter>();
public static readonly StyledProperty<TimeSpan> TimeProperty =
AvaloniaProperty.Register<TimePickerFlyoutPresenter, TimeSpan>(nameof(Time));
public static readonly StyledProperty<ClockIdentifierType> ClockIdentifierProperty =
TimePicker.ClockIdentifierProperty.AddOwner<TimePickerFlyoutPresenter>();
public int MinuteIncrement
{
get => GetValue(MinuteIncrementProperty);
set => SetValue(MinuteIncrementProperty, value);
}
public int SecondIncrement
{
get => GetValue(SecondIncrementProperty);
set => SetValue(SecondIncrementProperty, value);
}
public TimeSpan Time
{
get => GetValue(TimeProperty);
set => SetValue(TimeProperty, value);
}
public ClockIdentifierType ClockIdentifier
{
get => GetValue(ClockIdentifierProperty);
set => SetValue(ClockIdentifierProperty, value);
}
protected override Type StyleKeyOverride => typeof(TimePickerFlyoutPresenter); protected override Type StyleKeyOverride => typeof(TimePickerFlyoutPresenter);
internal TimePickerPresenter? TimePickerPresenter;
internal TimePicker TimePickerRef { get; set; }
private TimePickerPresenter? _timePickerPresenter;
private IDisposable? _disposable;
private Button? _confirmButton;
private Button? _nowButton;
public TimePickerFlyoutPresenter(TimePicker timePicker)
{
TimePickerRef = timePicker;
HorizontalAlignment = HorizontalAlignment.Left;
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
_timePickerPresenter = e.NameScope.Get<TimePickerPresenter>(ArrowDecoratedBoxTheme.ContentPresenterPart); TimePickerPresenter = e.NameScope.Get<TimePickerPresenter>(TimePickerFlyoutPresenterTheme.TimePickerPresenterPart);
_confirmButton = e.NameScope.Get<Button>(TimePickerFlyoutPresenterTheme.ConfirmButtonPart);
_nowButton = e.NameScope.Get<Button>(TimePickerFlyoutPresenterTheme.NowButtonPart);
if (_timePickerPresenter is not null)
{
_timePickerPresenter.Confirmed += (sender, args) =>
{
TimePickerRef.NotifyConfirmed(_timePickerPresenter.Time);
};
}
if (_confirmButton is not null)
{
_confirmButton.Click += HandleConfirmButtonClicked;
}
if (_nowButton is not null)
{
_nowButton.Click += HandleNowButtonClicked;
}
}
private void HandleNowButtonClicked(object? sender, RoutedEventArgs args)
{
_timePickerPresenter?.NowConfirm();
TimePickerRef.ClosePickerFlyout();
}
private void HandleConfirmButtonClicked(object? sender, RoutedEventArgs args)
{
_timePickerPresenter?.Confirm();
TimePickerRef.ClosePickerFlyout();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (_timePickerPresenter is not null)
{
_disposable = TimePickerPresenter.TemporaryTimeProperty.Changed.Subscribe(args =>
{
TimePickerRef.NotifyTemporaryTimeSelected(args.GetNewValue<TimeSpan>());
});
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_disposable?.Dispose();
} }
} }

View File

@ -1,93 +1,30 @@
using AtomUI.Controls.Localization; using AtomUI.Theme.Styling;
using AtomUI.Controls.TimePickerLang;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls; namespace AtomUI.Controls;
[ControlThemeProvider] [ControlThemeProvider]
internal class TimePickerFlyoutPresenterTheme : ArrowDecoratedBoxTheme internal class TimePickerFlyoutPresenterTheme : ArrowDecoratedBoxTheme
{ {
public const string ContentLayoutPart = "PART_ContentLayout"; public const string TimePickerPresenterPart = "PART_TimePickerPresenter";
public const string NowButtonPart = "PART_NowButton";
public const string ConfirmButtonPart = "PART_ConfirmButton";
public const string ButtonsContainerPart = "PART_ButtonsContainer";
public TimePickerFlyoutPresenterTheme() public TimePickerFlyoutPresenterTheme()
: base(typeof(TimePickerFlyoutPresenter)) : this(typeof(TimePickerFlyoutPresenter))
{
}
protected TimePickerFlyoutPresenterTheme(Type targetType) : base(targetType)
{ {
} }
protected override Control BuildContent(INameScope scope) protected override Control BuildContent(INameScope scope)
{ {
var contentLayout = new DockPanel var timePickerPresenter = new TimePickerPresenter()
{ {
Name = ContentLayoutPart Name = TimePickerPresenterPart,
}; };
timePickerPresenter.RegisterInNameScope(scope);
var contentPresenter = new TimePickerPresenter return timePickerPresenter;
{
Name = ContentPresenterPart,
IsShowHeader = false
};
CreateTemplateParentBinding(contentPresenter, TimePickerPresenter.MinuteIncrementProperty,
TimePickerFlyoutPresenter.MinuteIncrementProperty);
CreateTemplateParentBinding(contentPresenter, TimePickerPresenter.SecondIncrementProperty,
TimePickerFlyoutPresenter.SecondIncrementProperty);
CreateTemplateParentBinding(contentPresenter, TimePickerPresenter.ClockIdentifierProperty,
TimePickerFlyoutPresenter.ClockIdentifierProperty);
CreateTemplateParentBinding(contentPresenter, TimePickerPresenter.TimeProperty,
TimePickerFlyoutPresenter.TimeProperty);
contentPresenter.RegisterInNameScope(scope);
var buttons = new Panel
{
Name = ButtonsContainerPart
};
DockPanel.SetDock(buttons, Dock.Bottom);
var nowButton = new Button
{
ButtonType = ButtonType.Link,
Name = NowButtonPart,
HorizontalAlignment = HorizontalAlignment.Left,
SizeType = SizeType.Small
};
LanguageResourceBinder.CreateBinding(nowButton, Button.TextProperty, TimePickerLangResourceKey.Now);
nowButton.RegisterInNameScope(scope);
buttons.Children.Add(nowButton);
var confirmButton = new Button
{
Name = ConfirmButtonPart,
ButtonType = ButtonType.Primary,
SizeType = SizeType.Small,
HorizontalAlignment = HorizontalAlignment.Right
};
LanguageResourceBinder.CreateBinding(confirmButton, Button.TextProperty, CommonLangResourceKey.OkText);
confirmButton.RegisterInNameScope(scope);
buttons.Children.Add(confirmButton);
contentLayout.Children.Add(buttons);
contentLayout.Children.Add(contentPresenter);
return contentLayout;
}
protected override void BuildStyles()
{
base.BuildStyles();
var buttonsContainerStyle = new Style(selector => selector.Nesting().Template().Name(ButtonsContainerPart));
buttonsContainerStyle.Add(Layoutable.MarginProperty, TimePickerTokenResourceKey.ButtonsMargin);
Add(buttonsContainerStyle);
var contentPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ContentPresenterPart));
contentPresenterStyle.Add(Layoutable.MaxWidthProperty, TimePickerTokenResourceKey.PickerPopupWidth);
contentPresenterStyle.Add(Layoutable.HeightProperty, TimePickerTokenResourceKey.PickerPopupHeight);
Add(contentPresenterStyle);
} }
} }

View File

@ -4,59 +4,48 @@ using AtomUI.Theme.Styling;
using AtomUI.Utils; using AtomUI.Utils;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
namespace AtomUI.Controls; namespace AtomUI.Controls;
/// <summary>
/// Defines the presenter used for selecting a time. Intended for use with
/// <see cref="TimePicker" /> but can be used independently
/// </summary>
[TemplatePart(TimePickerPresenterTheme.HourSelectorPart, typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart(TimePickerPresenterTheme.MinuteSelectorPart, typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart(TimePickerPresenterTheme.SecondSelectorPart, typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart(TimePickerPresenterTheme.PeriodHostPart, typeof(Panel), IsRequired = true)]
[TemplatePart(TimePickerPresenterTheme.PeriodSelectorPart, typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart(TimePickerPresenterTheme.PickerContainerPart, typeof(Grid), IsRequired = true)]
[TemplatePart(TimePickerPresenterTheme.SecondSpacerPart, typeof(Rectangle), IsRequired = true)]
internal class TimePickerPresenter : PickerPresenterBase internal class TimePickerPresenter : PickerPresenterBase
{ {
#region #region
/// <summary> public static readonly StyledProperty<bool> IsNeedConfirmProperty =
/// Defines the <see cref="MinuteIncrement" /> property TimePicker.IsNeedConfirmProperty.AddOwner<TimePickerPresenter>();
/// </summary>
public static readonly StyledProperty<bool> IsShowNowProperty =
TimePicker.IsShowNowProperty.AddOwner<TimePickerPresenter>();
public static readonly StyledProperty<int> MinuteIncrementProperty = public static readonly StyledProperty<int> MinuteIncrementProperty =
TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>(); TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>();
public static readonly StyledProperty<int> SecondIncrementProperty = public static readonly StyledProperty<int> SecondIncrementProperty =
TimePicker.SecondIncrementProperty.AddOwner<TimePickerPresenter>(); TimePicker.SecondIncrementProperty.AddOwner<TimePickerPresenter>();
/// <summary>
/// Defines the <see cref="ClockIdentifier" /> property
/// </summary>
public static readonly StyledProperty<ClockIdentifierType> ClockIdentifierProperty = public static readonly StyledProperty<ClockIdentifierType> ClockIdentifierProperty =
TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>(); TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>();
/// <summary> public static readonly StyledProperty<TimeSpan?> SelectedTimeProperty =
/// Defines the <see cref="Time" /> property TimePicker.SelectedTimeProperty.AddOwner<TimePickerPresenter>();
/// </summary>
public static readonly StyledProperty<TimeSpan> TimeProperty =
AvaloniaProperty.Register<TimePickerPresenter, TimeSpan>(nameof(Time));
public static readonly StyledProperty<TimeSpan> TemporaryTimeProperty = public bool IsNeedConfirm
AvaloniaProperty.Register<TimePickerPresenter, TimeSpan>(nameof(TemporaryTime)); {
get => GetValue(IsNeedConfirmProperty);
set => SetValue(IsNeedConfirmProperty, value);
}
public static readonly StyledProperty<bool> IsShowHeaderProperty = public bool IsShowNow
AvaloniaProperty.Register<TimePickerPresenter, bool>(nameof(IsShowHeader), true); {
get => GetValue(IsShowNowProperty);
set => SetValue(IsShowNowProperty, value);
}
/// <summary>
/// Gets or sets the minute increment in the selector
/// </summary>
public int MinuteIncrement public int MinuteIncrement
{ {
get => GetValue(MinuteIncrementProperty); get => GetValue(MinuteIncrementProperty);
@ -69,162 +58,54 @@ internal class TimePickerPresenter : PickerPresenterBase
set => SetValue(SecondIncrementProperty, value); set => SetValue(SecondIncrementProperty, value);
} }
/// <summary>
/// Gets or sets the current clock identifier, either 12HourClock or 24HourClock
/// </summary>
public ClockIdentifierType ClockIdentifier public ClockIdentifierType ClockIdentifier
{ {
get => GetValue(ClockIdentifierProperty); get => GetValue(ClockIdentifierProperty);
set => SetValue(ClockIdentifierProperty, value); set => SetValue(ClockIdentifierProperty, value);
} }
public TimeSpan? SelectedTime
{
get => GetValue(SelectedTimeProperty);
set => SetValue(SelectedTimeProperty, value);
}
#endregion
#region
internal static readonly DirectProperty<TimePickerPresenter, bool> ButtonsPanelVisibleProperty =
AvaloniaProperty.RegisterDirect<TimePickerPresenter, bool>(nameof(ButtonsPanelVisible),
o => o.ButtonsPanelVisible,
(o, v) => o.ButtonsPanelVisible = v);
private bool _buttonsPanelVisible = true;
internal bool ButtonsPanelVisible
{
get => _buttonsPanelVisible;
set => SetAndRaise(ButtonsPanelVisibleProperty, ref _buttonsPanelVisible, value);
}
#endregion
#region
/// <summary> /// <summary>
/// Gets or sets the current time /// 当前 Pointer 选中的日期和时间的变化事件
/// </summary> /// </summary>
public TimeSpan Time public event EventHandler<TimeSelectedEventArgs>? HoverTimeChanged;
{
get => GetValue(TimeProperty);
set => SetValue(TimeProperty, value);
}
public TimeSpan TemporaryTime /// <summary>
{ /// 当前是否处于选择中状态
get => GetValue(TemporaryTimeProperty); /// </summary>
set => SetValue(TemporaryTimeProperty, value); public event EventHandler<ChoosingStatusEventArgs>? ChoosingStatueChanged;
}
public bool IsShowHeader
{
get => GetValue(IsShowHeaderProperty);
set => SetValue(IsShowHeaderProperty, value);
}
#endregion #endregion
#region private IDisposable? _choosingStateDisposable;
private Button? _nowButton;
internal static readonly DirectProperty<TimePickerPresenter, double> SpacerThicknessProperty = private Button? _confirmButton;
AvaloniaProperty.RegisterDirect<TimePickerPresenter, double>(nameof(SpacerWidth), private TimeView? _timeView;
o => o.SpacerWidth,
(o, v) => o.SpacerWidth = v);
private double _spacerWidth;
public double SpacerWidth
{
get => _spacerWidth;
set => SetAndRaise(SpacerThicknessProperty, ref _spacerWidth, value);
}
#endregion
public TimePickerPresenter()
{
SetCurrentValue(TimeProperty, DateTime.Now.TimeOfDay);
}
static TimePickerPresenter()
{
KeyboardNavigation.TabNavigationProperty
.OverrideDefaultValue<TimePickerPresenter>(KeyboardNavigationMode.Cycle);
}
// TemplateItems
private Grid? _pickerContainer;
private Rectangle? _spacer3;
private Panel? _periodHost;
private TextBlock? _headerText;
private DateTimePickerPanel? _hourSelector;
private DateTimePickerPanel? _minuteSelector;
private DateTimePickerPanel? _secondSelector;
private DateTimePickerPanel? _periodSelector;
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
TokenResourceBinder.CreateGlobalTokenBinding(this, SpacerThicknessProperty, GlobalTokenResourceKey.LineWidth,
BindingPriority.Template,
new RenderScaleAwareDoubleConfigure(this));
InitPicker();
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_pickerContainer = e.NameScope.Get<Grid>(TimePickerPresenterTheme.PickerContainerPart);
_periodHost = e.NameScope.Get<Panel>(TimePickerPresenterTheme.PeriodHostPart);
_headerText = e.NameScope.Get<TextBlock>(TimePickerPresenterTheme.HeaderTextPart);
_hourSelector = e.NameScope.Get<DateTimePickerPanel>(TimePickerPresenterTheme.HourSelectorPart);
_minuteSelector = e.NameScope.Get<DateTimePickerPanel>(TimePickerPresenterTheme.MinuteSelectorPart);
_secondSelector = e.NameScope.Get<DateTimePickerPanel>(TimePickerPresenterTheme.SecondSelectorPart);
_periodSelector = e.NameScope.Get<DateTimePickerPanel>(TimePickerPresenterTheme.PeriodSelectorPart);
if (_hourSelector is not null)
{
_hourSelector.SelectionChanged += HandleSelectionChanged;
}
if (_minuteSelector is not null)
{
_minuteSelector.SelectionChanged += HandleSelectionChanged;
}
if (_secondSelector is not null)
{
_secondSelector.SelectionChanged += HandleSelectionChanged;
}
if (_periodSelector is not null)
{
_periodSelector.SelectionChanged += HandleSelectionChanged;
}
_spacer3 = e.NameScope.Get<Rectangle>(TimePickerPresenterTheme.ThirdSpacerPart);
}
private void HandleSelectionChanged(object? sender, EventArgs args)
{
var selectedValue = CollectValue();
TemporaryTime = selectedValue;
if (IsShowHeader)
{
if (_headerText is not null)
{
_headerText.Text =
DateTimeUtils.FormatTimeSpan(selectedValue, ClockIdentifier == ClockIdentifierType.HourClock12);
}
}
}
private TimeSpan CollectValue()
{
var hour = _hourSelector!.SelectedValue;
var minute = _minuteSelector!.SelectedValue;
var second = _secondSelector!.SelectedValue;
var period = _periodSelector!.SelectedValue;
if (ClockIdentifier == ClockIdentifierType.HourClock12)
{
hour = period == 1 ? hour == 12 ? 12 : hour + 12 : period == 0 && hour == 12 ? 0 : hour;
}
return new TimeSpan(hour, minute, second);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == MinuteIncrementProperty ||
change.Property == SecondIncrementProperty ||
change.Property == ClockIdentifierProperty ||
change.Property == TimeProperty)
{
InitPicker();
}
}
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
@ -252,63 +133,126 @@ internal class TimePickerPresenter : PickerPresenterBase
base.OnKeyDown(e); base.OnKeyDown(e);
} }
protected override void OnConfirmed() protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
var value = CollectValue(); base.OnApplyTemplate(e);
SetCurrentValue(TimeProperty, value); _nowButton = e.NameScope.Get<Button>(TimePickerPresenterTheme.NowButtonPart);
base.OnConfirmed(); _confirmButton = e.NameScope.Get<Button>(TimePickerPresenterTheme.ConfirmButtonPart);
_timeView = e.NameScope.Get<TimeView>(TimePickerPresenterTheme.TimeViewPart);
SetupButtonStatus();
if (_timeView is not null)
{
_timeView.HoverTimeChanged += HandleTimeViewDateHoverChanged;
_timeView.TimeSelected += HandleTimeViewDateSelected;
} }
internal void NowConfirm() if (_nowButton is not null)
{ {
SetCurrentValue(TimeProperty, DateTime.Now.TimeOfDay); _nowButton.Click += HandleNowButtonClicked;
base.OnConfirmed();
} }
internal void Confirm() if (_confirmButton is not null)
{ {
OnConfirmed(); _confirmButton.Click += HandleConfirmButtonClicked;
_confirmButton.IsEnabled = SelectedTime is not null;
_confirmButton.PointerEntered += (sender, args) =>
{
if (_timeView?.SelectedTime is not null)
{
HoverTimeChanged?.Invoke(this, new TimeSelectedEventArgs(_timeView?.SelectedTime));
}
};
_confirmButton.PointerExited += (sender, args) =>
{
ChoosingStatueChanged?.Invoke(this, new ChoosingStatusEventArgs(false));
};
}
} }
internal void Dismiss() private void SetupButtonStatus()
{ {
OnDismiss(); if (_nowButton is null || _confirmButton is null)
}
private void InitPicker()
{
if (_pickerContainer == null)
{ {
return; return;
} }
var clock12 = ClockIdentifier == ClockIdentifierType.HourClock12; _confirmButton.IsVisible = IsNeedConfirm;
var use24HourClock = ClockIdentifier == ClockIdentifierType.HourClock24;
_hourSelector!.MaximumValue = clock12 ? 12 : 23;
_hourSelector.MinimumValue = clock12 ? 1 : 0;
_hourSelector.ItemFormat = "%h";
var hour = Time.Hours;
_hourSelector.SelectedValue = !clock12 ? hour :
hour > 12 ? hour - 12 :
hour == 0 ? 12 : hour;
_minuteSelector!.MaximumValue = 59; if (IsShowNow)
_minuteSelector.MinimumValue = 0; {
_minuteSelector.Increment = MinuteIncrement; if (!IsNeedConfirm)
_minuteSelector.SelectedValue = Time.Minutes; {
_minuteSelector.ItemFormat = "mm"; _nowButton.HorizontalAlignment = HorizontalAlignment.Center;
}
else
{
_nowButton.HorizontalAlignment = HorizontalAlignment.Left;
}
}
else
{
_nowButton.IsVisible = false;
_nowButton.HorizontalAlignment = HorizontalAlignment.Left;
}
_secondSelector!.MaximumValue = 59; ButtonsPanelVisible = _nowButton.IsVisible || _confirmButton.IsVisible;
_secondSelector.MinimumValue = 0; }
_secondSelector.Increment = SecondIncrement;
_secondSelector.SelectedValue = Time.Seconds;
_secondSelector.ItemFormat = "ss";
_periodSelector!.MaximumValue = 1; private void HandleNowButtonClicked(object? sender, RoutedEventArgs args)
_periodSelector.MinimumValue = 0; {
_periodSelector.SelectedValue = hour >= 12 ? 1 : 0; SelectedTime = DateTime.Now.TimeOfDay;
if (!IsNeedConfirm)
{
OnConfirmed();
}
}
_spacer3!.IsVisible = !use24HourClock; private void HandleConfirmButtonClicked(object? sender, RoutedEventArgs args)
_periodHost!.IsVisible = !use24HourClock; {
if (SelectedTime is not null)
{
OnConfirmed();
}
}
private void HandleTimeViewDateHoverChanged(object? sender, TimeSelectedEventArgs args)
{
HoverTimeChanged?.Invoke(this, new TimeSelectedEventArgs(args.Time));
}
private void HandleTimeViewDateSelected(object? sender, TimeSelectedEventArgs args)
{
SelectedTime = args.Time;
if (!IsNeedConfirm)
{
OnConfirmed();
}
}
protected override void OnConfirmed()
{
base.OnConfirmed();
ChoosingStatueChanged?.Invoke(this, new ChoosingStatusEventArgs(false));
}
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)));
if (_timeView is not null)
{
_choosingStateDisposable = TimeView.IsPointerInSelectorProperty.Changed.Subscribe(args =>
{
ChoosingStatueChanged?.Invoke(this, new ChoosingStatusEventArgs(args.GetNewValue<bool>()));
});
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_choosingStateDisposable?.Dispose();
_choosingStateDisposable = null;
} }
} }

View File

@ -1,13 +1,11 @@
using AtomUI.Theme; using AtomUI.Controls.DatePickerLang;
using AtomUI.Controls.Localization;
using AtomUI.Theme;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling; using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
namespace AtomUI.Controls; namespace AtomUI.Controls;
@ -15,23 +13,12 @@ namespace AtomUI.Controls;
[ControlThemeProvider] [ControlThemeProvider]
internal class TimePickerPresenterTheme : BaseControlTheme internal class TimePickerPresenterTheme : BaseControlTheme
{ {
public const string MainContainerPart = "PART_MainContainer"; public const string MainLayoutPart = "PART_MainLayout";
public const string PickerContainerPart = "PART_PickerContainer"; public const string NowButtonPart = "PART_NowButton";
public const string HeaderTextPart = "PART_HeaderText"; public const string ConfirmButtonPart = "PART_ConfirmButton";
public const string ButtonsLayoutPart = "PART_ButtonsLayout";
public const string HourHostPart = "PART_HourHost"; public const string ButtonsFramePart = "PART_ButtonsFrame";
public const string MinuteHostPart = "PART_MinuteHost"; public const string TimeViewPart = "PART_TimeView";
public const string SecondHostPart = "PART_SecondHost";
public const string PeriodHostPart = "PART_PeriodHost";
public const string HourSelectorPart = "PART_HourSelector";
public const string MinuteSelectorPart = "PART_MinuteSelector";
public const string SecondSelectorPart = "PART_SecondSelector";
public const string PeriodSelectorPart = "PART_PeriodSelector";
public const string FirstSpacerPart = "PART_FirstSpacer";
public const string SecondSpacerPart = "PART_SecondSpacer";
public const string ThirdSpacerPart = "PART_ThirdSpacer";
public TimePickerPresenterTheme() public TimePickerPresenterTheme()
: base(typeof(TimePickerPresenter)) : base(typeof(TimePickerPresenter))
@ -42,222 +29,89 @@ internal class TimePickerPresenterTheme : BaseControlTheme
{ {
return new FuncControlTemplate<TimePickerPresenter>((presenter, scope) => return new FuncControlTemplate<TimePickerPresenter>((presenter, scope) =>
{ {
var mainContainer = new Grid var mainLayout = new DockPanel()
{ {
Name = MainContainerPart, Name = MainLayoutPart,
RowDefinitions = new RowDefinitions LastChildFill = true
{
new(GridLength.Auto),
new(GridLength.Auto),
new(GridLength.Star)
}
}; };
BuildHeader(mainContainer, scope);
BuildHosts(mainContainer, scope); var calendarView = BuildTimeView(presenter, scope);
return mainContainer;
var buttonsContainerFrame = new Border()
{
Name = ButtonsFramePart
};
CreateTemplateParentBinding(buttonsContainerFrame, Border.BorderThicknessProperty, TimePickerPresenter.BorderThicknessProperty);
var buttonsPanel = BuildButtons(presenter, scope);
CreateTemplateParentBinding(buttonsContainerFrame, Border.IsVisibleProperty, TimePickerPresenter.ButtonsPanelVisibleProperty);
buttonsContainerFrame.Child = buttonsPanel;
DockPanel.SetDock(buttonsContainerFrame, Dock.Bottom);
mainLayout.Children.Add(buttonsContainerFrame);
mainLayout.Children.Add(calendarView);
return mainLayout;
}); });
} }
private void BuildHeader(Grid mainContainer, INameScope scope) protected virtual Control BuildTimeView(TimePickerPresenter presenter, INameScope scope)
{ {
var headerText = new TextBlock var timeView = new TimeView()
{ {
Name = HeaderTextPart Name = TimeViewPart,
IsShowHeader = false
}; };
headerText.RegisterInNameScope(scope); CreateTemplateParentBinding(timeView, TimeView.SelectedTimeProperty, TimePickerPresenter.SelectedTimeProperty);
CreateTemplateParentBinding(headerText, Visual.IsVisibleProperty, TimePickerPresenter.IsShowHeaderProperty); timeView.RegisterInNameScope(scope);
mainContainer.Children.Add(headerText); return timeView;
Grid.SetRow(headerText, 0);
var separator = new Rectangle
{
HorizontalAlignment = HorizontalAlignment.Stretch
};
CreateTemplateParentBinding(separator, Layoutable.HeightProperty, TimePickerPresenter.SpacerThicknessProperty);
CreateTemplateParentBinding(separator, Visual.IsVisibleProperty, TimePickerPresenter.IsShowHeaderProperty);
TokenResourceBinder.CreateGlobalTokenBinding(separator, Shape.FillProperty,
GlobalTokenResourceKey.ColorBorder);
mainContainer.Children.Add(separator);
Grid.SetRow(separator, 1);
} }
private void BuildHosts(Grid mainContainer, INameScope scope) protected virtual Panel BuildButtons(TimePickerPresenter presenter, INameScope scope)
{ {
var pickerContainer = new Grid var buttonsPanel = new Panel()
{ {
Name = PickerContainerPart, Name = ButtonsLayoutPart
ColumnDefinitions = new ColumnDefinitions
{
new(GridLength.Star),
new(GridLength.Auto),
new(GridLength.Star),
new(GridLength.Auto),
new(GridLength.Star),
new(GridLength.Auto),
new(GridLength.Star)
}
}; };
pickerContainer.RegisterInNameScope(scope);
var hourHost = new Panel var nowButton = new Button
{ {
Name = HourHostPart ButtonType = ButtonType.Link,
Name = NowButtonPart,
HorizontalAlignment = HorizontalAlignment.Left,
SizeType = SizeType.Small
}; };
Grid.SetColumn(hourHost, 0); LanguageResourceBinder.CreateBinding(nowButton, Button.TextProperty, DatePickerLangResourceKey.Now);
hourHost.RegisterInNameScope(scope); nowButton.RegisterInNameScope(scope);
pickerContainer.Children.Add(hourHost); buttonsPanel.Children.Add(nowButton);
{
var scrollViewer = new ScrollViewer
{
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden
};
var hourSelector = new DateTimePickerPanel
{
Name = HourSelectorPart,
PanelType = DateTimePickerPanelType.Hour,
ShouldLoop = true
};
hourSelector.RegisterInNameScope(scope);
TokenResourceBinder.CreateTokenBinding(hourSelector, DateTimePickerPanel.ItemHeightProperty,
TimePickerTokenResourceKey.ItemHeight);
scrollViewer.Content = hourSelector;
hourHost.Children.Add(scrollViewer);
}
var firstSpacer = new Rectangle var confirmButton = new Button
{ {
Name = FirstSpacerPart, Name = ConfirmButtonPart,
HorizontalAlignment = HorizontalAlignment.Center ButtonType = ButtonType.Primary,
SizeType = SizeType.Small,
HorizontalAlignment = HorizontalAlignment.Right
}; };
firstSpacer.RegisterInNameScope(scope); LanguageResourceBinder.CreateBinding(confirmButton, Button.TextProperty, CommonLangResourceKey.OkText);
CreateTemplateParentBinding(firstSpacer, Layoutable.WidthProperty, TimePickerPresenter.SpacerThicknessProperty); confirmButton.RegisterInNameScope(scope);
TokenResourceBinder.CreateGlobalTokenBinding(firstSpacer, Shape.FillProperty, buttonsPanel.Children.Add(confirmButton);
GlobalTokenResourceKey.ColorBorder);
Grid.SetColumn(firstSpacer, 1);
pickerContainer.Children.Add(firstSpacer);
var minuteHost = new Panel return buttonsPanel;
{
Name = MinuteHostPart
};
Grid.SetColumn(minuteHost, 2);
minuteHost.RegisterInNameScope(scope);
pickerContainer.Children.Add(minuteHost);
{
var scrollViewer = new ScrollViewer
{
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden
};
var minuteSelector = new DateTimePickerPanel
{
Name = MinuteSelectorPart,
PanelType = DateTimePickerPanelType.Minute,
ShouldLoop = true
};
minuteSelector.RegisterInNameScope(scope);
TokenResourceBinder.CreateTokenBinding(minuteSelector, DateTimePickerPanel.ItemHeightProperty,
TimePickerTokenResourceKey.ItemHeight);
scrollViewer.Content = minuteSelector;
minuteHost.Children.Add(scrollViewer);
}
var secondSpacer = new Rectangle
{
Name = SecondSpacerPart,
HorizontalAlignment = HorizontalAlignment.Center
};
CreateTemplateParentBinding(secondSpacer, Layoutable.WidthProperty,
TimePickerPresenter.SpacerThicknessProperty);
secondSpacer.RegisterInNameScope(scope);
TokenResourceBinder.CreateGlobalTokenBinding(secondSpacer, Shape.FillProperty,
GlobalTokenResourceKey.ColorBorder);
Grid.SetColumn(secondSpacer, 3);
pickerContainer.Children.Add(secondSpacer);
var secondHost = new Panel
{
Name = SecondHostPart
};
Grid.SetColumn(secondHost, 4);
secondHost.RegisterInNameScope(scope);
pickerContainer.Children.Add(secondHost);
{
var scrollViewer = new ScrollViewer
{
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden
};
var secondSelector = new DateTimePickerPanel
{
Name = SecondSelectorPart,
PanelType = DateTimePickerPanelType.Second,
ShouldLoop = true
};
secondSelector.RegisterInNameScope(scope);
TokenResourceBinder.CreateTokenBinding(secondSelector, DateTimePickerPanel.ItemHeightProperty,
TimePickerTokenResourceKey.ItemHeight);
scrollViewer.Content = secondSelector;
secondHost.Children.Add(scrollViewer);
}
var thirdSpacer = new Rectangle
{
Name = ThirdSpacerPart,
HorizontalAlignment = HorizontalAlignment.Center
};
CreateTemplateParentBinding(thirdSpacer, Layoutable.WidthProperty, TimePickerPresenter.SpacerThicknessProperty);
thirdSpacer.RegisterInNameScope(scope);
Grid.SetColumn(thirdSpacer, 5);
pickerContainer.Children.Add(thirdSpacer);
TokenResourceBinder.CreateGlobalTokenBinding(thirdSpacer, Shape.FillProperty,
GlobalTokenResourceKey.ColorBorder);
var periodHost = new Panel
{
Name = PeriodHostPart
};
Grid.SetColumn(periodHost, 6);
periodHost.RegisterInNameScope(scope);
pickerContainer.Children.Add(periodHost);
{
var scrollViewer = new ScrollViewer
{
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden
};
var periodSelector = new DateTimePickerPanel
{
Name = PeriodSelectorPart,
PanelType = DateTimePickerPanelType.TimePeriod,
ShouldLoop = false
};
periodSelector.RegisterInNameScope(scope);
TokenResourceBinder.CreateTokenBinding(periodSelector, DateTimePickerPanel.ItemHeightProperty,
TimePickerTokenResourceKey.ItemHeight);
scrollViewer.Content = periodSelector;
periodHost.Children.Add(scrollViewer);
}
mainContainer.Children.Add(pickerContainer);
mainContainer.RegisterInNameScope(scope);
Grid.SetRow(pickerContainer, 2);
} }
protected override void BuildStyles() protected override void BuildStyles()
{ {
var commonStyle = new Style(selector => selector.Nesting()); 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.FontWeightProperty, FontWeight.SemiBold);
commonStyle.Add(headerTextStyle); commonStyle.Add(TimePickerPresenter.WidthProperty, TimePickerTokenResourceKey.PickerPopupWidth);
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);
var buttonsPanelStyle = new Style(selector => selector.Nesting().Template().Name(ButtonsLayoutPart));
buttonsPanelStyle.Add(Panel.MarginProperty, DatePickerTokenResourceKey.ButtonsPanelMargin);
commonStyle.Add(buttonsPanelStyle);
Add(commonStyle); Add(commonStyle);
} }
} }

View File

@ -1,6 +1,7 @@
using AtomUI.Controls.Internal; using AtomUI.Controls.Internal;
using AtomUI.Theme.Styling; using AtomUI.Theme.Styling;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Styling;
namespace AtomUI.Controls; namespace AtomUI.Controls;
@ -18,4 +19,13 @@ internal class TimePickerTheme : InfoPickerInputTheme
Kind = "ClockCircleOutlined" Kind = "ClockCircleOutlined"
}; };
} }
protected override void BuildStyles()
{
base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(TimePicker.MinWidthProperty, TimePickerTokenResourceKey.PickerInputMinWidth);
Add(commonStyle);
}
} }

View File

@ -48,6 +48,11 @@ internal class TimePickerToken : AbstractControlDesignToken
/// </summary> /// </summary>
public double RangePickerIndicatorThickness { get; set; } public double RangePickerIndicatorThickness { get; set; }
/// <summary>
/// 时间选择器最小的宽度
/// </summary>
public double PickerInputMinWidth { get; set; }
internal override void CalculateFromAlias() internal override void CalculateFromAlias()
{ {
base.CalculateFromAlias(); base.CalculateFromAlias();
@ -55,8 +60,9 @@ internal class TimePickerToken : AbstractControlDesignToken
ItemPadding = new Thickness(0, _globalToken.PaddingXXS); ItemPadding = new Thickness(0, _globalToken.PaddingXXS);
ButtonsMargin = new Thickness(0, _globalToken.MarginXS, 0, 0); ButtonsMargin = new Thickness(0, _globalToken.MarginXS, 0, 0);
PickerPopupWidth = 200; PickerPopupWidth = 200;
PickerPopupHeight = ItemHeight * 7; PickerPopupHeight = ItemHeight * 8;
RangePickerArrowMargin = new Thickness(_globalToken.MarginXS, 0); RangePickerArrowMargin = new Thickness(_globalToken.MarginXS, 0);
RangePickerIndicatorThickness = _globalToken.LineWidthFocus; RangePickerIndicatorThickness = _globalToken.LineWidthFocus;
PickerInputMinWidth = 120;
} }
} }

View File

@ -0,0 +1,379 @@
using AtomUI.Controls.Utils;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Raw;
namespace AtomUI.Controls;
internal class TimeSelectedEventArgs : EventArgs
{
public TimeSpan? Time { get; }
public TimeSelectedEventArgs(TimeSpan? value)
{
Time = value;
}
}
[TemplatePart(TimeViewTheme.HourSelectorPart, typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart(TimeViewTheme.MinuteSelectorPart, typeof(DateTimePickerPanel), IsRequired = true)]
[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.SecondSpacerPart, typeof(Rectangle), IsRequired = true)]
internal class TimeView : TemplatedControl
{
#region
public static readonly StyledProperty<int> MinuteIncrementProperty =
TimePicker.MinuteIncrementProperty.AddOwner<TimeView>();
public static readonly StyledProperty<int> SecondIncrementProperty =
TimePicker.SecondIncrementProperty.AddOwner<TimeView>();
public static readonly StyledProperty<ClockIdentifierType> ClockIdentifierProperty =
TimePicker.ClockIdentifierProperty.AddOwner<TimeView>();
public static readonly StyledProperty<TimeSpan> SelectedTimeProperty =
AvaloniaProperty.Register<TimeView, TimeSpan>(nameof(SelectedTime));
public static readonly StyledProperty<bool> IsShowHeaderProperty =
AvaloniaProperty.Register<TimeView, bool>(nameof(IsShowHeader), true);
/// <summary>
/// Gets or sets the minute increment in the selector
/// </summary>
public int MinuteIncrement
{
get => GetValue(MinuteIncrementProperty);
set => SetValue(MinuteIncrementProperty, value);
}
public int SecondIncrement
{
get => GetValue(SecondIncrementProperty);
set => SetValue(SecondIncrementProperty, value);
}
/// <summary>
/// Gets or sets the current clock identifier, either 12HourClock or 24HourClock
/// </summary>
public ClockIdentifierType ClockIdentifier
{
get => GetValue(ClockIdentifierProperty);
set => SetValue(ClockIdentifierProperty, value);
}
public TimeSpan SelectedTime
{
get => GetValue(SelectedTimeProperty);
set => SetValue(SelectedTimeProperty, value);
}
public bool IsShowHeader
{
get => GetValue(IsShowHeaderProperty);
set => SetValue(IsShowHeaderProperty, value);
}
#endregion
#region
internal static readonly DirectProperty<TimeView, double> SpacerThicknessProperty =
AvaloniaProperty.RegisterDirect<TimeView, double>(nameof(SpacerWidth),
o => o.SpacerWidth,
(o, v) => o.SpacerWidth = v);
internal static readonly StyledProperty<bool> IsPointerInSelectorProperty =
AvaloniaProperty.Register<Calendar, bool>(nameof(IsPointerInSelector), false);
private double _spacerWidth;
public double SpacerWidth
{
get => _spacerWidth;
set => SetAndRaise(SpacerThicknessProperty, ref _spacerWidth, value);
}
internal bool IsPointerInSelector
{
get => GetValue(IsPointerInSelectorProperty);
set => SetValue(IsPointerInSelectorProperty, value);
}
#endregion
#region
public event EventHandler<TimeSelectedEventArgs>? TimeSelected;
public event EventHandler<TimeSelectedEventArgs>? HoverTimeChanged;
#endregion
static TimeView()
{
KeyboardNavigation.TabNavigationProperty
.OverrideDefaultValue<TimeView>(KeyboardNavigationMode.Cycle);
}
// TemplateItems
private Grid? _pickerContainer;
private Rectangle? _spacer3;
private Panel? _periodHost;
private TextBlock? _headerText;
private DateTimePickerPanel? _hourSelector;
private DateTimePickerPanel? _minuteSelector;
private DateTimePickerPanel? _secondSelector;
private DateTimePickerPanel? _periodSelector;
private IDisposable? _pointerPositionDisposable;
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
TokenResourceBinder.CreateGlobalTokenBinding(this, SpacerThicknessProperty, GlobalTokenResourceKey.LineWidth,
BindingPriority.Template,
new RenderScaleAwareDoubleConfigure(this));
var inputManager = AvaloniaLocator.Current.GetService<IInputManager>()!;
_pointerPositionDisposable = inputManager.Process.Subscribe(DetectPointerPosition);
}
private void DetectPointerPosition(RawInputEventArgs args)
{
if (args is RawPointerEventArgs pointerEventArgs)
{
if (!CheckPointerInSelectors(pointerEventArgs.Position))
{
IsPointerInSelector = false;
}
else
{
IsPointerInSelector = true;
}
}
}
protected virtual bool CheckPointerInSelectors(Point position)
{
if (ClockIdentifier == ClockIdentifierType.HourClock12)
{
return CheckPointerInSelector(_hourSelector, position) ||
CheckPointerInSelector(_minuteSelector, position) ||
CheckPointerInSelector(_secondSelector, position) ||
CheckPointerInSelector(_periodSelector, position);
}
return CheckPointerInSelector(_hourSelector, position) ||
CheckPointerInSelector(_minuteSelector, position) ||
CheckPointerInSelector(_secondSelector, position);
}
private bool CheckPointerInSelector(DateTimePickerPanel? selector, Point position)
{
if (selector is null)
{
return false;
}
var globalRect = GetSelectorGlobalRect(selector);
return globalRect.Contains(position);
}
private Rect GetSelectorGlobalRect(DateTimePickerPanel selector)
{
var pos = selector.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(selector)!) ?? default;
return new Rect(pos, selector.Bounds.Size);
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_pointerPositionDisposable?.Dispose();
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_pickerContainer = e.NameScope.Get<Grid>(TimeViewTheme.PickerContainerPart);
_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);
if (_hourSelector is not null)
{
_hourSelector.SelectionChanged += HandleSelectionChanged;
_hourSelector.CellHovered += HandleSelectorCellHovered;
}
if (_minuteSelector is not null)
{
_minuteSelector.SelectionChanged += HandleSelectionChanged;
_minuteSelector.CellHovered += HandleSelectorCellHovered;
}
if (_secondSelector is not null)
{
_secondSelector.SelectionChanged += HandleSelectionChanged;
_secondSelector.CellHovered += HandleSelectorCellHovered;
}
if (_periodSelector is not null)
{
_periodSelector.SelectionChanged += HandleSelectionChanged;
_periodSelector.CellHovered += HandleSelectorCellHovered;
}
_spacer3 = e.NameScope.Get<Rectangle>(TimeViewTheme.ThirdSpacerPart);
InitPicker();
}
private void HandleSelectorCellHovered(object? sender, CellHoverEventArgs args)
{
var selectedTime = CollectValue(false);
var hour = selectedTime.Hours;
var minute = selectedTime.Minutes;
var second = selectedTime.Seconds;
var period = _secondSelector?.SelectedValue ?? default;
var cellHoverInfo = args.CellHoverInfo;
if (cellHoverInfo.HasValue)
{
var panelType = cellHoverInfo.Value.PanelType;
var cellValue = cellHoverInfo.Value.CellValue;
if (panelType == DateTimePickerPanelType.Hour)
{
hour = cellValue;
if (ClockIdentifier == ClockIdentifierType.HourClock12)
{
if (period == 0 && hour == 12)
{
// AM
hour = 0;
}
if (period == 1 && hour != 12)
{
hour += 12;
}
}
} else if (panelType == DateTimePickerPanelType.Minute)
{
minute = cellValue;
} else if (panelType == DateTimePickerPanelType.Second)
{
second = cellValue;
} else if (panelType == DateTimePickerPanelType.TimePeriod)
{
period = cellValue;
if (period == 0 && hour == 12)
{
// AM
hour = 0;
}
if (period == 1 && hour != 12)
{
hour += 12;
}
}
var hoverTime = new TimeSpan(hour, minute, second);
HoverTimeChanged?.Invoke(this, new TimeSelectedEventArgs(hoverTime));
}
}
private void HandleSelectionChanged(object? sender, EventArgs args)
{
var selectedValue = CollectValue();
if (IsShowHeader)
{
if (_headerText is not null)
{
_headerText.Text =
DateTimeUtils.FormatTimeSpan(selectedValue, ClockIdentifier == ClockIdentifierType.HourClock12);
}
}
}
private TimeSpan CollectValue(bool translate = true)
{
var hour = _hourSelector!.SelectedValue;
var minute = _minuteSelector!.SelectedValue;
var second = _secondSelector!.SelectedValue;
var period = _periodSelector!.SelectedValue;
if (translate)
{
if (ClockIdentifier == ClockIdentifierType.HourClock12)
{
hour = period == 1 ? hour == 12 ? 12 : hour + 12 : period == 0 && hour == 12 ? 0 : hour;
}
}
return new TimeSpan(hour, minute, second);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == MinuteIncrementProperty ||
change.Property == SecondIncrementProperty ||
change.Property == ClockIdentifierProperty ||
change.Property == SelectedTimeProperty)
{
InitPicker();
}
}
private void InitPicker()
{
if (_pickerContainer == null)
{
return;
}
var clock12 = ClockIdentifier == ClockIdentifierType.HourClock12;
var use24HourClock = ClockIdentifier == ClockIdentifierType.HourClock24;
_hourSelector!.MaximumValue = clock12 ? 12 : 23;
_hourSelector.MinimumValue = clock12 ? 1 : 0;
_hourSelector.ItemFormat = "%h";
var hour = SelectedTime.Hours;
_hourSelector.SelectedValue = !clock12 ? hour :
hour > 12 ? hour - 12 :
hour == 0 ? 12 : hour;
_minuteSelector!.MaximumValue = 59;
_minuteSelector.MinimumValue = 0;
_minuteSelector.Increment = MinuteIncrement;
_minuteSelector.SelectedValue = SelectedTime.Minutes;
_minuteSelector.ItemFormat = "mm";
_secondSelector!.MaximumValue = 59;
_secondSelector.MinimumValue = 0;
_secondSelector.Increment = SecondIncrement;
_secondSelector.SelectedValue = SelectedTime.Seconds;
_secondSelector.ItemFormat = "ss";
_periodSelector!.MaximumValue = 1;
_periodSelector.MinimumValue = 0;
_periodSelector.SelectedValue = hour >= 12 ? 1 : 0;
_spacer3!.IsVisible = !use24HourClock;
_periodHost!.IsVisible = !use24HourClock;
}
}

View File

@ -0,0 +1,263 @@
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class TimeViewTheme : BaseControlTheme
{
public const string MainContainerPart = "PART_MainContainer";
public const string PickerContainerPart = "PART_PickerContainer";
public const string HeaderTextPart = "PART_HeaderText";
public const string HourHostPart = "PART_HourHost";
public const string MinuteHostPart = "PART_MinuteHost";
public const string SecondHostPart = "PART_SecondHost";
public const string PeriodHostPart = "PART_PeriodHost";
public const string HourSelectorPart = "PART_HourSelector";
public const string MinuteSelectorPart = "PART_MinuteSelector";
public const string SecondSelectorPart = "PART_SecondSelector";
public const string PeriodSelectorPart = "PART_PeriodSelector";
public const string FirstSpacerPart = "PART_FirstSpacer";
public const string SecondSpacerPart = "PART_SecondSpacer";
public const string ThirdSpacerPart = "PART_ThirdSpacer";
public TimeViewTheme()
: base(typeof(TimeView))
{
}
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<TimeView>((presenter, scope) =>
{
var mainContainer = new Grid
{
Name = MainContainerPart,
RowDefinitions = new RowDefinitions
{
new(GridLength.Auto),
new(GridLength.Auto),
new(GridLength.Star)
}
};
BuildHeader(mainContainer, scope);
BuildHosts(mainContainer, scope);
return mainContainer;
});
}
private void BuildHeader(Grid mainContainer, INameScope scope)
{
var headerText = new TextBlock
{
Name = HeaderTextPart
};
headerText.RegisterInNameScope(scope);
CreateTemplateParentBinding(headerText, Visual.IsVisibleProperty, TimeView.IsShowHeaderProperty);
mainContainer.Children.Add(headerText);
Grid.SetRow(headerText, 0);
var separator = new Rectangle
{
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);
Grid.SetRow(separator, 1);
}
private void BuildHosts(Grid mainContainer, INameScope scope)
{
var pickerContainer = new Grid
{
Name = PickerContainerPart,
ColumnDefinitions = new ColumnDefinitions
{
new(GridLength.Star),
new(GridLength.Auto),
new(GridLength.Star),
new(GridLength.Auto),
new(GridLength.Star),
new(GridLength.Auto),
new(GridLength.Star)
}
};
pickerContainer.RegisterInNameScope(scope);
var hourHost = new Panel
{
Name = HourHostPart
};
Grid.SetColumn(hourHost, 0);
hourHost.RegisterInNameScope(scope);
pickerContainer.Children.Add(hourHost);
{
var scrollViewer = new ScrollViewer
{
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden
};
var hourSelector = new DateTimePickerPanel
{
Name = HourSelectorPart,
PanelType = DateTimePickerPanelType.Hour,
ShouldLoop = true
};
hourSelector.RegisterInNameScope(scope);
TokenResourceBinder.CreateTokenBinding(hourSelector, DateTimePickerPanel.ItemHeightProperty,
TimePickerTokenResourceKey.ItemHeight);
scrollViewer.Content = hourSelector;
hourHost.Children.Add(scrollViewer);
}
var firstSpacer = new Rectangle
{
Name = FirstSpacerPart,
HorizontalAlignment = HorizontalAlignment.Center
};
firstSpacer.RegisterInNameScope(scope);
CreateTemplateParentBinding(firstSpacer, Layoutable.WidthProperty, TimeView.SpacerThicknessProperty);
TokenResourceBinder.CreateGlobalTokenBinding(firstSpacer, Shape.FillProperty,
GlobalTokenResourceKey.ColorBorder);
Grid.SetColumn(firstSpacer, 1);
pickerContainer.Children.Add(firstSpacer);
var minuteHost = new Panel
{
Name = MinuteHostPart
};
Grid.SetColumn(minuteHost, 2);
minuteHost.RegisterInNameScope(scope);
pickerContainer.Children.Add(minuteHost);
{
var scrollViewer = new ScrollViewer
{
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden
};
var minuteSelector = new DateTimePickerPanel
{
Name = MinuteSelectorPart,
PanelType = DateTimePickerPanelType.Minute,
ShouldLoop = true
};
minuteSelector.RegisterInNameScope(scope);
TokenResourceBinder.CreateTokenBinding(minuteSelector, DateTimePickerPanel.ItemHeightProperty,
TimePickerTokenResourceKey.ItemHeight);
scrollViewer.Content = minuteSelector;
minuteHost.Children.Add(scrollViewer);
}
var secondSpacer = new Rectangle
{
Name = SecondSpacerPart,
HorizontalAlignment = HorizontalAlignment.Center
};
CreateTemplateParentBinding(secondSpacer, Layoutable.WidthProperty,
TimeView.SpacerThicknessProperty);
secondSpacer.RegisterInNameScope(scope);
TokenResourceBinder.CreateGlobalTokenBinding(secondSpacer, Shape.FillProperty,
GlobalTokenResourceKey.ColorBorder);
Grid.SetColumn(secondSpacer, 3);
pickerContainer.Children.Add(secondSpacer);
var secondHost = new Panel
{
Name = SecondHostPart
};
Grid.SetColumn(secondHost, 4);
secondHost.RegisterInNameScope(scope);
pickerContainer.Children.Add(secondHost);
{
var scrollViewer = new ScrollViewer
{
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden
};
var secondSelector = new DateTimePickerPanel
{
Name = SecondSelectorPart,
PanelType = DateTimePickerPanelType.Second,
ShouldLoop = true
};
secondSelector.RegisterInNameScope(scope);
TokenResourceBinder.CreateTokenBinding(secondSelector, DateTimePickerPanel.ItemHeightProperty,
TimePickerTokenResourceKey.ItemHeight);
scrollViewer.Content = secondSelector;
secondHost.Children.Add(scrollViewer);
}
var thirdSpacer = new Rectangle
{
Name = ThirdSpacerPart,
HorizontalAlignment = HorizontalAlignment.Center
};
CreateTemplateParentBinding(thirdSpacer, Layoutable.WidthProperty, TimeView.SpacerThicknessProperty);
thirdSpacer.RegisterInNameScope(scope);
Grid.SetColumn(thirdSpacer, 5);
pickerContainer.Children.Add(thirdSpacer);
TokenResourceBinder.CreateGlobalTokenBinding(thirdSpacer, Shape.FillProperty,
GlobalTokenResourceKey.ColorBorder);
var periodHost = new Panel
{
Name = PeriodHostPart
};
Grid.SetColumn(periodHost, 6);
periodHost.RegisterInNameScope(scope);
pickerContainer.Children.Add(periodHost);
{
var scrollViewer = new ScrollViewer
{
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden
};
var periodSelector = new DateTimePickerPanel
{
Name = PeriodSelectorPart,
PanelType = DateTimePickerPanelType.TimePeriod,
ShouldLoop = false
};
periodSelector.RegisterInNameScope(scope);
TokenResourceBinder.CreateTokenBinding(periodSelector, DateTimePickerPanel.ItemHeightProperty,
TimePickerTokenResourceKey.ItemHeight);
scrollViewer.Content = periodSelector;
periodHost.Children.Add(scrollViewer);
}
mainContainer.Children.Add(pickerContainer);
mainContainer.RegisterInNameScope(scope);
Grid.SetRow(pickerContainer, 2);
}
protected override void BuildStyles()
{
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.FontWeightProperty, FontWeight.SemiBold);
commonStyle.Add(headerTextStyle);
Add(commonStyle);
}
}

View File

@ -6,9 +6,13 @@ namespace AtomUI.Controls.Utils;
internal static class DateTimeUtils internal static class DateTimeUtils
{ {
public static string FormatTimeSpan(TimeSpan value, bool is12HourClock = false) public static string FormatTimeSpan(TimeSpan? time, bool is12HourClock = false)
{ {
var dateTime = DateTime.Today.Add(value); if (time is null)
{
return string.Empty;
}
var dateTime = DateTime.Today.Add(time.Value);
if (is12HourClock) if (is12HourClock)
{ {
var formatInfo = new DateTimeFormatInfo(); var formatInfo = new DateTimeFormatInfo();