Files
AtomUI/docs/controls/desktop/layout/splitter/implementation.md
T

12 KiB
Raw Blame History

Splitter 桌面版实现原理

本文档描述 Splitter 桌面版的内部实现范围、源码职责、状态流、生命周期、资源边界和维护规则。公共设计与 API 契约见 Splitter 桌面版架构设计,变化记录见 Splitter Changelog。涉及组件 Token 的实现应同时阅读 Splitter Token 设计。

1. 实现定位

Splitter 的实现由一个 public 根控件和三个 internal 协作层组成:

Splitter
  -> SplitterTheme / PART_SplitterPanel
  -> SplitterPanel
  -> SplitterHandle
  -> SplitterDragBar

Splitter 暴露用户 API、Children、附加属性和 resize 事件;SplitterPanel 负责布局算法、面板集合、handle 生成和折叠状态;SplitterHandle 负责单条分割边界的状态、折叠按钮和 drag bar 协调;SplitterDragBar 负责 pointer drag 入口和 grip 视觉。

实现文档不复述完整属性清单。完整 public surface 以源码和 Gallery API 表为准。

2. 源码文件结构

主要源码文件:

  • src/AtomUI.Desktop.Controls/Splitter/Splitter.cs
  • src/AtomUI.Desktop.Controls/Splitter/SplitterPanel.cs
  • src/AtomUI.Desktop.Controls/Splitter/SplitterHandle.cs
  • src/AtomUI.Desktop.Controls/Splitter/SplitterDragBar.cs
  • src/AtomUI.Desktop.Controls/Splitter/SplitterPanelCollapsible.cs
  • src/AtomUI.Desktop.Controls/Splitter/SplitterResizeEventArgs.cs
  • src/AtomUI.Desktop.Controls/Splitter/SplitterToken.cs
  • src/AtomUI.Desktop.Controls/Splitter/Themes/SplitterTheme.axaml
  • src/AtomUI.Desktop.Controls/Splitter/Themes/SplitterHandleTheme.axaml
  • src/AtomUI.Desktop.Controls/Splitter/Themes/SplitterDragBarTheme.axaml
  • src/AtomUI.Desktop.Controls/Splitter/Themes/SplitterThemes.axaml

Gallery 与文档结构:

  • controlgallery/AtomUIGallery/ShowCases/Layout/Splitter
  • tests/AtomUIGallery.Tests/ShowCases/SplitterShowCasePageTests.cs
  • tests/AtomUIGallery.Tests/ShowCases/SplitterShowCaseExamples.snapshot
  • docs/controls/desktop/layout/splitter/

职责边界:

  • Splitter.cs 保留 public API、附加属性、Children 同步、template part 获取和 resize 事件抛出。
  • SplitterPanel.cs 保留布局、拖拽、折叠、尺寸约束和 handle 状态刷新。
  • SplitterHandle.cs 保留单个 handle 的按钮、hover、dragging、collapse request 和 drag event 转发。
  • SplitterDragBar.cs 保留 Thumb 输入事件和禁用拖拽拦截。
  • Theme 文件负责静态视觉结构、template binding、selector 和 TokenResource 映射。
  • Token 文件只提供组件视觉变量,不保存实例状态。

3. 核心类职责

3.1 Splitter

Splitter 是唯一 public 控件入口:

  • 持有 Children 集合。
  • 注册根控件属性和面板附加属性。
  • 在 OnApplyTemplate 中获取 PART_SplitterPanel。
  • 把 Children 同步到 SplitterPanel.Children。
  • 通过 RaiseResizeStarted、RaiseResizeDelta、RaiseResizeCompleted 把 internal drag 状态转换为 public resize 事件。
  • 注册 SplitterToken scope。

维护规则:

  • 不把 internal handle 或 panel 对象暴露给用户代码。
  • 模板重套用时必须保证 Children 同步不会重复持有旧视觉对象。
  • 新增 Splitter 实例级样式 API 时,入口必须先定义在 Splitter 上,再由 SplitterPanel 传递给 internal handle。

3.2 SplitterPanel

SplitterPanel 是布局和状态 owner:

  • 从 Children 中识别用户面板。
  • 在相邻面板之间创建 SplitterHandle。
  • 监听面板附加属性变化并触发布局或 handle 状态刷新。
  • 根据 Orientation、HandleSize、尺寸约束和折叠状态执行 measure/arrange。
  • 处理 drag started/delta/completed,按 IsLazy 决定实时布局或延迟提交。
  • 处理折叠和恢复,维护可见面板边界。

