feat(UI): 布局边界改为四角粗/四边细包角 + 全元素覆盖 + 几何同源(RowRect O(1))

## 1. 边界观感:四角粗、四边细

用户反馈"边框就是实心矩形、粗细不一,效果不佳"。旧实现是四条等宽实心矩形
(层级 0 用 2px、其余 1px),同一元素的角与边粗细不一致,父子矩形共边时还会叠成更粗的一条。

新规则:
- 四条边固定 1 设备像素(四条独立实心矩形;不用 DrawRectangle,笔宽奇偶会偏半像素);
- 四个角额外加粗:层级 0 = 4px、其余 3px,沿水平/垂直各延伸 cornerLen(≤14px 且 ≤ 短边一半);
- 层级 > 0 整体内缩 1px,父子矩形共边不再叠粗;
- 绝不填充(几十层嵌套填充会把内容糊住);
- 整像素对齐 + PixelOffsetModeNone + 关抗锯齿。

四角粗不只是观感:多条边重合时靠角的形状分辨层次(父=青蓝大包角、子=同位置品红小包角)。

## 2. 协议:组件"收集 box",宿主统一绘制

UiComponent::CollectDebugBoxes(UiDebugBoxList&)  默认只登记 Area()
UiWidget::CollectDebugBoxes(boxes, depth)        递归整棵控件树
Application::PaintLayoutBounds                   收集后按 depth 升序一次性描边

刻意保持 box 为**值类型**而不是让它继承控件基类:PlotView 的 4 层位图缓存、
SignalPanel 的 192 槽行位图缓存都要求"绘制由组件自己负责",几何进继承体系会与
缓存键/失效体系打架。box 不参与绘制,因此能让绘制/命中/调试共用同一份几何。

## 3. 覆盖补齐(此前大量元素被裹在外层单位里)

- Toolbar:区域 / 每个按钮 / **按钮内容(文字或齿轮图标,新增 UiButtonWidget::ContentRect())**
- TabBar:区域 / 标签条 / 新建按钮 / 每个 ✕
- SignalPanel:4 分区 + 筛选输入框/清空✕/全部显示 + 滚动轨道与滑块
  + 样式区 5 行及其 12 色块/自定义/3 模式/3 调整/线宽与透明度轨道和数值
  + 目标行/名称/GX 徽标 + **每个可见行**的折起三角/复选框/色块/名称/GX 徽标/✕
  (组行另有组名/计数)+ 内联编辑框
- PlotView:区域 / 每个网格 / GX 徽标 / 装饰层矩形 / 图例托底+色块+文本
  / **5 类浮动面板全部 20+ 字段**(标题/✕/折起三角/徽标/编辑行/按钮/内容区/滚动轨道与滑块
  /透明度轨道与数值/两个开关/各行复选框与数值与滑轨与复位与名称与✕/分组栏/表头/固定点行)
- PopupMenu:浮层 / 每个条目 / 可滚动可视区 / 轨道 / 滑块 / 二级子菜单与条目
- ColorPickerPopup:面板 / 标题 / 预览 / 色号 / 色域 / 色相条 / 3 条滑轨与数值 / 输入框

标签增加**尺寸门槛**(< 56×16 逻辑像素不画):复选框/色块/✕ 紧邻,逐个小框贴字会糊成一片。

## 4. 设置按钮配色 + 齿轮图标

- ToolbarAction::SETTINGS 改为 primary = true,与「导入文件」同为强调色主按钮。
- 齿轮几何重做:齿占最外一圈(0.66R→1.0R)、外环中心线 0.54R/笔宽 0.24R、轴孔 0.17R。
  旧版让齿从环中心线伸出,齿被环的描边吃掉 ⇒ 看起来只剩一个"甜甜圈"。

## 5. 几何同源与性能

- RowRect(row) 原本每次调用都从头累加行高(O(row)),而画一行要调它 **5 次**
  (✕/徽标/名称/组计数/折起三角各一次)⇒ 200 行只见 30 行时每帧上万次加法,
  命中测试(每次鼠标移动)同样走这条路。
  新增 m_rowTop 前缀和(由 UpdateAnimGeometry() 统一重建)⇒ **O(1)**;
  长度与行集合不一致时自动退回逐行累加(正确性优先)。
