NavMenu dark mode completed

This commit is contained in:
polarboy 2024-09-25 14:23:18 +08:00
parent 003cb21847
commit 8710ccbd48
15 changed files with 426 additions and 121 deletions

View File

@ -63,6 +63,7 @@ Welcome to communicate and give suggestions to AtomUI, thank you for giving the
| Breadcrumb | TODO |
| Dropdown | Completed ✅ |
| Menu | Completed ✅ |
| NavMenu | Completed ✅ |
| Pagination | TODO |
| Steps | TODO |

View File

@ -90,7 +90,8 @@ PS: AtomUI 目前仅在 Windows 11 平台测试<br>
|:---------------|:-------|
| Breadcrumb 面包屑 | 未完成 |
| Dropdown 下拉菜单 | 已完成 ✅ |
| Menu 导航菜单 | 已完成 ✅ |
| Menu 菜单 | 已完成 ✅ |
| NavMenu 导航菜单 | 已完成 ✅ |
| Pagination 分页 | 进行中 💪 |
| Steps 步骤条 | 未完成 |

View File

@ -241,7 +241,7 @@
<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" />
@ -250,6 +250,36 @@
<atom:NavMenuItem Header="Navigation Four"/>
</atom:NavMenu>
</desktop:ShowCaseItem>
<desktop:ShowCaseItem
Title="Switch the menu type"
Description="Show the dynamic switching mode (between inline and vertical).">
<StackPanel Orientation="Vertical" Spacing="10">
<StackPanel Orientation="Horizontal" Spacing="5">
<atom:ToggleSwitch Name="ChangeModeSwitch"/>
<TextBlock>Change Mode</TextBlock>
<atom:ToggleSwitch Margin="10, 0, 0, 0" Name="ChangeStyleSwitch"/>
<TextBlock>Change Style</TextBlock>
</StackPanel>
<atom:NavMenu Mode="{Binding Mode}" Width="256" Margin="0, 0, 0, 20" IsDarkStyle="{Binding IsDark}">
<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>
</StackPanel>
</desktop:ShowCaseItem>
</desktop:ShowCasePanel>
</UserControl>

View File

@ -1,11 +1,66 @@
using AtomUI.Controls;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace AtomUI.Demo.Desktop.ShowCase;
public partial class MenuShowCase : UserControl
{
public static readonly StyledProperty<bool> IsDarkProperty =
AvaloniaProperty.Register<ButtonShowCase, bool>(nameof(IsDark), false);
public static readonly StyledProperty<NavMenuMode> ModeProperty =
AvaloniaProperty.Register<ButtonShowCase, NavMenuMode>(nameof(Mode), NavMenuMode.Inline);
public bool IsDark
{
get => GetValue(IsDarkProperty);
set => SetValue(IsDarkProperty, value);
}
public NavMenuMode Mode
{
get => GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
}
public MenuShowCase()
{
InitializeComponent();
DataContext = this;
ChangeModeSwitch.IsCheckedChanged += HandleChangeModeCheckChanged;
ChangeStyleSwitch.IsCheckedChanged += HandleChangeStyleCheckChanged;
}
private void HandleChangeModeCheckChanged(object? sender, RoutedEventArgs? args)
{
if (ChangeModeSwitch.IsChecked.HasValue)
{
if (ChangeModeSwitch.IsChecked.Value)
{
Mode = NavMenuMode.Vertical;
}
else
{
Mode = NavMenuMode.Inline;
}
}
else
{
Mode = NavMenuMode.Inline;
}
}
private void HandleChangeStyleCheckChanged(object? sender, RoutedEventArgs? args)
{
if (ChangeStyleSwitch.IsChecked.HasValue)
{
IsDark = ChangeStyleSwitch.IsChecked.Value;
}
else
{
IsDark = false;
}
}
}

View File

@ -406,6 +406,7 @@ namespace AtomUI.Theme.Styling
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 VerticalMenuContentPadding = new TokenResourceKey("NavMenu.VerticalMenuContentPadding", "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");

View File

