refactor(panels): 平行数组合并为 std::vector<PanelRow>(消除错位与字段重载)
对应 Code_Review_Report §15.6 第 1 项(方案 B:有结构,无列视图)。
结构
- EdgePanelLayout 的 16 个平行 std::vector 合并为 std::vector<PanelRow> rows,
PanelRow 带 17 个具名成员:ref / header / nameRect / nameClose / triangle / check /
stats / levelTrack / levelValue / levelReset / iterTrack / iterValue / iterReset /
infoMaxInterval / infoMinInterval / intervalField / frequencyField。
- 唯一新增入口:AppendRow(ref, header, nameRect, triangle, check)(返回新行引用)与
AppendMarkerGroupRow(ref, nameRect, nameClose)(「自由标记」分组行)。
参数行/重采样行一律 layout.rows.back().xxx = … 赋值。
- 原 int rows(可见信号数)与新容器同名 ⇒ 改名 signalCount,并注明它与 rows.size()
在 MARK 面板里不相等(只有有固定点的信号才有行)。
消掉的两类问题
1. 某分支漏 push 一列 ⇒ 该列之后所有行读到别人的矩形(静默错位)。现在一行一次成型。
2. rowRefs 字段重载(列表式 = 行信号 / MARK = 分组信号,其余 12 列必须为空)。
现在每行自带 ref,语义由“填哪些字段”表达;不变式简化为语义断言
(每行必须有名称区;MARK 行不得带 header/check/stats/滑块/重采样字段)。
顺带清理
- 合并后 rowIndex < layout.rowXxx.size() 全部变成恒真,批量删除 50+ 处
(含 15 元组长度数组、? x : gd::RectF() 三元判空、if(...){extendRow(...)} 包裹)。
- 调试框与缓存键改为逐行遍历(新增字段只需在 PanelRow 与这两处循环各加一行)。
等价性证明(rowgeom 键名刻意保持不变,故可逐键比对)
old keys = 919, new keys = 965
仅存在于旧 = 0;已有键值变化 = 20(全部是 ".count":0 -> N)
仅存在于新 = 46(全部是新增列的 [NNN],值 0.00,0.00,0.00,0.00)
⇒ 逐行逐字段的真实几何零位移;7 个面板像素哈希(含 resample=2BFC74B3D9B3239C)逐项不变。
验证
- make release:零 warning;784,896 → 782,336 B(−2,560);make debug / make -C Tools 同样零 warning
- UnitTests:checks=260 failed=0 cases=19
- OffscreenChecks -all:7 检查 / 133 断言(上轮 127)全 PASS
- panels -mode compare:7 面板像素哈希逐项不变
- rowgeom -mode compare:965+7 键 0 差异;-data MF4-Record.MF4 时 1268+7 键 0 差异
- 读点:旧 243 处 layout.rowXxx[…] → 现 95 处 layout.rows[…](84 命中判定 / 11 绘制)
过程记录:清理恒真判空时曾用整段替换把「均值滤波」双滑块段误换成「弱峰」双滑块,
编译器报 brace 不平衡,用备份回滚后重做 —— 整段替换必须先精确读出上下文。
文档:Plan §十一、Progress 第 39 轮、Report §16、agent_mem 第 39 轮、Tools/README §4.1/§4.4
This commit is contained in:
1 parent
f07ac74988
commit
1463f9d3f8
11 files changed
+672
-591
No files matched your search
@@ -1999,7 +1999,14 @@ namespace jdi
|
||||
gd::RectF badge;
|
||||
gd::RectF edit; /**< 面板底部编辑行(无编辑内容时宽度为 0)。 */
|
||||
gd::RectF button; /**< 面板底部按钮(无按钮时宽度为 0)。 */
|
||||
int rows = 0; /**< 统计行数(每条可见信号一行)。 */
|
||||
/**
|
||||
* @brief 该面板统计的**可见信号数**(不是行数!行容器见 `rows`)。
|
||||
*
|
||||
* 它来自“网格里的可见信号数”(`EdgePanelLayoutFor` 开头统计),
|
||||
* 与 `rows.size()` 在**「自由标记」面板里不相等**(那里只有至少有一个固定点的
|
||||
* 信号才有一行)。历史名字叫 `rows`,与新的行容器同名,故改名 `signalCount`。
|
||||
*/
|
||||
int signalCount = 0;
|
||||
|
||||
/** @brief 表格的一列(左边界 + 宽度,客户区坐标)。 */
|
||||
struct TableColumn
|
||||
@@ -2018,60 +2025,66 @@ namespace jdi
|
||||
/** @brief 表格三列的几何(`0` 信号名 / `1` 上升沿计数 / `2` 下降沿计数)。 */
|
||||
std::array<TableColumn, 3> tableColumns{};
|
||||
/**
|
||||
* @brief 「逐条目」面板的矩形(MEAN / CENTER / PEAK 面板共用同一套排布)。
|
||||
* @brief 面板里的一**行**:该行对应的信号 + 它的全部几何。
|
||||
*
|
||||
* 索引与 `rowRefs` 一一对应(即网格内可见信号的顺序):
|
||||
* `rowChecks` 为第一排右侧的复选框;`rowStats` 为第三排的统计结果(中心轴边沿计数专用)。
|
||||
* 未勾选的条目对应的矩形均为空。
|
||||
* ⚠️⚠️ 2026-11-04(第 39 轮)把原来的 **16 个平行 `std::vector`** 合并成
|
||||
* `std::vector<PanelRow> rows`。合并的动机不是"省字节",而是消除三类**结构性**缺陷:
|
||||
*
|
||||
* ⚠️ 2026-11-04 删除了 `rowPoints` / `rowIterations`:它们是**旧式输入框**(点数 / 迭代次数)
|
||||
* 的矩形,自滑块版排版(`rowLevelTracks` / `rowIterTracks`)上线后就没有绘制方读它们了 ——
|
||||
* 所有写入都是空矩形,读取方只剩调试框与缓存键。保留它们只会让每次 `AppendRow`
|
||||
* 多两次 `push_back`、并且误导后续读者以为“这里还有一套输入框”。
|
||||
* 1. **漏推一列 ⇒ 该列之后所有行读到别人的矩形**。平行数组靠"每个分支按同一顺序
|
||||
* push 恰好 N 项"来维持,任何一处漏推都是静默错位(历史上"改一次布局就出错"的机制)。
|
||||
* 现在一个 `PanelRow` 是一个整体,`rows.push_back(row)` 一次成型 —— **不可能错位**。
|
||||
* 2. **`rowRefs` 字段重载**:旧结构里「自由标记」面板借用 `rowRefs` 表示"信号分组",
|
||||
* 其余 12 列必须保持为空,长度不变式只能按面板类别分流判定。现在每行自带
|
||||
* `ref`,语义由**填哪些字段**表达,不再有"同一字段两种含义"。
|
||||
* 3. 旧结构每实例 16 个 vector 头部(16 × 24 B)且各自独立分配;现在一次分配。
|
||||
*
|
||||
* ⚠️ **不要**改回平行数组,也不要引入"列视图"(`PanelColumn<T>` 持有 `rows` 指针 +
|
||||
* 成员指针):那样拷贝 `EdgePanelLayout` 后忘记重绑就是**静默悬垂**,
|
||||
* 没有任何检查能抓到;具名成员访问(`rows[i].check`)在结构上不可能悬垂。
|
||||
*
|
||||
* 字段布局(每一行都拥有全部字段,未使用的**恒为空矩形**,不需要"数组可能为空"的判空):
|
||||
*
|
||||
* | 字段 | 含义 |
|
||||
* | --- | --- |
|
||||
* | `ref` | 该行的信号(「自由标记」里是**分组**的代表信号) |
|
||||
* | `header` | 信号色叠底条目栏 / 分组栏(整行宽度) |
|
||||
* | `nameRect` | 名称区(列表式 = 条目栏;自由标记 = 分组栏;表格 = 名称列) |
|
||||
* | `nameClose` | 名称右侧 ✕(仅「自由标记」:清除该信号全部固定点) |
|
||||
* | `triangle` | 左侧展开 / 收起三角(仅勾选后出现) |
|
||||
* | `check` | 右端复选框 |
|
||||
* | `stats` | 「中心轴边沿计数」的上升 / 下降沿统计行 |
|
||||
* | `levelTrack` / `levelValue` / `levelReset` | 参数滑块 1(均值点值 / 弱峰增强等级) |
|
||||
* | `iterTrack` / `iterValue` / `iterReset` | 参数滑块 2(均值迭代次数 / 弱峰迭代次数) |
|
||||
* | `infoMaxInterval` / `infoMinInterval` | 「重采样」两行只读统计 |
|
||||
* | `intervalField` / `frequencyField` | 「重采样」两行联动输入框(间隔秒 / 频率 Hz) |
|
||||
*/
|
||||
std::vector<gd::RectF> rowChecks;
|
||||
std::vector<gd::RectF> rowStats;
|
||||
struct PanelRow
|
||||
{
|
||||
SignalRef ref; /**< 该行的信号(MARK 面板 = 分组的代表信号)。 */
|
||||
gd::RectF header; /**< 信号色叠底条目栏 / 分组栏(整行宽度)。 */
|
||||
gd::RectF nameRect; /**< 名称区(列表式条目栏 / 分组栏 / 表格名称列)。 */
|
||||
gd::RectF nameClose; /**< 名称右侧 ✕(仅「自由标记」)。 */
|
||||
gd::RectF triangle; /**< 左侧展开 / 收起三角。 */
|
||||
gd::RectF check; /**< 右端复选框。 */
|
||||
gd::RectF stats; /**< 「中心轴」上升 / 下降沿统计行。 */
|
||||
gd::RectF levelTrack; /**< 参数滑块 1 轨道。 */
|
||||
gd::RectF levelValue; /**< 参数滑块 1 数值文本。 */
|
||||
gd::RectF levelReset; /**< 参数滑块 1 复位按钮。 */
|
||||
gd::RectF iterTrack; /**< 参数滑块 2 轨道。 */
|
||||
gd::RectF iterValue; /**< 参数滑块 2 数值文本。 */
|
||||
gd::RectF iterReset; /**< 参数滑块 2 复位按钮。 */
|
||||
gd::RectF infoMaxInterval; /**< 「重采样」最大数据间隔(只读)。 */
|
||||
gd::RectF infoMinInterval; /**< 「重采样」最小数据间隔(只读)。 */
|
||||
gd::RectF intervalField; /**< 「重采样」间隔输入框。 */
|
||||
gd::RectF frequencyField; /**< 「重采样」频率输入框。 */
|
||||
};
|
||||
/**
|
||||
* @brief 「弱峰调整」每个被勾选信号自己的两行**滑块**(与 `rowRefs` 同序)。
|
||||
* @brief 面板的全部行(**唯一的**行容器:顺序 = 从上到下)。
|
||||
*
|
||||
* `rowLevelTracks/rowLevelValues` = 增强等级滑块与它右侧的数值文本;
|
||||
* `rowIterTracks/rowIterValues` = 迭代次数滑块与它右侧的数值文本
|
||||
*(**点值数值文本可以把滑块上限改为别的正整数**)。
|
||||
* 未勾选或非 PEAK 面板时对应元素为空矩形(数组也可能为空)。
|
||||
* ⚠️ 只有 `AppendRow` / `AppendMarkerGroupRow` 可以追加,不要 `rows.push_back`
|
||||
* (那会绕过"字段一次成型"的保证)。
|
||||
*/
|
||||
std::vector<gd::RectF> rowLevelTracks;
|
||||
std::vector<gd::RectF> rowLevelValues;
|
||||
std::vector<gd::RectF> rowIterTracks;
|
||||
std::vector<gd::RectF> rowIterValues;
|
||||
/**
|
||||
* @brief 「弱峰调整」每行滑块右侧的**复位按钮**矩形(与 `rowLevelTracks` 同序)。
|
||||
*
|
||||
* 点击把该行参数恢复为默认值(幅度 0 / 迭代次数 1 且上限回到初始上限)。
|
||||
* 未勾选或非 PEAK 面板时对应元素为空矩形(数组也可能为空)。
|
||||
*/
|
||||
std::vector<gd::RectF> rowLevelResets;
|
||||
std::vector<gd::RectF> rowIterResets;
|
||||
/**
|
||||
* @brief 「重采样」面板每个被勾选信号的四行内容(与 `rowRefs` 同序)。
|
||||
*
|
||||
* `rowInfoMaxInterval` / `rowInfoMinInterval` = 只读统计(最大 / 最小数据间隔);
|
||||
* `rowIntervalField` / `rowFrequencyField` = 两行**联动输入框**(间隔秒 / 频率 Hz)。
|
||||
* 未勾选或非 RESAMPLE 面板时对应元素为空矩形(数组也可能为空)。
|
||||
*/
|
||||
std::vector<gd::RectF> rowInfoMaxInterval;
|
||||
std::vector<gd::RectF> rowInfoMinInterval;
|
||||
std::vector<gd::RectF> rowIntervalField;
|
||||
std::vector<gd::RectF> rowFrequencyField;
|
||||
/**
|
||||
* @brief 「均值滤波」面板每个信号的**信号色叠底标题栏**矩形(与 `rowRefs` 同序)。
|
||||
*
|
||||
* 排版与「自由标记」面板的信号分组栏一致(左侧展开三角 + 信号名),
|
||||
* 但右端是**复选框**(`rowChecks`)而不是 ✕。未勾选时标题栏仍显示(用于勾选)。
|
||||
*/
|
||||
std::vector<gd::RectF> rowHeaders;
|
||||
/** @brief 「均值滤波」面板每个信号标题栏左侧的**展开 / 收起三角**(仅勾选后出现)。 */
|
||||
std::vector<gd::RectF> rowTriangles;
|
||||
std::vector<SignalRef> rowRefs;
|
||||
std::vector<PanelRow> rows;
|
||||
|
||||
/**
|
||||
* @brief 「自由标记」面板的一行(一个固定点)。
|
||||
@@ -2174,18 +2187,6 @@ namespace jdi
|
||||
gd::RectF clipDestructiveButton;
|
||||
/** @brief 「选区截取」需要双光标时的提示文案矩形(未启用双光标时使用)。 */
|
||||
gd::RectF clipHint;
|
||||
/**
|
||||
* @brief 「自由标记」:每个信号名右侧的「清除该信号全部固定点」✕。
|
||||
*
|
||||
* 与 `rowRefs` 一一对应;该信号没有任何固定点时为空矩形。
|
||||
*/
|
||||
std::vector<gd::RectF> rowNameCloses;
|
||||
/**
|
||||
* @brief 「自由标记」:每个信号**名称行**的矩形(与 `rowRefs` 一一对应)。
|
||||
*
|
||||
* 绘制只读这个矩形(与 `rowNameCloses` 同一套横坐标),人为主观反推容易与布局漂移。
|
||||
*/
|
||||
std::vector<gd::RectF> rowNameRects;
|
||||
/**
|
||||
* @brief 「自由标记」面板的**六列**文本区
|
||||
* (① 序号 ② 标记值 ③ 枚举文本 ④ 增量 ⑤ 时间点 ⑥ 时间间隔)。
|
||||
@@ -2215,40 +2216,54 @@ namespace jdi
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 追加一**行**(条目栏 / 名称 / 三角 / 复选框),其余列一律补空矩形。
|
||||
* @brief 追加一**行**(条目栏 / 名称 / 三角 / 复选框),其余字段一律留空。
|
||||
*
|
||||
* ⚠️⚠️ 这是**唯一**允许新增行的入口。面板每行对应 16 个平行数组
|
||||
* (`rowRefs` / `rowHeaders` / `rowChecks` / `rowLevelTracks` / …);
|
||||
* 如果每个排版分支各写一遍 `push_back`,只要某分支漏推一项,
|
||||
* 那一列之后的所有行都会**读到别人的矩形**("改一次布局就出错"的典型机制)。
|
||||
* 现在:新增行的分支只调本函数,**新增一列只需改这里一处**;
|
||||
* 参数行 / 重采样行则用 `rowXxx.back() = …` 赋值(长度天然对齐)。
|
||||
* ⚠️⚠️ 这是**唯一**允许新增列表式行的入口(「自由标记」的分组行走
|
||||
* `AppendMarkerGroupRow`)。一个 `PanelRow` 是一个整体,字段一次成型,
|
||||
* 因此**不可能**出现"某列漏推 ⇒ 之后所有行错位"(旧平行数组结构的老毛病)。
|
||||
* 参数行 / 重采样行一律用 `rows.back().xxx = …` **赋值**。
|
||||
*
|
||||
* @param [IN] ref 该行对应的信号。
|
||||
* @param [IN] header 信号色叠底条目栏(整行宽度)。
|
||||
* @param [IN] nameRect 名称(表格里的名称列)。
|
||||
* @param [IN] triangle 左侧展开 / 收起三角(不适用时传空矩形)。
|
||||
* @param [IN] check 右端复选框。
|
||||
* @return 新行的引用(调用方可直接赋值参数行)。
|
||||
*/
|
||||
void AppendRow(const SignalRef& ref, const gd::RectF& header, const gd::RectF& nameRect,
|
||||
const gd::RectF& triangle, const gd::RectF& check)
|
||||
PanelRow& AppendRow(const SignalRef& ref, const gd::RectF& header,
|
||||
const gd::RectF& nameRect, const gd::RectF& triangle,
|
||||
const gd::RectF& check)
|
||||
{
|
||||
rowRefs.push_back(ref);
|
||||
rowHeaders.push_back(header);
|
||||
rowNameRects.push_back(nameRect);
|
||||
rowTriangles.push_back(triangle);
|
||||
rowChecks.push_back(check);
|
||||
rowStats.push_back(gd::RectF());
|
||||
rowLevelTracks.push_back(gd::RectF());
|
||||
rowLevelValues.push_back(gd::RectF());
|
||||
rowLevelResets.push_back(gd::RectF());
|
||||
rowIterTracks.push_back(gd::RectF());
|
||||
rowIterValues.push_back(gd::RectF());
|
||||
rowIterResets.push_back(gd::RectF());
|
||||
rowInfoMaxInterval.push_back(gd::RectF());
|
||||
rowInfoMinInterval.push_back(gd::RectF());
|
||||
rowIntervalField.push_back(gd::RectF());
|
||||
rowFrequencyField.push_back(gd::RectF());
|
||||
PanelRow row;
|
||||
row.ref = ref;
|
||||
row.header = header;
|
||||
row.nameRect = nameRect;
|
||||
row.triangle = triangle;
|
||||
row.check = check;
|
||||
rows.push_back(row);
|
||||
return rows.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 追加一个**「自由标记」信号分组行**(只有名称与 ✕ 有几何)。
|
||||
*
|
||||
* 单独成一个入口(而不是复用 `AppendRow`)是因为语义不同:这里的 `ref` 是
|
||||
* **分组**的代表信号,`nameRect` / `nameClose` 是分组栏与它右端的 ✕;
|
||||
* `header` / `triangle` / `check` 都不使用(分组栏自己的几何在
|
||||
* `markerGroups` 里,那里还需要 `tableHeader` / `pinned` 等行外状态)。
|
||||
*
|
||||
* @param [IN] ref 该分组的代表信号。
|
||||
* @param [IN] nameRect 分组栏矩形。
|
||||
* @param [IN] nameClose 分组栏右端 ✕ 的命中矩形。
|
||||
*/
|
||||
void AppendMarkerGroupRow(const SignalRef& ref, const gd::RectF& nameRect,
|
||||
const gd::RectF& nameClose)
|
||||
{
|
||||
PanelRow row;
|
||||
row.ref = ref;
|
||||
row.nameRect = nameRect;
|
||||
row.nameClose = nameClose;
|
||||
rows.push_back(row);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user