refactor(panels): 删除 rowPoints/rowIterations 死列 + 提取 PaintPanelResampleRows
对应 Code_Review_Report §14.5 第 1 项与第 3 项。
第 1 项:删除 EdgePanelLayout::rowPoints / rowIterations(行数组 18 → 16)
- 写入方只有 AppendRow(推空矩形),读取方只有调试框 AddAll 与 PanelSlotKey 的 mix 循环,
绘制方零(旧式“点数 / 迭代次数”输入框早被滑块版 rowLevelTracks / rowIterTracks 取代)。
- 连带同步:AppendRow、调试框、缓存键、rowgeom 导出、panels/panelscroll 长度不变式、
文档里的列数(18/14 → 16/12)。
等价性由基线证伪,而不是靠人工 grep:
old lines = 983, new lines = 919
only in OLD = 64, only in NEW = 0
only-in-OLD lines NOT belonging to rowPoints/rowIterations = 0
即除被删两列外,其余 919 条键逐字节相同。
第 3 项(前半):提取 PlotView::PaintPanelResampleRows
- PaintPanelListRows 里 `if (resamplePanel) { …; continue; }` 共 132 行整块提取,
签名 (g, layout, rowIndex, laneIndex, ref, item);两个局部 lambda
(paintInfoRow 只读统计行 / paintInputRow 联动输入框)随之迁移。
- maxInterval / minInterval 两个纯拷贝局部量直接用 item.maxInterval / item.minInterval。
- 通用参数行(中心轴统计 + 两行滑块)未提取:需 9 个参数,签名比函数体更难读。
§14.5 第 2 项(完整结构合并)本轮不做,方案已定并写入文档:
- 列视图(PanelColumn<T>)方案否决:拷贝忘重绑 = 静默悬垂,需额外自检兜住;
- 若要做就做“vector<PanelRow> + 具名成员访问(无视图)”,242 处读点(127 处命中判定),
必须单独一轮。它属整洁性收益(漏推一列已被 AppendRow + 长度不变式挡住),不是紧急项。
验证
- make release:零 warning;785,408 → 784,896 B(−512);make debug 同样零 warning
- make -C Tools:零 warning
- UnitTests:checks=260 failed=0 cases=19
- OffscreenChecks -all:7 检查 / 127 断言全 PASS
- panels -mode compare:7 面板像素哈希 + 几何逐项不变(resample=2BFC74B3D9B3239C)
- rowgeom -mode compare:919 + 7 键,0 差异(基线已按 16 列重录)
- PlotViewPaint.cpp 5575 → 5565;PlotViewPanelPaint.cpp 1016 → 1020
文档:Plan §十、Progress 第 38 轮、Report §15、agent_mem 第 38 轮、Tools/README §4.4
This commit is contained in:
1 parent
353c553721
commit
f07ac74988
9 files changed
+384
-150
No files matched your search
@@ -2021,13 +2021,15 @@ namespace jdi
|
||||
* @brief 「逐条目」面板的矩形(MEAN / CENTER / PEAK 面板共用同一套排布)。
|
||||
*
|
||||
* 索引与 `rowRefs` 一一对应(即网格内可见信号的顺序):
|
||||
* `rowChecks` 为第一排右侧的复选框;`rowPoints` / `rowIterations` 为**输入框**
|
||||
*(MEAN / CENTER 使用);`rowStats` 为第三排的统计结果(中心轴边沿计数专用)。
|
||||
* `rowChecks` 为第一排右侧的复选框;`rowStats` 为第三排的统计结果(中心轴边沿计数专用)。
|
||||
* 未勾选的条目对应的矩形均为空。
|
||||
*
|
||||
* ⚠️ 2026-11-04 删除了 `rowPoints` / `rowIterations`:它们是**旧式输入框**(点数 / 迭代次数)
|
||||
* 的矩形,自滑块版排版(`rowLevelTracks` / `rowIterTracks`)上线后就没有绘制方读它们了 ——
|
||||
* 所有写入都是空矩形,读取方只剩调试框与缓存键。保留它们只会让每次 `AppendRow`
|
||||
* 多两次 `push_back`、并且误导后续读者以为“这里还有一套输入框”。
|
||||
*/
|
||||
std::vector<gd::RectF> rowChecks;
|
||||
std::vector<gd::RectF> rowPoints;
|
||||
std::vector<gd::RectF> rowIterations;
|
||||
std::vector<gd::RectF> rowStats;
|
||||
/**
|
||||
* @brief 「弱峰调整」每个被勾选信号自己的两行**滑块**(与 `rowRefs` 同序)。
|
||||
@@ -2215,7 +2217,7 @@ namespace jdi
|
||||
/**
|
||||
* @brief 追加一**行**(条目栏 / 名称 / 三角 / 复选框),其余列一律补空矩形。
|
||||
*
|
||||
* ⚠️⚠️ 这是**唯一**允许新增行的入口。面板每行对应 18 个平行数组
|
||||
* ⚠️⚠️ 这是**唯一**允许新增行的入口。面板每行对应 16 个平行数组
|
||||
* (`rowRefs` / `rowHeaders` / `rowChecks` / `rowLevelTracks` / …);
|
||||
* 如果每个排版分支各写一遍 `push_back`,只要某分支漏推一项,
|
||||
* 那一列之后的所有行都会**读到别人的矩形**("改一次布局就出错"的典型机制)。
|
||||
@@ -2236,8 +2238,6 @@ namespace jdi
|
||||
rowNameRects.push_back(nameRect);
|
||||
rowTriangles.push_back(triangle);
|
||||
rowChecks.push_back(check);
|
||||
rowPoints.push_back(gd::RectF());
|
||||
rowIterations.push_back(gd::RectF());
|
||||
rowStats.push_back(gd::RectF());
|
||||
rowLevelTracks.push_back(gd::RectF());
|
||||
rowLevelValues.push_back(gd::RectF());
|
||||
@@ -2280,6 +2280,26 @@ namespace jdi
|
||||
void PaintPanelListRows(gd::Graphics& g, const EdgePanelLayout& layout, const Lane& tabLane,
|
||||
EdgePanelKind kind, std::size_t laneIndex);
|
||||
|
||||
/**
|
||||
* @brief 「重采样」面板**单个信号的参数区**(四行:最大间隔 / 最小间隔 / 间隔输入 / 频率输入)。
|
||||
*
|
||||
* 从 `PaintPanelListRows` 里提取:这四行的排布与其它面板完全不同(两行只读统计 + 两行
|
||||
* 联动输入框、标签在左数值在右),混在同一个行循环里会让"条目栏 / 参数行 / 重采样行"
|
||||
* 三种排版互相纠缠。提取后调用点只剩一行,`PaintPanelListRows` 只保留
|
||||
* "条目栏 + 通用参数行"两段。
|
||||
* 定义在 `PlotViewPanelPaint.cpp`。
|
||||
*
|
||||
* @param [IN,OUT] g 绘图上下文(调用方已按内容区裁剪)。
|
||||
* @param [IN] layout 面板布局。
|
||||
* @param [IN] rowIndex 行下标(取该行的四个矩形)。
|
||||
* @param [IN] laneIndex 网格下标(判定"正在编辑的是不是这一行")。
|
||||
* @param [IN] ref 该行对应的信号(同上)。
|
||||
* @param [IN] item 该信号的「重采样」条目(已确认存在)。
|
||||
*/
|
||||
void PaintPanelResampleRows(gd::Graphics& g, const EdgePanelLayout& layout, std::size_t rowIndex,
|
||||
std::size_t laneIndex, const SignalRef& ref,
|
||||
const Lane::ResampleItem& item);
|
||||
|
||||
/**
|
||||
* @brief 「自由标记」面板的**内容区**(分组栏 + 表头 + 条目 + 吸顶)。
|
||||
*
|
||||
|
||||
@@ -815,8 +815,6 @@ namespace jdi
|
||||
boxes.Add(layout.clipDestructiveButton, 3);
|
||||
|
||||
boxes.AddAll(layout.rowChecks, 4);
|
||||
boxes.AddAll(layout.rowPoints, 4);
|
||||
boxes.AddAll(layout.rowIterations, 4);
|
||||
boxes.AddAll(layout.rowStats, 4);
|
||||
boxes.AddAll(layout.rowLevelTracks, 4);
|
||||
boxes.AddAll(layout.rowLevelValues, 4);
|
||||
@@ -4088,14 +4086,6 @@ namespace jdi
|
||||
{
|
||||
mixRect(rect);
|
||||
}
|
||||
for (const gd::RectF& rect : layout.rowPoints)
|
||||
{
|
||||
mixRect(rect);
|
||||
}
|
||||
for (const gd::RectF& rect : layout.rowIterations)
|
||||
{
|
||||
mixRect(rect);
|
||||
}
|
||||
for (const gd::RectF& rect : layout.rowStats)
|
||||
{
|
||||
mixRect(rect);
|
||||
|
||||
@@ -328,124 +328,10 @@ namespace jdi
|
||||
|
||||
// 「重采样」:四行 = 最大间隔(只读)/ 最小间隔(只读)/
|
||||
// 重采样间隔(输入)/ 重采样频率(输入)。标签在左、数值在右。
|
||||
// 排布细节见 `PaintPanelResampleRows`(从本循环里提取的独立函数)。
|
||||
if (resamplePanel)
|
||||
{
|
||||
const float labelWidth =
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, kResampleMaxIntervalLabel);
|
||||
const auto paintInfoRow = [&](const gd::RectF& row, const wchar_t* label,
|
||||
const std::wstring& text)
|
||||
{
|
||||
if (row.Width <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
UiTextClipped(g, label, m_fonts->uiSmall,
|
||||
gd::RectF(row.X, row.Y, std::max(10.0f, labelWidth),
|
||||
row.Height),
|
||||
m_theme->textMuted, TextAlign::LEFT, layout.content);
|
||||
// ⚠️ 统计数字**绝不省略**(用户 2026-11-03 要求):
|
||||
// 面板宽度已按真实文本量宽(`EdgePanelWidth` 逐信号实测),
|
||||
// 因此这里用 `TextAlign::RIGHT` + 完整矩形即可;
|
||||
// 万一仍被窗口边界夹住,也宁可超出面板右缘(由内容区裁剪兜底),
|
||||
// **不要**退化成 `…` 让用户看不到真实数值。
|
||||
UiTextClipped(g, text, m_fonts->monoSmall,
|
||||
gd::RectF(row.X + labelWidth + 8.0f * m_scale, row.Y,
|
||||
std::max(10.0f, row.Width - labelWidth -
|
||||
8.0f * m_scale),
|
||||
row.Height),
|
||||
m_theme->textStrong, TextAlign::RIGHT,
|
||||
layout.content);
|
||||
};
|
||||
const auto paintInputRow = [&](const gd::RectF& field, const wchar_t* label,
|
||||
const std::wstring& text, bool editing)
|
||||
{
|
||||
if (field.Width <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 标签与数值同高:标签从内容区左缘画到字段左缘。
|
||||
UiTextClipped(
|
||||
g, label, m_fonts->uiSmall,
|
||||
gd::RectF(layout.content.X, field.Y,
|
||||
std::max(10.0f, field.X - 8.0f * m_scale - layout.content.X),
|
||||
field.Height),
|
||||
m_theme->textMuted, TextAlign::LEFT, layout.content);
|
||||
if (editing)
|
||||
{
|
||||
return; // 正在编辑:内容由 `PaintEditField` 画
|
||||
}
|
||||
const gd::RectF box(field.X, field.Y + 2.0f * m_scale, field.Width,
|
||||
std::max(10.0f, field.Height - 4.0f * m_scale));
|
||||
UiFillRoundRect(g, box, 3.0f * m_scale, m_theme->pressed);
|
||||
UiDrawRoundRect(g, box, 3.0f * m_scale, m_theme->border, 1.0f);
|
||||
UiTextClipped(g, text, m_fonts->monoSmall,
|
||||
gd::RectF(box.X + 4.0f * m_scale, box.Y,
|
||||
std::max(6.0f, box.Width - 8.0f * m_scale),
|
||||
box.Height),
|
||||
m_theme->textStrong, TextAlign::LEFT, layout.content);
|
||||
};
|
||||
double maxInterval = 0.0;
|
||||
double minInterval = 0.0;
|
||||
const bool hasInterval =
|
||||
(resampleItem->maxInterval > 0.0) && (resampleItem->minInterval > 0.0);
|
||||
maxInterval = resampleItem->maxInterval;
|
||||
minInterval = resampleItem->minInterval;
|
||||
// 「最大 / 最小数据间隔」= 自适应单位的**小数**文本(绝不用科学计数法),
|
||||
// 末尾追加一个括号内的「对应频率」尾缀(单位在 Hz/kHz/MHz/GHz 之间自适应)。
|
||||
//
|
||||
// ⚠️⚠️ 全部使用**真实值**(用户 2026-11-03 明确要求):
|
||||
// * 时间间隔:`FormatDurationCompound` 会把次级单位保留 3 位小数
|
||||
// ⇒ `0.003696631s` 显示为 `3ms 696.631us`(旧实现舍入到 3 位有效数字
|
||||
// 得到 `3ms 700us`,把真实尾数抹掉了);
|
||||
// * 频率:由**未舍入的真实间隔**换算 ⇒ `270.517 Hz`(旧实现用舍入后的
|
||||
// `3.70ms` 换算得到 `270.27 Hz`,与输入框不一致)。
|
||||
// 两条路径都用真实值 ⇒ 统计行与输入框天然一致。
|
||||
const auto intervalWithFrequency = [](double seconds) -> std::wstring
|
||||
{
|
||||
std::wstring text = FormatDurationCompound(seconds);
|
||||
if (std::isfinite(seconds) && seconds > 0.0)
|
||||
{
|
||||
text += L"(";
|
||||
text += FormatFrequencyText(1.0 / seconds);
|
||||
text += L")";
|
||||
}
|
||||
return text;
|
||||
};
|
||||
if (rowIndex < layout.rowInfoMaxInterval.size())
|
||||
{
|
||||
paintInfoRow(layout.rowInfoMaxInterval[rowIndex], kResampleMaxIntervalLabel,
|
||||
hasInterval ? intervalWithFrequency(maxInterval) : L"—");
|
||||
}
|
||||
if (rowIndex < layout.rowInfoMinInterval.size())
|
||||
{
|
||||
paintInfoRow(layout.rowInfoMinInterval[rowIndex], kResampleMinIntervalLabel,
|
||||
hasInterval ? intervalWithFrequency(minInterval) : L"—");
|
||||
}
|
||||
const double interval = (resampleItem->interval > 0.0)
|
||||
? resampleItem->interval
|
||||
: minInterval;
|
||||
const bool editingInterval =
|
||||
(m_editTarget == PlotEditTarget::RESAMPLE_INTERVAL) &&
|
||||
(m_editLane == static_cast<int>(index)) && (m_editMeanRef == ref);
|
||||
const bool editingFrequency =
|
||||
(m_editTarget == PlotEditTarget::RESAMPLE_FREQUENCY) &&
|
||||
(m_editLane == static_cast<int>(index)) && (m_editMeanRef == ref);
|
||||
if (rowIndex < layout.rowIntervalField.size())
|
||||
{
|
||||
// 非编辑态:自适应单位 + 小数(与「最大/最小数据间隔」同一套单位形式)。
|
||||
// 编辑态:只显示**秒**数值(不带单位),由 `PaintEditField` 绘制。
|
||||
paintInputRow(layout.rowIntervalField[rowIndex], kResampleIntervalLabel,
|
||||
FormatIntervalText(interval), editingInterval);
|
||||
}
|
||||
if (rowIndex < layout.rowFrequencyField.size())
|
||||
{
|
||||
// ⚠️ 频率同样用**真实间隔**换算(与时间间隔行同源)⇒ `270.517 Hz`。
|
||||
const double hertz = (interval > 0.0) ? (1.0 / interval) : 0.0;
|
||||
// 非编辑态:频率单位自适应(Hz/kHz/MHz/GHz);编辑态:只显示 Hz 数值。
|
||||
paintInputRow(layout.rowFrequencyField[rowIndex], kResampleFrequencyLabel,
|
||||
(hertz > 0.0) ? FormatFrequencyText(hertz) : L"—",
|
||||
editingFrequency);
|
||||
}
|
||||
PaintPanelResampleRows(g, layout, rowIndex, index, ref, *resampleItem);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -669,7 +555,125 @@ namespace jdi
|
||||
PaintPanelScrollBar(g, layout, *m_theme);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief 「重采样」面板**单个信号的参数区**(四行)。
|
||||
*
|
||||
* 从 `PaintPanelListRows` 里整块提取(第 38 轮):这四行的排布(两行只读统计 +
|
||||
* 两行联动输入框、标签在左数值在右)与其它面板的滑块参数行完全不同,
|
||||
* 混在同一个行循环里只会让"条目栏 / 参数行 / 重采样行"三种排版互相纠缠。
|
||||
* **零逻辑改动**,判据 = `rowgeom -mode compare` + `panels -mode compare` 全绿。
|
||||
*/
|
||||
void PlotView::PaintPanelResampleRows(gd::Graphics& g, const EdgePanelLayout& layout,
|
||||
std::size_t rowIndex, std::size_t laneIndex,
|
||||
const SignalRef& ref, const Lane::ResampleItem& item)
|
||||
{
|
||||
const std::size_t index = laneIndex;
|
||||
const float labelWidth =
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, kResampleMaxIntervalLabel);
|
||||
const auto paintInfoRow = [&](const gd::RectF& row, const wchar_t* label,
|
||||
const std::wstring& text)
|
||||
{
|
||||
if (row.Width <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
UiTextClipped(g, label, m_fonts->uiSmall,
|
||||
gd::RectF(row.X, row.Y, std::max(10.0f, labelWidth), row.Height),
|
||||
m_theme->textMuted, TextAlign::LEFT, layout.content);
|
||||
// ⚠️ 统计数字**绝不省略**(用户 2026-11-03 要求):
|
||||
// 面板宽度已按真实文本量宽(`EdgePanelWidth` 逐信号实测),
|
||||
// 因此这里用 `TextAlign::RIGHT` + 完整矩形即可;
|
||||
// 万一仍被窗口边界夹住,也宁可超出面板右缘(由内容区裁剪兜底),
|
||||
// **不要**退化成 `…` 让用户看不到真实数值。
|
||||
UiTextClipped(g, text, m_fonts->monoSmall,
|
||||
gd::RectF(row.X + labelWidth + 8.0f * m_scale, row.Y,
|
||||
std::max(10.0f, row.Width - labelWidth - 8.0f * m_scale),
|
||||
row.Height),
|
||||
m_theme->textStrong, TextAlign::RIGHT, layout.content);
|
||||
};
|
||||
const auto paintInputRow = [&](const gd::RectF& field, const wchar_t* label,
|
||||
const std::wstring& text, bool editing)
|
||||
{
|
||||
if (field.Width <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 标签与数值同高:标签从内容区左缘画到字段左缘。
|
||||
UiTextClipped(
|
||||
g, label, m_fonts->uiSmall,
|
||||
gd::RectF(layout.content.X, field.Y,
|
||||
std::max(10.0f, field.X - 8.0f * m_scale - layout.content.X),
|
||||
field.Height),
|
||||
m_theme->textMuted, TextAlign::LEFT, layout.content);
|
||||
if (editing)
|
||||
{
|
||||
return; // 正在编辑:内容由 `PaintEditField` 画
|
||||
}
|
||||
const gd::RectF box(field.X, field.Y + 2.0f * m_scale, field.Width,
|
||||
std::max(10.0f, field.Height - 4.0f * m_scale));
|
||||
UiFillRoundRect(g, box, 3.0f * m_scale, m_theme->pressed);
|
||||
UiDrawRoundRect(g, box, 3.0f * m_scale, m_theme->border, 1.0f);
|
||||
UiTextClipped(g, text, m_fonts->monoSmall,
|
||||
gd::RectF(box.X + 4.0f * m_scale, box.Y,
|
||||
std::max(6.0f, box.Width - 8.0f * m_scale), box.Height),
|
||||
m_theme->textStrong, TextAlign::LEFT, layout.content);
|
||||
};
|
||||
const bool hasInterval = (item.maxInterval > 0.0) && (item.minInterval > 0.0);
|
||||
// 「最大 / 最小数据间隔」= 自适应单位的**小数**文本(绝不用科学计数法),
|
||||
// 末尾追加一个括号内的「对应频率」尾缀(单位在 Hz/kHz/MHz/GHz 之间自适应)。
|
||||
//
|
||||
// ⚠️⚠️ 全部使用**真实值**(用户 2026-11-03 明确要求):
|
||||
// * 时间间隔:`FormatDurationCompound` 会把次级单位保留 3 位小数
|
||||
// ⇒ `0.003696631s` 显示为 `3ms 696.631us`(旧实现舍入到 3 位有效数字
|
||||
// 得到 `3ms 700us`,把真实尾数抹掉了);
|
||||
// * 频率:由**未舍入的真实间隔**换算 ⇒ `270.517 Hz`(旧实现用舍入后的
|
||||
// `3.70ms` 换算得到 `270.27 Hz`,与输入框不一致)。
|
||||
// 两条路径都用真实值 ⇒ 统计行与输入框天然一致。
|
||||
const auto intervalWithFrequency = [](double seconds) -> std::wstring
|
||||
{
|
||||
std::wstring text = FormatDurationCompound(seconds);
|
||||
if (std::isfinite(seconds) && seconds > 0.0)
|
||||
{
|
||||
text += L"(";
|
||||
text += FormatFrequencyText(1.0 / seconds);
|
||||
text += L")";
|
||||
}
|
||||
return text;
|
||||
};
|
||||
if (rowIndex < layout.rowInfoMaxInterval.size())
|
||||
{
|
||||
paintInfoRow(layout.rowInfoMaxInterval[rowIndex], kResampleMaxIntervalLabel,
|
||||
hasInterval ? intervalWithFrequency(item.maxInterval) : L"—");
|
||||
}
|
||||
if (rowIndex < layout.rowInfoMinInterval.size())
|
||||
{
|
||||
paintInfoRow(layout.rowInfoMinInterval[rowIndex], kResampleMinIntervalLabel,
|
||||
hasInterval ? intervalWithFrequency(item.minInterval) : L"—");
|
||||
}
|
||||
const double interval = (item.interval > 0.0) ? item.interval : item.minInterval;
|
||||
const bool editingInterval = (m_editTarget == PlotEditTarget::RESAMPLE_INTERVAL) &&
|
||||
(m_editLane == static_cast<int>(index)) && (m_editMeanRef == ref);
|
||||
const bool editingFrequency = (m_editTarget == PlotEditTarget::RESAMPLE_FREQUENCY) &&
|
||||
(m_editLane == static_cast<int>(index)) && (m_editMeanRef == ref);
|
||||
if (rowIndex < layout.rowIntervalField.size())
|
||||
{
|
||||
// 非编辑态:自适应单位 + 小数(与「最大/最小数据间隔」同一套单位形式)。
|
||||
// 编辑态:只显示**秒**数值(不带单位),由 `PaintEditField` 绘制。
|
||||
paintInputRow(layout.rowIntervalField[rowIndex], kResampleIntervalLabel,
|
||||
FormatIntervalText(interval), editingInterval);
|
||||
}
|
||||
if (rowIndex < layout.rowFrequencyField.size())
|
||||
{
|
||||
// ⚠️ 频率同样用**真实间隔**换算(与时间间隔行同源)⇒ `270.517 Hz`。
|
||||
const double hertz = (interval > 0.0) ? (1.0 / interval) : 0.0;
|
||||
// 非编辑态:频率单位自适应(Hz/kHz/MHz/GHz);编辑态:只显示 Hz 数值。
|
||||
paintInputRow(layout.rowFrequencyField[rowIndex], kResampleFrequencyLabel,
|
||||
(hertz > 0.0) ? FormatFrequencyText(hertz) : L"—", editingFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Marker panel content: per-signal group bars (collapsible) + table header +
|
||||
* fixed-point rows, with sticky group bars.
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user