@ -20,7 +20,7 @@ namespace AtomUI.Controls;
internal class BaseNavMenuItemTheme : BaseControlTheme
{
public const string ItemDecoratorPart = "PART_ItemDecorator";
public const string HeaderDecoratorPart = "PART_HeaderDecorator";
public const string MainContainerPart = "PART_MainContainer";
public const string ItemIconPresenterPart = "PART_ItemIconPresenter";
public const string ItemTextPresenterPart = "PART_ItemTextPresenter";
@ -35,7 +35,6 @@ internal class BaseNavMenuItemTheme : BaseControlTheme
{
return new FuncControlTemplate<NavMenuItem>((item, scope) =>
{
BuildInstanceStyles(item);
// 仅仅为了把 Popup 包进来,没有其他什么作用
var layoutWrapper = new Panel();
var header = BuildMenuItemContent(item, scope);
@ -49,13 +48,14 @@ internal class BaseNavMenuItemTheme : BaseControlTheme
{
var headerFrame = new Border
{
Name = ItemDecoratorPart
Name = HeaderDecoratorPart,
Transitions = new Transitions()
{
AnimationUtils.CreateTransition<SolidColorBrushTransition>(Border.BackgroundProperty),
AnimationUtils.CreateTransition<SolidColorBrushTransition>(TemplatedControl.ForegroundProperty)
}
};
var transitions = new Transitions();
transitions.Add(AnimationUtils.CreateTransition<SolidColorBrushTransition>(Border.BackgroundProperty));
headerFrame.Transitions = transitions;
headerFrame.RegisterInNameScope(scope);
headerFrame.Child = BuildMenuItemInfoGrid(navMenuItem, scope);
return headerFrame;
}
@ -84,17 +84,16 @@ internal class BaseNavMenuItemTheme : BaseControlTheme
};
layout.RegisterInNameScope(scope);
var iconPresenter = new Viewbox
var iconPresenter = new ContentPresenter()
{
Name = ItemIconPresenterPart,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Stretch = Stretch.Uniform
};
Grid.SetColumn(iconPresenter, 0);
iconPresenter.RegisterInNameScope(scope);
CreateTemplateParentBinding(iconPresenter, Viewbox.ChildProperty, NavMenuItem.IconProperty);
CreateTemplateParentBinding(iconPresenter, ContentPresenter.ContentProperty, NavMenuItem.IconProperty);
TokenResourceBinder.CreateTokenBinding(iconPresenter, Layoutable.MarginProperty,
NavMenuTokenResourceKey.ItemMargin);
TokenResourceBinder.CreateGlobalTokenBinding(iconPresenter, Layoutable.WidthProperty,
@ -161,17 +160,9 @@ internal class BaseNavMenuItemTheme : BaseControlTheme
VerticalAlignment = VerticalAlignment.Center,
Kind = "RightOutlined"
};
CreateTemplateParentBinding(menuIndicatorIcon, PathIcon.IsEnabledProperty, NavMenuItem.IsEnabledProperty);
TokenResourceBinder.CreateGlobalTokenBinding(menuIndicatorIcon, PathIcon.NormalFilledBrushProperty,
NavMenuTokenResourceKey.ItemColor);
TokenResourceBinder.CreateGlobalTokenBinding(menuIndicatorIcon, PathIcon.SelectedFilledBrushProperty,
NavMenuTokenResourceKey.ItemSelectedColor);
TokenResourceBinder.CreateGlobalTokenBinding(menuIndicatorIcon, PathIcon.DisabledFilledBrushProperty,
NavMenuTokenResourceKey.ItemDisabledColor);
TokenResourceBinder.CreateGlobalTokenBinding(menuIndicatorIcon, Layoutable.WidthProperty,
NavMenuTokenResourceKey.MenuArrowSize);
TokenResourceBinder.CreateGlobalTokenBinding(menuIndicatorIcon, Layoutable.HeightProperty,
@ -199,17 +190,17 @@ internal class BaseNavMenuItemTheme : BaseControlTheme
commonStyle.Add(keyGestureStyle);
}
{
var borderStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart));
var borderStyle = new Style(selector => selector.Nesting().Template().Name(HeaderDecoratorPart));
borderStyle.Add(Border.CursorProperty, new Cursor(StandardCursorType.Hand));
borderStyle.Add(Layoutable.MinHeightProperty, NavMenuTokenResourceKey.ItemHeight);
borderStyle.Add(Decorator.PaddingProperty, NavMenuTokenResourceKey.ItemContentPadding);
borderStyle.Add(Border.MinHeightProperty, NavMenuTokenResourceKey.ItemHeight);
borderStyle.Add(Border.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().Template().Name(ItemDecoratorPart).Class(StdPseudoClass.PointerOver));
var hoverStyle = new Style(selector => selector.Nesting().Template().Name(HeaderDecoratorPart).Class(StdPseudoClass.PointerOver));
hoverStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.ItemHoverColor);
hoverStyle.Add(Border.BackgroundProperty, NavMenuTokenResourceKey.ItemHoverBg);
commonStyle.Add(hoverStyle);
@ -219,7 +210,7 @@ internal class BaseNavMenuItemTheme : BaseControlTheme
{
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
{
var itemDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart));
var itemDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(HeaderDecoratorPart));
itemDecoratorStyle.Add(Border.BackgroundProperty, NavMenuTokenResourceKey.ItemSelectedBg);
itemDecoratorStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.ItemSelectedColor);
selectedStyle.Add(itemDecoratorStyle);
@ -232,7 +223,7 @@ internal class BaseNavMenuItemTheme : BaseControlTheme
{
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
{
var itemDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart));
var itemDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(HeaderDecoratorPart));
itemDecoratorStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.ItemSelectedColor);
selectedStyle.Add(itemDecoratorStyle);
}
@ -240,25 +231,94 @@ internal class BaseNavMenuItemTheme : BaseControlTheme
}
commonStyle.Add(hasSubMenuStyle);
Add(commonStyle);
BuildDarkCommonStyle();
}
private void BuildDarkCommonStyle()
{
var darkCommonStyle = new Style(selector => selector.Nesting().PropertyEquals(NavMenuItem.IsDarkStyleProperty, true));
darkCommonStyle.Add(NavMenuItem.ForegroundProperty, NavMenuTokenResourceKey.DarkItemColor);
{
var borderStyle = new Style(selector => selector.Nesting().Template().Name(HeaderDecoratorPart));
borderStyle.Add(Border.BackgroundProperty, NavMenuTokenResourceKey.DarkItemBg);
darkCommonStyle.Add(borderStyle);
}
// 选中分两种,一种是有子菜单一种是没有子菜单
var hasNoSubMenuStyle = new Style(selector => selector.Nesting().PropertyEquals(NavMenuItem.HasSubMenuProperty, false));
{
// Hover 状态
var hoverStyle = new Style(selector => selector.Nesting().Template().Name(HeaderDecoratorPart).Class(StdPseudoClass.PointerOver));
hoverStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.DarkItemHoverColor);
hoverStyle.Add(Border.BackgroundProperty, NavMenuTokenResourceKey.DarkItemHoverBg);
hasNoSubMenuStyle.Add(hoverStyle);
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
{
var itemDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(HeaderDecoratorPart));
itemDecoratorStyle.Add(Border.BackgroundProperty, NavMenuTokenResourceKey.DarkItemSelectedBg);
itemDecoratorStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.DarkItemSelectedColor);
selectedStyle.Add(itemDecoratorStyle);
}
hasNoSubMenuStyle.Add(selectedStyle);
}
darkCommonStyle.Add(hasNoSubMenuStyle);
var hasSubMenuStyle = new Style(selector => selector.Nesting().PropertyEquals(NavMenuItem.HasSubMenuProperty, true));
{
// Hover 状态
var hoverStyle = new Style(selector => selector.Nesting().Template().Name(HeaderDecoratorPart).Class(StdPseudoClass.PointerOver));
hoverStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.DarkItemColor);
hoverStyle.Add(Border.BackgroundProperty, NavMenuTokenResourceKey.DarkItemBg);
hasSubMenuStyle.Add(hoverStyle);
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
{
var itemDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(HeaderDecoratorPart));
itemDecoratorStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.DarkItemSelectedColor);
selectedStyle.Add(itemDecoratorStyle);
}
hasSubMenuStyle.Add(selectedStyle);
}
darkCommonStyle.Add(hasSubMenuStyle);
Add(darkCommonStyle);
}
private void BuildMenuIndicatorStyle()
{
{
{
var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconPart));
menuIndicatorStyle.Add(Visual.IsVisibleProperty, true);
Add(menuIndicatorStyle);
}
var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconPart));
menuIndicatorStyle.Add(Visual.IsVisibleProperty, true);
menuIndicatorStyle.Add(PathIcon.NormalFilledBrushProperty, NavMenuTokenResourceKey.ItemColor);
menuIndicatorStyle.Add(PathIcon.SelectedFilledBrushProperty, NavMenuTokenResourceKey.ItemSelectedColor);
menuIndicatorStyle.Add(PathIcon.DisabledFilledBrushProperty, NavMenuTokenResourceKey.ItemDisabledColor);
// 设置颜色
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
Add(menuIndicatorStyle);
}
{
var darkCommonStyle = new Style(selector => selector.Nesting().PropertyEquals(NavMenuItem.IsDarkStyleProperty, true));
{
var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconPart));
menuIndicatorStyle.Add(PathIcon.IconModeProperty, IconMode.Selected);
selectedStyle.Add(menuIndicatorStyle);
menuIndicatorStyle.Add(PathIcon.NormalFilledBrushProperty, NavMenuTokenResourceKey.DarkItemColor);
menuIndicatorStyle.Add(PathIcon.SelectedFilledBrushProperty, NavMenuTokenResourceKey.DarkItemSelectedColor);
menuIndicatorStyle.Add(PathIcon.DisabledFilledBrushProperty, NavMenuTokenResourceKey.DarkItemDisabledColor);
darkCommonStyle.Add(menuIndicatorStyle);
}
Add(selectedStyle);
Add(darkCommonStyle);
}
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
{
var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconPart));
menuIndicatorStyle.Add(PathIcon.IconModeProperty, IconMode.Selected);
selectedStyle.Add(menuIndicatorStyle);
}
Add(selectedStyle);
var hasNoSubMenuStyle = new Style(selector => selector.Nesting().PropertyEquals(NavMenuItem.HasSubMenuProperty, false));
{
var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(MenuIndicatorIconPart));
@ -271,41 +331,34 @@ internal class BaseNavMenuItemTheme : BaseControlTheme
private void BuildMenuIconStyle()
{
{
var iconViewBoxStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPresenterPart));
iconViewBoxStyle.Add(Visual.IsVisibleProperty, false);
Add(iconViewBoxStyle);
var iconContentPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPresenterPart));
iconContentPresenterStyle.Add(Visual.IsVisibleProperty, false);
Add(iconContentPresenterStyle);
}
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);
var iconContentPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPresenterPart));
iconContentPresenterStyle.Add(Visual.IsVisibleProperty, true);
hasIconStyle.Add(iconContentPresenterStyle);
}
Add(hasIconStyle);
}
private void BuildDisabledStyle()
{
var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled));
disabledStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.ItemDisabledColor);
Add(disabledStyle);
{
var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled));
disabledStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.ItemDisabledColor);
Add(disabledStyle);
}
var darkStyle = new Style(selector => selector.Nesting().PropertyEquals(NavMenuItem.IsDarkStyleProperty, true));
{
var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled));
disabledStyle.Add(TemplatedControl.ForegroundProperty, NavMenuTokenResourceKey.DarkItemDisabledColor);
darkStyle.Add(disabledStyle);
}
Add(darkStyle);
}
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<PathIcon>().Class(StdPseudoClass.Disabled));
disabledIconStyle.Add(PathIcon.IconModeProperty, IconMode.Disabled);
control.Styles.Add(disabledIconStyle);
}
}

