From e98729e458df96c03c959f9426ff8873e9000e6c Mon Sep 17 00:00:00 2001 From: polarboy Date: Tue, 24 Sep 2024 15:35:58 +0800 Subject: [PATCH] Complete the inline NavMenu --- .../ShowCase/MenuShowCase.axaml | 46 ++-- .../TokenResourceConst.g.cs | 1 + .../NavMenu/BaseNavMenuItemTheme.cs | 106 +++++--- .../InlineNavMenuInteractionHandler.cs | 7 +- .../NavMenu/InlineNavMenuItemTheme.cs | 72 ++---- src/AtomUI.Controls/NavMenu/NavMenu.cs | 22 +- src/AtomUI.Controls/NavMenu/NavMenuItem.cs | 239 +++++++++++++----- src/AtomUI.Controls/NavMenu/NavMenuToken.cs | 7 + src/AtomUI.Controls/PathIcon/PathIcon.cs | 8 + 9 files changed, 341 insertions(+), 167 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/MenuShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/MenuShowCase.axaml index 8b09b25..aaa647d 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/MenuShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/MenuShowCase.axaml @@ -187,28 +187,28 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + @@ -220,7 +220,7 @@ - + diff --git a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs index 2a40933..2790c34 100644 --- a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs +++ b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs @@ -411,6 +411,7 @@ namespace AtomUI.Theme.Styling public static readonly TokenResourceKey HorizontalItemHoverBg = new TokenResourceKey("NavMenu.HorizontalItemHoverBg", "AtomUI.Token"); public static readonly TokenResourceKey HorizontalItemBorderRadius = new TokenResourceKey("NavMenu.HorizontalItemBorderRadius", "AtomUI.Token"); public static readonly TokenResourceKey ItemHeight = new TokenResourceKey("NavMenu.ItemHeight", "AtomUI.Token"); + public static readonly TokenResourceKey InlineItemIndentUnit = new TokenResourceKey("NavMenu.InlineItemIndentUnit", "AtomUI.Token"); public static readonly TokenResourceKey CollapsedWidth = new TokenResourceKey("NavMenu.CollapsedWidth", "AtomUI.Token"); public static readonly TokenResourceKey MenuPopupBg = new TokenResourceKey("NavMenu.MenuPopupBg", "AtomUI.Token"); public static readonly TokenResourceKey HorizontalLineHeight = new TokenResourceKey("NavMenu.HorizontalLineHeight", "AtomUI.Token"); diff --git a/src/AtomUI.Controls/NavMenu/BaseNavMenuItemTheme.cs b/src/AtomUI.Controls/NavMenu/BaseNavMenuItemTheme.cs index 8a873b5..c7a4a49 100644 --- a/src/AtomUI.Controls/NavMenu/BaseNavMenuItemTheme.cs +++ b/src/AtomUI.Controls/NavMenu/BaseNavMenuItemTheme.cs @@ -38,14 +38,14 @@ internal class BaseNavMenuItemTheme : BaseControlTheme BuildInstanceStyles(item); // 仅仅为了把 Popup 包进来,没有其他什么作用 var layoutWrapper = new Panel(); - var header = BuildMenuItemContent(scope); + var header = BuildMenuItemContent(item, scope); BuildExtraItem(layoutWrapper, scope); layoutWrapper.Children.Add(header); return layoutWrapper; }); } - protected virtual Control BuildMenuItemContent(INameScope scope) + protected virtual Control BuildMenuItemContent(NavMenuItem navMenuItem, INameScope scope) { var headerFrame = new Border { @@ -55,7 +55,13 @@ internal class BaseNavMenuItemTheme : BaseControlTheme var transitions = new Transitions(); transitions.Add(AnimationUtils.CreateTransition(Border.BackgroundProperty)); headerFrame.Transitions = transitions; + + headerFrame.Child = BuildMenuItemInfoGrid(navMenuItem, scope); + return headerFrame; + } + protected virtual Grid BuildMenuItemInfoGrid(NavMenuItem navMenuItem, INameScope scope) + { var layout = new Grid { Name = MainContainerPart, @@ -132,24 +138,21 @@ internal class BaseNavMenuItemTheme : BaseControlTheme inputGestureText.RegisterInNameScope(scope); - var menuIndicatorIcon = BuildMenuIndicatorIcon(); + var menuIndicatorIcon = BuildMenuIndicatorIcon(scope); Grid.SetColumn(menuIndicatorIcon, 3); - menuIndicatorIcon.RegisterInNameScope(scope); layout.Children.Add(iconPresenter); layout.Children.Add(itemTextPresenter); layout.Children.Add(inputGestureText); layout.Children.Add(menuIndicatorIcon); - - headerFrame.Child = layout; - return headerFrame; + return layout; } protected virtual void BuildExtraItem(Panel layout, INameScope scope) { } - protected virtual Control BuildMenuIndicatorIcon() + protected virtual Control BuildMenuIndicatorIcon(INameScope scope) { var menuIndicatorIcon = new PathIcon { @@ -158,12 +161,23 @@ internal class BaseNavMenuItemTheme : BaseControlTheme VerticalAlignment = VerticalAlignment.Center, Kind = "RightOutlined" }; - + + + CreateTemplateParentBinding(menuIndicatorIcon, PathIcon.IsEnabledProperty, NavMenuItem.IsEnabledProperty); + + TokenResourceBinder.CreateGlobalTokenBinding(menuIndicatorIcon, PathIcon.NormalFilledBrushProperty, + NavMenuTokenResourceKey.ItemColor); + TokenResourceBinder.CreateGlobalTokenBinding(menuIndicatorIcon, PathIcon.SelectedFilledBrushProperty, + NavMenuTokenResourceKey.ItemSelectedColor); + TokenResourceBinder.CreateGlobalTokenBinding(menuIndicatorIcon, PathIcon.DisabledFilledBrushProperty, + NavMenuTokenResourceKey.ItemDisabledColor); + TokenResourceBinder.CreateGlobalTokenBinding(menuIndicatorIcon, Layoutable.WidthProperty, NavMenuTokenResourceKey.MenuArrowSize); TokenResourceBinder.CreateGlobalTokenBinding(menuIndicatorIcon, Layoutable.HeightProperty, NavMenuTokenResourceKey.MenuArrowSize); - + menuIndicatorIcon.RegisterInNameScope(scope); + return menuIndicatorIcon; } @@ -195,31 +209,63 @@ internal class BaseNavMenuItemTheme : BaseControlTheme } // Hover 状态 - var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver)); + var hoverStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart).Class(StdPseudoClass.PointerOver)); hoverStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.ItemHoverColor); - { - var borderStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart)); - borderStyle.Add(Border.BackgroundProperty, NavMenuTokenResourceKey.ItemHoverBg); - hoverStyle.Add(borderStyle); - } + hoverStyle.Add(Border.BackgroundProperty, NavMenuTokenResourceKey.ItemHoverBg); commonStyle.Add(hoverStyle); + + // 选中分两种,一种是有子菜单一种是没有子菜单 + var hasNoSubMenuStyle = new Style(selector => selector.Nesting().PropertyEquals(NavMenuItem.HasSubMenuProperty, false)); + { + var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected)); + { + var itemDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart)); + itemDecoratorStyle.Add(Border.BackgroundProperty, NavMenuTokenResourceKey.ItemSelectedBg); + itemDecoratorStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.ItemSelectedColor); + selectedStyle.Add(itemDecoratorStyle); + } + hasNoSubMenuStyle.Add(selectedStyle); + } + commonStyle.Add(hasNoSubMenuStyle); + + var hasSubMenuStyle = new Style(selector => selector.Nesting().PropertyEquals(NavMenuItem.HasSubMenuProperty, true)); + { + var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected)); + { + var itemDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart)); + itemDecoratorStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.ItemSelectedColor); + selectedStyle.Add(itemDecoratorStyle); + } + hasSubMenuStyle.Add(selectedStyle); + } + commonStyle.Add(hasSubMenuStyle); Add(commonStyle); } private void BuildMenuIndicatorStyle() { { - var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconPart)); - menuIndicatorStyle.Add(Visual.IsVisibleProperty, true); - Add(menuIndicatorStyle); + { + var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconPart)); + menuIndicatorStyle.Add(Visual.IsVisibleProperty, true); + Add(menuIndicatorStyle); + } + + var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected)); + { + var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconPart)); + menuIndicatorStyle.Add(PathIcon.IconModeProperty, IconMode.Selected); + selectedStyle.Add(menuIndicatorStyle); + } + Add(selectedStyle); } - var hasSubMenuStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Empty)); + var hasNoSubMenuStyle = new Style(selector => selector.Nesting().PropertyEquals(NavMenuItem.HasSubMenuProperty, false)); { var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconPart)); menuIndicatorStyle.Add(Visual.IsVisibleProperty, false); - hasSubMenuStyle.Add(menuIndicatorStyle); + hasNoSubMenuStyle.Add(menuIndicatorStyle); } - Add(hasSubMenuStyle); + Add(hasNoSubMenuStyle); } private void BuildMenuIconStyle() @@ -248,13 +294,15 @@ internal class BaseNavMenuItemTheme : BaseControlTheme protected override void BuildInstanceStyles(Control control) { - var iconStyle = new Style(selector => selector.Name(ThemeConstants.ItemIconPart)); - iconStyle.Add(PathIcon.WidthProperty, NavMenuTokenResourceKey.ItemIconSize); - iconStyle.Add(PathIcon.HeightProperty, NavMenuTokenResourceKey.ItemIconSize); - iconStyle.Add(PathIcon.NormalFilledBrushProperty, GlobalTokenResourceKey.ColorText); - iconStyle.Add(PathIcon.DisabledFilledBrushProperty, NavMenuTokenResourceKey.ItemDisabledColor); - iconStyle.Add(PathIcon.SelectedFilledBrushProperty, GlobalTokenResourceKey.ColorPrimary); - control.Styles.Add(iconStyle); + { + var iconStyle = new Style(selector => selector.Name(ThemeConstants.ItemIconPart)); + iconStyle.Add(PathIcon.WidthProperty, NavMenuTokenResourceKey.ItemIconSize); + iconStyle.Add(PathIcon.HeightProperty, NavMenuTokenResourceKey.ItemIconSize); + iconStyle.Add(PathIcon.NormalFilledBrushProperty, GlobalTokenResourceKey.ColorText); + iconStyle.Add(PathIcon.DisabledFilledBrushProperty, NavMenuTokenResourceKey.ItemDisabledColor); + iconStyle.Add(PathIcon.SelectedFilledBrushProperty, GlobalTokenResourceKey.ColorPrimary); + control.Styles.Add(iconStyle); + } var disabledIconStyle = new Style(selector => selector.OfType().Class(StdPseudoClass.Disabled)); disabledIconStyle.Add(PathIcon.IconModeProperty, IconMode.Disabled); diff --git a/src/AtomUI.Controls/NavMenu/InlineNavMenuInteractionHandler.cs b/src/AtomUI.Controls/NavMenu/InlineNavMenuInteractionHandler.cs index 8bd814e..7d2ba21 100644 --- a/src/AtomUI.Controls/NavMenu/InlineNavMenuInteractionHandler.cs +++ b/src/AtomUI.Controls/NavMenu/InlineNavMenuInteractionHandler.cs @@ -53,8 +53,9 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler { if (NavMenu is NavMenu navMenu) { - navMenu.UpdateSelectionFromItem(item); + navMenu.ClearSelection(); } + item?.SelectItemRecursively(); } e.Handled = true; @@ -66,7 +67,7 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler item.Open(); } - internal static INavMenuItem? GetMenuItemCore(StyledElement? item) + internal static NavMenuItem? GetMenuItemCore(StyledElement? item) { while (true) { @@ -75,7 +76,7 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler return null; } - if (item is INavMenuItem menuItem) + if (item is NavMenuItem menuItem) { return menuItem; } diff --git a/src/AtomUI.Controls/NavMenu/InlineNavMenuItemTheme.cs b/src/AtomUI.Controls/NavMenu/InlineNavMenuItemTheme.cs index 023b313..1ee4eab 100644 --- a/src/AtomUI.Controls/NavMenu/InlineNavMenuItemTheme.cs +++ b/src/AtomUI.Controls/NavMenu/InlineNavMenuItemTheme.cs @@ -4,6 +4,7 @@ using AtomUI.Utils; using Avalonia; using Avalonia.Animation; using Avalonia.Controls; +using Avalonia.Controls.Converters; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; @@ -30,10 +31,10 @@ internal class InlineNavMenuItemTheme : BaseNavMenuItemTheme return ID; } - protected override Control BuildMenuIndicatorIcon() + protected override Control BuildMenuIndicatorIcon(INameScope scope) { - var indicatorIcon = base.BuildMenuIndicatorIcon(); - var menuIndicatorIconPresenter = new ContentPresenter() + var indicatorIcon = base.BuildMenuIndicatorIcon(scope); + var menuIndicatorIconPresenter = new Border() { Name = MenuIndicatorIconLayoutPart, Transitions = new Transitions() @@ -41,18 +42,20 @@ internal class InlineNavMenuItemTheme : BaseNavMenuItemTheme AnimationUtils.CreateTransition(ContentPresenter.RenderTransformProperty) } }; - menuIndicatorIconPresenter.Content = indicatorIcon; + menuIndicatorIconPresenter.Child = indicatorIcon; + menuIndicatorIconPresenter.RegisterInNameScope(scope); return menuIndicatorIconPresenter; } - protected override Control BuildMenuItemContent(INameScope scope) + protected override Control BuildMenuItemContent(NavMenuItem navMenuItem, INameScope scope) { var rootLayout = new StackPanel() { Orientation = Orientation.Vertical }; - var headerContent = base.BuildMenuItemContent(scope); - + + var headerContent = base.BuildMenuItemContent(navMenuItem, scope); + TokenResourceBinder.CreateTokenBinding(headerContent, Control.MarginProperty, NavMenuTokenResourceKey.VerticalItemsPanelSpacing, BindingPriority.Template, (v) => { @@ -84,6 +87,21 @@ internal class InlineNavMenuItemTheme : BaseNavMenuItemTheme return rootLayout; } + protected override Grid BuildMenuItemInfoGrid(NavMenuItem navMenuItem, INameScope scope) + { + var infoGrid = base.BuildMenuItemInfoGrid(navMenuItem, scope); + var indentConverter = new MarginMultiplierConverter + { + Left = true, + Indent = navMenuItem.InlineItemIndentUnit + }; + CreateTemplateParentBinding(infoGrid, Grid.MarginProperty, + NavMenuItem.LevelProperty, BindingMode.OneWay, + indentConverter); + + return infoGrid; + } + protected override void BuildStyles() { base.BuildStyles(); @@ -100,7 +118,7 @@ internal class InlineNavMenuItemTheme : BaseNavMenuItemTheme var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconLayoutPart)); var transformOptions = new TransformOperations.Builder(1); transformOptions.AppendRotate(MathUtils.Deg2Rad(90)); - menuIndicatorStyle.Add(ContentPresenter.RenderTransformProperty, transformOptions.Build()); + menuIndicatorStyle.Add(Border.RenderTransformProperty, transformOptions.Build()); menuIndicatorStyle.Add(Visual.IsVisibleProperty, true); Add(menuIndicatorStyle); } @@ -109,7 +127,7 @@ internal class InlineNavMenuItemTheme : BaseNavMenuItemTheme var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconLayoutPart)); var transformOptions = new TransformOperations.Builder(1); transformOptions.AppendRotate(MathUtils.Deg2Rad(-90)); - menuIndicatorStyle.Add(ContentPresenter.RenderTransformProperty, transformOptions.Build()); + menuIndicatorStyle.Add(Border.RenderTransformProperty, transformOptions.Build()); openSubMenuStyle.Add(menuIndicatorStyle); } Add(openSubMenuStyle); @@ -121,40 +139,4 @@ internal class InlineNavMenuItemTheme : BaseNavMenuItemTheme } Add(emptySubMenuStyle); } - - // private void BuildAnimationStyle() - // { - // var closeSubMenuStyle = new Style(selector => selector.Nesting().Not(selector.Nesting().Class(StdPseudoClass.Open))); - // { - // var layoutTransformStyle = - // new Style(selector => selector.Nesting().Template().Name(ChildItemsLayoutTransformPart)); - // var slideDownInMotionConfig = MotionFactory.BuildSlideUpOutMotion(TimeSpan.FromMilliseconds(300), new QuadraticEaseIn(), - // FillMode.Forward); - // foreach (var animation in slideDownInMotionConfig.Animations) - // { - // layoutTransformStyle.Animations.Add(animation); - // } - // layoutTransformStyle.Add(LayoutTransformControl.RenderTransformOriginProperty, slideDownInMotionConfig.RenderTransformOrigin); - // layoutTransformStyle.Add(LayoutTransformControl.IsVisibleProperty, false); - // closeSubMenuStyle.Add(layoutTransformStyle); - // } - // Add(closeSubMenuStyle); - // - // var openSubMenuStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Open)); - // { - // var layoutTransformStyle = - // new Style(selector => selector.Nesting().Template().Name(ChildItemsLayoutTransformPart)); - // var slideDownInMotionConfig = MotionFactory.BuildSlideUpInMotion(TimeSpan.FromMilliseconds(300), new QuadraticEaseIn(), - // FillMode.Forward); - // foreach (var animation in slideDownInMotionConfig.Animations) - // { - // layoutTransformStyle.Animations.Add(animation); - // } - // - // layoutTransformStyle.Add(LayoutTransformControl.RenderTransformOriginProperty, slideDownInMotionConfig.RenderTransformOrigin); - // layoutTransformStyle.Add(LayoutTransformControl.IsVisibleProperty, true); - // openSubMenuStyle.Add(layoutTransformStyle); - // } - // Add(openSubMenuStyle); - // } } \ No newline at end of file diff --git a/src/AtomUI.Controls/NavMenu/NavMenu.cs b/src/AtomUI.Controls/NavMenu/NavMenu.cs index 0fcbf6f..48335ab 100644 --- a/src/AtomUI.Controls/NavMenu/NavMenu.cs +++ b/src/AtomUI.Controls/NavMenu/NavMenu.cs @@ -254,8 +254,26 @@ public class NavMenu : NavMenuBase } } - internal void UpdateSelectionFromItem(INavMenuItem? item) + internal void ClearSelection() { - UpdateSelectionFromEventSource(item); + foreach (var item in Items) + { + if (item is NavMenuItem navMenuItem) + { + ClearSelectionRecursively(navMenuItem); + } + } + } + + private void ClearSelectionRecursively(NavMenuItem item) + { + item.IsSelected = false; + foreach (var childItem in item.Items) + { + if (childItem is NavMenuItem navMenuItem) + { + ClearSelectionRecursively(navMenuItem); + } + } } } \ No newline at end of file diff --git a/src/AtomUI.Controls/NavMenu/NavMenuItem.cs b/src/AtomUI.Controls/NavMenu/NavMenuItem.cs index 22b95a5..3f528c1 100644 --- a/src/AtomUI.Controls/NavMenu/NavMenuItem.cs +++ b/src/AtomUI.Controls/NavMenu/NavMenuItem.cs @@ -1,6 +1,7 @@ using System.Windows.Input; using AtomUI.Controls.Utils; using AtomUI.Data; +using AtomUI.Icon; using AtomUI.Input; using AtomUI.Theme.Styling; using AtomUI.Utils; @@ -84,6 +85,13 @@ public class NavMenuItem : HeaderedSelectingItemsControl, /// public static readonly StyledProperty IsCheckedProperty = AvaloniaProperty.Register(nameof(IsChecked)); + + /// + /// Defines the property. + /// + public static readonly DirectProperty LevelProperty = + AvaloniaProperty.RegisterDirect( + nameof(Level), o => o.Level); /// /// Gets or sets the command associated with the menu item. @@ -171,10 +179,25 @@ public class NavMenuItem : HeaderedSelectingItemsControl, set => SetValue(IsCheckedProperty, value); } + private bool _hasSubMenu; /// /// Gets or sets a value that indicates whether the has a submenu. /// - public bool HasSubMenu => !Classes.Contains(StdPseudoClass.Empty); + public bool HasSubMenu + { + get => _hasSubMenu; + set => SetAndRaise(HasSubMenuProperty, ref _hasSubMenu, value); + } + + private int _level; + /// + /// Gets the level/indentation of the item. + /// + public int Level + { + get => _level; + private set => SetAndRaise(LevelProperty, ref _level, value); + } /// /// Gets a value that indicates whether the is a top-level main menu item. @@ -250,6 +273,16 @@ public class NavMenuItem : HeaderedSelectingItemsControl, o => o.OpenCloseMotionDuration, (o, v) => o.OpenCloseMotionDuration = v); + internal static readonly DirectProperty HasSubMenuProperty = + AvaloniaProperty.RegisterDirect(nameof(HasSubMenu), + o => o.HasSubMenu, + (o, v) => o.HasSubMenu = v); + + internal static readonly DirectProperty InlineItemIndentUnitProperty = + AvaloniaProperty.RegisterDirect(nameof(InlineItemIndentUnit), + o => o.InlineItemIndentUnit, + (o, v) => o.InlineItemIndentUnit = v); + internal double ActiveBarWidth { get => GetValue(ActiveBarWidthProperty); @@ -297,7 +330,14 @@ public class NavMenuItem : HeaderedSelectingItemsControl, set => SetAndRaise(ModeProperty, ref _mode, value); } + private double _inlineItemIndentUnit; + internal double InlineItemIndentUnit + { + get => _inlineItemIndentUnit; + set => SetAndRaise(InlineItemIndentUnitProperty, ref _inlineItemIndentUnit, value); + } + #endregion #region 公共事件定义 @@ -428,31 +468,6 @@ public class NavMenuItem : HeaderedSelectingItemsControl, /// public void Open() { - if (Mode == NavMenuMode.Inline) - { - if (_childItemsLayoutTransform is not null) - { - if (_animating) - { - return; - } - - _animating = true; - var slideDownInMotionConfig = MotionFactory.BuildSlideUpInMotion(_openCloseMotionDuration, new QuinticEaseOut(), - FillMode.Forward); - SetCurrentValue(IsSubMenuOpenProperty, true); - _childItemsLayoutTransform.RenderTransformOrigin = slideDownInMotionConfig.RenderTransformOrigin; - MotionInvoker.Invoke(_childItemsLayoutTransform, slideDownInMotionConfig, () => - { - _childItemsLayoutTransform.SetCurrentValue(IsVisibleProperty, true); - }, () => - { - _animating = false; - }); - return; - } - } - SetCurrentValue(IsSubMenuOpenProperty, true); } @@ -464,27 +479,6 @@ public class NavMenuItem : HeaderedSelectingItemsControl, /// public void Close() { - if (Mode == NavMenuMode.Inline) - { - if (_childItemsLayoutTransform is not null) - { - if (_animating) - { - return; - } - - _animating = true; - SetCurrentValue(IsSubMenuOpenProperty, false); - var slideDownOutMotionConfig = MotionFactory.BuildSlideUpOutMotion(_openCloseMotionDuration, new QuinticEaseOut(), - FillMode.Forward); - MotionInvoker.Invoke(_childItemsLayoutTransform, slideDownOutMotionConfig, null, () => - { - _childItemsLayoutTransform.SetCurrentValue(IsVisibleProperty, false); - _animating = false; - }); - return; - } - } SetCurrentValue(IsSubMenuOpenProperty, false); } @@ -528,6 +522,8 @@ public class NavMenuItem : HeaderedSelectingItemsControl, } base.OnAttachedToLogicalTree(e); + + Level = CalculateDistanceFromLogicalParent(this) - 1; (var command, var parameter) = (Command, CommandParameter); if (command is not null) @@ -545,6 +541,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl, } _isEmbeddedInMenu = parent?.FindLogicalAncestorOfType(true) != null; + TokenResourceBinder.CreateTokenBinding(this, InlineItemIndentUnitProperty, NavMenuTokenResourceKey.InlineItemIndentUnit); } /// @@ -590,7 +587,10 @@ public class NavMenuItem : HeaderedSelectingItemsControl, protected override void OnGotFocus(GotFocusEventArgs e) { base.OnGotFocus(e); - e.Handled = UpdateSelectionFromEventSource(e.Source, true); + if (Mode != NavMenuMode.Inline || !HasSubMenu) + { + e.Handled = UpdateSelectionFromEventSource(e.Source, true); + } } /// @@ -620,15 +620,19 @@ public class NavMenuItem : HeaderedSelectingItemsControl, protected virtual void OnSubmenuOpened(RoutedEventArgs e) { var menuItem = e.Source as NavMenuItem; - + if (menuItem != null && menuItem.Parent == this) { - foreach (var child in ((INavMenuItem)this).SubItems) + // TODO 我们在这里对模式做一个区分, Inline 暂时不互斥关闭,后面有时间看是否加一个互斥的标记 + if (Mode != NavMenuMode.Inline) { - if (child != menuItem && child.IsSubMenuOpen) + foreach (var child in ((INavMenuItem)this).SubItems) { - child.IsSubMenuOpen = false; - } + if (child != menuItem && child.IsSubMenuOpen) + { + child.IsSubMenuOpen = false; + } + } } } } @@ -819,6 +823,9 @@ public class NavMenuItem : HeaderedSelectingItemsControl, } else if (change.Property == ModeProperty) { SetupItemContainerTheme(true); + } else if (change.Property == ItemCountProperty) + { + HasSubMenu = ItemCount > 0; } if (change.Property == BoundsProperty || @@ -917,11 +924,23 @@ public class NavMenuItem : HeaderedSelectingItemsControl, private void IsSelectedChanged(AvaloniaPropertyChangedEventArgs e) { var parentMenu = Parent as NavMenu; - - if ((bool)e.NewValue! && (parentMenu is null || parentMenu.IsOpen)) + var isSelected = e.GetNewValue(); + if (isSelected && (parentMenu is null || parentMenu.IsOpen)) { Focus(); } + + if (Icon is not null && Icon is PathIcon menuIcon) + { + if (isSelected) + { + menuIcon.SetValue(PathIcon.IconModeProperty, IconMode.Selected); + } + else + { + menuIcon.SetValue(PathIcon.IconModeProperty, IconMode.Normal); + } + } } /// @@ -932,22 +951,87 @@ public class NavMenuItem : HeaderedSelectingItemsControl, { var value = (bool)e.NewValue!; - if (value) + if (Mode == NavMenuMode.Inline) { - foreach (var item in ItemsView.OfType()) + // 在这里我们有一个动画的效果 + if (value) { - item.TryUpdateCanExecute(); - } + foreach (var item in ItemsView.OfType()) + { + item.TryUpdateCanExecute(); + } - RaiseEvent(new RoutedEventArgs(SubmenuOpenedEvent)); - SetCurrentValue(IsSelectedProperty, true); - PseudoClasses.Add(StdPseudoClass.Open); + RaiseEvent(new RoutedEventArgs(SubmenuOpenedEvent)); + PseudoClasses.Add(StdPseudoClass.Open); + OpenInlineItem(); + } else + { + PseudoClasses.Remove(StdPseudoClass.Open); + CloseInlineItem(); + } } else { - CloseSubmenus(); - SelectedIndex = -1; - PseudoClasses.Remove(StdPseudoClass.Open); + if (value) + { + foreach (var item in ItemsView.OfType()) + { + item.TryUpdateCanExecute(); + } + RaiseEvent(new RoutedEventArgs(SubmenuOpenedEvent)); + SetCurrentValue(IsSelectedProperty, true); + PseudoClasses.Add(StdPseudoClass.Open); + } + else + { + CloseSubmenus(); + SelectedIndex = -1; + PseudoClasses.Remove(StdPseudoClass.Open); + } + } + } + + private void OpenInlineItem() + { + if (_childItemsLayoutTransform is not null) + { + if (_animating) + { + return; + } + + _animating = true; + var slideDownInMotionConfig = MotionFactory.BuildSlideUpInMotion(_openCloseMotionDuration, new QuinticEaseOut(), + FillMode.Forward); + _childItemsLayoutTransform.RenderTransformOrigin = slideDownInMotionConfig.RenderTransformOrigin; + MotionInvoker.Invoke(_childItemsLayoutTransform, slideDownInMotionConfig, () => + { + _childItemsLayoutTransform.SetCurrentValue(IsVisibleProperty, true); + }, () => + { + _animating = false; + }); + } + } + + private void CloseInlineItem() + { + if (_childItemsLayoutTransform is not null) + { + if (_animating) + { + return; + } + + _animating = true; + SetCurrentValue(IsSubMenuOpenProperty, false); + var slideDownOutMotionConfig = MotionFactory.BuildSlideUpOutMotion(_openCloseMotionDuration, new QuinticEaseOut(), + FillMode.Forward); + MotionInvoker.Invoke(_childItemsLayoutTransform, slideDownOutMotionConfig, null, () => + { + _childItemsLayoutTransform.SetCurrentValue(IsVisibleProperty, false); + _animating = false; + }); } } @@ -1038,4 +1122,29 @@ public class NavMenuItem : HeaderedSelectingItemsControl, BindUtils.RelayBind(this, ModeProperty, navMenuItem, ModeProperty); } } + + internal void SelectItemRecursively() + { + IsSelected = true; + if (!IsTopLevel) + { + if (Parent is NavMenuItem parent) + { + parent.SelectItemRecursively(); + } + } + } + + private static int CalculateDistanceFromLogicalParent(ILogical? logical, int @default = -1) where T : class + { + var result = 0; + + while (logical != null && !(logical is T)) + { + ++result; + logical = logical.LogicalParent; + } + + return logical != null ? result : @default; + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/NavMenu/NavMenuToken.cs b/src/AtomUI.Controls/NavMenu/NavMenuToken.cs index 51c4862..56f7c8e 100644 --- a/src/AtomUI.Controls/NavMenu/NavMenuToken.cs +++ b/src/AtomUI.Controls/NavMenu/NavMenuToken.cs @@ -215,6 +215,11 @@ internal class NavMenuToken : AbstractControlDesignToken /// public double ItemHeight { get; set; } + /// + /// 内联菜单项的缩进单位 + /// + public double InlineItemIndentUnit { get; set; } + /// /// 收起后的宽度 /// @@ -446,5 +451,7 @@ internal class NavMenuToken : AbstractControlDesignToken MenuPopupContentPadding = new Thickness(_globalToken.PaddingXXS, MenuPopupBorderRadius.TopLeft / 2); MenuPopupBoxShadows = _globalToken.BoxShadowsSecondary; VerticalItemsPanelSpacing = _globalToken.MarginXXS; + + InlineItemIndentUnit = ItemHeight / 2; } } \ No newline at end of file diff --git a/src/AtomUI.Controls/PathIcon/PathIcon.cs b/src/AtomUI.Controls/PathIcon/PathIcon.cs index 0cf8fb9..cea5997 100644 --- a/src/AtomUI.Controls/PathIcon/PathIcon.cs +++ b/src/AtomUI.Controls/PathIcon/PathIcon.cs @@ -206,6 +206,14 @@ public sealed class PathIcon : Control, ICustomHitTest BuildSourceRenderData(); } } + else if (change.Property == IsEnabledProperty) + { + // TODO 这个地方需要优化一点,是否需要保存老的,当状态为 Enabled 的时候进行还原 + if (!IsEnabled) + { + IconMode = IconMode.Disabled; + } + } else if (change.Property == NormalFilledBrushProperty || change.Property == ActiveFilledBrushProperty || change.Property == SelectedFilledBrushProperty ||