From 87b971654a351600c7c95d4684d8c8a40bdb73c8 Mon Sep 17 00:00:00 2001 From: SignorParabellaux <211683353+SignorParabellaux@users.noreply.github.com> Date: Sat, 31 May 2025 05:35:43 +0300 Subject: [PATCH] refactor: NavMenu clarity and method order, NavMenuItem dedupe pointer, simplify and method order --- src/Ursa/Controls/NavMenu/NavMenu.cs | 96 ++++++------ src/Ursa/Controls/NavMenu/NavMenuItem.cs | 178 +++++++++++------------ 2 files changed, 132 insertions(+), 142 deletions(-) diff --git a/src/Ursa/Controls/NavMenu/NavMenu.cs b/src/Ursa/Controls/NavMenu/NavMenu.cs index 893a22e..b816485 100644 --- a/src/Ursa/Controls/NavMenu/NavMenu.cs +++ b/src/Ursa/Controls/NavMenu/NavMenu.cs @@ -69,7 +69,7 @@ public class NavMenu : ItemsControl public static readonly RoutedEvent SelectionChangedEvent = RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble); - private bool _updateFromUI; + private bool _isSelectionFromUI = false; static NavMenu() { @@ -183,21 +183,14 @@ public class NavMenu : ItemsControl remove => RemoveHandler(SelectionChangedEvent, value); } - private static void OnInputRegisteredAsToggle(InputElement input, AvaloniaPropertyChangedEventArgs e) + protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) { - if (e.NewValue.Value) - input.AddHandler(PointerPressedEvent, OnElementToggle); - else - input.RemoveHandler(PointerPressedEvent, OnElementToggle); + return NeedsContainer(item, out recycleKey); } - private static void OnElementToggle(object? sender, RoutedEventArgs args) + protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) { - if (sender is not InputElement input) return; - var nav = input.FindLogicalAncestorOfType(); - if (nav is null) return; - var collapsed = nav.IsHorizontalCollapsed; - nav.IsHorizontalCollapsed = !collapsed; + return new NavMenuItem(); } protected override void OnLoaded(RoutedEventArgs e) @@ -217,7 +210,7 @@ public class NavMenu : ItemsControl SelectionChangedEvent, new[] { args.OldValue.Value }, new[] { args.NewValue.Value }); - if (_updateFromUI) + if (_isSelectionFromUI) { RaiseEvent(a); return; @@ -234,7 +227,48 @@ public class NavMenu : ItemsControl if (!found) ClearAll(); RaiseEvent(a); } - + + private static void OnInputRegisteredAsToggle(InputElement input, AvaloniaPropertyChangedEventArgs e) + { + if (e.NewValue.Value) + input.AddHandler(PointerPressedEvent, OnElementToggle); + else + input.RemoveHandler(PointerPressedEvent, OnElementToggle); + } + + private static void OnElementToggle(object? sender, RoutedEventArgs args) + { + if (sender is not InputElement input) return; + var nav = input.FindLogicalAncestorOfType(); + if (nav is null) return; + var collapsed = nav.IsHorizontalCollapsed; + nav.IsHorizontalCollapsed = !collapsed; + } + + internal void SelectItem(NavMenuItem item, NavMenuItem parent) + { + _isSelectionFromUI = true; + foreach (var child in LogicalChildren) + { + if (Equals(child, parent)) continue; + if (child is NavMenuItem navMenuItem) navMenuItem.ClearSelection(); + } + + if (item.DataContext is not null && item.DataContext != DataContext) + SelectedItem = item.DataContext; + else + SelectedItem = item; + item.BringIntoView(); + _isSelectionFromUI = false; + } + + private void ClearAll() + { + foreach (var child in LogicalChildren) + if (child is NavMenuItem item) + item.ClearSelection(); + } + private bool TryToSelectItem(object? item) { if (item is null) return false; @@ -250,33 +284,6 @@ public class NavMenu : ItemsControl return found; } - protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) - { - return NeedsContainer(item, out recycleKey); - } - - protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) - { - return new NavMenuItem(); - } - - internal void SelectItem(NavMenuItem item, NavMenuItem parent) - { - _updateFromUI = true; - foreach (var child in LogicalChildren) - { - if (Equals(child, parent)) continue; - if (child is NavMenuItem navMenuItem) navMenuItem.ClearSelection(); - } - - if (item.DataContext is not null && item.DataContext != DataContext) - SelectedItem = item.DataContext; - else - SelectedItem = item; - item.BringIntoView(); - _updateFromUI = false; - } - private IEnumerable GetLeafMenus() { foreach (var child in LogicalChildren) @@ -286,11 +293,4 @@ public class NavMenu : ItemsControl foreach (var leaf in leafs) yield return leaf; } } - - private void ClearAll() - { - foreach (var child in LogicalChildren) - if (child is NavMenuItem item) - item.ClearSelection(); - } } \ No newline at end of file diff --git a/src/Ursa/Controls/NavMenu/NavMenuItem.cs b/src/Ursa/Controls/NavMenu/NavMenuItem.cs index 5112ef6..52eea38 100644 --- a/src/Ursa/Controls/NavMenu/NavMenuItem.cs +++ b/src/Ursa/Controls/NavMenu/NavMenuItem.cs @@ -18,7 +18,12 @@ namespace Ursa.Controls; /// /// Navigation Menu Item /// -[PseudoClasses(PC_Highlighted, PC_HorizontalCollapsed, PC_VerticalCollapsed, PC_FirstLevel, PC_Selector)] +[PseudoClasses( + PC_Highlighted, + PC_HorizontalCollapsed, + PC_VerticalCollapsed, + PC_FirstLevel, + PC_Selector)] public class NavMenuItem : HeaderedItemsControl { public const string PC_Highlighted = ":highlighted"; @@ -27,8 +32,6 @@ public class NavMenuItem : HeaderedItemsControl public const string PC_VerticalCollapsed = ":vertical-collapsed"; public const string PC_Selector = ":selector"; - private static readonly Point InvalidPoint = new(double.NaN, double.NaN); - public static readonly StyledProperty IconProperty = AvaloniaProperty.Register( nameof(Icon)); @@ -72,7 +75,7 @@ public class NavMenuItem : HeaderedItemsControl private bool _isHighlighted; private int _level; private Panel? _overflowPanel; - private Point _pointerDownPoint = InvalidPoint; + private bool _isPointerDown = false; private Popup? _popup; private NavMenu? _rootMenu; @@ -157,23 +160,6 @@ public class NavMenuItem : HeaderedItemsControl set => SetValue(IsSeparatorProperty, value); } - private void OnIsHorizontalCollapsedChanged(AvaloniaPropertyChangedEventArgs args) - { - if (args.NewValue.Value) - { - if (ItemsPanelRoot is OverflowStackPanel s) s.MoveChildrenToOverflowPanel(); - } - else - { - if (ItemsPanelRoot is OverflowStackPanel s) s.MoveChildrenToMainPanel(); - } - } - - private void OnLevelChange(AvaloniaPropertyChangedEventArgs args) - { - PseudoClasses.Set(PC_FirstLevel, args.NewValue.Value == 1); - } - protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) { return NeedsContainer(item, out recycleKey); @@ -230,82 +216,50 @@ public class NavMenuItem : HeaderedItemsControl var p = e.GetCurrentPoint(this); if (p.Properties.PointerUpdateKind is not (PointerUpdateKind.LeftButtonPressed or PointerUpdateKind.RightButtonPressed)) return; + if (p.Pointer.Type == PointerType.Mouse) - { - if (ItemCount == 0) - { - SelectItem(this); - Command?.Execute(CommandParameter); - e.Handled = true; - } - else - { - if (!IsHorizontalCollapsed) - { - SetCurrentValue(IsVerticalCollapsedProperty, !IsVerticalCollapsed); - e.Handled = true; - } - else - { - if (_popup is null || e.Source is not Visual v || _popup.IsInsidePopup(v)) return; - if (_popup.IsOpen) - _popup.Close(); - else - _popup.Open(); - } - } - } + ActivateMenuItem(e); else - { - _pointerDownPoint = p.Position; - } + _isPointerDown = true; } protected override void OnPointerReleased(PointerReleasedEventArgs e) { base.OnPointerReleased(e); - if (!e.Handled && !double.IsNaN(_pointerDownPoint.X) && - e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right) + + if (e.Handled || !_isPointerDown) return; + + _isPointerDown = false; + + if (e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right) { var point = e.GetCurrentPoint(this); - if (!new Rect(Bounds.Size).ContainsExclusive(point.Position) || e.Pointer.Type != PointerType.Touch) return; - if (ItemCount == 0) - { - SelectItem(this); - Command?.Execute(CommandParameter); - e.Handled = true; - } - else - { - if (!IsHorizontalCollapsed) - { - SetCurrentValue(IsVerticalCollapsedProperty, !IsVerticalCollapsed); - e.Handled = true; - } - else - { - if (_popup is null || e.Source is not Visual v || _popup.IsInsidePopup(v)) return; - if (_popup.IsOpen) - _popup.Close(); - else - _popup.Open(); - } - } + if (new Rect(Bounds.Size).ContainsExclusive(point.Position) && e.Pointer.Type == PointerType.Touch) + ActivateMenuItem(e); } } + private void OnIsHorizontalCollapsedChanged(AvaloniaPropertyChangedEventArgs args) + { + if (args.NewValue.Value) + { + if (ItemsPanelRoot is OverflowStackPanel s) s.MoveChildrenToOverflowPanel(); + } + else + { + if (ItemsPanelRoot is OverflowStackPanel s) s.MoveChildrenToMainPanel(); + } + } + + private void OnLevelChange(AvaloniaPropertyChangedEventArgs args) + { + PseudoClasses.Set(PC_FirstLevel, args.NewValue.Value == 1); + } + internal void SelectItem(NavMenuItem item) { - if (item == this) - { - SetCurrentValue(IsSelectedProperty, true); - SetCurrentValue(IsHighlightedProperty, true); - } - else - { - SetCurrentValue(IsSelectedProperty, false); - SetCurrentValue(IsHighlightedProperty, true); - } + SetCurrentValue(IsSelectedProperty, item == this); + SetCurrentValue(IsHighlightedProperty, true); if (Parent is NavMenuItem menuItem) { @@ -320,7 +274,7 @@ public class NavMenuItem : HeaderedItemsControl menu.SelectItem(item, this); } - if (_popup is not null) _popup.Close(); + _popup?.Close(); } internal void ClearSelection() @@ -332,23 +286,40 @@ public class NavMenuItem : HeaderedItemsControl item.ClearSelection(); } - private NavMenu? GetRootMenu() + private void SelectAndExecute() { - var root = this.FindAncestorOfType() ?? this.FindLogicalAncestorOfType(); - return root; + SelectItem(this); + Command?.Execute(CommandParameter); } - private static int CalculateDistanceFromLogicalParent(ILogical? logical, int @default = -1) where T : class + private void ActivateMenuItem(RoutedEventArgs e) { - var result = 0; - - while (logical != null && !(logical is T)) + if (ItemCount == 0) { - if (logical is NavMenuItem) result++; - logical = logical.LogicalParent; + SelectAndExecute(); + e.Handled = true; + return; } - return logical != null ? result : @default; + if (!IsHorizontalCollapsed) + { + SetCurrentValue(IsVerticalCollapsedProperty, !IsVerticalCollapsed); + e.Handled = true; + } + else if (_popup is not null) + { + TogglePopup(e.Source, true, !_popup.IsOpen); + } + } + + private void TogglePopup(object? source, bool outsidePopup, bool open) + { + if (ItemCount > 0 && + _popup is not null && + source is Visual visual && + _popup.IsInsidePopup(visual) != outsidePopup && + _popup.IsOpen != open) + _popup.IsOpen = open; } internal IEnumerable GetLeafMenus() @@ -366,4 +337,23 @@ public class NavMenuItem : HeaderedItemsControl foreach (var i in items) yield return i; } } + + private NavMenu? GetRootMenu() + { + var root = this.FindAncestorOfType() ?? this.FindLogicalAncestorOfType(); + return root; + } + + private static int CalculateDistanceFromLogicalParent(ILogical? logical, int @default = -1) where T : class + { + var result = 0; + + while (logical is not null and not T) + { + if (logical is NavMenuItem) result++; + logical = logical.LogicalParent; + } + + return logical is not null ? result : @default; + } } \ No newline at end of file