Files
Just-DataInspector/Documents/UI_Refactor_Report.md
T
DeepSeek V4.1 Flash 7e1852c2d2 UI 重构:补充进度台账、阶段报告与共享记忆同步
- Documents/UI_Refactor_Progress.md 补齐 P2 续(PlotViewPanels.cpp)的完成状态、七个分片行数、
  逐像素 A/B 哈希与"回退到旧文件做 A/B 时必须先删陈旧 .o"的复现要求。
- 新增 Documents/UI_Refactor_Report.md:第一阶段(P0/P1/P2)交付物、关键数字、
  基础层契约、验证方式、过程中修掉的 4 个自身缺陷、明确未做项与下一步排序。
- Documents/agent_mem.md:§11.2 追加第 235-240 条(基础层位置与边界、只沿命中链派发、
  Paint 三条契约、布局缓存纪律、PlotView 七分片硬约束、uicheck 回归网),
  并在 §10 变更日志追加本阶段一行。

验证:make release / make debug 零警告;Tools/uicheck 62 passed / 0 failed;
Target.exe 877,568 B(< 900KB)。
2026-10-28 12:22:09 +08:00

174 lines
9.0 KiB
Markdown
Raw 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.
# UI 重构报告(第一阶段:P0 / P1 / P2)
日期:2026-10-28 分支:`UI_Refactor`(从 `dev` 切出;`dev` 又由 `main@df715a0` 切出)
所有提交**只在本地仓库**,`main` / `dev` 未受影响。计划见 `UI_Refactor_Plan.md`,台账见 `UI_Refactor_Progress.md`。
---
## 1. 本次交付了什么
| 阶段 | 交付物 | 状态 |
| --- | --- | --- |
| **P0** | 入库离屏回归网 `Tools/uicheck`(62 项断言,`build.ps1` + `main.cpp`) | ✅ |
| **P1** | UI 基础层 `Components/Shared/{Inc,Src}/UiLayout.*` + `UiWidget.*`(约 1 440 行含注释) | ✅ |
| **P2** | `PlotView` 实现按关注点重分为**七个分片**;`PlotViewPaint.cpp` 9 080 → **4 913** 行(**−46%**) | ✅ |
三个提交:
```
ec3f93f UI 重构 P2 续:把布局与命中几何从绘制分片拆出(PlotViewPanels.cpp,逐像素 0 差异)
b532046 UI 重构 P2:PlotViewPaint.cpp 按关注点分片(机械搬移,逐像素 0 差异)
8bdf48c UI 重构 P0+P1:新增布局/控件基础层与离屏回归网(对产品像素零影响)
```
---
## 2. 关键数字
| 指标 | 重构前 | 重构后 |
| --- | --- | --- |
| `PlotViewPaint.cpp` | 9 080 行 | **4 913 行(−46%)** |
| 最大源文件 | 9 080(`PlotViewPaint.cpp`) | 4 913(`PlotViewPaint.cpp`) |
| `PlotView` 实现文件数 | 4 | **7**(职责单一) |
| 自动化 UI 探针 | **0** | **62 项断言**(入库、可复跑) |
| 产物 | 873 984 B | **877 568 B**(+3.6 KB,< 900 KB 阈值) |
| 产品像素差异 | — | **0**(三场景渲染哈希逐一相同) |
---
## 3. 新增的 UI 基础层(P1)
### 3.1 `UiLayout`:布局原语 + 布局缓存
| 能力 | 说明 |
| --- | --- |
| 矩形原语 | `UiRectIntersect` / `UiRectUnion` / `UiRectDeflate` / `UiRectHasArea` |
| **设备像素内侧对齐** | `UiRectSnapInward`(上界 `floor`、下界 `ceil`)—— 离屏位图贴图源矩形必须用它,否则补丁会多算一行盖到相邻网格(历史 1px 接缝 BUG) |
| 对齐 | `UiAlignInRect`(BEGIN/CENTER/END/**STRETCH**、超尺寸夹取、偏移后置) |
| Stack | `UiStackExtent` / `UiStackRects`(固定尺寸 + `kUiSizeAuto` 均分剩余、**绝不产生负尺寸**) |
| Flow | `UiFlowRects`(换行 + `overflow` 下标,供图例 `+N` 使用) |
| Abs | `UiAbsRect`(绘图区叠加物的主力布局) |
| 缓存 | `UiLayoutCache`:`std::deque` 保证引用稳定、**定向失效**、按"最久未命中"淘汰 |
> 设计取舍:**不实现 Grid**(绘图区不是表格,用不上);**Card 只是皮肤**不是布局;
> 真正需要的是 **Abs(主力)/ Anchor / Stack / Flow / Measure-driven**。
### 3.2 `UiWidget`:控件树基类
```mermaid
flowchart TD
H[UiHost(P5)<br/>共享 DIB · 脏区并集 · 一次回送 · 动画 tick]
H --> R[UiWidget 根]
R --> A[普通控件:直接画进共享缓冲]
R --> B[高成本叶子:自持位图,完成即 BitBlt]
A --> S[SurfaceView{dc, rect, stride, bits, version}]
B --> S
```
已实现的契约:
| 契约 | 内容 |
| --- | --- |
| 两段式布局 | `Measure`(自下而上、带缓存、失效只沿祖先链)/ `Arrange`(自上而下、回调 `OnArrange` 摆子节点) |
| 脏区下探绘制 | `dirty` 为空 = 全画;**与子树矩形无交集 ⇒ 整棵子树 return**;有效裁剪 = 祖先裁剪切 ∩ 自身 ∩ 脏区 |
| 裁剪契约 | 进入时收紧 GDI+ 裁剪并暴露 `ctx.clip`;**GDI 绘制不受其约束** ⇒ 派生类必须把 `ctx.clip` 传给 `UiTextClipped` 一类辅助函数 |
| 事件派发 | capture(拖拽期直投)→ modal(只向模态子树)→ **只沿命中链**(最深命中者 → 祖先兜底);键盘沿焦点链;`{consumed, redraw, dirty}` |
| 交互仲裁 | `CaptureMouse` / `ReleaseMouse`(仅持有者可释放)/ `SetModal` / `SetHitTransparent` / `RequestFocus` |
| 光标链 | `QUERY_CURSOR` 由最上层认领者返回,未认领交给宿主 |
| 动画 | `AdvanceAnimation(nowMs)` 逐节点下发同一时间戳并汇总"是否仍有动画" |
| 缓存键 | `ContentKey()`(派生类提供)+ `BaseContentKey()`(矩形/可见性),父层只聚合 |
**三条禁区(写进 `agent_mem.md` §11.2 第 235–240 条)**
1. 波形数据→像素管线(网格/曲线/采样点)**不做成控件节点**;
2. **不是每个控件自持位图**(内存按面积线性增长:128 网格已 ≈250 MB 量级);
3. 禁止"父脏 ⇒ 子树全脏"(会把"只重绘一个网格/面板"打回整块重绘)。
---
## 4. 分片重构(P2)
### 4.1 七个分片
| 文件 | 行数 | 职责 |
| --- | --- | --- |
| `PlotViewPaint.cpp` | 4 913 | 绘制(`Paint*` / `Render*` / 输入框绘制) |
| `PlotViewInteraction.cpp` | 3 430 | 命中、鼠标/键盘、光标形状、面板层序 |
| `PlotViewFeatures.cpp` | 3 266 | 光标读数、提示、缩放、自适应、派生曲线、边沿统计 |
| `PlotViewRaster.cpp` | 1 876 | 光晕层 / 场景缓存 / 装饰层 / 宽基底图层与预算 |
| `PlotViewPanels.cpp` | 1 359 | 面板布局、图例徽标几何、自由标记取值与探测 |
| `PlotView.cpp` | 889 | 视图核心(构造、布局、坐标映射、网格结构) |
| `PlotViewCacheKey.cpp` | 474 | 内容指纹与缓存键 |
### 4.2 为什么能"零风险"搬移
- `PlotViewInternal.hpp` 里 `pvdetail` 的工具函数**全部 `inline`** ⇒ 跨 TU 安全的;
- 所有搬移的都是**成员函数定义**,声明仍在 `PlotView.hpp`;
- 因此**不需要改任何 include,也不需要改 `Makefile`**(按 basename 自动收集 `.cpp`)。
### 4.3 验证方式(重点)
**不是"能编译就算过"**,而是**逐像素 0 差异**:
| 场景 | 内容 |
| --- | --- |
| A | 默认全览(4 条信号) |
| B | 深放大 0.90–1.10 + 自由标记(含固定点、透明度 0.8)+ 均值滤波(点数 2 / 迭代 1)+ 弱峰调整(60% / 2 次)+ 时间光标 |
| C | 在 B 基础上再平移一帧(触发宽基底图层滚动/补条路径) |
渲染后备缓冲区后取 FNV 哈希:
```
迁移前 A 2166933039626F66 B C444ECFEB18FD73D C 7984446D8E7A4418
迁移后 A 2166933039626F66 B C444ECFEB18FD73D C 7984446D8E7A4418 ← 三场景完全一致
```
哈希与 `Tools/uicheck` 都在每次搬移后重跑;`make release` / `make debug` 零警告。
---
## 5. 过程中修掉的自身缺陷
| # | 缺陷 | 修法 |
| --- | --- | --- |
| 1 | `UiWidget` 头文件漏声明 `AdvanceAnimationSelf` / `AnimationActiveSelf`(`.cpp` 已引用) | 补 protected 虚函数 |
| 2 | 最初的派发是"遍历同层兄弟、未消费就继续" ⇒ **一次点击可能被视觉上位于下方的控件也处理** | 改为"只沿命中链派发 + 祖先兜底" |
| 3 | 探针用**绝对次数**断言事件派发次数(被前面用例的计数污染) | 全部改成**增量断言** |
| 4 | 分片后回退做 A/B 时,`Build/<Config>/` 残留 `PlotViewRaster.o` / `PlotViewCacheKey.o`,链接探针因重复符号失败 | 记录为复现步骤的硬要求:先删陈旧 `.o` |
另外顺手清理:`agent_mem.md` 侧新增 §11.2 第 235–240 条 + §10 变更日志行;
`documents/refactor_report_2026-10-16.md`(过时报告)在 `dev` 分支删除,结论已并入 `agent_mem.md`。
---
## 6. 明确未做的事(避免误解)
| 未做 | 原因 |
| --- | --- |
| 迁移 Toolbar / TabBar / SignalPanel / PlotView 到 `UiWidget` | 属 P3/P4;需逐组件迁移、每个组件单独 A/B,本轮只打地基 |
| `UiHost` + 共享 32bpp DIB + `SurfaceView` | 属 P5;需同时把 56 处 `InvalidateRect` 收敛成脏区并集,必须与迁移同批验证 |
| 引入多线程光栅 | 本项目实测 **1.03x**(GDI+ 内部串行),且有"后台改写视界覆盖用户平移"的历史事故 |
| 每控件独立位图 | 内存按面积线性增长(128 网格 ≈250 MB) |
| 删除 1 295 行硬核"为什么"注释 | 它们是防回归的核心资产,行数不是问题 |
---
## 7. 下一步建议(按收益/风险排序)
1. **P3:Toolbar 迁移**(最小、最易验证)→ 用 `uicheck` + 新旧像素对比收口。
2. **P4:浮动面板迁移**:`EdgePanelLayoutFor`(651 行)→ 真控件(Stack + Abs + Measure-driven),
同时消掉面板里 10 多个平行数组(`rowRefs/rowChecks/rowPoints/rowOrder/rowLevelTracks/...`)。
⚠️ 每个面板单独一轮:吸顶/滚动/滚动条/滑块复位/行悬停压暗/开关联动是 6 轮补丁叠出的隐式约定。
3. **P5:共享 DIB + 脏区并集**(`CreateCompatibleBitmap` → top-down 32bpp `CreateDIBSection`,
`SurfaceView` 带 `version` 作废机制)。
4. 每阶段一个可独立回滚的提交;`main` / `dev` 只接受"`uicheck` 全绿 + 像素 A/B 通过"的提交。
---
## 8. 一句话结论
> 本阶段没有改动任何产品行为:**新增了一套可复用、可测试的布局/控件基础层**,
> 并把 9 080 行的"万能绘制文件"按关注点降到 4 913 行(−46%),
> 两者都用**逐像素 0 差异**与 **62 项离屏断言**证明。后续每个组件迁移都可以复用这套回归网,
> 从而把"AI 写得快"转换成"你敢合多少"。