mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-28 23:08:36 +08:00
Refactor Menu
This commit is contained in:
@@ -130,7 +130,6 @@ public class CountBadge : Control,
|
||||
|
||||
private CountBadgeAdorner? _badgeAdorner;
|
||||
private AdornerLayer? _adornerLayer;
|
||||
private bool _isInitialized;
|
||||
|
||||
static CountBadge()
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
Reference in New Issue
Block a user