diff --git a/Components/PlotView/Inc/PlotView.hpp b/Components/PlotView/Inc/PlotView.hpp index 1449abc..98609ee 100644 --- a/Components/PlotView/Inc/PlotView.hpp +++ b/Components/PlotView/Inc/PlotView.hpp @@ -2070,6 +2070,7 @@ namespace jdi /** @brief 「均值滤波」面板每个信号标题栏左侧的**展开 / 收起三角**(仅勾选后出现)。 */ std::vector rowTriangles; std::vector rowRefs; + /** * @brief 「自由标记」面板的一行(一个固定点)。 * @@ -2210,6 +2211,45 @@ namespace jdi MARKER_COL_TIME = 4, MARKER_COL_DT = 5 }; + + /** + * @brief 追加一**行**(条目栏 / 名称 / 三角 / 复选框),其余列一律补空矩形。 + * + * ⚠️⚠️ 这是**唯一**允许新增行的入口。面板每行对应 18 个平行数组 + * (`rowRefs` / `rowHeaders` / `rowChecks` / `rowLevelTracks` / …); + * 如果每个排版分支各写一遍 `push_back`,只要某分支漏推一项, + * 那一列之后的所有行都会**读到别人的矩形**("改一次布局就出错"的典型机制)。 + * 现在:新增行的分支只调本函数,**新增一列只需改这里一处**; + * 参数行 / 重采样行则用 `rowXxx.back() = …` 赋值(长度天然对齐)。 + * + * @param [IN] ref 该行对应的信号。 + * @param [IN] header 信号色叠底条目栏(整行宽度)。 + * @param [IN] nameRect 名称(表格里的名称列)。 + * @param [IN] triangle 左侧展开 / 收起三角(不适用时传空矩形)。 + * @param [IN] check 右端复选框。 + */ + void 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); + 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()); + 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()); + } }; /** diff --git a/Components/PlotView/Src/PlotViewPanels.cpp b/Components/PlotView/Src/PlotViewPanels.cpp index 7b78260..34b22c5 100644 --- a/Components/PlotView/Src/PlotViewPanels.cpp +++ b/Components/PlotView/Src/PlotViewPanels.cpp @@ -735,22 +735,12 @@ namespace jdi { continue; } - layout.rowRefs.push_back(ref); - layout.rowHeaders.push_back(gd::RectF(contentLeft, rowY, innerWidth, rowH)); - layout.rowNameRects.push_back( - gd::RectF(contentLeft + 4.0f * m_scale, rowY, - std::max(10.0f, nameColumn - 8.0f * m_scale), rowH)); - layout.rowTriangles.push_back(gd::RectF()); - layout.rowChecks.push_back(gd::RectF()); - layout.rowPoints.push_back(gd::RectF()); - layout.rowIterations.push_back(gd::RectF()); - layout.rowStats.push_back(gd::RectF()); - layout.rowLevelTracks.push_back(gd::RectF()); - layout.rowLevelValues.push_back(gd::RectF()); - layout.rowLevelResets.push_back(gd::RectF()); - layout.rowIterTracks.push_back(gd::RectF()); - layout.rowIterValues.push_back(gd::RectF()); - layout.rowIterResets.push_back(gd::RectF()); + // ⚠️ 行的追加走 `AppendRow()`(**唯一入口**):18 个平行数组一次补齐, + // 不会出现"某个分支漏推一列 ⇒ 该列之后所有行读到别人的矩形"。 + layout.AppendRow(ref, gd::RectF(contentLeft, rowY, innerWidth, rowH), + gd::RectF(contentLeft + 4.0f * m_scale, rowY, + std::max(10.0f, nameColumn - 8.0f * m_scale), rowH), + gd::RectF(), gd::RectF()); rowY += rowH; } } @@ -886,42 +876,26 @@ namespace jdi checked = (item != nullptr); itemCollapsed = (item != nullptr && item->collapsed); } - layout.rowRefs.push_back(ref); // 组头(信号色叠底):整行宽度,左侧三角(仅勾选时)+ 名称,右端复选框。 - layout.rowHeaders.push_back( - gd::RectF(contentLeft, rowY, innerWidth, groupRow)); - layout.rowNameRects.push_back(gd::RectF(contentLeft, rowY, innerWidth, groupRow)); - layout.rowTriangles.push_back( + // ⚠️ 行的追加走 `AppendRow()`(**唯一入口**):18 个平行数组一次补齐, + // 不会出现"某个分支漏推一列 ⇒ 该列之后所有行读到别人的矩形"。 + // 下面的参数行 / 重采样行一律用 `rowXxx.back() = …` **赋值**(长度天然对齐)。 + layout.AppendRow( + ref, gd::RectF(contentLeft, rowY, innerWidth, groupRow), + gd::RectF(contentLeft, rowY, innerWidth, groupRow), (checked && !clipPanel) ? gd::RectF(contentLeft + 3.0f * m_scale, rowY + (groupRow - kEdgePanelRowTriangleSize * m_scale) * 0.5f, kEdgePanelRowTriangleSize * m_scale, kEdgePanelRowTriangleSize * m_scale) - : gd::RectF()); - layout.rowChecks.push_back( + : gd::RectF(), gd::RectF(contentLeft + innerWidth - closeSize - 4.0f * m_scale, rowY + (groupRow - closeSize) * 0.5f, closeSize, closeSize)); - layout.rowPoints.push_back(gd::RectF()); - layout.rowIterations.push_back(gd::RectF()); - layout.rowStats.push_back(gd::RectF()); rowY += groupRow; // 参数行:只有勾选且未收起时才有几何(未勾选的行两者都是空矩形)。 if (!checked || itemCollapsed || clipPanel) { - layout.rowLevelTracks.push_back(gd::RectF()); - layout.rowLevelValues.push_back(gd::RectF()); - layout.rowLevelResets.push_back(gd::RectF()); - layout.rowIterTracks.push_back(gd::RectF()); - layout.rowIterValues.push_back(gd::RectF()); - layout.rowIterResets.push_back(gd::RectF()); - if (resamplePanel) - { - layout.rowInfoMaxInterval.push_back(gd::RectF()); - layout.rowInfoMinInterval.push_back(gd::RectF()); - layout.rowIntervalField.push_back(gd::RectF()); - layout.rowFrequencyField.push_back(gd::RectF()); - } continue; } if (resamplePanel) @@ -929,27 +903,17 @@ namespace jdi // 「重采样」四行:① 最大间隔(只读)② 最小间隔(只读) // ③ 重采样间隔(输入)④ 重采样频率(输入,与③联动)。 // 每行 = 左侧标签 + 右侧数值/输入框区域。 - layout.rowLevelTracks.push_back(gd::RectF()); - layout.rowLevelValues.push_back(gd::RectF()); - layout.rowLevelResets.push_back(gd::RectF()); - layout.rowIterTracks.push_back(gd::RectF()); - layout.rowIterValues.push_back(gd::RectF()); - layout.rowIterResets.push_back(gd::RectF()); - layout.rowInfoMaxInterval.push_back( - gd::RectF(contentLeft, rowY, innerWidth, sliderRow)); + layout.rowInfoMaxInterval.back() = gd::RectF(contentLeft, rowY, innerWidth, sliderRow); rowY += sliderRow; - layout.rowInfoMinInterval.push_back( - gd::RectF(contentLeft, rowY, innerWidth, sliderRow)); + layout.rowInfoMinInterval.back() = gd::RectF(contentLeft, rowY, innerWidth, sliderRow); rowY += sliderRow; // 输入行的**数值矩形**靠右对齐,宽度取剩余可用宽度(绘制框与编辑框同源)。 const float fieldLeft = contentLeft + rowLabelColumn + 8.0f * m_scale; const float fieldWidth = std::max(24.0f * m_scale, innerWidth - rowLabelColumn - 8.0f * m_scale); - layout.rowIntervalField.push_back( - gd::RectF(fieldLeft, rowY, fieldWidth, sliderRow)); + layout.rowIntervalField.back() = gd::RectF(fieldLeft, rowY, fieldWidth, sliderRow); rowY += sliderRow; - layout.rowFrequencyField.push_back( - gd::RectF(fieldLeft, rowY, fieldWidth, sliderRow)); + layout.rowFrequencyField.back() = gd::RectF(fieldLeft, rowY, fieldWidth, sliderRow); rowY += sliderRow; continue; } @@ -961,22 +925,22 @@ namespace jdi } const gd::RectF pointsTrack(trackLeft, rowY + (sliderRow - trackHeight) * 0.5f, trackWidth, trackHeight); - layout.rowLevelTracks.push_back(pointsTrack); - layout.rowLevelValues.push_back( + layout.rowLevelTracks.back() = pointsTrack; + layout.rowLevelValues.back() = gd::RectF(pointsTrack.X + pointsTrack.Width + 6.0f * m_scale, rowY, valueColumn, - sliderRow)); - layout.rowLevelResets.push_back( - gd::RectF(resetLeft, rowY + (sliderRow - resetSize) * 0.5f, resetSize, resetSize)); + sliderRow); + layout.rowLevelResets.back() = + gd::RectF(resetLeft, rowY + (sliderRow - resetSize) * 0.5f, resetSize, resetSize); rowY += sliderRow; const gd::RectF iterTrack(trackLeft, rowY + (sliderRow - trackHeight) * 0.5f, trackWidth, trackHeight); - layout.rowIterTracks.push_back(iterTrack); - layout.rowIterValues.push_back( + layout.rowIterTracks.back() = iterTrack; + layout.rowIterValues.back() = gd::RectF(iterTrack.X + iterTrack.Width + 6.0f * m_scale, rowY, valueColumn, - sliderRow)); - layout.rowIterResets.push_back( - gd::RectF(resetLeft, rowY + (sliderRow - resetSize) * 0.5f, resetSize, resetSize)); + sliderRow); + layout.rowIterResets.back() = + gd::RectF(resetLeft, rowY + (sliderRow - resetSize) * 0.5f, resetSize, resetSize); rowY += sliderRow; } }