## 1. 新功能:齿轮按钮 -> 应用设置菜单 -> 显示布局边界 - ToolbarAction::SETTINGS 位于「导入文件」左侧;工具栏布局改为 「左侧固定簇(kLeftClusterCount = 2:齿轮 + 导入文件)+ 右侧按钮组」。 - 齿轮是矢量化绘制(8 齿 + 外环 + 轴孔,全部按 half 的比例推导), 不用字体字符「⚙」——该字符在不同回退字体里大小与基线不一致,会忽大忽小、垂直不居中。 UiButtonWidget 新增 UiButtonIcon(NONE / GEAR)与 SetIcon。 - MenuScope::APP_SETTINGS(新增)+ 设置菜单:HEADER「调试」+ 勾选切换项「显示布局边界」 (keepOpen:点击后菜单保持打开,勾选态由 UpdateItemLabel 回写)。 锚点取 Toolbar::ButtonRect(SETTINGS) 的控件实测矩形,宿主不再自己推算按钮几何。 ## 2. 布局边界:基类给协议,组件自己上报 - UiComponent 新增 virtual PaintDebugBounds(g, scale),默认只画 Area()。 - Toolbar / TabBar 覆盖:跳过与区域重合的根控件,遍历 m_root->Children()(层级 1); TabBar 另上报每个 ✕(层级 2)。 - SignalPanel 覆盖:表头 / 筛选框 / 列表 / 样式区(1)+ 滚动条、样式行、当前可见列表行(2)。 - PlotView 覆盖:每个 LaneVisual::rect(1);不上报随数据变化的瞬态几何(曲线/胶囊/提示)。 - UiWidget 新增 PaintDebugBounds(g, scale, depth) 递归整棵控件树 —— 已上树的控件零成本获得边界。 - 宿主 PaintLayoutBounds 补客户区外框、分隔条、打开的菜单/拾色器。 视觉与实现约束(Android「Show layout bounds」风): - 只描边、不填充(几十个嵌套矩形叠加填充会把内容糊住); - 层级配色 青蓝(2px) / 品红 / 琥珀 / 绿(1px); - 线宽按设备像素固定(不乘 scale),临时 PixelOffsetModeNone + 关抗锯齿 + 整像素对齐, 保证 1px 就是 1px; - ⚠️ 只在 Render 最后一步叠加,绝不进入任何位图缓存(PlotView 4 层缓存 / SignalPanel 行位图缓存), 否则关掉开关后线条会残留; - ⚠️ fullFrame 并入 m_showLayoutBounds:开启时必须整帧回送,否则「只回送绘图区」的快路径 会让装饰区边界停在旧帧上。 ## 3. 可见收益优化(行为不变) - Toolbar::Layout:控件树改为按需重建(m_treeDirty),只在规格/字体/主题/缩放/动态文本 变化时重建。拖动面板分隔条时 Relayout 每帧调用一次 -> 不再每帧 10 次堆分配 + 8 次测量缓存失效。 - Toolbar::SetActionLabel:只写 m_labelOverrides(唯一权威)+ 置树脏,消除「覆盖表与控件文本 双写」可能不同步的问题。 - UiWidget::DispatchRedraw(event):新增;收敛 Toolbar / TabBar 里重复的 Dispatch(); return UiRectHasArea(TakeDirty());(TakeDirty 会清零,漏一次就把下一次真实变化 误判成「无变化」)。 ## 4. 验证 - make release / make debug:零 error 零 warning;TargetCheck.exe 897,024 B(< 900 KB)。 - 新增离屏探针 .preflight/lbcheck:21 / 21 PASS (齿轮在导入文件左侧/同高/更窄;开启后青蓝 24,930 px、品红 19,734 px;关闭后逐像素还原; 菜单 scope、锚定贴合按钮、宽度未被撑大、条目初始未勾选、点击翻转开关、菜单保持打开)。 - Tools/rendercheck:8 个场景的「工具栏带以下区域」哈希与改造前**逐一相同** (证明改动像素被完全限制在工具栏带内);整帧哈希因新增按钮整体变化,属预期。 FUNC 断言 7 / 7 PASS。 - Tools/uicheck:69 passed / 0 failed。 ## 5. 文档 Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md 追加第六阶段; Documents/agent_mem.md 同步变更日志与「2026-10-28 第八轮」硬约束与验证坑。
404 lines
24 KiB
Markdown
404 lines
24 KiB
Markdown
# UI 重构进度(UI_Refactor 分支)
|
||
|
||
> 本文件是**跨会话/跨 Agent 的交接台账**。每次提交前更新;每一行都要能对应到一个可复跑的验证结果。
|
||
> 计划见 `Documents/UI_Refactor_Plan.md`;硬约束见 `Documents/agent_mem.md` §11.2。
|
||
|
||
## 分支状态
|
||
|
||
| 分支 | 基线 | 用途 |
|
||
| --- | --- | --- |
|
||
| `main` | `df715a0` | 可发布,**不受本次重构影响** |
|
||
| `dev` | `4e51b41` | 日常开发基线;本分支上已 **删除过时报告** `Documents/refactor_report_2026-10-16.md`(−508 行) |
|
||
| `UI_Refactor` | 从 `dev` 切出 | 本次 UI 重构(全部提交只留在本地) |
|
||
|
||
**分支操作取证(可直接复跑)**
|
||
|
||
```powershell
|
||
git show 4e51b41 --stat --oneline # dev: Documents/refactor_report_2026-10-16.md | 508 ----------
|
||
Test-Path Documents\refactor_report_2026-10-16.md # False(已不在工作区/分支里)
|
||
git log --oneline --all -- Documents/refactor_report_2026-10-16.md # 末条即为 4e51b41 的删除提交
|
||
git log --oneline main -1 # df715a0(main 未被任何重构提交触碰)
|
||
```
|
||
|
||
## 阶段看板
|
||
|
||
> **本计划已全部结案**:可达成项已落地(P0/P1/P2/P3-Toolbar+TabBar/P5-共享表面),
|
||
> 其余项经评估**不采用**并写明理由(见 `UI_Refactor_Report.md` §7)—— 计划里没有悬空阶段。
|
||
|
||
| 阶段 | 内容 | 结论 |
|
||
| --- | --- | --- |
|
||
| **P0** | 离屏回归网 `Tools/uicheck` | ✅ 已落地(69 项断言) |
|
||
| **P1** | `UiLayout` + `UiWidget` 基础层 | ✅ 已落地 |
|
||
| **P2** | `PlotViewPaint.cpp` 零行为分片 | ✅ 已落地(9 080→4 913,逐像素 0 差异) |
|
||
| **P3** | chrome 迁移 | ✅ Toolbar / TabBar 已迁移;PopupMenu / ColorPicker / SignalPanel 行**经评估不采用** |
|
||
| **P4** | 5 个浮动面板控件化 | **经评估不采用**(理由见 Report §7) |
|
||
| **P5** | 共享表面 `UiSurface` + top-down 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/<Config>/*.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`)<br>场景缓存(`Ensure/Capture/Blit/Invalidate/ReleaseSceneCache`)<br>装饰层(`Ensure/Release/Render/BlitDecorCache` / `ResolveAlphaBlend`)<br>宽基底图层与预算(`EnsureLaneLayer` … `BlitWideBuffer`、并行重建、滚动补条、预热) | 1 876 |
|
||
| `PlotViewCacheKey.cpp`(新) | `MakeSceneCacheKey` / `SceneCacheMatches` / `SceneHaloRef`<br>`SceneContentHash` / `SceneGeometryHash` / `SceneDecorHash`<br>`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/<Config>/` 里会残留
|
||
`PlotViewRaster.o` / `PlotViewCacheKey.o`,链接探针(`Build/Release/*.o`)会因重复符号失败 ——
|
||
**必须先删掉这两个陈旧 `.o`** 再链接。
|
||
|
||
---
|
||
|
||
### 3. `P2 续`:把"布局与命中几何"从绘制分片里拆出(`PlotViewPanels.cpp`)
|
||
|
||
**为什么**:`EdgePanelLayoutFor`(651 行,19 个调用点)、`EdgePanelWidth`、`LaneBadgeRect`、
|
||
`FreeMarkerValue/Delta/Contents/LabelRects`、`ProbeFreeMarker` 都是**几何**,却被塞在绘制分片里 ——
|
||
改一次布局要动 9 千行的绘制文件,回归面被放大。
|
||
|
||
**做法**:同样机械搬移(5 个不连续块),`PlotViewPaint.cpp` 从 6 645 → **4 913** 行。
|
||
|
||
**最终七个分片**(按关注点)
|
||
|
||
| 文件 | 行数 | 职责 |
|
||
| --- | --- | --- |
|
||
| `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 | 内容指纹与缓存键 |
|
||
|
||
**累计效果**:`PlotViewPaint.cpp` **9 080 → 4 913(−46%)**,最大文件从 9.1k 降到 4.9k。
|
||
|
||
**验证**
|
||
|
||
1. `make release TARGET_FILE_NAME=TargetCheck` 零警告;`Target.exe` **877 568 B**(< 900 KB)。
|
||
2. **逐像素 A/B**:同三场景哈希与迁移前**完全一致**
|
||
(`A 2166933039626F66 / B C444ECFEB18FD73D / C 7984446D8E7A4418`)。
|
||
3. `Tools/uicheck` 62/62。
|
||
4. 顶层注释更新为"七个分片(按关注点)",并标明 `Paint/PaintLane` 只允许定义在 `PlotViewPaint.cpp`。
|
||
|
||
---
|
||
|
||
### 4. `P3`:Toolbar 迁移到控件树(**逐像素 0 差异**)
|
||
|
||
**新增**:`Components/Shared/{Inc,Src}/UiButtonWidget.*` —— 通用"文字按钮"控件,视觉规则与旧 Toolbar
|
||
按钮逐像素一致(主按钮强调色实底 / 普通按钮 1px 描边 / 可切换选中态 / 禁用态配色);
|
||
交互由自己处理:按下即 `CaptureMouse()` 进入按下态,**在自身范围内**松开才触发 `onClick`,
|
||
指针离开时 `CancelPress()`。
|
||
|
||
**`Toolbar` 内部重写(公开 API 一字未改)**:
|
||
|
||
| 旧实现 | 新实现 |
|
||
| --- | --- |
|
||
| `std::vector<Button>` 里既存配置又存矩形/悬停/按下 | `Button` 只存**静态配置 + 控件指针**;几何与外观状态归控件 |
|
||
| `m_hoverIndex` / `m_pressedIndex` 手工维护 | 由 `UiWidget` 的**悬停链**与 capture 维护 |
|
||
| `Paint` 自己遍历按钮绘制 | 根控件(`ToolbarRootWidget`:底色 + 底部分隔线)+ 按钮子节点,`root->Paint(ctx)` |
|
||
| `OnMouseMove/Down/Up/Leave` 各自扫按钮矩形 | 统一翻译成 `UiEvent` → `root->Dispatch(event)`;动作经 `onClick` 回调登记、抬起后执行 |
|
||
| `Button::hover` 死字段 | 删除(`Hovered()` 由基类提供) |
|
||
|
||
**新增框架约定(重要)**:`UiWidget::Paint` 的裁剪由"严格等于自身矩形"改为
|
||
**"自身矩形外扩 `kUiClipBleed`(1px)"**。原因:边框/描边以矩形边界为中心绘制,1px 笔宽有一半在矩形外,
|
||
严格裁剪会把外半侧切掉 ⇒ 控件看起来变细(与旧绘制代码不再逐像素一致)。
|
||
`uicheck` 对应断言改为"裁剪 ⊇ 自身矩形且不超出外扩范围"。
|
||
|
||
**验证**
|
||
|
||
1. `make release` / `make debug` **零警告**;`Target.exe` **881 664 B**(< 900 KB)。
|
||
2. **逐像素 A/B(8 个场景)**:迁移前后哈希逐一相同
|
||
`A 2166933… / B C444ECF… / C 7984446… / D BF85587… / E 1C663E4… / F 0535292… / G 7A58172… / H 23F390D…`
|
||
(A 全览;B 深放大+自由标记+均值滤波+弱峰调整+光标;C 平移;D 悬停按钮;E 按下未松开;
|
||
F 松开;G 悬停主按钮;H 移出工具栏)。
|
||
3. **功能验证**(证明点击真的翻译成动作,而不是"看起来没坏"):
|
||
* `FUNC locate-cursor-button PASS (1291,73)` —— 用控件自身矩形定位按钮;
|
||
* `FUNC click-cursor-changes-mode PASS (1 -> 2)` —— 点击「光标」按钮后光标模式 单→双;
|
||
* `FUNC release-outside-does-not-fire PASS (2 -> 2)` —— 按下后移出再松开,不触发动作。
|
||
4. `Tools/uicheck` **62/62**。
|
||
|
||
---
|
||
|
||
### 5. `P5(1)`:共享表面 `UiSurface` + 后备缓冲改 top-down 32bpp DIB(**逐像素 0 差异**)
|
||
|
||
**新增** `Components/Shared/{Inc,Src}/UiSurface.*`:`{ dc, rect, stride, bits, version, scale }`。
|
||
`SubView(localRect)` 把子矩形换算到宿主坐标、**夹取**在本表面内、按 `stride` 重算 `bits` 偏移、传递 `version`。
|
||
|
||
> ⚠️ 文件头注释里明确写了取舍:**画控件只需要 `dc + rect`**;`bits` 只在 CPU 侧合成
|
||
> (memcpy / alpha 混合 / 重采样)时才有意义。用 `bits` 直写像素来"画"控件会破坏 ClearType、
|
||
> 引入 stride / 翻转 / 写序三类错,是净亏损。
|
||
|
||
**`Application`**:
|
||
* 后备缓冲由 `CreateCompatibleBitmap` 改为 **top-down 32bpp `CreateDIBSection`**
|
||
(`biHeight` 取负 ⇒ `bits` 从第一行开始、y 向下递增);
|
||
* 新增 `m_backBits` / `m_backStride` / `m_backVersion`,重建位图时 `++m_backVersion`;
|
||
* 新增 `BackBufferSurface()` 对外暴露整块表面。
|
||
|
||
**顺带修掉一个真实缺陷(退出期访问违例)**:
|
||
|
||
| 问题 | 修法 |
|
||
| --- | --- |
|
||
| `Shutdown()` 里先 `GdiplusShutdown` 再 `DestroyWindow`:`DestroyWindow` 期间派发的 `WM_PAINT` 会进 `Render()`,在**已关闭**的 GDI+ 上构造 `Graphics` ⇒ 崩溃 | 顺序改为 **先销毁窗口 / 注销类,最后 shutdown GDI+** |
|
||
| 任何晚到的绘制都可能踩到这条路径 | `Render()` 开头加保护:`!m_gdiplusReady || m_hwnd == nullptr` 直接返回 |
|
||
|
||
> 另:临时探针此前的退出崩溃其实来自"探针自己又调了一次 `GdiplusShutdown`"(`Application::Shutdown`
|
||
> 内部已 shutdown)——与本轮修复无关,但顺序调整与 `Render` 保护保留为防御性正确做法。
|
||
|
||
**验证**
|
||
|
||
1. 8 场景像素哈希与 DIB 改造前**逐一相同**;3 条工具栏功能断言仍 PASS。
|
||
2. `Tools/uicheck` **69/69**(新增 7 条 `UiSurface` 断言:bits 按 stride 偏移、矩形夹取、
|
||
完全在外返回无效表面、`version`/`scale` 传递)。
|
||
3. `make release` / `make debug` 零警告;`Target.exe` **883 200 B**(< 900 KB)。
|
||
|
||
---
|
||
|
||
### 6. `P3(2)`:TabBar 迁移到控件树(**逐像素 0 差异**)
|
||
|
||
| 旧实现 | 新实现 |
|
||
| --- | --- |
|
||
| `m_hoverIndex` / `m_hoverClose` / `m_hoverAdd` | 删除;悬停由框架悬停链维护,脏区用 `root->TakeDirty()` |
|
||
| `Paint` 一次画全条 | 根控件(底色)+ 标签控件 + 「+」控件 + **命中透明覆盖层**(拖拽条 + 分隔线,必须最后画) |
|
||
| 关闭 ✕ 热区手算 | 标签控件用 `HoverPoint()` 自行判定 |
|
||
|
||
**手势语义一字未改**(自行判双击、3px 拖拽阈值、`DropIndexAt`、✕ 的“移开取消”)。
|
||
|
||
验证:8 场景哈希一致;4 条交互断言全 PASS
|
||
(hover 只脏一次 / 「+」新建 / 点击切换 active=0 / ✕ 关闭 2→1);`uicheck` 69/69;产物 886,784 B。
|
||
|
||
---
|
||
|
||
## 后续可选工作(**计划项均已结案**,以下为将来若要推进时的入口)
|
||
|
||
本计划的每个阶段都已有结论(落地 / 不采用),**当前没有悬空待办**。若将来要重新开启,
|
||
按下列入口与判据推进;判据固定为:`uicheck` 全绿 + **8+ 场景像素哈希一致** + **功能断言**。
|
||
|
||
1. 若要将 `PopupMenu` / `ColorPicker` 控件化(目前评估为不采用,见 Report §7):
|
||
沿用 TabBar 的套路——手势语义原样保留,只把"绘制 + 命中矩形"搬进控件;
|
||
先用 `Tools/rendercheck` 记录菜单/拾色器的场景哈希,再逐控件替换。
|
||
2. 若要将 5 个浮动面板控件化(目前评估为不采用):先做"布局只算一次"的缓存
|
||
(必须用**覆盖全部输入的键**,否则就是历史上"漏混字段 → 贴旧像素"那类静默 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)
|
||
```
|
||
|
||
---
|
||
|
||
## 第八轮(2026-10-28):显示布局边界 + 三处可见收益优化
|
||
|
||
### 进度总览
|
||
|
||
| # | 任务 | 状态 | 验证 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | `Toolbar` 控件树按需重建(`m_treeDirty`) | ✅ | 8 场景区域哈希不变 |
|
||
| 2 | `Toolbar::SetActionLabel` 单一真值来源 | ✅ | 光标按钮文本仍随模式变化 |
|
||
| 3 | `UiWidget::DispatchRedraw` 收敛重复判断 | ✅ | `uicheck` 69/69 |
|
||
| 4 | `UiButtonWidget` 支持矢量图标(GEAR) | ✅ | 放大 4 倍核对形状 |
|
||
| 5 | `ToolbarAction::SETTINGS` + 左侧固定簇布局 | ✅ | 4 条几何断言 |
|
||
| 6 | `MenuScope::APP_SETTINGS` + 设置菜单 | ✅ | 5 条菜单断言 |
|
||
| 7 | `UiComponent::PaintDebugBounds` 协议 + 四组件上报 | ✅ | 无开关时零像素影响 |
|
||
| 8 | `UiWidget::PaintDebugBounds` 递归控件树 | ✅ | 品红描边 19 734 px |
|
||
| 9 | `Application::PaintLayoutBounds` + 整帧回送 | ✅ | 关闭后逐像素还原 |
|
||
| 10 | 文档更新 | ✅ | — |
|
||
|
||
### 关键文件
|
||
|
||
**新增**
|
||
* `Components/Shared/Inc/UiDebugBounds.hpp` / `Src/UiDebugBounds.cpp` —— 调试边界描边原语
|
||
(层级配色、只描边不填充、整像素锐利对齐)。
|
||
* `Components/Shared/Src/UiComponent.cpp` —— `PaintDebugBounds` 默认实现(只画 `Area()`)。
|
||
|
||
**修改**
|
||
* `Shared/Inc/UiComponent.hpp`、`Shared/Inc|Src/UiWidget.hpp/.cpp`、`Shared/Inc|Src/UiButtonWidget.hpp/.cpp`
|
||
* `Toolbar/Inc|Src/*`、`TabBar/Inc|Src/*`、`SignalPanel/Inc/SignalPanel.hpp` + `Src/SignalPanel.cpp`
|
||
* `PlotView/Inc/PlotView.hpp` + `Src/PlotViewPaint.cpp`
|
||
* `Shared/Inc/PopupMenu.hpp`(`MenuScope::APP_SETTINGS`、`Rect()`)、`Shared/Inc/ColorPickerPopup.hpp`(`Rect()`)
|
||
* `Application/Inc/Application.hpp`、`Inc/ApplicationInternal.hpp`、
|
||
`Src/Application.cpp`(Render 末尾叠加 + `fullFrame`)、`Src/ApplicationCommands.cpp`(菜单/派发/边界绘制)
|
||
|
||
### 验证数据
|
||
|
||
```
|
||
make release (TargetCheck) -> 零 error / 零 warning, 897,024 B
|
||
.preflight/lbcheck -> 21 passed, 0 failed
|
||
INFO area-pixels=24930 node-pixels=19734
|
||
INFO gear=(15.0,55.0 50.0x37.5) menu=(15.0,97.0 185.0x73.8)
|
||
rendercheck 工具栏以下区域哈希(改造前 == 改造后):
|
||
AB AABF558F499583EA BB 1B8B2D389D41B988 CB CAC17AA78C515079 DB 154617738CD9C422
|
||
EB DA484B02EEEC778A FB B3CE740F60CF3688 GB 4DD14C0C2EC4CE91 HB D84400B723089F43
|
||
rendercheck FUNC 7/7 PASS
|
||
uicheck 69 passed, 0 failed
|
||
```
|
||
|
||
> 说明:整帧哈希 A–H 因为工具栏左侧多了一个齿轮按钮而整体变化(预期),
|
||
> 因此给探针加了「工具栏带以下」的区域哈希用于**证明改动范围被完全限制在工具栏内**。
|
||
|
||
### 验证坑(本轮新增)
|
||
|
||
* PS 5.1 读中文 `.ps1` 会乱码 ⇒ 探针/脚本里只写 ASCII,中文注释另存 UTF-8 无 BOM 数据文件。
|
||
* 想拿"改造前"的区域基线必须 `git stash push -u`(**含未跟踪文件**),否则新增的
|
||
`UiDebugBounds.cpp` / `UiComponent.cpp` 仍会被 Makefile 的 `Src/*.cpp` 通配扫到而编译失败。
|
||
* 「工具栏带以下」的起点必须用**运行时** `m_toolbarRect.Y + Height`;写死 70 会切进工具栏,
|
||
齿轮正好落在被统计的区间里,导致本应相同的哈希全都不同。
|