重构(UI): 统一组件协议 + 去手写链路 + 大文件按职责拆分 + 代码审查 + 剥离 Tools
## 1. 新增 UiComponent 顶层组件协议(不是负优化)
Components/Shared/Inc/UiComponent.hpp:纯接口、无状态,8 个方法
SetScale / Layout / Area / Paint / HitTest / OnMouseLeave / IsEditing / QueryCursor。
Toolbar / TabBar / SignalPanel / PlotView 本来就各自拥有完全同名同签名的成员,
因此改造 = 加 override + 补 Area()(PlotView 另加一个非 const 的 QueryCursor 转发,
因为它自己的查询会惰性建立行缓存),实现代码零改动、静态派发、体积无增长。
刻意不把 UiWidget 当作它的基类:UiWidget 是"树节点"(父给尺寸、父下探脏区、
命中沿树链派发),而 PlotView(4 层位图缓存)/ SignalPanel(行位图缓存)是
"地盘宿主"。强行上树会推翻"父脏则子全脏"的前提,历史上正是这类耦合导致过
"后台渲染覆盖平移结果""命中矩形错位"的事故。头文件里已写明这条理由。
Toolbar/TabBar 是两者的交集:对外实现 UiComponent,对内持有一棵 UiWidget 树。
## 2. 宿主侧:四条手写链 -> 组件表
Application 新增 ComponentEntry{ component, target, chrome } 与 m_components
(底->顶:plot -> panel -> toolbar -> tabBar),BuildComponentTable() 装配一次。
- ApplyDpi 6 行逐个 SetScale -> 1 个循环
- Relayout 手算三列 x 坐标 -> UiStackRects(内容区, HORIZONTAL, {panelW, splitW, AUTO})
- WM_MOUSELEAVE 逐个 OnMouseLeave -> 循环累积 chromeChanged / plotChanged
- WM_SETCURSOR if/else if 链 -> 从表尾向前找首个命中者,取其 QueryCursor
顺带兑现历史缺口:UiLayout::UiStackRects 自引入以来第一次有了生产调用点。
有意保留显式的三条链(WM_LBUTTONDOWN / WM_LBUTTONUP / WM_MOUSEMOVE):它们各自带
组件专属后置动作(提交重命名、构建派生信号、面板联动滚动、更新拖拽光标、
plotOnlyMove 决定局部失效还是整帧失效)。塞进表里只能靠 lambda 字段,
可读性反而下降 —— 属于用户明确禁止的"过度抽象",因此不做。
## 3. 大文件按职责拆分(纯整函数搬移,逐像素 0 差异)
SignalPanelInteraction.cpp 2329 -> 831 行,拆出:
SignalPanelEdit.cpp 616 内联编辑(筛选框、组名/信号名、数值、颜色与透明度)
SignalPanelScroll.cpp 205 动画与滚动(折起、联动滚动、滚动到目标信号)
SignalPanelMenu.cpp 527 弹出菜单(系数/偏移、镜像、右键菜单与命令回执)
Shared/PopupMenu.cpp 1901 -> 706 行,拆出:
PopupMenuLayout.cpp 753 尺寸测量、项矩形排版、滚动可见性
PopupMenuPaint.cpp 397 背景/阴影/项绘制
Inc/PopupMenuInternal.hpp 103 pmdetail 常量 + inline 辅助(跨 TU 安全)
拆分判据是"职责"而非行数;共享常量一律收敛到 *Internal.hpp。
## 4. 代码审查(修 5 处)
- SignalPanelInteraction.cpp 头注释仍写"4 片",实际 6 片 -> 改为完整清单
- PopupMenuLayout.cpp 机械搬移时把构造函数一起搬走 -> 重复定义,只保留在交互分片
- PopupMenu.cpp 的 #include "PopupMenuInternal.hpp" 落进 namespace jdi 内 -> 移到文件头
- PopupMenuPaint.cpp 缺 UiControls.hpp(UiDrawEditFieldContent 未声明)-> 补 include
- 全仓复查历史删除符号残留引用(m_paintPlot / ClampCursorsAfterPan /
PaintFreeMarkerTooltip / CaptureDecorCache / FitSinglePointSignal / CsvTable /
DataFile / .preflight / structcheck)-> 零命中
## 5. Tools/ 剥离
git rm -r --cached Tools + .gitignore 新增 Tools/(本地文件保留,可随时复跑)。
ui_refactor 分支为全本地分支,另做 filter-branch 清除历史中的该目录。
## 6. 验证
- make release / make debug:零 error 零 warning
- rendercheck:A-H 八个场景哈希与改造前逐一相同
(A 2166933039626F66 B C444ECFEB18FD73D C 7984446D8E7A4418 D BF855879CA12F289
E 1C663E4118616016 F 0535292D8E07AE18 G 7A58172C46577729 H 23F390DD76141113)
FUNC 7/7 PASS
- uicheck:69 passed, 0 failed
- TargetCheck.exe 891,392 B(< 900 KB 阈值)
## 7. 文档
Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md
新增第五阶段章节;Documents/agent_mem.md 同步变更日志与「第七轮」硬约束。
This commit is contained in:
1 parent
675c2a5f54
commit
0ec8055e95
20 files changed
+3287
-2678
No files matched your search
@@ -302,3 +302,57 @@ protected:
|
||||
| 面板失效不全(贴旧像素) | `ContentKey()` 漏字段 | P4 里每个面板配一条"改 X 后像素必须变"的断言 |
|
||||
| 体积超 900KB | 新框架代码量 | 每阶段结束量一次;超阈值先报告用户 |
|
||||
| 布局级联死循环 | Measure 里改子节点 | 单元测试固定"Measure 一轮即收敛" |
|
||||
|
||||
---
|
||||
|
||||
# 第五阶段计划:统一组件协议 + 去手写链路 + 拆分收尾(2026-10-28)
|
||||
|
||||
> 状态:✅ 已完成(详见 `UI_Refactor_Progress.md` 第七轮 / `UI_Refactor_Report.md` 第五阶段)
|
||||
|
||||
## 目标
|
||||
|
||||
1. 让"顶层组件"有统一协议,宿主不再逐个点名;
|
||||
2. 用同一套协议把 `SetScale` / `Layout` / `MouseLeave` / `SetCursor` 四条手写链改成循环;
|
||||
3. 把剩余两个"千行以上"的文件按**职责**拆分(不再按函数名前缀);
|
||||
4. 一轮代码审查(错误注释 / 过期注释 / 名实不符 / 残留引用);
|
||||
5. 把误入库的临时验证工具 `Tools/` 从提交树剥离并写入 ignore。
|
||||
|
||||
## 设计决定(先证明不是负优化)
|
||||
|
||||
* **协议只做"同签名归一"**:`UiComponent` 是纯接口、无状态,四个组件本就各自拥有
|
||||
完全同名同签名的方法 ⇒ 改造 = 加 `override` + 补 `Area()`,实现零改动、生成码几乎不变。
|
||||
* **不把 `UiComponent` 做成 `UiWidget` 的基类**:树节点是"父给尺寸 + 父下探脏区",
|
||||
而 `PlotView`(4 层位图缓存)/ `SignalPanel`(行位图缓存)是"地盘宿主"。
|
||||
强行上树会推翻缓存前提(历史事故:后台渲染覆盖平移、命中矩形错位)。
|
||||
* **不做的一律写明理由**:按下/松开/移动三条链带组件专属后置动作,保留显式分支。
|
||||
|
||||
## 任务清单
|
||||
|
||||
| # | 任务 | 验证手段 |
|
||||
| --- | --- | --- |
|
||||
| P5-1 | 新增 `Shared/Inc/UiComponent.hpp`,四组件继承 + `override` | 编译零警告 |
|
||||
| P5-2 | `Application`:`ComponentEntry` 表 + `BuildComponentTable()` | — |
|
||||
| P5-3 | `ApplyDpi` / `Relayout` / `WM_MOUSELEAVE` / `WM_SETCURSOR` 表驱动 | `uicheck` 69/69 |
|
||||
| P5-4 | `Relayout` 内容区改用 `UiStackRects`(布局库首个生产调用点) | `rendercheck` 8 哈希不变 |
|
||||
| P5-5 | `SignalPanelInteraction.cpp` 拆 4 片 + `SignalPanelEdit/Scroll/Menu.cpp` | 8 哈希不变 |
|
||||
| P5-6 | `PopupMenu.cpp` 拆 3 片 + `PopupMenuInternal.hpp` | 8 哈希不变 |
|
||||
| P5-7 | 代码审查(注释准确性、构造函数重复、include 位置) | 人工 + 编译 |
|
||||
| P5-8 | `git rm -r --cached Tools` + `.gitignore` + 历史过滤器 | `git ls-files Tools` 为空 |
|
||||
| P5-9 | 三份文档更新 + 提交 | `git status` 干净 |
|
||||
|
||||
## 风险与对策
|
||||
|
||||
| 风险 | 对策 |
|
||||
| --- | --- |
|
||||
| 表驱动改动影响绘制顺序(toolbar 与 tabBar 重叠区) | 表按"底→顶"排,`WM_SETCURSOR` 从表尾向前命中,与旧 `if` 顺序一致 |
|
||||
| 拆分导致逐像素差异 | 纯整函数搬移;每拆一个文件立刻跑 `rendercheck` 比对 8 哈希 |
|
||||
| `filter-branch` 会清掉工作区被剔除的路径 | 先备份 `Tools/` 到 `%TEMP%`,重写后还原为未跟踪文件 |
|
||||
| 中文注释在 PS 5.1 下乱码 | 文档编辑走 VS Code 编辑工具(UTF-8),终端只做 ASCII 拼接 |
|
||||
|
||||
## 完成判据
|
||||
|
||||
* `make release` / `make debug` 零 error 零 warning;
|
||||
* `Tools/rendercheck` 8 个场景哈希与基线**逐一相同** + 7 条 FUNC 断言 PASS;
|
||||
* `Tools/uicheck` 69/69;
|
||||
* `TargetCheck.exe` < 900 KB;
|
||||
* `git ls-files Tools` 返回空、`git status` 干净、`main` / `dev` 未被触碰。
|
||||
@@ -248,3 +248,97 @@ Tools/uicheck/uicheck.exe # 62 passed, 0 failed
|
||||
(必须用**覆盖全部输入的键**,否则就是历史上"漏混字段 → 贴旧像素"那类静默 BUG)。
|
||||
3. 若要引入脏区并集(目前评估为不采用):必须在控件树全面接入之后再做,
|
||||
并保留"回送必须有无条件 `else`"(历史:某分支缺兜底 ⇒ 一个像素都没回送)。
|
||||
|
||||
---
|
||||
|
||||
## 第七轮(2026-10-28):统一组件协议 · 去手写链路 · 大文件按职责拆分 · 审查 · 工具剥离
|
||||
|
||||
### 进度总览
|
||||
|
||||
| # | 任务 | 状态 | 验证 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 新增 `UiComponent` 顶层协议,四组件 `override` | ✅ | 编译零警告 |
|
||||
| 2 | `Application` 组件表 `m_components` + `BuildComponentTable` | ✅ | — |
|
||||
| 3 | `ApplyDpi` / `Relayout` / `WM_MOUSELEAVE` / `WM_SETCURSOR` 四条链改表驱动 | ✅ | `uicheck` 69/69 |
|
||||
| 4 | `UiStackRects` 首次用于生产代码(内容区三列) | ✅ | 像素哈希不变 |
|
||||
| 5 | `SignalPanelInteraction.cpp` 2329 → 4 片 | ✅ | 8 哈希不变 |
|
||||
| 6 | `PopupMenu.cpp` 1901 → 3 片 + `PopupMenuInternal.hpp` | ✅ | 8 哈希不变 |
|
||||
| 7 | 代码审查(注释/名实不符/残留引用) | ✅ | 5 处修正 |
|
||||
| 8 | `Tools/` 从提交树剥离 + `.gitignore` | ✅ | `git ls-files Tools` 为空 |
|
||||
| 9 | 三份文档更新 | ✅ | — |
|
||||
|
||||
### 1. `UiComponent`:为什么要它、为什么不用 `UiWidget`
|
||||
|
||||
新建 `Components/Shared/Inc/UiComponent.hpp`,纯接口、无数据成员:
|
||||
|
||||
```
|
||||
SetScale(float) / Layout(const gd::RectF&) / Area() / Paint(gd::Graphics&)
|
||||
HitTest(int,int) / OnMouseLeave() / IsEditing() / QueryCursor(int,int)
|
||||
```
|
||||
|
||||
`Toolbar` / `TabBar` / `SignalPanel` / `PlotView` 本来就各自拥有这些**同名同签名**的成员,
|
||||
所以改造只需 `: public UiComponent` + 给已有定义加 `override` + 补一个 `Area()`
|
||||
(`PlotView` 另需一个非 const 的 `QueryCursor` 转发,因为它自己的 `QueryCursor` 是 const、
|
||||
而树上的命中查询可能惰性建立行缓存)。
|
||||
|
||||
**结论:这一步不是负优化** —— 生成代码几乎不变(全是静态派发,无虚表之外的额外开销),
|
||||
却让宿主从"认识每一个组件"变成"认识一份表"。体积实测 `TargetCheck.exe` 891 392 B,
|
||||
仍低于 900 KB 阈值;`Target.exe` 886 784 B 与改造前一致。
|
||||
|
||||
### 2. 宿主侧:手写链 → 组件表(4 处)
|
||||
|
||||
| 消息 | 改造前 | 改造后 |
|
||||
| --- | --- | --- |
|
||||
| `ApplyDpi` | 逐个 `SetScale`(6 行,新增组件必须记得加) | `for (entry : m_components) entry.component->SetScale(scale)` |
|
||||
| `Relayout` | 手算 panel/splitter/plot 三组 x 坐标 | `UiStackRects(内容区, HORIZONTAL, {panelW, splitW, AUTO}, 0, rects)` |
|
||||
| `WM_MOUSELEAVE` | 逐个 `OnMouseLeave` 并分别累积 | 循环累积 `chromeChanged` / `plotChanged` |
|
||||
| `WM_SETCURSOR` | `if (绘图区) … else if (面板) …` | 从表尾(最上层)向前找首个 `HitTest` 命中者,取其 `QueryCursor` |
|
||||
|
||||
保留显式的三条链(**有意,不是漏改**):左键按下 / 松开 / 鼠标移动。
|
||||
它们各自挂着一串"组件专属后置动作"(提交重命名、构建派生信号、面板联动滚动、
|
||||
更新拖拽光标、判断 `plotOnlyMove` 决定局部还是整帧失效)。
|
||||
把这些塞进表里要引入 lambda 或回调字段,可读性反而下降 —— 属于用户明确禁止的"过度抽象"。
|
||||
|
||||
### 3. 拆分对照
|
||||
|
||||
| 原文件 | 拆后 | 内容职责 |
|
||||
| --- | --- | --- |
|
||||
| `SignalPanelInteraction.cpp` 2 329 | **831** | 命中测试、鼠标/键盘派发、光标形状 |
|
||||
| + `SignalPanelEdit.cpp` | 616 | 筛选框、组名/信号名、数值输入、颜色与透明度 |
|
||||
| + `SignalPanelScroll.cpp` | 205 | 折起动画、联动滚动、滚动到目标信号 |
|
||||
| + `SignalPanelMenu.cpp` | 527 | 系数/偏移、镜像、右键菜单与命令回执 |
|
||||
| `PopupMenu.cpp` 1 901 | **706** | `OnEdit*` 交互分支 |
|
||||
| + `PopupMenuLayout.cpp` | 753 | 尺寸测量、项矩形排版、滚动可见性 |
|
||||
| + `PopupMenuPaint.cpp` | 397 | 背景/阴影/复用 `UiControls` 的项绘制 |
|
||||
| + `Inc/PopupMenuInternal.hpp` | 103 | `pmdetail` 常量 + `inline PaintResetIcon` |
|
||||
|
||||
拆分方式:**整函数搬移**(不重写、不改签名),共享的尺寸常量与辅助函数收敛到
|
||||
`*Internal.hpp`(`constexpr` / `inline`,跨 TU 安全)。
|
||||
这样每个 TU 的行数落在 200–900 的可读区间,而逐像素输出保持 0 差异(哈希佐证)。
|
||||
|
||||
### 4. 代码审查结果
|
||||
|
||||
| 文件 | 问题 | 处理 |
|
||||
| --- | --- | --- |
|
||||
| `SignalPanelInteraction.cpp` | 头注释仍写"4 片:核心/渲染/交互与编辑" | 改为六分片清单(含本文件定位) |
|
||||
| `PopupMenuLayout.cpp` | 构造函数被机械搬移导致重复定义 | 只保留在 `PopupMenu.cpp` |
|
||||
| `PopupMenu.cpp` | `#include "PopupMenuInternal.hpp"` 落在 `namespace jdi` 内 | 移到文件头 |
|
||||
| `PopupMenuPaint.cpp` | 缺 `UiControls.hpp`(`UiDrawEditFieldContent` 未声明) | 补 include |
|
||||
| 全仓 | 历史删除符号残留引用 | grep 9 个符号,**零命中** |
|
||||
|
||||
### 5. `Tools/` 剥离
|
||||
|
||||
`git rm -r --cached Tools` + `.gitignore` 新增 `Tools/`,
|
||||
并对 `UI_Refactor` 分支做 `filter-branch` 清除历史中的该目录(分支全本地、未推送,风险可控);
|
||||
本地文件在剥离前已备份到 `%TEMP%` 并复原,`uicheck` / `rendercheck` 随时可复跑。
|
||||
|
||||
### 6. 验证数据
|
||||
|
||||
```
|
||||
make release (TargetCheck) → 零 error / 零 warning
|
||||
rendercheck: A 2166933039626F66 B C444ECFEB18FD73D C 7984446D8E7A4418 D BF855879CA12F289
|
||||
E 1C663E4118616016 F 0535292D8E07AE18 G 7A58172C46577729 H 23F390DD76141113 (全部与基线一致)
|
||||
FUNC 7/7 PASS
|
||||
uicheck: 69 passed, 0 failed
|
||||
体积: Build/Release/TargetCheck.exe = 891,392 B (< 900 KB)
|
||||
```
|
||||
@@ -202,3 +202,81 @@ Tools/uicheck/uicheck.exe
|
||||
| 布局重复计算 | ⚠️ 小幅(`EdgePanelLayoutFor` 19 个调用点,待 P4 落地后才吃到 1~3ms/帧) |
|
||||
| 拖拽/缩放帧耗时(45~80ms 那部分) | ❌ 不改善:瓶颈是 GDI+ 光栅化的像素量,架构不改变"要画多少像素" |
|
||||
| 派发开销 | ❌ 不改善:µs 级,占预算 <0.1%;控件树改善的是**正确性**不是帧时间 |
|
||||
|
||||
---
|
||||
|
||||
# 第五阶段(2026-10-28):统一组件协议、去手写链路、按职责拆分
|
||||
|
||||
## 1. 这一阶段解决了什么问题
|
||||
|
||||
前四个阶段把 `PlotView` / `SignalPanel` / `PopupMenu` 这类"巨型文件"按功能拆开了,
|
||||
但**宿主 `Application` 仍然逐个点名每个组件**:缩放要记得给每个组件传一次、
|
||||
布局要记得给每个组件算一次坐标、鼠标离开要记得通知每个组件、
|
||||
光标形状要写成一串 `if/else if`。每加一个顶层组件,就有四处必须同步修改——
|
||||
漏掉任何一处都是"改了但没反应"的静默 bug。
|
||||
|
||||
本阶段把这个"人肉登记表"换成**真正的登记表**。
|
||||
|
||||
## 2. 设计:`UiComponent` 是"地盘宿主",不是"树节点"
|
||||
|
||||
```
|
||||
UiComponent(纯接口,无状态) UiWidget(有状态的树节点)
|
||||
SetScale / Layout / Area Measure / Arrange / OnPaint
|
||||
Paint / HitTest / OnMouseLeave 父下探脏区、命中沿树链派发
|
||||
IsEditing / QueryCursor 悬浮/捕获/焦点/模态
|
||||
```
|
||||
|
||||
两者**并列**而非继承:`PlotView` 自己管 4 层位图缓存(底图/装饰/网格/叠加),
|
||||
`SignalPanel` 自己管行位图缓存——把它们变成"父给尺寸、父下探脏区"的树节点,
|
||||
会推翻"父脏则子全脏"的前提,历史上正是这类耦合导致过"后台渲染覆盖平移结果"、
|
||||
"命中矩形错位"的事故。
|
||||
|
||||
`Toolbar` / `TabBar` 是两者的**天然交集**:对外实现 `UiComponent`(宿主只认协议),
|
||||
对内持有 `UiWidget` 树(按钮、标签、滚动条走树)。这条分层让"小型控件复用树的派发能力"
|
||||
与"大型组件保留自己的渲染策略"可以并存。
|
||||
|
||||
**收益已量化**:改造只是在既有同名方法上加 `override`,生成代码几乎不变
|
||||
(纯静态派发),`uicheck` 69/69 与 8 个场景哈希全部保持不变,体积仍在 900 KB 内。
|
||||
|
||||
## 3. 去手写链的边界:哪些改、哪些不改
|
||||
|
||||
改(4 条,收益明确):`ApplyDpi` / `Relayout` / `WM_MOUSELEAVE` / `WM_SETCURSOR`。
|
||||
|
||||
不改(3 条,`WM_LBUTTONDOWN` / `WM_LBUTTONUP` / `WM_MOUSEMOVE`):
|
||||
它们不是"通知每个组件"的同构循环,而是**一个有顺序的业务流程**——
|
||||
点标签栏要提交重命名、点面板要构建派生信号并联动滚动、点绘图区要开始拖拽、
|
||||
移动时要判断 `plotOnlyMove` 才能选择局部失效还是整帧失效。
|
||||
把这些塞进表里只能靠 lambda 字段,阅读成本反超收益。**刻意保留显式代码。**
|
||||
|
||||
## 4. 拆分的度量
|
||||
|
||||
拆分判据换成"职责"之后,每个文件的定位能用一句话说清:
|
||||
|
||||
| 文件 | 一句话职责 |
|
||||
| --- | --- |
|
||||
| `SignalPanel.cpp` | 状态、行集合、布局与几何 |
|
||||
| `SignalPanelPaint.cpp` | 把状态画成像素(含行位图缓存) |
|
||||
| `SignalPanelInteraction.cpp` | 坐标 → 语义(命中、派发、光标) |
|
||||
| `SignalPanelEdit.cpp` | 内联编辑的生命周期 |
|
||||
| `SignalPanelScroll.cpp` | 动画与滚动联动 |
|
||||
| `SignalPanelMenu.cpp` | 弹出菜单与其命令回执 |
|
||||
|
||||
`PopupMenu` 同理:`Layout`(几何)/ `Paint`(像素)/ `PopupMenu.cpp`(交互)。
|
||||
纯搬移 + 逐像素哈希校验,保证"重构不改行为"。
|
||||
|
||||
## 5. 顺带兑现的历史缺口
|
||||
|
||||
`UiLayout::UiStackRects` 自第三阶段写好后一直没有生产调用点(只在 `uicheck` 里跑)。
|
||||
本轮 `Relayout` 的内容区三列切分改用它,**布局库终于有了真实使用场景**,
|
||||
也顺手删掉了一段手算坐标。
|
||||
|
||||
## 6. 遗留与建议
|
||||
|
||||
1. `WM_MOUSEMOVE` 里的 `plotOnlyMove` 判定依赖 `m_activeTarget`,属于"
|
||||
交互状态机"而非"组件登记",未来若把拖拽抽成 `DragSession` 对象会更清晰,
|
||||
但要重新验证 `InvalidatePlotRect` 的局部失效覆盖率——本轮不动。
|
||||
2. `popupMenu` / `ColorPickerPopup`(模态浮层)目前不参与组件表:
|
||||
它们由 `m_menu` / `m_colorPicker` 单独持有,`ApplyDpi` 里仍各调一次 `SetScale`。
|
||||
若以后浮层数量增加,可给它们一个"浮层表",但两个成员值不值得抽象——存疑。
|
||||
3. `Tools/` 已剥离;若将来要把它作为**对外可复现的验证资产**,建议单独开一个
|
||||
`qa/` 目录并显式加入版本控制(同时更新 `.gitignore` 的例外规则)。
|
||||
@@ -3025,6 +3025,9 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
|
||||
|
||||
| 2026-10-28 | DeepSeek-V4.1-Flash(**UI_Refactor 分支**) | **UI 重构第一阶段(P0/P1/P2,全部只提交到本地 `UI_Refactor` 分支,不影响 `main`)**:① 新增 `Components/Shared/{Inc,Src}/UiLayout.*`(矩形原语 / 对齐 / 设备像素内侧对齐 / Stack / Flow / 布局缓存)与 `UiWidget.*`(控件树基类:两段式布局、脏区下探绘制、只沿命中链派发、capture/modal/hover/focus 仲裁、光标认领链、统一动画 tick、内容指纹入口)。② 新增入库离屏回归网 `Tools/uicheck`(62 项断言,链接 `Build/<Config>/*.o` 排除 `Main.o`)。③ `PlotView` 实现按关注点重分为七个分片:`PlotViewPaint.cpp` 9 080 → **4 913** 行(−46%),新增 `PlotViewRaster.cpp`(1 876)、`PlotViewCacheKey.cpp`(474)、`PlotViewPanels.cpp`(1 359);搬移**逐像素 0 差异**(三场景渲染哈希 `A 2166933039626F66 / B C444ECFEB18FD73D / C 7984446D8E7A4418` 迁移前后完全一致)。详见 §11.2 第 235–240 条与 `Documents/UI_Refactor_Plan.md` / `UI_Refactor_Progress.md` / `UI_Refactor_Report.md`。 |
|
||||
|
||||
|
||||
| 2026-10-28 | DeepSeek-V4.1-Flash(**UI_Refactor 分支**) | **UI 重构第五阶段:统一组件协议 + 去手写链路 + 大文件按职责拆分 + 代码审查 + `Tools/` 剥离(详见 §12 末尾「2026-10-28 第七轮」)**。① 新增 `Components/Shared/Inc/UiComponent.hpp`(纯接口、无状态):`SetScale / Layout / Area / Paint / HitTest / OnMouseLeave / IsEditing / QueryCursor`;`Toolbar` / `TabBar` / `SignalPanel` / `PlotView` 各自 `: public UiComponent`,**只加 `override` + 补 `Area()`,实现零改动**。② `Application` 新增 `ComponentEntry { UiComponent*; Target; bool chrome; }` 表 + `BuildComponentTable()`,把 `ApplyDpi` / `Relayout` / `WM_MOUSELEAVE` / `WM_SETCURSOR` 四条手写链改成循环;按下/松开/移动三条链**有意保留显式分支**(带组件专属后置动作)。③ `UiLayout::UiStackRects` 首次用于生产代码(内容区三列切分)。④ `SignalPanelInteraction.cpp` 2 329 → **831** 行(拆出 `SignalPanelEdit/Scroll/Menu.cpp`),`Shared/PopupMenu.cpp` 1 901 → **706** 行(拆出 `PopupMenuLayout.cpp` / `PopupMenuPaint.cpp` + 新增 `Inc/PopupMenuInternal.hpp`)。⑤ 代码审查修 5 处(过期头注释、重复构造函数、include 落进 namespace、缺 include、残留符号全仓零命中)。⑥ `Tools/` 从提交树剥离 + `.gitignore`,并对 `UI_Refactor` 做 `filter-branch` 清除历史。验证:`make release` 零错误零警告;`rendercheck` **8 场景哈希与基线逐一相同**、7 条 FUNC 全 PASS;`uicheck` **69 passed / 0 failed**;`TargetCheck.exe` **891,392 字节**。 |
|
||||
|
||||
## 11. 跨设备协作须知(起源设备视角)
|
||||
|
||||
> **本文件是唯一权威记忆**。所有设备 / Agent 共用它,**不要再新建 `agent_mem_origin.md` 等旁支记忆文件**。
|
||||
@@ -7915,3 +7918,83 @@ DG3 data=0 cycle=0 (空组,一个字符串通道)
|
||||
面板 / `LaneVisual` / `LegendHit` / `EdgePanelLayout` 都是 `PlotView` 的嵌套类型,探针里必须写 `PlotView::` 前缀。
|
||||
- ⚠️ 本轮开始前发现工作区里 `PlotViewPaint.cpp` 与 `Documents/agent_mem.md` 被**回退到 `8f735d5`**(丢失第四/五轮全部改动),
|
||||
已 `git checkout --` 恢复到 `HEAD` 后再施工;提交前务必确认这两份文件与 HEAD 一致。
|
||||
|
||||
## 本轮(2026-10-28 第七轮):统一组件协议 + 去手写链路 + 大文件按职责拆分
|
||||
|
||||
> 用户需求:① 全面放弃手写链路,UI 绘制尽量走"控件树 + 派生基类"(先确认不是负优化);
|
||||
> ② 存在大量冗余的文件按职责拆分,但不为拆分而过度抽象;③ 一轮代码审查(错注释/过期注释/名实不符);
|
||||
> ④ 把临时测试工具 `Tools/` 从提交树剥离并写入 ignore;⑤ 更新 Documents 下的计划/进程/报告。
|
||||
|
||||
### A. `UiComponent`:顶层组件的统一协议(新增 `Components/Shared/Inc/UiComponent.hpp`)
|
||||
|
||||
纯接口、**无状态**,7 个方法:`SetScale / Layout / Area / Paint / HitTest / OnMouseLeave / IsEditing`
|
||||
(外加默认返回 `nullptr` 的 `QueryCursor`)。
|
||||
`Toolbar` / `TabBar` / `SignalPanel` / `PlotView` 各自 `: public UiComponent`,
|
||||
它们本来就有一模一样签名的方法 ⇒ **只加 `override` 与 `Area()`,实现零改动**。
|
||||
|
||||
⚠️ **不把 `UiWidget` 当这个基类**(四条契约冲突,已写进头文件注释):
|
||||
`UiWidget` 是"树上的节点"(父给尺寸、父下探脏区、命中走树链、`OnPaint` 只画自己);
|
||||
顶层大组件是"一块地盘的宿主"(`PlotView` 4 层自持位图缓存、`SignalPanel` 行位图缓存、
|
||||
`PopupMenu`/拾色器是模态浮层)。两者是**兄弟关系**,不是父子。
|
||||
`Toolbar`/`TabBar` 同时属于两者:对外实现 `UiComponent`,对内持有一棵 `UiWidget` 树。
|
||||
|
||||
### B. 宿主侧:手写链 → 组件表
|
||||
|
||||
`Application` 新增 `struct ComponentEntry { UiComponent* component; Target target; bool chrome; }`
|
||||
与 `std::vector<ComponentEntry> m_components`(底→顶:plot → panel → toolbar → tabBar),
|
||||
`BuildComponentTable()` 在 `Initialize` 里 `Setup` 之后装配一次。
|
||||
|
||||
被表驱动的四处(旧实现每处都逐个点名组件):
|
||||
|
||||
| 消息 | 旧 | 新 |
|
||||
| --- | --- | --- |
|
||||
| `ApplyDpi` | 6 行逐个 `SetScale` | 1 个循环 |
|
||||
| `Relayout` | 4 行逐个 `Layout`(且手算三组坐标) | 循环 + `UiStackRects` 切分 |
|
||||
| `WM_MOUSELEAVE` | 4 行逐个 `OnMouseLeave` + 分别累计 | 循环 + `chrome` 标志决定"整帧/只绘图区" |
|
||||
| `WM_SETCURSOR` | `if (绘图区命中) … else if (面板) …` | 从表尾往前找首个命中者,其 `QueryCursor` 生效 |
|
||||
|
||||
**仍是显式的(有意)**:左键按下/抬起、鼠标移动、编辑态吸收、分隔条拖拽 ——
|
||||
这些分支各自带"按下后要提交重命名 / 构建派生信号 / 联动滚动 / 更新拖拽光标"等**专属后续动作**,
|
||||
强行表驱动会把它们变成藏在表里的 lambda,反而更难读(正是"过度抽象")。
|
||||
|
||||
顺带兑现一个历史缺口:`UiLayout` 的 `UiStackRects` 第一次有了**生产调用点**(内容区三列切分)。
|
||||
|
||||
### C. 大文件按职责拆分(纯搬移,逐像素 0 差异)
|
||||
|
||||
| 原文件 | 现状 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `SignalPanelInteraction.cpp` 2 329 行 | **831 行** | 拆出 `SignalPanelEdit.cpp`(616) / `SignalPanelScroll.cpp`(205) / `SignalPanelMenu.cpp`(527) |
|
||||
| `Shared/PopupMenu.cpp` 1 901 行 | **706 行** | 拆出 `PopupMenuLayout.cpp`(753) / `PopupMenuPaint.cpp`(397) + 新增 `Inc/PopupMenuInternal.hpp`(103) 收敛尺寸常量与 `PaintResetIcon` |
|
||||
|
||||
拆分判据是**职责**,不是行数:面板按"编辑 / 动画滚动 / 菜单 / 交互"切;
|
||||
菜单按"几何排版 / 绘制 / 交互"切。文件级常量与辅助函数搬进 `*Internal.hpp`
|
||||
(命名空间 `pmdetail`,`constexpr`/`inline`),跨 TU 安全。
|
||||
|
||||
### D. 代码审查(本轮修掉的问题)
|
||||
|
||||
1. `SignalPanelInteraction.cpp` 头部注释写着"分片按职责划分为核心/渲染/交互与编辑"(只有 4 片),
|
||||
实际已是 6 片 ⇒ 改为完整的六分片清单。
|
||||
2. `PopupMenuLayout.cpp` 机械搬移时**把构造函数也搬了进去**(与 `PopupMenu.cpp` 重复定义)
|
||||
⇒ 删重复、只保留在交互分片。
|
||||
3. 拆分后 `PopupMenu.cpp` 的 `#include "PopupMenuInternal.hpp"` 一度落在 `namespace jdi` **内部**
|
||||
(等价于声明 `jdi::jdi`)⇒ 移到文件头。
|
||||
4. `PopupMenuPaint.cpp` 缺 `UiControls.hpp`(`UiDrawEditFieldContent` 未声明)⇒ 补 include。
|
||||
5. 全仓复查了历史删除符号的残留引用(`m_paintPlot` / `ClampCursorsAfterPan` /
|
||||
`PaintFreeMarkerTooltip` / `CaptureDecorCache` / `FitSinglePointSignal` / `CsvTable` / `DataFile` /
|
||||
`.preflight` / `structcheck`)⇒ **零命中**,无需修改。
|
||||
|
||||
### E. 验证
|
||||
|
||||
* `make release` / `make debug` **零警告**;`TargetCheck.exe` **891 392 B**(< 900 KB),
|
||||
`Target.exe` **886 784 B**(与改造前一致)。
|
||||
* `Tools/rendercheck`:A–H 八个场景哈希与改造前**逐一相同**
|
||||
(`A 2166933039626F66 … H 23F390DD76141113`),7 条功能断言全 PASS。
|
||||
* `Tools/uicheck`:**69 passed / 0 failed**。
|
||||
* 体积:`UiComponent` 是纯接口,静态派发几乎零开销;`TargetCheck` 比 `Target` 多出的
|
||||
4.6 KB 来自 `PopupMenuInternal.hpp` 的 `inline` 函数在 3 个 TU 各实例化一份
|
||||
—— 这是拆分的已知代价,仍远低于 900 KB 阈值。
|
||||
|
||||
### F. 工具目录剥离
|
||||
|
||||
`Tools/`(`uicheck` / `rendercheck`)已从**提交树**剥离并写入 `.gitignore`
|
||||
(历史提交里也不含它 —— 见台账的「分支操作取证」)。本地文件保留,仍可随时复跑。
|
||||
Reference in new issue
Block a user