From 3eb5fa1ed7c26110b3ca0ea086dbe40aeda690ad Mon Sep 17 00:00:00 2001 From: CHINBOY Date: Sun, 9 Mar 2025 13:06:28 +0800 Subject: [PATCH] Refactor Menu --- src/AtomUI.Controls/Badge/CountBadge.cs | 1 - src/AtomUI.Controls/Menu/MenuItem.cs | 133 ++---------------- src/AtomUI.Controls/Menu/MenuItemTheme.cs | 99 +++++++++---- src/AtomUI.Controls/Menu/MenuScrollViewer.cs | 2 - src/AtomUI.Controls/Menu/MenuTheme.cs | 2 +- .../Menu/TopLevelMenuItemTheme.cs | 19 ++- src/AtomUI.Theme/BaseControlTheme.cs | 4 +- 7 files changed, 101 insertions(+), 159 deletions(-) diff --git a/src/AtomUI.Controls/Badge/CountBadge.cs b/src/AtomUI.Controls/Badge/CountBadge.cs index fa2885d84..8a3276173 100644 --- a/src/AtomUI.Controls/Badge/CountBadge.cs +++ b/src/AtomUI.Controls/Badge/CountBadge.cs @@ -130,7 +130,6 @@ public class CountBadge : Control, private CountBadgeAdorner? _badgeAdorner; private AdornerLayer? _adornerLayer; - private bool _isInitialized; static CountBadge() { diff --git a/src/AtomUI.Controls/Menu/MenuItem.cs b/src/AtomUI.Controls/Menu/MenuItem.cs index 04e606984..8c7d757d8 100644 --- a/src/AtomUI.Controls/Menu/MenuItem.cs +++ b/src/AtomUI.Controls/Menu/MenuItem.cs @@ -1,29 +1,18 @@ -using System.Reactive.Disposables; -using AtomUI.Controls.Utils; +using AtomUI.Controls.Utils; using AtomUI.Data; using AtomUI.IconPkg; -using AtomUI.Media; -using AtomUI.Theme; -using AtomUI.Theme.Data; -using AtomUI.Theme.Styling; using Avalonia; -using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.Converters; using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; -using Avalonia.Controls.Primitives; -using Avalonia.LogicalTree; -using Avalonia.VisualTree; -using AnimationUtils = AtomUI.Utils.AnimationUtils; namespace AtomUI.Controls; using AvaloniaMenuItem = Avalonia.Controls.MenuItem; [PseudoClasses(TopLevelPC)] -public class MenuItem : AvaloniaMenuItem, - ITokenResourceConsumer +public class MenuItem : AvaloniaMenuItem { public const string TopLevelPC = ":toplevel"; @@ -51,14 +40,8 @@ public class MenuItem : AvaloniaMenuItem, set => SetValue(IsMotionEnabledProperty, value); } - CompositeDisposable? ITokenResourceConsumer.TokenBindingsDisposable => _tokenBindingsDisposable; - #endregion - private CompositeDisposable? _tokenBindingsDisposable; - private ContentPresenter? _topLevelContentPresenter; - private ContentControl? _togglePresenter; - internal static PlatformKeyGestureConverter KeyGestureConverter = new(); static MenuItem() @@ -66,44 +49,7 @@ public class MenuItem : AvaloniaMenuItem, AffectsRender(BackgroundProperty); AffectsMeasure(IconProperty); } - - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - - var scope = e.NameScope; - if (IsTopLevel) - { - _topLevelContentPresenter = scope.Find(TopLevelMenuItemTheme.HeaderPresenterPart); - } - else - { - _togglePresenter = scope.Find(MenuItemTheme.TogglePresenterPart); - } - HandleToggleTypeChanged(); - SetupTransitions(); - UpdatePseudoClasses(); - } - - private void SetupTransitions() - { - if (_topLevelContentPresenter is not null) - { - if (IsMotionEnabled) - { - _topLevelContentPresenter.Transitions ??= new Transitions - { - AnimationUtils.CreateTransition(BackgroundProperty), - AnimationUtils.CreateTransition(ForegroundProperty) - }; - } - else - { - _topLevelContentPresenter.Transitions = null; - } - } - } - + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) { base.OnPropertyChanged(e); @@ -111,70 +57,22 @@ public class MenuItem : AvaloniaMenuItem, { UpdatePseudoClasses(); } - else if (e.Property == ToggleTypeProperty) - { - HandleToggleTypeChanged(); - } - if (this.IsAttachedToVisualTree()) + if (e.Property == IconProperty) { - if (e.Property == IsMotionEnabledProperty) + if (e.OldValue is Icon oldIcon) { - SetupTransitions(); + oldIcon.SetTemplatedParent(null); } - } - if (this.IsAttachedToLogicalTree()) - { - if (e.Property == IconProperty) + if (e.NewValue is Icon newIcon) { - SetupIcon(); + LogicalChildren.Remove(newIcon); + newIcon.SetTemplatedParent(this); } } } - private void SetupIcon() - { - if (Icon is Icon icon) - { - this.AddTokenBindingDisposable(TokenResourceBinder.CreateTokenBinding(icon, WidthProperty, MenuTokenKey.ItemIconSize)); - this.AddTokenBindingDisposable(TokenResourceBinder.CreateTokenBinding(icon, HeightProperty, MenuTokenKey.ItemIconSize)); - this.AddTokenBindingDisposable(TokenResourceBinder.CreateTokenBinding(icon, IconPkg.Icon.NormalFilledBrushProperty, - MenuTokenKey.ItemColor)); - } - } - - private void HandleToggleTypeChanged() - { - if (IsTopLevel || _togglePresenter is null) - { - return; - } - - if (ToggleType == MenuItemToggleType.None) - { - if (_togglePresenter.Presenter is not null) - { - _togglePresenter.Presenter.IsVisible = false; - } - } - else if (ToggleType == MenuItemToggleType.CheckBox) - { - var checkbox = new CheckBox(); - BindUtils.RelayBind(this, IsCheckedProperty, checkbox, ToggleButton.IsCheckedProperty); - _togglePresenter.Content = checkbox; - _togglePresenter.IsVisible = true; - } - else if (ToggleType == MenuItemToggleType.Radio) - { - var radioButton = new RadioButton(); - BindUtils.RelayBind(this, IsCheckedProperty, radioButton, ToggleButton.IsCheckedProperty); - BindUtils.RelayBind(this, GroupNameProperty, radioButton, Avalonia.Controls.RadioButton.GroupNameProperty); - _togglePresenter.Content = radioButton; - _togglePresenter.IsVisible = true; - } - } - private void UpdatePseudoClasses() { PseudoClasses.Set(TopLevelPC, IsTopLevel); @@ -191,16 +89,9 @@ public class MenuItem : AvaloniaMenuItem, base.PrepareContainerForItemOverride(container, item, index); } - protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - base.OnAttachedToLogicalTree(e); - _tokenBindingsDisposable = new CompositeDisposable(); - SetupIcon(); - } - - protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) - { - base.OnDetachedFromLogicalTree(e); - this.DisposeTokenBindings(); + base.OnAttachedToVisualTree(e); + UpdatePseudoClasses(); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Menu/MenuItemTheme.cs b/src/AtomUI.Controls/Menu/MenuItemTheme.cs index 17fff4a5a..589ca511f 100644 --- a/src/AtomUI.Controls/Menu/MenuItemTheme.cs +++ b/src/AtomUI.Controls/Menu/MenuItemTheme.cs @@ -1,4 +1,5 @@ -using AtomUI.IconPkg.AntDesign; +using AtomUI.IconPkg; +using AtomUI.IconPkg.AntDesign; using AtomUI.Media; using AtomUI.Theme; using AtomUI.Theme.Styling; @@ -23,7 +24,9 @@ internal class MenuItemTheme : BaseControlTheme { public const string ItemDecoratorPart = "PART_ItemDecorator"; public const string MainContainerPart = "PART_MainContainer"; - public const string TogglePresenterPart = "PART_TogglePresenter"; + public const string ToggleItemsLayoutPart = "PART_ToggleItemsLayout"; + public const string ToggleCheckboxPart = "PART_ToggleCheckboxPresenter"; + public const string ToggleRadioPart = "PART_ToggleRadio"; public const string ItemIconPresenterPart = "PART_ItemIconPresenter"; public const string ItemTextPresenterPart = "PART_ItemTextPresenter"; public const string InputGestureTextPart = "PART_InputGestureText"; @@ -55,7 +58,7 @@ internal class MenuItemTheme : BaseControlTheme { new ColumnDefinition(GridLength.Auto) { - SharedSizeGroup = "TogglePresenter" + SharedSizeGroup = "ToggleItemsLayout" }, new ColumnDefinition(GridLength.Auto) { @@ -74,19 +77,62 @@ internal class MenuItemTheme : BaseControlTheme }; layout.RegisterInNameScope(scope); - var togglePresenter = new ContentControl + var toggleItemsLayout = new Panel() { - Name = TogglePresenterPart, + Name = ToggleItemsLayoutPart, + }; + toggleItemsLayout.Bind(Panel.IsVisibleProperty, new MultiBinding() + { + Bindings = + { + new Binding("IsTopLevel") + { + RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), + }, + new TemplateBinding(MenuItem.ToggleTypeProperty) + }, + Converter = new FuncMultiValueConverter(objects => + { + var items = objects.ToList(); + if (items[0] is bool isTopLevel && items[1] is MenuItemToggleType toggleType) + { + return !isTopLevel && toggleType != MenuItemToggleType.None; + } + + return false; + }) + }); + CreateTemplateParentBinding(toggleItemsLayout, Panel.IsEnabledProperty, + MenuItem.IsEnabledProperty); + + Grid.SetColumn(toggleItemsLayout, 0); + toggleItemsLayout.RegisterInNameScope(scope); + + var toggleCheckbox = new CheckBox() + { + Name = ToggleCheckboxPart, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - IsVisible = false }; - CreateTemplateParentBinding(togglePresenter, InputElement.IsEnabledProperty, - InputElement.IsEnabledProperty); - - Grid.SetColumn(togglePresenter, 0); - togglePresenter.RegisterInNameScope(scope); + CreateTemplateParentBinding(toggleCheckbox, CheckBox.IsCheckedProperty, MenuItem.IsCheckedProperty); + CreateTemplateParentBinding(toggleCheckbox, CheckBox.IsVisibleProperty, + MenuItem.ToggleTypeProperty, BindingMode.Default, + new FuncValueConverter(type => type == MenuItemToggleType.CheckBox)); + toggleItemsLayout.Children.Add(toggleCheckbox); + var toggleRadio = new RadioButton() + { + Name = ToggleRadioPart, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + }; + CreateTemplateParentBinding(toggleCheckbox, RadioButton.IsCheckedProperty, MenuItem.IsCheckedProperty); + CreateTemplateParentBinding(toggleCheckbox, RadioButton.GroupNameProperty, MenuItem.GroupNameProperty); + CreateTemplateParentBinding(toggleRadio, ContentPresenter.IsVisibleProperty, + MenuItem.ToggleTypeProperty, BindingMode.Default, + new FuncValueConverter(type => type == MenuItemToggleType.Radio)); + toggleItemsLayout.Children.Add(toggleRadio); + var iconPresenter = new ContentPresenter() { Name = ItemIconPresenterPart, @@ -97,6 +143,8 @@ internal class MenuItemTheme : BaseControlTheme Grid.SetColumn(iconPresenter, 1); iconPresenter.RegisterInNameScope(scope); CreateTemplateParentBinding(iconPresenter, ContentPresenter.ContentProperty, MenuItem.IconProperty); + CreateTemplateParentBinding(iconPresenter, ContentPresenter.IsVisibleProperty, MenuItem.IconProperty, + BindingMode.Default, ObjectConverters.IsNotNull); var itemTextPresenter = new ContentPresenter { @@ -158,7 +206,7 @@ internal class MenuItemTheme : BaseControlTheme var popup = CreateMenuPopup(menuItem); popup.RegisterInNameScope(scope); - layout.Children.Add(togglePresenter); + layout.Children.Add(toggleItemsLayout); layout.Children.Add(iconPresenter); layout.Children.Add(itemTextPresenter); layout.Children.Add(inputGestureText); @@ -223,9 +271,9 @@ internal class MenuItemTheme : BaseControlTheme new SetterValueFactory(() => new Cursor(StandardCursorType.Hand))); // 设置元素外间距 - var togglePresenterStyle = new Style(selector => selector.Nesting().Template().Name(TogglePresenterPart)); - togglePresenterStyle.Add(Layoutable.MarginProperty, MenuTokenKey.ItemMargin); - commonStyle.Add(togglePresenterStyle); + var toggleItemsLayoutStyle = new Style(selector => selector.Nesting().Template().Name(ToggleItemsLayoutPart)); + toggleItemsLayoutStyle.Add(Layoutable.MarginProperty, MenuTokenKey.ItemMargin); + commonStyle.Add(toggleItemsLayoutStyle); var inputGestureTextStyle = new Style(selector => selector.Nesting().Template().Name(InputGestureTextPart)); inputGestureTextStyle.Add(Layoutable.MarginProperty, MenuTokenKey.ItemMargin); @@ -233,8 +281,6 @@ internal class MenuItemTheme : BaseControlTheme var iconPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPresenterPart)); iconPresenterStyle.Add(Layoutable.MarginProperty, MenuTokenKey.ItemMargin); - iconPresenterStyle.Add(Layoutable.WidthProperty, MenuTokenKey.ItemIconSize); - iconPresenterStyle.Add(Layoutable.HeightProperty, MenuTokenKey.ItemIconSize); commonStyle.Add(iconPresenterStyle); var itemTextPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemTextPresenterPart)); @@ -300,19 +346,12 @@ internal class MenuItemTheme : BaseControlTheme private void BuildMenuIconStyle() { - { - var iconPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPresenterPart)); - iconPresenterStyle.Add(Visual.IsVisibleProperty, false); - Add(iconPresenterStyle); - } - - var hasIconStyle = new Style(selector => selector.Nesting().Class(":icon")); - { - var iconPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPresenterPart)); - iconPresenterStyle.Add(Visual.IsVisibleProperty, true); - hasIconStyle.Add(iconPresenterStyle); - } - Add(hasIconStyle); + var iconStyle = new Style(selector => + selector.Nesting().Template().Name(ItemIconPresenterPart).Descendant().OfType()); + iconStyle.Add(Icon.WidthProperty, MenuTokenKey.ItemIconSize); + iconStyle.Add(Icon.HeightProperty, MenuTokenKey.ItemIconSize); + iconStyle.Add(Icon.NormalFilledBrushProperty, MenuTokenKey.ItemColor); + Add(iconStyle); } private void BuildPopupStyle() diff --git a/src/AtomUI.Controls/Menu/MenuScrollViewer.cs b/src/AtomUI.Controls/Menu/MenuScrollViewer.cs index 52f0ec719..1a57e3f2e 100644 --- a/src/AtomUI.Controls/Menu/MenuScrollViewer.cs +++ b/src/AtomUI.Controls/Menu/MenuScrollViewer.cs @@ -1,5 +1,4 @@ using System.Globalization; -using AtomUI.Theme; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Converters; @@ -35,7 +34,6 @@ public class MenuScrollViewer : AvaloniaScrollViewer protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - this.RunThemeTokenBindingActions(); _scrollUpButton = e.NameScope.Find(MenuScrollViewerTheme.ScrollUpButtonPart); _scrollDownButton = e.NameScope.Find(MenuScrollViewerTheme.ScrollDownButtonPart); diff --git a/src/AtomUI.Controls/Menu/MenuTheme.cs b/src/AtomUI.Controls/Menu/MenuTheme.cs index b9f114bda..e08bc7740 100644 --- a/src/AtomUI.Controls/Menu/MenuTheme.cs +++ b/src/AtomUI.Controls/Menu/MenuTheme.cs @@ -20,7 +20,7 @@ internal class MenuTheme : BaseControlTheme { } - protected override IControlTemplate? BuildControlTemplate() + protected override IControlTemplate BuildControlTemplate() { return new FuncControlTemplate((menu, scope) => { diff --git a/src/AtomUI.Controls/Menu/TopLevelMenuItemTheme.cs b/src/AtomUI.Controls/Menu/TopLevelMenuItemTheme.cs index e42a4b2d8..535331181 100644 --- a/src/AtomUI.Controls/Menu/TopLevelMenuItemTheme.cs +++ b/src/AtomUI.Controls/Menu/TopLevelMenuItemTheme.cs @@ -1,5 +1,8 @@ -using AtomUI.Theme; +using AtomUI.Controls.Utils; +using AtomUI.Media; +using AtomUI.Theme; using AtomUI.Theme.Styling; +using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; @@ -107,9 +110,23 @@ internal class TopLevelMenuItemTheme : BaseControlTheme BuildSizeTypeStyle(topLevelStyle); BuildDisabledStyle(topLevelStyle); BuildPopupStyle(topLevelStyle); + BuildMotionStyle(topLevelStyle); Add(topLevelStyle); } + private void BuildMotionStyle(Style topLevelStyle) + { + var isMotionEnabledStyle = new Style(selector => selector.Nesting().PropertyEquals(MenuItem.IsMotionEnabledProperty, true)); + var headerPresenterStyle = new Style(selector => selector.Nesting().Template().Name(HeaderPresenterPart)); + headerPresenterStyle.Add(ContentPresenter.TransitionsProperty, new SetterValueFactory(() => new Transitions() + { + AnimationUtils.CreateTransition(ContentPresenter.BackgroundProperty), + AnimationUtils.CreateTransition(ContentPresenter.ForegroundProperty) + })); + isMotionEnabledStyle.Add(headerPresenterStyle); + topLevelStyle.Add(isMotionEnabledStyle); + } + private void BuildPopupStyle(Style topLevelStyle) { var popupFrameStyle = new Style(selector => selector.Nesting().Template().Name(PopupFramePart)); diff --git a/src/AtomUI.Theme/BaseControlTheme.cs b/src/AtomUI.Theme/BaseControlTheme.cs index d37b856a7..3ea9a3914 100644 --- a/src/AtomUI.Theme/BaseControlTheme.cs +++ b/src/AtomUI.Theme/BaseControlTheme.cs @@ -1,11 +1,9 @@ -using System.Diagnostics; -using Avalonia; +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Data.Converters; -using Avalonia.LogicalTree; using Avalonia.Styling; namespace AtomUI.Theme;