mirror of
https://gitee.com/chinware/atomui.git
synced 2024-11-29 18:38:16 +08:00
Refactor TimePicker
Use the new Hover selection event mechanism
This commit is contained in:
parent
79e1c510ed
commit
561aaddcd5
@ -51,8 +51,20 @@ Avalonia 11.1.1 及其以上<br>
|
||||
PS: AtomUI 目前仅在 Windows 11 平台测试<br>
|
||||
|
||||
#### 中文社区
|
||||
目前我们暂时只创建微信开发者群的交流方式,下面是二维码,有兴趣的同学可以扫码加入:
|
||||
<img src="./docs/images/wechat.jpg" width="200" height="200"/>
|
||||
目前我们暂时只创建 QQ 和微信开发者群的交流方式,下面是二维码,有兴趣的同学可以扫码加入:
|
||||
|
||||
<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`爱好者
|
||||
|
||||
|
BIN
docs/images/QQ.png
Normal file
BIN
docs/images/QQ.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 183 KiB |
@ -5,20 +5,25 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:desktop="clr-namespace:AtomUI.Demo.Desktop"
|
||||
xmlns:calendarpresenter="clr-namespace:AtomUI.Controls.CalendarView;assembly=AtomUI.Controls"
|
||||
xmlns:atom="https://atomui.net"
|
||||
mc:Ignorable="d">
|
||||
<desktop:ShowCasePanel>
|
||||
<desktop:ShowCaseItem
|
||||
Title="Basic"
|
||||
Description="Click DatePicker, and then we could select or input a date in panel.">
|
||||
<atom:DatePicker Watermark="Select date" 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 -->
|
||||
<!-- 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>
|
||||
</UserControl>
|
@ -11,7 +11,7 @@ using Avalonia.Media;
|
||||
|
||||
namespace AtomUI.Controls.CalendarView;
|
||||
|
||||
public class DateSelectedEventArgs : RoutedEventArgs
|
||||
public class DateSelectedEventArgs : EventArgs
|
||||
{
|
||||
public DateTime? Value { get; }
|
||||
public DateSelectedEventArgs(DateTime? value)
|
||||
|
@ -4,7 +4,6 @@ using AtomUI.Data;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
|
@ -98,7 +98,6 @@ internal class DatePickerPresenter : PickerPresenterBase
|
||||
private Button? _confirmButton;
|
||||
private PickerCalendar? _calendarView;
|
||||
private IDisposable? _choosingStateDisposable;
|
||||
private bool _isConfirmed;
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
@ -112,13 +111,12 @@ internal class DatePickerPresenter : PickerPresenterBase
|
||||
ChoosingStatueChanged?.Invoke(this, new ChoosingStatusEventArgs(args.GetNewValue<bool>()));
|
||||
});
|
||||
}
|
||||
|
||||
_isConfirmed = false;
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
_choosingStateDisposable?.Dispose();
|
||||
_choosingStateDisposable = null;
|
||||
}
|
||||
|
||||
|
@ -18,8 +18,8 @@ internal class DatePickerPresenterTheme : BaseControlTheme
|
||||
public const string NowButtonPart = "PART_NowButton";
|
||||
public const string TodayButtonPart = "PART_TodayButton";
|
||||
public const string ConfirmButtonPart = "PART_ConfirmButton";
|
||||
public const string ButtonsContainerPart = "PART_ButtonsContainer";
|
||||
public const string ButtonsContainerFramePart = "PART_ButtonsContainerFrame";
|
||||
public const string ButtonsLayoutPart = "PART_ButtonsLayout";
|
||||
public const string ButtonsFramePart = "PART_ButtonsFrame";
|
||||
public const string CalendarViewPart = "PART_CalendarView";
|
||||
|
||||
public DatePickerPresenterTheme() : this(typeof(DatePickerPresenter))
|
||||
@ -44,7 +44,7 @@ internal class DatePickerPresenterTheme : BaseControlTheme
|
||||
|
||||
var buttonsContainerFrame = new Border()
|
||||
{
|
||||
Name = ButtonsContainerFramePart
|
||||
Name = ButtonsFramePart
|
||||
};
|
||||
CreateTemplateParentBinding(buttonsContainerFrame, Border.BorderThicknessProperty, DatePickerPresenter.BorderThicknessProperty);
|
||||
var buttonsPanel = BuildButtons(presenter, scope);
|
||||
@ -73,7 +73,7 @@ internal class DatePickerPresenterTheme : BaseControlTheme
|
||||
{
|
||||
var buttonsPanel = new Panel()
|
||||
{
|
||||
Name = ButtonsContainerPart
|
||||
Name = ButtonsLayoutPart
|
||||
};
|
||||
|
||||
var nowButton = new Button
|
||||
@ -114,11 +114,11 @@ internal class DatePickerPresenterTheme : BaseControlTheme
|
||||
|
||||
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);
|
||||
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);
|
||||
Add(buttonsPanelStyle);
|
||||
|
||||
|
@ -25,10 +25,6 @@ internal class DatePickerTheme : InfoPickerInputTheme
|
||||
base.BuildStyles();
|
||||
var commonStyle = new Style(selector => selector.Nesting());
|
||||
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);
|
||||
}
|
||||
|
@ -89,11 +89,11 @@
|
||||
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TabStripItemTheme());
|
||||
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TabStripTheme());
|
||||
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.TimePickerFlyoutPresenterTheme());
|
||||
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TimePickerPresenterTheme());
|
||||
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.NodeSwitcherButtonTheme());
|
||||
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TreeViewItemTheme());
|
||||
|
@ -581,6 +581,7 @@ namespace AtomUI.Theme.Styling
|
||||
public static readonly TokenResourceKey PickerPopupHeight = new TokenResourceKey("TimePicker.PickerPopupHeight", "AtomUI.Token");
|
||||
public static readonly TokenResourceKey RangePickerArrowMargin = new TokenResourceKey("TimePicker.RangePickerArrowMargin", "AtomUI.Token");
|
||||
public static readonly TokenResourceKey RangePickerIndicatorThickness = new TokenResourceKey("TimePicker.RangePickerIndicatorThickness", "AtomUI.Token");
|
||||
public static readonly TokenResourceKey PickerInputMinWidth = new TokenResourceKey("TimePicker.PickerInputMinWidth", "AtomUI.Token");
|
||||
}
|
||||
|
||||
public static class ToolTipTokenResourceKey
|
||||
|
@ -140,6 +140,11 @@ internal class InfoPickerInputTheme : BaseControlTheme
|
||||
base.BuildStyles();
|
||||
var commonStyle = new Style(selector => selector.Nesting());
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,4 +1,8 @@
|
||||
using Avalonia;
|
||||
using AtomUI.Media;
|
||||
using AtomUI.Theme.Utils;
|
||||
using Avalonia;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.LogicalTree;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
@ -33,6 +37,53 @@ public class ListBoxItem : AvaloniaListBoxItem
|
||||
get => _disabledItemHoverEffect;
|
||||
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
|
||||
|
||||
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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,5 @@
|
||||
using AtomUI.Media;
|
||||
using AtomUI.Theme;
|
||||
using AtomUI.Theme;
|
||||
using AtomUI.Theme.Styling;
|
||||
using AtomUI.Theme.Utils;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Primitives;
|
||||
@ -34,11 +31,10 @@ internal class ListBoxItemTheme : BaseControlTheme
|
||||
Name = ContentPresenterPart
|
||||
};
|
||||
|
||||
contentPresenter.Transitions = new Transitions
|
||||
{
|
||||
AnimationUtils.CreateTransition<SolidColorBrushTransition>(ContentPresenter.BackgroundProperty)
|
||||
};
|
||||
|
||||
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ForegroundProperty,
|
||||
TemplatedControl.ForegroundProperty);
|
||||
CreateTemplateParentBinding(contentPresenter, ContentPresenter.BackgroundProperty,
|
||||
TemplatedControl.BackgroundProperty);
|
||||
CreateTemplateParentBinding(contentPresenter, ContentPresenter.CornerRadiusProperty,
|
||||
TemplatedControl.CornerRadiusProperty);
|
||||
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty,
|
||||
@ -66,32 +62,20 @@ internal class ListBoxItemTheme : BaseControlTheme
|
||||
private void BuildCommonStyle()
|
||||
{
|
||||
var commonStyle = new Style(selector => selector.Nesting());
|
||||
commonStyle.Add(Layoutable.MarginProperty, ListBoxTokenResourceKey.ItemMargin);
|
||||
{
|
||||
var contentPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ContentPresenterPart));
|
||||
contentPresenterStyle.Add(ContentPresenter.ForegroundProperty, ListBoxTokenResourceKey.ItemColor);
|
||||
contentPresenterStyle.Add(ContentPresenter.BackgroundProperty, ListBoxTokenResourceKey.ItemBgColor);
|
||||
commonStyle.Add(contentPresenterStyle);
|
||||
}
|
||||
commonStyle.Add(ListBoxItem.MarginProperty, ListBoxTokenResourceKey.ItemMargin);
|
||||
commonStyle.Add(ListBoxItem.ForegroundProperty, ListBoxTokenResourceKey.ItemColor);
|
||||
commonStyle.Add(ListBoxItem.BackgroundProperty, ListBoxTokenResourceKey.ItemBgColor);
|
||||
|
||||
var disabledItemHoverStyle = new Style(selector =>
|
||||
selector.Nesting().PropertyEquals(ListBoxItem.DisabledItemHoverEffectProperty, false));
|
||||
{
|
||||
var contentPresenterStyle = new Style(selector =>
|
||||
selector.Nesting().Template().Name(ContentPresenterPart).Class(StdPseudoClass.PointerOver));
|
||||
contentPresenterStyle.Add(ContentPresenter.ForegroundProperty, ListBoxTokenResourceKey.ItemHoverColor);
|
||||
contentPresenterStyle.Add(ContentPresenter.BackgroundProperty, ListBoxTokenResourceKey.ItemHoverBgColor);
|
||||
disabledItemHoverStyle.Add(contentPresenterStyle);
|
||||
}
|
||||
selector.Nesting().PropertyEquals(ListBoxItem.DisabledItemHoverEffectProperty, false).Class(StdPseudoClass.PointerOver));
|
||||
disabledItemHoverStyle.Add(ListBoxItem.ForegroundProperty, ListBoxTokenResourceKey.ItemHoverColor);
|
||||
disabledItemHoverStyle.Add(ListBoxItem.BackgroundProperty, ListBoxTokenResourceKey.ItemHoverBgColor);
|
||||
|
||||
commonStyle.Add(disabledItemHoverStyle);
|
||||
|
||||
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
|
||||
{
|
||||
var contentPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ContentPresenterPart));
|
||||
contentPresenterStyle.Add(ContentPresenter.ForegroundProperty, ListBoxTokenResourceKey.ItemSelectedColor);
|
||||
contentPresenterStyle.Add(ContentPresenter.BackgroundProperty, ListBoxTokenResourceKey.ItemSelectedBgColor);
|
||||
selectedStyle.Add(contentPresenterStyle);
|
||||
}
|
||||
selectedStyle.Add(ListBoxItem.ForegroundProperty, ListBoxTokenResourceKey.ItemSelectedColor);
|
||||
selectedStyle.Add(ListBoxItem.BackgroundProperty, ListBoxTokenResourceKey.ItemSelectedBgColor);
|
||||
commonStyle.Add(selectedStyle);
|
||||
Add(commonStyle);
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using AtomUI.Controls.TimePickerLang;
|
||||
using AtomUI.Data;
|
||||
using AtomUI.Theme.Data;
|
||||
using AtomUI.Theme.Styling;
|
||||
using AtomUI.Utils;
|
||||
@ -29,31 +28,99 @@ public enum DateTimePickerPanelType
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ItemHeight" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> ItemHeightProperty =
|
||||
#region 公共属性定义
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ItemHeight" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> ItemHeightProperty =
|
||||
AvaloniaProperty.Register<DateTimePickerPanel, double>(nameof(ItemHeight), 40.0);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="PanelType" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<DateTimePickerPanelType> PanelTypeProperty =
|
||||
/// <summary>
|
||||
/// Defines the <see cref="PanelType" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<DateTimePickerPanelType> PanelTypeProperty =
|
||||
AvaloniaProperty.Register<DateTimePickerPanel, DateTimePickerPanelType>(nameof(PanelType));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ItemFormat" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<string> ItemFormatProperty =
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ItemFormat" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<string> ItemFormatProperty =
|
||||
AvaloniaProperty.Register<DateTimePickerPanel, string>(nameof(ItemFormat), "yyyy");
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ShouldLoop" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> ShouldLoopProperty =
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ShouldLoop" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> ShouldLoopProperty =
|
||||
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
|
||||
private int _minimumValue = 1;
|
||||
@ -76,7 +143,7 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
|
||||
public DateTimePickerPanel()
|
||||
{
|
||||
FormatDate = DateTime.Now;
|
||||
AddHandler(TappedEvent, OnItemTapped, RoutingStrategies.Bubble);
|
||||
AddHandler(TappedEvent, HandleItemTapped, RoutingStrategies.Bubble);
|
||||
}
|
||||
|
||||
static DateTimePickerPanel()
|
||||
@ -85,44 +152,7 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
|
||||
BackgroundProperty.OverrideDefaultValue<DateTimePickerPanel>(Brushes.Transparent);
|
||||
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>
|
||||
/// Gets or sets the minimum value
|
||||
/// </summary>
|
||||
@ -370,6 +400,7 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
|
||||
{
|
||||
UpdateItems();
|
||||
RaiseScrollInvalidated(EventArgs.Empty);
|
||||
EnableCellHoverAnimation();
|
||||
_hasInit = true;
|
||||
}
|
||||
|
||||
@ -532,7 +563,16 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
|
||||
HorizontalContentAlignment = HorizontalAlignment.Center,
|
||||
Focusable = false,
|
||||
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,
|
||||
TimePickerTokenResourceKey.ItemPadding, BindingPriority.LocalValue);
|
||||
@ -640,7 +680,7 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
|
||||
return newValue;
|
||||
}
|
||||
|
||||
private void OnItemTapped(object? sender, TappedEventArgs e)
|
||||
private void HandleItemTapped(object? sender, TappedEventArgs e)
|
||||
{
|
||||
if (e.Source is Visual source &&
|
||||
GetItemFromSource(source) is ListBoxItem listBoxItem &&
|
||||
@ -687,4 +727,14 @@ internal class DateTimePickerPanel : Panel, ILogicalScrollable
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -104,7 +104,7 @@ public class RangeTimePicker : RangeInfoPickerInput
|
||||
|
||||
protected override Flyout CreatePickerFlyout()
|
||||
{
|
||||
return new RangeTimePickerFlyout(this);
|
||||
return new TimePickerFlyout();
|
||||
}
|
||||
|
||||
protected override void NotifyFlyoutAboutToClose(bool selectedIsValid)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
using AtomUI.Controls.Internal;
|
||||
using AtomUI.Controls.Utils;
|
||||
using AtomUI.Data;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Data;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
@ -16,6 +17,12 @@ public class TimePicker : InfoPickerInput
|
||||
{
|
||||
#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 =
|
||||
AvaloniaProperty.Register<TimePicker, int>(nameof(MinuteIncrement), 1, coerce: CoerceMinuteIncrement);
|
||||
|
||||
@ -27,13 +34,24 @@ public class TimePicker : InfoPickerInput
|
||||
|
||||
public static readonly StyledProperty<TimeSpan?> SelectedTimeProperty =
|
||||
AvaloniaProperty.Register<TimePicker, TimeSpan?>(nameof(SelectedTime),
|
||||
defaultBindingMode: BindingMode.TwoWay,
|
||||
enableDataValidation: true);
|
||||
|
||||
public static readonly StyledProperty<TimeSpan?> DefaultTimeProperty =
|
||||
AvaloniaProperty.Register<TimePicker, TimeSpan?>(nameof(DefaultTime),
|
||||
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
|
||||
{
|
||||
get => GetValue(MinuteIncrementProperty);
|
||||
@ -66,38 +84,96 @@ public class TimePicker : InfoPickerInput
|
||||
|
||||
#endregion
|
||||
|
||||
internal void NotifyTemporaryTimeSelected(TimeSpan value)
|
||||
{
|
||||
Text = DateTimeUtils.FormatTimeSpan(value, ClockIdentifier == ClockIdentifierType.HourClock12);
|
||||
}
|
||||
|
||||
internal void NotifyConfirmed(TimeSpan value)
|
||||
{
|
||||
_currentValidSelected = true;
|
||||
SelectedTime = value;
|
||||
}
|
||||
private TimePickerPresenter? _pickerPresenter;
|
||||
|
||||
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)
|
||||
{
|
||||
base.NotifyFlyoutAboutToClose(selectedIsValid);
|
||||
if (!selectedIsValid)
|
||||
if (_pickerPresenter is not null)
|
||||
{
|
||||
if (SelectedTime.HasValue)
|
||||
{
|
||||
Text = DateTimeUtils.FormatTimeSpan(SelectedTime.Value,
|
||||
ClockIdentifier == ClockIdentifierType.HourClock12);
|
||||
}
|
||||
else
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
_pickerPresenter.ChoosingStatueChanged -= HandleChoosingStatueChanged;
|
||||
_pickerPresenter.HoverTimeChanged -= HandleHoverTimeChanged;
|
||||
_pickerPresenter.Confirmed -= HandleConfirmed;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleChoosingStatueChanged(object? sender, ChoosingStatusEventArgs args)
|
||||
{
|
||||
_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);
|
||||
}
|
||||
else
|
||||
{
|
||||
Text = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleConfirmed(object? sender, EventArgs args)
|
||||
{
|
||||
SelectedTime = _pickerPresenter?.SelectedTime;
|
||||
ClosePickerFlyout();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除时间选择器的值,不考虑默认值
|
||||
@ -124,20 +200,10 @@ public class TimePicker : InfoPickerInput
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
if (VisualRoot is not null)
|
||||
if (change.Property == SelectedTimeProperty)
|
||||
{
|
||||
if (change.Property == SelectedTimeProperty)
|
||||
{
|
||||
if (SelectedTime.HasValue)
|
||||
{
|
||||
Text = DateTimeUtils.FormatTimeSpan(SelectedTime.Value,
|
||||
ClockIdentifier == ClockIdentifierType.HourClock12);
|
||||
}
|
||||
else
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
Text = DateTimeUtils.FormatTimeSpan(SelectedTime,
|
||||
ClockIdentifier == ClockIdentifierType.HourClock12);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,28 +5,12 @@ namespace AtomUI.Controls;
|
||||
|
||||
internal class TimePickerFlyout : Flyout
|
||||
{
|
||||
internal TimePicker TimePickerRef { get; set; }
|
||||
|
||||
public TimePickerFlyout(TimePicker timePicker)
|
||||
{
|
||||
TimePickerRef = timePicker;
|
||||
}
|
||||
|
||||
protected override Control CreatePresenter()
|
||||
{
|
||||
var presenter = new TimePickerFlyoutPresenter(TimePickerRef);
|
||||
var presenter = new TimePickerFlyoutPresenter();
|
||||
|
||||
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();
|
||||
SetupArrowPosition(Popup, presenter);
|
||||
return presenter;
|
||||
|
@ -1,116 +1,16 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
internal TimePickerPresenter? TimePickerPresenter;
|
||||
|
||||
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 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();
|
||||
TimePickerPresenter = e.NameScope.Get<TimePickerPresenter>(TimePickerFlyoutPresenterTheme.TimePickerPresenterPart);
|
||||
}
|
||||
}
|
@ -1,93 +1,30 @@
|
||||
using AtomUI.Controls.Localization;
|
||||
using AtomUI.Controls.TimePickerLang;
|
||||
using AtomUI.Theme.Data;
|
||||
using AtomUI.Theme.Styling;
|
||||
using AtomUI.Theme.Styling;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
[ControlThemeProvider]
|
||||
internal class TimePickerFlyoutPresenterTheme : 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 const string TimePickerPresenterPart = "PART_TimePickerPresenter";
|
||||
|
||||
public TimePickerFlyoutPresenterTheme()
|
||||
: base(typeof(TimePickerFlyoutPresenter))
|
||||
: this(typeof(TimePickerFlyoutPresenter))
|
||||
{
|
||||
}
|
||||
|
||||
protected TimePickerFlyoutPresenterTheme(Type targetType) : base(targetType)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Control BuildContent(INameScope scope)
|
||||
{
|
||||
var contentLayout = new DockPanel
|
||||
var timePickerPresenter = new TimePickerPresenter()
|
||||
{
|
||||
Name = ContentLayoutPart
|
||||
Name = TimePickerPresenterPart,
|
||||
};
|
||||
|
||||
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);
|
||||
timePickerPresenter.RegisterInNameScope(scope);
|
||||
return timePickerPresenter;
|
||||
}
|
||||
}
|
@ -4,59 +4,48 @@ 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.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
|
||||
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
|
||||
{
|
||||
#region 公共属性定义
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="MinuteIncrement" /> property
|
||||
/// </summary>
|
||||
|
||||
public static readonly StyledProperty<bool> IsNeedConfirmProperty =
|
||||
TimePicker.IsNeedConfirmProperty.AddOwner<TimePickerPresenter>();
|
||||
|
||||
public static readonly StyledProperty<bool> IsShowNowProperty =
|
||||
TimePicker.IsShowNowProperty.AddOwner<TimePickerPresenter>();
|
||||
|
||||
public static readonly StyledProperty<int> MinuteIncrementProperty =
|
||||
TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>();
|
||||
|
||||
public static readonly StyledProperty<int> SecondIncrementProperty =
|
||||
TimePicker.SecondIncrementProperty.AddOwner<TimePickerPresenter>();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ClockIdentifier" /> property
|
||||
/// </summary>
|
||||
|
||||
public static readonly StyledProperty<ClockIdentifierType> ClockIdentifierProperty =
|
||||
TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Time" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<TimeSpan> TimeProperty =
|
||||
AvaloniaProperty.Register<TimePickerPresenter, TimeSpan>(nameof(Time));
|
||||
|
||||
public static readonly StyledProperty<TimeSpan> TemporaryTimeProperty =
|
||||
AvaloniaProperty.Register<TimePickerPresenter, TimeSpan>(nameof(TemporaryTime));
|
||||
|
||||
public static readonly StyledProperty<bool> IsShowHeaderProperty =
|
||||
AvaloniaProperty.Register<TimePickerPresenter, bool>(nameof(IsShowHeader), true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minute increment in the selector
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<TimeSpan?> SelectedTimeProperty =
|
||||
TimePicker.SelectedTimeProperty.AddOwner<TimePickerPresenter>();
|
||||
|
||||
public bool IsNeedConfirm
|
||||
{
|
||||
get => GetValue(IsNeedConfirmProperty);
|
||||
set => SetValue(IsNeedConfirmProperty, value);
|
||||
}
|
||||
|
||||
public bool IsShowNow
|
||||
{
|
||||
get => GetValue(IsShowNowProperty);
|
||||
set => SetValue(IsShowNowProperty, value);
|
||||
}
|
||||
|
||||
public int MinuteIncrement
|
||||
{
|
||||
get => GetValue(MinuteIncrementProperty);
|
||||
@ -68,163 +57,55 @@ internal class TimePickerPresenter : PickerPresenterBase
|
||||
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);
|
||||
}
|
||||
|
||||
#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>
|
||||
/// Gets or sets the current time
|
||||
/// 当前 Pointer 选中的日期和时间的变化事件
|
||||
/// </summary>
|
||||
public TimeSpan Time
|
||||
{
|
||||
get => GetValue(TimeProperty);
|
||||
set => SetValue(TimeProperty, value);
|
||||
}
|
||||
|
||||
public TimeSpan TemporaryTime
|
||||
{
|
||||
get => GetValue(TemporaryTimeProperty);
|
||||
set => SetValue(TemporaryTimeProperty, value);
|
||||
}
|
||||
|
||||
public bool IsShowHeader
|
||||
{
|
||||
get => GetValue(IsShowHeaderProperty);
|
||||
set => SetValue(IsShowHeaderProperty, value);
|
||||
}
|
||||
public event EventHandler<TimeSelectedEventArgs>? HoverTimeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 当前是否处于选择中状态
|
||||
/// </summary>
|
||||
public event EventHandler<ChoosingStatusEventArgs>? ChoosingStatueChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 私有属性定义
|
||||
|
||||
internal static readonly DirectProperty<TimePickerPresenter, double> SpacerThicknessProperty =
|
||||
AvaloniaProperty.RegisterDirect<TimePickerPresenter, double>(nameof(SpacerWidth),
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
private IDisposable? _choosingStateDisposable;
|
||||
private Button? _nowButton;
|
||||
private Button? _confirmButton;
|
||||
private TimeView? _timeView;
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
@ -251,64 +132,127 @@ internal class TimePickerPresenter : PickerPresenterBase
|
||||
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void OnConfirmed()
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
var value = CollectValue();
|
||||
SetCurrentValue(TimeProperty, value);
|
||||
base.OnConfirmed();
|
||||
base.OnApplyTemplate(e);
|
||||
_nowButton = e.NameScope.Get<Button>(TimePickerPresenterTheme.NowButtonPart);
|
||||
_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;
|
||||
}
|
||||
|
||||
if (_nowButton is not null)
|
||||
{
|
||||
_nowButton.Click += HandleNowButtonClicked;
|
||||
}
|
||||
|
||||
if (_confirmButton is not null)
|
||||
{
|
||||
_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 NowConfirm()
|
||||
|
||||
private void SetupButtonStatus()
|
||||
{
|
||||
SetCurrentValue(TimeProperty, DateTime.Now.TimeOfDay);
|
||||
base.OnConfirmed();
|
||||
}
|
||||
|
||||
internal void Confirm()
|
||||
{
|
||||
OnConfirmed();
|
||||
}
|
||||
|
||||
internal void Dismiss()
|
||||
{
|
||||
OnDismiss();
|
||||
}
|
||||
|
||||
private void InitPicker()
|
||||
{
|
||||
if (_pickerContainer == null)
|
||||
if (_nowButton is null || _confirmButton is 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 = Time.Hours;
|
||||
_hourSelector.SelectedValue = !clock12 ? hour :
|
||||
hour > 12 ? hour - 12 :
|
||||
hour == 0 ? 12 : hour;
|
||||
_confirmButton.IsVisible = IsNeedConfirm;
|
||||
|
||||
if (IsShowNow)
|
||||
{
|
||||
if (!IsNeedConfirm)
|
||||
{
|
||||
_nowButton.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
}
|
||||
else
|
||||
{
|
||||
_nowButton.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_nowButton.IsVisible = false;
|
||||
_nowButton.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
}
|
||||
|
||||
_minuteSelector!.MaximumValue = 59;
|
||||
_minuteSelector.MinimumValue = 0;
|
||||
_minuteSelector.Increment = MinuteIncrement;
|
||||
_minuteSelector.SelectedValue = Time.Minutes;
|
||||
_minuteSelector.ItemFormat = "mm";
|
||||
ButtonsPanelVisible = _nowButton.IsVisible || _confirmButton.IsVisible;
|
||||
}
|
||||
|
||||
_secondSelector!.MaximumValue = 59;
|
||||
_secondSelector.MinimumValue = 0;
|
||||
_secondSelector.Increment = SecondIncrement;
|
||||
_secondSelector.SelectedValue = Time.Seconds;
|
||||
_secondSelector.ItemFormat = "ss";
|
||||
private void HandleNowButtonClicked(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
SelectedTime = DateTime.Now.TimeOfDay;
|
||||
if (!IsNeedConfirm)
|
||||
{
|
||||
OnConfirmed();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleConfirmButtonClicked(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
if (SelectedTime is not null)
|
||||
{
|
||||
OnConfirmed();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTimeViewDateHoverChanged(object? sender, TimeSelectedEventArgs args)
|
||||
{
|
||||
HoverTimeChanged?.Invoke(this, new TimeSelectedEventArgs(args.Time));
|
||||
}
|
||||
|
||||
_periodSelector!.MaximumValue = 1;
|
||||
_periodSelector.MinimumValue = 0;
|
||||
_periodSelector.SelectedValue = hour >= 12 ? 1 : 0;
|
||||
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>()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_spacer3!.IsVisible = !use24HourClock;
|
||||
_periodHost!.IsVisible = !use24HourClock;
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
_choosingStateDisposable?.Dispose();
|
||||
_choosingStateDisposable = null;
|
||||
}
|
||||
}
|
@ -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.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;
|
||||
@ -15,23 +13,12 @@ namespace AtomUI.Controls;
|
||||
[ControlThemeProvider]
|
||||
internal class TimePickerPresenterTheme : 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 const string MainLayoutPart = "PART_MainLayout";
|
||||
public const string NowButtonPart = "PART_NowButton";
|
||||
public const string ConfirmButtonPart = "PART_ConfirmButton";
|
||||
public const string ButtonsLayoutPart = "PART_ButtonsLayout";
|
||||
public const string ButtonsFramePart = "PART_ButtonsFrame";
|
||||
public const string TimeViewPart = "PART_TimeView";
|
||||
|
||||
public TimePickerPresenterTheme()
|
||||
: base(typeof(TimePickerPresenter))
|
||||
@ -42,222 +29,89 @@ internal class TimePickerPresenterTheme : BaseControlTheme
|
||||
{
|
||||
return new FuncControlTemplate<TimePickerPresenter>((presenter, scope) =>
|
||||
{
|
||||
var mainContainer = new Grid
|
||||
var mainLayout = new DockPanel()
|
||||
{
|
||||
Name = MainContainerPart,
|
||||
RowDefinitions = new RowDefinitions
|
||||
{
|
||||
new(GridLength.Auto),
|
||||
new(GridLength.Auto),
|
||||
new(GridLength.Star)
|
||||
}
|
||||
Name = MainLayoutPart,
|
||||
LastChildFill = true
|
||||
};
|
||||
BuildHeader(mainContainer, scope);
|
||||
BuildHosts(mainContainer, scope);
|
||||
return mainContainer;
|
||||
|
||||
var calendarView = BuildTimeView(presenter, scope);
|
||||
|
||||
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(headerText, Visual.IsVisibleProperty, TimePickerPresenter.IsShowHeaderProperty);
|
||||
mainContainer.Children.Add(headerText);
|
||||
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);
|
||||
CreateTemplateParentBinding(timeView, TimeView.SelectedTimeProperty, TimePickerPresenter.SelectedTimeProperty);
|
||||
timeView.RegisterInNameScope(scope);
|
||||
return timeView;
|
||||
}
|
||||
|
||||
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,
|
||||
ColumnDefinitions = new ColumnDefinitions
|
||||
{
|
||||
new(GridLength.Star),
|
||||
new(GridLength.Auto),
|
||||
new(GridLength.Star),
|
||||
new(GridLength.Auto),
|
||||
new(GridLength.Star),
|
||||
new(GridLength.Auto),
|
||||
new(GridLength.Star)
|
||||
}
|
||||
Name = ButtonsLayoutPart
|
||||
};
|
||||
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);
|
||||
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);
|
||||
}
|
||||
LanguageResourceBinder.CreateBinding(nowButton, Button.TextProperty, DatePickerLangResourceKey.Now);
|
||||
nowButton.RegisterInNameScope(scope);
|
||||
buttonsPanel.Children.Add(nowButton);
|
||||
|
||||
var firstSpacer = new Rectangle
|
||||
var confirmButton = new Button
|
||||
{
|
||||
Name = FirstSpacerPart,
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
Name = ConfirmButtonPart,
|
||||
ButtonType = ButtonType.Primary,
|
||||
SizeType = SizeType.Small,
|
||||
HorizontalAlignment = HorizontalAlignment.Right
|
||||
};
|
||||
firstSpacer.RegisterInNameScope(scope);
|
||||
CreateTemplateParentBinding(firstSpacer, Layoutable.WidthProperty, TimePickerPresenter.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,
|
||||
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);
|
||||
LanguageResourceBinder.CreateBinding(confirmButton, Button.TextProperty, CommonLangResourceKey.OkText);
|
||||
confirmButton.RegisterInNameScope(scope);
|
||||
buttonsPanel.Children.Add(confirmButton);
|
||||
|
||||
return buttonsPanel;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
var commonStyle = new Style(selector => selector.Nesting());
|
||||
|
||||
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);
|
||||
|
||||
commonStyle.Add(headerTextStyle);
|
||||
var buttonsPanelStyle = new Style(selector => selector.Nesting().Template().Name(ButtonsLayoutPart));
|
||||
buttonsPanelStyle.Add(Panel.MarginProperty, DatePickerTokenResourceKey.ButtonsPanelMargin);
|
||||
commonStyle.Add(buttonsPanelStyle);
|
||||
|
||||
Add(commonStyle);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using AtomUI.Controls.Internal;
|
||||
using AtomUI.Theme.Styling;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
@ -18,4 +19,13 @@ internal class TimePickerTheme : InfoPickerInputTheme
|
||||
Kind = "ClockCircleOutlined"
|
||||
};
|
||||
}
|
||||
|
||||
protected override void BuildStyles()
|
||||
{
|
||||
base.BuildStyles();
|
||||
var commonStyle = new Style(selector => selector.Nesting());
|
||||
commonStyle.Add(TimePicker.MinWidthProperty, TimePickerTokenResourceKey.PickerInputMinWidth);
|
||||
|
||||
Add(commonStyle);
|
||||
}
|
||||
}
|
@ -47,6 +47,11 @@ internal class TimePickerToken : AbstractControlDesignToken
|
||||
/// 选择指示器厚度
|
||||
/// </summary>
|
||||
public double RangePickerIndicatorThickness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间选择器最小的宽度
|
||||
/// </summary>
|
||||
public double PickerInputMinWidth { get; set; }
|
||||
|
||||
internal override void CalculateFromAlias()
|
||||
{
|
||||
@ -55,8 +60,9 @@ internal class TimePickerToken : AbstractControlDesignToken
|
||||
ItemPadding = new Thickness(0, _globalToken.PaddingXXS);
|
||||
ButtonsMargin = new Thickness(0, _globalToken.MarginXS, 0, 0);
|
||||
PickerPopupWidth = 200;
|
||||
PickerPopupHeight = ItemHeight * 7;
|
||||
PickerPopupHeight = ItemHeight * 8;
|
||||
RangePickerArrowMargin = new Thickness(_globalToken.MarginXS, 0);
|
||||
RangePickerIndicatorThickness = _globalToken.LineWidthFocus;
|
||||
PickerInputMinWidth = 120;
|
||||
}
|
||||
}
|
379
src/AtomUI.Controls/TimePicker/TimeView/TimeView.cs
Normal file
379
src/AtomUI.Controls/TimePicker/TimeView/TimeView.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
263
src/AtomUI.Controls/TimePicker/TimeView/TimeViewTheme.cs
Normal file
263
src/AtomUI.Controls/TimePicker/TimeView/TimeViewTheme.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -6,9 +6,13 @@ namespace AtomUI.Controls.Utils;
|
||||
|
||||
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)
|
||||
{
|
||||
var formatInfo = new DateTimeFormatInfo();
|
||||
|
Loading…
Reference in New Issue
Block a user