自由标记:面板表头/吸顶/行选中与悬停联动 + 原位显示 + 探测容差与阶跃吸附

- 面板:每个展开分组下方新增一行列表头(标记值/枚举文本/增量/时间点/时间间隔),
  与数据行共用列边界;列宽同时容纳列标题;表头行计入内容总高与面板高度;
  全部无枚举文本时仍整列缺省
- 吸顶:分组栏与表头各自按 max(自然, min(吸顶, 组下边界-自身高)) 钉住,
  组末尾按"表头先走 -> 条目 -> 分组栏最后离场";绘制顺序改为条目->表头->吸顶分组栏,
  被盖住的条目不绘制也不可命中
- 行交互:Lane::freeMarkerSelected(单击选中/再点取消)+ 独立的整行悬停状态
  (整行悬停不会把该行 ✕ 变成 danger 色);悬停时网格内对应固定点整体高亮
  (数据点圆环 + 胶囊强制不透明 + accent 描边,仅视界内)
- 固定点恢复原位显示(删除纵向避让,允许遮挡,回到初版方案)
- 探测判据重写:鼠标到画出来的折线的垂直距离 <= kSignalHitTolerance(与曲线高亮同源),
  命中后吸附到最近采样时刻 => 修复阶跃竖直跳变线无法探测/固定的问题;
  删除已无用的 kFreeMarkerHoverTolerance
- 修复滚动时条目文字串到面板标题栏(GDI 文本不认 GDI+ 裁剪,改用 UiTextClipped)
- 顺带清理过期注释(四列->五列、折起指示器已无横线、探测判据说明),
  RemoveFreeMarker 改为复用 RemoveFreeMarkerAt(选中下标校正只有一处)
- 验证:新增离屏 .preflight/mark4check 38/38(三组 A/B 均如实失败);
  回归 markcheck 61/61、fixcheck 37/37、round3check 31/31、round4check 34/34、
  round5check 5/5、editcheck 20/20、convcheck 18/18;make release/debug 零警告
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-22 21:04:03 +08:00
1 parent 8f735d51be
commit 79eebc281e
10 files changed
+618 -116

No files matched your search

