Refactor Menu

This commit is contained in:
CHINBOY
2025-03-09 13:06:28 +08:00
parent 48c7b0da95
commit 3eb5fa1ed7
7 changed files with 101 additions and 159 deletions
-1
View File
@@ -130,7 +130,6 @@ public class CountBadge : Control,
private CountBadgeAdorner? _badgeAdorner;
private AdornerLayer? _adornerLayer;
private bool _isInitialized;
static CountBadge()
{
+12 -121
View File
@@ -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<MenuItem>(BackgroundProperty);
AffectsMeasure<MenuItem>(IconProperty);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
var scope = e.NameScope;
if (IsTopLevel)
{
_topLevelContentPresenter = scope.Find<ContentPresenter>(TopLevelMenuItemTheme.HeaderPresenterPart);
}
else
{
_togglePresenter = scope.Find<ContentControl>(MenuItemTheme.TogglePresenterPart);
}
HandleToggleTypeChanged();
SetupTransitions();
UpdatePseudoClasses();
}
private void SetupTransitions()
{
if (_topLevelContentPresenter is not null)
{
if (IsMotionEnabled)
{
_topLevelContentPresenter.Transitions ??= new Transitions
{
AnimationUtils.CreateTransition<SolidColorBrushTransition>(BackgroundProperty),
AnimationUtils.CreateTransition<SolidColorBrushTransition>(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();
}
}
+69 -30
View File
@@ -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<object?, bool>(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<MenuItemToggleType, bool>(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<MenuItemToggleType, bool>(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<Cursor>(() => 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<Icon>());
iconStyle.Add(Icon.WidthProperty, MenuTokenKey.ItemIconSize);
iconStyle.Add(Icon.HeightProperty, MenuTokenKey.ItemIconSize);
iconStyle.Add(Icon.NormalFilledBrushProperty, MenuTokenKey.ItemColor);
Add(iconStyle);
}
private void BuildPopupStyle()
@@ -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<IconButton>(MenuScrollViewerTheme.ScrollUpButtonPart);
_scrollDownButton = e.NameScope.Find<IconButton>(MenuScrollViewerTheme.ScrollDownButtonPart);
+1 -1
View File
@@ -20,7 +20,7 @@ internal class MenuTheme : BaseControlTheme
{
}
protected override IControlTemplate? BuildControlTemplate()
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<Menu>((menu, scope) =>
{
@@ -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<Transitions>(() => new Transitions()
{
AnimationUtils.CreateTransition<SolidColorBrushTransition>(ContentPresenter.BackgroundProperty),
AnimationUtils.CreateTransition<SolidColorBrushTransition>(ContentPresenter.ForegroundProperty)
}));
isMotionEnabledStyle.Add(headerPresenterStyle);
topLevelStyle.Add(isMotionEnabledStyle);
}
private void BuildPopupStyle(Style topLevelStyle)
{
var popupFrameStyle = new Style(selector => selector.Nesting().Template().Name(PopupFramePart));
+1 -3
View File
@@ -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;