30
demo/Ursa.Demo/Pages/TimePickerDemo.axaml
Normal file
30
demo/Ursa.Demo/Pages/TimePickerDemo.axaml
Normal file
@@ -0,0 +1,30 @@
|
||||
<UserControl
|
||||
x:Class="Ursa.Demo.Pages.TimePickerDemo"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<StackPanel HorizontalAlignment="Left">
|
||||
<ToggleSwitch Name="needConfirm" Content="Need Confirm" />
|
||||
<TextBlock Text="{Binding #picker.SelectedTime}" />
|
||||
<u:TimePicker
|
||||
Name="picker"
|
||||
Width="200"
|
||||
HorizontalAlignment="Left"
|
||||
NeedConfirmation="{Binding #needConfirm.IsChecked}"
|
||||
PanelFormat="hh mm tt" />
|
||||
<u:TimePicker
|
||||
Width="300"
|
||||
Classes="ClearButton"
|
||||
DisplayFormat="HH 时 mm 分 ss 秒"
|
||||
PanelFormat="tt HH mm ss"
|
||||
HorizontalAlignment="Left"
|
||||
NeedConfirmation="True"
|
||||
InnerLeftContent="时刻"
|
||||
InnerRightContent="截止" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
13
demo/Ursa.Demo/Pages/TimePickerDemo.axaml.cs
Normal file
13
demo/Ursa.Demo/Pages/TimePickerDemo.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Ursa.Demo.Pages;
|
||||
|
||||
public partial class TimePickerDemo : UserControl
|
||||
{
|
||||
public TimePickerDemo()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ public class MainViewViewModel : ViewModelBase
|
||||
MenuKeys.MenuKeySelectionList => new SelectionListDemoViewModel(),
|
||||
MenuKeys.MenuKeySkeleton => new SkeletonDemoViewModel(),
|
||||
MenuKeys.MenuKeyTagInput => new TagInputDemoViewModel(),
|
||||
MenuKeys.MenuKeyTimePicker => new TimePickerDemoViewModel(),
|
||||
MenuKeys.MenuKeyTimeline => new TimelineDemoViewModel(),
|
||||
MenuKeys.MenuKeyTreeComboBox => new TreeComboBoxDemoViewModel(),
|
||||
MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(),
|
||||
|
||||
@@ -43,6 +43,7 @@ public class MenuViewModel: ViewModelBase
|
||||
new() { MenuHeader = "Skeleton", Key = MenuKeys.MenuKeySkeleton },
|
||||
new() { MenuHeader = "TagInput", Key = MenuKeys.MenuKeyTagInput },
|
||||
new() { MenuHeader = "Theme Toggler", Key = MenuKeys.MenuKeyThemeToggler },
|
||||
new() { MenuHeader = "TimePicker", Key = MenuKeys.MenuKeyTimePicker },
|
||||
new() { MenuHeader = "Timeline", Key = MenuKeys.MenuKeyTimeline },
|
||||
new() { MenuHeader = "TreeComboBox", Key = MenuKeys.MenuKeyTreeComboBox },
|
||||
new() { MenuHeader = "TwoTonePathIcon", Key = MenuKeys.MenuKeyTwoTonePathIcon},
|
||||
@@ -86,6 +87,7 @@ public static class MenuKeys
|
||||
public const string MenuKeySelectionList = "SelectionList";
|
||||
public const string MenuKeyTagInput = "TagInput";
|
||||
public const string MenuKeySkeleton = "Skeleton";
|
||||
public const string MenuKeyTimePicker = "TimePicker";
|
||||
public const string MenuKeyTimeline = "Timeline";
|
||||
public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon";
|
||||
public const string MenuKeyThemeToggler = "ThemeToggler";
|
||||
|
||||
8
demo/Ursa.Demo/ViewModels/TimePickerDemoViewModel.cs
Normal file
8
demo/Ursa.Demo/ViewModels/TimePickerDemoViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Ursa.Demo.ViewModels;
|
||||
|
||||
public class TimePickerDemoViewModel: ObservableObject
|
||||
{
|
||||
|
||||
}
|
||||
270
src/Ursa.Themes.Semi/Controls/TimePicker.axaml
Normal file
270
src/Ursa.Themes.Semi/Controls/TimePicker.axaml
Normal file
@@ -0,0 +1,270 @@
|
||||
<ResourceDictionary
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:Ursa.Converters;assembly=Ursa"
|
||||
xmlns:iri="https://irihi.tech/shared"
|
||||
xmlns:u="https://irihi.tech/ursa">
|
||||
<!-- Add Resources Here -->
|
||||
<Design.PreviewWith>
|
||||
<u:TimePickerPresenter Height="300" />
|
||||
</Design.PreviewWith>
|
||||
<ControlTheme x:Key="{x:Type u:TimePickerPresenter}" TargetType="u:TimePickerPresenter">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
<Setter Property="MaxHeight" Value="300" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="u:TimePickerPresenter">
|
||||
<Grid Name="{x:Static u:TimePickerPresenter.PART_PickerContainer}" ColumnDefinitions="*, Auto, *, Auto, *, Auto, *">
|
||||
<Grid.Styles>
|
||||
<Style Selector="u|UrsaDateTimeScrollPanel > ListBoxItem">
|
||||
<Setter Property="Theme" Value="{DynamicResource DateTimePickerItem}" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<ScrollViewer
|
||||
Name="{x:Static u:TimePickerPresenter.PART_HourScrollPanel}"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Left"
|
||||
HorizontalContentAlignment="Left"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Hidden">
|
||||
<u:UrsaDateTimeScrollPanel
|
||||
Name="{x:Static u:TimePickerPresenter.PART_HourSelector}"
|
||||
MinWidth="64"
|
||||
HorizontalAlignment="Left"
|
||||
ItemHeight="32"
|
||||
PanelType="Hour"
|
||||
ShouldLoop="True" />
|
||||
</ScrollViewer>
|
||||
<Rectangle
|
||||
Name="{x:Static u:TimePickerPresenter.PART_FirstSeparator}"
|
||||
Grid.Column="1"
|
||||
Width="1"
|
||||
Margin="0,4"
|
||||
VerticalAlignment="Stretch"
|
||||
Fill="{DynamicResource DateTimePickerSeparatorBackground}" />
|
||||
<ScrollViewer
|
||||
Name="{x:Static u:TimePickerPresenter.PART_MinuteScrollPanel}"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Left"
|
||||
HorizontalContentAlignment="Left"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Hidden">
|
||||
<u:UrsaDateTimeScrollPanel
|
||||
Name="{x:Static u:TimePickerPresenter.PART_MinuteSelector}"
|
||||
MinWidth="64"
|
||||
ItemHeight="32"
|
||||
PanelType="Minute"
|
||||
ShouldLoop="True" />
|
||||
</ScrollViewer>
|
||||
<Rectangle
|
||||
Name="{x:Static u:TimePickerPresenter.PART_SecondSeparator}"
|
||||
Grid.Column="3"
|
||||
Width="1"
|
||||
Margin="0,4"
|
||||
VerticalAlignment="Stretch"
|
||||
Fill="{DynamicResource DateTimePickerSeparatorBackground}" />
|
||||
<ScrollViewer
|
||||
Name="{x:Static u:TimePickerPresenter.PART_SecondScrollPanel}"
|
||||
Grid.Column="4"
|
||||
HorizontalAlignment="Left"
|
||||
HorizontalContentAlignment="Left"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Hidden">
|
||||
<u:UrsaDateTimeScrollPanel
|
||||
Name="{x:Static u:TimePickerPresenter.PART_SecondSelector}"
|
||||
MinWidth="64"
|
||||
ItemHeight="32"
|
||||
PanelType="Minute"
|
||||
ShouldLoop="True" />
|
||||
</ScrollViewer>
|
||||
<Rectangle
|
||||
Name="{x:Static u:TimePickerPresenter.PART_ThirdSeparator}"
|
||||
Grid.Column="5"
|
||||
Width="1"
|
||||
Margin="0,4"
|
||||
VerticalAlignment="Stretch"
|
||||
Fill="{DynamicResource DateTimePickerSeparatorBackground}" />
|
||||
<ScrollViewer
|
||||
Name="{x:Static u:TimePickerPresenter.PART_AmPmScrollPanel}"
|
||||
Grid.Column="6"
|
||||
HorizontalAlignment="Left"
|
||||
HorizontalContentAlignment="Left"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Hidden">
|
||||
<u:UrsaDateTimeScrollPanel
|
||||
Name="{x:Static u:TimePickerPresenter.PART_AmPmSelector}"
|
||||
MinWidth="64"
|
||||
ItemHeight="32"
|
||||
PanelType="TimePeriod" />
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="{x:Type u:TimePicker}" TargetType="u:TimePicker">
|
||||
<Setter Property="Background" Value="{DynamicResource TextBoxDefaultBackground}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBoxForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource TextBoxDefaultBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{DynamicResource TextBoxBorderThickness}" />
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource TextBoxDefaultCornerRadius}" />
|
||||
<Setter Property="MinHeight" Value="32"/>
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="u:TimePicker">
|
||||
<DataValidationErrors>
|
||||
<Panel
|
||||
x:Name="LayoutRoot"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Border
|
||||
x:Name="Background"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}" />
|
||||
<Grid ColumnDefinitions="*, Auto">
|
||||
<TextBox
|
||||
Name="{x:Static u:TimePicker.PART_TextBox}"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
InnerLeftContent="{TemplateBinding InnerLeftContent}"
|
||||
InnerRightContent="{TemplateBinding InnerRightContent}"
|
||||
IsReadOnly="{TemplateBinding IsReadonly}"
|
||||
Theme="{DynamicResource NoErrorTextBox}"
|
||||
Watermark="{TemplateBinding Watermark}">
|
||||
<TextBox.Styles>
|
||||
<Style Selector="TextBox#PART_TextBox:pointerover /template/ Border#PART_ContentPresenterBorder">
|
||||
<!-- By default the TextBox has its own focused state, override this to disable it here -->
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
<Style Selector="TextBox#PART_TextBox:focus /template/ Border#PART_ContentPresenterBorder">
|
||||
<!-- By default the TextBox has its own focused state, override this to disable it here -->
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
<Style Selector="TextBox#PART_TextBox:disabled">
|
||||
<Style Selector="^ /template/ Border#PART_ContentPresenterBorder">
|
||||
<!-- By default the TextBox has its own disabled state, override this to make the border background show through -->
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
</Style>
|
||||
</Style>
|
||||
</TextBox.Styles>
|
||||
</TextBox>
|
||||
<Button
|
||||
Name="ClearButton"
|
||||
Grid.Column="1"
|
||||
Padding="0,0,8,0"
|
||||
Command="{Binding $parent[iri:IClearControl].Clear}"
|
||||
Content="{DynamicResource IconButtonClearData}"
|
||||
Focusable="False"
|
||||
IsVisible="False"
|
||||
Theme="{DynamicResource InnerIconButton}" />
|
||||
<Button
|
||||
Name="{x:Static u:TimePicker.PART_Button}"
|
||||
Grid.Column="1"
|
||||
Padding="0,0,8,0"
|
||||
Content="{DynamicResource TimePickerIconGlyph}"
|
||||
Focusable="False"
|
||||
Theme="{DynamicResource InnerIconButton}" />
|
||||
<Popup
|
||||
Name="{x:Static u:TimePicker.PART_Popup}"
|
||||
Grid.Column="0"
|
||||
IsLightDismissEnabled="True"
|
||||
IsOpen="{TemplateBinding IsDropdownOpen,
|
||||
Mode=TwoWay}"
|
||||
Placement="BottomEdgeAlignedLeft"
|
||||
PlacementTarget="Background">
|
||||
<Border
|
||||
Margin="0,4"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource ComboBoxPopupBackground}"
|
||||
BorderBrush="{DynamicResource ComboBoxPopupBorderBrush}"
|
||||
BorderThickness="{DynamicResource ComboBoxPopupBorderThickness}"
|
||||
BoxShadow="{DynamicResource ComboBoxPopupBoxShadow}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="6">
|
||||
<DockPanel>
|
||||
<StackPanel DockPanel.Dock="Bottom" IsVisible="{TemplateBinding NeedConfirmation}">
|
||||
<Button
|
||||
Margin="8"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{Binding $parent[u:TimePicker].Confirm}"
|
||||
Content="{DynamicResource STRING_DATE_TIME_CONFIRM}" />
|
||||
</StackPanel>
|
||||
<ContentPresenter
|
||||
Name="PART_PopupHeader"
|
||||
Margin="8,8,8,0"
|
||||
Content="{TemplateBinding PopupInnerTopContent}"
|
||||
DockPanel.Dock="Top"
|
||||
IsVisible="{TemplateBinding PopupInnerTopContent,
|
||||
Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
<ContentPresenter
|
||||
Name="PART_PopupFooter"
|
||||
Margin="8,0,8,8"
|
||||
Content="{TemplateBinding PopupInnerBottomContent}"
|
||||
DockPanel.Dock="Bottom"
|
||||
IsVisible="{TemplateBinding PopupInnerBottomContent,
|
||||
Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
<u:TimePickerPresenter
|
||||
Name="{x:Static u:TimePicker.PART_Presenter}"
|
||||
NeedsConfirmation="{TemplateBinding NeedConfirmation}"
|
||||
PanelFormat="{TemplateBinding PanelFormat}"
|
||||
Time="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedTime, Mode=OneWayToSource}" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</Panel>
|
||||
</DataValidationErrors>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
|
||||
<Style Selector="^.clearButton, ^.ClearButton">
|
||||
<Style Selector="^:pointerover /template/ Button#ClearButton">
|
||||
<Setter Property="IsVisible" Value="{Binding $parent[u:TimePicker].SelectedTime, Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
</Style>
|
||||
<Style Selector="^:pointerover /template/ Button#PART_Button">
|
||||
<Setter Property="IsVisible" Value="{Binding $parent[u:TimePicker].SelectedTime, Converter={x:Static ObjectConverters.IsNull}}"/>
|
||||
</Style>
|
||||
</Style>
|
||||
|
||||
<Style Selector="^:pointerover">
|
||||
<Style Selector="^ /template/ Border#Background">
|
||||
<Setter Property="Background" Value="{DynamicResource CalendarDatePickerPointeroverBackground}" />
|
||||
</Style>
|
||||
</Style>
|
||||
|
||||
<!-- Disabled State -->
|
||||
<Style Selector="^:disabled">
|
||||
<Style Selector="^ /template/ Border#Background">
|
||||
<Setter Property="Background" Value="{DynamicResource CalendarDatePickerDisabledBackground}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^ /template/ Button#PART_Button">
|
||||
<Setter Property="Foreground" Value="{DynamicResource CalendarDatePickerDisabledIconForeground}" />
|
||||
</Style>
|
||||
<Style Selector="^ /template/ TextBox#PART_TextBox">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
</Style>
|
||||
</Style>
|
||||
|
||||
<!-- Focused State -->
|
||||
<Style Selector="^:focus /template/ Border#Background">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource CalendarDatePickerFocusBorderBrush}" />
|
||||
</Style>
|
||||
<Style Selector="^:focus-within /template/ Border#Background">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource CalendarDatePickerFocusBorderBrush}" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
@@ -32,6 +32,7 @@
|
||||
<ResourceInclude Source="SelectionList.axaml" />
|
||||
<ResourceInclude Source="TagInput.axaml" />
|
||||
<ResourceInclude Source="ThemeSelector.axaml" />
|
||||
<ResourceInclude Source="TimePicker.axaml" />
|
||||
<ResourceInclude Source="Timeline.axaml" />
|
||||
<ResourceInclude Source="TreeComboBox.axaml"/>
|
||||
<ResourceInclude Source="Skeleton.axaml" />
|
||||
|
||||
@@ -15,4 +15,5 @@
|
||||
<x:String x:Key="STRING_THEME_TOGGLE_DARK">Dark</x:String>
|
||||
<x:String x:Key="STRING_THEME_TOGGLE_LIGHT">Light</x:String>
|
||||
<x:String x:Key="STRING_THEME_TOGGLE_SYSTEM">System</x:String>
|
||||
<x:String x:Key="STRING_DATE_TIME_CONFIRM">Confirm</x:String>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -15,4 +15,5 @@
|
||||
<x:String x:Key="STRING_THEME_TOGGLE_DARK">暗色</x:String>
|
||||
<x:String x:Key="STRING_THEME_TOGGLE_LIGHT">亮色</x:String>
|
||||
<x:String x:Key="STRING_THEME_TOGGLE_SYSTEM">跟随系统</x:String>
|
||||
<x:String x:Key="STRING_DATE_TIME_CONFIRM">确认</x:String>
|
||||
</ResourceDictionary>
|
||||
|
||||
264
src/Ursa/Controls/DateTimePicker/TimePicker.cs
Normal file
264
src/Ursa/Controls/DateTimePicker/TimePicker.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using System.Globalization;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Irihi.Avalonia.Shared.Contracts;
|
||||
using Irihi.Avalonia.Shared.Helpers;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
[TemplatePart(PART_TextBox, typeof(TextBox))]
|
||||
[TemplatePart(PART_Popup, typeof(Popup))]
|
||||
[TemplatePart(PART_Presenter, typeof(TimePickerPresenter))]
|
||||
[TemplatePart(PART_Button, typeof(Button))]
|
||||
public class TimePicker : TemplatedControl, IClearControl, IInnerContentControl, IPopupInnerContent
|
||||
{
|
||||
public const string PART_TextBox = "PART_TextBox";
|
||||
public const string PART_Popup = "PART_Popup";
|
||||
public const string PART_Presenter = "PART_Presenter";
|
||||
public const string PART_Button = "PART_Button";
|
||||
|
||||
public static readonly StyledProperty<string?> DisplayFormatProperty =
|
||||
AvaloniaProperty.Register<TimePicker, string?>(
|
||||
nameof(DisplayFormat), "HH:mm:ss");
|
||||
|
||||
public static readonly StyledProperty<string> PanelFormatProperty = AvaloniaProperty.Register<TimePicker, string>(
|
||||
nameof(PanelFormat), "HH mm ss");
|
||||
|
||||
public static readonly StyledProperty<TimeSpan?> SelectedTimeProperty =
|
||||
AvaloniaProperty.Register<TimePicker, TimeSpan?>(
|
||||
nameof(SelectedTime));
|
||||
|
||||
public static readonly StyledProperty<bool> NeedConfirmationProperty = AvaloniaProperty.Register<TimePicker, bool>(
|
||||
nameof(NeedConfirmation));
|
||||
|
||||
public static readonly StyledProperty<object?> InnerLeftContentProperty =
|
||||
AvaloniaProperty.Register<TimePicker, object?>(
|
||||
nameof(InnerLeftContent));
|
||||
|
||||
public static readonly StyledProperty<object?> InnerRightContentProperty =
|
||||
AvaloniaProperty.Register<TimePicker, object?>(
|
||||
nameof(InnerRightContent));
|
||||
|
||||
|
||||
public static readonly StyledProperty<object?> PopupInnerTopContentProperty =
|
||||
AvaloniaProperty.Register<TimePicker, object?>(
|
||||
nameof(PopupInnerTopContent));
|
||||
|
||||
public static readonly StyledProperty<object?> PopupInnerBottomContentProperty =
|
||||
AvaloniaProperty.Register<TimePicker, object?>(
|
||||
nameof(PopupInnerBottomContent));
|
||||
|
||||
public static readonly StyledProperty<string?> WatermarkProperty = AvaloniaProperty.Register<TimePicker, string?>(
|
||||
nameof(Watermark));
|
||||
|
||||
public static readonly StyledProperty<bool> IsDropdownOpenProperty = AvaloniaProperty.Register<TimePicker, bool>(
|
||||
nameof(IsDropdownOpen), defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public static readonly StyledProperty<bool> IsReadonlyProperty = AvaloniaProperty.Register<TimePicker, bool>(
|
||||
nameof(IsReadonly));
|
||||
|
||||
private Button? _button;
|
||||
private Popup? _popup;
|
||||
private TimePickerPresenter? _presenter;
|
||||
private TextBox? _textBox;
|
||||
|
||||
|
||||
static TimePicker()
|
||||
{
|
||||
SelectedTimeProperty.Changed.AddClassHandler<TimePicker, TimeSpan?>((picker, args) =>
|
||||
picker.OnSelectionChanged(args));
|
||||
}
|
||||
|
||||
public bool IsReadonly
|
||||
{
|
||||
get => GetValue(IsReadonlyProperty);
|
||||
set => SetValue(IsReadonlyProperty, value);
|
||||
}
|
||||
|
||||
public bool IsDropdownOpen
|
||||
{
|
||||
get => GetValue(IsDropdownOpenProperty);
|
||||
set => SetValue(IsDropdownOpenProperty, value);
|
||||
}
|
||||
|
||||
public string? Watermark
|
||||
{
|
||||
get => GetValue(WatermarkProperty);
|
||||
set => SetValue(WatermarkProperty, value);
|
||||
}
|
||||
|
||||
public string? DisplayFormat
|
||||
{
|
||||
get => GetValue(DisplayFormatProperty);
|
||||
set => SetValue(DisplayFormatProperty, value);
|
||||
}
|
||||
|
||||
public string PanelFormat
|
||||
{
|
||||
get => GetValue(PanelFormatProperty);
|
||||
set => SetValue(PanelFormatProperty, value);
|
||||
}
|
||||
|
||||
public TimeSpan? SelectedTime
|
||||
{
|
||||
get => GetValue(SelectedTimeProperty);
|
||||
set => SetValue(SelectedTimeProperty, value);
|
||||
}
|
||||
|
||||
public bool NeedConfirmation
|
||||
{
|
||||
get => GetValue(NeedConfirmationProperty);
|
||||
set => SetValue(NeedConfirmationProperty, value);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Focus(NavigationMethod.Pointer);
|
||||
_presenter?.SetValue(TimePickerPresenter.TimeProperty, null);
|
||||
}
|
||||
|
||||
public object? InnerLeftContent
|
||||
{
|
||||
get => GetValue(InnerLeftContentProperty);
|
||||
set => SetValue(InnerLeftContentProperty, value);
|
||||
}
|
||||
|
||||
public object? InnerRightContent
|
||||
{
|
||||
get => GetValue(InnerRightContentProperty);
|
||||
set => SetValue(InnerRightContentProperty, value);
|
||||
}
|
||||
|
||||
public object? PopupInnerTopContent
|
||||
{
|
||||
get => GetValue(PopupInnerTopContentProperty);
|
||||
set => SetValue(PopupInnerTopContentProperty, value);
|
||||
}
|
||||
|
||||
public object? PopupInnerBottomContent
|
||||
{
|
||||
get => GetValue(PopupInnerBottomContentProperty);
|
||||
set => SetValue(PopupInnerBottomContentProperty, value);
|
||||
}
|
||||
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
GotFocusEvent.RemoveHandler(OnTextBoxGetFocus, _textBox);
|
||||
TextBox.TextChangedEvent.RemoveHandler(OnTextChanged, _textBox);
|
||||
PointerPressedEvent.RemoveHandler(OnTextBoxPointerPressed, _textBox);
|
||||
Button.ClickEvent.RemoveHandler(OnButtonClick, _button);
|
||||
|
||||
_textBox = e.NameScope.Find<TextBox>(PART_TextBox);
|
||||
_popup = e.NameScope.Find<Popup>(PART_Popup);
|
||||
_presenter = e.NameScope.Find<TimePickerPresenter>(PART_Presenter);
|
||||
_button = e.NameScope.Find<Button>(PART_Button);
|
||||
|
||||
GotFocusEvent.AddHandler(OnTextBoxGetFocus, _textBox);
|
||||
TextBox.TextChangedEvent.AddHandler(OnTextChanged, _textBox);
|
||||
PointerPressedEvent.AddHandler(OnTextBoxPointerPressed, RoutingStrategies.Tunnel, false, _textBox);
|
||||
Button.ClickEvent.AddHandler(OnButtonClick, _button);
|
||||
|
||||
SetCurrentValue(SelectedTimeProperty, DateTime.Now.TimeOfDay);
|
||||
}
|
||||
|
||||
private void OnButtonClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Focus(NavigationMethod.Pointer);
|
||||
SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen);
|
||||
}
|
||||
|
||||
private void OnTextBoxPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||
}
|
||||
|
||||
private void OnTextBoxGetFocus(object? sender, GotFocusEventArgs e)
|
||||
{
|
||||
IsDropdownOpen = true;
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape)
|
||||
{
|
||||
SetCurrentValue(IsDropdownOpenProperty, false);
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
if (e.Key == Key.Down)
|
||||
{
|
||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Key == Key.Tab)
|
||||
{
|
||||
SetCurrentValue(IsDropdownOpenProperty, false);
|
||||
return;
|
||||
}
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
|
||||
private void OnTextChanged(object? sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_textBox?.Text))
|
||||
{
|
||||
TimePickerPresenter.TimeProperty.SetValue(null, _presenter);
|
||||
}
|
||||
else if (DisplayFormat is null || DisplayFormat.Length == 0)
|
||||
{
|
||||
if (TimeSpan.TryParse(_textBox?.Text, out var defaultTime))
|
||||
TimePickerPresenter.TimeProperty.SetValue(defaultTime, _presenter);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DateTime.TryParseExact(_textBox?.Text, DisplayFormat, CultureInfo.CurrentUICulture, DateTimeStyles.None,
|
||||
out var time)) TimePickerPresenter.TimeProperty.SetValue(time.TimeOfDay, _presenter);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs<TimeSpan?> args)
|
||||
{
|
||||
if (_textBox is null) return;
|
||||
var time = args.NewValue.Value;
|
||||
if (time is null)
|
||||
{
|
||||
_textBox.Text = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var date = new DateTime(1, 1, 1, time.Value.Hours, time.Value.Minutes, time.Value.Seconds);
|
||||
var text = date.ToString(DisplayFormat);
|
||||
_textBox.Text = text;
|
||||
}
|
||||
|
||||
public void Confirm()
|
||||
{
|
||||
_presenter?.Confirm();
|
||||
SetCurrentValue(IsDropdownOpenProperty, false);
|
||||
TopLevel.GetTopLevel(this);
|
||||
Focus();
|
||||
}
|
||||
|
||||
public void Dismiss()
|
||||
{
|
||||
SetCurrentValue(IsDropdownOpenProperty, false);
|
||||
Focus();
|
||||
}
|
||||
|
||||
protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
|
||||
{
|
||||
base.UpdateDataValidation(property, state, error);
|
||||
if (property == SelectedTimeProperty) DataValidationErrors.SetError(this, error);
|
||||
}
|
||||
}
|
||||
318
src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs
Normal file
318
src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs
Normal file
@@ -0,0 +1,318 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Irihi.Avalonia.Shared.Helpers;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
[TemplatePart(PART_PickerContainer, typeof(Grid))]
|
||||
[TemplatePart(PART_HourSelector, typeof(DateTimePickerPanel))]
|
||||
[TemplatePart(PART_MinuteSelector, typeof(DateTimePickerPanel))]
|
||||
[TemplatePart(PART_SecondSelector, typeof(DateTimePickerPanel))]
|
||||
[TemplatePart(PART_AmPmSelector, typeof(DateTimePickerPanel))]
|
||||
[TemplatePart(PART_HourScrollPanel, typeof(Control))]
|
||||
[TemplatePart(PART_MinuteScrollPanel, typeof(Control))]
|
||||
[TemplatePart(PART_SecondScrollPanel, typeof(Control))]
|
||||
[TemplatePart(PART_AmPmScrollPanel, typeof(Control))]
|
||||
[TemplatePart(PART_FirstSeparator, typeof(Control))]
|
||||
[TemplatePart(PART_SecondSeparator, typeof(Control))]
|
||||
[TemplatePart(PART_ThirdSeparator, typeof(Control))]
|
||||
public class TimePickerPresenter : TemplatedControl
|
||||
{
|
||||
public const string PART_HourSelector = "PART_HourSelector";
|
||||
public const string PART_MinuteSelector = "PART_MinuteSelector";
|
||||
public const string PART_SecondSelector = "PART_SecondSelector";
|
||||
public const string PART_AmPmSelector = "PART_AmPmSelector";
|
||||
public const string PART_PickerContainer = "PART_PickerContainer";
|
||||
|
||||
public const string PART_HourScrollPanel = "PART_HourScrollPanel";
|
||||
public const string PART_MinuteScrollPanel = "PART_MinuteScrollPanel";
|
||||
public const string PART_SecondScrollPanel = "PART_SecondScrollPanel";
|
||||
public const string PART_AmPmScrollPanel = "PART_AmPmScrollPanel";
|
||||
|
||||
public const string PART_FirstSeparator = "PART_FirstSeparator";
|
||||
public const string PART_SecondSeparator = "PART_SecondSeparator";
|
||||
public const string PART_ThirdSeparator = "PART_ThirdSeparator";
|
||||
|
||||
|
||||
public static readonly StyledProperty<bool> NeedsConfirmationProperty =
|
||||
AvaloniaProperty.Register<TimePickerPresenter, bool>(
|
||||
nameof(NeedsConfirmation));
|
||||
|
||||
public static readonly StyledProperty<int> MinuteIncrementProperty =
|
||||
AvaloniaProperty.Register<TimePickerPresenter, int>(
|
||||
nameof(MinuteIncrement));
|
||||
|
||||
public static readonly StyledProperty<int> SecondIncrementProperty =
|
||||
AvaloniaProperty.Register<TimePickerPresenter, int>(
|
||||
nameof(SecondIncrement));
|
||||
|
||||
public static readonly StyledProperty<TimeSpan?> TimeProperty =
|
||||
AvaloniaProperty.Register<TimePickerPresenter, TimeSpan?>(
|
||||
nameof(Time));
|
||||
|
||||
public static readonly StyledProperty<string> PanelFormatProperty =
|
||||
AvaloniaProperty.Register<TimePickerPresenter, string>(
|
||||
nameof(PanelFormat), "HH mm ss t");
|
||||
|
||||
private Control? _ampmScrollPanel;
|
||||
private DateTimePickerPanel? _ampmSelector;
|
||||
private Control? _firstSeparator;
|
||||
private Control? _hourScrollPanel;
|
||||
|
||||
private DateTimePickerPanel? _hourSelector;
|
||||
private Control? _minuteScrollPanel;
|
||||
private DateTimePickerPanel? _minuteSelector;
|
||||
private Grid? _pickerContainer;
|
||||
private Control? _secondScrollPanel;
|
||||
private DateTimePickerPanel? _secondSelector;
|
||||
private Control? _secondSeparator;
|
||||
private Control? _thirdSeparator;
|
||||
internal TimeSpan _timeHolder;
|
||||
private bool _updateFromTimeChange;
|
||||
private bool _use12Clock;
|
||||
|
||||
static TimePickerPresenter()
|
||||
{
|
||||
PanelFormatProperty.Changed.AddClassHandler<TimePickerPresenter, string>((presenter, args) =>
|
||||
presenter.OnPanelFormatChanged(args));
|
||||
TimeProperty.Changed.AddClassHandler<TimePickerPresenter, TimeSpan?>((presenter, args) =>
|
||||
presenter.OnTimeChanged(args));
|
||||
}
|
||||
|
||||
public TimePickerPresenter()
|
||||
{
|
||||
SetCurrentValue(TimeProperty, DateTime.Now.TimeOfDay);
|
||||
}
|
||||
|
||||
public bool NeedsConfirmation
|
||||
{
|
||||
get => GetValue(NeedsConfirmationProperty);
|
||||
set => SetValue(NeedsConfirmationProperty, value);
|
||||
}
|
||||
|
||||
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 string PanelFormat
|
||||
{
|
||||
get => GetValue(PanelFormatProperty);
|
||||
set => SetValue(PanelFormatProperty, value);
|
||||
}
|
||||
|
||||
public event EventHandler<TimePickerSelectedValueChangedEventArgs>? SelectedTimeChanged;
|
||||
|
||||
private void OnTimeChanged(AvaloniaPropertyChangedEventArgs<TimeSpan?> args)
|
||||
{
|
||||
_updateFromTimeChange = true;
|
||||
UpdatePanelsFromSelectedTime(args.NewValue.Value);
|
||||
_updateFromTimeChange = false;
|
||||
SelectedTimeChanged?.Invoke(this,
|
||||
new TimePickerSelectedValueChangedEventArgs(args.OldValue.Value, args.NewValue.Value));
|
||||
}
|
||||
|
||||
private void OnPanelFormatChanged(AvaloniaPropertyChangedEventArgs<string> args)
|
||||
{
|
||||
var format = args.NewValue.Value;
|
||||
|
||||
UpdatePanelLayout(format);
|
||||
}
|
||||
|
||||
private void UpdatePanelLayout(string panelFormat)
|
||||
{
|
||||
var parts = panelFormat.Split(new[] { ' ', '-', ':' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var panels = new List<Control?>();
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (part.Length < 1) continue;
|
||||
if ((part.Contains('h') || part.Contains('H')) && !panels.Contains(_hourScrollPanel))
|
||||
{
|
||||
panels.Add(_hourScrollPanel);
|
||||
_use12Clock = part.Contains('h');
|
||||
_hourSelector?.SetValue(DateTimePickerPanel.ItemFormatProperty, part.ToLower());
|
||||
if (_hourSelector is not null)
|
||||
{
|
||||
_hourSelector.MaximumValue = _use12Clock ? 12 : 23;
|
||||
_hourSelector.MinimumValue = _use12Clock ? 1: 0;
|
||||
}
|
||||
}
|
||||
else if (part[0] == 'm' && !panels.Contains(_minuteSelector))
|
||||
{
|
||||
panels.Add(_minuteScrollPanel);
|
||||
_minuteSelector?.SetValue(DateTimePickerPanel.ItemFormatProperty, part);
|
||||
}
|
||||
else if (part[0] == 's' && !panels.Contains(_secondScrollPanel))
|
||||
{
|
||||
panels.Add(_secondScrollPanel);
|
||||
_secondSelector?.SetValue(DateTimePickerPanel.ItemFormatProperty, part.Replace('s', 'm'));
|
||||
}
|
||||
else if (part[0] == 't' && !panels.Contains(_ampmScrollPanel))
|
||||
{
|
||||
panels.Add(_ampmScrollPanel);
|
||||
_ampmSelector?.SetValue(DateTimePickerPanel.ItemFormatProperty, part);
|
||||
}
|
||||
}
|
||||
|
||||
if (panels.Count < 1) return;
|
||||
IsVisibleProperty.SetValue(false, _hourScrollPanel, _minuteScrollPanel, _secondScrollPanel, _ampmScrollPanel,
|
||||
_firstSeparator, _secondSeparator, _thirdSeparator);
|
||||
for (var i = 0; i < panels.Count; i++)
|
||||
{
|
||||
var panel = panels[i];
|
||||
if (panel is null) continue;
|
||||
panel.IsVisible = true;
|
||||
Grid.SetColumn(panel, 2 * i);
|
||||
var separator = i switch
|
||||
{
|
||||
0 => _firstSeparator,
|
||||
1 => _secondSeparator,
|
||||
2 => _thirdSeparator,
|
||||
_ => null
|
||||
};
|
||||
if (i != panels.Count - 1) IsVisibleProperty.SetValue(true, separator);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
if (_hourSelector is not null) _hourSelector.SelectionChanged -= OnPanelSelectionChanged;
|
||||
if (_minuteSelector is not null) _minuteSelector.SelectionChanged -= OnPanelSelectionChanged;
|
||||
if (_secondSelector is not null) _secondSelector.SelectionChanged -= OnPanelSelectionChanged;
|
||||
if (_ampmSelector is not null) _ampmSelector.SelectionChanged -= OnPanelSelectionChanged;
|
||||
_hourSelector = e.NameScope.Find<DateTimePickerPanel>(PART_HourSelector);
|
||||
_minuteSelector = e.NameScope.Find<DateTimePickerPanel>(PART_MinuteSelector);
|
||||
_secondSelector = e.NameScope.Find<DateTimePickerPanel>(PART_SecondSelector);
|
||||
_ampmSelector = e.NameScope.Find<DateTimePickerPanel>(PART_AmPmSelector);
|
||||
if (_hourSelector is not null) _hourSelector.SelectionChanged += OnPanelSelectionChanged;
|
||||
if (_minuteSelector is not null) _minuteSelector.SelectionChanged += OnPanelSelectionChanged;
|
||||
if (_secondSelector is not null) _secondSelector.SelectionChanged += OnPanelSelectionChanged;
|
||||
if (_ampmSelector is not null) _ampmSelector.SelectionChanged += OnPanelSelectionChanged;
|
||||
_pickerContainer = e.NameScope.Find<Grid>(PART_PickerContainer);
|
||||
_hourScrollPanel = e.NameScope.Find<Control>(PART_HourScrollPanel);
|
||||
_minuteScrollPanel = e.NameScope.Find<Control>(PART_MinuteScrollPanel);
|
||||
_secondScrollPanel = e.NameScope.Find<Control>(PART_SecondScrollPanel);
|
||||
_ampmScrollPanel = e.NameScope.Find<Control>(PART_AmPmScrollPanel);
|
||||
_firstSeparator = e.NameScope.Find<Control>(PART_FirstSeparator);
|
||||
_secondSeparator = e.NameScope.Find<Control>(PART_SecondSeparator);
|
||||
_thirdSeparator = e.NameScope.Find<Control>(PART_ThirdSeparator);
|
||||
Initialize();
|
||||
UpdatePanelLayout(PanelFormat);
|
||||
UpdatePanelsFromSelectedTime(Time);
|
||||
}
|
||||
|
||||
private void OnPanelSelectionChanged(object sender, System.EventArgs e)
|
||||
{
|
||||
if (_updateFromTimeChange) return;
|
||||
if (!_use12Clock && sender == _ampmSelector) return;
|
||||
var time = NeedsConfirmation ? _timeHolder : Time ?? DateTime.Now.TimeOfDay;
|
||||
var hour = _hourSelector?.SelectedValue ?? time.Hours;
|
||||
var minute = _minuteSelector?.SelectedValue ?? time.Minutes;
|
||||
var second = _secondSelector?.SelectedValue ?? time.Seconds;
|
||||
var ampm = _ampmSelector?.SelectedValue ?? (time.Hours >= 12 ? 1 : 0);
|
||||
if (_use12Clock)
|
||||
hour = ampm switch
|
||||
{
|
||||
0 when hour == 12 => 0,
|
||||
1 when hour < 12 => hour + 12,
|
||||
_ => hour
|
||||
};
|
||||
else
|
||||
{
|
||||
ampm = hour switch
|
||||
{
|
||||
>= 12 => 1,
|
||||
_ => 0
|
||||
};
|
||||
SetIfChanged(_ampmSelector, ampm);
|
||||
}
|
||||
var newTime = new TimeSpan(hour, minute, second);
|
||||
if (NeedsConfirmation)
|
||||
_timeHolder = newTime;
|
||||
else
|
||||
SetCurrentValue(TimeProperty, newTime);
|
||||
}
|
||||
|
||||
private void UpdatePanelsFromSelectedTime(TimeSpan? time)
|
||||
{
|
||||
if (time is null) return;
|
||||
if (_hourSelector is not null)
|
||||
{
|
||||
var index = _use12Clock ? time.Value.Hours % 12 : time.Value.Hours;
|
||||
if (_use12Clock && index == 0) index = 12;
|
||||
SetIfChanged(_hourSelector, index);
|
||||
}
|
||||
SetIfChanged(_minuteSelector, time.Value.Minutes);
|
||||
SetIfChanged(_secondSelector, time.Value.Seconds);
|
||||
var ampm = time.Value.Hours switch
|
||||
{
|
||||
>= 12 => 1,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
SetIfChanged(_ampmSelector, ampm);
|
||||
if (_ampmSelector is not null)
|
||||
{
|
||||
_ampmSelector.IsEnabled = _use12Clock;
|
||||
}
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (_pickerContainer is null) return;
|
||||
if (_hourSelector is not null)
|
||||
{
|
||||
_hourSelector.ItemFormat = "hh";
|
||||
_hourSelector.MaximumValue = _use12Clock ? 12 : 23;
|
||||
_hourSelector.MinimumValue = _use12Clock ? 1 : 0;
|
||||
}
|
||||
|
||||
if (_minuteSelector is not null)
|
||||
{
|
||||
_minuteSelector.ItemFormat = "mm";
|
||||
_minuteSelector.MaximumValue = 59;
|
||||
_minuteSelector.MinimumValue = 0;
|
||||
}
|
||||
|
||||
if (_secondSelector is not null)
|
||||
{
|
||||
_secondSelector.ItemFormat = "mm";
|
||||
_secondSelector.MaximumValue = 59;
|
||||
_secondSelector.MinimumValue = 0;
|
||||
}
|
||||
|
||||
if (_ampmSelector is not null)
|
||||
{
|
||||
_ampmSelector.ItemFormat = "t";
|
||||
_ampmSelector.MaximumValue = 1;
|
||||
_ampmSelector.MinimumValue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void Confirm()
|
||||
{
|
||||
if (NeedsConfirmation) SetCurrentValue(TimeProperty, _timeHolder);
|
||||
}
|
||||
|
||||
private void SetIfChanged(DateTimePickerPanel? panel, int index)
|
||||
{
|
||||
if (panel is null) return;
|
||||
if (panel.SelectedValue != index) panel.SelectedValue = index;
|
||||
}
|
||||
}
|
||||
29
src/Ursa/Controls/DateTimePicker/UrsaDateTimeScrollPanel.cs
Normal file
29
src/Ursa/Controls/DateTimePicker/UrsaDateTimeScrollPanel.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public class UrsaDateTimeScrollPanel : DateTimePickerPanel
|
||||
{
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
var size = base.MeasureOverride(availableSize);
|
||||
var width = this.Children.Select(a=>a.DesiredSize.Width).Max();
|
||||
width = Math.Max(width, this.MinWidth);
|
||||
return new Size(width, size.Height);
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
var width = this.Children.Select(a=>a.DesiredSize.Width).Max();
|
||||
width = Math.Max(width, this.MinWidth);
|
||||
finalSize = new Size(width, finalSize.Height);
|
||||
return base.ArrangeOverride(finalSize);
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Controls\Panels\"/>
|
||||
<None Include="irihi.png" Pack="true" PackagePath=""/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Controls\Panels\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user