# UI 重构进度(UI_Refactor 分支) > 本文件是**跨会话/跨 Agent 的交接台账**。每次提交前更新;每一行都要能对应到一个可复跑的验证结果。 > 计划见 `Documents/UI_Refactor_Plan.md`;硬约束见 `Documents/agent_mem.md` §11.2。 ## 分支状态 | 分支 | 基线 | 用途 | | --- | --- | --- | | `main` | `df715a0` | 可发布,**不受本次重构影响** | | `dev` | `4e51b41`(删除过时报告 `refactor_report_2026-10-16.md`) | 日常开发基线 | | `UI_Refactor` | 从 `dev` 切出 | 本次 UI 重构(全部提交只留在本地) | ## 阶段看板 | 阶段 | 内容 | 状态 | | --- | --- | --- | | **P0** | 离屏回归网 `Tools/uicheck`(入库) | ✅ 完成(62 项断言全绿) | | **P1** | `UiLayout` + `UiWidget` 基础层 | ✅ 完成(编译零警告,`uicheck` 覆盖) | | **P2** | `PlotViewPaint.cpp` 零行为分片 | ✅ 完成(**逐像素 0 差异**,见 §2) | | **P3** | 迁移 chrome(Toolbar / TabBar / 菜单 / 拾色器 / 列表行) | ⏳ 待做 | | **P4** | 迁移 5 个浮动面板 | ⏳ 待做 | | **P5** | 波形管线包成叶子 surface + 共享 DIB | ⏳ 待做 | --- ## 提交记录 ### 1. `P0 + P1`:UI 基础层与离屏回归网 **新增文件** | 文件 | 行数 | 职责 | | --- | --- | --- | | `Components/Shared/Inc/UiLayout.hpp` | ~250 | 布局原语 + `UiLayoutCache` | | `Components/Shared/Src/UiLayout.cpp` | ~300 | 矩形原语 / 对齐 / Stack / Flow / 缓存 | | `Components/Shared/Inc/UiWidget.hpp` | ~330 | 控件基类接口与设计说明 | | `Components/Shared/Src/UiWidget.cpp` | ~560 | 树 / Measure·Arrange / Paint(脏区+裁剪)/ 派发 / 仲裁 / 动画 | | `Tools/uicheck/main.cpp` | ~570 | 离屏回归网(62 项断言) | | `Tools/uicheck/build.ps1` | ~70 | 链接 `Build//*.o`(排除 `Main.o`) | **已实现并验证的行为** - 布局:`UiAlignInRect`(含 `STRETCH`、超尺寸夹取、偏移后置)、`UiRectSnapInward`(设备像素内侧对齐)、 `UiStackRects`(固定 / `kUiSizeAuto` 均分 / **绝不产生负尺寸**)、`UiFlowRects`(换行 + `overflow` 下标)。 - 缓存:`UiLayoutCache` 用 `std::deque` 保证**引用稳定**;容量淘汰按"最久未命中";`Invalidate` 为**定向**失效。 - 布局 pass:`Measure` 命中缓存不重复调用 `OnMeasure`;`InvalidateMeasure` **只**沿祖先链失效(兄弟不重算)。 - 绘制:`dirty` 为空 = 全画;与子树矩形无交集 ⇒ **整棵子树跳过**;有效裁剪 = 祖先裁剪切 ∩ 自身矩形 ∩ 脏区; 不可见节点跳过。 - 派发:**只沿命中链**(最深的命中节点 → 祖先冒泡),**绝不分发给同层兄弟**;`capture` 期间事件直投捕获者; `modal` 只向模态子树派发;`hit-transparent` 让事件穿透;`QUERY_CURSOR` 由最上层认领者返回; 键盘/字符沿**焦点链**派发(无焦点交给根,保证宿主级快捷键可用)。 - 动画:`AdvanceAnimation` 逐节点下发同一时间戳并汇总"是否仍有动画"。 - 悬停:进入 / 离开各只触发一次,跨子节点迁移时正确配对。 **过程中修掉的两个自身缺陷** 1. `UiWidget` 缺 `AdvanceAnimationSelf` / `AnimationActiveSelf` 两个 protected 虚函数(`.cpp` 已引用、头文件漏声明)。 2. 最初的派发实现是"遍历同层兄弟、未消费就继续" ⇒ **一个点击可能同时被视觉上位于下方的控件处理**; 已改为"只沿命中链派发 + 祖先兜底"。回归网里对应的断言从"绝对次数"改为**增量断言** (绝对次数会被前面用例计数污染,本轮踩过一次)。 **验证** ``` make release TARGET_FILE_NAME=TargetCheck # 零警告 powershell -File Tools/uicheck/build.ps1 # 链接 Build/Release/*.o(排除 Main.o) Tools/uicheck/uicheck.exe # 62 passed, 0 failed ``` **未做(有意)** - 未改动任何现有组件(Toolbar / TabBar / SignalPanel / PlotView 仍走旧路径),因此**本提交对产品像素零影响**。 - 未把 `UiWidget` 接入 `Application`,避免在 P2 之前引入行为差异。 --- ### 2. `P2`:`PlotViewPaint.cpp` 按关注点分片(零行为变更) **为什么**:该文件 9 080 行里混住四类内容 —— 真绘制 ≈3.5k / 光栅化缓存 ≈2.5k / 缓存键 ≈1.1k / 布局与命中几何 ≈1.3k。按"函数名前缀"分片是上一轮膨胀的直接原因,本轮按**关注点**再分一次。 **做法**:**机械搬移成员函数定义**(不改一行逻辑)。`PlotViewInternal.hpp` 里的 `pvdetail` 全部 `inline` ⇒ 跨分片安全,无需改任何 include、无需改 `Makefile`(按 basename 自动收集 `.cpp`)。 | 文件 | 搬入内容 | 行数变化 | | --- | --- | --- | | `PlotViewRaster.cpp`(新) | 光晕离屏层(`PaintSignalHalo*` / `UploadHaloLayer` / `ReleaseHaloLayer`)
场景缓存(`Ensure/Capture/Blit/Invalidate/ReleaseSceneCache`)
装饰层(`Ensure/Release/Render/BlitDecorCache` / `ResolveAlphaBlend`)
宽基底图层与预算(`EnsureLaneLayer` … `BlitWideBuffer`、并行重建、滚动补条、预热) | 1 876 | | `PlotViewCacheKey.cpp`(新) | `MakeSceneCacheKey` / `SceneCacheMatches` / `SceneHaloRef`
`SceneContentHash` / `SceneGeometryHash` / `SceneDecorHash`
`CollectSceneStyles` / `FindStyleOnlyLanes` / `CollectSceneDerived` / `RepaintSceneCachePatch` | 474 | | `PlotViewPaint.cpp` | 只剩绘制(`Paint*` / `Render*` / 输入框绘制) | 9 080 → **6 252**(−31%) | **验证(关键)** 1. `make release TARGET_FILE_NAME=TargetCheck` **零警告**;`Target.exe` 873 984 → **875 008 B**(+1 KB, 属分片后各 TU 的对齐差异;仍远低于 900 KB 阈值)。 2. **逐像素 A/B**:临时写了一个渲染哈希探针(仓库外),对三个场景渲染后备缓冲区并取 FNV 哈希: * A 默认全览;B 深放大 + 自由标记(含固定点)+ 均值滤波 + 弱峰调整 + 时间光标;C 再平移一帧(触发宽基底滚动/补条)。 * **分片前**:`A 2166933039626F66 / B C444ECFEB18FD73D / C 7984446D8E7A4418` * **分片后**:**完全相同**(三个场景逐一一致)。 * 该探针未入库(一次性验证用),结论记录在此以便复现。 3. `Tools/uicheck` 62/62 全绿。 4. 顶层注释从"四个分片"更新为"六个分片(按关注点)",并写明"`Paint/PaintLane` 仍只允许定义在 `PlotViewPaint.cpp`"。 **⚠️ 复现 A/B 的坑**:机械搬移后用旧的 `.bak` 回退时,`Build//` 里会残留 `PlotViewRaster.o` / `PlotViewCacheKey.o`,链接探针(`Build/Release/*.o`)会因重复符号失败 —— **必须先删掉这两个陈旧 `.o`** 再链接。 --- ## 下一步(接手者请从这里开始) 1. **P2 续**:把"布局与命中几何"也搬出去(`EdgePanelLayoutFor` 651 行 + `ProbeFreeMarker` 286 行 + `FreeMarkerLabelRects` 118 行 → 新 `PlotViewPanels.cpp`),判据同上(逐像素 0 差异)。 2. **P3:Toolbar 优先**(最小、最容易验证的组件),迁移后跑 `uicheck` + 新旧像素对比。 3. 每个阶段单独提交,提交信息写清"改了什么 / 为什么 / 怎么验证"。