维护规则:

  • _panels 是用户面板列表,_handles 是 internal handle 列表,两者不能混淆。
  • handle 的创建、事件订阅和移除必须成对维护。
  • 面板属性变化必须从 SplitterPanel 统一归一,不允许 handle 直接修改用户面板业务状态。
  • 尺寸计算必须同时考虑固定值、百分比、默认尺寸、最小值、最大值和折叠尺寸。

3.3 SplitterHandle

SplitterHandle 是 internal 分割边界:

  • 接收 Orientation、IsDragEnabled、line brush、line thickness 等视觉状态。
  • 获取 PART_DragBar 和折叠按钮。
  • 转发 SplitterDragBar 的 drag event。
  • 根据可折叠状态、hover side 和 icon display mode 更新折叠按钮。
  • 使用 :pointerover 和 :dragging 驱动主题 selector。

维护规则:

  • SplitterHandle 不应成为用户样式 API。
  • 折叠按钮点击只抛出 request,由 SplitterPanel 决定是否真正折叠或恢复。
  • LineBrush、LineThickness、LineCornerRadius 这类分割线视觉状态必须可由 Splitter 根控件统一传入,不能只停留在 internal handle 默认值。

3.4 SplitterDragBar

SplitterDragBar 继承 AtomUI Thumb:

  • 提供 pointer drag 入口。
  • 通过 IsDragEnabled 禁用拖拽输入。
  • 使用 Orientation 设置 resize cursor。
  • 使用 line brush、line thickness 和 line corner radius 展示 grip。
  • 使用 :dragging 表达拖拽状态。

维护规则:

  • drag bar 负责输入和 grip 视觉,不负责面板尺寸计算。
  • grip 的厚度应来自 LineThickness,不能绕过 handle 状态直接写死 token。
  • drag bar 命中区域尺寸和可见 grip 尺寸必须分离。

4. 状态与数据流

Splitter 的主状态流:

User children / attached panel properties
  -> Splitter.Children
  -> SplitterPanel.Children
  -> _panels + generated _handles
  -> Measure / Arrange / collapse state

拖拽状态流:

Pointer drag on SplitterDragBar
  -> SplitterHandle.DragStarted / DragDelta / DragCompleted
  -> SplitterPanel.HandleDragStarted / HandleDragDelta / HandleDragCompleted
  -> Apply panel sizes
  -> Splitter.ResizeStarted / ResizeDelta / ResizeCompleted

折叠状态流:

Collapse IconButton click
  -> SplitterHandle.CollapsePreviousRequested / CollapseNextRequested
  -> SplitterPanel ExpandLeftAtHandle / ExpandRightAtHandle
  -> attached IsCollapsed + panel size update
  -> handle visibility and button state refresh

样式状态流:

Splitter public style API / SplitterToken
  -> SplitterPanel owner state
  -> SplitterHandle line state
  -> PART_HandleLine + SplitterDragBar
  -> PART_Grip

维护要求:

  • 外部设置的 Avalonia 属性必须在模板应用前后保持一致。
  • HandleSize 影响布局和 hit area;line thickness 只影响可见线条和 grip。
  • IsResizable=False 只禁用对应边界拖拽,不应移除分割线视觉。
  • IsLazy=True 时中间 drag feedback 不应提前写入面板 Size。
  • Gallery API 表中的状态说明应与源码实际状态流一致。

5. 生命周期与模板接入

Splitter 生命周期:

  • 构造阶段订阅 Children.CollectionChanged 并注册 Token scope。
  • ChildrenChanged 把用户集合变更同步到当前 SplitterPanel。
  • OnApplyTemplate 获取 PART_SplitterPanel,设置 owner,并同步现有 Children。

SplitterPanel 生命周期:

  • 构造阶段订阅自身 Children.CollectionChanged。
  • RefreshPanelsAndHandles 移除旧 handle、解除事件订阅、重建面板列表、创建新 handle、建立 handle 事件。
  • SyncTrackedPanels 跟踪用户面板属性变化,并在面板离开时解绑。
  • 布局变化和属性变化触发 measure/arrange 或 handle state update。

SplitterHandle 生命周期:

  • OnApplyTemplate 先解除旧 SplitterDragBar 和按钮事件,再获取新 part 并重新订阅。
  • SetDragging 同步 handle 和 drag bar 的 :dragging 状态。
  • UpdateCollapseButtons 在 hover、orientation、collapse state 变化后刷新按钮。

稳定接入点:

