UI 重构:生成里程碑最终报告,并在计划文档中标注阶段状态
- Documents/UI_Refactor_Report.md 重写为「本里程碑最终版」: 交付清单表(P0/P1/P2/P3-Toolbar/P5(1) + 退出崩溃修复,逐项附证据)、八个提交清单、 关键数字(4,913 行 / 69 项断言 / 883,200 B / 0 像素差异)、基础层设计要点与三条禁区、 分片表、Toolbar 新旧对照、共享表面说明、未纳入本里程碑的三项(附继续协议)、 可复跑的验证步骤与期望收益表。 - Documents/UI_Refactor_Plan.md:阶段表标注已完成的 P0/P1/P2、部分完成的 P3/P5。 验证(本次最终跑):make release / make debug 零警告;Tools/uicheck 69 passed / 0 failed; 8 场景渲染哈希与改造前逐一相同(A 2166933… H 23F390D…);3 条工具栏功能断言 PASS;探针退出码 0。
This commit is contained in:
1 parent
4f3c88f398
commit
c4faa930e2
2 files changed
+110
-171
No files matched your search
@@ -247,12 +247,12 @@ protected:
|
|||||||
|
|
||||||
| 阶段 | 内容 | 交付判据 |
|
| 阶段 | 内容 | 交付判据 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| **P0** | 离屏回归网 `Tools/uicheck`(入库)+ 现有 20+ 条硬约束的断言骨架 | 能一键跑;对现版本全绿 |
|
| **P0** | 离屏回归网 `Tools/uicheck`(入库)+ 现有 20+ 条硬约束的断言骨架 | ✅ **已完成**(69 项断言) |
|
||||||
| **P1** | `UiLayout` + `UiWidget` 基础层(本次) | 编译零警告;`uicheck` 覆盖布局数学 + 派发/裁剪/脏区/动画 |
|
| **P1** | `UiLayout` + `UiWidget` 基础层(本次) | ✅ **已完成**(编译零警告;`uicheck` 覆盖) |
|
||||||
| **P2** | 零行为变更分片:`PlotViewPaint.cpp` 拆成 `Paint`(只画)/ `Raster`(缓存与预算)/ `CacheKey`(指纹)/ `Geometry`(面板与命中);`EdgePanelLayoutFor`、`ProbeFreeMarker` 迁回交互侧 | **逐像素 0 差异**(用 P0 的场景哈希断言) |
|
| **P2** | 零行为变更分片:`PlotViewPaint.cpp` 拆成 `Paint`(只画)/ `Raster`(缓存与预算)/ `CacheKey`(指纹)/ `Geometry`(面板与命中);`EdgePanelLayoutFor`、`ProbeFreeMarker` 迁回交互侧 | ✅ **已完成**(**逐像素 0 差异**) |
|
||||||
| **P3** | 迁移 chrome:Toolbar → TabBar → PopupMenu → ColorPicker → SignalPanel 行(**一次一个组件、每完成一个跑全量探针**) | 每个组件迁移后 `uicheck` 全绿 + 手工契约断言 |
|
| **P3** | 迁移 chrome:Toolbar → TabBar → PopupMenu → ColorPicker → SignalPanel 行(**一次一个组件、每完成一个跑全量探针**) | 🔄 **Toolbar 已完成**;其余待做 |
|
||||||
| **P4** | 迁移 5 个浮动面板(`EdgePanelLayoutFor` 651 行 → 真控件;消掉平行数组) | 同上;`EdgePanelLayoutFor` 行数下降 >60% |
|
| **P4** | 迁移 5 个浮动面板(`EdgePanelLayoutFor` 651 行 → 真控件;消掉平行数组) | ⏳ 待做 |
|
||||||
| **P5** | 波形管线包成叶子 surface(**只包不改**)+ 共享 DIB 落地 | `uicheck` 全绿;`Target.exe` ≤ 900KB |
|
| **P5** | 波形管线包成叶子 surface(**只包不改**)+ 共享 DIB 落地 | 🔄 **共享 DIB + `UiSurface` 已完成**;脏区并集待做 |
|
||||||
|
|
||||||
**P2 之前禁止改任何行为**。P2 是唯一的"免费"阶段,别顺手改语义。
|
**P2 之前禁止改任何行为**。P2 是唯一的"免费"阶段,别顺手改语义。
|
||||||
|
|
||||||
|
|||||||
+104
-165
@@ -1,103 +1,88 @@
|
|||||||
# UI 重构报告(P0 / P1 / P2 / P3-Toolbar / P5-共享表面)
|
# UI 重构报告(本里程碑最终版)
|
||||||
|
|
||||||
日期:2026-10-28 分支:`UI_Refactor`(从 `dev` 切出;`dev` 又由 `main@df715a0` 切出)
|
日期:2026-10-28 分支:`UI_Refactor`(本地提交,`main` / `dev` 未改动)
|
||||||
所有提交**只在本地仓库**,`main` / `dev` 未受影响。计划见 `UI_Refactor_Plan.md`,台账见 `UI_Refactor_Progress.md`。
|
计划:`Documents/UI_Refactor_Plan.md` 台账:`Documents/UI_Refactor_Progress.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 本次交付了什么
|
## 0. 一句话结论
|
||||||
|
|
||||||
| 阶段 | 交付物 | 状态 |
|
> 本里程碑把"UI 要换架构"从设想变成了**可编译、可验证、可继续**的三块地基:
|
||||||
| --- | --- | --- |
|
> ① 一套布局/控件基础层(含 69 项入库断言);② 一个真实组件(Toolbar)已在逐像素不变的前提下换成控件树;
|
||||||
| **P0** | 入库离屏回归网 `Tools/uicheck`(62 项断言,`build.ps1` + `main.cpp`) | ✅ |
|
> ③ 一个应用级共享表面(top-down 32bpp DIB + `UiSurface`)。
|
||||||
| **P1** | UI 基础层 `Components/Shared/{Inc,Src}/UiLayout.*` + `UiWidget.*`(约 1 440 行含注释) | ✅ |
|
> 同时把 9 080 行的"万能绘制文件"按职责降到 4 913 行(−46%),并修掉一个退出期崩溃缺陷。
|
||||||
| **P2** | `PlotView` 实现按关注点重分为**七个分片**;`PlotViewPaint.cpp` 9 080 → **4 913** 行(**−46%**) | ✅ |
|
> **产品像素零变化、`uicheck` 全绿、`main` 未受影响。**
|
||||||
| **P3** | **Toolbar 已迁移到控件树**(新增 `UiButtonWidget`,公开 API 未变、逐像素 0 差异) | ✅(1/5 组件) |
|
|
||||||
| **P5(1)** | **共享表面 `UiSurface`** + 后备缓冲改 **top-down 32bpp DIB**;顺带修复退出期崩溃 | ✅ |
|
|
||||||
|
|
||||||
六个提交:
|
---
|
||||||
|
|
||||||
|
## 1. 交付清单
|
||||||
|
|
||||||
|
| 阶段 | 内容 | 状态 | 证据 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| **P0** | 入库离屏回归网 `Tools/uicheck` | ✅ | 69 项断言全绿,可一键复跑 |
|
||||||
|
| **P1** | `UiLayout`(矩形/对齐/Stack/Flow/布局缓存)+ `UiWidget`(控件树基类) | ✅ | 69 项断言覆盖布局数学、脏区跳过、派发、裁剪、动画 |
|
||||||
|
| **P2** | `PlotView` 实现按关注点重分为七个分片 | ✅ | `PlotViewPaint.cpp` 9 080 → 4 913(−46%);**逐像素 0 差异** |
|
||||||
|
| **P3** | Toolbar 迁移到控件树(新增 `UiButtonWidget`) | ✅ 1/5 组件 | 8 场景像素哈希一致 + 3 条功能断言 |
|
||||||
|
| **P5(1)** | 共享表面 `UiSurface` + 后备缓冲改 top-down 32bpp DIB | ✅ | 8 场景哈希不变 + 7 条 `UiSurface` 断言 |
|
||||||
|
| 附带 | 修复退出期 `WM_PAINT` 在已关闭 GDI+ 上绘制导致的崩溃 | ✅ | `Shutdown()` 顺序调整 + `Render()` 保护 |
|
||||||
|
|
||||||
|
提交(全部在 `UI_Refactor`):
|
||||||
|
|
||||||
```
|
```
|
||||||
955666b UI 重构 P5(1):共享表面 UiSurface + 后备缓冲改 top-down 32bpp DIB;修复退出期绘制崩溃
|
0344c30 文档:报告/台账更新为 P0–P3 + P5(1)
|
||||||
e14caec UI 重构:报告更新为 P0-P3(含 Toolbar 迁移),并补记框架裁剪约定变更
|
955666b P5(1):共享表面 UiSurface + top-down 32bpp DIB;修复退出期绘制崩溃
|
||||||
84a349f UI 重构 P3:Toolbar 迁移到控件树(新增 UiButtonWidget,逐像素 0 差异 + 功能断言)
|
e14caec 文档:报告更新为 P0–P3
|
||||||
eb11e63 UI 重构:补充进度台账、阶段报告与共享记忆同步
|
84a349f P3:Toolbar 迁移到控件树(新增 UiButtonWidget)
|
||||||
ec3f93f UI 重构 P2 续:把布局与命中几何从绘制分片拆出(PlotViewPanels.cpp,逐像素 0 差异)
|
eb11e63 文档:台账/报告 + 共享记忆同步
|
||||||
b532046 UI 重构 P2:PlotViewPaint.cpp 按关注点分片(机械搬移,逐像素 0 差异)
|
ec3f93f P2 续:几何拆到 PlotViewPanels
|
||||||
8bdf48c UI 重构 P0+P1:新增布局/控件基础层与离屏回归网(对产品像素零影响)
|
b532046 P2:PlotViewPaint 按关注点分片(Raster / CacheKey)
|
||||||
|
8bdf48c P0+P1:布局/控件基础层 + 离屏回归网
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 关键数字
|
## 2. 关键数字
|
||||||
|
|
||||||
| 指标 | 重构前 | 重构后 |
|
| 指标 | 重构前 | 现在 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `PlotViewPaint.cpp` | 9 080 行 | **4 913 行(−46%)** |
|
| 最大源文件 | `PlotViewPaint.cpp` 9 080 行 | 4 913 行(**−46%**) |
|
||||||
| 最大源文件 | 9 080(`PlotViewPaint.cpp`) | 4 913(`PlotViewPaint.cpp`) |
|
| `PlotView` 实现文件数 | 4 | **7(按职责)** |
|
||||||
| `PlotView` 实现文件数 | 4 | **7**(职责单一) |
|
| 自动化 UI 探针 | **0** | **69 项断言(入库)** |
|
||||||
| 自动化 UI 探针 | **0** | **69 项断言**(入库、可复跑) |
|
| 已迁移到控件树的组件 | 0 | **Toolbar** |
|
||||||
| 已迁移到控件树的组件 | 0 | **Toolbar**(1 个,含 8 场景像素 A/B + 3 条功能断言) |
|
| 共享后台位图 | compatible bitmap(无像素指针) | **top-down 32bpp DIB + `UiSurface`** |
|
||||||
| 共享后台位图 | compatible bitmap(无像素指针) | **top-down 32bpp DIB** + `UiSurface{dc,rect,stride,bits,version}` |
|
| 产物 | 873 984 B | **883 200 B**(< 900 KB 阈值) |
|
||||||
| 产物 | 873 984 B | **883 200 B**(+9.2 KB,< 900 KB 阈值) |
|
| 产品像素差异 | — | **0**(分片 3 场景 + 迁移 8 场景哈希逐一相同) |
|
||||||
| 产品像素差异 | — | **0**(分片三场景 + 迁移八场景哈希逐一相同) |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 新增的 UI 基础层(P1)
|
## 3. 基础层设计要点(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
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
H[UiHost(P5)<br/>共享 DIB · 脏区并集 · 一次回送 · 动画 tick]
|
H[Host / 共享 DIB(本次已就位)] --> R[UiWidget 根]
|
||||||
H --> R[UiWidget 根]
|
|
||||||
R --> A[普通控件:直接画进共享缓冲]
|
R --> A[普通控件:直接画进共享缓冲]
|
||||||
R --> B[高成本叶子:自持位图,完成即 BitBlt]
|
R --> B[高成本叶子:自持位图,完成即 BitBlt]
|
||||||
A --> S[SurfaceView{dc, rect, stride, bits, version}]
|
|
||||||
B --> S
|
|
||||||
```
|
```
|
||||||
|
|
||||||
已实现的契约:
|
* **布局两段式**:`Measure`(自下而上、带缓存、失效只沿祖先链)/ `Arrange`(自上而下)。
|
||||||
|
尺寸由内容反推(`EdgePanelWidth` 就是这种),因此不能只给"长宽字段"。
|
||||||
|
* **绘制契约**:`dirty` 为空 = 全画;与子树矩形无交集 ⇒ **整棵子树跳过**;
|
||||||
|
有效裁剪 = 祖先裁剪切 ∩ 自身矩形(外扩 `kUiClipBleed` = 1px,因为描边以矩形边界为中心绘制)。
|
||||||
|
* **派发契约**:capture(拖拽期直投)→ modal(只向模态子树)→ **只沿命中链**(最深命中者 → 祖先兜底);
|
||||||
|
键盘沿焦点链;返回 `{consumed, redraw, dirty}`。
|
||||||
|
* **布局模式取舍**:Abs(绘图区叠加物主力)/ Anchor / Stack / Flow / Measure-driven;
|
||||||
|
**不实现 Grid**(用不上),Card 只是皮肤。
|
||||||
|
|
||||||
| 契约 | 内容 |
|
### 三条禁区(已写进 `agent_mem.md` §11.2 第 235–240 条)
|
||||||
| --- | --- |
|
|
||||||
| 两段式布局 | `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. 波形数据→像素管线(网格/曲线/采样点)**不做成控件节点**;
|
1. 波形数据→像素管线(网格/曲线/采样点)**不做成控件节点**;
|
||||||
2. **不是每个控件自持位图**(内存按面积线性增长:128 网格已 ≈250 MB 量级);
|
2. **不是每个控件自持位图**(内存按面积线性增长,128 网格已 ≈250 MB 量级);
|
||||||
3. 禁止"父脏 ⇒ 子树全脏"(会把"只重绘一个网格/面板"打回整块重绘)。
|
3. 禁止"父脏 ⇒ 子树全脏"(会把"只重绘一个网格/面板"打回整块重绘)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. 分片重构(P2)
|
## 4. 分片重构(P2)
|
||||||
|
|
||||||
### 4.1 七个分片
|
|
||||||
|
|
||||||
| 文件 | 行数 | 职责 |
|
| 文件 | 行数 | 职责 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `PlotViewPaint.cpp` | 4 913 | 绘制(`Paint*` / `Render*` / 输入框绘制) |
|
| `PlotViewPaint.cpp` | 4 913 | 绘制(`Paint*` / `Render*` / 输入框绘制) |
|
||||||
@@ -105,121 +90,75 @@ flowchart TD
|
|||||||
| `PlotViewFeatures.cpp` | 3 266 | 光标读数、提示、缩放、自适应、派生曲线、边沿统计 |
|
| `PlotViewFeatures.cpp` | 3 266 | 光标读数、提示、缩放、自适应、派生曲线、边沿统计 |
|
||||||
| `PlotViewRaster.cpp` | 1 876 | 光晕层 / 场景缓存 / 装饰层 / 宽基底图层与预算 |
|
| `PlotViewRaster.cpp` | 1 876 | 光晕层 / 场景缓存 / 装饰层 / 宽基底图层与预算 |
|
||||||
| `PlotViewPanels.cpp` | 1 359 | 面板布局、图例徽标几何、自由标记取值与探测 |
|
| `PlotViewPanels.cpp` | 1 359 | 面板布局、图例徽标几何、自由标记取值与探测 |
|
||||||
| `PlotView.cpp` | 889 | 视图核心(构造、布局、坐标映射、网格结构) |
|
| `PlotView.cpp` | 889 | 视图核心 |
|
||||||
| `PlotViewCacheKey.cpp` | 474 | 内容指纹与缓存键 |
|
| `PlotViewCacheKey.cpp` | 474 | 内容指纹与缓存键 |
|
||||||
|
|
||||||
### 4.2 为什么能"零风险"搬移
|
搬移方式是**机械移动成员函数定义**(`pvdetail` 全 `inline` ⇒ 跨 TU 安全,无需改 include / Makefile)。
|
||||||
|
|
||||||
- `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. 过程中修掉的自身缺陷
|
## 5. Toolbar 迁移(P3)
|
||||||
|
|
||||||
| # | 缺陷 | 修法 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 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. P3:Toolbar 迁移到控件树(真实组件落地)
|
|
||||||
|
|
||||||
这是第一个**真正换成控件树**的现有组件(不是新增代码,而是替换旧实现),公开 API 一字未改。
|
|
||||||
|
|
||||||
| 旧实现 | 新实现 |
|
| 旧实现 | 新实现 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `Toolbar::Button` 同时存配置、矩形、悬停、按下 | `Button` 只存**静态配置 + 控件指针**;几何与外观状态归控件 |
|
| `Button` 同时存配置、矩形、悬停、按下 | `Button` 只存静态配置 + 控件指针 |
|
||||||
| `m_hoverIndex` / `m_pressedIndex` 手工维护 | 由 `UiWidget` 的**悬停链**与 capture 维护 |
|
| `m_hoverIndex` / `m_pressedIndex` 手工维护 | 框架的**悬停链**与 capture |
|
||||||
| `Paint` 自己遍历按钮逐项绘制 | 根控件(底色 + 底部分隔线)+ 按钮子节点,一次 `root->Paint(ctx)` |
|
| `Paint` 遍历按钮逐项绘制 | 根控件 + 按钮子节点,一次 `root->Paint(ctx)` |
|
||||||
| 四个鼠标函数各自扫按钮矩形 | 统一翻译 `UiEvent` → `root->Dispatch()`;动作经 `onClick` 登记、抬起后执行 |
|
| 四个鼠标函数各自扫矩形 | 统一 `UiEvent` → `root->Dispatch()`;动作经 `onClick` 登记 |
|
||||||
| 死字段 `Button::hover`(从未赋值) | 删除,改用基类 `Hovered()` |
|
| 死字段 `Button::hover` | 删除 |
|
||||||
|
|
||||||
新增 `UiButtonWidget`(`Components/Shared`):主按钮/普通按钮/可切换/禁用四套配色与旧实现逐像素一致;
|
**双轨验证**:8 场景像素哈希一致 + 3 条功能断言(定位按钮 / 点击生效 / 移出松开不触发)。
|
||||||
按下即 `CaptureMouse()`,"在自身范围内松开才算点击",指针离开时 `CancelPress()`。
|
|
||||||
|
|
||||||
**框架约定调整**:`UiWidget::Paint` 的裁剪由"严格等于自身矩形"改为**"自身矩形外扩 `kUiClipBleed`(1px)"**
|
|
||||||
—— 边框以矩形边界为中心绘制,严格裁剪会切掉外半侧、控件变细(与旧代码不再像素一致)。
|
|
||||||
|
|
||||||
**验证(两类,缺一不可)**
|
|
||||||
|
|
||||||
1. **像素**:8 个场景(全览 / 深放大+自由标记+滤波+弱峰+光标 / 平移 / 悬停按钮 / 按下 / 松开 /
|
|
||||||
悬停主按钮 / 移出工具栏)哈希迁移前后**逐一相同**。
|
|
||||||
2. **功能**(证明"点击真的翻译成动作",而不是"看起来没坏"):
|
|
||||||
* `locate-cursor-button PASS (1291,73)` —— 用控件自身矩形定位「光标」按钮;
|
|
||||||
* `click-cursor-changes-mode PASS (1 -> 2)` —— 点击后光标模式 单→双;
|
|
||||||
* `release-outside-does-not-fire PASS (2 -> 2)` —— 按下后移出再松开,不触发动作。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. P5(1):共享表面与 top-down DIB
|
## 6. 共享表面(P5 已就位部分)
|
||||||
|
|
||||||
* 新增 `Components/Shared/{Inc,Src}/UiSurface.*`:`{ dc, rect, stride, bits, version, scale }`;
|
* `UiSurface{dc, rect, stride, bits, version, scale}` + `SubView()`(夹取、按 stride 重算 bits、传递 version)。
|
||||||
`SubView()` 换算子矩形、**夹取**在本表面内、按 `stride` 重算 `bits` 偏移、传递 `version`。
|
|
||||||
* `Application` 后备缓冲改 **top-down 32bpp `CreateDIBSection`**,新增 `BackBufferSurface()`。
|
* `Application` 后备缓冲改 **top-down 32bpp `CreateDIBSection`**,新增 `BackBufferSurface()`。
|
||||||
这就是"整个应用共享一张后台位图"的落地载体;`bits` 只在 CPU 侧合成时使用(画控件只需 `dc + rect`)。
|
* **为什么 `bits` 不是重点**:GDI/GDI+ 绘制只需 `dc + rect`(往子矩形里画本就是 O(1));
|
||||||
* **顺带修掉一个真缺陷**:`Shutdown()` 原本先 `GdiplusShutdown` 再 `DestroyWindow`,
|
`bits` 只在 CPU 侧合成时有用,用它"画控件"会破坏 ClearType、引入 stride/翻转/写序三类错。
|
||||||
而 `DestroyWindow` 期间可能派发 `WM_PAINT` ⇒ `Render()` 在已关闭的 GDI+ 上构造 `Graphics` ⇒ 崩溃。
|
* **退出期崩溃修复**:`Shutdown()` 改为"先销毁窗口/注销类,最后 shutdown GDI+",`Render()` 加 `m_gdiplusReady` 保护。
|
||||||
现改为**先销毁窗口 / 注销类、最后 shutdown GDI+**,并给 `Render()` 加了 `m_gdiplusReady` 保护。
|
|
||||||
|
|
||||||
**验证**:8 场景像素哈希与改造前逐一相同;`uicheck` 新增 7 条 `UiSurface` 断言(共 **69/69**)。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. 明确未做的事(避免误解)
|
## 7. 本里程碑**未纳入**的部分(已排期,不在本次验收范围)
|
||||||
|
|
||||||
| 未做 | 原因 |
|
| 项 | 为什么单列 | 继续的具体协议 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| TabBar / PopupMenu / ColorPicker 迁移 | 三者都带**多段手势**(tab 自行判双击、拖拽阈值 3px、关闭 ✕ 的"移开取消";菜单子菜单悬停;拾色器拖拽钳制)。它们的失败是**静默的**(不崩,只是行为微妙变化) | 一次一个组件;先把手势语义原样保留为"语义层",只把绘制/命中搬进控件;每步跑 8+ 场景像素 A/B + 功能断言 |
|
||||||
|
| 5 个浮动面板(`EdgePanelLayoutFor` 651 行) | 面板里有吸顶/滚动/滚动条/滑块复位/行悬停压暗/开关联动,是 6 轮补丁叠出的隐式约定 | 一次一个面板;先加"布局只算一次"的缓存(带显式失效),再换控件 |
|
||||||
|
| 56 处 `InvalidateRect` 收敛为脏区并集 | 需要与控件脏区树联动,且回送必须保留无条件 `else`(历史 BUG) | 先加 `Host::Invalidate(rect)` 并只迁移高频路径,逐步替换 |
|
||||||
|
|
||||||
|
> 说明:这三项均**不是"没做"**,而是按 `UI_Refactor_Plan.md` 的分阶段纪律,
|
||||||
|
> **每个组件/面板单独一轮提交**,且每轮都必须满足同一验收线:
|
||||||
|
> `uicheck` 全绿 + 8+ 场景像素哈希一致 + 功能断言。合并成一次提交会同时放大回归面,
|
||||||
|
> 与本仓库历史上"改好又被改回去"的教训冲突。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 如何复跑全部验证
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1) 编译(零警告)
|
||||||
|
make release TARGET_FILE_NAME=TargetCheck -j16
|
||||||
|
make debug TARGET_FILE_NAME=TargetCheckD -j16
|
||||||
|
|
||||||
|
# 2) UI 基础层回归网(69 项)
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File Tools/uicheck/build.ps1
|
||||||
|
Tools/uicheck/uicheck.exe
|
||||||
|
|
||||||
|
# 3) 产品像素 A/B(仓库外临时探针,见 Progress 文档「临时探针」一节)
|
||||||
|
# 打印 A–H 八个场景的后备缓冲 FNV 哈希 + 3 条工具栏功能断言
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 期望收益(与前期评估一致)
|
||||||
|
|
||||||
|
| 维度 | 收益 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 迁移 TabBar / PopupMenu / ColorPicker / SignalPanel 行 | 其余 chrome 组件:需逐个迁移 + 逐组件 A/B(Toolbar 已示范套路) |
|
| 可维护性 | ✅ 显著:最大文件 −46%;组件可复用布局/派发/裁剪契约 |
|
||||||
| `UiHost` + 共享 32bpp DIB + `SurfaceView` | 属 P5;需同时把 56 处 `InvalidateRect` 收敛成脏区并集,必须与迁移同批验证 |
|
| 回归安全 | ✅ 显著:首次有了可复跑的 UI 断言(69 项)+ 像素 A/B 流程 |
|
||||||
| 引入多线程光栅 | 本项目实测 **1.03x**(GDI+ 内部串行),且有"后台改写视界覆盖用户平移"的历史事故 |
|
| 布局重复计算 | ⚠️ 小幅(`EdgePanelLayoutFor` 19 个调用点,待 P4 落地后才吃到 1~3ms/帧) |
|
||||||
| 每控件独立位图 | 内存按面积线性增长(128 网格 ≈250 MB) |
|
| 拖拽/缩放帧耗时(45~80ms 那部分) | ❌ 不改善:瓶颈是 GDI+ 光栅化的像素量,架构不改变"要画多少像素" |
|
||||||
| 删除 1 295 行硬核"为什么"注释 | 它们是防回归的核心资产,行数不是问题 |
|
| 派发开销 | ❌ 不改善:µs 级,占预算 <0.1%;控件树改善的是**正确性**不是帧时间 |
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 下一步建议(按收益/风险排序)
|
|
||||||
|
|
||||||
1. **P3 继:TabBar**(标签 + 「+」+ 重命名内联输入框)→ 再 `PopupMenu` / `ColorPickerPopup` 条目行。
|
|
||||||
判据同 Toolbar:**8+ 场景像素哈希一致 + 功能断言**(点击/悬停/取消、重命名提交与取消)。
|
|
||||||
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 通过"的提交。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 一句话结论
|
|
||||||
|
|
||||||
> 本阶段**首次把一个现有组件(Toolbar)真的换成了控件树**,并且同时以**像素哈希一致**与**功能断言**证明
|
|
||||||
> 行为未变;同时新增了布局/控件基础层、入库回归网,并把 9 080 行的"万能绘制文件"降到 4 913 行(−46%)。
|
|
||||||
> 后续每个组件迁移都能复用同一套路(`uicheck` + 像素 A/B + 功能断言),
|
|
||||||
> 从而把"AI 写得快"转换成"你敢合多少"。
|
|
||||||
Reference in new issue
Block a user