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
@@ -328,7 +328,7 @@ namespace jdi
|
||||
AppendRectLine(text, key("clipBuildButton"), layout.clipBuildButton);
|
||||
AppendRectLine(text, key("clipDestructiveButton"), layout.clipDestructiveButton);
|
||||
text += key("collapsed") + "=" + (layout.collapsed ? "1" : "0") + "\n";
|
||||
text += key("rows") + "=" + std::to_string(layout.rows) + "\n";
|
||||
text += key("rows") + "=" + std::to_string(layout.signalCount) + "\n";
|
||||
text += key("markerContentHeight") + "=" + std::to_string(layout.markerContentHeight) + "\n";
|
||||
text += key("scrollOffset") + "=" + std::to_string(layout.scrollOffset) + "\n";
|
||||
|
||||
@@ -342,46 +342,53 @@ namespace jdi
|
||||
std::to_string(cell.width) + "\n";
|
||||
}
|
||||
|
||||
// ---- 每一行 / 每一列 ----
|
||||
// ⚠️ **每一列只按自己的长度导出**,不要假设所有列等长:
|
||||
// 「自由标记」面板复用 `rowRefs` 表示"信号分组",只填
|
||||
// `rowNameRects` / `rowNameCloses`,其余 12 列**全为空**
|
||||
// (这一点正是 `rowgeom` 第一次运行就抓出来的结构事实)。
|
||||
// 按 `rowRefs.size()` 统一索引会越界崩溃。
|
||||
const auto dumpRects = [&text, &key](const char* name, const std::vector<gd::RectF>& rects) {
|
||||
text += key(name) + ".count=" + std::to_string(rects.size()) + "\n";
|
||||
for (std::size_t index = 0; index < rects.size(); ++index)
|
||||
{
|
||||
char cell[64] = {};
|
||||
std::snprintf(cell, sizeof(cell), "[%03d]", static_cast<int>(index));
|
||||
AppendRectLine(text, key(name) + cell, rects[index]);
|
||||
}
|
||||
};
|
||||
text += key("rowRefs") + ".count=" + std::to_string(layout.rowRefs.size()) + "\n";
|
||||
for (std::size_t index = 0; index < layout.rowRefs.size(); ++index)
|
||||
// ---- 每一行 / 每一个字段 ----
|
||||
// 2026-11-04(第 39 轮)`EdgePanelLayout` 的 16 个平行数组已合并为
|
||||
// `std::vector<PanelRow> rows`,所以这里**逐行**导出全部字段即可,
|
||||
// 不再需要"每一列可能长度不同"的分列遍历(那种结构问题已从根上消失)。
|
||||
//
|
||||
// ⚠️ 键名保持与合并前**完全一致**(`<panel>.rowLevelTracks[001]=…`):
|
||||
// 这样重构前后的基线可以直接逐字节比对,等价性不靠人工核对。
|
||||
text += key("rowRefs") + ".count=" + std::to_string(layout.rows.size()) + "\n";
|
||||
for (std::size_t index = 0; index < layout.rows.size(); ++index)
|
||||
{
|
||||
char cell[64] = {};
|
||||
std::snprintf(cell, sizeof(cell), "[%03d]", static_cast<int>(index));
|
||||
text += key("rowRefs") + cell + "=" +
|
||||
std::to_string(layout.rowRefs[index].groupIndex) + "/" +
|
||||
std::to_string(layout.rowRefs[index].signalIndex) + "\n";
|
||||
std::to_string(layout.rows[index].ref.groupIndex) + "/" +
|
||||
std::to_string(layout.rows[index].ref.signalIndex) + "\n";
|
||||
}
|
||||
dumpRects("rowHeaders", layout.rowHeaders);
|
||||
dumpRects("rowNameRects", layout.rowNameRects);
|
||||
dumpRects("rowNameCloses", layout.rowNameCloses);
|
||||
dumpRects("rowTriangles", layout.rowTriangles);
|
||||
dumpRects("rowChecks", layout.rowChecks);
|
||||
dumpRects("rowStats", layout.rowStats);
|
||||
dumpRects("rowLevelTracks", layout.rowLevelTracks);
|
||||
dumpRects("rowLevelValues", layout.rowLevelValues);
|
||||
dumpRects("rowLevelResets", layout.rowLevelResets);
|
||||
dumpRects("rowIterTracks", layout.rowIterTracks);
|
||||
dumpRects("rowIterValues", layout.rowIterValues);
|
||||
dumpRects("rowIterResets", layout.rowIterResets);
|
||||
dumpRects("rowInfoMaxInterval", layout.rowInfoMaxInterval);
|
||||
dumpRects("rowInfoMinInterval", layout.rowInfoMinInterval);
|
||||
dumpRects("rowIntervalField", layout.rowIntervalField);
|
||||
dumpRects("rowFrequencyField", layout.rowFrequencyField);
|
||||
const auto dumpField = [&text, &key](const char* name,
|
||||
const std::vector<PlotView::EdgePanelLayout::PanelRow>& rows,
|
||||
gd::RectF PlotView::EdgePanelLayout::PanelRow::*field) {
|
||||
text += key(name) + ".count=" + std::to_string(rows.size()) + "\n";
|
||||
for (std::size_t index = 0; index < rows.size(); ++index)
|
||||
{
|
||||
char cell[64] = {};
|
||||
std::snprintf(cell, sizeof(cell), "[%03d]", static_cast<int>(index));
|
||||
AppendRectLine(text, key(name) + cell, rows[index].*field);
|
||||
}
|
||||
};
|
||||
dumpField("rowHeaders", layout.rows, &PlotView::EdgePanelLayout::PanelRow::header);
|
||||
dumpField("rowNameRects", layout.rows, &PlotView::EdgePanelLayout::PanelRow::nameRect);
|
||||
dumpField("rowNameCloses", layout.rows, &PlotView::EdgePanelLayout::PanelRow::nameClose);
|
||||
dumpField("rowTriangles", layout.rows, &PlotView::EdgePanelLayout::PanelRow::triangle);
|
||||
dumpField("rowChecks", layout.rows, &PlotView::EdgePanelLayout::PanelRow::check);
|
||||
dumpField("rowStats", layout.rows, &PlotView::EdgePanelLayout::PanelRow::stats);
|
||||
dumpField("rowLevelTracks", layout.rows, &PlotView::EdgePanelLayout::PanelRow::levelTrack);
|
||||
dumpField("rowLevelValues", layout.rows, &PlotView::EdgePanelLayout::PanelRow::levelValue);
|
||||
dumpField("rowLevelResets", layout.rows, &PlotView::EdgePanelLayout::PanelRow::levelReset);
|
||||
dumpField("rowIterTracks", layout.rows, &PlotView::EdgePanelLayout::PanelRow::iterTrack);
|
||||
dumpField("rowIterValues", layout.rows, &PlotView::EdgePanelLayout::PanelRow::iterValue);
|
||||
dumpField("rowIterResets", layout.rows, &PlotView::EdgePanelLayout::PanelRow::iterReset);
|
||||
dumpField("rowInfoMaxInterval", layout.rows,
|
||||
&PlotView::EdgePanelLayout::PanelRow::infoMaxInterval);
|
||||
dumpField("rowInfoMinInterval", layout.rows,
|
||||
&PlotView::EdgePanelLayout::PanelRow::infoMinInterval);
|
||||
dumpField("rowIntervalField", layout.rows,
|
||||
&PlotView::EdgePanelLayout::PanelRow::intervalField);
|
||||
dumpField("rowFrequencyField", layout.rows,
|
||||
&PlotView::EdgePanelLayout::PanelRow::frequencyField);
|
||||
|
||||
// ---- 自由标记:分组栏 / 表头 / 条目 ----
|
||||
text += key("markerGroupCount") + "=" + std::to_string(layout.markerGroups.size()) + "\n";
|
||||
@@ -663,64 +670,47 @@ namespace jdi
|
||||
rect.y + rect.height <= plotRect.y + plotRect.height + 1;
|
||||
Expect(inside, std::string("panel inside plot area: ") + PanelKindName(kind));
|
||||
|
||||
// 不变式:行的几何数组必须与行数**形状一致**。
|
||||
// 不变式:**行结构自检**。
|
||||
//
|
||||
// ⚠️⚠️ 判据必须**按排版类别**区分 —— 旧版"所有数组全部全等"是**错的**:
|
||||
// 「自由标记」面板复用 `rowRefs` 表示"信号分组",只填
|
||||
// `rowNameRects` / `rowNameCloses`,其余 12 列**必须为空**
|
||||
// (它真正的行在 `markerRows` / `markerGroups` 里)。
|
||||
// 这是 `rowgeom` 第一次带真实固定点运行时越界崩溃所暴露的结构事实。
|
||||
// 2026-11-04(第 39 轮)起 `EdgePanelLayout` 的 16 个平行数组已合并成
|
||||
// `std::vector<PanelRow> rows` ⇒ "某列比别的列短"这类错位**在结构上不可能**
|
||||
// (一行的字段是一个整体,由 `AppendRow` / `AppendMarkerGroupRow` 一次成型)。
|
||||
// 旧的"15 列长度全部等于行数"因此退化为恒真,这里改为守住**语义**:
|
||||
// * 每一行都必须有名称区(布局漏算就会变成空矩形,绘制时该行不可见/不可点);
|
||||
// * 「自由标记」的行是**分组行**:参数滑块 / 重采样 / 统计字段必须保持为空
|
||||
// (真正的条目行在 `markerRows`),防止有人把列表式布局误用到它上面。
|
||||
const PanelStyle rowStyle = PanelStyleOf(kind);
|
||||
const std::size_t rowCount = layout.rowRefs.size();
|
||||
bool parallel = true;
|
||||
if (rowStyle.markerGroups)
|
||||
bool rowsOk = true;
|
||||
for (const PlotView::EdgePanelLayout::PanelRow& row : layout.rows)
|
||||
{
|
||||
parallel = layout.rowNameRects.size() == rowCount &&
|
||||
layout.rowNameCloses.size() == rowCount && layout.rowHeaders.empty() &&
|
||||
layout.rowChecks.empty() && layout.rowLevelTracks.empty() &&
|
||||
layout.rowIterTracks.empty();
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::size_t parallelSizes[] = {
|
||||
layout.rowHeaders.size(), layout.rowNameRects.size(),
|
||||
layout.rowTriangles.size(), layout.rowChecks.size(),
|
||||
layout.rowStats.size(), layout.rowLevelTracks.size(),
|
||||
layout.rowLevelValues.size(), layout.rowLevelResets.size(),
|
||||
layout.rowIterTracks.size(), layout.rowIterValues.size(),
|
||||
layout.rowIterResets.size(), layout.rowInfoMaxInterval.size(),
|
||||
layout.rowInfoMinInterval.size(), layout.rowIntervalField.size(),
|
||||
layout.rowFrequencyField.size()};
|
||||
for (const std::size_t size : parallelSizes)
|
||||
if (row.nameRect.Width <= 0.0f || row.nameRect.Height <= 0.0f)
|
||||
{
|
||||
if (size != rowCount)
|
||||
{
|
||||
parallel = false;
|
||||
break;
|
||||
}
|
||||
rowsOk = false;
|
||||
break;
|
||||
}
|
||||
if (rowStyle.markerGroups &&
|
||||
(row.header.Width > 0.0f || row.check.Width > 0.0f ||
|
||||
row.stats.Width > 0.0f || row.levelTrack.Width > 0.0f ||
|
||||
row.iterTrack.Width > 0.0f || row.intervalField.Width > 0.0f ||
|
||||
row.frequencyField.Width > 0.0f))
|
||||
{
|
||||
rowsOk = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parallel)
|
||||
if (!rowsOk)
|
||||
{
|
||||
EmitLine("[panels] %s row arrays: refs=%d name=%d nameClose=%d hdr=%d chk=%d lvl=%d/%d/%d "
|
||||
"iter=%d/%d/%d info=%d/%d fld=%d/%d\n",
|
||||
PanelKindName(kind), static_cast<int>(layout.rowRefs.size()),
|
||||
static_cast<int>(layout.rowNameRects.size()),
|
||||
static_cast<int>(layout.rowNameCloses.size()),
|
||||
static_cast<int>(layout.rowHeaders.size()),
|
||||
static_cast<int>(layout.rowChecks.size()),
|
||||
static_cast<int>(layout.rowLevelTracks.size()),
|
||||
static_cast<int>(layout.rowLevelValues.size()),
|
||||
static_cast<int>(layout.rowLevelResets.size()),
|
||||
static_cast<int>(layout.rowIterTracks.size()),
|
||||
static_cast<int>(layout.rowIterValues.size()),
|
||||
static_cast<int>(layout.rowIterResets.size()),
|
||||
static_cast<int>(layout.rowInfoMaxInterval.size()),
|
||||
static_cast<int>(layout.rowInfoMinInterval.size()),
|
||||
static_cast<int>(layout.rowIntervalField.size()),
|
||||
static_cast<int>(layout.rowFrequencyField.size()));
|
||||
EmitLine("[panels] %s row structure broken (rows=%d markerGroups=%d)\n",
|
||||
PanelKindName(kind), static_cast<int>(layout.rows.size()),
|
||||
layout.markerGroups.size());
|
||||
}
|
||||
Expect(rowsOk, std::string("row structure is sound: ") + PanelKindName(kind));
|
||||
// 每一行的信号引用必须有效(布局用无效引用填行 ⇒ 绘制时会跳过该行)。
|
||||
for (const PlotView::EdgePanelLayout::PanelRow& row : layout.rows)
|
||||
{
|
||||
Expect(row.ref.IsValid() || rowStyle.markerGroups,
|
||||
std::string("row has a valid signal ref: ") + PanelKindName(kind));
|
||||
}
|
||||
Expect(parallel, std::string("row arrays match row count: ") + PanelKindName(kind));
|
||||
|
||||
char buffer[160] = {};
|
||||
std::snprintf(buffer, sizeof(buffer), "%s=%016llX,%d,%d,%d,%d\n", PanelKindName(kind),
|
||||
@@ -845,47 +835,21 @@ namespace jdi
|
||||
EmitLine("[panelscroll] %-8s content=%.1f view=%.1f maxScroll=%.1f scrollable=%d rows=%d\n",
|
||||
name, initial.markerContentHeight, initial.content.Height, maxScroll,
|
||||
initial.scrollTrack.Width > 0.0f ? 1 : 0,
|
||||
static_cast<int>(initial.rowRefs.size()));
|
||||
static_cast<int>(initial.rows.size()));
|
||||
|
||||
// 多信号场景下同样守住"行数组形状一致"(判据按排版类别区分:
|
||||
// MARK 只用 rowRefs / rowNameRects / rowNameCloses,详见 `panels` 检查里的说明)。
|
||||
// 行结构自检(合并成 `std::vector<PanelRow>` 后长度错位已不可能,
|
||||
// 这里守住"每行都有名称区",详见 `panels` 检查里的说明)。
|
||||
{
|
||||
const PanelStyle rowStyle = PanelStyleOf(kind);
|
||||
const std::size_t rows = initial.rowRefs.size();
|
||||
bool aligned = true;
|
||||
if (rowStyle.markerGroups)
|
||||
bool rowsOk = true;
|
||||
for (const PlotView::EdgePanelLayout::PanelRow& row : initial.rows)
|
||||
{
|
||||
aligned = initial.rowNameRects.size() == rows &&
|
||||
initial.rowNameCloses.size() == rows && initial.rowHeaders.empty() &&
|
||||
initial.rowChecks.empty();
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::size_t sizes[] = {initial.rowHeaders.size(),
|
||||
initial.rowNameRects.size(),
|
||||
initial.rowTriangles.size(),
|
||||
initial.rowChecks.size(),
|
||||
initial.rowStats.size(),
|
||||
initial.rowLevelTracks.size(),
|
||||
initial.rowLevelValues.size(),
|
||||
initial.rowLevelResets.size(),
|
||||
initial.rowIterTracks.size(),
|
||||
initial.rowIterValues.size(),
|
||||
initial.rowIterResets.size(),
|
||||
initial.rowInfoMaxInterval.size(),
|
||||
initial.rowInfoMinInterval.size(),
|
||||
initial.rowIntervalField.size(),
|
||||
initial.rowFrequencyField.size()};
|
||||
for (const std::size_t size : sizes)
|
||||
if (row.nameRect.Width <= 0.0f || row.nameRect.Height <= 0.0f)
|
||||
{
|
||||
if (size != rows)
|
||||
{
|
||||
aligned = false;
|
||||
break;
|
||||
}
|
||||
rowsOk = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Expect(aligned, std::string("row arrays match row count: ") + name);
|
||||
Expect(rowsOk, std::string("every row has a name rect: ") + name);
|
||||
}
|
||||
|
||||
if (maxScroll <= 1.0f)
|
||||
@@ -1132,7 +1096,7 @@ namespace jdi
|
||||
char line[160] = {};
|
||||
std::snprintf(line, sizeof(line), "%s.hash=%016llX rows=%d markerRows=%d\n",
|
||||
PanelKindName(kind), static_cast<unsigned long long>(hash),
|
||||
static_cast<int>(layout.rowRefs.size()),
|
||||
static_cast<int>(layout.rows.size()),
|
||||
static_cast<int>(layout.markerRows.size()));
|
||||
scale += line;
|
||||
}
|
||||
|
||||
+15
-11
@@ -174,9 +174,9 @@ Tools\OffscreenChecks\Build\OffscreenChecks.exe -list
|
||||
| --- | --- | --- |
|
||||
| `smoke` | 建隐藏窗口 → 加载 → 显示 3 条信号 → 渲染 → 抓帧 → 落盘 `smoke.png` | 客户区/绘图区尺寸合理、绘图区墨迹 > 1000 像素、**同一状态连续两帧逐像素相同**(渲染确定性) |
|
||||
| `frames` | 沿信号列表逐行移动"鼠标",每帧抓图 + 区域哈希 + 耗时 | 平均帧 < 500ms、帧序列**至少出现一次可见变化**;输出 `frame-XXX.png` 供事后分析 |
|
||||
| `panels` | 打开全部 7 个实验性面板(含双光标下的「选区截取」) | 每个面板:功能已启用、有尺寸、**必须被绘图区夹住**、**行数组与行数形状一致**(按排版类别判定,见 §4.4);`-mode record` 写 `panels-baseline.txt`,`-mode compare` 逐面板比对**像素哈希 + 几何** |
|
||||
| `panelscroll` | 全部信号合并到同一网格后,逐一滚动 7 种面板的内容区 | 可滚动时:滚到底/滚回顶、`scrollOffset` 夹到 `maxScroll`、滑块到达/返回轨道端点、内容区几何不动;不可滚动时不得出现滚动条。另守住行数组与行数形状一致 |
|
||||
| `rowgeom` | **逐行几何基线**:7 个面板按「细节 / 规模」两层导出全部矩形并比对 | 细节层(6 信号 1 网格、参数行展开、3 个固定点、折起+展开)导出**每个矩形的每个字段**共 983 条键;规模层(全部信号 1 网格,MF4 = 566 行)导出每面板 FNV-1a 摘要 + 行数。`-mode record` / `-mode compare` 双向比对(MISSING / ADDED / CHANGED) |
|
||||
| `panels` | 打开全部 7 个实验性面板(含双光标下的「选区截取」) | 每个面板:功能已启用、有尺寸、**必须被绘图区夹住**、**行结构自检**(每行都要有名称区;「自由标记」行不得带参数行字段,见 §4.4);`-mode record` 写 `panels-baseline.txt`,`-mode compare` 逐面板比对**像素哈希 + 几何** |
|
||||
| `panelscroll` | 全部信号合并到同一网格后,逐一滚动 7 种面板的内容区 | 可滚动时:滚到底/滚回顶、`scrollOffset` 夹到 `maxScroll`、滑块到达/返回轨道端点、内容区几何不动;不可滚动时不得出现滚动条。另守住“每行都有名称区” |
|
||||
| `rowgeom` | **逐行几何基线**:7 个面板按「细节 / 规模」两层导出全部矩形并比对 | 细节层(6 信号 1 网格、参数行展开、3 个固定点、折起+展开)导出**每行全部字段**共 965 条键(`-data MF4-Record.MF4` 时 1268 条);规模层(全部信号 1 网格)导出每面板 FNV-1a 摘要 + 行数。`-mode record` / `-mode compare` 双向比对(MISSING / ADDED / CHANGED) |
|
||||
| `edgestats` | 3 个阈值下的边沿计数(普通计数)+ 中心轴计数 | 与 `Checks.cpp` 内的**独立参考实现**(朴素逐点)**逐位相等**;同参数重复查询结果稳定 |
|
||||
| `perf` | 整帧中位数、`EdgePanelLayoutFor` 单次耗时、198 个统计状态的整轮查询耗时 | 整帧 < 500ms、面板几何 < 200µs/次、统计查询 < 20µs/次;数字用于前后对比 |
|
||||
|
||||
@@ -208,16 +208,20 @@ OffscreenChecks.exe -run panels -mode compare -out ..\Temp\tools\panel-before
|
||||
|
||||
**基线是检测器,不是黄金图**:预期内的几何变化要重新 `-mode record`,并在提交信息里写明原因。
|
||||
|
||||
### 4.4 长度不变式为什幺要“按类别”判定
|
||||
### 4.4 长度不变式为什幺要“按类别”判定(2026-11-04 已作废,改为结构+语义断言)
|
||||
|
||||
`EdgePanelLayout::rowRefs` 是**重载字段**:
|
||||
> ⚠️ 本节描述的历史问题**已经不存在**:`EdgePanelLayout` 的 16 个平行数组
|
||||
> 已在第 39 轮合并为 `std::vector<PanelRow> rows`,“某列比别的列短”在结构上不可能。
|
||||
> 现在 `panels` / `panelscroll` 守的是**语义**:每行必须有名称区;「自由标记」行不得带
|
||||
> `header`/`check`/`stats`/滑块/重采样字段。`rowgeom` 的键名(`<panel>.<旧列名>[NNN]`)
|
||||
> 刻意保持不变,因此重构前后可以直接逐键比对(919 → 965 键时,消失 0 条、
|
||||
> 已有值变化仅 20 条 `.count`、新增 46 条全为空矩形 ⇒ 几何零位移)。
|
||||
|
||||
* 列表式面板(均值/中心轴/弱峰/重采样/截取):`rowRefs[i]` = 第 i 行的信号;
|
||||
* 「自由标记」面板:`rowRefs[i]` = 第 i 个**信号分组**,真正的条目行在 `markerRows`,
|
||||
且只填 `rowNameRects` / `rowNameCloses`,其余 12 列**必须为空**。
|
||||
|
||||
所以判据按 `PanelStyleOf(kind).markerGroups` 分流(见 `Checks.cpp` 里 `panels` 的注释)。
|
||||
早先“18 列全等”的写法对 MARK 面板是**错的**,只因既有场景恰好未触发才没暴露。
|
||||
历史背景(第 37–38 轮):当时 `EdgePanelLayout::rowRefs` 是**重载字段** ——
|
||||
列表式面板里是"第 i 行的信号",而「自由标记」面板里是"第 i 个信号分组",
|
||||
且只填 `rowNameRects` / `rowNameCloses`,其余 12 列必须为空。
|
||||
因此那时的长度不变式只能按 `PanelStyleOf(kind).markerGroups` 分流判定。
|
||||
第 39 轮合并成 `PanelRow` 后,这类"同一字段两种含义"的判据全部消失。
|
||||
|
||||
### 4.5 输出(全部在 `Temp/tools/`,已 gitignore)
|
||||
|
||||
|
||||
Reference in new issue
Block a user