Complete the inline NavMenu

This commit is contained in:
polarboy 2024-09-24 15:35:58 +08:00
parent a21c5a8d7d
commit e98729e458
9 changed files with 341 additions and 167 deletions

View File

@ -187,28 +187,28 @@
<!-- </Border> -->
<!-- -->
<!-- </desktop:ShowCaseItem> -->
<desktop:ShowCaseItem
Title="Top Navigation"
Description="Horizontal top navigation menu.">
<atom:NavMenu>
<atom:NavMenuItem Header="Navigation One" Icon="{atom:IconProvider Kind=MailOutlined}" />
<atom:NavMenuItem Header="Navigation Two" Icon="{atom:IconProvider Kind=AppstoreOutlined}" IsEnabled="False"/>
<atom:NavMenuItem Header="Navigation Three - Submenu" Icon="{atom:IconProvider Kind=SettingOutlined}">
<atom:NavMenuItem Header="Item 1">
<atom:NavMenuItem Header="Option 1" />
<atom:NavMenuItem Header="Option 2" />
</atom:NavMenuItem>
<atom:NavMenuItem Header="Item 2">
<atom:NavMenuItem Header="Option 3" />
<atom:NavMenuItem Header="Option 4" />
</atom:NavMenuItem>
</atom:NavMenuItem>
<atom:NavMenuItem Header="Navigation Four"/>
</atom:NavMenu>
</desktop:ShowCaseItem>
<!-- -->
<!-- <desktop:ShowCaseItem -->
<!-- Title="Top Navigation" -->
<!-- Description="Horizontal top navigation menu."> -->
<!-- <atom:NavMenu> -->
<!-- <atom:NavMenuItem Header="Navigation One" Icon="{atom:IconProvider Kind=MailOutlined}" /> -->
<!-- <atom:NavMenuItem Header="Navigation Two" Icon="{atom:IconProvider Kind=AppstoreOutlined}" IsEnabled="False"/> -->
<!-- <atom:NavMenuItem Header="Navigation Three - Submenu" Icon="{atom:IconProvider Kind=SettingOutlined}"> -->
<!-- <atom:NavMenuItem Header="Item 1"> -->
<!-- <atom:NavMenuItem Header="Option 1" /> -->
<!-- <atom:NavMenuItem Header="Option 2" /> -->
<!-- </atom:NavMenuItem> -->
<!-- -->
<!-- <atom:NavMenuItem Header="Item 2"> -->
<!-- <atom:NavMenuItem Header="Option 3" /> -->
<!-- <atom:NavMenuItem Header="Option 4" /> -->
<!-- </atom:NavMenuItem> -->
<!-- </atom:NavMenuItem> -->
<!-- <atom:NavMenuItem Header="Navigation Four"/> -->
<!-- </atom:NavMenu> -->
<!-- </desktop:ShowCaseItem> -->
<!-- -->
<desktop:ShowCaseItem
Title="Vertical menu"
Description="Submenus open as pop-ups.">
@ -220,7 +220,7 @@
<atom:NavMenuItem Header="Option 1" />
<atom:NavMenuItem Header="Option 2" />
</atom:NavMenuItem>
<atom:NavMenuItem Header="Item 2">
<atom:NavMenuItem Header="Option 3" />
<atom:NavMenuItem Header="Option 4" />

View File

@ -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");

View File

@ -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<SolidColorBrushTransition>(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<PathIcon>().Class(StdPseudoClass.Disabled));
disabledIconStyle.Add(PathIcon.IconModeProperty, IconMode.Disabled);

View File

@ -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;
}

View File

@ -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<TransformOperationsTransition>(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);
// }
}

View File

@ -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);
}
}
}
}

View File

@ -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,
/// </summary>
public static readonly StyledProperty<bool> IsCheckedProperty =
AvaloniaProperty.Register<NavMenuItem, bool>(nameof(IsChecked));
/// <summary>
/// Defines the <see cref="Level"/> property.
/// </summary>
public static readonly DirectProperty<NavMenuItem, int> LevelProperty =
AvaloniaProperty.RegisterDirect<NavMenuItem, int>(
nameof(Level), o => o.Level);
/// <summary>
/// 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;
/// <summary>
/// Gets or sets a value that indicates whether the <see cref="NavMenuItem"/> has a submenu.
/// </summary>
public bool HasSubMenu => !Classes.Contains(StdPseudoClass.Empty);
public bool HasSubMenu
{
get => _hasSubMenu;
set => SetAndRaise(HasSubMenuProperty, ref _hasSubMenu, value);
}
private int _level;
/// <summary>
/// Gets the level/indentation of the item.
/// </summary>
public int Level
{
get => _level;
private set => SetAndRaise(LevelProperty, ref _level, value);
}
/// <summary>
/// Gets a value that indicates whether the <see cref="NavMenuItem"/> 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<NavMenuItem, bool> HasSubMenuProperty =
AvaloniaProperty.RegisterDirect<NavMenuItem, bool>(nameof(HasSubMenu),
o => o.HasSubMenu,
(o, v) => o.HasSubMenu = v);
internal static readonly DirectProperty<NavMenuItem, double> InlineItemIndentUnitProperty =
AvaloniaProperty.RegisterDirect<NavMenuItem, double>(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,
/// </remarks>
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,
/// </remarks>
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<NavMenu>(this) - 1;
(var command, var parameter) = (Command, CommandParameter);
if (command is not null)
@ -545,6 +541,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
}
_isEmbeddedInMenu = parent?.FindLogicalAncestorOfType<INavMenu>(true) != null;
TokenResourceBinder.CreateTokenBinding(this, InlineItemIndentUnitProperty, NavMenuTokenResourceKey.InlineItemIndentUnit);
}
/// <inheritdoc />
@ -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);
}
}
/// <inheritdoc/>
@ -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<bool>();
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);
}
}
}
/// <summary>
@ -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<NavMenuItem>())
// 在这里我们有一个动画的效果
if (value)
{
item.TryUpdateCanExecute();
}
foreach (var item in ItemsView.OfType<NavMenuItem>())
{
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<NavMenuItem>())
{
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<T>(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;
}
}

View File

@ -215,6 +215,11 @@ internal class NavMenuToken : AbstractControlDesignToken
/// </summary>
public double ItemHeight { get; set; }
/// <summary>
/// 内联菜单项的缩进单位
/// </summary>
public double InlineItemIndentUnit { get; set; }
/// <summary>
/// 收起后的宽度
/// </summary>
@ -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;
}
}

View File

@ -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 ||