View File

@ -56,20 +56,21 @@ internal class InlineNavMenuItemTheme : BaseNavMenuItemTheme
var headerContent = base.BuildMenuItemContent(navMenuItem, 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,
};
TokenResourceBinder.CreateTokenBinding(childItemsLayoutTransform, LayoutTransformControl.MarginProperty,
NavMenuTokenResourceKey.VerticalItemsPanelSpacing, BindingPriority.Template,
(v) =>
{
if (v is double dval)
{
return new Thickness(0, dval, 0, 0);
}
return new Thickness();
});
childItemsLayoutTransform.RegisterInNameScope(scope);
var itemsPresenter = new ItemsPresenter
@ -106,6 +107,10 @@ internal class InlineNavMenuItemTheme : BaseNavMenuItemTheme
{
base.BuildStyles();
BuildMenuIndicatorStyle();
var itemsPanelStyle = new Style(selector => selector.Nesting().Template().Name(ChildItemsPresenterPart).Child().OfType<StackPanel>());
itemsPanelStyle.Add(StackPanel.SpacingProperty, NavMenuTokenResourceKey.VerticalItemsPanelSpacing);
Add(itemsPanelStyle);
}
private void BuildMenuIndicatorStyle()

View File

@ -147,13 +147,20 @@ public class NavMenu : NavMenuBase
{
if (change.Property == ModeProperty)
{
SetupItemContainerTheme(true);
SetupInteractionHandler(true);
HandleModeChanged();
}
UpdatePseudoClasses();
}
}
private void HandleModeChanged()
{
CloseChildItemsRecursively();
SetupItemContainerTheme(true);
RegenerateContainersRecursively();
SetupInteractionHandler(true);
}
protected override void PrepareContainerForItemOverride(Control element, object? item, int index)
{
base.PrepareContainerForItemOverride(element, item, index);
@ -172,7 +179,9 @@ public class NavMenu : NavMenuBase
BindUtils.RelayBind(this, ActiveBarWidthProperty, navMenuItem, NavMenuItem.ActiveBarWidthProperty);
}
BindUtils.RelayBind(this, ModeProperty, navMenuItem, NavMenuItem.ModeProperty);
BindUtils.RelayBind(this, IsDarkStyleProperty, navMenuItem, NavMenuItem.IsDarkStyleProperty);
}
}
private void UpdatePseudoClasses()
@ -276,4 +285,48 @@ public class NavMenu : NavMenuBase
}
}
}
private void CloseChildItemsRecursively()
{
CloseInlineItems();
foreach (var i in ((INavMenu)this).SubItems)
{
i.Close();
}
}
private void RegenerateContainersRecursively()
{
foreach (var item in Items)
{
if (item is NavMenuItem navMenuItem)
{
navMenuItem.RegenerateContainers();
}
}
}
private void CloseInlineItems()
{
foreach (var item in Items)
{
if (item is NavMenuItem navMenuItem)
{
CloseInlineItemRecursively(navMenuItem);
}
}
}
private void CloseInlineItemRecursively(NavMenuItem navMenuItem)
{
foreach (var item in navMenuItem.Items)
{
if (item is NavMenuItem childNavMenuItem)
{
CloseInlineItemRecursively(childNavMenuItem);
}
}
navMenuItem.CloseInlineItem();
navMenuItem.IsSubMenuOpen = false;
}
}