- ContentOffsetBeforeRow 同样改用前缀和。
- PanelBounds() 收敛 6 处重复的 (m_clientBounds.Width > 1.0f) ? m_clientBounds : m_area。

## 6. 验证

- make release / make debug 零 error 零 warning;TargetCheck.exe 910,336 B(889 KiB < 900 KiB)。
- .preflight/lbcheck:21 / 21 PASS(含 bounds-off-restores-pixels:关闭后逐像素回到开启前)。
- Tools/rendercheck:「工具栏带以下」区域哈希 8/8 与上一轮**逐一相同**
  (证明本轮改动只落在工具栏带内);FUNC 断言 7/7 PASS。
- Tools/uicheck:69 passed / 0 failed。

## 7. 文档

Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md 追加第七阶段;
Documents/agent_mem.md 同步变更日志与「2026-10-28 第九轮」硬规则与踩坑记录。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-28 16:28:57 +08:00
1 parent ab19a6e72e
commit 24706c906a
26 files changed
+1003 -163

No files matched your search

+98
View File
@@ -401,3 +401,101 @@ uicheck 69 passed, 0 failed
`UiDebugBounds.cpp` / `UiComponent.cpp` 仍会被 Makefile 的 `Src/*.cpp` 通配扫到而编译失败。
* 「工具栏带以下」的起点必须用**运行时** `m_toolbarRect.Y + Height`;写死 70 会切进工具栏,
齿轮正好落在被统计的区间里,导致本应相同的哈希全都不同。
---
## 第九轮(2026-10-28):边界观感重做 + 覆盖补全 + 几何同源
### 进度总览
| # | 任务 | 状态 | 验证 |
| --- | --- | --- | --- |
| 1 | 齿轮按钮 `primary = true`(与「导入文件」同色) | ✅ | 截图核对(两按钮同为强调色) |
| 2 | 边界改为「四角粗 + 四边细」包角 | ✅ | 截图核对 |
| 3 | 层级 > 0 内缩 1px | ✅ | 同上 |
| 4 | 协议改为 `CollectDebugBoxes`(收集 → 宿主统一绘制) | ✅ | 编译 + 探针 |
| 5 | `SignalPanel` 行内元素全覆盖 | ✅ | box 计数 |
| 6 | `PlotView` GX 徽标 / 图例 / 5 类浮动面板 | ✅ | box 计数 |
| 7 | `Toolbar` 按钮内容矩形 | ✅ | 截图(文字/图标各有框) |
| 8 | `PopupMenu` / `ColorPickerPopup` 内部结构 | ✅ | 截图(菜单项有框) |
| 9 | `RowRect` O(row) → O(1) | ✅ | 8 场景区域哈希不变 |
| 10 | `PanelBounds()` 收敛 6 处重复 | ✅ | 编译 |
| 11 | 文档 + 共享记忆 | ✅ | — |
### 1. 描边样式:`⌜ ⌝ ⌞ ⌟` 包角
之前是四条**等宽实心矩形**(层级 0 用 2px、其余 1px),所以同一元素的"角"和"边"粗细不一致,
而且父子矩形共边时会叠成更粗的一条线。
现在:
| 部位 | 规则 |
| --- | --- |
| 四边 | 固定 **1 设备像素** |
| 四角 | 额外加粗(层级 0 = 4px,其余 3px),沿水平/垂直各延伸 `cornerLen`(≤14px,且不超过短边一半) |
| 内缩 | 层级 > 0 整体内缩 **1px**,与父矩形共边时不再叠粗 |
| 填充 | **完全不填充**(几十层嵌套填充会把内容糊住) |
实现要点:整像素对齐 + `PixelOffsetModeNone` + 关抗锯齿,四条边与八个角段全部用
`FillPixels` 实心矩形画(`DrawRectangle` 会因笔宽奇偶偏移半像素)。
### 2. 覆盖率:从"一根大框"到"逐元素"
协议改成**两段式**:组件只负责"收集 box",宿主统一描边。
```
UiComponent::CollectDebugBoxes(UiDebugBoxList&) 默认只登记 Area()
UiWidget::CollectDebugBoxes(boxes, depth) 递归整棵控件树
宿主 PaintLayoutBounds 收集完一次性画(按 depth 升序)
```
新增覆盖:
| 组件 | 本轮新增的 box |
| --- | --- |
| `Toolbar` | 每个按钮 + **按钮内容**(文本/齿轮图标,居中矩形) |
| `TabBar` | 标签条 / 新建按钮 / 每个 ✕ |
| `SignalPanel` | 表头 / 筛选框 / 列表 / 样式区;筛选输入框 + 清空 ✕ + 全部显示;滚动条轨道 + 滑块;样式 5 行 + 12 个色块 + 自定义 + 3 个模式按钮 + 3 个调整按钮 + 线宽/透明度轨道与数值;目标行 + 名称 + GX 徽标;**每行**(仅可见)+ 折起三角 + 复选框 + 色块 + 名称 + GX 徽标 + ✕ / 组名 + 计数;内联编辑框 |
| `PlotView` | 每个网格 + GX 徽标 + 装饰层矩形;图例条目(托底/色块/文本);5 类浮动面板的面板/标题/✕/折起三角/徽标/编辑行/按钮/内容区/滚动轨道/滑块/透明度轨道与数值/两个显示开关 + 各行复选框/数值/滑轨/复位/名称/✕ + 分组栏/表头/三角/✕ + 每个固定点行与 ✕ |
| `PopupMenu` | 菜单浮层 + 每个条目 + 可滚动可视区 + 轨道 + 滑块 + 二级子菜单与其条目 |
| `ColorPickerPopup` | 面板 + 标题/预览/色号/色域/色相条 + 3 条分量滑轨与数值 + 输入框 |
**标签做尺寸门槛**:小于 56×16 逻辑像素的 box 不画文字标签(复选框/色块/✕ 紧邻,
逐个小框贴字会糊成一片;它们的归属靠包角形状与颜色层级就能分辨)。
### 3. 几何同源与性能
* `RowRect(row)`:原来每次都从头累加行高(**O(row)**),而画一行要调它 **5 次**
(✕ / 徽标 / 名称 / 组计数 / 折起三角)。新增 `m_rowTop` 前缀和,由
`UpdateAnimGeometry()` 统一重建 ⇒ **O(1)**;长度不一致时自动退回逐行累加(正确性优先)。
* `ContentOffsetBeforeRow()` 同样改用前缀和。
* `PanelBounds()` 收敛 `(m_clientBounds.Width > 1.0f) ? m_clientBounds : m_area`
在 6 处的重复。
### 4. 验证数据
```
make release / make debug 零 error 零 warning
TargetCheck.exe 910,336 B(889 KiB,< 900 KiB)
.preflight/lbcheck 21 passed, 0 failed
INFO area-pixels=16080 node-pixels=20629
uicheck 69 passed, 0 failed
rendercheck 「工具栏带以下」区域哈希(与上一轮逐一相同):
AB AABF558F499583EA BB 1B8B2D389D41B988 CB CAC17AA78C515079 DB 154617738CD9C422
EB DA484B02EEEC778A FB B3CE740F60CF3688 GB 4DD14C0C2EC4CE91 HB D84400B723089F43
rendercheck FUNC 7/7 PASS
```
> 整帧哈希必然变化(齿轮变主色 + 图标重画),因此继续用「工具栏带以下」的区域哈希
> 证明"改动被完全限制在工具栏带内"。
### 5. 本轮踩到的坑
* `UiDebugPaintBounds` 的旧签名带 `scale` 参数,但线宽刻意不随 DPI 变 —— 重构时应当**删掉这个
参数**而不是留个 `(void)scale`(已删)。
* 用 PowerShell 按行替换大段代码时,`while ($lines[$end].TrimEnd() -ne "}")` 找结尾会失败:
文件里的闭括号带**前导缩进**,必须用 `Trim()` 而不是 `TrimEnd()`;否则会越界并报
`InvokeMethodOnNull`,而且**已经把开头删掉了**,必须在替换后逐个核对括号配对。
* 齿轮图标在 22px 时,"齿从环中心线伸出"会让齿被环的描边吃掉,看起来只剩一个甜甜圈。
必须让**齿占最外一圈、外环缩到 0.54R**,齿才会明显。
* 中文不能写进 `.ps1`(PS 5.1 按 GBK 读),文本块要落到 UTF-8 数据文件再由 ASCII 脚本拼接。