diff --git a/src/AtomUI.Controls.DataGrid/Utils/Converters/DataGridHeaderIndicatorLayoutMarginsConverter.cs b/src/AtomUI.Controls.DataGrid/Utils/Converters/DataGridHeaderIndicatorLayoutMarginsConverter.cs deleted file mode 100644 index 377769d92..000000000 --- a/src/AtomUI.Controls.DataGrid/Utils/Converters/DataGridHeaderIndicatorLayoutMarginsConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Globalization; -using Avalonia; -using Avalonia.Data.Converters; - -namespace AtomUI.Controls.Converters; - -internal class DataGridHeaderIndicatorLayoutMarginsConverter : IValueConverter -{ - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - if (value is Thickness paddings) - { - return new Thickness(0, 0, paddings.Right, 0); - } - return value; - } - - public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/src/AtomUI.Controls/NavMenu/DefaultNavMenuInteractionHandler.cs b/src/AtomUI.Controls/NavMenu/DefaultNavMenuInteractionHandler.cs index 9520534b8..4ec2b96c8 100644 --- a/src/AtomUI.Controls/NavMenu/DefaultNavMenuInteractionHandler.cs +++ b/src/AtomUI.Controls/NavMenu/DefaultNavMenuInteractionHandler.cs @@ -140,15 +140,13 @@ internal class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler // 判断当前选中的是不是自己 if (!ReferenceEquals(LatestSelectedItem, navMenuItem)) { + var navMenu = Menu as NavMenu; if (LatestSelectedItem != null) { var ancestorInfo = HasCommonAncestor(LatestSelectedItem, navMenuItem); if (!ancestorInfo.Item1) { - if (Menu is NavMenu navMenu) - { - navMenu.ClearSelection(); - } + navMenu?.ClearSelection(); } else { @@ -160,6 +158,7 @@ internal class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler } navMenuItem.SelectItemRecursively(); LatestSelectedItem = navMenuItem; + navMenu?.RaiseNavMenuItemSelected(navMenuItem); } } } @@ -219,7 +218,7 @@ internal class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler } _currentPressedIsValid = false; - if (e.InitialPressMouseButton == MouseButton.Left && item.HasSubMenu == false) + if (e.InitialPressMouseButton == MouseButton.Left) { Click(item); e.Handled = true; @@ -264,14 +263,25 @@ internal class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler { if (e.Source is ILogical control && !Menu.IsLogicalAncestorOf(control)) { - Menu?.Close(); + CloseAllTopLevelMenuItems(); } } } protected virtual void WindowDeactivated(object? sender, EventArgs e) { - Menu?.Close(); + CloseAllTopLevelMenuItems(); + } + + private void CloseAllTopLevelMenuItems() + { + if (Menu != null) + { + foreach (var i in Menu.SubItems) + { + i.Close(); + } + } } internal static NavMenuItem? GetMenuItem(StyledElement? item) => (NavMenuItem?)GetMenuItemCore(item); @@ -352,12 +362,14 @@ internal class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler internal void Click(INavMenuItem item) { item.RaiseClick(); - var navMenu = FindNavMenu(item); - navMenu?.RaiseNavMenuItemClick(item); - if (!item.StaysOpenOnClick) + if (Menu is NavMenu navMenu) { - var topLevelItem = FindTopLevelMenuItem(item); - topLevelItem?.Close(); + navMenu.RaiseNavMenuItemClick(item); + if (!item.HasSubMenu && !item.StaysOpenOnClick) + { + var topLevelItem = FindTopLevelMenuItem(item); + topLevelItem?.Close(); + } } } diff --git a/src/AtomUI.Controls/NavMenu/INavMenu.cs b/src/AtomUI.Controls/NavMenu/INavMenu.cs index d97d9217f..344cc67f9 100644 --- a/src/AtomUI.Controls/NavMenu/INavMenu.cs +++ b/src/AtomUI.Controls/NavMenu/INavMenu.cs @@ -1,10 +1,15 @@ -using Avalonia.Input; +using AtomUI.Controls.Primitives; using Avalonia.Rendering; namespace AtomUI.Controls; -internal interface INavMenu : INavMenuElement, IInputElement +internal interface INavMenu : INavMenuElement { + /// + /// Gets or sets the currently selected submenu item. + /// + INavMenuItem? SelectedItem { get; } + /// /// Gets the menu interaction handler. /// @@ -19,4 +24,14 @@ internal interface INavMenu : INavMenuElement, IInputElement /// Gets the root of the visual tree, if the control is attached to a visual tree. /// IRenderRoot? VisualRoot { get; } + + /// + /// List of paths opened by default + /// + IList? DefaultOpenPaths { get; set; } + + /// + /// Default selected path + /// + TreeNodePath? DefaultSelectedPath { get; set; } } \ No newline at end of file diff --git a/src/AtomUI.Controls/NavMenu/INavMenuElement.cs b/src/AtomUI.Controls/NavMenu/INavMenuElement.cs index 5a441a7b1..89997c34e 100644 --- a/src/AtomUI.Controls/NavMenu/INavMenuElement.cs +++ b/src/AtomUI.Controls/NavMenu/INavMenuElement.cs @@ -5,11 +5,6 @@ namespace AtomUI.Controls; public interface INavMenuElement : IInputElement, ILogical { - /// - /// Gets or sets the currently selected submenu item. - /// - INavMenuItem? SelectedItem { get; set; } - /// /// Gets the submenu items. /// diff --git a/src/AtomUI.Controls/NavMenu/INavMenuItem.cs b/src/AtomUI.Controls/NavMenu/INavMenuItem.cs index 8f7ffc0d5..d6e434ac0 100644 --- a/src/AtomUI.Controls/NavMenu/INavMenuItem.cs +++ b/src/AtomUI.Controls/NavMenu/INavMenuItem.cs @@ -2,6 +2,11 @@ public interface INavMenuItem : INavMenuElement { + /// + /// Gets or sets the currently selected submenu item. + /// + INavMenuItem? SelectedItem { get; set; } + /// /// Gets or sets a value that item key. /// diff --git a/src/AtomUI.Controls/NavMenu/InlineNavMenuInteractionHandler.cs b/src/AtomUI.Controls/NavMenu/InlineNavMenuInteractionHandler.cs index 9b571da7a..c47869fc1 100644 --- a/src/AtomUI.Controls/NavMenu/InlineNavMenuInteractionHandler.cs +++ b/src/AtomUI.Controls/NavMenu/InlineNavMenuInteractionHandler.cs @@ -9,7 +9,7 @@ using NavMenuControl = NavMenu; internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler { - internal INavMenu? NavMenu { get; private set; } + internal INavMenu? Menu { get; private set; } public void Attach(NavMenu navMenu) => AttachCore(navMenu); public void Detach(NavMenu navMenu) => DetachCore(navMenu); @@ -19,24 +19,24 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler internal void AttachCore(INavMenu navMenu) { - if (NavMenu != null) + if (Menu != null) { throw new NotSupportedException("InlineNavMenuInteractionHandler is already attached."); } - NavMenu = navMenu; - NavMenu.PointerPressed += PointerPressed; - NavMenu.PointerReleased += PointerReleased; + Menu = navMenu; + Menu.PointerPressed += PointerPressed; + Menu.PointerReleased += PointerReleased; } internal void DetachCore(INavMenu navMenu) { - if (NavMenu != navMenu) + if (Menu != navMenu) { throw new NotSupportedException("InlineNavMenuInteractionHandler is not attached to the navMenu."); } - NavMenu.PointerPressed -= PointerPressed; - NavMenu.PointerReleased -= PointerReleased; - NavMenu = null; + Menu.PointerPressed -= PointerPressed; + Menu.PointerReleased -= PointerReleased; + Menu = null; } protected virtual void PointerPressed(object? sender, PointerPressedEventArgs e) @@ -75,15 +75,13 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler // 判断当前选中的是不是自己 if (!ReferenceEquals(LatestSelectedItem, navMenuItem)) { + var navMenu = Menu as NavMenu; if (LatestSelectedItem != null) { var ancestorInfo = HasCommonAncestor(LatestSelectedItem, navMenuItem); if (!ancestorInfo.Item1) { - if (NavMenu is NavMenu navMenu) - { - navMenu.ClearSelection(); - } + navMenu?.ClearSelection(); } else { @@ -96,6 +94,7 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler navMenuItem.SelectItemRecursively(); LatestSelectedItem = navMenuItem; + navMenu?.RaiseNavMenuItemSelected(navMenuItem); } } } @@ -156,7 +155,7 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler _currentPressedIsValid = false; - if (e.InitialPressMouseButton == MouseButton.Left && !item.HasSubMenu) + if (e.InitialPressMouseButton == MouseButton.Left) { Click(item); e.Handled = true; @@ -166,8 +165,10 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler internal void Click(INavMenuItem item) { item.RaiseClick(); - var navMenu = FindNavMenu(item); - navMenu?.RaiseNavMenuItemClick(item); + if (Menu is NavMenu navMenu) + { + navMenu.RaiseNavMenuItemClick(item); + } } internal void Open(INavMenuItem item) @@ -192,21 +193,4 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler item = item.Parent; } } - - private static NavMenu? FindNavMenu(INavMenuItem item) - { - var current = (INavMenuElement?)item; - NavMenu? result = null; - - while (current != null && !(current is NavMenu)) - { - current = (current as INavMenuItem)?.Parent; - } - - if (current is NavMenu navMenu) - { - result = navMenu; - } - return result; - } } \ No newline at end of file diff --git a/src/AtomUI.Controls/NavMenu/NavMenu.cs b/src/AtomUI.Controls/NavMenu/NavMenu.cs index c5f9741b1..352ec899c 100644 --- a/src/AtomUI.Controls/NavMenu/NavMenu.cs +++ b/src/AtomUI.Controls/NavMenu/NavMenu.cs @@ -3,7 +3,6 @@ using System.Reactive.Disposables; using AtomUI.Controls.Primitives; using AtomUI.Controls.Themes; using AtomUI.Data; -using AtomUI.Exceptions; using AtomUI.Theme; using AtomUI.Theme.Data; using AtomUI.Theme.Styling; @@ -28,60 +27,62 @@ using Avalonia.VisualTree; namespace AtomUI.Controls; -public class NavMenuItemClickEventArgs : RoutedEventArgs -{ - public NavMenuItemClickEventArgs(RoutedEvent routedEvent, INavMenuItem navMenuItem) - : base(routedEvent) - { - NavMenuItem = navMenuItem; - } - - public INavMenuItem NavMenuItem { get; } -} - [PseudoClasses( NavMenuPseudoClass.InlineMode, NavMenuPseudoClass.HorizontalMode, NavMenuPseudoClass.VerticalMode, NavMenuPseudoClass.DarkStyle, NavMenuPseudoClass.LightStyle)] -public class NavMenu : SelectingItemsControl, - IFocusScope, +public class NavMenu : ItemsControl, + IFocusScope, INavMenu, IMotionAwareControl, IControlSharedTokenResourcesHost { #region 公共属性定义 - public static readonly DirectProperty?> DefaultOpenPathsProperty = - AvaloniaProperty.RegisterDirect?>( - nameof(DefaultOpenPaths), - o => o.DefaultOpenPaths, - (o, v) => o.DefaultOpenPaths = v); - + public static readonly DirectProperty SelectedItemProperty = + AvaloniaProperty.RegisterDirect( + nameof(SelectedItem), + o => o.SelectedItem); + public static readonly DirectProperty DefaultSelectedPathProperty = AvaloniaProperty.RegisterDirect( nameof(DefaultSelectedPath), o => o.DefaultSelectedPath, (o, v) => o.DefaultSelectedPath = v); - + public static readonly DirectProperty IsOpenProperty = AvaloniaProperty.RegisterDirect( nameof(IsOpen), o => o.IsOpen); - + public static readonly StyledProperty IsMotionEnabledProperty = MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner(); - + public static readonly StyledProperty IsAccordionModeProperty = AvaloniaProperty.Register(nameof(IsAccordionMode), false); - + public static readonly StyledProperty ModeProperty = AvaloniaProperty.Register(nameof(Mode), NavMenuMode.Horizontal); public static readonly StyledProperty IsDarkStyleProperty = AvaloniaProperty.Register(nameof(IsDarkStyle), false); + public static readonly DirectProperty?> DefaultOpenPathsProperty = + AvaloniaProperty.RegisterDirect?>( + nameof(DefaultOpenPaths), + o => o.DefaultOpenPaths, + (o, v) => o.DefaultOpenPaths = v); + + public INavMenuItem? _selectedItem; + + public INavMenuItem? SelectedItem + { + get => _selectedItem; + private set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value); + } + private IList? _defaultOpenPaths; public IList? DefaultOpenPaths @@ -135,9 +136,10 @@ public class NavMenu : SelectingItemsControl, #region 公共事件定义 public static readonly RoutedEvent NavMenuItemClickEvent = - RoutedEvent.Register( - nameof(NavMenuItemClick), - RoutingStrategies.Bubble); + RoutedEvent.Register(nameof(NavMenuItemClick), RoutingStrategies.Bubble); + + public static readonly RoutedEvent NavMenuItemSelectedEvent = + RoutedEvent.Register(nameof(NavMenuItemSelected), RoutingStrategies.Bubble); public static readonly RoutedEvent OpenedEvent = RoutedEvent.Register(nameof(Opened), RoutingStrategies.Bubble); @@ -151,6 +153,12 @@ public class NavMenu : SelectingItemsControl, remove => RemoveHandler(NavMenuItemClickEvent, value); } + public event EventHandler? NavMenuItemSelected + { + add => AddHandler(NavMenuItemSelectedEvent, value); + remove => RemoveHandler(NavMenuItemSelectedEvent, value); + } + public event EventHandler? Opened { add => AddHandler(OpenedEvent, value); @@ -185,17 +193,6 @@ public class NavMenu : SelectingItemsControl, IRenderRoot? INavMenu.VisualRoot => VisualRoot; - INavMenuItem? INavMenuElement.SelectedItem - { - get - { - var index = SelectedIndex; - return (index != -1) ? (INavMenuItem?)ContainerFromIndex(index) : null; - } - - set => SelectedIndex = value is Control c ? IndexFromContainer(c) : -1; - } - IEnumerable INavMenuElement.SubItems => LogicalChildren.OfType(); /// @@ -217,7 +214,6 @@ public class NavMenu : SelectingItemsControl, KeyboardNavigationMode.Once); AutomationProperties.AccessibilityViewProperty.OverrideDefaultValue(AccessibilityView.Control); AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(AutomationControlType.Menu); - AutoScrollToSelectedItemProperty.OverrideDefaultValue(false); NavMenuItem.SubmenuOpenedEvent.AddClassHandler((x, e) => x.OnSubmenuOpened(e)); } @@ -240,7 +236,7 @@ public class NavMenu : SelectingItemsControl, } IsOpen = false; - SelectedIndex = -1; + SelectedItem = null; RaiseEvent(new RoutedEventArgs { @@ -286,15 +282,6 @@ public class NavMenu : SelectingItemsControl, } IsOpen = true; } - - private void ValidateSelectionMode() - { - if (SelectionMode.HasFlag(SelectionMode.Multiple)) - { - throw new InvalidPropertyValueException(SelectionModeProperty.Name, SelectionMode.Multiple, - $"The value '{SelectionMode.Multiple}' is invalid for the '{SelectionModeProperty.Name}' property in NavMenuItem."); - } - } private void HandleItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { @@ -331,10 +318,6 @@ public class NavMenu : SelectingItemsControl, } } } - else if (change.Property == SelectionModeProperty) - { - ValidateSelectionMode(); - } if (change.Property == IsDarkStyleProperty || change.Property == ModeProperty) { @@ -660,6 +643,11 @@ public class NavMenu : SelectingItemsControl, RaiseEvent(new NavMenuItemClickEventArgs(NavMenuItemClickEvent, navMenuItem)); } + internal void RaiseNavMenuItemSelected(INavMenuItem navMenuItem) + { + RaiseEvent(new NavMenuItemSelectedEventArgs(NavMenuItemSelectedEvent, navMenuItem)); + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); diff --git a/src/AtomUI.Controls/NavMenu/NavMenuEventArgs.cs b/src/AtomUI.Controls/NavMenu/NavMenuEventArgs.cs new file mode 100644 index 000000000..46e85c512 --- /dev/null +++ b/src/AtomUI.Controls/NavMenu/NavMenuEventArgs.cs @@ -0,0 +1,25 @@ +using Avalonia.Interactivity; + +namespace AtomUI.Controls; + +public class NavMenuItemClickEventArgs : RoutedEventArgs +{ + public NavMenuItemClickEventArgs(RoutedEvent routedEvent, INavMenuItem navMenuItem) + : base(routedEvent) + { + NavMenuItem = navMenuItem; + } + + public INavMenuItem NavMenuItem { get; } +} + +public class NavMenuItemSelectedEventArgs : RoutedEventArgs +{ + public NavMenuItemSelectedEventArgs(RoutedEvent routedEvent, INavMenuItem navMenuItem) + : base(routedEvent) + { + NavMenuItem = navMenuItem; + } + + public INavMenuItem NavMenuItem { get; } +} \ No newline at end of file diff --git a/src/AtomUI.Controls/NavMenu/NavMenuItem.cs b/src/AtomUI.Controls/NavMenu/NavMenuItem.cs index 2a27e975f..acffc2b1c 100644 --- a/src/AtomUI.Controls/NavMenu/NavMenuItem.cs +++ b/src/AtomUI.Controls/NavMenu/NavMenuItem.cs @@ -191,12 +191,12 @@ public class NavMenuItem : HeaderedSelectingItemsControl, protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; - INavMenuItem? INavMenuElement.SelectedItem + INavMenuItem? INavMenuItem.SelectedItem { get { var index = SelectedIndex; - return (index != -1) ? (INavMenuItem?)ContainerFromIndex(index) : null; + return index != -1 ? (INavMenuItem?)ContainerFromIndex(index) : null; } set => SelectedIndex = value is Control c ? IndexFromContainer(c) : -1;