View File

@ -61,8 +61,8 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
/// <summary>
/// Defines the <see cref="Icon"/> property.
/// </summary>
public static readonly StyledProperty<object?> IconProperty =
AvaloniaProperty.Register<NavMenuItem, object?>(nameof(Icon));
public static readonly StyledProperty<PathIcon?> IconProperty =
AvaloniaProperty.Register<NavMenuItem, PathIcon?>(nameof(Icon));
/// <summary>
/// Defines the <see cref="InputGesture"/> property.
@ -71,7 +71,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
AvaloniaProperty.Register<NavMenuItem, KeyGesture?>(nameof(InputGesture));
/// <summary>
/// Defines the <see cref="IsSubMenuOpen"/> property.
/// Defines the <see cref="IsSubMenuOpen"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsSubMenuOpenProperty =
AvaloniaProperty.Register<NavMenuItem, bool>(nameof(IsSubMenuOpen));
@ -126,7 +126,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
/// <summary>
/// Gets or sets the icon that appears in a <see cref="NavMenuItem"/>.
/// </summary>
public object? Icon
public PathIcon? Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
@ -285,6 +285,11 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
o => o.InlineItemIndentUnit,
(o, v) => o.InlineItemIndentUnit = v);
internal static readonly DirectProperty<NavMenuItem, bool> IsDarkStyleProperty =
AvaloniaProperty.RegisterDirect<NavMenuItem, bool>(nameof(IsDarkStyle),
o => o.IsDarkStyle,
(o, v) => o.IsDarkStyle = v);
internal double ActiveBarWidth
{
get => GetValue(ActiveBarWidthProperty);
@ -340,6 +345,14 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
set => SetAndRaise(InlineItemIndentUnitProperty, ref _inlineItemIndentUnit, value);
}
private bool _isDarkStyle;
internal bool IsDarkStyle
{
get => _isDarkStyle;
set => SetAndRaise(IsDarkStyleProperty, ref _isDarkStyle, value);
}
#endregion
#region
@ -444,6 +457,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
private Border? _horizontalFrame;
private IDisposable? _itemContainerThemeDisposable;
private LayoutTransformControl? _childItemsLayoutTransform;
private Border? _headerFrame;
private bool _animating = false;
static NavMenuItem()
@ -639,7 +653,13 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
_popup.DependencyResolver = null;
}
_popup = e.NameScope.Find<Popup>("PART_Popup");
_popup = e.NameScope.Find<Popup>(ThemeConstants.PopupPart);
_headerFrame = e.NameScope.Find<Border>(BaseNavMenuItemTheme.HeaderDecoratorPart);
if (_headerFrame is not null)
{
_headerFrame.PointerEntered += HandleHeaderFrameEnter;
_headerFrame.PointerExited += HandleHeaderFrameExit;
}
if (_popup != null)
{
@ -670,6 +690,34 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
}
}
private void HandleHeaderFrameEnter(object? sender, PointerEventArgs args)
{
if (!IsDarkStyle || Icon is null || HasSubMenu)
{
return;
}
Icon.IconMode = IconMode.Active;
}
private void HandleHeaderFrameExit(object? sender, PointerEventArgs args)
{
if (!IsDarkStyle || Icon is null || HasSubMenu)
{
return;
}
if (IsSelected)
{
Icon.IconMode = IconMode.Selected;
}
else
{
Icon.IconMode = IconMode.Normal;
}
}
protected override void UpdateDataValidation(
AvaloniaProperty property,
BindingValueType state,
@ -816,10 +864,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
{
SetupHorizontalEffectiveIndicatorWidth();
}
else if (change.Property == IconProperty)
{
SetupItemIcon();
} else if (change.Property == ModeProperty)
else if (change.Property == ModeProperty)
{
SetupItemContainerTheme(true);
} else if (change.Property == ItemCountProperty)
@ -832,14 +877,36 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
{
SetupEffectivePopupMinWidth();
}
if (change.Property == IconProperty ||
change.Property == IsDarkStyleProperty)
{
SetupItemIcon();
}
}
private void SetupItemIcon()
{
if (Icon is not null && Icon is PathIcon menuItemIcon)
{
menuItemIcon.Name = ThemeConstants.ItemIconPart;
BindUtils.RelayBind(this, IsEnabledProperty, menuItemIcon, PathIcon.IsEnabledProperty);
TokenResourceBinder.CreateTokenBinding(menuItemIcon, PathIcon.WidthProperty, NavMenuTokenResourceKey.ItemIconSize);
TokenResourceBinder.CreateTokenBinding(menuItemIcon, PathIcon.HeightProperty, NavMenuTokenResourceKey.ItemIconSize);
if (IsDarkStyle)
{
TokenResourceBinder.CreateTokenBinding(menuItemIcon, PathIcon.NormalFilledBrushProperty, NavMenuTokenResourceKey.DarkItemColor);
TokenResourceBinder.CreateTokenBinding(menuItemIcon, PathIcon.SelectedFilledBrushProperty, NavMenuTokenResourceKey.DarkItemSelectedColor);
TokenResourceBinder.CreateTokenBinding(menuItemIcon, PathIcon.ActiveFilledBrushProperty, NavMenuTokenResourceKey.DarkItemHoverColor);
TokenResourceBinder.CreateTokenBinding(menuItemIcon, PathIcon.DisabledFilledBrushProperty, NavMenuTokenResourceKey.DarkItemDisabledColor);
}
else
{
TokenResourceBinder.CreateTokenBinding(menuItemIcon, PathIcon.NormalFilledBrushProperty, NavMenuTokenResourceKey.ItemColor);
TokenResourceBinder.CreateTokenBinding(menuItemIcon, PathIcon.SelectedFilledBrushProperty, NavMenuTokenResourceKey.ItemSelectedColor);
TokenResourceBinder.CreateTokenBinding(menuItemIcon, PathIcon.ActiveFilledBrushProperty, NavMenuTokenResourceKey.ItemHoverColor);
TokenResourceBinder.CreateTokenBinding(menuItemIcon, PathIcon.DisabledFilledBrushProperty, NavMenuTokenResourceKey.ItemDisabledColor);
}
}
}
@ -901,7 +968,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
/// <param name="e">The property change event.</param>
private void IconChanged(AvaloniaPropertyChangedEventArgs e)
{
var (oldValue, newValue) = e.GetOldAndNewValue<object?>();
var (oldValue, newValue) = e.GetOldAndNewValue<PathIcon?>();
if (oldValue is ILogical oldLogical)
{
@ -988,7 +1055,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
}
}
private void OpenInlineItem()
internal void OpenInlineItem()
{
if (_childItemsLayoutTransform is not null)
{
@ -1011,7 +1078,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
}
}
private void CloseInlineItem()
internal void CloseInlineItem()
{
if (_childItemsLayoutTransform is not null)
{
@ -1030,6 +1097,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
_animating = false;
});
}
}
/// <summary>
@ -1116,6 +1184,7 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
if (element is NavMenuItem navMenuItem)
{
BindUtils.RelayBind(this, ModeProperty, navMenuItem, ModeProperty);
BindUtils.RelayBind(this, IsDarkStyleProperty, navMenuItem, IsDarkStyleProperty);
}
}
@ -1130,6 +1199,20 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
}
}
}
internal void RegenerateContainers()
{
foreach (var item in Items)
{
if (item is NavMenuItem childNavMenuItem)
{
childNavMenuItem.RegenerateContainers();
}
}
ItemsPanel = new FuncTemplate<Panel?>(() => new StackPanel());
RefreshContainers();
}
private static int CalculateDistanceFromLogicalParent<T>(ILogical? logical, int @default = -1) where T : class
{
@ -1143,4 +1226,5 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
return logical != null ? result : @default;
}
}

