Files
Ursa.Avalonia/src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs
2025-02-18 18:02:37 +08:00

349 lines
13 KiB
C#

using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
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), 1);
public static readonly StyledProperty<int> SecondIncrementProperty =
AvaloniaProperty.Register<TimePickerPresenter, int>(
nameof(SecondIncrement), 1);
public static readonly StyledProperty<string> PanelFormatProperty =
AvaloniaProperty.Register<TimePickerPresenter, string>(
nameof(PanelFormat), "HH mm ss t");
public static readonly RoutedEvent<TimeChangedEventArgs> SelectedTimeChangedEvent =
RoutedEvent.Register<TimePickerPresenter, TimeChangedEventArgs>(
nameof(SelectedTimeChanged), RoutingStrategies.Bubble);
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 bool _surpressTimeEvent = true;
private Control? _thirdSeparator;
private bool _use12Clock;
internal TimeSpan? TimeHolder;
static TimePickerPresenter()
{
PanelFormatProperty.Changed.AddClassHandler<TimePickerPresenter, string>((presenter, args) =>
presenter.OnPanelFormatChanged(args));
}
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 string PanelFormat
{
get => GetValue(PanelFormatProperty);
set => SetValue(PanelFormatProperty, value);
}
public event EventHandler<TimeChangedEventArgs> SelectedTimeChanged
{
add => AddHandler(SelectedTimeChangedEvent, value);
remove => RemoveHandler(SelectedTimeChangedEvent, value);
}
private void OnPanelFormatChanged(AvaloniaPropertyChangedEventArgs<string> args)
{
var format = args.NewValue.Value;
UpdatePanelLayout(format);
}
private void UpdatePanelLayout(string? panelFormat)
{
if (panelFormat is null) return;
var parts = panelFormat.Split(new[] { ' ', '-', ':' }, StringSplitOptions.RemoveEmptyEntries);
var panels = new List<Control?>();
foreach (var part in parts)
{
if (part.Length < 1) continue;
try
{
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);
}
}
catch
{
// ignored
}
}
if (panels.Count < 1) return;
IsVisibleProperty.SetValue(false, _hourScrollPanel, _minuteScrollPanel, _secondScrollPanel, _ampmScrollPanel,
_firstSeparator, _secondSeparator, _thirdSeparator);
Grid.ColumnProperty.SetValue(0, _hourScrollPanel, _minuteScrollPanel, _secondScrollPanel, _ampmScrollPanel);
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);
_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);
_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);
_surpressTimeEvent = false;
}
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
UpdatePanelsFromSelectedTime(TimeHolder);
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;
}
protected override void OnUnloaded(RoutedEventArgs e)
{
base.OnUnloaded(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;
}
private void OnPanelSelectionChanged(object? sender, System.EventArgs e)
{
if (_surpressTimeEvent) return;
if (!_use12Clock && Equals(sender, _ampmSelector)) return;
var time = TimeHolder ?? 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
{
if (_surpressTimeEvent) return;
RaiseEvent(new TimeChangedEventArgs(null, newTime) { RoutedEvent = SelectedTimeChangedEvent });
}
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
OnPanelSelectionChanged(null, System.EventArgs.Empty);
}
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, true);
}
SetIfChanged(_minuteSelector, time.Value.Minutes, true);
SetIfChanged(_secondSelector, time.Value.Seconds, true);
var ampm = time.Value.Hours switch
{
>= 12 => 1,
_ => 0
};
SetIfChanged(_ampmSelector, ampm, true);
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)
RaiseEvent(new TimeChangedEventArgs(null, TimeHolder) { RoutedEvent = SelectedTimeChangedEvent });
}
private void SetIfChanged(DateTimePickerPanel? panel, int index, bool surpress = false)
{
if (panel is null) return;
panel.SelectionChanged -= OnPanelSelectionChanged;
if (panel.SelectedValue != index) panel.SelectedValue = index;
panel.SelectionChanged += OnPanelSelectionChanged;
}
internal void SyncTime(TimeSpan? time)
{
_surpressTimeEvent = true;
TimeHolder = time;
UpdatePanelsFromSelectedTime(time);
_surpressTimeEvent = false;
}
}