面板限高与滚动改造 + 边沿统计不再卡在“统计中…” + 组名折起真正收起
一、浮动面板(用户报告:下方空白、滚动时画到面板外、未改造的面板让一切交互卡顿) 1. ⭐ 根因:旧「逐条目」排布没有高度上限 —— 288 条信号时面板 13010px(绘图区 846px) ⇒ PanelRectInsideLayer 恒假 ⇒ 面板像素缓存一次都存不进去 ⇒ 每帧整块重画。 现在 5 个面板全部限高 + 可滚动,旧的 itemized 布局/绘制/交互整体删除。 2. 无按钮面板(中心轴边沿计数)不再预留「按钮行 + pad」⇒ 底部空白 55px → 8.8px(1 个 pad)。 3. ⚠️ 统计文字改用 UiTextClipped:GDI 文本不受 GDI+ SetClip 约束,滚动时画到标题栏之外。 4. 「弱峰调整」改造为与均值滤波同款的分组列表(条目栏 + 右侧复选框 + 展开后的两行滑块 + 复位按钮 + 底部「构建为新信号」);新增 Lane::weakPeakScroll / SetLaneWeakPeakScroll, WeakPeakItem 增加 collapsed。 5. 「边沿计数」保留两行排版,但内容区同样限高、可滚动(新增 Lane::edgeCountScroll)。 6. 鼠标指针:列表式面板的条目栏**只有已勾选**才显示手型(未勾选点击无效果,保持箭头); 参数行控件只在参数行展开时给手型 / 左右箭头。 7. 内部标志 m_meanDragCenter / m_editMeanCenter(bool)改为 m_meanDragKind / m_editMeanKind (EdgePanelKind),以便 MEAN / CENTER / PEAK / COUNT 共用同一套滚动与滑块拖拽。 二、边沿统计“全部统计中…且永不更新”(288 信号合并到同一网格) 1. ⭐ 根因:累积状态上限固定 64 < 信号数 288 ⇒ 新建状态总淘汰未完成的 ⇒ 没有任何状态能扫完。 新增 EdgeScanStateLimit() = max(64, 全部信号数 × 2 + 16)。 2. 去掉每状态 60000 的固定配额下限(状态一多就把整帧预算耗光),改为 budget / 未完成状态数; 分母只数未完成状态,短信号扫完后立刻把预算让给长信号。 三、组名折起后“三角已收起但信号行仍在、仍可点选” 1. ⭐ 根因:RequestEdgeScanFrame() 在每个未完成状态里各 Post 一次(单帧最多 288 次), 把优先级最低的 WM_TIMER 饿死 ⇒ AdvanceGroupAnimations 永不执行 ⇒ 折起动画永不结束。 修法:每帧最多 Post 一次(m_edgeScanRequestFrame)。 2. 另一道保险:SignalPanel::EnsureRows() 开头推进一次动画 —— 动画进度只依赖挂钟时间, 不依赖消息能否按时到达。 四、验证(离屏探针 .preflight/p20,全部 SW_HIDE + 合成消息,不占用鼠标/键盘/屏幕) p20.exe 9/9:面板限高且像素缓存真的写进去了、288 个统计全部 complete、中心轴面板底部留白、 组名折起后行集合无该组信号、两组都折起后 signalRows == 0(真实消息 + WM_TIMER)。 p20c.exe 11/11:弱峰面板限高/可滚动/缓存命中、展开与收起、滚动生效、条目栏手型规则、 中心轴滚动时标题栏 0 像素变化(A/B 关掉裁剪 ⇒ 805px)、288 信号空闲帧 2.6ms。 make release / make debug 零 warning。
This commit is contained in:
1 parent
334015c1a9
commit
7a6fcd68e2
9 files changed
+914
-795
No files matched your search
@@ -28,10 +28,12 @@ namespace jdi
|
||||
const bool centerAxis = (kind == EdgePanelKind::CENTER);
|
||||
const bool meanFilter = (kind == EdgePanelKind::MEAN);
|
||||
const bool weakPeak = (kind == EdgePanelKind::PEAK);
|
||||
// 「弱峰调整」仍是旧式逐条目面板(名称 + 复选框 + 两行滑块)。
|
||||
// ⚠️ 「均值滤波」与「中心轴边沿计数」已改为**可滚动的分组列表**(信号色叠底条目栏),
|
||||
// 它们的宽度由下面的 `groupWidth` / 滑块行分支确定。
|
||||
const bool itemized = weakPeak;
|
||||
// 「均值滤波」「中心轴边沿计数」「弱峰调整」自 2026-11-02 起全部是**可滚动的分组列表**
|
||||
//(信号色叠底条目栏 + 右侧复选框 + 展开后的参数行)。
|
||||
// ⚠️ 旧式「逐条目」排版(名称 + 复选框 + 参数行,无滚动)已彻底删除:
|
||||
// 上百个信号时它会算出上万像素高的面板,超出绘图区后**面板像素缓存一次都存不进去**
|
||||
// ⇒ 每一帧整块重画 ⇒ 所有交互卡顿(用户 2026-11-02 报告)。
|
||||
const bool listStyle = (meanFilter || centerAxis || weakPeak);
|
||||
|
||||
// 标题行 = “GX” 徽标 + 标题(“边沿计数” / “中心轴边沿计数” / “均值滤波” / “弱峰调整”
|
||||
// + 光标区间尾缀)。
|
||||
@@ -65,16 +67,10 @@ namespace jdi
|
||||
std::min(UiMeasureTextWidth(m_fonts->uiSmall, resolved.data->name),
|
||||
kEdgePanelNameMaxWidth * m_scale);
|
||||
nameColumnWidth = std::max(nameColumnWidth, nameWidth);
|
||||
if (itemized)
|
||||
if (listStyle)
|
||||
{
|
||||
// 逐条目面板:第一排 = 名称 + 复选框;第三排(中心轴)= 统计结果。
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + nameWidth +
|
||||
kEdgePanelMeanCheckSize * m_scale + 6.0f * m_scale);
|
||||
}
|
||||
if (meanFilter || centerAxis)
|
||||
{
|
||||
// 「均值滤波 / 中心轴边沿计数」的条目 = **信号色叠底条目栏**(三角 + 名称 + 复选框)
|
||||
// 与参数行(标签 + 轨道 + 数值 + 复位按钮),右侧还要给滚动条留位。
|
||||
// 条目 = **信号色叠底条目栏**(三角 + 名称 + 复选框)与参数行
|
||||
//(标签 + 轨道 + 数值 + 复位按钮),右侧还要给滚动条留位。
|
||||
const float groupWidth = kEdgePanelPad * 2.0f * m_scale +
|
||||
kEdgePanelDisclosureWidth * m_scale + 6.0f * m_scale +
|
||||
nameWidth + 6.0f * m_scale +
|
||||
@@ -126,19 +122,22 @@ namespace jdi
|
||||
std::max(nameColumnWidth, countsWidth));
|
||||
}
|
||||
|
||||
if (meanFilter || centerAxis)
|
||||
if (listStyle)
|
||||
{
|
||||
// 两行滑块 = 「标签列 + 轨道(最小宽度)+ 数值列 + 复位按钮」(两行共用同一列宽),
|
||||
// 右侧再给滚动条留位(上百个信号时内容必然超高)。
|
||||
const float labelColumn =
|
||||
std::max(UiMeasureTextWidth(m_fonts->uiSmall, kMeanFilterPointsSliderLabel),
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, kMeanFilterIterationsSliderLabel));
|
||||
const float weakPeakLabelColumn =
|
||||
std::max(UiMeasureTextWidth(m_fonts->uiSmall, kWeakPeakLevelLabel),
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, kWeakPeakIterationsSliderLabel));
|
||||
const float valueColumn =
|
||||
std::max(UiMeasureTextWidth(m_fonts->monoSmall, L"100000"),
|
||||
UiMeasureTextWidth(m_fonts->monoSmall, L"1000"));
|
||||
const float sliderRowWidth = labelColumn + 6.0f * m_scale + kWeakPeakTrackMinWidth * m_scale +
|
||||
6.0f * m_scale + valueColumn + kWeakPeakResetGap * m_scale +
|
||||
kWeakPeakResetButtonSize * m_scale;
|
||||
UiMeasureTextWidth(m_fonts->monoSmall, L"-400%"));
|
||||
const float sliderRowWidth = std::max(labelColumn, weakPeakLabelColumn) + 6.0f * m_scale +
|
||||
kWeakPeakTrackMinWidth * m_scale + 6.0f * m_scale + valueColumn +
|
||||
kWeakPeakResetGap * m_scale + kWeakPeakResetButtonSize * m_scale;
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + sliderRowWidth +
|
||||
(kFreeMarkerScrollWidth + 4.0f) * m_scale);
|
||||
}
|
||||
@@ -153,7 +152,7 @@ namespace jdi
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + rowWidth);
|
||||
}
|
||||
|
||||
if (meanFilter || weakPeak)
|
||||
if (listStyle)
|
||||
{
|
||||
// 底部按钮行 = 「构建为新信号」,按按钮文字实测宽度确定下限。
|
||||
const float buttonWidth =
|
||||
@@ -161,6 +160,13 @@ namespace jdi
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + buttonWidth);
|
||||
}
|
||||
|
||||
if (kind == EdgePanelKind::COUNT)
|
||||
{
|
||||
// 「边沿计数」:右侧要给内容滚动条留位(上百个信号时内容必然超高)。
|
||||
width = std::max(width, kEdgePanelPad * 3.0f * m_scale +
|
||||
(kFreeMarkerScrollWidth + 4.0f) * m_scale);
|
||||
}
|
||||
|
||||
if (kind == EdgePanelKind::MARK)
|
||||
{
|
||||
// 「自由标记」面板:宽度 = 六列(取「缺省列宽」「实测内容宽度」「列标题宽度」三者
|
||||
@@ -254,8 +260,7 @@ namespace jdi
|
||||
// ⚠️ 「中心轴边沿计数」从 2026-11-02 起同样改为**可滚动的分组列表**(用户明确要求
|
||||
// “改造为与均值滤波相同的风格”)⇒ 也不走 `itemized`。它没有「构建为新信号」按钮,
|
||||
// 但内容多一行「上升沿 / 下降沿」统计。
|
||||
const bool listStyle = (meanFilter || centerAxis);
|
||||
const bool itemized = weakPeak;
|
||||
const bool listStyle = (meanFilter || centerAxis || weakPeak);
|
||||
const bool hasButton = (meanFilter || weakPeak);
|
||||
const bool enabled = meanFilter ? lane.meanFilter
|
||||
: (weakPeak ? lane.weakPeak
|
||||
@@ -299,27 +304,27 @@ namespace jdi
|
||||
const float titleHeight = kEdgePanelTitleHeight * m_scale;
|
||||
const float rowHeight = kEdgePanelRowHeight * m_scale;
|
||||
const float pad = kEdgePanelPad * m_scale;
|
||||
// 「中心轴边沿计数」「均值滤波」「弱峰调整」共用同一套「逐条目」排布,
|
||||
// 因此不再有独立的底部编辑行;只有「边沿计数」保留两行文本式排版。
|
||||
// 「中心轴边沿计数」「均值滤波」「弱峰调整」共用同一套**可滚动列表**排布,
|
||||
// 因此不再有独立的底部编辑行;只有「边沿计数」保留两行文本式排版(同样可滚动)。
|
||||
const float buttonHeight = hasButton ? (kEdgePanelButtonHeight * m_scale) : 0.0f;
|
||||
const float rowsPerSignal = meanFilter ? 1.0f : 2.0f;
|
||||
const float rowsPerSignal = 2.0f; // 「边沿计数」每信号两行(名称 / 上升沿·下降沿)
|
||||
// 面板底部(内容区之下)占的纵向空间:有按钮时 = pad + 按钮 + pad,
|
||||
// 无按钮时只有 pad。⚠️ 旧实现无条件按「按钮行 + 三个 pad」预留 ——
|
||||
// 无按钮的面板(中心轴边沿计数)因此在内容区下方留下**一大块空白**
|
||||
//(= 按钮高度 30 逻辑像素 + 多出的 pad,用户 2026-11-02 报告)。
|
||||
const float footerHeight = hasButton ? (buttonHeight + pad) : 0.0f;
|
||||
const float bottomPad = pad;
|
||||
|
||||
// 逐条目面板:每个条目 = 第一排(信号名 + 复选框)
|
||||
// + 参数部分(**只在该条目被勾选时才占位**,因此面板高度随勾选数量动态变化):
|
||||
// * MEAN / CENTER:一行「均值点数 X + 迭代次数 N」输入框(中心轴再多一行统计结果);
|
||||
// * PEAK:两行滑块(调整幅度 % / 迭代次数 N),每个信号各有一套自己的参数。
|
||||
const bool itemHasParams = (centerAxis || weakPeak);
|
||||
const float weakPeakSliderRow = kWeakPeakSliderRowHeight * m_scale;
|
||||
float meanRowHeight = 0.0f;
|
||||
float itemsHeight = 0.0f;
|
||||
float meanContentHeight = 0.0f;
|
||||
float height = 0.0f;
|
||||
if (listStyle)
|
||||
{
|
||||
// 「均值滤波 / 中心轴边沿计数」:内容区 = 逐信号的**信号色叠底条目栏**
|
||||
// 「均值滤波 / 中心轴边沿计数 / 弱峰调整」:内容区 = 逐信号的**信号色叠底条目栏**
|
||||
//(内容总是显示,用于勾选/取消)+(勾选且未收起时)参数行。
|
||||
// 「中心轴」的参数行 = 一行「上升沿 / 下降沿」统计 + 两行滑块;
|
||||
// 「均值滤波」= 两行滑块。内容超高时出现滚动条;底部「构建为新信号」按钮固定不滚动。
|
||||
// 「均值滤波」与「弱峰调整」= 两行滑块。
|
||||
// 内容超高时出现滚动条;底部「构建为新信号」按钮固定不滚动。
|
||||
const float groupRow = kEdgePanelMeanRowHeight * m_scale;
|
||||
const float sliderRow = weakPeakSliderRow;
|
||||
float content = 0.0f;
|
||||
@@ -331,9 +336,19 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
content += groupRow;
|
||||
const Lane::MeanFilterItem* item = meanFilter ? lane.FindMeanFilterItem(ref)
|
||||
: lane.FindEdgeCenterItem(ref);
|
||||
if (item != nullptr && !item->collapsed)
|
||||
bool expanded = false;
|
||||
if (weakPeak)
|
||||
{
|
||||
const Lane::WeakPeakItem* item = lane.FindWeakPeakItem(ref);
|
||||
expanded = (item != nullptr && !item->collapsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
const Lane::MeanFilterItem* item = meanFilter ? lane.FindMeanFilterItem(ref)
|
||||
: lane.FindEdgeCenterItem(ref);
|
||||
expanded = (item != nullptr && !item->collapsed);
|
||||
}
|
||||
if (expanded)
|
||||
{
|
||||
content += sliderRow * 2.0f;
|
||||
if (centerAxis)
|
||||
@@ -343,50 +358,29 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
meanContentHeight = content;
|
||||
const float budget = m_plotRect.Height - (titleHeight + pad * 3.0f + buttonHeight);
|
||||
const float budget =
|
||||
m_plotRect.Height - (titleHeight + pad + bottomPad + footerHeight);
|
||||
const float maxContent =
|
||||
std::max(3.0f * groupRow,
|
||||
std::min(kFreeMarkerContentMaxHeight * m_scale, std::max(3.0f * groupRow, budget)));
|
||||
const float view = std::min(content, maxContent);
|
||||
height = titleHeight + pad + view + pad + buttonHeight + pad;
|
||||
height = titleHeight + pad + view + bottomPad + footerHeight;
|
||||
}
|
||||
else if (itemized)
|
||||
else if (freeMarker)
|
||||
{
|
||||
meanRowHeight = kEdgePanelMeanRowHeight * m_scale;
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
itemsHeight += meanRowHeight;
|
||||
const bool checked = meanFilter ? lane.MeanFilterEnabled(ref)
|
||||
: (weakPeak ? lane.WeakPeakEnabled(ref)
|
||||
: lane.EdgeCenterEnabled(ref));
|
||||
if (checked && itemHasParams)
|
||||
{
|
||||
if (weakPeak)
|
||||
{
|
||||
itemsHeight += weakPeakSliderRow * 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemsHeight += (kEdgePanelMeanEditHeight + kEdgePanelMeanRowGap) * m_scale;
|
||||
if (centerAxis)
|
||||
{
|
||||
itemsHeight += kEdgePanelMeanRowHeight * m_scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 底部按钮行:上方与条目之间、下方与面板下边界之间各留一个 `pad`,
|
||||
// 与面板的左右内边距完全一致(原先按钮直接贴着下边框,看起来很不协调)。
|
||||
height = titleHeight + pad + itemsHeight + pad + buttonHeight + (hasButton ? pad : 0.0f);
|
||||
// 「自由标记」的高度在下面的 `if (freeMarker)` 分支里计算(含底部固定区)。
|
||||
height = titleHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
height = titleHeight + pad + static_cast<float>(rowCount) * rowHeight * rowsPerSignal + pad;
|
||||
// 「边沿计数」:每信号两行(名称 / 上升沿·下降沿),内容超高时滚动。
|
||||
const float content = static_cast<float>(rowCount) * rowHeight * rowsPerSignal;
|
||||
meanContentHeight = content;
|
||||
const float budget = m_plotRect.Height - (titleHeight + pad + bottomPad);
|
||||
const float maxContent =
|
||||
std::max(2.0f * rowHeight, std::min(kFreeMarkerContentMaxHeight * m_scale, budget));
|
||||
const float view = std::min(content, maxContent);
|
||||
height = titleHeight + pad + view + bottomPad;
|
||||
}
|
||||
|
||||
// 「自由标记」面板:按信号分组(**分组栏** + 下属固定点条目),
|
||||
@@ -574,11 +568,13 @@ namespace jdi
|
||||
const float groupRow = kEdgePanelMeanRowHeight * m_scale;
|
||||
const float sliderRow = kWeakPeakSliderRowHeight * m_scale;
|
||||
const float scrollW = kFreeMarkerScrollWidth * m_scale;
|
||||
const float footerHeight = hasButton ? (buttonRowHeight + pad) : 0.0f;
|
||||
const float viewHeight =
|
||||
std::max(0.0f, height - titleHeight - pad * 3.0f - buttonRowHeight);
|
||||
std::max(0.0f, height - titleHeight - pad - pad - footerHeight);
|
||||
const bool scrollable = meanContentHeight > viewHeight + 0.5f;
|
||||
const float maxScroll = std::max(0.0f, meanContentHeight - viewHeight);
|
||||
const float storedScroll = meanFilter ? lane.meanFilterScroll : lane.edgeCenterScroll;
|
||||
const float storedScroll = meanFilter ? lane.meanFilterScroll
|
||||
: (weakPeak ? lane.weakPeakScroll : lane.edgeCenterScroll);
|
||||
const float scroll = std::max(0.0f, std::min(storedScroll, maxScroll));
|
||||
const float innerWidth =
|
||||
std::max(0.0f, width - pad * 2.0f - (scrollable ? (scrollW + 4.0f * m_scale) : 0.0f));
|
||||
@@ -600,11 +596,13 @@ namespace jdi
|
||||
|
||||
// 两行滑块的列宽(与 `EdgePanelWidth` 同一组算式 ⇒ 布局与宽度不会漂移)。
|
||||
const float labelColumn =
|
||||
std::max(UiMeasureTextWidth(m_fonts->uiSmall, kMeanFilterPointsSliderLabel),
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, kMeanFilterIterationsSliderLabel));
|
||||
std::max(std::max(UiMeasureTextWidth(m_fonts->uiSmall, kMeanFilterPointsSliderLabel),
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, kMeanFilterIterationsSliderLabel)),
|
||||
std::max(UiMeasureTextWidth(m_fonts->uiSmall, kWeakPeakLevelLabel),
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, kWeakPeakIterationsSliderLabel)));
|
||||
const float valueColumn =
|
||||
std::max(UiMeasureTextWidth(m_fonts->monoSmall, L"100000"),
|
||||
UiMeasureTextWidth(m_fonts->monoSmall, L"1000"));
|
||||
UiMeasureTextWidth(m_fonts->monoSmall, L"-400%"));
|
||||
const float resetSize = kWeakPeakResetButtonSize * m_scale;
|
||||
const float resetGap = kWeakPeakResetGap * m_scale;
|
||||
const float trackHeight = kWeakPeakTrackHeight * m_scale;
|
||||
@@ -624,9 +622,21 @@ namespace jdi
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const Lane::MeanFilterItem* item = meanFilter ? lane.FindMeanFilterItem(ref)
|
||||
: lane.FindEdgeCenterItem(ref);
|
||||
const bool checked = (item != nullptr);
|
||||
bool checked = false;
|
||||
bool itemCollapsed = false;
|
||||
if (weakPeak)
|
||||
{
|
||||
const Lane::WeakPeakItem* item = lane.FindWeakPeakItem(ref);
|
||||
checked = (item != nullptr);
|
||||
itemCollapsed = (item != nullptr && item->collapsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
const Lane::MeanFilterItem* item = meanFilter ? lane.FindMeanFilterItem(ref)
|
||||
: lane.FindEdgeCenterItem(ref);
|
||||
checked = (item != nullptr);
|
||||
itemCollapsed = (item != nullptr && item->collapsed);
|
||||
}
|
||||
layout.rowRefs.push_back(ref);
|
||||
// 组头(信号色叠底):整行宽度,左侧三角(仅勾选时)+ 名称,右端复选框。
|
||||
layout.rowHeaders.push_back(
|
||||
@@ -647,7 +657,7 @@ namespace jdi
|
||||
rowY += groupRow;
|
||||
|
||||
// 参数行:只有勾选且未收起时才有几何(未勾选的行两者都是空矩形)。
|
||||
if (!checked || item->collapsed)
|
||||
if (!checked || itemCollapsed)
|
||||
{
|
||||
layout.rowLevelTracks.push_back(gd::RectF());
|
||||
layout.rowLevelValues.push_back(gd::RectF());
|
||||
@@ -684,139 +694,30 @@ namespace jdi
|
||||
rowY += sliderRow;
|
||||
}
|
||||
}
|
||||
else if (itemized)
|
||||
else if (!freeMarker)
|
||||
{
|
||||
const float buttonRowHeight = kEdgePanelButtonHeight * m_scale;
|
||||
if (hasButton)
|
||||
// 「边沿计数」:内容区 = 逐信号两行(名称 / 上升沿·下降沿),可滚动。
|
||||
const float scrollW = kFreeMarkerScrollWidth * m_scale;
|
||||
const float viewHeight = std::max(0.0f, height - titleHeight - pad - pad);
|
||||
const bool scrollable = meanContentHeight > viewHeight + 0.5f;
|
||||
const float maxScroll = std::max(0.0f, meanContentHeight - viewHeight);
|
||||
const float scroll = std::max(0.0f, std::min(lane.edgeCountScroll, maxScroll));
|
||||
const float innerWidth =
|
||||
std::max(0.0f, width - pad * 2.0f - (scrollable ? (scrollW + 4.0f * m_scale) : 0.0f));
|
||||
layout.content = gd::RectF(left + pad, top + titleHeight + pad, innerWidth, viewHeight);
|
||||
layout.markerContentHeight = meanContentHeight;
|
||||
layout.scrollOffset = scroll;
|
||||
if (scrollable)
|
||||
{
|
||||
// 底部按钮与面板下边界之间留出与左右、与内容相同的 `pad` 间距
|
||||
//(原先按钮行直接贴着下边框,视觉上与其它元素不协调);
|
||||
// 面板高度已含这份 pad,因此这里只上移按钮行即可。
|
||||
layout.button = gd::RectF(left + pad, top + height - pad - buttonRowHeight,
|
||||
std::max(0.0f, width - pad * 2.0f), buttonRowHeight);
|
||||
}
|
||||
|
||||
// 逐信号条目:第一排 = 名称 + 复选框(复选框靠右);
|
||||
// 参数部分:MEAN / CENTER = 一行两个输入框(中心轴再多一行统计结果);
|
||||
// PEAK = 两行滑块(每个信号各有自己的等级与迭代次数)。
|
||||
const float checkSize = kEdgePanelMeanCheckSize * m_scale;
|
||||
const float fieldHeight = kEdgePanelMeanEditHeight * m_scale;
|
||||
const float halfWidth = std::max(40.0f, (width - pad * 2.0f - 8.0f * m_scale) * 0.5f);
|
||||
// 滑块行列宽(与 `EdgePanelWidth` 使用同一组算式,避免布局与宽度计算漂移)。
|
||||
const float weakPeakLabelWidth = WeakPeakLabelColumnWidth(m_fonts->uiSmall);
|
||||
const float weakPeakValueWidth = WeakPeakValueColumnWidth(m_fonts->monoSmall);
|
||||
const float weakPeakResetSize = kWeakPeakResetButtonSize * m_scale;
|
||||
const float weakPeakResetGap = kWeakPeakResetGap * m_scale;
|
||||
const float weakPeakTrackHeight = kWeakPeakTrackHeight * m_scale;
|
||||
const float weakPeakTrackLeft = left + pad + weakPeakLabelWidth + 6.0f * m_scale;
|
||||
const float weakPeakTrackWidth =
|
||||
std::max(10.0f * m_scale,
|
||||
(left + width - pad - weakPeakValueWidth - weakPeakResetGap -
|
||||
weakPeakResetSize - 6.0f * m_scale) -
|
||||
weakPeakTrackLeft);
|
||||
float rowY = top + titleHeight + pad;
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
layout.rowRefs.push_back(ref);
|
||||
layout.rowChecks.push_back(gd::RectF(
|
||||
left + width - pad - checkSize, rowY + (meanRowHeight - checkSize) * 0.5f, checkSize,
|
||||
checkSize));
|
||||
const bool checked = meanFilter ? lane.MeanFilterEnabled(ref)
|
||||
: (weakPeak ? lane.WeakPeakEnabled(ref)
|
||||
: lane.EdgeCenterEnabled(ref));
|
||||
if (weakPeak)
|
||||
{
|
||||
// 每个被勾选的信号各有两行滑块:调整幅度(-400% – +400%)与迭代次数(1 … 上限)。
|
||||
if (checked)
|
||||
{
|
||||
const float levelRowTop = rowY + meanRowHeight;
|
||||
const float iterRowTop = levelRowTop + weakPeakSliderRow;
|
||||
const gd::RectF levelTrack(
|
||||
weakPeakTrackLeft,
|
||||
levelRowTop + (weakPeakSliderRow - weakPeakTrackHeight) * 0.5f,
|
||||
weakPeakTrackWidth, weakPeakTrackHeight);
|
||||
const gd::RectF iterTrack(
|
||||
weakPeakTrackLeft,
|
||||
iterRowTop + (weakPeakSliderRow - weakPeakTrackHeight) * 0.5f,
|
||||
weakPeakTrackWidth, weakPeakTrackHeight);
|
||||
layout.rowLevelTracks.push_back(levelTrack);
|
||||
layout.rowLevelValues.push_back(gd::RectF(
|
||||
levelTrack.X + levelTrack.Width + 6.0f * m_scale, levelRowTop,
|
||||
weakPeakValueWidth, weakPeakSliderRow));
|
||||
layout.rowIterTracks.push_back(iterTrack);
|
||||
layout.rowIterValues.push_back(gd::RectF(
|
||||
iterTrack.X + iterTrack.Width + 6.0f * m_scale, iterRowTop,
|
||||
weakPeakValueWidth, weakPeakSliderRow));
|
||||
// 每行滑块右侧的**复位按钮**(与数值列同一右边界,保证两行上下对齐)。
|
||||
const float resetX = levelTrack.X + levelTrack.Width + 6.0f * m_scale +
|
||||
weakPeakValueWidth + weakPeakResetGap;
|
||||
layout.rowLevelResets.push_back(
|
||||
gd::RectF(resetX, levelRowTop + (weakPeakSliderRow - weakPeakResetSize) * 0.5f,
|
||||
weakPeakResetSize, weakPeakResetSize));
|
||||
layout.rowIterResets.push_back(
|
||||
gd::RectF(resetX, iterRowTop + (weakPeakSliderRow - weakPeakResetSize) * 0.5f,
|
||||
weakPeakResetSize, weakPeakResetSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
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.rowPoints.push_back(gd::RectF());
|
||||
layout.rowIterations.push_back(gd::RectF());
|
||||
layout.rowStats.push_back(gd::RectF());
|
||||
|
||||
rowY += meanRowHeight;
|
||||
if (checked)
|
||||
{
|
||||
rowY += weakPeakSliderRow * 2.0f;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (checked && itemHasParams)
|
||||
{
|
||||
const float editTop = rowY + meanRowHeight;
|
||||
layout.rowPoints.push_back(
|
||||
gd::RectF(left + pad, editTop, halfWidth, fieldHeight));
|
||||
layout.rowIterations.push_back(
|
||||
gd::RectF(left + pad + halfWidth + 8.0f * m_scale, editTop, halfWidth, fieldHeight));
|
||||
if (centerAxis)
|
||||
{
|
||||
layout.rowStats.push_back(
|
||||
gd::RectF(left + pad, editTop + fieldHeight, width - pad * 2.0f,
|
||||
kEdgePanelMeanRowHeight * m_scale));
|
||||
}
|
||||
else
|
||||
{
|
||||
layout.rowStats.push_back(gd::RectF());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
layout.rowPoints.push_back(gd::RectF());
|
||||
layout.rowIterations.push_back(gd::RectF());
|
||||
layout.rowStats.push_back(gd::RectF());
|
||||
}
|
||||
|
||||
rowY += meanRowHeight;
|
||||
if (checked && itemHasParams)
|
||||
{
|
||||
rowY += (kEdgePanelMeanEditHeight + kEdgePanelMeanRowGap) * m_scale;
|
||||
if (centerAxis)
|
||||
{
|
||||
rowY += kEdgePanelMeanRowHeight * m_scale;
|
||||
}
|
||||
}
|
||||
layout.scrollTrack =
|
||||
gd::RectF(left + pad + innerWidth + 4.0f * m_scale, layout.content.Y, scrollW,
|
||||
viewHeight);
|
||||
const float thumbHeight =
|
||||
std::max(18.0f * m_scale, viewHeight * (viewHeight / meanContentHeight));
|
||||
const float thumbTop =
|
||||
layout.content.Y +
|
||||
(viewHeight - thumbHeight) * ((maxScroll > 0.0f) ? (scroll / maxScroll) : 0.0f);
|
||||
layout.scrollThumb = gd::RectF(layout.scrollTrack.X, thumbTop, scrollW, thumbHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user