滚动动画根治(巨型信号不再影响滚动)+「N 项」计数点击语义 + 浮动面板首现右上角 + 开关/关闭面板卡顿
一、滚动过渡动画在巨型信号处卡顿 —— 真因是「退化成整幅重绘」 滚动到**从未构建过图层的区域**时,可见网格一个都贴不出图层像素 ⇒ BlitWideBuffer() 返回 false ⇒ 落到 Paint() 末尾的整幅重绘 ⇒ 把本帧全部可见网格的波形(含千万级采样的巨型信号)每帧重画一遍。 实测(MF4 124 网格 + VERY_BIG_DATA 5 网格,点选最后一个信号):单帧 20~35ms,且「可见的巨型信号 网格数」越多越慢(0 个 ≈20ms、5 个 ≈34ms);对照手动拖滚动条只要 ~6ms。 修法:BlitWideBuffer 只要还能画网格底衬(backing 非空)就返回 true, 只有 blits 与 backing 都为空时才交给整幅重绘 —— 这正是「没来得及画信号时就按只有网格叠底的状态继续滚动」。 实测:动画均值 23.0→11.2ms、最坏 35.6→13.1ms;含 5 个巨型信号网格的帧 33→10.7~12.3ms。 二、面板动画帧只重绘面板(额外 −2.7ms/帧) 新增 Application::m_panelDirty:滚动定时器在只有面板动画在跑时置位,Render 走 else if (panelOnly) 只重绘面板 + 分隔条(SignalPanel::Paint 会铺满自己的区域)。m_chromeDirty(真内容变化)仍走完整装饰重绘。 ⚠️ 回送仍是 if (fullFrame) {整窗} else {绘图区} 的无条件分支(遵守 §9.41)。 实测帧耗时拆解:plot 4.8ms / plot+panel 11.4ms / plot+全装饰 13.9ms。 三、分组行「N 项」计数:只折叠展开,绝不进入改名 新增 RowPart::GROUP_COUNT 与 SignalPanel::GroupStatsRect(row)(绘制与命中的唯一几何来源,命中矩形在 文本左右各外扩 3 逻辑像素)。RowHitPart 在名称区域之前判定计数区: - OnLButtonDown:折叠/展开;OnDoubleClick:只折叠/展开,任何点击频率都不进入改名; - DoubleClickIsPlainPress 返回 true ⇒ 快速连点每一击都生效。 ⚠️「快速双击进改名」的计时只对 RowPart::BODY 记录(否则“点计数、紧接着点名称”会被误判成双击)。 四、浮动面板首次出现的位置:右上角 旧默认位置 top = plotTop + 16 + laneIndex*28:上百个网格时 laneIndex 很大 ⇒ 被夹到右下角 (实测 plotH=1081 时 topMargin=964)。改为按「当前真正显示出来的面板中排在本面板之前的个数」错开 (固定遍历顺序:网格下标 → 面板种类),并把错开量夹到“面板下沿不超过绘图区 45% 高度”以内: 第一个面板错开 0 ⇒ 正好贴在上右角(实测 topMargin=24、rightMargin=24),6 个面板时最坏 topMargin=234。 ⚠️ 判定「面板是否显示」不能只看 *Panel.open —— LanePanelState::open 默认就是 true,必须同时看功能启用。 五、开关实验性功能 / 关闭面板卡顿 —— RebuildWideBuffer() 整批重建 四个 SetLane* 末尾都调 RebuildWideBuffer()(注释说是“预建宽缓冲基底”),而它同步重渲染全部网格的图层: 129 个网格(含 5 个千万级采样的巨型信号)整批重建要几百毫秒 ⇒ 点一下功能开关 / 关闭浮动面板就会卡住。 修法:新增 PlotView::EnsureLaneLayersPrefetched() —— 只有「图层数组与网格数量不符」(首次绘制 / 增删网格) 才同步整批建立;其余只 RollWideBuffer() 登记缺失/过期网格,由既有预算(每帧最多一个)逐帧补齐。 实测:几百毫秒 → ≤22ms;巨型信号网格上开关均值滤波同样 15ms 量级。 验证(全部离屏,不占用鼠标/键盘/屏幕) - 新增 .preflight/perf/lanescroll_main.cpp(lanescroll.exe)23/23:逐帧耗时 + 「可见巨型信号网格数」关联、 「N 项」计数热区与双击不进改名、面板首现位置与多面板错开、开着巨型信号的开关/关闭面板耗时。 - 既有回归:verify 49/49、panelanim 158/158、datacheck 69/69、edgecheck 46/46、errcheck 19/19、 gridanim 93/93。 - make release / make debug 零错误零警告(用 TARGET_FILE_NAME=TargetCheck/TargetCheckD 避免与运行中实例冲突)。 文档:Documents/agent_mem.md 新增 §9.44、§11.2 第 134–140 条与变更日志一行。
This commit is contained in:
1 parent
436dcbfd52
commit
6b15575829
10 files changed
+352
-38
No files matched your search
@@ -438,6 +438,7 @@ namespace jdi
|
||||
CHECK, /**< 复选框。 */
|
||||
BODY, /**< 行主体(分组行的名称区域 / 信号行的名称区域)。 */
|
||||
GROUP_TITLE, /**< 分组行的「组X」标签(与折叠三角一样可点击折起)。 */
|
||||
GROUP_COUNT, /**< 分组行右侧的「N 项」计数(同样只折叠展开,**绝不**进入改名)。 */
|
||||
CLOSE, /**< 分组行的关闭按钮。 */
|
||||
LANE_BADGE, /**< 信号行的网格徽标。 */
|
||||
REMOVE_SIGNAL /**< 信号行左侧的「移除该信号」✕ 按钮。 */
|
||||
@@ -730,6 +731,17 @@ namespace jdi
|
||||
*/
|
||||
gd::RectF GroupCloseRect(int row) const;
|
||||
|
||||
/**
|
||||
* @brief 计算分组行右侧「N 项」计数文本的矩形(**绘制与命中的唯一几何来源**)。
|
||||
*
|
||||
* ⚠️ 计数区与分组名相邻,但它**只有折叠/展开语义**(用户 2026-10-17 要求):
|
||||
* 快速点击计数不得进入改名。命中判定必须用本矩形把 `RowPart::GROUP_COUNT`
|
||||
* 从名称区域(`RowPart::BODY`)中切出来,否则两者会混在一起。
|
||||
* @param [IN] row 行下标。
|
||||
* @return 计数文本矩形;该行不是分组行时返回空矩形。
|
||||
*/
|
||||
gd::RectF GroupStatsRect(int row) const;
|
||||
|
||||
/**
|
||||
* @brief 计算信号行网格徽标矩形。
|
||||
* @param [IN] row 行下标。
|
||||
|
||||
@@ -655,6 +655,12 @@ namespace jdi
|
||||
{
|
||||
return RowPart::GROUP_TITLE;
|
||||
}
|
||||
// 「N 项」计数:**只有折叠/展开语义**(用户 2026-10-17 要求快速点击计数不得进入改名)。
|
||||
// ⚠️ 必须在名称区域之前判定:计数就紧贴在名称右侧、两者同属一行。
|
||||
if (UiPointInRect(GroupStatsRect(row), fx, fy))
|
||||
{
|
||||
return RowPart::GROUP_COUNT;
|
||||
}
|
||||
return RowPart::BODY;
|
||||
}
|
||||
|
||||
@@ -1077,6 +1083,28 @@ namespace jdi
|
||||
return gd::RectF(left, rect.Y + 3.0f * m_scale, right - left, rect.Height - 6.0f * m_scale);
|
||||
}
|
||||
|
||||
gd::RectF SignalPanel::GroupStatsRect(int row) const
|
||||
{
|
||||
if (m_fonts == nullptr || m_fonts->uiSmall == nullptr || row < 0 ||
|
||||
row >= static_cast<int>(m_rows.size()) ||
|
||||
m_rows[static_cast<std::size_t>(row)].kind != RowKind::GROUP)
|
||||
{
|
||||
return gd::RectF();
|
||||
}
|
||||
const gd::RectF rect = RowRect(row);
|
||||
const float statsRight = GroupCloseRect(row).X - 6.0f * m_scale;
|
||||
const float statsWidth = UiMeasureTextWidth(m_fonts->uiSmall, GroupStatsText(row));
|
||||
if (!(statsWidth > 0.0f))
|
||||
{
|
||||
return gd::RectF();
|
||||
}
|
||||
// 命中矩形在文本左右各多给 3 逻辑像素(计数文字很短,多给一点更好点)。
|
||||
// 左侧不会吃掉名称区域:`RowNameRect` 的右边界已经让出了 `6*scale`。
|
||||
const gd::RectF textRect(statsRight - statsWidth, rect.Y, statsWidth, rect.Height);
|
||||
const float pad = 3.0f * m_scale;
|
||||
return gd::RectF(textRect.X - pad, textRect.Y, textRect.Width + pad * 2.0f, textRect.Height);
|
||||
}
|
||||
|
||||
float SignalPanel::GroupLabelWidth(int row) const
|
||||
{
|
||||
if (m_fonts == nullptr || m_fonts->uiBold == nullptr || row < 0 ||
|
||||
|
||||
@@ -1446,14 +1446,20 @@ namespace jdi
|
||||
m_pendingCloseRow = row;
|
||||
return true;
|
||||
}
|
||||
// 折叠 / 展开分组:三角、「组X」标签、**以及名称本体**都生效。
|
||||
// 折叠 / 展开分组:三角、「组X」标签、**名称本体**、以及右侧的「N 项」计数都生效。
|
||||
// 用户 2026-10-17 要求:名称本体恢复单击展开/收起(此前为了避开“误入改名”
|
||||
// 而禁掉了它,结果只能点三角、很不方便);误入改名改由**收紧双击阈值**解决
|
||||
//(见 `OnDoubleClick`:只有快速双击才改名)。
|
||||
//(见 `OnDoubleClick`:只有快速双击才改名);
|
||||
// 右侧的「N 项」计数**只折叠展开**,任何频率的点击都不进入改名(用户 2026-10-17 要求)。
|
||||
{
|
||||
m_groupNameClickMs = GetTickCount64();
|
||||
m_groupNameClickRow = row;
|
||||
m_groupNameClickWasCollapsed = IsGroupCollapsed(item.groupIndex);
|
||||
// 只有名称区域(BODY)参与“快速双击进改名”的计时;三角 /「组X」/ 计数都不参与,
|
||||
// 否则“点一次计数、紧接着点一次名称”会被误判成双击。
|
||||
if (part == RowPart::BODY)
|
||||
{
|
||||
m_groupNameClickMs = GetTickCount64();
|
||||
m_groupNameClickRow = row;
|
||||
m_groupNameClickWasCollapsed = IsGroupCollapsed(item.groupIndex);
|
||||
}
|
||||
}
|
||||
ToggleGroupFromRow(item.groupIndex, row);
|
||||
return true;
|
||||
@@ -1684,6 +1690,13 @@ namespace jdi
|
||||
// 超过 `kGroupRenameDoubleClickMs` 的“慢双击”只当**又一次单击**(再切一次折叠)。
|
||||
if (item.kind == RowKind::GROUP)
|
||||
{
|
||||
if (part == RowPart::GROUP_COUNT)
|
||||
{
|
||||
// 「N 项」计数:**只折叠展开**,绝不进入改名(用户 2026-10-17 要求)。
|
||||
// 按下那一 次已经切过折叠状态,这里再切一次 = 两次点击各自生效。
|
||||
ToggleGroupFromRow(item.groupIndex, row);
|
||||
return true;
|
||||
}
|
||||
if (part == RowPart::DISCLOSURE)
|
||||
{
|
||||
// 与单击分组行同一入口(悬浮时先滚动就位再播放折起动画)。
|
||||
|
||||
@@ -291,11 +291,13 @@ namespace jdi
|
||||
|
||||
// 分组标题:左侧为“组X”标签,右侧以绘制的竖线分隔分组名(不使用字符),再显示可重命名的名称。
|
||||
const std::wstring groupLabel = GroupLabelText(item.groupIndex);
|
||||
// 项数文本与 `RowNameRect`(组名编辑框)共用同一来源,保证名称宽度与绘制不漂移。
|
||||
// 项数文本与 `RowNameRect`(组名编辑框)、`GroupStatsRect`(命中区)共用同一来源,
|
||||
// 保证“画的”与“点的”不漂移。
|
||||
const std::wstring stats = GroupStatsText(row);
|
||||
const float groupWidth = GroupLabelWidth(row);
|
||||
const float statsWidth = UiMeasureTextWidth(m_fonts->uiSmall, stats);
|
||||
const float statsRight = closeRect.X - 6.0f * m_scale;
|
||||
const float statsRight = GroupCloseRect(row).X - 6.0f * m_scale;
|
||||
const gd::RectF statsTextRect(statsRight - statsWidth, rect.Y, statsWidth, rect.Height);
|
||||
const float nameLeft = checkRect.X + checkRect.Width + 8.0f * m_scale;
|
||||
const float nameAreaLeft =
|
||||
nameLeft + groupWidth + kGroupSeparatorGap * 2.0f * m_scale + kGroupSeparatorWidth * m_scale;
|
||||
@@ -323,8 +325,8 @@ namespace jdi
|
||||
TextAlign::LEFT);
|
||||
}
|
||||
|
||||
PaintRowText(g, stats, m_fonts->uiSmall, gd::RectF(statsRight - statsWidth, rect.Y, statsWidth, rect.Height),
|
||||
m_theme->textMuted, TextAlign::RIGHT);
|
||||
PaintRowText(g, stats, m_fonts->uiSmall, statsTextRect, m_theme->textMuted,
|
||||
TextAlign::RIGHT);
|
||||
|
||||
// 关闭按钮(从工作区移除该分组)。
|
||||
const bool closeHot = hovered && (m_hoverPart == RowPart::CLOSE);
|
||||
|
||||
Reference in new issue
Block a user