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 c0f26a67d..36c9eb29d 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 @@ -370,12 +370,20 @@ namespace AtomUI.Theme.Styling public static class NavMenuTokenResourceKey { - public static readonly TokenResourceKey PopupMinWidth = new TokenResourceKey("NavMenu.PopupMinWidth", "AtomUI.Token"); + public static readonly TokenResourceKey MenuPopupBorderRadius = new TokenResourceKey("NavMenu.MenuPopupBorderRadius", "AtomUI.Token"); + public static readonly TokenResourceKey MenuPopupBoxShadows = new TokenResourceKey("NavMenu.MenuPopupBoxShadows", "AtomUI.Token"); + public static readonly TokenResourceKey MenuPopupContentPadding = new TokenResourceKey("NavMenu.MenuPopupContentPadding", "AtomUI.Token"); + public static readonly TokenResourceKey MenuPopupMinWidth = new TokenResourceKey("NavMenu.MenuPopupMinWidth", "AtomUI.Token"); + public static readonly TokenResourceKey MenuPopupMaxWidth = new TokenResourceKey("NavMenu.MenuPopupMaxWidth", "AtomUI.Token"); + public static readonly TokenResourceKey MenuPopupMinHeight = new TokenResourceKey("NavMenu.MenuPopupMinHeight", "AtomUI.Token"); + public static readonly TokenResourceKey MenuPopupMaxHeight = new TokenResourceKey("NavMenu.MenuPopupMaxHeight", "AtomUI.Token"); + public static readonly TokenResourceKey TopLevelItemPopupMarginToAnchor = new TokenResourceKey("NavMenu.TopLevelItemPopupMarginToAnchor", "AtomUI.Token"); public static readonly TokenResourceKey GroupTitleColor = new TokenResourceKey("NavMenu.GroupTitleColor", "AtomUI.Token"); public static readonly TokenResourceKey GroupTitleLineHeight = new TokenResourceKey("NavMenu.GroupTitleLineHeight", "AtomUI.Token"); public static readonly TokenResourceKey GroupTitleFontSize = new TokenResourceKey("NavMenu.GroupTitleFontSize", "AtomUI.Token"); public static readonly TokenResourceKey ItemBorderRadius = new TokenResourceKey("NavMenu.ItemBorderRadius", "AtomUI.Token"); public static readonly TokenResourceKey SubMenuItemBorderRadius = new TokenResourceKey("NavMenu.SubMenuItemBorderRadius", "AtomUI.Token"); + public static readonly TokenResourceKey KeyGestureColor = new TokenResourceKey("NavMenu.KeyGestureColor", "AtomUI.Token"); public static readonly TokenResourceKey ItemColor = new TokenResourceKey("NavMenu.ItemColor", "AtomUI.Token"); public static readonly TokenResourceKey ItemHoverColor = new TokenResourceKey("NavMenu.ItemHoverColor", "AtomUI.Token"); public static readonly TokenResourceKey HorizontalItemHoverColor = new TokenResourceKey("NavMenu.HorizontalItemHoverColor", "AtomUI.Token"); @@ -396,19 +404,20 @@ namespace AtomUI.Theme.Styling public static readonly TokenResourceKey HorizontalItemSelectedBg = new TokenResourceKey("NavMenu.HorizontalItemSelectedBg", "AtomUI.Token"); public static readonly TokenResourceKey ActiveBarWidth = new TokenResourceKey("NavMenu.ActiveBarWidth", "AtomUI.Token"); 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 ItemMargin = new TokenResourceKey("NavMenu.ItemMargin", "AtomUI.Token"); public static readonly TokenResourceKey HorizontalItemMargin = new TokenResourceKey("NavMenu.HorizontalItemMargin", "AtomUI.Token"); - public static readonly TokenResourceKey ItemPadding = new TokenResourceKey("NavMenu.ItemPadding", "AtomUI.Token"); 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 CollapsedWidth = new TokenResourceKey("NavMenu.CollapsedWidth", "AtomUI.Token"); - public static readonly TokenResourceKey PopupBg = new TokenResourceKey("NavMenu.PopupBg", "AtomUI.Token"); + public static readonly TokenResourceKey MenuPopupBg = new TokenResourceKey("NavMenu.MenuPopupBg", "AtomUI.Token"); public static readonly TokenResourceKey HorizontalLineHeight = new TokenResourceKey("NavMenu.HorizontalLineHeight", "AtomUI.Token"); public static readonly TokenResourceKey IconMargin = new TokenResourceKey("NavMenu.IconMargin", "AtomUI.Token"); public static readonly TokenResourceKey IconSize = new TokenResourceKey("NavMenu.IconSize", "AtomUI.Token"); public static readonly TokenResourceKey CollapsedIconSize = new TokenResourceKey("NavMenu.CollapsedIconSize", "AtomUI.Token"); - public static readonly TokenResourceKey DarkPopupBg = new TokenResourceKey("NavMenu.DarkPopupBg", "AtomUI.Token"); + public static readonly TokenResourceKey DarkMenuPopupBg = new TokenResourceKey("NavMenu.DarkMenuPopupBg", "AtomUI.Token"); public static readonly TokenResourceKey DarkItemColor = new TokenResourceKey("NavMenu.DarkItemColor", "AtomUI.Token"); public static readonly TokenResourceKey DarkDangerItemColor = new TokenResourceKey("NavMenu.DarkDangerItemColor", "AtomUI.Token"); public static readonly TokenResourceKey DarkItemBg = new TokenResourceKey("NavMenu.DarkItemBg", "AtomUI.Token"); diff --git a/src/AtomUI.Controls/NavMenu/NavMenuItem.cs b/src/AtomUI.Controls/NavMenu/NavMenuItem.cs index 139741524..2a5550d8c 100644 --- a/src/AtomUI.Controls/NavMenu/NavMenuItem.cs +++ b/src/AtomUI.Controls/NavMenu/NavMenuItem.cs @@ -4,6 +4,7 @@ using AtomUI.Theme.Styling; using AtomUI.Utils; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Converters; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; @@ -353,6 +354,8 @@ public class NavMenuItem : HeaderedSelectingItemsControl, private EventHandler CanExecuteChangedHandler => _canExecuteChangeHandler ??= new(CanExecuteChanged); #endregion + + internal static PlatformKeyGestureConverter KeyGestureConverter = new(); /// /// The default value for the property. @@ -563,7 +566,8 @@ public class NavMenuItem : HeaderedSelectingItemsControl, } _horizontalFrame = e.NameScope.Find(TopLevelHorizontalNavMenuItemTheme.FramePart); - TokenResourceBinder.CreateTokenBinding(this, PopupMinWidthProperty, NavMenuTokenResourceKey.PopupMinWidth); + TokenResourceBinder.CreateTokenBinding(this, PopupMinWidthProperty, NavMenuTokenResourceKey.MenuPopupMinWidth); + SetupItemIcon(); } protected override void UpdateDataValidation( @@ -712,6 +716,10 @@ public class NavMenuItem : HeaderedSelectingItemsControl, { SetupHorizontalEffectiveIndicatorWidth(); } + else if (change.Property == IconProperty) + { + SetupItemIcon(); + } if (change.Property == BoundsProperty || change.Property == PopupMinWidthProperty) @@ -720,6 +728,14 @@ public class NavMenuItem : HeaderedSelectingItemsControl, } } + private void SetupItemIcon() + { + if (Icon is not null && Icon is PathIcon menuItemIcon) + { + menuItemIcon.Name = ThemeConstants.ItemIconPart; + } + } + private void SetupHorizontalEffectiveIndicatorWidth() { var width = Bounds.Width; diff --git a/src/AtomUI.Controls/NavMenu/NavMenuItemTheme.cs b/src/AtomUI.Controls/NavMenu/NavMenuItemTheme.cs index a72d38530..fa4c02f86 100644 --- a/src/AtomUI.Controls/NavMenu/NavMenuItemTheme.cs +++ b/src/AtomUI.Controls/NavMenu/NavMenuItemTheme.cs @@ -1,21 +1,298 @@ -using AtomUI.Theme; +using AtomUI.Icon; +using AtomUI.Media; +using AtomUI.Theme; using AtomUI.Theme.Styling; +using AtomUI.Theme.Utils; +using AtomUI.Utils; +using Avalonia; +using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; -using Avalonia.Input; +using Avalonia.Data; using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Styling; namespace AtomUI.Controls; [ControlThemeProvider] internal class NavMenuItemTheme : BaseControlTheme { + public const string ItemDecoratorPart = "PART_ItemDecorator"; + public const string MainContainerPart = "PART_MainContainer"; + public const string ItemIconPresenterPart = "PART_ItemIconPresenter"; + public const string ItemTextPresenterPart = "PART_ItemTextPresenter"; + public const string InputGestureTextPart = "PART_InputGestureText"; + public const string MenuIndicatorIconPart = "PART_MenuIndicatorIcon"; + public const string PopupPart = "PART_Popup"; public const string ItemsPresenterPart = "PART_ItemsPresenter"; public NavMenuItemTheme() : base(typeof(NavMenuItem)) { } + + protected override IControlTemplate BuildControlTemplate() + { + return new FuncControlTemplate((item, scope) => + { + BuildInstanceStyles(item); + // 仅仅为了把 Popup 包进来,没有其他什么作用 + var layoutWrapper = new Panel(); + var container = new Border + { + Name = ItemDecoratorPart + }; + + var transitions = new Transitions(); + transitions.Add(AnimationUtils.CreateTransition(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 = new PathIcon + { + Name = MenuIndicatorIconPart, + HorizontalAlignment = HorizontalAlignment.Right, + VerticalAlignment = VerticalAlignment.Center, + Kind = "RightOutlined" + }; + TokenResourceBinder.CreateGlobalTokenBinding(menuIndicatorIcon, Layoutable.WidthProperty, + GlobalTokenResourceKey.IconSizeXS); + TokenResourceBinder.CreateGlobalTokenBinding(menuIndicatorIcon, Layoutable.HeightProperty, + GlobalTokenResourceKey.IconSizeXS); + Grid.SetColumn(menuIndicatorIcon, 3); + menuIndicatorIcon.RegisterInNameScope(scope); + + var popup = CreateMenuPopup(); + popup.RegisterInNameScope(scope); + + layout.Children.Add(iconPresenter); + layout.Children.Add(itemTextPresenter); + layout.Children.Add(inputGestureText); + layout.Children.Add(menuIndicatorIcon); + layout.Children.Add(popup); + + container.Child = layout; + layoutWrapper.Children.Add(container); + return layoutWrapper; + }); + } + + private Popup CreateMenuPopup() + { + var popup = new Popup + { + Name = PopupPart, + WindowManagerAddShadowHint = false, + IsLightDismissEnabled = false, + Placement = PlacementMode.RightEdgeAlignedTop + }; + + var border = new Border(); + TokenResourceBinder.CreateTokenBinding(border, Border.BackgroundProperty, + GlobalTokenResourceKey.ColorBgContainer); + TokenResourceBinder.CreateTokenBinding(border, Border.CornerRadiusProperty, + NavMenuTokenResourceKey.MenuPopupBorderRadius); + TokenResourceBinder.CreateTokenBinding(border, Layoutable.MinWidthProperty, + NavMenuTokenResourceKey.MenuPopupMinWidth); + TokenResourceBinder.CreateTokenBinding(border, Layoutable.MaxWidthProperty, + NavMenuTokenResourceKey.MenuPopupMaxWidth); + TokenResourceBinder.CreateTokenBinding(border, Layoutable.MinHeightProperty, + NavMenuTokenResourceKey.MenuPopupMinHeight); + TokenResourceBinder.CreateTokenBinding(border, Layoutable.MaxHeightProperty, + NavMenuTokenResourceKey.MenuPopupMaxHeight); + TokenResourceBinder.CreateTokenBinding(border, Decorator.PaddingProperty, + NavMenuTokenResourceKey.MenuPopupContentPadding); + + var scrollViewer = new MenuScrollViewer(); + var itemsPresenter = new ItemsPresenter + { + Name = ItemsPresenterPart + }; + CreateTemplateParentBinding(itemsPresenter, ItemsPresenter.ItemsPanelProperty, ItemsControl.ItemsPanelProperty); + Grid.SetIsSharedSizeScope(itemsPresenter, true); + scrollViewer.Content = itemsPresenter; + border.Child = scrollViewer; + + popup.Child = border; + + TokenResourceBinder.CreateTokenBinding(popup, Popup.MarginToAnchorProperty, + NavMenuTokenResourceKey.TopLevelItemPopupMarginToAnchor); + TokenResourceBinder.CreateTokenBinding(popup, Popup.MaskShadowsProperty, + NavMenuTokenResourceKey.MenuPopupBoxShadows); + CreateTemplateParentBinding(popup, Avalonia.Controls.Primitives.Popup.IsOpenProperty, + NavMenuItem.IsSubMenuOpenProperty, BindingMode.TwoWay); + + return popup; + } + + protected override void BuildStyles() + { + BuildCommonStyle(); + BuildMenuIndicatorStyle(); + BuildMenuIconStyle(); + BuildDisabledStyle(); + } + + private void BuildCommonStyle() + { + var commonStyle = new Style(selector => selector.Nesting()); + commonStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.ItemColor); + { + var keyGestureStyle = new Style(selector => selector.Nesting().Template().Name(InputGestureTextPart)); + keyGestureStyle.Add(TextBlock.ForegroundProperty, NavMenuTokenResourceKey.KeyGestureColor); + commonStyle.Add(keyGestureStyle); + } + { + var borderStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart)); + borderStyle.Add(Layoutable.MinHeightProperty, NavMenuTokenResourceKey.ItemHeight); + borderStyle.Add(Decorator.PaddingProperty, NavMenuTokenResourceKey.ItemContentPadding); + borderStyle.Add(Border.BackgroundProperty, NavMenuTokenResourceKey.ItemBg); + borderStyle.Add(Border.CornerRadiusProperty, NavMenuTokenResourceKey.ItemBorderRadius); + commonStyle.Add(borderStyle); + } + + // Hover 状态 + var hoverStyle = new Style(selector => selector.Nesting().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); + } + commonStyle.Add(hoverStyle); + Add(commonStyle); + } + + private void BuildMenuIndicatorStyle() + { + { + var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconPart)); + menuIndicatorStyle.Add(Visual.IsVisibleProperty, true); + Add(menuIndicatorStyle); + } + var hasSubMenuStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Empty)); + { + var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconPart)); + menuIndicatorStyle.Add(Visual.IsVisibleProperty, false); + hasSubMenuStyle.Add(menuIndicatorStyle); + } + Add(hasSubMenuStyle); + } + + private void BuildMenuIconStyle() + { + { + var iconViewBoxStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPresenterPart)); + iconViewBoxStyle.Add(Visual.IsVisibleProperty, false); + Add(iconViewBoxStyle); + } + + var hasIconStyle = new Style(selector => selector.Nesting().Class(":icon")); + { + var iconViewBoxStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPresenterPart)); + iconViewBoxStyle.Add(Visual.IsVisibleProperty, true); + hasIconStyle.Add(iconViewBoxStyle); + } + Add(hasIconStyle); + } + + private void BuildDisabledStyle() + { + var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled)); + disabledStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.ItemDisabledColor); + Add(disabledStyle); + } + + 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 disabledIconStyle = new Style(selector => selector.OfType().Class(StdPseudoClass.Disabled)); + disabledIconStyle.Add(PathIcon.IconModeProperty, IconMode.Disabled); + control.Styles.Add(disabledIconStyle); + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/NavMenu/NavMenuToken.cs b/src/AtomUI.Controls/NavMenu/NavMenuToken.cs index 39618c9b8..770242a8b 100644 --- a/src/AtomUI.Controls/NavMenu/NavMenuToken.cs +++ b/src/AtomUI.Controls/NavMenu/NavMenuToken.cs @@ -11,9 +11,44 @@ internal class NavMenuToken : AbstractControlDesignToken public const string ID = "NavMenu"; /// - /// 弹出菜单的宽度 + /// 菜单的圆角 /// - public double PopupMinWidth { get; set; } + public CornerRadius MenuPopupBorderRadius { get; set; } + + /// + /// 菜单 Popup 阴影 + /// + public BoxShadows MenuPopupBoxShadows { get; set; } + + /// + /// 菜单内容边距 + /// + public Thickness MenuPopupContentPadding { get; set; } + + /// + /// 菜单 Popup 最小宽度 + /// + public double MenuPopupMinWidth { get; set; } + + /// + /// 菜单 Popup 最大宽度 + /// + public double MenuPopupMaxWidth { get; set; } + + /// + /// 菜单 Popup 最小高度 + /// + public double MenuPopupMinHeight { get; set; } + + /// + /// 菜单 Popup 最大高度 + /// + public double MenuPopupMaxHeight { get; set; } + + /// + /// 顶层弹出菜单,距离顶层菜单项的边距 + /// + public double TopLevelItemPopupMarginToAnchor { get; set; } /// /// 分组标题文字颜色 @@ -40,6 +75,11 @@ internal class NavMenuToken : AbstractControlDesignToken /// public CornerRadius SubMenuItemBorderRadius { get; set; } + /// + /// 快捷键颜色 + /// + public Color KeyGestureColor { get; set; } + /// /// 菜单项文字颜色 /// @@ -143,6 +183,16 @@ internal class NavMenuToken : AbstractControlDesignToken /// /// 菜单项外间距 /// + public Thickness ItemContentMargin { get; set; } + + /// + /// 菜单项横向内间距 + /// + public Thickness ItemContentPadding { get; set; } + + /// + /// 菜单项内部元素边距 + /// public Thickness ItemMargin { get; set; } /// @@ -150,11 +200,6 @@ internal class NavMenuToken : AbstractControlDesignToken /// public Thickness HorizontalItemMargin { get; set; } - /// - /// 菜单项横向内间距 - /// - public Thickness ItemPadding { get; set; } - /// /// 横向菜单项横悬浮态背景色 /// @@ -178,7 +223,7 @@ internal class NavMenuToken : AbstractControlDesignToken /// /// 弹出框背景色 /// - public Color PopupBg { get; set; } + public Color MenuPopupBg { get; set; } /// /// 横向菜单行高 @@ -204,7 +249,7 @@ internal class NavMenuToken : AbstractControlDesignToken /// /// 暗色模式下的浮层菜单的背景颜色 /// - public Color DarkPopupBg { get; set; } + public Color DarkMenuPopupBg { get; set; } /// /// 暗色模式下的菜单项文字颜色 @@ -315,15 +360,13 @@ internal class NavMenuToken : AbstractControlDesignToken var activeBarHeight = !double.IsNaN(ActiveBarHeight) ? ActiveBarHeight : _globalToken.LineWidthBold; - var itemMargin = ItemMargin != default ? ItemMargin : new Thickness(_globalToken.MarginXXS, _globalToken.MarginXXS); + var itemContentMargin = ItemContentMargin != default ? ItemContentMargin : new Thickness(_globalToken.MarginXXS, _globalToken.MarginXXS); var colorTextDark = ColorUtils.FromRgbF( 0.65d, colorTextLightSolid.GetRedF(), colorTextLightSolid.GetGreenF(), colorTextLightSolid.GetBlueF()); - - - PopupMinWidth = 160d; + ItemBorderRadius = _globalToken.BorderRadiusLG; SubMenuItemBorderRadius = _globalToken.BorderRadiusSM; ItemColor = _globalToken.ColorText; @@ -351,19 +394,18 @@ internal class NavMenuToken : AbstractControlDesignToken DangerItemActiveBg = _globalToken.ColorError; DangerItemSelectedBg = _globalToken.ColorError; - ItemMargin = itemMargin; - HorizontalItemBorderRadius = new CornerRadius(0); - HorizontalItemHoverBg = Colors.Transparent; - ItemHeight = _globalToken.ControlHeightLG; - GroupTitleLineHeight = _globalToken.ControlHeight; - CollapsedWidth = _globalToken.ControlHeightLG * 2; - PopupBg = _globalToken.ColorBgElevated; - ItemPadding = new Thickness(_globalToken.Padding, 0); - HorizontalLineHeight = _globalToken.ControlHeightLG * 1.15; - IconSize = _globalToken.FontSize; - IconMargin = new Thickness(0, 0, _globalToken.ControlHeightSM - _globalToken.FontSize, 0); - CollapsedIconSize = _globalToken.FontSizeLG; - GroupTitleFontSize = _globalToken.FontSize; + KeyGestureColor = _globalToken.ColorTextSecondary; + + ItemContentMargin = itemContentMargin; + ItemContentPadding = new Thickness(_globalToken.Padding, _globalToken.PaddingXXS); + ItemMargin = new Thickness(0, 0, _globalToken.MarginXXS, 0); + ItemHeight = _globalToken.ControlHeight; + GroupTitleLineHeight = _globalToken.ControlHeight; + CollapsedWidth = _globalToken.ControlHeight * 2; + IconSize = _globalToken.FontSize; + IconMargin = new Thickness(0, 0, _globalToken.ControlHeightSM - _globalToken.FontSize, 0); + CollapsedIconSize = _globalToken.FontSizeLG; + GroupTitleFontSize = _globalToken.FontSize; // Disabled DarkItemDisabledColor = ColorUtils.FromRgbF(0.25d, @@ -375,7 +417,7 @@ internal class NavMenuToken : AbstractControlDesignToken DarkItemColor = colorTextDark; DarkDangerItemColor = _globalToken.ColorError; DarkItemBg = Color.Parse("#001529"); - DarkPopupBg = Color.Parse("#001529"); + DarkMenuPopupBg = Color.Parse("#001529"); DarkSubMenuItemBg = Color.Parse("#000c17"); DarkItemSelectedColor = colorTextLightSolid; @@ -388,13 +430,27 @@ internal class NavMenuToken : AbstractControlDesignToken DarkDangerItemSelectedColor = colorTextLightSolid; DarkDangerItemActiveBg = _globalToken.ColorError; - MenuHorizontalHeight = _globalToken.ControlHeightLG * 1.15; - HorizontalItemMargin = new Thickness(_globalToken.Padding, 0); + MenuHorizontalHeight = _globalToken.ControlHeightLG * 1.15; + HorizontalItemMargin = new Thickness(_globalToken.Padding, 0); + HorizontalLineHeight = _globalToken.ControlHeightLG * 1.15; + HorizontalItemBorderRadius = new CornerRadius(0); + HorizontalItemHoverBg = Colors.Transparent; + MenuArrowSize = _globalToken.FontSize / 7 * 5; MenuArrowOffset = MenuArrowSize * 0.25; MenuSubMenuBg = _globalToken.ColorBgElevated; ItemIconSize = _globalToken.IconSize; + + MenuPopupMinWidth = 160d; + MenuPopupMaxWidth = 800d; + MenuPopupMinHeight = ItemHeight * 3; + MenuPopupMaxHeight = ItemHeight * 30; + TopLevelItemPopupMarginToAnchor = _globalToken.MarginXXS; + + MenuPopupBg = _globalToken.ColorBgElevated; + MenuPopupBorderRadius = _globalToken.BorderRadiusLG; + MenuPopupContentPadding = new Thickness(_globalToken.PaddingXXS, MenuPopupBorderRadius.TopLeft / 2); + MenuPopupBoxShadows = _globalToken.BoxShadowsSecondary; } - } \ No newline at end of file diff --git a/src/AtomUI.Controls/NavMenu/ThemeConstants.cs b/src/AtomUI.Controls/NavMenu/ThemeConstants.cs index 433d76c6c..c0abe959b 100644 --- a/src/AtomUI.Controls/NavMenu/ThemeConstants.cs +++ b/src/AtomUI.Controls/NavMenu/ThemeConstants.cs @@ -3,4 +3,10 @@ internal static class ThemeConstants { public const string PopupPart = "PART_Popup"; + public const string ItemIconPart = "PART_ItemIcon"; + + // 菜单项 SizeGridName + public const string IconPresenterSizeGroup = "IconPresenter"; + public const string InputGestureTextSizeGroup = "InputGestureText"; + public const string MenuIndicatorIconSizeGroup = "MenuIndicatorIcon"; } \ No newline at end of file diff --git a/src/AtomUI.Controls/NavMenu/TopLevelHorizontalNavMenuItemTheme.cs b/src/AtomUI.Controls/NavMenu/TopLevelHorizontalNavMenuItemTheme.cs index 162efbafe..f3ab02ef2 100644 --- a/src/AtomUI.Controls/NavMenu/TopLevelHorizontalNavMenuItemTheme.cs +++ b/src/AtomUI.Controls/NavMenu/TopLevelHorizontalNavMenuItemTheme.cs @@ -135,7 +135,7 @@ internal class TopLevelHorizontalNavMenuItemTheme : BaseControlTheme { Name = PopupPart, WindowManagerAddShadowHint = false, - IsLightDismissEnabled = true, + IsLightDismissEnabled = false, Placement = PlacementMode.BottomEdgeAlignedLeft }; @@ -229,7 +229,7 @@ internal class TopLevelHorizontalNavMenuItemTheme : BaseControlTheme protected override void BuildInstanceStyles(Control control) { - var iconStyle = new Style(selector => selector.OfType()); + 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); diff --git a/src/AtomUI.Controls/Popup/Popup.cs b/src/AtomUI.Controls/Popup/Popup.cs index 01bb61087..84e7e76f8 100644 --- a/src/AtomUI.Controls/Popup/Popup.cs +++ b/src/AtomUI.Controls/Popup/Popup.cs @@ -6,8 +6,11 @@ using AtomUI.Theme.Styling; using AtomUI.Utils; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Input; +using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; @@ -64,6 +67,7 @@ public class Popup : AvaloniaPopup private PopupShadowLayer? _shadowLayer; private CompositeDisposable? _compositeDisposable; + private IDisposable? _selfLightDismissDisposable; private bool _initialized; private ManagedPopupPositionerInfo? _managedPopupPositioner; protected bool _animating; @@ -77,13 +81,14 @@ public class Popup : AvaloniaPopup AffectsMeasure(PlacementProperty); AffectsMeasure(PlacementAnchorProperty); AffectsMeasure(PlacementGravityProperty); + + IsLightDismissEnabledProperty.OverrideDefaultValue(false); } public Popup() { - IsLightDismissEnabled = false; - Closed += HandleClosed; - Opened += HandleOpened; + Closed += HandleClosed; + Opened += HandleOpened; } protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) @@ -124,7 +129,9 @@ public class Popup : AvaloniaPopup VerticalOffset = offsetY; _compositeDisposable?.Dispose(); - _shadowLayer = null; + _selfLightDismissDisposable?.Dispose(); + _firstDetected = true; + _shadowLayer = null; } private void HandleOpened(object? sender, EventArgs? args) @@ -160,6 +167,41 @@ public class Popup : AvaloniaPopup { CreateShadowLayer(); } + + // 如果没有启动,我们使用自己的处理函数,一版是为了增加我们自己的动画效果 + if (!IsLightDismissEnabled) + { + var inputManager = AvaloniaLocator.Current.GetService()!; + _selfLightDismissDisposable = inputManager.Process.Subscribe(HandleMouseClick); + } + } + } + + private bool _firstDetected = true; + + private void HandleMouseClick(RawInputEventArgs args) + { + if (!IsOpen) + { + return; + } + if (args is RawPointerEventArgs pointerEventArgs) + { + if (pointerEventArgs.Type == RawPointerEventType.LeftButtonUp) + { + if (_firstDetected) + { + _firstDetected = false; + return; + } + if (this is IPopupHostProvider popupHostProvider) + { + if (popupHostProvider.PopupHost != pointerEventArgs.Root) + { + CloseAnimation(); + } + } + } } } @@ -270,6 +312,7 @@ public class Popup : AvaloniaPopup ?? new Rect(0, 0, double.MaxValue, double.MaxValue); } + // TODO review 后可能需要删除 private static IReadOnlyList GetScreenInfos(TopLevel topLevel) { if (topLevel is WindowBase window) @@ -280,10 +323,11 @@ public class Popup : AvaloniaPopup new ManagedPopupPositionerScreenInfo(s.Bounds.ToRect(1), s.WorkingArea.ToRect(1))) .ToArray(); } - + return Array.Empty(); } - + + // TODO review 后可能需要删除 private static Rect GetParentClientAreaScreenGeometry(TopLevel topLevel) { // Popup positioner operates with abstract coordinates, but in our case they are pixel ones