feat: add demo for presenter temporarily.
This commit is contained in:
11
demo/Ursa.Demo/Pages/TimePickerDemo.axaml
Normal file
11
demo/Ursa.Demo/Pages/TimePickerDemo.axaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<UserControl 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"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Ursa.Demo.Pages.TimePickerDemo">
|
||||||
|
<StackPanel HorizontalAlignment="Left">
|
||||||
|
<u:TimePickerPresenter HorizontalAlignment="Left" VerticalAlignment="Top"/>
|
||||||
|
</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.MenuKeySelectionList => new SelectionListDemoViewModel(),
|
||||||
MenuKeys.MenuKeySkeleton => new SkeletonDemoViewModel(),
|
MenuKeys.MenuKeySkeleton => new SkeletonDemoViewModel(),
|
||||||
MenuKeys.MenuKeyTagInput => new TagInputDemoViewModel(),
|
MenuKeys.MenuKeyTagInput => new TagInputDemoViewModel(),
|
||||||
|
MenuKeys.MenuKeyTimePicker => new TimePickerDemoViewModel(),
|
||||||
MenuKeys.MenuKeyTimeline => new TimelineDemoViewModel(),
|
MenuKeys.MenuKeyTimeline => new TimelineDemoViewModel(),
|
||||||
MenuKeys.MenuKeyTreeComboBox => new TreeComboBoxDemoViewModel(),
|
MenuKeys.MenuKeyTreeComboBox => new TreeComboBoxDemoViewModel(),
|
||||||
MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(),
|
MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(),
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public class MenuViewModel: ViewModelBase
|
|||||||
new() { MenuHeader = "Skeleton", Key = MenuKeys.MenuKeySkeleton },
|
new() { MenuHeader = "Skeleton", Key = MenuKeys.MenuKeySkeleton },
|
||||||
new() { MenuHeader = "TagInput", Key = MenuKeys.MenuKeyTagInput },
|
new() { MenuHeader = "TagInput", Key = MenuKeys.MenuKeyTagInput },
|
||||||
new() { MenuHeader = "Theme Toggler", Key = MenuKeys.MenuKeyThemeToggler },
|
new() { MenuHeader = "Theme Toggler", Key = MenuKeys.MenuKeyThemeToggler },
|
||||||
|
new() { MenuHeader = "TimePicker", Key = MenuKeys.MenuKeyTimePicker },
|
||||||
new() { MenuHeader = "Timeline", Key = MenuKeys.MenuKeyTimeline },
|
new() { MenuHeader = "Timeline", Key = MenuKeys.MenuKeyTimeline },
|
||||||
new() { MenuHeader = "TreeComboBox", Key = MenuKeys.MenuKeyTreeComboBox },
|
new() { MenuHeader = "TreeComboBox", Key = MenuKeys.MenuKeyTreeComboBox },
|
||||||
new() { MenuHeader = "TwoTonePathIcon", Key = MenuKeys.MenuKeyTwoTonePathIcon},
|
new() { MenuHeader = "TwoTonePathIcon", Key = MenuKeys.MenuKeyTwoTonePathIcon},
|
||||||
@@ -86,6 +87,7 @@ public static class MenuKeys
|
|||||||
public const string MenuKeySelectionList = "SelectionList";
|
public const string MenuKeySelectionList = "SelectionList";
|
||||||
public const string MenuKeyTagInput = "TagInput";
|
public const string MenuKeyTagInput = "TagInput";
|
||||||
public const string MenuKeySkeleton = "Skeleton";
|
public const string MenuKeySkeleton = "Skeleton";
|
||||||
|
public const string MenuKeyTimePicker = "TimePicker";
|
||||||
public const string MenuKeyTimeline = "Timeline";
|
public const string MenuKeyTimeline = "Timeline";
|
||||||
public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon";
|
public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon";
|
||||||
public const string MenuKeyThemeToggler = "ThemeToggler";
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
77
src/Ursa.Themes.Semi/Controls/TimePicker.axaml
Normal file
77
src/Ursa.Themes.Semi/Controls/TimePicker.axaml
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:u="https://irihi.tech/ursa">
|
||||||
|
<!-- Add Resources Here -->
|
||||||
|
<Design.PreviewWith>
|
||||||
|
<u:TimePickerPresenter Width="600" 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}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
ColumnDefinitions="*, *, *, *">
|
||||||
|
<Grid.Styles>
|
||||||
|
<Style Selector="DateTimePickerPanel > ListBoxItem">
|
||||||
|
<Setter Property="Theme" Value="{DynamicResource DateTimePickerItem}"/>
|
||||||
|
</Style>
|
||||||
|
</Grid.Styles>
|
||||||
|
<ScrollViewer
|
||||||
|
Grid.Column="0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Hidden">
|
||||||
|
<DateTimePickerPanel
|
||||||
|
Name="{x:Static u:TimePickerPresenter.PART_HourSelector}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="64"
|
||||||
|
ItemHeight="32"
|
||||||
|
PanelType="Hour"
|
||||||
|
ShouldLoop="True" />
|
||||||
|
</ScrollViewer>
|
||||||
|
<ScrollViewer
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Hidden">
|
||||||
|
<DateTimePickerPanel
|
||||||
|
Name="{x:Static u:TimePickerPresenter.PART_MinuteSelector}"
|
||||||
|
MinWidth="64"
|
||||||
|
ItemHeight="32"
|
||||||
|
PanelType="Minute"
|
||||||
|
ShouldLoop="True" />
|
||||||
|
</ScrollViewer>
|
||||||
|
<ScrollViewer
|
||||||
|
Grid.Column="2"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Hidden">
|
||||||
|
<DateTimePickerPanel
|
||||||
|
Name="{x:Static u:TimePickerPresenter.PART_SecondSelector}"
|
||||||
|
MinWidth="64"
|
||||||
|
ItemHeight="32"
|
||||||
|
PanelType="Minute"
|
||||||
|
ShouldLoop="True" />
|
||||||
|
</ScrollViewer>
|
||||||
|
<ScrollViewer
|
||||||
|
Grid.Column="3"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Hidden">
|
||||||
|
<DateTimePickerPanel
|
||||||
|
Name="{x:Static u:TimePickerPresenter.PART_AmPmSelector}"
|
||||||
|
MinWidth="64"
|
||||||
|
ItemHeight="32"
|
||||||
|
PanelType="TimePeriod" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
</ControlTheme>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
<ResourceInclude Source="SelectionList.axaml" />
|
<ResourceInclude Source="SelectionList.axaml" />
|
||||||
<ResourceInclude Source="TagInput.axaml" />
|
<ResourceInclude Source="TagInput.axaml" />
|
||||||
<ResourceInclude Source="ThemeSelector.axaml" />
|
<ResourceInclude Source="ThemeSelector.axaml" />
|
||||||
|
<ResourceInclude Source="TimePicker.axaml" />
|
||||||
<ResourceInclude Source="Timeline.axaml" />
|
<ResourceInclude Source="Timeline.axaml" />
|
||||||
<ResourceInclude Source="TreeComboBox.axaml"/>
|
<ResourceInclude Source="TreeComboBox.axaml"/>
|
||||||
<ResourceInclude Source="Skeleton.axaml" />
|
<ResourceInclude Source="Skeleton.axaml" />
|
||||||
|
|||||||
@@ -4,14 +4,33 @@ using Avalonia.Controls.Primitives;
|
|||||||
using Avalonia.Data;
|
using Avalonia.Data;
|
||||||
using Irihi.Avalonia.Shared.Contracts;
|
using Irihi.Avalonia.Shared.Contracts;
|
||||||
|
|
||||||
namespace Ursa.Controls.TimePicker;
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
public class TimePicker : TemplatedControl, IClearControl
|
public class TimePicker : TemplatedControl, IClearControl
|
||||||
{
|
{
|
||||||
|
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<bool> IsLoopingProperty = AvaloniaProperty.Register<TimePicker, bool>(
|
||||||
|
nameof(IsLooping));
|
||||||
|
|
||||||
private TimeSpan? _selectedTimeHolder;
|
private TimeSpan? _selectedTimeHolder;
|
||||||
|
|
||||||
public static readonly StyledProperty<string> DisplayFormatProperty = AvaloniaProperty.Register<TimePicker, string>(
|
static TimePicker()
|
||||||
nameof(DisplayFormat), defaultValue: "HH:mm:ss");
|
{
|
||||||
|
PanelFormatProperty.Changed.AddClassHandler<TimePicker, string>((picker, args) =>
|
||||||
|
picker.OnPanelFormatChanged(args));
|
||||||
|
}
|
||||||
|
|
||||||
public string DisplayFormat
|
public string DisplayFormat
|
||||||
{
|
{
|
||||||
@@ -19,92 +38,65 @@ public class TimePicker: TemplatedControl, IClearControl
|
|||||||
set => SetValue(DisplayFormatProperty, value);
|
set => SetValue(DisplayFormatProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<string> PanelFormatProperty = AvaloniaProperty.Register<TimePicker, string>(
|
|
||||||
nameof(PanelFormat), defaultValue: "HH mm ss");
|
|
||||||
|
|
||||||
public string PanelFormat
|
public string PanelFormat
|
||||||
{
|
{
|
||||||
get => GetValue(PanelFormatProperty);
|
get => GetValue(PanelFormatProperty);
|
||||||
set => SetValue(PanelFormatProperty, value);
|
set => SetValue(PanelFormatProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<TimeSpan?> SelectedTimeProperty = AvaloniaProperty.Register<TimePicker, TimeSpan?>(
|
|
||||||
nameof(SelectedTime));
|
|
||||||
|
|
||||||
public TimeSpan? SelectedTime
|
public TimeSpan? SelectedTime
|
||||||
{
|
{
|
||||||
get => GetValue(SelectedTimeProperty);
|
get => GetValue(SelectedTimeProperty);
|
||||||
set => SetValue(SelectedTimeProperty, value);
|
set => SetValue(SelectedTimeProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> NeedConfirmationProperty = AvaloniaProperty.Register<TimePicker, bool>(
|
|
||||||
nameof(NeedConfirmation));
|
|
||||||
|
|
||||||
public bool NeedConfirmation
|
public bool NeedConfirmation
|
||||||
{
|
{
|
||||||
get => GetValue(NeedConfirmationProperty);
|
get => GetValue(NeedConfirmationProperty);
|
||||||
set => SetValue(NeedConfirmationProperty, value);
|
set => SetValue(NeedConfirmationProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> IsLoopingProperty = AvaloniaProperty.Register<TimePicker, bool>(
|
|
||||||
nameof(IsLooping));
|
|
||||||
|
|
||||||
public bool IsLooping
|
public bool IsLooping
|
||||||
{
|
{
|
||||||
get => GetValue(IsLoopingProperty);
|
get => GetValue(IsLoopingProperty);
|
||||||
set => SetValue(IsLoopingProperty, value);
|
set => SetValue(IsLoopingProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TimePicker()
|
public void Clear()
|
||||||
{
|
{
|
||||||
PanelFormatProperty.Changed.AddClassHandler<TimePicker, string>((picker, args)=> picker.OnPanelFormatChanged(args));
|
SetCurrentValue(SelectedTimeProperty, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPanelFormatChanged(AvaloniaPropertyChangedEventArgs<string> args)
|
private void OnPanelFormatChanged(AvaloniaPropertyChangedEventArgs<string> args)
|
||||||
{
|
{
|
||||||
var format = args.NewValue.Value;
|
var format = args.NewValue.Value;
|
||||||
string[] parts = format.Split(new char[] { ' ', '-', ':' });
|
var parts = format.Split(' ', '-', ':');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnApplyTemplate(e);
|
base.OnApplyTemplate(e);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSelectionChanged()
|
private void OnSelectionChanged()
|
||||||
{
|
{
|
||||||
if (NeedConfirmation)
|
if (NeedConfirmation)
|
||||||
{
|
|
||||||
_selectedTimeHolder = new TimeSpan();
|
_selectedTimeHolder = new TimeSpan();
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
SelectedTime = new TimeSpan();
|
SelectedTime = new TimeSpan();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
SetCurrentValue(SelectedTimeProperty, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Confirm()
|
public void Confirm()
|
||||||
{
|
{
|
||||||
if (NeedConfirmation)
|
if (NeedConfirmation)
|
||||||
{
|
|
||||||
// TODO: close popup.
|
// TODO: close popup.
|
||||||
SetCurrentValue(SelectedTimeProperty, _selectedTimeHolder);
|
SetCurrentValue(SelectedTimeProperty, _selectedTimeHolder);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
|
protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
|
||||||
{
|
{
|
||||||
base.UpdateDataValidation(property, state, error);
|
base.UpdateDataValidation(property, state, error);
|
||||||
if (property == SelectedTimeProperty)
|
if (property == SelectedTimeProperty) DataValidationErrors.SetError(this, error);
|
||||||
{
|
|
||||||
DataValidationErrors.SetError(this, error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
129
src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs
Normal file
129
src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Metadata;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
|
||||||
|
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))]
|
||||||
|
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";
|
||||||
|
|
||||||
|
private DateTimePickerPanel? _hourSelector;
|
||||||
|
private DateTimePickerPanel? _minuteSelector;
|
||||||
|
private DateTimePickerPanel? _secondSelector;
|
||||||
|
private DateTimePickerPanel? _ampmSelector;
|
||||||
|
private Grid? _pickerContainer;
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> NeedsConfirmationProperty = AvaloniaProperty.Register<TimePickerPresenter, bool>(
|
||||||
|
nameof(NeedsConfirmation));
|
||||||
|
|
||||||
|
public bool NeedsConfirmation
|
||||||
|
{
|
||||||
|
get => GetValue(NeedsConfirmationProperty);
|
||||||
|
set => SetValue(NeedsConfirmationProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<int> MinuteIncrementProperty = AvaloniaProperty.Register<TimePickerPresenter, int>(
|
||||||
|
nameof(MinuteIncrement));
|
||||||
|
|
||||||
|
public int MinuteIncrement
|
||||||
|
{
|
||||||
|
get => GetValue(MinuteIncrementProperty);
|
||||||
|
set => SetValue(MinuteIncrementProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<TimeSpan?> TimeProperty = AvaloniaProperty.Register<TimePickerPresenter, TimeSpan?>(
|
||||||
|
nameof(Time));
|
||||||
|
|
||||||
|
public TimeSpan? Time
|
||||||
|
{
|
||||||
|
get => GetValue(TimeProperty);
|
||||||
|
set => SetValue(TimeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> Use12HoursProperty = AvaloniaProperty.Register<TimePickerPresenter, bool>(
|
||||||
|
nameof(Use12Hours));
|
||||||
|
|
||||||
|
public bool Use12Hours
|
||||||
|
{
|
||||||
|
get => GetValue(Use12HoursProperty);
|
||||||
|
set => SetValue(Use12HoursProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string> PanelFormatProperty = AvaloniaProperty.Register<TimePickerPresenter, string>(
|
||||||
|
nameof(PanelFormat));
|
||||||
|
|
||||||
|
public string PanelFormat
|
||||||
|
{
|
||||||
|
get => GetValue(PanelFormatProperty);
|
||||||
|
set => SetValue(PanelFormatProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimePickerPresenter()
|
||||||
|
{
|
||||||
|
SetCurrentValue(TimeProperty, DateTime.Now.TimeOfDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
if (_pickerContainer is null) return;
|
||||||
|
var use12Clock = Use12Hours;
|
||||||
|
if (_hourSelector is not null)
|
||||||
|
{
|
||||||
|
_hourSelector.MaximumValue = use12Clock ? 12 : 23;
|
||||||
|
_hourSelector.MinimumValue = use12Clock ? 1 : 0;
|
||||||
|
_hourSelector.ItemFormat = "%h";
|
||||||
|
var hour = Time?.Hours;
|
||||||
|
_hourSelector.SelectedValue = hour ?? 0;
|
||||||
|
}
|
||||||
|
if(_minuteSelector is not null)
|
||||||
|
{
|
||||||
|
_minuteSelector.MaximumValue = 59;
|
||||||
|
_minuteSelector.MinimumValue = 0;
|
||||||
|
_minuteSelector.ItemFormat = "mm";
|
||||||
|
var minute = Time?.Minutes;
|
||||||
|
_minuteSelector.SelectedValue = minute ?? 0;
|
||||||
|
}
|
||||||
|
if(_secondSelector is not null)
|
||||||
|
{
|
||||||
|
_secondSelector.MaximumValue = 59;
|
||||||
|
_secondSelector.MinimumValue = 0;
|
||||||
|
_secondSelector.ItemFormat = "mm";
|
||||||
|
var second = Time?.Seconds;
|
||||||
|
_secondSelector.SelectedValue = second ?? 0;
|
||||||
|
}
|
||||||
|
if(_ampmSelector is not null)
|
||||||
|
{
|
||||||
|
_ampmSelector.MaximumValue = 1;
|
||||||
|
_ampmSelector.MinimumValue = 0;
|
||||||
|
_ampmSelector.ItemFormat = "%t";
|
||||||
|
var ampm = Time?.Hours switch
|
||||||
|
{
|
||||||
|
>= 12 => 1,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
_ampmSelector.SelectedValue = ampm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,59 +9,75 @@ using Avalonia.VisualTree;
|
|||||||
|
|
||||||
namespace Ursa.Controls.Panels;
|
namespace Ursa.Controls.Panels;
|
||||||
|
|
||||||
public enum TimePickerPanelType
|
public enum UrsaDateTimeScrollPanelType
|
||||||
{
|
{
|
||||||
|
Year,
|
||||||
|
Month,
|
||||||
|
Day,
|
||||||
Hour,
|
Hour,
|
||||||
Minute,
|
Minute,
|
||||||
Second,
|
Second,
|
||||||
TimePeriod // AM/PM
|
TimePeriod // AM/PM
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The panel to display items for time selection
|
/// The panel to display items for time selection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UrsaTimePickerPanel: Panel, ILogicalScrollable
|
public class UrsaDateTimeScrollPanel : Panel, ILogicalScrollable
|
||||||
{
|
{
|
||||||
private ScrollContentPresenter? _parentScroller;
|
|
||||||
private double _extendOne;
|
|
||||||
private Vector _offset;
|
|
||||||
private bool _initialized;
|
|
||||||
private int _countItemAboveBelowSelected;
|
|
||||||
|
|
||||||
public Vector Offset
|
|
||||||
{
|
|
||||||
get => _offset;
|
|
||||||
set => SetOffset(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int _increment;
|
|
||||||
|
|
||||||
public int Increment
|
|
||||||
{
|
|
||||||
get => _increment;
|
|
||||||
set => _increment = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int _selectedIndex;
|
|
||||||
public int SelectedIndex
|
|
||||||
{
|
|
||||||
get => _selectedIndex;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_selectedIndex = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int _selectedValue;
|
|
||||||
|
|
||||||
public int SelectedValue
|
|
||||||
{
|
|
||||||
get => _selectedValue;
|
|
||||||
set => _selectedValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<double> ItemHeightProperty =
|
public static readonly StyledProperty<double> ItemHeightProperty =
|
||||||
AvaloniaProperty.Register<UrsaTimePickerPanel, double>(
|
AvaloniaProperty.Register<UrsaDateTimeScrollPanel, double>(
|
||||||
nameof(ItemHeight), defaultValue: 32);
|
nameof(ItemHeight), 32);
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> ShouldLoopProperty =
|
||||||
|
AvaloniaProperty.Register<UrsaDateTimeScrollPanel, bool>(
|
||||||
|
nameof(ShouldLoop));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<UrsaDateTimeScrollPanelType> PanelTypeProperty = AvaloniaProperty.Register<UrsaDateTimeScrollPanel, UrsaDateTimeScrollPanelType>(
|
||||||
|
nameof(PanelType));
|
||||||
|
|
||||||
|
public UrsaDateTimeScrollPanelType PanelType
|
||||||
|
{
|
||||||
|
get => GetValue(PanelTypeProperty);
|
||||||
|
set => SetValue(PanelTypeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string> ItemFormatProperty = AvaloniaProperty.Register<UrsaDateTimeScrollPanel, string>(
|
||||||
|
nameof(ItemFormat));
|
||||||
|
|
||||||
|
public string ItemFormat
|
||||||
|
{
|
||||||
|
get => GetValue(ItemFormatProperty);
|
||||||
|
set => SetValue(ItemFormatProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int _countItemAboveBelowSelected;
|
||||||
|
private double _extendOne;
|
||||||
|
|
||||||
|
private bool _initialized;
|
||||||
|
private int _maximumValue;
|
||||||
|
private int _minimumValue;
|
||||||
|
private Vector _offset;
|
||||||
|
private ScrollContentPresenter? _parentScroller;
|
||||||
|
|
||||||
|
private int _range;
|
||||||
|
|
||||||
|
private int _totalItems;
|
||||||
|
|
||||||
|
static UrsaDateTimeScrollPanel()
|
||||||
|
{
|
||||||
|
ItemHeightProperty.Changed.AddClassHandler<UrsaDateTimeScrollPanel, double>((panel, args) =>
|
||||||
|
panel.OnItemHeightChanged(args));
|
||||||
|
AffectsArrange<UrsaDateTimeScrollPanel>(ItemHeightProperty);
|
||||||
|
AffectsMeasure<UrsaDateTimeScrollPanel>(ItemHeightProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Increment { get; set; }
|
||||||
|
|
||||||
|
public int SelectedIndex { get; set; }
|
||||||
|
|
||||||
|
public int SelectedValue { get; set; }
|
||||||
|
|
||||||
public double ItemHeight
|
public double ItemHeight
|
||||||
{
|
{
|
||||||
@@ -69,29 +85,61 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable
|
|||||||
set => SetValue(ItemHeightProperty, value);
|
set => SetValue(ItemHeightProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> ShouldLoopProperty = AvaloniaProperty.Register<UrsaTimePickerPanel, bool>(
|
|
||||||
nameof(ShouldLoop));
|
|
||||||
|
|
||||||
public bool ShouldLoop
|
public bool ShouldLoop
|
||||||
{
|
{
|
||||||
get => GetValue(ShouldLoopProperty);
|
get => GetValue(ShouldLoopProperty);
|
||||||
set => SetValue(ShouldLoopProperty, value);
|
set => SetValue(ShouldLoopProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static UrsaTimePickerPanel()
|
public Vector Offset
|
||||||
{
|
{
|
||||||
ItemHeightProperty.Changed.AddClassHandler<UrsaTimePickerPanel, double>((panel, args) => panel.OnItemHeightChanged(args));
|
get => _offset;
|
||||||
AffectsArrange<UrsaTimePickerPanel>(ItemHeightProperty);
|
set => SetOffset(value);
|
||||||
AffectsMeasure<UrsaTimePickerPanel>(ItemHeightProperty);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Size _scrollSize;
|
public Size Extent { get; private set; }
|
||||||
private Size _pageScrollSize;
|
|
||||||
|
public Size Viewport => Bounds.Size;
|
||||||
|
|
||||||
|
public bool BringIntoView(Control target, Rect targetRect)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Control? GetControlInDirection(NavigationDirection direction, Control? from)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RaiseScrollInvalidated(System.EventArgs e)
|
||||||
|
{
|
||||||
|
ScrollInvalidated?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanHorizontallyScroll
|
||||||
|
{
|
||||||
|
get => false;
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanVerticallyScroll
|
||||||
|
{
|
||||||
|
get => false;
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLogicalScrollEnabled => true;
|
||||||
|
public Size ScrollSize { get; private set; }
|
||||||
|
|
||||||
|
public Size PageScrollSize { get; private set; }
|
||||||
|
|
||||||
|
public event EventHandler? ScrollInvalidated;
|
||||||
|
|
||||||
private void OnItemHeightChanged(AvaloniaPropertyChangedEventArgs<double> args)
|
private void OnItemHeightChanged(AvaloniaPropertyChangedEventArgs<double> args)
|
||||||
{
|
{
|
||||||
var newValue = args.NewValue.Value;
|
var newValue = args.NewValue.Value;
|
||||||
_scrollSize = new Size(0, newValue);
|
ScrollSize = new Size(0, newValue);
|
||||||
_pageScrollSize = new Size(0, newValue * 3);
|
PageScrollSize = new Size(0, newValue * 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
@@ -127,10 +175,7 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable
|
|||||||
private ListBoxItem? GetItemFromSource(Visual source)
|
private ListBoxItem? GetItemFromSource(Visual source)
|
||||||
{
|
{
|
||||||
var item = source;
|
var item = source;
|
||||||
while (item != null && !(item is ListBoxItem))
|
while (item != null && !(item is ListBoxItem)) item = item.GetVisualParent();
|
||||||
{
|
|
||||||
item = item.GetVisualParent();
|
|
||||||
}
|
|
||||||
return item as ListBoxItem;
|
return item as ListBoxItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,17 +184,14 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable
|
|||||||
if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
|
if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
|
||||||
throw new InvalidOperationException("Panel must have finite height");
|
throw new InvalidOperationException("Panel must have finite height");
|
||||||
if (_initialized) UpdateHelperInfo();
|
if (_initialized) UpdateHelperInfo();
|
||||||
double initY = availableSize.Height / 2.0 - ItemHeight / 2.0;
|
var initY = availableSize.Height / 2.0 - ItemHeight / 2.0;
|
||||||
_countItemAboveBelowSelected = (int)Math.Ceiling(initY / ItemHeight);
|
_countItemAboveBelowSelected = (int)Math.Ceiling(initY / ItemHeight);
|
||||||
|
|
||||||
var children = Children;
|
var children = Children;
|
||||||
|
|
||||||
CreateOrDestroyItems(children);
|
CreateOrDestroyItems(children);
|
||||||
|
|
||||||
for(int i = 0; i< children.Count; i++)
|
for (var i = 0; i < children.Count; i++) children[i].Measure(availableSize);
|
||||||
{
|
|
||||||
children[i].Measure(availableSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_initialized)
|
if (!_initialized)
|
||||||
{
|
{
|
||||||
@@ -163,20 +205,17 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable
|
|||||||
|
|
||||||
protected override Size ArrangeOverride(Size finalSize)
|
protected override Size ArrangeOverride(Size finalSize)
|
||||||
{
|
{
|
||||||
if (Children.Count == 0)
|
if (Children.Count == 0) return base.ArrangeOverride(finalSize);
|
||||||
{
|
|
||||||
return base.ArrangeOverride(finalSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
var itemHeight = ItemHeight;
|
var itemHeight = ItemHeight;
|
||||||
var children = Children;
|
var children = Children;
|
||||||
Rect rc;
|
Rect rc;
|
||||||
double initY = finalSize.Height / 2.0 - itemHeight / 2.0;
|
var initY = finalSize.Height / 2.0 - itemHeight / 2.0;
|
||||||
|
|
||||||
if (ShouldLoop)
|
if (ShouldLoop)
|
||||||
{
|
{
|
||||||
var currentSet = Math.Truncate(Offset.Y / _extendOne);
|
var currentSet = Math.Truncate(Offset.Y / _extendOne);
|
||||||
initY += _extendOne * currentSet + (_selectedIndex - _countItemAboveBelowSelected) * ItemHeight;
|
initY += _extendOne * currentSet + (SelectedIndex - _countItemAboveBelowSelected) * ItemHeight;
|
||||||
foreach (var child in children)
|
foreach (var child in children)
|
||||||
{
|
{
|
||||||
rc = new Rect(0, initY - Offset.Y, finalSize.Width, itemHeight);
|
rc = new Rect(0, initY - Offset.Y, finalSize.Width, itemHeight);
|
||||||
@@ -186,7 +225,7 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var first = Math.Max(0, _selectedIndex - _countItemAboveBelowSelected);
|
var first = Math.Max(0, SelectedIndex - _countItemAboveBelowSelected);
|
||||||
foreach (var child in children)
|
foreach (var child in children)
|
||||||
{
|
{
|
||||||
rc = new Rect(0, initY + first + itemHeight - Offset.Y, finalSize.Width, itemHeight);
|
rc = new Rect(0, initY + first + itemHeight - Offset.Y, finalSize.Width, itemHeight);
|
||||||
@@ -194,16 +233,14 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable
|
|||||||
initY += itemHeight;
|
initY += itemHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return finalSize;
|
return finalSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e)
|
private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e)
|
||||||
{
|
{
|
||||||
var snapY = Math.Round(Offset.Y / ItemHeight) * ItemHeight;
|
var snapY = Math.Round(Offset.Y / ItemHeight) * ItemHeight;
|
||||||
if(!snapY.Equals(Offset.Y))
|
if (!snapY.Equals(Offset.Y)) Offset = Offset.WithY(snapY);
|
||||||
{
|
|
||||||
Offset = Offset.WithY(snapY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetOffset(Vector value)
|
private void SetOffset(Vector value)
|
||||||
@@ -211,24 +248,20 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable
|
|||||||
var oldValue = _offset;
|
var oldValue = _offset;
|
||||||
_offset = value;
|
_offset = value;
|
||||||
var dy = _offset.Y - oldValue.Y;
|
var dy = _offset.Y - oldValue.Y;
|
||||||
var children = this.Children;
|
var children = Children;
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
private int _range;
|
|
||||||
private int _maximumValue;
|
|
||||||
private int _minimumValue;
|
|
||||||
private int _totalItems;
|
|
||||||
private void UpdateHelperInfo()
|
private void UpdateHelperInfo()
|
||||||
{
|
{
|
||||||
_range = _maximumValue - _minimumValue + 1;
|
_range = _maximumValue - _minimumValue + 1;
|
||||||
_totalItems = (int)Math.Ceiling((double)_range / _increment);
|
_totalItems = (int)Math.Ceiling((double)_range / Increment);
|
||||||
var itemHeight = ItemHeight;
|
var itemHeight = ItemHeight;
|
||||||
_extent = new Size(0, ShouldLoop ? _totalItems * itemHeight * 100 : _totalItems * itemHeight);
|
Extent = new Size(0, ShouldLoop ? _totalItems * itemHeight * 100 : _totalItems * itemHeight);
|
||||||
|
|
||||||
_extendOne = _totalItems * itemHeight;
|
_extendOne = _totalItems * itemHeight;
|
||||||
_offset = new Vector(0,
|
_offset = new Vector(0,
|
||||||
ShouldLoop ? _extendOne * 50 + _selectedIndex * itemHeight : _selectedIndex * itemHeight);
|
ShouldLoop ? _extendOne * 50 + SelectedIndex * itemHeight : SelectedIndex * itemHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateItems()
|
private void UpdateItems()
|
||||||
@@ -241,56 +274,45 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable
|
|||||||
int first;
|
int first;
|
||||||
if (ShouldLoop)
|
if (ShouldLoop)
|
||||||
{
|
{
|
||||||
first = (_selectedIndex - _countItemAboveBelowSelected) % _totalItems;
|
first = (SelectedIndex - _countItemAboveBelowSelected) % _totalItems;
|
||||||
first = first < 0 ? min + (first + _totalItems) * Increment : min + first * Increment;
|
first = first < 0 ? min + (first + _totalItems) * Increment : min + first * Increment;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
first = min + Math.Max(0, _selectedIndex - _countItemAboveBelowSelected) * Increment;
|
first = min + Math.Max(0, SelectedIndex - _countItemAboveBelowSelected) * Increment;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < children.Count; i++)
|
for (var i = 0; i < children.Count; i++)
|
||||||
{
|
{
|
||||||
ListBoxItem item = (ListBoxItem)children[i];
|
var item = (ListBoxItem)children[i];
|
||||||
item.Content = first + i * Increment; // TODO
|
item.Content = first + i * Increment; // TODO
|
||||||
item.Tag = first;
|
item.Tag = first;
|
||||||
item.IsSelected = first == selected;
|
item.IsSelected = first == selected;
|
||||||
first += Increment;
|
first += Increment;
|
||||||
if(first > max)
|
if (first > max) first = min;
|
||||||
{
|
|
||||||
first = min;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateOrDestroyItems(Avalonia.Controls.Controls children)
|
private void CreateOrDestroyItems(Avalonia.Controls.Controls children)
|
||||||
{
|
{
|
||||||
int totalItemsInViewport = _countItemAboveBelowSelected * 2 + 1;
|
var totalItemsInViewport = _countItemAboveBelowSelected * 2 + 1;
|
||||||
if (!ShouldLoop)
|
if (!ShouldLoop)
|
||||||
{
|
{
|
||||||
int numItemAboveSelect = _countItemAboveBelowSelected;
|
var numItemAboveSelect = _countItemAboveBelowSelected;
|
||||||
if (_selectedIndex - _countItemAboveBelowSelected < 0)
|
if (SelectedIndex - _countItemAboveBelowSelected < 0) numItemAboveSelect = SelectedIndex;
|
||||||
{
|
var numItemBelowSelect = _countItemAboveBelowSelected;
|
||||||
numItemAboveSelect = _selectedIndex;
|
if (SelectedIndex + _countItemAboveBelowSelected >= _totalItems)
|
||||||
}
|
numItemBelowSelect = _totalItems - SelectedIndex - 1;
|
||||||
int numItemBelowSelect = _countItemAboveBelowSelected;
|
|
||||||
if (_selectedIndex + _countItemAboveBelowSelected >= _totalItems)
|
|
||||||
{
|
|
||||||
numItemBelowSelect = _totalItems - _selectedIndex - 1;
|
|
||||||
}
|
|
||||||
totalItemsInViewport = numItemAboveSelect + numItemBelowSelect + 1;
|
totalItemsInViewport = numItemAboveSelect + numItemBelowSelect + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (children.Count < totalItemsInViewport)
|
while (children.Count < totalItemsInViewport)
|
||||||
{
|
|
||||||
children.Add(new ListBoxItem
|
children.Add(new ListBoxItem
|
||||||
{
|
{
|
||||||
Height = ItemHeight,
|
Height = ItemHeight,
|
||||||
VerticalContentAlignment = VerticalAlignment.Center,
|
VerticalContentAlignment = VerticalAlignment.Center,
|
||||||
Focusable = false,
|
Focusable = false
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (children.Count > totalItemsInViewport)
|
if (children.Count > totalItemsInViewport)
|
||||||
{
|
{
|
||||||
@@ -301,34 +323,14 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable
|
|||||||
|
|
||||||
private int CoerceSelected(int newValue)
|
private int CoerceSelected(int newValue)
|
||||||
{
|
{
|
||||||
if(newValue < _minimumValue)
|
if (newValue < _minimumValue) return _minimumValue;
|
||||||
{
|
if (newValue > _maximumValue) return _maximumValue;
|
||||||
return _minimumValue;
|
if (newValue % Increment == 0) return newValue;
|
||||||
}
|
var items = Enumerable.Range(_minimumValue, _range).Where(x => x % Increment == 0).ToList();
|
||||||
if(newValue > _maximumValue)
|
|
||||||
{
|
|
||||||
return _maximumValue;
|
|
||||||
}
|
|
||||||
if (newValue % _increment == 0) return newValue;
|
|
||||||
var items = Enumerable.Range(_minimumValue, _range).Where(x => x % _increment == 0).ToList();
|
|
||||||
var nearest = items.Aggregate((x, y) => Math.Abs(x - newValue) > Math.Abs(y - newValue) ? y : x);
|
var nearest = items.Aggregate((x, y) => Math.Abs(x - newValue) > Math.Abs(y - newValue) ? y : x);
|
||||||
return items.IndexOf(nearest) * Increment;
|
return items.IndexOf(nearest) * Increment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler? OnSelectionChanged;
|
public event EventHandler? OnSelectionChanged;
|
||||||
private Size _extent;
|
|
||||||
public Size Extent {
|
|
||||||
get => _extent;
|
|
||||||
private set => _extent = value;
|
|
||||||
}
|
|
||||||
public Size Viewport => Bounds.Size;
|
|
||||||
public bool BringIntoView(Control target, Rect targetRect) => false;
|
|
||||||
public Control? GetControlInDirection(NavigationDirection direction, Control? from) => null;
|
|
||||||
public void RaiseScrollInvalidated(System.EventArgs e) => ScrollInvalidated?.Invoke(this, e);
|
|
||||||
public bool CanHorizontallyScroll { get => false; set { } }
|
|
||||||
public bool CanVerticallyScroll { get => false; set {} }
|
|
||||||
public bool IsLogicalScrollEnabled => true;
|
|
||||||
public Size ScrollSize => _scrollSize;
|
|
||||||
public Size PageScrollSize => _pageScrollSize;
|
|
||||||
public event EventHandler? ScrollInvalidated;
|
|
||||||
public event EventHandler? SelectionChanged;
|
public event EventHandler? SelectionChanged;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user