面板动画帧不再重绘绘图区(组名展开/收起卡顿根治)+ 均值面板两处修正 + 信号行集合自愈

1. 组名展开/收起卡顿的真因(用户报告:288 个信号同一网格、双光标/实验性功能开启时,
   点组名展开收起的动画非常卡顿,而网格里没有任何需要重绘的变化)
   - 滚动定时器给面板动画帧做的是整窗失效,WM_PAINT 里"更新区域不在绘图区内 ⇒
     m_chromeDirty = true"这条启发式把整帧判成脏 ⇒ Render 走 fullChrome 分支 ⇒
     标签栏/工具栏/绘图区全部重绘。绘图区走场景缓存很快,但**光标叠加层每帧都要重画**:
     288 个信号的取值胶囊(圆角 + GDI 文本 + 重叠避让)实测 24~46ms/帧。
   - 新增 Application::m_panelAnimationFrame:滚动定时器在只有面板动画时置位,
     WM_PAINT 命中它且没有任何其它消息要求装饰区重绘时不再把整帧判脏 ⇒ 走 panelOnly。
   - 新增 Application::m_plotPaintValid:panelOnly 帧且该标志为真时跳过 PlotView::Paint
     (面板不覆盖绘图区,上一帧像素原样留在后备缓冲区,整窗回送也不会错)。
     作废点:InvalidatePlotRect(签名多收一个 bool&,5 个调用点统一传入)、Relayout、
     EnsureBackBuffer、WM_PAINT 中"更新区域就在绘图区内"、以及"面板动画帧里绘图区的
     滚动/回到最上方动画同时也在跑"(否则会把绘图区动画冻住)。
   - panelOnly 分支画分隔条时把右边界夹到 floor(m_plotRect.X):分隔条是浮点矩形
     (457.5…462.5),GDI+ 会把最后一列按 50% 混进去;整帧重绘时绘图区随后会盖掉它,
     但跳过绘图区的帧会留下**一整列 1px 接缝**(实测 847 像素)。
   - 实测(283 信号同网格 + 双光标):动画帧 34~43ms → 1.6~2.1ms。

2. 均值滤波面板
   - **未勾选的条目栏点击完全无响应**:未勾选时点名称区域不再自动勾选、也不展开/收起,
     只能点右端复选框(点击仍被消费,不穿透)。
   - **滚动时参数滑块不再消失**:原实现按条目栏矩形判断整行可见性,条目栏滚出内容区后
     整行(含两行参数滑块)被跳过。现在按整行纵向范围(条目栏 ∪ 两行轨道/数值/复位)判定。

3. 信号列表:行集合与折叠状态自愈(用户报告:开自由标记并标记点后,点 MF4 组名,
   三角显示已展开但下属 283 条信号行一条都不出现)
   - 新增 SignalPanel::RowsSignature()(FNV 混合 分组集合/每组信号数/是否折起/是否有动画),
     BuildRows 记录它,EnsureRows 每帧 O(分组数) 校验,不一致就地重建。
     这样任何"改了折叠状态却忘记置 m_rowsDirty"的路径都不会再让行集合与折叠状态脱节。

4. 验证(全部离屏,不占鼠标/键盘/屏幕)
   uicheck 69 / lbcheck 21 / lbresid 5 / lbzorder 4 / lbcovr 7 / lbpanel 10 /
   derivecheck 16 / round15check 28 / themecheck 17 / meancheck 21 全绿;
   新增 .preflight/panelbug 14/14(外部改折叠状态→行集合自愈、动画中途改状态→位移清零、
   未勾选行点击无响应、条目栏滚出后滑块仍绘制);
   新增 .preflight/panelperf 12/12(动画帧 <16ms、且跳过绘图区后该区域像素与紧接的
   整幅重绘逐像素完全相同);
   rendercheck A–H 8 个场景哈希与基线一致;make release / make debug 零 warning
   (805,888 B)。

同步文档:Documents/agent_mem.md 追加「2026-10-30 第十八轮」,
并把第十七轮里"尚未启用 → 直接开始预览"改标注为已推翻。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-30 15:06:01 +08:00
1 parent 2d83b494a3
commit 8271879ede
8 files changed
+268 -27

No files matched your search