View File

@ -13,6 +13,7 @@ namespace AtomUI.Controls;
internal class NavMenuItemTheme : BaseNavMenuItemTheme
{
public const string ItemsPresenterPart = "PART_ItemsPresenter";
public const string PopupFramePart = "PART_PopupFrame";
public NavMenuItemTheme()
: base(typeof(NavMenuItem))
@ -37,12 +38,16 @@ internal class NavMenuItemTheme : BaseNavMenuItemTheme
Name = ThemeConstants.PopupPart,
WindowManagerAddShadowHint = false,
IsLightDismissEnabled = false,
Placement = PlacementMode.RightEdgeAlignedTop
Placement = PlacementMode.RightEdgeAlignedTop,
};
var border = new Border();
TokenResourceBinder.CreateTokenBinding(border, Border.BackgroundProperty,
GlobalTokenResourceKey.ColorBgContainer);
var border = new Border()
{
Name = PopupFramePart
};
TokenResourceBinder.CreateTokenBinding(popup, Popup.MarginToAnchorProperty,
NavMenuTokenResourceKey.TopLevelItemPopupMarginToAnchor);
TokenResourceBinder.CreateTokenBinding(border, Border.CornerRadiusProperty,
NavMenuTokenResourceKey.MenuPopupBorderRadius);
TokenResourceBinder.CreateTokenBinding(border, Layoutable.MinWidthProperty,
@ -70,7 +75,7 @@ internal class NavMenuItemTheme : BaseNavMenuItemTheme
NavMenuTokenResourceKey.TopLevelItemPopupMarginToAnchor);
TokenResourceBinder.CreateTokenBinding(popup, Popup.MaskShadowsProperty,
NavMenuTokenResourceKey.MenuPopupBoxShadows);
CreateTemplateParentBinding(popup, Avalonia.Controls.Primitives.Popup.IsOpenProperty,
CreateTemplateParentBinding(popup, Popup.IsOpenProperty,
NavMenuItem.IsSubMenuOpenProperty, BindingMode.TwoWay);
return popup;
@ -82,5 +87,19 @@ internal class NavMenuItemTheme : BaseNavMenuItemTheme
var itemsPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemsPanelStyle.Add(StackPanel.SpacingProperty, NavMenuTokenResourceKey.VerticalItemsPanelSpacing);
Add(itemsPanelStyle);
{
var popupFrameStyle = new Style(selector => selector.Nesting().Template().Name(PopupFramePart));
popupFrameStyle.Add(Border.BackgroundProperty, GlobalTokenResourceKey.ColorBgContainer);
Add(popupFrameStyle);
}
var darkCommonStyle = new Style(selector => selector.Nesting().PropertyEquals(NavMenuItem.IsDarkStyleProperty, true));
{
var popupFrameStyle = new Style(selector => selector.Nesting().Template().Name(PopupFramePart));
popupFrameStyle.Add(Border.BackgroundProperty, NavMenuTokenResourceKey.DarkItemBg);
darkCommonStyle.Add(popupFrameStyle);
}
Add(darkCommonStyle);
}
}