接入点 Owner 维护规则
PART_SplitterPanel Splitter public template part,重命名需要同步主题、实现、测试和文档。
PART_HandleLine SplitterHandle internal theme part,用于线条视觉,不作为用户 API。
PART_DragBar SplitterHandle internal theme part,用于 pointer drag,不作为用户 API。
PART_Grip SplitterDragBar internal theme part,用于 grip 视觉,不作为用户 API。
collapse buttons SplitterHandle internal theme part,用于折叠操作。

6. 交互与事件处理

交互规则:

  • Pointer drag 只从 SplitterDragBar 进入。
  • SplitterHandle 只转发 drag event 和 collapse request。
  • SplitterPanel 是唯一可修改面板尺寸和折叠状态的内部 owner。
  • Splitter 只负责对外抛 resize 事件。

resize 事件语义:

  • ResizeStarted 在某个 handle 开始拖拽时触发。
  • ResizeDelta 在尺寸变化过程中触发,lazy 模式下应符合延迟提交语义。
  • ResizeCompleted 在拖拽结束后触发。
  • 事件参数暴露 handle index 和尺寸快照,不暴露 internal handle 对象。

禁用拖拽语义:

  • Splitter.IsResizable=False 的面板会影响相邻 handle 的 IsDragEnabled。
  • IsDragEnabled=False 时 drag bar 不处理 pointer pressed/moved/released。
  • 禁用拖拽不等于隐藏分割线;视觉仍应表达面板边界。

7. 内部算法与关键流程

维护者需要重点关注:

  • RefreshPanelsAndHandles:从用户 children 派生 internal handle。
  • UpdateHandleState:根据相邻面板、折叠状态和尺寸约束决定 drag/collapse 能力。
  • measure/arrange 流程:把 Dimension、HandleSize 和剩余空间归一为实际尺寸。
  • drag 流程:实时或 lazy 地更新尺寸。
  • collapse 流程:寻找可见边界、可恢复面板和尺寸 owner。

布局算法不应把视觉线条厚度混入 handle 占位尺寸。可见线条变粗时,只应该影响 handle 内部视觉,不应该让面板布局发生额外跳动,除非用户显式改变 HandleSize。

8. 资源、性能与 AOT 边界

资源边界:

  • 不通过运行时反射扫描 public API、Token 或 Gallery 表格数据。
  • 不把可静态声明的模板结构迁移到 C# 动态创建。
  • handle、drag bar、button 的事件订阅必须在模板重套用或 handle 移除时解绑。
  • _trackedPanels 中的面板属性订阅必须在面板离开时解绑。

性能边界:

  • 面板集合变化时只重建必要的 handle 结构,避免把业务子控件重新包装成不可追踪结构。
  • 拖拽过程中避免创建临时视觉对象。
  • lazy 模式下拖拽反馈应复用现有 drag bar 状态,不引入额外弹层或全局输入监听。
  • 分割线样式变化应通过 Avalonia 属性和模板绑定流动,不使用手动遍历视觉树刷新。

AOT 边界:

  • Gallery API/Token 表不通过运行时反射生成。
  • Source generator 生成文件不手工编辑。
  • 文档、Gallery 和源码发生冲突时,应修复源文档或结构化数据,不直接改 docs/AI/llms 生成产物。

9. 维护不变量

维护 Splitter 时不得破坏:

  • Public API、默认值、事件顺序和 Gallery 可观察行为。
  • HandleSize 作为 hit area 的语义。
  • SplitterPanel 作为尺寸与折叠状态 owner 的语义。
  • internal handle template part 的绑定关系和事件释放路径。
  • Light/Dark、Browser/Desktop 和不同方向下的主题一致性。
  • Gallery API 表、Token 表、ShowCase 示例和控件文档的一致性。

新增分割线样式能力时必须遵守:

  • API 定义在 Splitter,不定义在 internal handle 上作为用户入口。
  • LineThickness 不替代 HandleSize。
  • LineCornerRadius 同时作用于 PART_HandleLine 和 PART_Grip。
  • 默认值来自 Splitter Token 或 SharedToken,保证现有视觉不变。
  • Gallery API 表、Token 表、ShowCase 示例和回归测试同步更新。

10. 测试与验证

推荐验证:

  • 纯文档改动运行 git diff --check 并检查相对链接。
  • 控件 API 或行为变更运行 tests/AtomUI.Desktop.Controls.Tests。
  • Gallery 示例、API 表或 Token 表变更运行 tests/AtomUIGallery.Tests 中 Splitter 相关测试。
  • 主题变更检查 SplitterTheme.axaml、SplitterHandleTheme.axaml、SplitterDragBarTheme.axaml 中 TemplateBinding、TokenResource 和 selector 是否一致。
  • AOT、生成器或动态数据路径变更按 Gallery NativeAOT 发布流程验证。