+89 -2
View File
@@ -8802,7 +8802,9 @@ make debug -> 零 error 零 warning
### 3. 条目栏交互:整行点击 + 悬停高亮预览曲线
* **整行可点**(不再只能点 12px 的三角):点击落在右端复选框上 → 切换"是否预览";
已启用 → 切换参数行的收起/展开;尚未启用 → 直接开始预览。
已启用 → 切换参数行的收起/展开;
**尚未启用 → 什么都不做**(⚠️ 2026-10-30 第十八轮推翻:原实现会"直接开始预览",
用户明确要求未勾选前点击条目栏不得自动勾选/展开收起,只能点复选框)。
* **悬停行 → 对应预览曲线高亮**:`m_meanRowHoverLane / m_meanRowHoverRef` 由 `OnMouseMove` 维护,
`OnMouseLeave` / `InvalidateDerivedCaches` 清除;绘制侧
`PaintDerivedCurve(..., bool highlight)` 先画 `+5px` 半透明同色光晕、再把线加粗 `+1.6px`,
@@ -8851,6 +8853,91 @@ make release -> 零 warning(805,376 B);make debug -> 零 warning
`const gd::RectF& header` 重复声明导致 `make` 失败而被过滤掉,白查了半小时)。
* ⚠️ 探针里断言"某行已启用"前要确认条目真的存在:`SetLaneMeanFilter(lane,true)` 会**自动启用
第一个信号**,再"全量切换"会把它关掉 ⇒ 出现 39/40 这种偏差,断言就会落在"未启用的行"上,
而“未启用的行单击 = 开始预览”本来就不切换 `collapsed`。
而“未启用的行单击”自**第十八轮**起是**完全无响应**(早期是“开始预览”),断言必须挑**已启用**的行。
* ⚠️ 异步派生曲线(大信号走后台线程)让"改参数 → 立刻截图比对"变得不稳定:
每个状态**连渲染两帧**再取像素,才是稳定态。
---
## 2026-10-30 第十八轮(本机):面板动画帧不再重绘绘图区(卡顿根治)+ 均值面板两处显示/交互 + 行集合自愈
### 1. 组名展开/收起卡顿的**真因**:面板动画帧每帧都整幅重绘绘图区
用户复现:`MF4-Record.MF4` + `VERY_BIG_DATA.CSV` 全部信号合并到一个网格,
开「弱峰调整 / 中心轴边沿计数」或**只开双光标**,点信号列表组名 → 展开/收起动画非常卡顿,
而网格里没有任何需要重绘的变化。
**测量(`.preflight/panelperf`,283 信号同网格)**:
| 场景 | 单帧耗时 |
| --- | --- |
| `PlotView::Paint`(双光标) | **31~46 ms** |
| `PlotView::Paint`(无光标) | 7~13 ms |
| 组名动画帧(修复前) | **34~43 ms/帧** |
| 组名动画帧(修复后) | **1.6~2.1 ms/帧** |
**根因链**:滚动定时器给面板动画帧做的是**整窗失效**(`InvalidateRect(hwnd,nullptr)`)⇒
`WM_PAINT` 里"更新区域不在绘图区内 ⇒ `m_chromeDirty = true`"这条启发式把整帧判成脏 ⇒
`Render` 走 `fullChrome` 分支 ⇒ **标签栏/工具栏/绘图区全部重绘**。
绘图区本身走场景缓存(~1ms),但**光标叠加层每帧都要重画**:288 个信号 ×
(圆角胶囊 + GDI 文本 + 重叠避让)≈ 24~40ms。于是每一帧都掉到 25fps 以下。
**修法(三处,缺一不可)**:
1. 新增 `Application::m_panelAnimationFrame`:滚动定时器在"只有面板动画在跑"时置位;
`WM_PAINT` 命中它**且**没有任何其它消息要求装饰区重绘(`m_chromeDirty` 仍为假)时,
**不再**把整帧判脏 ⇒ 走 `panelOnly` 分支(只画面板 + 分隔条)。
2. 新增 `Application::m_plotPaintValid`:`panelOnly` 帧且该标志为真时**跳过 `PlotView::Paint`**
—— 面板不覆盖绘图区,上一帧的像素原样留在后备缓冲区里,整窗回送也不会错。
作废点:`InvalidatePlotRect()`(签名改为多收一个 `bool&`,5 个调用点统一传
`m_plotPaintValid`)、`Relayout()`、`EnsureBackBuffer()`、`WM_PAINT` 里"更新区域就在绘图区内"、
以及"面板动画帧里绘图区动画(网格滚动 / 回到最上方)同时也在跑"。
3. `panelOnly` 分支画分隔条时**把右边界夹到 `floor(m_plotRect.X)`**:
分隔条矩形是浮点的(457.5…462.5),GDI+ 会把最后一列按 50% 混进去;整帧重绘时绘图区随后
会盖掉它,但跳过绘图区的帧会留下**一整列 1px 接缝**(实测 847 像素)。
探针断言"动画最后一帧的绘图区与紧接的整幅重绘**逐像素相同**"就是这个坑逼出来的。
### 2. 均值面板:两处显示/交互修正
* **未勾选条目栏不再响应点击**(用户 2026-10-30 要求):未勾选时点击名称区域
**既不会自动勾选、也不会展开/收起**,只能点右端复选框(点击仍被消费,不穿透)。
已勾选时整行点击仍是展开/收起。
* **滚动时滑块不再消失**:原实现按**条目栏矩形**判断整行是否可见,条目栏滚出内容区后
整行(含下面两行参数滑块)被 `continue` 跳过 ⇒ 滑块凭空消失。
现在按**整行纵向范围**(条目栏 ∪ 两行轨道/数值/复位)判断,只要还有任何一部分在内容区内就照常绘制
(裁剪仍由 `g.SetClip(layout.content)` 负责)。
### 3. 信号列表:行集合与折叠状态**自愈**
用户报告:开自由标记并标记了几个点后,点 `MF4-Record.MF4` 组名,三角已显示"展开",
但下属 283 条信号行一条都不出现。逐一排查后确认这只能是
**"折叠状态改了、行集合没重建"**(`m_rows` 里根本没有那些行)——
直接切 `TabConfig` 折叠状态的探针能 100% 复现该表现。
**修法(自愈不变量)**:新增 `SignalPanel::RowsSignature()`(FNV 混合
"分组集合 / 每组信号数 / 是否折起 / 是否有折起动画"),`BuildRows()` 记录它,
`EnsureRows()` 每帧花 O(分组数) 校验一次,**不一致就地重建**。
这样任何"忘记置 `m_rowsDirty`"的路径(数据层变化、自由标记、外部调用……)
都不会再让行集合与折叠状态脱节。
### 4. 验证
```
uicheck 69 / lbcheck 21 / lbresid 5 / lbzorder 4 / lbcovr 7 / lbpanel 10
derivecheck 16 / round15check 28 / themecheck 17 / meancheck 21
.preflight/panelbug 14/14(含"外部改折叠状态 → 行集合自愈"、"动画中途改状态 → 位移清零")
.preflight/panelperf 12/12(动画帧 <16ms + 跳过绘图区后像素与整幅重绘**完全一致**)
rendercheck 8 个场景哈希与基线完全一致;make release / make debug 零 warning(805,888 B)
```
### 5. 踩坑(本轮新增)
* ⚠️ **"只改一小块像素"的路径必须自己保证不越界**:`panelOnly` 帧只画面板+分隔条,
任何一点点溢出(浮点矩形的抗锯齿边缘)都会在绘图区留下**永久的 1px 接缝**。
判据要写成"动画最后一帧 vs 紧接的整幅重绘**逐像素相同**",而不是"看起来没问题"。
* ⚠️ 与"跳过绘图区"配套时,**必须**同时检查"绘图区自己的动画是否在跑"
(网格滚动到可视区 /「回到最上方」淡入淡出),否则会把它冻住。
* ⚠️ 探针断言里先打印真实状态(`m_rows.size()` / `m_rowShift` / 折叠标志 / `m_collapsed.size()`)
再写断言:本轮因为前面场景留下的折叠状态,导致"collapsedRows / expandedRows"语义正好反了,
白改了一轮断言。
* ⚠️ PowerShell 里用 `'...'` 拼接含 `%d` / 引号 / 长中文的补丁串极易被截断(出现 `>>` 续行提示后
只能 Ctrl+C)。**补丁一律写成 `.git\xxx.ps1`(纯 ASCII)+ `%TEMP%\xxx.txt` 数据文件**。