View File

@ -72,7 +72,6 @@ internal class NavMenuTheme : BaseControlTheme
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(TemplatedControl.BorderBrushProperty, GlobalTokenResourceKey.ColorBorderSecondary);
commonStyle.Add(TemplatedControl.CornerRadiusProperty, GlobalTokenResourceKey.BorderRadius);
var horizontalStyle = new Style(selector => selector.Nesting().Class(NavMenu.HorizontalModePC));
horizontalStyle.Add(NavMenu.BackgroundProperty, GlobalTokenResourceKey.ColorBgContainer);
@ -92,6 +91,7 @@ internal class NavMenuTheme : BaseControlTheme
var verticalOrInlineStyle = new Style(selector => Selectors.Or(selector.Nesting().Class(NavMenu.VerticalModePC),
selector.Nesting().Class(NavMenu.InlineModePC)));
verticalOrInlineStyle.Add(NavMenu.PaddingProperty, NavMenuTokenResourceKey.VerticalMenuContentPadding);
verticalOrInlineStyle.Add(NavMenu.HorizontalAlignmentProperty, HorizontalAlignment.Left);
verticalOrInlineStyle.Add(NavMenu.VerticalAlignmentProperty, VerticalAlignment.Stretch);
verticalOrInlineStyle.Add(NavMenu.BackgroundProperty, GlobalTokenResourceKey.ColorBgContainer);