+115
View File
@@ -3017,6 +3017,8 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
| 2026-10-21 | DeepSeek-V4.1-Flash(**本机**) | **四处 BUG 修复 + 两处交互改进(详见 §12 末尾「2026-10-21 第三轮」与 §11.2 第 203–212 条)**。① **合并 → 拆分会把一个网格的实验性功能复制到每个拆分结果**(用户:第二个网格也自动启用了功能;「自由标记」还会在新网格里画出**别的信号的固定点**、而面板里查不到):`LaneCarry` 新增 `owner`(只有原网格**首个信号**继承功能状态),`SplitLane` 只让第一份继承;新增 `dmdetail::PruneLaneSignalItems` 在每次重建/拆分后按 `lane.signals` 过滤四类逐信号条目。② **阶跃信号的竖直沿比水平段粗**:`EmitEnvelopeBand` 的横向膨胀半径由 `ceil` 改 `floor`(1px 线宽不再膨胀 1 列)。③ **拖动浮动面板时下方另一个面板出现拖拽残影**:面板位置从 `PanelGlobalKey` 移除(曾让拖动任一面板都重画全部面板,而 MARK 面板的位置漏混 ⇒ 残影),并新增 `vacatedRects`(上一帧被移动/缩小/关闭面板腾出的矩形 → 与之相交的面板强制重画)。④ **大量固定点时拖全局光标/平移卡顿**:新增 `FreeMarkerContents` 内容缓存(取值 + 五列文本 + 信号色,键**不含视界**)。⑤ **面板外观/交互统一**:折起指示器改成与信号列表组名同款三角(去掉横线);GX 徽标与标题栏悬停**不改光标**、徽标气泡移除;标题栏**双击**滚动到该网格(单击不触发,折起只走三角)。⑥ **自由标记**:「标记透明度」滑块与光标模式解耦(始终显示);悬停胶囊恢复不透明(探针瞄着被遮住的数据点时除外);胶囊与面板五列**不再省略**(列宽按实测内容撑开)。验证:新增离屏 `.preflight/fixcheck` **37/37**(三处 A/B:恢复 `ceil` ⇒ 线宽断言失败;恢复 `owner`/`PruneLaneSignalItems` ⇒ 归属断言失败;禁用 `vacatedRects` ⇒ 残影 111px);回归 `round3check` 31/31(更新一条被用户推翻的旧断言)、`round4check` 34/34、`round5check` 5/5、`editcheck` 20/20、`convcheck` 18/18;`make release`/`make debug` 零警告;`TargetCheck.exe` **852,480 字节(832.5 KiB)**。 |
| 2026-10-22 | DeepSeek-V4.1-Flash(**本机**) | **「自由标记」面板表头 / 吸顶 / 行选中与悬停联动 + 固定点原位显示 + 探测容差与阶跃吸附(详见 §12 末尾「2026-10-22 第四轮」与 §11.2 第 213–220 条)**。① **列表头**:每个展开分组下方新增一行列标题(标记值 / 枚举文本 / 增量 / 时间点 / 时间间隔),与数据行共用列边界;列宽还需容纳列标题;内容总高与面板高度计入表头行;全部无枚举文本时整列缺省。② **吸顶**:分组栏与表头**各自**按 `y = max(自然, min(吸顶, 本组下边界 − 自身高度))` 钉住 ⇒ 组末尾按「表头先走 → 条目 → 分组栏最后离场」;绘制顺序改为「条目 → 表头 → 吸顶分组栏」,被盖住的条目不绘制也不可命中。③ **行交互**:`Lane::freeMarkerSelected`(单击选中/再点取消,`PanelSlotKey` 混入)+ 独立的整行悬停状态(整行悬停不会把 ✕ 变成 danger 色),悬停时网格内对应固定点整体高亮(数据点圆环 + 胶囊强制不透明 + accent 描边)。④ **固定点恢复原位显示**:删除 2026-10-21 的纵向避让,允许互相遮挡。⑤ **探测判据重写**:改为“鼠标到画出来的折线的垂直距离 ≤ `kSignalHitTolerance`”(与曲线高亮同源,复用 `AcquirePolyline` 顶点 + 点到线段距离),命中后吸附到最近采样时刻 ⇒ 修复 `SCATTER_TEST.csv` 阶跃竖直跳变线无法探测/固定的问题;删除 `kFreeMarkerHoverTolerance`。⑥ **滚动文字串到标题栏**:MARK 面板内全部文字改走 `UiTextClipped(..., content)`(GDI 文本不认 GDI+ 裁剪)。验证:新增离屏 `.preflight/mark4check` **38/38**(三组 A/B 均如实失败);回归 `markcheck` 61/61、`fixcheck` 37/37、`round3check` 31/31、`round4check` 34/34、`round5check` 5/5、`editcheck` 20/20、`convcheck` 18/18;`make release`/`make debug` 零警告;`TargetCheck.exe` **855,552 字节**。 |
## 11. 跨设备协作须知(起源设备视角)
> **本文件是唯一权威记忆**。所有设备 / Agent 共用它,**不要再新建 `agent_mem_origin.md` 等旁支记忆文件**。
@@ -3991,6 +3993,36 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
面板宽度随之撑开;`DT_END_ELLIPSIS` 对“刚好装满”的文本仍可能截断,因此每列额外留 2px 余量。
网格内取值胶囊同理(宽度额外留 12px 余量)。
213. ⚠️ **「自由标记」面板每个展开分组下必须有一行列表头**
(`MarkerGroup::tableHeader` + `kFreeMarkerHeaderRowHeight` + `kFreeMarkerColumnLabels`),
与数据行共用 `markerColumns` 的列边界 ⇒ 表头与数据严格对齐;列宽还要 ≥ **列标题宽度**。
表头行高度必须计入内容总高(`totalContent`)与面板高度(`EdgePanelWidth`)。
214. ⚠️ **枚举列缺省只由 `SignalEnumText::Empty()` 决定**(O(1),不逐条采样):
所有带固定点的信号都没有枚举表时,枚举列宽记 0、列标题也不画。
215. ⚠️⚠️ **吸顶是“每个元素各自”的**:`y = max(自然, min(吸顶, 本组下边界 − 自身高度))`。
分组栏吸到内容区顶、表头吸到分组栏下方 ⇒ 组末尾时**表头先被推走 → 条目 → 分组栏最后离场**
(用户 2026-10-22 明确要求的次序)。**不要**把它们当成一个整体块一起吸顶
(那样两者会同时离场,不符合要求)。
⚠️ 只有一个分组且内容超高时分组栏会一直钉住,这是数学上正确的(组内容还没滚完)。
216. ⚠️⚠️ **MARK 面板的绘制顺序必须是「条目 → 表头 → 吸顶分组栏」**:否则向上滚动的条目会盖住
吸顶元素。分组栏底衬必须**完全不透明**(先铺信号色、再叠 `WithAlpha(signalColor,0xE8)`),
表头底衬同样不透明;被吸顶区域盖住的条目由 `coveredByPinned` 同时从绘制与命中里剔除。
217. ⚠️⚠️ **行选中与「✕ 悬停」是两套状态**:`Lane::freeMarkerSelected`(选中行,`PanelSlotKey`
必须混入)+ `m_freeMarkerRowHoverLane/Item`(整行悬停)。整行悬停时**不能**让该行的 ✕
变成 danger 色(那是“准备删除”的语义),但要把网格内对应的固定点整体高亮
(数据点 accent 圆环 + 胶囊强制不透明 + 2px accent 描边)。删除固定点时必须校正选中下标。
218. ⚠️ **固定点取值胶囊**原位显示**、不做重叠避让**(用户 2026-10-22 明确要求恢复初版方案:
允许遮挡)。2026-10-21 的纵向避让(`placed/occupied`)已删除,不要再加回来。
219. ⚠️⚠️ **非光标模式的探测判据 = 鼠标到「画出来的折线」的垂直距离 ≤ `kSignalHitTolerance`**
(与常规触发曲线高亮**完全同一范围**)。实现复用 `AcquirePolyline` 顶点 + 窗口二分 +
点到线段距离(散点模式比顶点)。旧实现只看“同一横坐标的采样值之差” ⇒ 阶跃的**竖直段**
永远判不中(两端采样值是 ±1,都不是鼠标高度),用户既看不到预览也无法在跳变线上固定点。
⚠️ 命中后时刻要**吸附到最近采样时刻**(非光标模式恒吸附;光标模式仍只在稀疏判据成立时吸附),
这样固定点一定落在真实数据点上。`kFreeMarkerHoverTolerance(8)` 已删除。
220. ⚠️⚠️ **面板内的文字一律用 `UiTextClipped(..., layout.content)`**:GDI `DrawTextW`
**不认 GDI+ 裁剪**,滚动时“部分可见”的条目会把文字画到标题栏上(用户 2026-10-22 报告)。
这条同时适用于分组栏名称/计数、表头列标题与空内容提示。
### 11.3 离屏验证方法学(本机偏好,强烈建议沿用)
- **不要占用用户的鼠标 / 键盘 / 屏幕**:优先 `.preflight/uitest`(离屏 harness:`SW_HIDE` 窗口 + 直接注入消息 + `BitBlt` 从 `m_backDc` 截图)或 `.preflight/perf`(离屏计时 / 逐像素对比)。
@@ -7567,3 +7599,86 @@ DG3 data=0 cycle=0 (空组,一个字符串通道)
**缓存矩形**"里,比较区域要取两个缓存矩形的交集(面板主体不透明会盖住大半残影);直接改
`lane.*Panel.x/y` 就等价于"拖动中的某一帧"(走鼠标注入会先 `BringPanelToFront` 并改全局键,
那一帧所有面板都重画,反而看不到残影)。
---
## 本轮(2026-10-22 第四轮,本机):「自由标记」面板表头 / 吸顶 / 行选中与悬停联动 + 原位显示 + 探测容差与阶跃吸附
> 用户需求(逐条):
> ① 面板在每个分组栏下方增加一行**列表头**(标记值 / 增量 / 枚举文本 / 时间点 / 时间间隔),
> 数据行按列对齐,一行 = 一个固定标记点;滚动时分组栏与表头**吸顶**,到组末尾按
> 「表头 → 最后条目 → 分组栏」的次序依次滚出;所有固定点都没有枚举文本时**缺省枚举列**。
> ② 条目行可**选中**(高亮);行**悬停**时对应的固定点在网格内(若在视界内)要连数值显示与
> 数据点标记一起高亮。
> ③ 固定点取值胶囊恢复**原位显示**(允许遮挡,回到初版方案)。
> ④ 非光标模式的"鼠标接近标记"范围改成**与常规触发信号曲线高亮一致**。
> ⑤ `TestData/SCATTER_TEST.csv` 的阶跃信号:默认视图下**竖直跳变线上点不中、也没有吸附提示**。
> ⑥ 滚动时**文字串到面板标题栏**上。
### A. 面板:表头行(§11.2 第 213–214 条)
- `EdgePanelLayout::MarkerGroup` 新增 `tableHeader`(一行列标题)与 `pinned`(是否吸顶)。
- 新常量 `kFreeMarkerHeaderRowHeight = 18`、`kFreeMarkerColumnLabels[5]`
(**顺序与 `markerColumns` 一致**:标记值 / 枚举文本 / 增量 / 时间点 / 时间间隔)。
- 内容总高与 `EdgePanelWidth` 都计入表头行;列宽 = max(缺省列宽, 实测内容宽度, **列标题宽度**)
⇒ 表头与数据都不会被省略。枚举列仍按 `SignalEnumText::Empty()` 整列缺省。
- 交互侧不登记表头(它只用于阅读),点击表头落到"面板主体"(消费、不穿透)。
### B. 吸顶:每个元素各自钉住(§11.2 第 215–216 条)
- 公式:`y = max(自然位置, min(吸顶位置, 本组下边界 − 自身高度))`,分组栏与表头**各自**计算:
分组栏吸到内容区顶、表头吸到分组栏下方 ⇒ 组末尾时表头先被推走(被分组栏盖住),
最后一个条目随后滚到分组栏下,分组栏最后离场。探针实测:表头脱顶 502 设备像素 < 分组栏离场 550。
- 绘制顺序固定为 **条目 → 表头 → 吸顶分组栏**:否则向上滚的条目会盖住吸顶元素;
分组栏底衬改为**完全不透明**(先铺信号色再叠 0xE8,观感不变)。
- 被吸顶区域盖住的条目**既不绘制也不登记命中**(`coveredByPinned`),否则会隔着分组栏点到条目。
### C. 行选中 / 行悬停(§11.2 第 217 条)
- 数据层:`Lane::freeMarkerSelected`(-1 = 未选中),随 `LaneCarry` 与 `SplitLane` 携带;
`RemoveFreeMarker / RemoveFreeMarkerAt / RemoveFreeMarkersOf` 会校正下标(删到自己 = 取消,
删在它前面 = 下标前移,整组清空 = 若选中项属于其它信号则重新定位)。
- 交互:`PlotView::SelectLaneFreeMarker(lane, index)`;单击行的数据区域(✕ 之外)选中、
再点同一条取消。
- 新状态 `m_freeMarkerRowHoverLane/Item`(**与“✕ 悬停”分开**:整行悬停时不能让该行的 ✕
变成 danger 色——那是“准备删除”的语义)。悬停该行时网格内对应固定点:数据点加 accent 圆环、
胶囊强制不透明并加 2px accent 描边(只在视界内显示)。
- 面板里选中行用 `WithAlpha(accent,0x3C)` 整行底色、悬停行用 `0x1E`;
`PanelSlotKey` 混入 `freeMarkerSelected`(悬停靠“鼠标在面板内时混坐标”已有的机制)。
### D. 固定点恢复原位显示(§11.2 第 218 条)
- 删除 2026-10-21 的纵向避让(`placed/occupied/totalRect`):胶囊直接画在数据点处,允许互相遮挡
(用户明确要求回到初版方案)。
### E. 探测容差与阶跃竖直沿(§11.2 第 219 条)
- `ProbeFreeMarker` 改成**按“鼠标到画出来的折线”的垂直距离**判定:复用 `AcquirePolyline` 顶点 +
与 `HitTestSignal` 相同的窗口二分与点到线段距离(散点模式比较顶点),
容差 = **`kSignalHitTolerance`(6 逻辑像素)** —— 与常规触发曲线高亮**完全同一范围**。
- 命中后把时刻**吸附到最近采样时刻**(非光标模式恒吸附;光标模式仍只在稀疏判据成立时吸附),
保证固定点落在真实数据点上。旧的 `kFreeMarkerHoverTolerance = 8` 已删除。
- 根因:旧实现只看“同一横坐标的采样值之差”,而阶跃**竖直段**那一列上下两个采样值是 ±1,
都不等于鼠标高度 ⇒ 竖直跳变线上永远探测不到、也固定不了。
### F. 滚动时文字串到标题栏(§11.2 第 220 条)
- GDI `DrawTextW`(`UiText`)**不认 GDI+ 裁剪**:内容区上方“部分可见”的条目会把文字画进标题栏。
MARK 面板里所有文字(条目单元格、表头列标题、分组栏名称与计数、空提示)改走
`UiTextClipped(..., layout.content)`(同一套 `DrawTextW` 光栅化 + `SaveDC/IntersectClipRect`)。
### G. 验证(全离屏,未占用鼠标/键盘/屏幕)
- 新增 `.preflight/mark4check` **38/38**:
[A] 表头存在/位置/高度、列宽容纳列标题、枚举列缺省与恢复;[B] 吸顶位置、被压条目不可命中、
表头先脱顶且分组栏后离场;[C] 选中/取消、悬停记录与网格高亮(胶囊区域像素变化)、
离开清除;[D] 两个近同时刻固定点的胶囊 Y 相同(无避让);[E] 阶跃竖直沿探测 + 单击固定到
真实采样时刻 + 3/15 像素内外容差;[F] 逐档滚动比较标题栏区域像素(必须为 0)。
- **A/B(判别力已证)**:禁用吸顶 ⇒ 分组栏不再钉住;改回 `UiText` ⇒ 标题栏 196 像素差异;
跳过竖直段 ⇒ 竖直沿探测/固定 2 项失败。
- 回归:`markcheck` 61/61、`fixcheck` 37/37、`round3check` 31/31、`round4check` 34/34、
`round5check` 5/5、`editcheck` 20/20、`convcheck` 18/18;
`make release` / `make debug` **零警告**;`TargetCheck.exe` **855,552 字节(835.5 KiB)**。
- ⚠️ 探针坑:两条信号同网格时阶跃幅度更小(阈值取 40 逻辑像素);
吸顶“离场”必须在**两组**内容上验证(单组且内容超高时分组栏数学上会一直钉住);
滚动扫描步长按设备像素(`step*2`),再乘 `m_scale` 会扫不到组末尾。