diff --git a/demo/Ursa.Demo/Pages/TimePickerDemo.axaml b/demo/Ursa.Demo/Pages/TimePickerDemo.axaml new file mode 100644 index 0000000..d5324f6 --- /dev/null +++ b/demo/Ursa.Demo/Pages/TimePickerDemo.axaml @@ -0,0 +1,11 @@ + + + + + diff --git a/demo/Ursa.Demo/Pages/TimePickerDemo.axaml.cs b/demo/Ursa.Demo/Pages/TimePickerDemo.axaml.cs new file mode 100644 index 0000000..330eb98 --- /dev/null +++ b/demo/Ursa.Demo/Pages/TimePickerDemo.axaml.cs @@ -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(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs index 80b9f6a..f981fa6 100644 --- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs @@ -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(), diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs index a1fcdb7..32066e6 100644 --- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs @@ -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"; diff --git a/demo/Ursa.Demo/ViewModels/TimePickerDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/TimePickerDemoViewModel.cs new file mode 100644 index 0000000..f115429 --- /dev/null +++ b/demo/Ursa.Demo/ViewModels/TimePickerDemoViewModel.cs @@ -0,0 +1,8 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Ursa.Demo.ViewModels; + +public class TimePickerDemoViewModel: ObservableObject +{ + +} \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/TimePicker.axaml b/src/Ursa.Themes.Semi/Controls/TimePicker.axaml new file mode 100644 index 0000000..3f0d93d --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/TimePicker.axaml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index 6e25af2..0ceb9e3 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -32,6 +32,7 @@ + diff --git a/src/Ursa/Controls/DateTimePicker/TimePicker.cs b/src/Ursa/Controls/DateTimePicker/TimePicker.cs index 6650a24..9cacca4 100644 --- a/src/Ursa/Controls/DateTimePicker/TimePicker.cs +++ b/src/Ursa/Controls/DateTimePicker/TimePicker.cs @@ -4,14 +4,33 @@ using Avalonia.Controls.Primitives; using Avalonia.Data; using Irihi.Avalonia.Shared.Contracts; -namespace Ursa.Controls.TimePicker; +namespace Ursa.Controls; -public class TimePicker: TemplatedControl, IClearControl +public class TimePicker : TemplatedControl, IClearControl { - private TimeSpan? _selectedTimeHolder; - public static readonly StyledProperty DisplayFormatProperty = AvaloniaProperty.Register( - nameof(DisplayFormat), defaultValue: "HH:mm:ss"); + nameof(DisplayFormat), "HH:mm:ss"); + + public static readonly StyledProperty PanelFormatProperty = AvaloniaProperty.Register( + nameof(PanelFormat), "HH mm ss"); + + public static readonly StyledProperty SelectedTimeProperty = + AvaloniaProperty.Register( + nameof(SelectedTime)); + + public static readonly StyledProperty NeedConfirmationProperty = AvaloniaProperty.Register( + nameof(NeedConfirmation)); + + public static readonly StyledProperty IsLoopingProperty = AvaloniaProperty.Register( + nameof(IsLooping)); + + private TimeSpan? _selectedTimeHolder; + + static TimePicker() + { + PanelFormatProperty.Changed.AddClassHandler((picker, args) => + picker.OnPanelFormatChanged(args)); + } public string DisplayFormat { @@ -19,26 +38,17 @@ public class TimePicker: TemplatedControl, IClearControl set => SetValue(DisplayFormatProperty, value); } - public static readonly StyledProperty PanelFormatProperty = AvaloniaProperty.Register( - nameof(PanelFormat), defaultValue: "HH mm ss"); - public string PanelFormat { get => GetValue(PanelFormatProperty); set => SetValue(PanelFormatProperty, value); } - public static readonly StyledProperty SelectedTimeProperty = AvaloniaProperty.Register( - nameof(SelectedTime)); - public TimeSpan? SelectedTime { get => GetValue(SelectedTimeProperty); set => SetValue(SelectedTimeProperty, value); } - - public static readonly StyledProperty NeedConfirmationProperty = AvaloniaProperty.Register( - nameof(NeedConfirmation)); public bool NeedConfirmation { @@ -46,65 +56,47 @@ public class TimePicker: TemplatedControl, IClearControl set => SetValue(NeedConfirmationProperty, value); } - public static readonly StyledProperty IsLoopingProperty = AvaloniaProperty.Register( - nameof(IsLooping)); - public bool IsLooping { get => GetValue(IsLoopingProperty); set => SetValue(IsLoopingProperty, value); } - static TimePicker() + public void Clear() { - PanelFormatProperty.Changed.AddClassHandler((picker, args)=> picker.OnPanelFormatChanged(args)); + SetCurrentValue(SelectedTimeProperty, null); } private void OnPanelFormatChanged(AvaloniaPropertyChangedEventArgs args) { var format = args.NewValue.Value; - string[] parts = format.Split(new char[] { ' ', '-', ':' }); + var parts = format.Split(' ', '-', ':'); } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - } private void OnSelectionChanged() { if (NeedConfirmation) - { _selectedTimeHolder = new TimeSpan(); - } else - { SelectedTime = new TimeSpan(); - } } - public void Clear() - { - SetCurrentValue(SelectedTimeProperty, null); - } - public void Confirm() { if (NeedConfirmation) - { // TODO: close popup. SetCurrentValue(SelectedTimeProperty, _selectedTimeHolder); - } } protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error) { base.UpdateDataValidation(property, state, error); - if (property == SelectedTimeProperty) - { - DataValidationErrors.SetError(this, error); - } + if (property == SelectedTimeProperty) DataValidationErrors.SetError(this, error); } } \ No newline at end of file diff --git a/src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs b/src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs new file mode 100644 index 0000000..ac27c1f --- /dev/null +++ b/src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs @@ -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 NeedsConfirmationProperty = AvaloniaProperty.Register( + nameof(NeedsConfirmation)); + + public bool NeedsConfirmation + { + get => GetValue(NeedsConfirmationProperty); + set => SetValue(NeedsConfirmationProperty, value); + } + + public static readonly StyledProperty MinuteIncrementProperty = AvaloniaProperty.Register( + nameof(MinuteIncrement)); + + public int MinuteIncrement + { + get => GetValue(MinuteIncrementProperty); + set => SetValue(MinuteIncrementProperty, value); + } + + public static readonly StyledProperty TimeProperty = AvaloniaProperty.Register( + nameof(Time)); + + public TimeSpan? Time + { + get => GetValue(TimeProperty); + set => SetValue(TimeProperty, value); + } + + public static readonly StyledProperty Use12HoursProperty = AvaloniaProperty.Register( + nameof(Use12Hours)); + + public bool Use12Hours + { + get => GetValue(Use12HoursProperty); + set => SetValue(Use12HoursProperty, value); + } + + public static readonly StyledProperty PanelFormatProperty = AvaloniaProperty.Register( + 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(PART_HourSelector); + _minuteSelector = e.NameScope.Find(PART_MinuteSelector); + _secondSelector = e.NameScope.Find(PART_SecondSelector); + _ampmSelector = e.NameScope.Find(PART_AmPmSelector); + _pickerContainer = e.NameScope.Find(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; + } + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/DateTimePicker/UrsaTimePickerPanel.cs b/src/Ursa/Controls/DateTimePicker/UrsaDateTimeScrollPanel.cs similarity index 57% rename from src/Ursa/Controls/DateTimePicker/UrsaTimePickerPanel.cs rename to src/Ursa/Controls/DateTimePicker/UrsaDateTimeScrollPanel.cs index e38d549..36a8893 100644 --- a/src/Ursa/Controls/DateTimePicker/UrsaTimePickerPanel.cs +++ b/src/Ursa/Controls/DateTimePicker/UrsaDateTimeScrollPanel.cs @@ -9,59 +9,75 @@ using Avalonia.VisualTree; namespace Ursa.Controls.Panels; -public enum TimePickerPanelType +public enum UrsaDateTimeScrollPanelType { + Year, + Month, + Day, Hour, Minute, Second, TimePeriod // AM/PM } + /// -/// The panel to display items for time selection +/// The panel to display items for time selection /// -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 ItemHeightProperty = - AvaloniaProperty.Register( - nameof(ItemHeight), defaultValue: 32); + AvaloniaProperty.Register( + nameof(ItemHeight), 32); + + public static readonly StyledProperty ShouldLoopProperty = + AvaloniaProperty.Register( + nameof(ShouldLoop)); + + public static readonly StyledProperty PanelTypeProperty = AvaloniaProperty.Register( + nameof(PanelType)); + + public UrsaDateTimeScrollPanelType PanelType + { + get => GetValue(PanelTypeProperty); + set => SetValue(PanelTypeProperty, value); + } + + public static readonly StyledProperty ItemFormatProperty = AvaloniaProperty.Register( + 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((panel, args) => + panel.OnItemHeightChanged(args)); + AffectsArrange(ItemHeightProperty); + AffectsMeasure(ItemHeightProperty); + } + + public int Increment { get; set; } + + public int SelectedIndex { get; set; } + + public int SelectedValue { get; set; } public double ItemHeight { @@ -69,29 +85,61 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable set => SetValue(ItemHeightProperty, value); } - public static readonly StyledProperty ShouldLoopProperty = AvaloniaProperty.Register( - nameof(ShouldLoop)); - public bool ShouldLoop { get => GetValue(ShouldLoopProperty); set => SetValue(ShouldLoopProperty, value); } - - static UrsaTimePickerPanel() + + public Vector Offset { - ItemHeightProperty.Changed.AddClassHandler((panel, args) => panel.OnItemHeightChanged(args)); - AffectsArrange(ItemHeightProperty); - AffectsMeasure(ItemHeightProperty); + get => _offset; + set => SetOffset(value); } - private Size _scrollSize; - private Size _pageScrollSize; + public Size Extent { get; private set; } + + 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 args) { var newValue = args.NewValue.Value; - _scrollSize = new Size(0, newValue); - _pageScrollSize = new Size(0, newValue * 3); + ScrollSize = new Size(0, newValue); + PageScrollSize = new Size(0, newValue * 3); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) @@ -127,10 +175,7 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable private ListBoxItem? GetItemFromSource(Visual source) { var item = source; - while (item != null && !(item is ListBoxItem)) - { - item = item.GetVisualParent(); - } + while (item != null && !(item is ListBoxItem)) item = item.GetVisualParent(); return item as ListBoxItem; } @@ -138,18 +183,15 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable { if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height)) throw new InvalidOperationException("Panel must have finite height"); - if(_initialized) UpdateHelperInfo(); - double initY = availableSize.Height / 2.0 - ItemHeight / 2.0; + if (_initialized) UpdateHelperInfo(); + var initY = availableSize.Height / 2.0 - ItemHeight / 2.0; _countItemAboveBelowSelected = (int)Math.Ceiling(initY / ItemHeight); - + var children = Children; - + CreateOrDestroyItems(children); - - for(int i = 0; i< children.Count; i++) - { - children[i].Measure(availableSize); - } + + for (var i = 0; i < children.Count; i++) children[i].Measure(availableSize); if (!_initialized) { @@ -163,72 +205,63 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable protected override Size ArrangeOverride(Size finalSize) { - if (Children.Count == 0) - { - return base.ArrangeOverride(finalSize); - } + if (Children.Count == 0) return base.ArrangeOverride(finalSize); var itemHeight = ItemHeight; var children = Children; Rect rc; - double initY = finalSize.Height / 2.0 - itemHeight / 2.0; + var initY = finalSize.Height / 2.0 - itemHeight / 2.0; if (ShouldLoop) { var currentSet = Math.Truncate(Offset.Y / _extendOne); - initY += _extendOne * currentSet + (_selectedIndex - _countItemAboveBelowSelected) * ItemHeight; + initY += _extendOne * currentSet + (SelectedIndex - _countItemAboveBelowSelected) * ItemHeight; 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); child.Arrange(rc); initY += itemHeight; } } else { - var first = Math.Max(0, _selectedIndex - _countItemAboveBelowSelected); + var first = Math.Max(0, SelectedIndex - _countItemAboveBelowSelected); 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); child.Arrange(rc); initY += itemHeight; } } + return finalSize; } private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e) { var snapY = Math.Round(Offset.Y / ItemHeight) * ItemHeight; - if(!snapY.Equals(Offset.Y)) - { - Offset = Offset.WithY(snapY); - } + if (!snapY.Equals(Offset.Y)) Offset = Offset.WithY(snapY); } - + private void SetOffset(Vector value) { var oldValue = _offset; _offset = value; var dy = _offset.Y - oldValue.Y; - var children = this.Children; + var children = Children; // TODO } - private int _range; - private int _maximumValue; - private int _minimumValue; - private int _totalItems; private void UpdateHelperInfo() { _range = _maximumValue - _minimumValue + 1; - _totalItems = (int)Math.Ceiling((double)_range / _increment); + _totalItems = (int)Math.Ceiling((double)_range / Increment); 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; _offset = new Vector(0, - ShouldLoop ? _extendOne * 50 + _selectedIndex * itemHeight : _selectedIndex * itemHeight); + ShouldLoop ? _extendOne * 50 + SelectedIndex * itemHeight : SelectedIndex * itemHeight); } private void UpdateItems() @@ -241,94 +274,63 @@ public class UrsaTimePickerPanel: Panel, ILogicalScrollable int first; if (ShouldLoop) { - first = (_selectedIndex - _countItemAboveBelowSelected) % _totalItems; + first = (SelectedIndex - _countItemAboveBelowSelected) % _totalItems; first = first < 0 ? min + (first + _totalItems) * Increment : min + first * Increment; } 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.Tag = first; item.IsSelected = first == selected; first += Increment; - if(first > max) - { - first = min; - } + if (first > max) first = min; } - } private void CreateOrDestroyItems(Avalonia.Controls.Controls children) { - int totalItemsInViewport = _countItemAboveBelowSelected * 2 + 1; + var totalItemsInViewport = _countItemAboveBelowSelected * 2 + 1; if (!ShouldLoop) { - int numItemAboveSelect = _countItemAboveBelowSelected; - if (_selectedIndex - _countItemAboveBelowSelected < 0) - { - numItemAboveSelect = _selectedIndex; - } - int numItemBelowSelect = _countItemAboveBelowSelected; - if (_selectedIndex + _countItemAboveBelowSelected >= _totalItems) - { - numItemBelowSelect = _totalItems - _selectedIndex - 1; - } + var numItemAboveSelect = _countItemAboveBelowSelected; + if (SelectedIndex - _countItemAboveBelowSelected < 0) numItemAboveSelect = SelectedIndex; + var numItemBelowSelect = _countItemAboveBelowSelected; + if (SelectedIndex + _countItemAboveBelowSelected >= _totalItems) + numItemBelowSelect = _totalItems - SelectedIndex - 1; totalItemsInViewport = numItemAboveSelect + numItemBelowSelect + 1; } - while (children.Count totalItemsInViewport) { var countToRemove = children.Count - totalItemsInViewport; - children.RemoveRange(children.Count-countToRemove, countToRemove); + children.RemoveRange(children.Count - countToRemove, countToRemove); } } private int CoerceSelected(int newValue) { - if(newValue < _minimumValue) - { - return _minimumValue; - } - if(newValue > _maximumValue) - { - return _maximumValue; - } - if (newValue % _increment == 0) return newValue; - var items = Enumerable.Range(_minimumValue, _range).Where(x => x % _increment == 0).ToList(); + if (newValue < _minimumValue) return _minimumValue; + 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); return items.IndexOf(nearest) * Increment; } + 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; } \ No newline at end of file