面板限高与滚动改造 + 边沿统计不再卡在“统计中…” + 组名折起真正收起

一、浮动面板(用户报告:下方空白、滚动时画到面板外、未改造的面板让一切交互卡顿)
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:
DeepSeek V4.1 Flash committed 2026-11-02 10:00:24 +08:00
1 parent 334015c1a9
commit 7a6fcd68e2
9 files changed
+914 -795

No files matched your search

+112 -211
View File
@@ -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);
}
}