Files

272 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# NavMenu 导航菜单
## 概述
导航菜单(NavMenu)是网站和应用中最常见的导航交互控件之一,用于为用户提供清晰的页面导航路径。它支持**水平**(Horizontal)、**垂直**(Vertical)和**内嵌**(Inline)三种展示模式,并可在亮色和暗色主题之间切换。
AtomUI 的 `NavMenu` 控件对标 [Ant Design 5.0 Menu(导航菜单)](https://ant.design/components/menu-cn) 的设计规范,在 .NET / Avalonia 平台上提供一致的视觉体验和交互行为。
---
## 设计原理
### Ant Design 的导航菜单设计哲学
Ant Design 导航菜单的核心定位是:**为页面和功能提供导航的菜单列表,帮助用户在不同层级的内容之间快速跳转**。菜单支持多级层级结构,用户可以展开/收起子菜单来浏览不同层级的导航项。
**三种菜单模式**(按场景区分):
| 模式 | 设计意图 | 典型场景 |
|---|---|---|
| 🔹 **水平模式(Horizontal)** | 水平排列的一级菜单项,子菜单通过弹出层展示。适合顶部导航栏 | 网站/应用的顶部全局导航 |
| 🔸 **垂直模式(Vertical)** | 垂直排列的菜单项,子菜单通过弹出层(Popup)在侧边展示 | 侧边栏导航(弹出式子菜单) |
| 🔻 **内嵌模式(Inline)** | 垂直排列的菜单项,子菜单在当前面板内展开/折叠,具有展开/收起动画 | 侧边栏导航(折叠式子菜单) |
**关键交互特性**:
| 特性 | 说明 |
|---|---|
| **选中高亮** | 当前选中的菜单项高亮显示,祖先路径上的父级菜单项也会标记为「选中路径」 |
| **手风琴模式** | 同级菜单项中只能有一个展开,展开新子菜单时自动关闭其他已展开的兄弟菜单 |
| **默认展开/选中** | 支持通过路径指定初始展开的子菜单和默认选中的菜单项 |
| **暗色主题** | 支持独立的暗色主题样式,常用于深色背景的侧边导航栏 |
| **图标支持** | 菜单项可配置图标,提升可识别性 |
| **禁用状态** | 单个菜单项可设为禁用,不响应交互 |
### 与 Avalonia 的关系
AtomUI 的 `NavMenu` **不继承自 Avalonia 内置的 `Menu` 控件**,而是直接继承自 `ItemsControl`。这是因为 Ant Design 的导航菜单与传统的应用程序菜单(File/Edit/View 等)有本质区别——它是一个页面级的导航组件,需要支持选中状态跟踪、路径高亮、内嵌展开动画等 Avalonia `Menu` 不具备的功能。
`NavMenu` 使用自定义的 `NavMenuItem`(继承自 `HeaderedSelectingItemsControl`)作为菜单项容器,实现了完整的自定义交互逻辑。
### AtomUI NavMenu 的核心设计
| 设计要素 | 实现方式 | 设计动机 |
|---|---|---|
| **三种菜单模式** | `NavMenuMode` 枚举 + 伪类驱动 + 交互处理器切换 | 不同模式对应不同的布局策略和子菜单展示方式 |
| **交互处理器策略** | `INavMenuInteractionHandler` 接口 + `DefaultNavMenuInteractionHandler` / `InlineNavMenuInteractionHandler` | 将交互逻辑从控件中解耦,不同模式使用不同的处理器 |
| **树形数据模型** | `INavMenuNode` / `NavMenuNode` + `ITreeNode<T>` | 支持层级数据结构,每个节点可包含子节点 |
| **选中路径追踪** | `IsInSelectedPath` 属性 + `CollectSelectPathItems` | 从选中项向上追溯到根节点,标记整条路径上的祖先菜单项 |
| **默认展开/选中** | `DefaultOpenPaths` / `DefaultSelectedPath` + `TreeNodePath` | 通过路径字符串指定初始状态 |
| **手风琴模式** | `IsAccordionMode` 属性 + `NotifySubmenuOpened` 互斥逻辑 | 同级只允许一个子菜单展开 |
| **亮色/暗色主题** | `IsDarkStyle` 属性 + `:dark` / `:light` 伪类 | 通过 Token 系统提供完整的暗色主题 |
| **展开/折叠动画** | Inline 模式下使用 `SlideUpInMotion` / `SlideUpOutMotion` | 平滑的子菜单展开/收起效果 |
| **Design Token** | `NavMenuToken` + 全局 `SharedToken` | 所有视觉属性从 Token 派生,支持主题定制 |
---
## 功能详解
### 菜单模式(NavMenuMode)
#### 水平模式(Horizontal)
菜单项水平排列成一行,通常用于页面顶部导航。子菜单通过 Popup 弹出层在菜单项下方展示。水平模式下选中项有底部指示条效果。
#### 垂直模式(Vertical)
菜单项垂直排列,子菜单通过 Popup 弹出层在菜单项右侧展示。鼠标悬浮时延迟展开子菜单,移出后延迟关闭。
#### 内嵌模式(Inline)
菜单项垂直排列,子菜单在当前面板内通过折叠/展开方式显示。点击有子菜单的菜单项会触发展开/收起动画(`SlideUpInMotion` / `SlideUpOutMotion`)。
**模式与交互处理器的对应关系:**
| 模式 | 交互处理器 | 子菜单展示方式 |
|---|---|---|
| Horizontal | `DefaultNavMenuInteractionHandler` | Popup 弹出层 |
| Vertical | `DefaultNavMenuInteractionHandler` | Popup 弹出层 |
| Inline | `InlineNavMenuInteractionHandler` | 就地展开/折叠 |
### 选中路径追踪
当用户选中一个叶子菜单项时,NavMenu 会:
1. 清除之前选中项及其祖先路径上的 `IsInSelectedPath` 标记
2. 为新选中项的所有祖先菜单项设置 `IsInSelectedPath = true`
3. 触发 `NavMenuNodeSelected` 事件,更新 `SelectedItem` 属性
这使得主题可以为整条选中路径上的菜单项应用高亮样式。
### 手风琴模式(Accordion)
当 `IsAccordionMode = true` 时,同一级别中只能有一个子菜单处于展开状态。当一个子菜单被展开时,其兄弟子菜单会自动关闭。
### 默认展开与默认选中
- **`DefaultOpenPaths`**:通过 `TreeNodePath` 列表指定初始展开的路径。路径格式为 `/Key1/Key2/Key3`,由各节点的 `ItemKey` 拼接而成。
- **`DefaultSelectedPath`**:指定初始选中的菜单项路径。
- **`SelectedItem`**:直接设置选中的 `INavMenuNode` 对象,优先级高于 `DefaultSelectedPath`。
### 亮色/暗色主题
通过 `IsDarkStyle` 属性切换菜单的视觉主题。暗色模式下使用一套独立的 Token(如 `DarkMenuBg`、`DarkItemColor`、`DarkItemSelectedBg` 等),提供深色背景下的完整样式支持。
---
## 与 Ant Design 规范的对齐程度
| 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 |
|---|---|---|---|
| 三种模式 `mode` | ✅ `vertical / horizontal / inline` | ✅ `NavMenuMode` 枚举 | ✅ 完全对齐 |
| 暗色主题 `theme` | ✅ `light / dark` | ✅ `IsDarkStyle` 属性 | ✅ 完全对齐 |
| 选中项 `selectedKeys` | ✅ 数组 | ✅ `SelectedItem` 单选 | ⚠️ 仅支持单选 |
| 默认展开 `defaultOpenKeys` | ✅ 数组 | ✅ `DefaultOpenPaths` 路径列表 | ✅ 对齐(路径格式不同) |
| 默认选中 `defaultSelectedKeys` | ✅ 数组 | ✅ `DefaultSelectedPath` 单路径 | ⚠️ 仅支持单选 |
| 手风琴模式 | ✅ `openKeys` 受控 | ✅ `IsAccordionMode` 属性 | ✅ 对齐 |
| 图标支持 `icon` | ✅ ReactNode | ✅ `PathIcon` | ✅ 对齐 |
| 禁用项 `disabled` | ✅ 布尔 | ✅ `IsEnabled` | ✅ 完全对齐 |
| 菜单项分组 `ItemGroup` | ✅ 支持 | ❌ 暂未支持 | ⚠️ 待实现 |
| 菜单分割线 `Divider` | ✅ 支持 | ❌ 暂未支持 | ⚠️ 待实现 |
| 折叠收起 `inlineCollapsed` | ✅ 支持 | ❌ 暂未支持 | ⚠️ 待实现 |
| 点击事件 `onClick` | ✅ 回调 | ✅ `NavMenuItemClick` 事件 | ✅ 完全对齐 |
| 选中事件 `onSelect` | ✅ 回调 | ✅ `NavMenuNodeSelected` 事件 | ✅ 完全对齐 |
| `items` 配置式 | ✅ JSON 数组 | ✅ `NavMenuNode` 数据模型 | ✅ 对齐(形式不同) |
---
## 继承关系
### NavMenu
```
Avalonia.Controls.Primitives.TemplatedControl
└── Avalonia.Controls.ItemsControl
└── AtomUI.Desktop.Controls.NavMenu
├── implements IFocusScope
├── implements INavMenu
├── implements IMotionAwareControl
└── implements IMenuChildSelectable (internal)
```
### NavMenuItem
```
Avalonia.Controls.Primitives.TemplatedControl
└── Avalonia.Controls.ItemsControl
└── Avalonia.Controls.Primitives.SelectingItemsControl
└── Avalonia.Controls.Primitives.HeaderedSelectingItemsControl
└── AtomUI.Desktop.Controls.NavMenuItem (internal)
├── implements INavMenuItem
├── implements ISelectable
├── implements ICommandSource
├── implements IClickableControl
├── implements ICustomHitTest
└── implements IMenuChildSelectable (internal)
```
### NavMenuNode(数据模型)
```
Avalonia.AvaloniaObject
└── AtomUI.Desktop.Controls.NavMenuNode
└── implements INavMenuNode
└── extends ITreeNode<INavMenuNode>
```
**各层级职责划分:**
| 层级 | 提供的能力 |
|---|---|
| `ItemsControl` | 子项集合管理、容器化、`ItemsSource` 绑定、`ItemTemplate` 模板 |
| `NavMenu` | 三种模式切换、交互处理器调度、选中路径追踪、默认展开/选中、手风琴模式、亮色/暗色主题、Design Token 集成 |
| `NavMenuItem` | 单个菜单项行为:子菜单展开/关闭、命令绑定、选中状态、Popup 管理、内嵌展开动画、层级计算 |
| `NavMenuNode` | 数据模型:Header、Icon、ItemKey、子节点集合、父节点引用 |
---
## 核心类型
### 接口体系
| 接口 | 定义位置 | 职责 |
|---|---|---|
| `INavMenu` | `AtomUI.Desktop.Controls` | NavMenu 根控件接口,提供 SelectedItem、DefaultOpenPaths、Close 等能力 |
| `INavMenuElement` | `AtomUI.Desktop.Controls` | 菜单元素基接口,提供 SubItems 子项枚举 |
| `INavMenuItem` | `AtomUI.Desktop.Controls` | 菜单项接口,定义 HasSubMenu、IsSubMenuOpen、Open/Close 等行为 |
| `INavMenuNode` | `AtomUI.Desktop.Controls` | 菜单节点数据接口,继承自 `ITreeNode<INavMenuNode>` |
| `INavMenuInteractionHandler` | `AtomUI.Desktop.Controls` (internal) | 交互处理器接口,策略模式 |
| `IMenuChildSelectable` | `AtomUI.Desktop.Controls` (internal) | 子项选中管理接口 |
### 辅助类型
| 类型 | 说明 |
|---|---|
| `NavMenuNode` | `INavMenuNode` 的默认实现,可直接在 AXAML 中声明使用 |
| `NavNodeKey` | 菜单节点键值结构体,支持字符串比较 |
| `TreeNodePath` | 树节点路径,格式为 `/Key1/Key2`,用于指定默认展开/选中路径 |
| `NavMenuItemClickEventArgs` | 菜单项点击事件参数 |
| `NavMenuNodeSelectedEventArgs` | 菜单节点选中事件参数 |
| `BaseNavMenuItemHeader` | 菜单项头部基类,承载 Header、Icon、动画过渡等通用逻辑 |
| `HorizontalNavMenuItemHeader` | 水平模式专用的菜单项头部 |
| `VerticalNavMenuItemHeader` | 垂直模式专用的菜单项头部 |
| `InlineNavMenuItemHeader` | 内嵌模式专用的菜单项头部 |
---
## 源码位置
| 层级 | 文件路径 | 说明 |
|---|---|---|
| 控件类 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenu.cs` | NavMenu 主控件 |
| 菜单项 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenuItem.cs` | NavMenuItem 菜单项容器(internal) |
| 数据模型 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenuNode.cs` | NavMenuNode 数据模型 + INavMenuNode 接口 |
| Token 定义 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenuToken.cs` | 组件级 Design Token |
| 伪类常量 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenuPseudoClass.cs` | 伪类定义 |
| 模式枚举 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenuMode.cs` | NavMenuMode 枚举 |
| 事件参数 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenuEventArgs.cs` | 事件参数类 |
| 接口定义 | `src/AtomUI.Desktop.Controls/NavMenu/INavMenu.cs` | INavMenu 接口 |
| 接口定义 | `src/AtomUI.Desktop.Controls/NavMenu/INavMenuElement.cs` | INavMenuElement 接口 |
| 接口定义 | `src/AtomUI.Desktop.Controls/NavMenu/INavMenuItem.cs` | INavMenuItem 接口 |
| 键结构体 | `src/AtomUI.Desktop.Controls/NavMenu/NavNodeKey.cs` | NavNodeKey 结构体 |
| 交互处理器 | `src/AtomUI.Desktop.Controls/NavMenu/DefaultNavMenuInteractionHandler.cs` | 默认(Vertical/Horizontal)交互处理器 |
| 交互处理器 | `src/AtomUI.Desktop.Controls/NavMenu/InlineNavMenuInteractionHandler.cs` | 内嵌模式交互处理器 |
| 头部控件 | `src/AtomUI.Desktop.Controls/NavMenu/Header/BaseNavMenuItemHeader.cs` | 菜单项头部基类 |
| 头部控件 | `src/AtomUI.Desktop.Controls/NavMenu/Header/HorizontalNavMenuItemHeader.cs` | 水平模式头部 |
| 头部控件 | `src/AtomUI.Desktop.Controls/NavMenu/Header/VerticalNavMenuItemHeader.cs` | 垂直模式头部 |
| 头部控件 | `src/AtomUI.Desktop.Controls/NavMenu/Header/InlineNavMenuItemHeader.cs` | 内嵌模式头部 |
| 主题模板 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/NavMenuTheme.axaml` | NavMenu ControlTheme |
| 主题模板 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/NavMenuItemTheme.axaml` | NavMenuItem ControlTheme |
| 主题模板 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/BaseNavMenuItemHeaderTheme.axaml` | 基础头部主题 |
| 主题模板 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/HorizontalNavMenuItemHeaderTheme.axaml` | 水平头部主题 |
| 主题模板 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/VerticalNavMenuItemHeaderTheme.axaml` | 垂直头部主题 |
| 主题模板 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/InlineNavMenuItemHeaderTheme.axaml` | 内嵌头部主题 |
| 模板常量 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/NavMenuThemeConstants.cs` | 模板部件名称常量 |
| 工具类 | `src/AtomUI.Desktop.Controls/NavMenu/Utils/MarginMultiplierConverter.cs` | 缩进边距倍数转换器 |
| 工具类 | `src/AtomUI.Desktop.Controls/NavMenu/Utils/NavMenuIsSubMenuOpenConverter.cs` | 子菜单展开状态转换器 |
| Gallery 示例 | `controlgallery/AtomUIGallery/ShowCases/Views/Navigation/MenuShowCase.axaml` | 使用范例 |
| Gallery 代码 | `controlgallery/AtomUIGallery/ShowCases/Views/Navigation/MenuShowCase.axaml.cs` | 示例 code-behind |
---
## 模板结构
### NavMenu 模板
NavMenu 自身模板比较简单,主要是一个带 `ItemsPresenter` 的容器:
- **水平模式**:`ItemsPanel` 使用水平布局的 `StackPanel`,底部有一条指示线
- **垂直/内嵌模式**:`ItemsPanel` 使用垂直布局的 `StackPanel`
### NavMenuItem 模板
NavMenuItem 的模板根据模式不同有较大差异:
- **水平/垂直模式**:使用 `Popup` 弹出子菜单
- **内嵌模式**:使用 `BaseMotionActor`(`PART_ChildItemsLayoutTransform`)包裹子菜单 `ItemsPresenter`,通过 `SlideUpInMotion` / `SlideUpOutMotion` 实现展开/收起动画
每种模式都有对应的 `NavMenuItemHeader` 控件作为菜单项头部区域。
### 模板部件常量
| 常量名 | 值 | 说明 |
|---|---|---|
| `NavMenuItemThemeConstants.HeaderPart` | `"PART_Header"` | 菜单项头部区域 |
| `NavMenuItemThemeConstants.ItemsPresenterPart` | `"PART_ItemsPresenter"` | 子项展示器 |
| `NavMenuItemThemeConstants.PopupFramePart` | `"PART_PopupFrame"` | Popup 框架 |
| `NavMenuItemThemeConstants.PopupPart` | `"PART_Popup"` | Popup 弹出层 |
| `InlineNavMenuItemThemeConstants.ChildItemsLayoutTransformPart` | `"PART_ChildItemsLayoutTransform"` | 内嵌模式子项动画容器 |
| `NavMenuThemeConstants.ItemsPresenterPart` | `"PART_ItemsPresenter"` | NavMenu 子项展示器 |
| `NavMenuThemeConstants.HorizontalLinePart` | `"PART_HorizontalLine"` | 水平模式底部指示线 |
| `TopLevelHorizontalNavMenuItemThemeConstants.ActiveIndicatorPart` | `"PART_ActiveIndicator"` | 水平顶层菜单项的活跃指示器 |