diff --git a/src/Ursa.Themes.Semi/Controls/TimePicker.axaml b/src/Ursa.Themes.Semi/Controls/TimePicker.axaml index 3f0d93d..38dbfe2 100644 --- a/src/Ursa.Themes.Semi/Controls/TimePicker.axaml +++ b/src/Ursa.Themes.Semi/Controls/TimePicker.axaml @@ -4,7 +4,7 @@ xmlns:u="https://irihi.tech/ursa"> - + @@ -14,20 +14,19 @@ + ColumnDefinitions="*, *, *, *, Auto"> - - - - - -/// The panel to display items for time selection -/// -public class UrsaDateTimeScrollPanel : Panel, ILogicalScrollable -{ - public static readonly StyledProperty ItemHeightProperty = - 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 - { - get => GetValue(ItemHeightProperty); - set => SetValue(ItemHeightProperty, value); - } - - public bool ShouldLoop - { - get => GetValue(ShouldLoopProperty); - set => SetValue(ShouldLoopProperty, value); - } - - public Vector Offset - { - get => _offset; - set => SetOffset(value); - } - - 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); - } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - _parentScroller?.RemoveHandler(Gestures.ScrollGestureEndedEvent, OnScrollGestureEnded); - _parentScroller = this.GetVisualParent() as ScrollContentPresenter; - _parentScroller?.AddHandler(Gestures.ScrollGestureEndedEvent, OnScrollGestureEnded); - } - - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnDetachedFromVisualTree(e); - _parentScroller?.RemoveHandler(Gestures.ScrollGestureEndedEvent, OnScrollGestureEnded); - _parentScroller = null; - } - - public override void ApplyTemplate() - { - base.ApplyTemplate(); - AddHandler(TappedEvent, OnItemTapped, RoutingStrategies.Bubble); - } - - private void OnItemTapped(object sender, TappedEventArgs e) - { - if (e.Source is Visual source && GetItemFromSource(source) is { Tag: int tag }) - { - SelectedValue = tag; - e.Handled = true; - } - } - - private ListBoxItem? GetItemFromSource(Visual source) - { - var item = source; - while (item != null && !(item is ListBoxItem)) item = item.GetVisualParent(); - return item as ListBoxItem; - } - protected override Size MeasureOverride(Size availableSize) { - if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height)) - throw new InvalidOperationException("Panel must have finite height"); - if (_initialized) UpdateHelperInfo(); - var initY = availableSize.Height / 2.0 - ItemHeight / 2.0; - _countItemAboveBelowSelected = (int)Math.Ceiling(initY / ItemHeight); - - var children = Children; - - CreateOrDestroyItems(children); - - for (var i = 0; i < children.Count; i++) children[i].Measure(availableSize); - - if (!_initialized) - { - UpdateItems(); - RaiseScrollInvalidated(System.EventArgs.Empty); - _initialized = true; - } - - return 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) { - if (Children.Count == 0) return base.ArrangeOverride(finalSize); - - var itemHeight = ItemHeight; - var children = Children; - Rect rc; - var initY = finalSize.Height / 2.0 - itemHeight / 2.0; - - if (ShouldLoop) - { - var currentSet = Math.Truncate(Offset.Y / _extendOne); - initY += _extendOne * currentSet + (SelectedIndex - _countItemAboveBelowSelected) * ItemHeight; - foreach (var child in children) - { - rc = new Rect(0, initY - Offset.Y, finalSize.Width, itemHeight); - child.Arrange(rc); - initY += itemHeight; - } - } - else - { - var first = Math.Max(0, SelectedIndex - _countItemAboveBelowSelected); - foreach (var child in children) - { - rc = new Rect(0, initY + first + itemHeight - Offset.Y, finalSize.Width, itemHeight); - child.Arrange(rc); - initY += itemHeight; - } - } - - return 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); } - - private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e) - { - var snapY = Math.Round(Offset.Y / ItemHeight) * ItemHeight; - 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 = Children; - // TODO - } - - private void UpdateHelperInfo() - { - _range = _maximumValue - _minimumValue + 1; - _totalItems = (int)Math.Ceiling((double)_range / Increment); - var itemHeight = 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); - } - - private void UpdateItems() - { - var children = Children; - var min = _minimumValue; - var max = _maximumValue; - var selected = SelectedValue; - - int first; - if (ShouldLoop) - { - first = (SelectedIndex - _countItemAboveBelowSelected) % _totalItems; - first = first < 0 ? min + (first + _totalItems) * Increment : min + first * Increment; - } - else - { - first = min + Math.Max(0, SelectedIndex - _countItemAboveBelowSelected) * Increment; - } - - for (var i = 0; i < children.Count; 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; - } - } - - private void CreateOrDestroyItems(Avalonia.Controls.Controls children) - { - var totalItemsInViewport = _countItemAboveBelowSelected * 2 + 1; - if (!ShouldLoop) - { - 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) - children.Add(new ListBoxItem - { - Height = ItemHeight, - VerticalContentAlignment = VerticalAlignment.Center, - Focusable = false - }); - - if (children.Count > totalItemsInViewport) - { - var countToRemove = children.Count - totalItemsInViewport; - 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(); - 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; - public event EventHandler? SelectionChanged; } \ No newline at end of file