View File

@ -190,6 +190,11 @@ internal class NavMenuToken : AbstractControlDesignToken
/// </summary>
public double VerticalItemsPanelSpacing { get; set; }
/// <summary>
/// 垂直面板的内容内间距
/// </summary>
public Thickness VerticalMenuContentPadding { get; set; }
/// <summary>
/// 菜单项内部元素边距
/// </summary>
@ -444,7 +449,7 @@ internal class NavMenuToken : AbstractControlDesignToken
MenuPopupMinWidth = 160d;
MenuPopupMaxWidth = 800d;
MenuPopupMaxHeight = ItemHeight * 30;
TopLevelItemPopupMarginToAnchor = _globalToken.MarginXXS;
TopLevelItemPopupMarginToAnchor = _globalToken.MarginXS;
MenuPopupBg = _globalToken.ColorBgElevated;
MenuPopupBorderRadius = _globalToken.BorderRadiusLG;
@ -452,6 +457,7 @@ internal class NavMenuToken : AbstractControlDesignToken
MenuPopupBoxShadows = _globalToken.BoxShadowsSecondary;
VerticalItemsPanelSpacing = _globalToken.MarginXXS;
InlineItemIndentUnit = ItemHeight / 2;
InlineItemIndentUnit = ItemHeight / 2;
VerticalMenuContentPadding = new Thickness(_globalToken.PaddingXXS);
}
}

