Complete the expansion and closing of the inline navigation menu

This commit is contained in:
polarboy
2024-09-23 20:55:02 +08:00
parent 266ec18ab2
commit 9c00a966f4
14 changed files with 514 additions and 220 deletions
@@ -229,6 +229,27 @@
<atom:NavMenuItem Header="Navigation Four"/>
</atom:NavMenu>
</desktop:ShowCaseItem>
<desktop:ShowCaseItem
Title="Inline menu"
Description="Vertical menu with inline submenus.">
<atom:NavMenu Mode="Inline" Width="256" Margin="0, 0, 0, 20">
<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:ShowCasePanel>
</UserControl>
@@ -405,6 +405,7 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey ActiveBarHeight = new TokenResourceKey("NavMenu.ActiveBarHeight", "AtomUI.Token");
public static readonly TokenResourceKey ItemContentMargin = new TokenResourceKey("NavMenu.ItemContentMargin", "AtomUI.Token");
public static readonly TokenResourceKey ItemContentPadding = new TokenResourceKey("NavMenu.ItemContentPadding", "AtomUI.Token");
public static readonly TokenResourceKey VerticalItemsPanelSpacing = new TokenResourceKey("NavMenu.VerticalItemsPanelSpacing", "AtomUI.Token");
public static readonly TokenResourceKey ItemMargin = new TokenResourceKey("NavMenu.ItemMargin", "AtomUI.Token");
public static readonly TokenResourceKey HorizontalItemMargin = new TokenResourceKey("NavMenu.HorizontalItemMargin", "AtomUI.Token");
public static readonly TokenResourceKey HorizontalItemHoverBg = new TokenResourceKey("NavMenu.HorizontalItemHoverBg", "AtomUI.Token");
@@ -38,109 +38,114 @@ internal class BaseNavMenuItemTheme : BaseControlTheme
BuildInstanceStyles(item);
// 仅仅为了把 Popup 包进来,没有其他什么作用
var layoutWrapper = new Panel();
var container = new Border
{
Name = ItemDecoratorPart
};
var transitions = new Transitions();
transitions.Add(AnimationUtils.CreateTransition<SolidColorBrushTransition>(Border.BackgroundProperty));
container.Transitions = transitions;
var layout = new Grid
{
Name = MainContainerPart,
ColumnDefinitions =
{
new ColumnDefinition(GridLength.Auto)
{
SharedSizeGroup = ThemeConstants.IconPresenterSizeGroup
},
new ColumnDefinition(GridLength.Star),
new ColumnDefinition(GridLength.Auto)
{
SharedSizeGroup = ThemeConstants.InputGestureTextSizeGroup
},
new ColumnDefinition(GridLength.Auto)
{
SharedSizeGroup = ThemeConstants.MenuIndicatorIconSizeGroup
}
}
};
layout.RegisterInNameScope(scope);
var iconPresenter = new Viewbox
{
Name = ItemIconPresenterPart,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Stretch = Stretch.Uniform
};
Grid.SetColumn(iconPresenter, 0);
iconPresenter.RegisterInNameScope(scope);
CreateTemplateParentBinding(iconPresenter, Viewbox.ChildProperty, NavMenuItem.IconProperty);
TokenResourceBinder.CreateTokenBinding(iconPresenter, Layoutable.MarginProperty,
NavMenuTokenResourceKey.ItemMargin);
TokenResourceBinder.CreateGlobalTokenBinding(iconPresenter, Layoutable.WidthProperty,
NavMenuTokenResourceKey.ItemIconSize);
TokenResourceBinder.CreateGlobalTokenBinding(iconPresenter, Layoutable.HeightProperty,
NavMenuTokenResourceKey.ItemIconSize);
var itemTextPresenter = new ContentPresenter
{
Name = ItemTextPresenterPart,
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Center,
RecognizesAccessKey = true,
IsHitTestVisible = false
};
Grid.SetColumn(itemTextPresenter, 1);
TokenResourceBinder.CreateTokenBinding(itemTextPresenter, Layoutable.MarginProperty,
NavMenuTokenResourceKey.ItemMargin);
CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentProperty,
HeaderedSelectingItemsControl.HeaderProperty);
CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentTemplateProperty,
HeaderedSelectingItemsControl.HeaderTemplateProperty);
itemTextPresenter.RegisterInNameScope(scope);
var inputGestureText = new TextBlock
{
Name = InputGestureTextPart,
HorizontalAlignment = HorizontalAlignment.Right,
TextAlignment = TextAlignment.Right,
VerticalAlignment = VerticalAlignment.Center
};
Grid.SetColumn(inputGestureText, 2);
TokenResourceBinder.CreateTokenBinding(inputGestureText, Layoutable.MarginProperty,
NavMenuTokenResourceKey.ItemMargin);
CreateTemplateParentBinding(inputGestureText,
TextBlock.TextProperty,
NavMenuItem.InputGestureProperty,
BindingMode.Default,
NavMenuItem.KeyGestureConverter);
inputGestureText.RegisterInNameScope(scope);
var menuIndicatorIcon = BuildMenuIndicatorIcon();
Grid.SetColumn(menuIndicatorIcon, 3);
menuIndicatorIcon.RegisterInNameScope(scope);
layout.Children.Add(iconPresenter);
layout.Children.Add(itemTextPresenter);
layout.Children.Add(inputGestureText);
layout.Children.Add(menuIndicatorIcon);
BuildExtraItem(layout, scope);
container.Child = layout;
layoutWrapper.Children.Add(container);
var header = BuildMenuItemContent(scope);
BuildExtraItem(layoutWrapper, scope);
layoutWrapper.Children.Add(header);
return layoutWrapper;
});
}
protected virtual void BuildExtraItem(Grid containerLayout, INameScope scope)
protected virtual Control BuildMenuItemContent(INameScope scope)
{
var headerFrame = new Border
{
Name = ItemDecoratorPart
};
var transitions = new Transitions();
transitions.Add(AnimationUtils.CreateTransition<SolidColorBrushTransition>(Border.BackgroundProperty));
headerFrame.Transitions = transitions;
var layout = new Grid
{
Name = MainContainerPart,
ColumnDefinitions =
{
new ColumnDefinition(GridLength.Auto)
{
SharedSizeGroup = ThemeConstants.IconPresenterSizeGroup
},
new ColumnDefinition(GridLength.Star),
new ColumnDefinition(GridLength.Auto)
{
SharedSizeGroup = ThemeConstants.InputGestureTextSizeGroup
},
new ColumnDefinition(GridLength.Auto)
{
SharedSizeGroup = ThemeConstants.MenuIndicatorIconSizeGroup
}
}
};
layout.RegisterInNameScope(scope);
var iconPresenter = new Viewbox
{
Name = ItemIconPresenterPart,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Stretch = Stretch.Uniform
};
Grid.SetColumn(iconPresenter, 0);
iconPresenter.RegisterInNameScope(scope);
CreateTemplateParentBinding(iconPresenter, Viewbox.ChildProperty, NavMenuItem.IconProperty);
TokenResourceBinder.CreateTokenBinding(iconPresenter, Layoutable.MarginProperty,
NavMenuTokenResourceKey.ItemMargin);
TokenResourceBinder.CreateGlobalTokenBinding(iconPresenter, Layoutable.WidthProperty,
NavMenuTokenResourceKey.ItemIconSize);
TokenResourceBinder.CreateGlobalTokenBinding(iconPresenter, Layoutable.HeightProperty,
NavMenuTokenResourceKey.ItemIconSize);
var itemTextPresenter = new ContentPresenter
{
Name = ItemTextPresenterPart,
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Center,
RecognizesAccessKey = true,
IsHitTestVisible = false
};
Grid.SetColumn(itemTextPresenter, 1);
TokenResourceBinder.CreateTokenBinding(itemTextPresenter, Layoutable.MarginProperty,
NavMenuTokenResourceKey.ItemMargin);
CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentProperty,
HeaderedSelectingItemsControl.HeaderProperty);
CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentTemplateProperty,
HeaderedSelectingItemsControl.HeaderTemplateProperty);
itemTextPresenter.RegisterInNameScope(scope);
var inputGestureText = new TextBlock
{
Name = InputGestureTextPart,
HorizontalAlignment = HorizontalAlignment.Right,
TextAlignment = TextAlignment.Right,
VerticalAlignment = VerticalAlignment.Center
};
Grid.SetColumn(inputGestureText, 2);
TokenResourceBinder.CreateTokenBinding(inputGestureText, Layoutable.MarginProperty,
NavMenuTokenResourceKey.ItemMargin);
CreateTemplateParentBinding(inputGestureText,
TextBlock.TextProperty,
NavMenuItem.InputGestureProperty,
BindingMode.Default,
NavMenuItem.KeyGestureConverter);
inputGestureText.RegisterInNameScope(scope);
var menuIndicatorIcon = BuildMenuIndicatorIcon();
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;
}
protected virtual void BuildExtraItem(Panel layout, INameScope scope)
{
}
@@ -11,25 +11,22 @@ using Avalonia.VisualTree;
namespace AtomUI.Controls;
public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
internal class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
{
private readonly bool _isContextMenu;
private IDisposable? _inputManagerSubscription;
private IRenderRoot? _root;
public DefaultNavMenuInteractionHandler(bool isContextMenu)
: this(isContextMenu, AvaloniaLocator.Current.GetService<IInputManager>(), DefaultDelayRun)
public DefaultNavMenuInteractionHandler()
: this(AvaloniaLocator.Current.GetService<IInputManager>(), DefaultDelayRun)
{
}
public DefaultNavMenuInteractionHandler(
bool isContextMenu,
IInputManager? inputManager,
Action<Action, TimeSpan> delayRun)
{
delayRun = delayRun ?? throw new ArgumentNullException(nameof(delayRun));
_isContextMenu = isContextMenu;
InputManager = inputManager;
DelayRun = delayRun;
}
@@ -45,7 +42,7 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
public static TimeSpan MenuShowDelay { get; set; } = TimeSpan.FromMilliseconds(400);
protected internal virtual void GotFocus(object? sender, GotFocusEventArgs e)
protected virtual void GotFocus(object? sender, GotFocusEventArgs e)
{
var item = GetMenuItemCore(e.Source as Control);
@@ -55,7 +52,7 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
}
}
protected internal virtual void LostFocus(object? sender, RoutedEventArgs e)
protected virtual void LostFocus(object? sender, RoutedEventArgs e)
{
var item = GetMenuItemCore(e.Source as Control);
@@ -65,36 +62,14 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
}
}
protected internal virtual void KeyDown(object? sender, KeyEventArgs e)
protected virtual void KeyDown(object? sender, KeyEventArgs e)
{
KeyDown(GetMenuItemCore(e.Source as Control), e);
}
protected internal virtual void AccessKeyPressed(object? sender, RoutedEventArgs e)
protected virtual void PointerEntered(object? sender, RoutedEventArgs e)
{
var item = GetMenuItemCore(e.Source as Control);
if (item == null)
{
return;
}
if (item.HasSubMenu && item.IsEffectivelyEnabled)
{
Open(item, true);
}
else
{
Click(item);
}
e.Handled = true;
}
protected internal virtual void PointerEntered(object? sender, RoutedEventArgs e)
{
var item = GetMenuItemCore(e.Source as Control);
if (item?.Parent == null)
{
return;
@@ -136,7 +111,7 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
}
}
protected internal virtual void PointerMoved(object? sender, PointerEventArgs e)
protected virtual void PointerMoved(object? sender, PointerEventArgs e)
{
// HACK: #8179 needs to be addressed to correctly implement it in the PointerPressed method.
var item = GetMenuItemCore(e.Source as Control) as NavMenuItem;
@@ -157,7 +132,7 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
}
}
protected internal virtual void PointerExited(object? sender, RoutedEventArgs e)
protected virtual void PointerExited(object? sender, RoutedEventArgs e)
{
var item = GetMenuItemCore(e.Source as Control);
@@ -192,7 +167,7 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
}
}
protected internal virtual void PointerPressed(object? sender, PointerPressedEventArgs e)
protected virtual void PointerPressed(object? sender, PointerPressedEventArgs e)
{
var item = GetMenuItemCore(e.Source as Control);
@@ -211,11 +186,6 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
}
else
{
// if (item.IsTopLevel && item.Parent is IMainMenu mainMenu)
// {
// mainNavMenu.Open();
// }
Open(item, false);
}
@@ -223,7 +193,7 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
}
}
protected internal virtual void PointerReleased(object? sender, PointerReleasedEventArgs e)
protected virtual void PointerReleased(object? sender, PointerReleasedEventArgs e)
{
var item = GetMenuItemCore(e.Source as Control);
@@ -234,7 +204,7 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
}
}
protected internal virtual void MenuOpened(object? sender, RoutedEventArgs e)
protected virtual void MenuOpened(object? sender, RoutedEventArgs e)
{
if (e.Source is NavMenu)
{
@@ -242,7 +212,7 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
}
}
protected internal virtual void RawInput(RawInputEventArgs e)
protected virtual void RawInput(RawInputEventArgs e)
{
var mouse = e as RawPointerEventArgs;
@@ -252,7 +222,7 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
}
}
protected internal virtual void RootPointerPressed(object? sender, PointerPressedEventArgs e)
protected virtual void RootPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (NavMenu?.IsOpen == true)
{
@@ -263,7 +233,7 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
}
}
protected internal virtual void WindowDeactivated(object? sender, EventArgs e)
protected virtual void WindowDeactivated(object? sender, EventArgs e)
{
NavMenu?.Close();
}
@@ -283,10 +253,9 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
NavMenu.KeyDown += KeyDown;
NavMenu.PointerPressed += PointerPressed;
NavMenu.PointerReleased += PointerReleased;
// NavMenu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
NavMenu.AddHandler(MenuBase.OpenedEvent, MenuOpened);
NavMenu.AddHandler(MenuItem.PointerEnteredItemEvent, PointerEntered);
NavMenu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited);
NavMenu.AddHandler(NavMenuBase.OpenedEvent, MenuOpened);
NavMenu.AddHandler(NavMenuItem.PointerEnteredItemEvent, PointerEntered);
NavMenu.AddHandler(NavMenuItem.PointerExitedItemEvent, PointerExited);
NavMenu.AddHandler(InputElement.PointerMovedEvent, PointerMoved);
_root = NavMenu.VisualRoot;
@@ -302,7 +271,9 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
}
if (_root is TopLevel tl && tl.PlatformImpl is ITopLevelImpl pimpl)
{
pimpl.LostFocus += TopLevelLostPlatformFocus;
}
_inputManagerSubscription = InputManager?.Process.Subscribe(RawInput);
}
@@ -319,10 +290,9 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
NavMenu.KeyDown -= KeyDown;
NavMenu.PointerPressed -= PointerPressed;
NavMenu.PointerReleased -= PointerReleased;
// NavMenu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
NavMenu.RemoveHandler(MenuBase.OpenedEvent, MenuOpened);
NavMenu.RemoveHandler(MenuItem.PointerEnteredItemEvent, PointerEntered);
NavMenu.RemoveHandler(MenuItem.PointerExitedItemEvent, PointerExited);
NavMenu.RemoveHandler(NavMenuBase.OpenedEvent, MenuOpened);
NavMenu.RemoveHandler(NavMenuItem.PointerEnteredItemEvent, PointerEntered);
NavMenu.RemoveHandler(NavMenuItem.PointerExitedItemEvent, PointerExited);
NavMenu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved);
if (_root is InputElement inputRoot)
@@ -483,14 +453,7 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
if (direction?.IsDirectional() == true)
{
if (item == null && _isContextMenu)
{
if (NavMenu!.MoveSelection(direction.Value, true) == true)
{
e.Handled = true;
}
}
else if (item?.Parent?.MoveSelection(direction.Value, true) == true)
if (item?.Parent?.MoveSelection(direction.Value, true) == true)
{
// If the the parent is an IMenu which successfully moved its selection,
// and the current navMenu is open then close the current navMenu and open the
@@ -557,9 +520,15 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
while (true)
{
if (item == null)
{
return null;
}
if (item is INavMenuItem menuItem)
{
return menuItem;
}
item = item.Parent;
}
}
@@ -573,5 +542,4 @@ public class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
{
DispatcherTimer.RunOnce(action, timeSpan);
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ internal interface INavMenu : INavMenuElement, IInputElement
/// <summary>
/// Gets the menu interaction handler.
/// </summary>
INavMenuInteractionHandler InteractionHandler { get; }
INavMenuInteractionHandler? InteractionHandler { get; }
/// <summary>
/// Gets a value indicating whether the menu is open.
@@ -0,0 +1,86 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
namespace AtomUI.Controls;
internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler
{
internal INavMenu? NavMenu { get; private set; }
public void Attach(NavMenuBase navMenu) => AttachCore(navMenu);
public void Detach(NavMenuBase navMenu) => DetachCore(navMenu);
internal void AttachCore(INavMenu navMenu)
{
if (NavMenu != null)
{
throw new NotSupportedException("InlineNavMenuInteractionHandler is already attached.");
}
NavMenu = navMenu;
NavMenu.PointerPressed += PointerPressed;
}
internal void DetachCore(INavMenu navMenu)
{
if (NavMenu != navMenu)
{
throw new NotSupportedException("InlineNavMenuInteractionHandler is not attached to the navMenu.");
}
NavMenu.PointerPressed -= PointerPressed;
NavMenu = null;
}
protected virtual void PointerPressed(object? sender, PointerPressedEventArgs e)
{
var item = GetMenuItemCore(e.Source as Control);
if (sender is Visual visual &&
e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{
if (item?.HasSubMenu == true)
{
if (item.IsSubMenuOpen)
{
item.Close();
}
else
{
Open(item);
}
}
else
{
if (NavMenu is NavMenu navMenu)
{
navMenu.UpdateSelectionFromItem(item);
}
}
e.Handled = true;
}
}
internal void Open(INavMenuItem item)
{
item.Open();
}
internal static INavMenuItem? GetMenuItemCore(StyledElement? item)
{
while (true)
{
if (item == null)
{
return null;
}
if (item is INavMenuItem menuItem)
{
return menuItem;
}
item = item.Parent;
}
}
}
@@ -1,13 +1,29 @@
using AtomUI.Theme.Styling;
using AtomUI.Controls.Utils;
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.Transformation;
using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class InlineNavMenuItemTheme : VerticalNavMenuItemTheme
internal class InlineNavMenuItemTheme : BaseNavMenuItemTheme
{
public new const string ID = "InlineNavMenuItem";
public const string MenuIndicatorIconLayoutPart = "PART_MenuIndicatorIconLayout";
public const string ChildItemsPresenterPart = "PART_ChildItemsPresenter";
public const string ChildItemsLayoutTransformPart = "PART_ChildItemsLayoutTransform";
public InlineNavMenuItemTheme() : base(typeof(NavMenuItem))
{
@@ -21,11 +37,128 @@ internal class InlineNavMenuItemTheme : VerticalNavMenuItemTheme
protected override Control BuildMenuIndicatorIcon()
{
var indicatorIcon = base.BuildMenuIndicatorIcon();
var layoutTransformControl = new LayoutTransformControl()
var menuIndicatorIconPresenter = new ContentPresenter()
{
Name = MenuIndicatorIconLayoutPart
Name = MenuIndicatorIconLayoutPart,
Transitions = new Transitions()
{
AnimationUtils.CreateTransition<TransformOperationsTransition>(ContentPresenter.RenderTransformProperty)
}
};
layoutTransformControl.Child = indicatorIcon;
return layoutTransformControl;
menuIndicatorIconPresenter.Content = indicatorIcon;
return menuIndicatorIconPresenter;
}
protected override Control BuildMenuItemContent(INameScope scope)
{
var rootLayout = new StackPanel()
{
Orientation = Orientation.Vertical
};
var headerContent = base.BuildMenuItemContent(scope);
TokenResourceBinder.CreateTokenBinding(headerContent, Control.MarginProperty, NavMenuTokenResourceKey.VerticalItemsPanelSpacing, BindingPriority.Template,
(v) =>
{
if (v is double dval)
{
return new Thickness(0, 0, 0, dval);
}
return new Thickness();
});
var childItemsLayoutTransform = new LayoutTransformControl()
{
Name = ChildItemsLayoutTransformPart,
};
var itemsPresenter = new ItemsPresenter
{
Name = ChildItemsPresenterPart,
Focusable = false,
};
itemsPresenter.RegisterInNameScope(scope);
CreateTemplateParentBinding(itemsPresenter, ItemsPresenter.ItemsPanelProperty, NavMenuItem.ItemsPanelProperty);
childItemsLayoutTransform.Child = itemsPresenter;
CreateTemplateParentBinding(itemsPresenter, Visual.IsVisibleProperty, NavMenuItem.IsSubMenuOpenProperty);
rootLayout.Children.Add(headerContent);
rootLayout.Children.Add(childItemsLayoutTransform);
return rootLayout;
}
protected override void BuildStyles()
{
base.BuildStyles();
var childItemsPanelStyle = new Style(selector => selector.Nesting().Template().Name(ChildItemsPresenterPart).Child().OfType<StackPanel>());
childItemsPanelStyle.Add(StackPanel.SpacingProperty, NavMenuTokenResourceKey.VerticalItemsPanelSpacing);
Add(childItemsPanelStyle);
BuildMenuIndicatorStyle();
}
private void BuildMenuIndicatorStyle()
{
{
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(Visual.IsVisibleProperty, true);
Add(menuIndicatorStyle);
}
var openSubMenuStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Open));
{
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());
openSubMenuStyle.Add(menuIndicatorStyle);
}
Add(openSubMenuStyle);
var emptySubMenuStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Empty));
{
var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconLayoutPart));
menuIndicatorStyle.Add(Visual.IsVisibleProperty, false);
emptySubMenuStyle.Add(menuIndicatorStyle);
}
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);
// }
}
+41 -14
View File
@@ -101,16 +101,6 @@ public class NavMenu : NavMenuBase
UpdatePseudoClasses();
}
/// <summary>
/// Initializes a new instance of the <see cref="NavMenu"/> class.
/// </summary>
/// <param name="interactionHandler">The menu interaction handler.</param>
public NavMenu(INavMenuInteractionHandler interactionHandler)
: base(interactionHandler)
{
UpdatePseudoClasses();
}
public override void Close()
{
if (!IsOpen)
@@ -158,6 +148,7 @@ public class NavMenu : NavMenuBase
if (change.Property == ModeProperty)
{
SetupItemContainerTheme(true);
SetupInteractionHandler(true);
}
UpdatePseudoClasses();
}
@@ -173,10 +164,14 @@ public class NavMenu : NavMenuBase
element.ClearValue(ItemContainerThemeProperty);
}
if (element is NavMenuItem navMenuItem && Mode == NavMenuMode.Horizontal)
if (element is NavMenuItem navMenuItem)
{
BindUtils.RelayBind(this, ActiveBarHeightProperty, navMenuItem, NavMenuItem.ActiveBarHeightProperty);
BindUtils.RelayBind(this, ActiveBarWidthProperty, navMenuItem, NavMenuItem.ActiveBarWidthProperty);
if (Mode == NavMenuMode.Horizontal)
{
BindUtils.RelayBind(this, ActiveBarHeightProperty, navMenuItem, NavMenuItem.ActiveBarHeightProperty);
BindUtils.RelayBind(this, ActiveBarWidthProperty, navMenuItem, NavMenuItem.ActiveBarWidthProperty);
}
BindUtils.RelayBind(this, ModeProperty, navMenuItem, NavMenuItem.ModeProperty);
}
}
@@ -193,16 +188,23 @@ public class NavMenu : NavMenuBase
{
base.OnAttachedToLogicalTree(e);
SetupItemContainerTheme();
SetupInteractionHandler();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
InteractionHandler?.Attach(this);
TokenResourceBinder.CreateGlobalTokenBinding(this, HorizontalBorderThicknessProperty, GlobalTokenResourceKey.LineWidth,
BindingPriority.Template,
new RenderScaleAwareDoubleConfigure(this));
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
InteractionHandler?.Detach(this);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
@@ -211,6 +213,26 @@ public class NavMenu : NavMenuBase
TokenResourceBinder.CreateTokenBinding(this, ActiveBarHeightProperty, NavMenuTokenResourceKey.ActiveBarHeight);
}
private void SetupInteractionHandler(bool needMount = false)
{
if (needMount)
{
InteractionHandler?.Detach(this);
}
if (Mode == NavMenuMode.Inline)
{
InteractionHandler = new InlineNavMenuInteractionHandler();
}
else
{
InteractionHandler = new DefaultNavMenuInteractionHandler();
}
if (needMount)
{
InteractionHandler?.Attach(this);
}
}
private void SetupItemContainerTheme(bool force = false)
{
if (ItemContainerTheme is null || force)
@@ -231,4 +253,9 @@ public class NavMenu : NavMenuBase
TokenResourceBinder.CreateGlobalResourceBinding(this, ItemContainerThemeProperty, resourceKey);
}
}
internal void UpdateSelectionFromItem(INavMenuItem? item)
{
UpdateSelectionFromEventSource(item);
}
}
+2 -26
View File
@@ -67,7 +67,7 @@ public abstract class NavMenuBase : SelectingItemsControl, IFocusScope, INavMenu
#endregion
INavMenuInteractionHandler INavMenu.InteractionHandler => InteractionHandler;
INavMenuInteractionHandler? INavMenu.InteractionHandler => InteractionHandler;
IRenderRoot? INavMenu.VisualRoot => VisualRoot;
@@ -87,17 +87,7 @@ public abstract class NavMenuBase : SelectingItemsControl, IFocusScope, INavMenu
/// <summary>
/// Gets the interaction handler for the menu.
/// </summary>
protected internal INavMenuInteractionHandler InteractionHandler { get; }
protected NavMenuBase()
{
InteractionHandler = new DefaultNavMenuInteractionHandler(false);
}
protected NavMenuBase(INavMenuInteractionHandler interactionHandler)
{
InteractionHandler = interactionHandler ?? throw new ArgumentNullException(nameof(interactionHandler));
}
protected internal INavMenuInteractionHandler? InteractionHandler { get; protected set; }
/// <summary>
/// Closes the menu.
@@ -135,20 +125,6 @@ public abstract class NavMenuBase : SelectingItemsControl, IFocusScope, INavMenu
// Don't handle here: let the interaction handler handle it.
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
InteractionHandler.Attach(this);
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
InteractionHandler.Detach(this);
}
/// <summary>
/// Called when a submenu opens somewhere in the menu.
/// </summary>
@@ -236,6 +236,11 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
o => o.PopupMinWidth,
(o, v) => o.PopupMinWidth = v);
internal static readonly DirectProperty<NavMenuItem, NavMenuMode> ModeProperty =
AvaloniaProperty.RegisterDirect<NavMenuItem, NavMenuMode>(nameof(Mode),
o => o.Mode,
(o, v) => o.Mode = v);
public double ActiveBarWidth
{
get => GetValue(ActiveBarWidthProperty);
@@ -268,6 +273,13 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
get => _popupMinWidth;
set => SetAndRaise(PopupMinWidthProperty, ref _popupMinWidth, value);
}
private NavMenuMode _mode;
public NavMenuMode Mode
{
get => _mode;
set => SetAndRaise(ModeProperty, ref _mode, value);
}
#endregion
@@ -371,6 +383,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
private KeyGesture? _hotkey;
private bool _isEmbeddedInMenu;
private Border? _horizontalFrame;
private IDisposable? _itemContainerThemeDisposable;
static NavMenuItem()
{
@@ -721,6 +734,9 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
else if (change.Property == IconProperty)
{
SetupItemIcon();
} else if (change.Property == ModeProperty)
{
SetupItemContainerTheme(true);
}
if (change.Property == BoundsProperty ||
@@ -728,6 +744,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
{
SetupEffectivePopupMinWidth();
}
}
private void SetupItemIcon()
@@ -918,4 +935,16 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
return true;
}
private void SetupItemContainerTheme(bool force = false)
{
if (ItemContainerTheme is null || force)
{
_itemContainerThemeDisposable?.Dispose();
if (Mode == NavMenuMode.Inline)
{
_itemContainerThemeDisposable = TokenResourceBinder.CreateGlobalResourceBinding(this, ItemContainerThemeProperty, InlineNavMenuItemTheme.ID);
}
}
}
}
@@ -22,11 +22,11 @@ internal class NavMenuItemTheme : BaseNavMenuItemTheme
{
}
protected override void BuildExtraItem(Grid containerLayout, INameScope scope)
protected override void BuildExtraItem(Panel layout, INameScope scope)
{
var popup = CreateMenuPopup();
popup.RegisterInNameScope(scope);
containerLayout.Children.Add(popup);
layout.Children.Add(popup);
}
protected Popup CreateMenuPopup()
+10 -4
View File
@@ -185,6 +185,11 @@ internal class NavMenuToken : AbstractControlDesignToken
/// </summary>
public Thickness ItemContentPadding { get; set; }
/// <summary>
/// 垂直面板的元素间距
/// </summary>
public double VerticalItemsPanelSpacing { get; set; }
/// <summary>
/// 菜单项内部元素边距
/// </summary>
@@ -436,9 +441,10 @@ internal class NavMenuToken : AbstractControlDesignToken
MenuPopupMaxHeight = ItemHeight * 30;
TopLevelItemPopupMarginToAnchor = _globalToken.MarginXXS;
MenuPopupBg = _globalToken.ColorBgElevated;
MenuPopupBorderRadius = _globalToken.BorderRadiusLG;
MenuPopupContentPadding = new Thickness(_globalToken.PaddingXXS, MenuPopupBorderRadius.TopLeft / 2);
MenuPopupBoxShadows = _globalToken.BoxShadowsSecondary;
MenuPopupBg = _globalToken.ColorBgElevated;
MenuPopupBorderRadius = _globalToken.BorderRadiusLG;
MenuPopupContentPadding = new Thickness(_globalToken.PaddingXXS, MenuPopupBorderRadius.TopLeft / 2);
MenuPopupBoxShadows = _globalToken.BoxShadowsSecondary;
VerticalItemsPanelSpacing = _globalToken.MarginXXS;
}
}
@@ -175,7 +175,7 @@ internal class TopLevelHorizontalNavMenuItemTheme : BaseControlTheme
TokenResourceBinder.CreateTokenBinding(popup, Popup.MaskShadowsProperty,
MenuTokenResourceKey.MenuPopupBoxShadows);
CreateTemplateParentBinding(popup, Avalonia.Controls.Primitives.Popup.IsOpenProperty,
CreateTemplateParentBinding(popup, Popup.IsOpenProperty,
NavMenuItem.IsSubMenuOpenProperty, BindingMode.TwoWay);
return popup;
@@ -36,11 +36,32 @@ public static partial class MotionFactory
var scaleYSetter = new Setter
{
Property = ScaleTransform.ScaleYProperty,
Value = 0.8
Value = 0.1
};
startFrame.Setters.Add(scaleYSetter);
}
animation.Children.Add(startFrame);
var middleFrame = new KeyFrame
{
Cue = new Cue(0.9)
};
{
var opacitySetter = new Setter
{
Property = Visual.OpacityProperty,
Value = 0.2
};
middleFrame.Setters.Add(opacitySetter);
var scaleYSetter = new Setter
{
Property = ScaleTransform.ScaleYProperty,
Value = 0.9
};
middleFrame.Setters.Add(scaleYSetter);
}
animation.Children.Add(middleFrame);
var endFrame = new KeyFrame
{
@@ -100,6 +121,27 @@ public static partial class MotionFactory
startFrame.Setters.Add(scaleYSetter);
}
animation.Children.Add(startFrame);
var middleFrame = new KeyFrame
{
Cue = new Cue(0.9)
};
{
var opacitySetter = new Setter
{
Property = Visual.OpacityProperty,
Value = 0.2
};
middleFrame.Setters.Add(opacitySetter);
var scaleYSetter = new Setter
{
Property = ScaleTransform.ScaleYProperty,
Value = 0.9
};
middleFrame.Setters.Add(scaleYSetter);
}
animation.Children.Add(middleFrame);
var endFrame = new KeyFrame
{
@@ -115,7 +157,7 @@ public static partial class MotionFactory
var scaleYSetter = new Setter
{
Property = ScaleTransform.ScaleYProperty,
Value = 0.8
Value = 0.0
};
endFrame.Setters.Add(scaleYSetter);
}
@@ -154,7 +196,7 @@ public static partial class MotionFactory
var scaleYSetter = new Setter
{
Property = ScaleTransform.ScaleYProperty,
Value = 0.8
Value = 0.1
};
startFrame.Setters.Add(scaleYSetter);
}
@@ -168,7 +210,7 @@ public static partial class MotionFactory
var opacitySetter = new Setter
{
Property = Visual.OpacityProperty,
Value = 1.0
Value = 0.2
};
endFrame.Setters.Add(opacitySetter);
var scaleYSetter = new Setter