View File

@ -471,16 +471,16 @@ public sealed class PathIcon : Control, ICustomHitTest
base.OnAttachedToLogicalTree(e);
SetupTransitions();
SetupRotateAnimation();
if (_sourceGeometriesData.Count == 0)
{
BuildSourceRenderData();
SetupFilledBrush();
}
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (_sourceGeometriesData.Count == 0)
{
BuildSourceRenderData();
SetupFilledBrush();
}
if (_animation is not null && _animationCancellationTokenSource is null)
{
_animationCancellationTokenSource = new CancellationTokenSource();
@ -516,28 +516,25 @@ public sealed class PathIcon : Control, ICustomHitTest
protected override Size ArrangeOverride(Size finalSize)
{
if (_sourceGeometriesData.Count != 0)
_transforms.Clear();
// This should probably use GetRenderBounds(strokeThickness) but then the calculations
// will multiply the stroke thickness as well, which isn't correct.
for (var i = 0; i < _sourceGeometriesData.Count; i++)
{
// This should probably use GetRenderBounds(strokeThickness) but then the calculations
// will multiply the stroke thickness as well, which isn't correct.
for (var i = 0; i < _sourceGeometriesData.Count; i++)
{
var sourceGeometry = _sourceGeometriesData[i];
var (_, transform) = CalculateSizeAndTransform(finalSize, sourceGeometry.Bounds);
_transforms.Insert(i, transform);
}
return finalSize;
var sourceGeometry = _sourceGeometriesData[i];
var (_, transform) = CalculateSizeAndTransform(finalSize, sourceGeometry.Bounds);
_transforms.Insert(i, transform);
}
return default;
return finalSize;
}
public override void Render(DrawingContext context)
{
if (_sourceGeometriesData.Count > 0 &&
DesiredSize.Width > 0 &&
DesiredSize.Width > 0)
if (IsVisible &&
_sourceGeometriesData.Count > 0 &&
Bounds.Width > 0 &&
Bounds.Width > 0)
{
for (var i = 0; i < _sourceGeometriesData.Count; i++)
{
@ -577,7 +574,7 @@ public sealed class PathIcon : Control, ICustomHitTest
{
fillBrush = FilledBrush;
}
using var state = context.PushTransform(_transforms[i]);
context.DrawGeometry(fillBrush, null, renderedGeometry);
}

View File

@ -70,8 +70,8 @@ public class Popup : AvaloniaPopup
private IDisposable? _selfLightDismissDisposable;
private bool _initialized;
private ManagedPopupPositionerInfo? _managedPopupPositioner;
protected bool _animating;
private bool _isNeedFlip = true;
protected bool _animating;
// 当鼠标移走了,但是打开动画还没完成,我们需要记录下来这个信号
internal bool RequestCloseWhereAnimationCompleted { get; set; }

View File

@ -309,7 +309,7 @@ public class ToggleSwitch : ToggleButton,
_switchKnob.KnobSize = new Size(handleSize, handleSize);
}
CalculateElementsOffset(DesiredSize);
CalculateElementsOffset(Bounds.Size);
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
@ -524,7 +524,7 @@ public class ToggleSwitch : ToggleButton,
CollectStyleState();
if (e.Property == IsCheckedProperty)
{
CalculateElementsOffset(DesiredSize);
CalculateElementsOffset(Bounds.Size);
WaveSpiritAdorner.ShowWaveAdorner(this, WaveType.PillWave);
}
}
@ -546,12 +546,12 @@ public class ToggleSwitch : ToggleButton,
private Rect GrooveRect()
{
return new Rect(new Point(0, 0), DesiredSize);
return new Rect(new Point(0, 0), Bounds.Size);
}
private Rect HandleRect()
{
return HandleRect(IsChecked.HasValue && IsChecked.Value, DesiredSize);
return HandleRect(IsChecked.HasValue && IsChecked.Value, Bounds.Size);
}
private Rect HandleRect(bool isChecked, Size controlSize)