修复组名编辑态点击穿透;浮动面板 GX 徽标悬停提示 + 点击滚动联动;最小网格高度提升到 81 逻辑像素

1) 组名/信号名处于编辑态时,在编辑框内双击、快速连点或框选编辑文本会让分组展开/收起。
   两处根因:
   - 行高大于输入框高度(上下各留 3 逻辑像素),起点落在内边距上时 OnEditMouseDown 返回
     false,宿主把按下交给 OnLButtonDown,命中分组行的 BODY 分支 ⇒ 折叠状态被切换;
   - 窗口带 CS_DBLCLKS,快速第二次按下被合成为 WM_LBUTTONDBLCLK,而 OnDoubleClick 没有
     编辑态保护,直接进入分组行双击逻辑 ⇒ 再次折叠 / 再次改名。
   修法:新增 NameEditAbsorbRect(输入框横向范围 × 整行高度)作为编辑态的“文本吸收区”:
   OnLButtonDown 命中即按文本操作处理(落点夹回输入框再算插入符),OnDoubleClick 命中即
   直接返回。横向仍限定在名称区域,同一行的复选框 / 「N 项」/ GX 徽标 / ✕ 继续可点。

2) 所有实验性功能浮动面板标题栏左侧的 GX 徽标:
   - EdgePanelLayout 新增 badge 矩形,成为绘制 / 悬停 / 手型光标 / 点击联动的唯一几何来源
     (命中顺序 close → badge → title,徽标在标题栏内部,放到 title 之后会被判成拖面板);
   - 悬停时徽标高亮 + 气泡提示(“G12 · 点击滚动到该网格” / “G12 · 已在视界内”),气泡在
     面板像素图层存进缓存之后绘制,避免被烘进位图后在鼠标移开后残留;
   - 点击调用 ScrollLaneIntoView:目标网格不可见时启动与“点选信号联动滚动”同源的平滑动画,
     已完整可见时不做任何改动。

3) 网格被压缩时的最小高度由 54 提升到 81 逻辑像素(1.5 倍)。同时修掉 m_minLaneHeight 被
   二次乘 m_scale 的隐患:Layout 里不再预先乘缩放,滚动条判定改用 laneCount × 81 × scale,
   与实际网格高度使用同一换算(125% 缩放下 128 个网格实测 101.25px = 81×1.25)。

验证(全部离屏:SW_HIDE 窗口 + 只注入合成消息 + BELOW_NORMAL,不占用鼠标键盘屏幕):
- 新增 .preflight/round3check(真实 TestData/MF4-Record.MF4,128 网格)31/31;
  A/B 临时禁用双击保护 ⇒ 1 项如实失败(证明断言有判别力);
- 回归:.preflight/editcheck 20/20、.preflight/check 137/137;
- make release / make debug 零警告。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-19 14:01:19 +08:00
1 parent 1e50494c41
commit be76f523f3
8 files changed
+307 -11

No files matched your search

+74
View File
@@ -2997,6 +2997,8 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
| 2026-10-19 | DeepSeek-V4.1-Flash(**本机**) | **修复列表内联编辑(组名/信号名)看不到插入符与选区 + `Utilities` 的 UI 基础层迁入 `Components/Shared`(详见 §12 末尾「2026-10-19 第二轮」)**。① **根因**:信号列表整行走**行位图缓存**(`SignalPanel::PaintRowCached`),而 `RowCacheKey` 只混合“是否编辑 + 编辑文本”,**没有插入符与选区** ⇒ 左右方向键、鼠标框选虽然改变了 `TextEdit` 状态,键却不变 ⇒ 贴回旧位图 ⇒ 用户看到“无法部分选择、光标不动”(绘图区光标时间输入没有行缓存,所以一直正常)。修法:编辑态把 `Caret/SelectionBegin/SelectionEnd` 混进键(分组行与信号行两处),并新增 `SignalPanel::EditFieldFont()`(分组名用 `uiSmall`、其余 `ui`)统一**绘制 / 命中 / 插入符换算**三处字体。② **目录归类**:`Theme.*` / `PaintUtil.*` / `TextEdit.*` / `PopupMenu.*` / `ColorPickerPopup.*` 由 `Utilities/{Inc,Src}` **`git mv`** 到 `Components/Shared/{Inc,Src}`(`Utilities` 只剩纯数据/逻辑;依赖方向仍 `Components → Shared → Utilities`);Makefile 无需改动,但要清掉 `Build/*` 里对应的旧 `.o/.d`。验证:新增离屏 `.preflight/editcheck`(`SW_HIDE` + 合成消息 + `BELOW_NORMAL`)**20/20**(A/B:修复前 6 项如实失败:键不变、屏幕不重绘);`make release`/`make debug` 零警告。 |
| 2026-10-19 | DeepSeek-V4.1-Flash(**本机**) | **组名/信号名编辑态点击穿透 + 浮动面板 GX 徽标悬停/点击联动 + 最小网格高度 1.5 倍(详见 §12 末尾「2026-10-19 第三轮」与 §11.2 第 151–153 条)**。① **点击穿透根因**:改名时行高大于输入框高度(上下各 3 逻辑像素),起点落在内边距上会继续走 `OnLButtonDown` 的行分支 ⇒ 分组展开/收起;且第二次快速按下被合成为 `WM_LBUTTONDBLCLK`,旧 `OnDoubleClick` 无编辑态保护 ⇒ 同样折叠/再次改名。修法:`NameEditAbsorbRect`(输入框横向范围 × 整行高度)在 `OnLButtonDown`/`OnEditMouseDown` 一律吸收为文本操作,`OnDoubleClick` 编辑态命中即返回。② **GX 徽标**:`EdgePanelLayout::badge` 成为唯一几何来源;悬停高亮 + 提示气泡(在面板缓存之后绘制,避免被烘进位图)+ 手型光标;点击 `ScrollLaneIntoView` 平滑滚动到该网格(已可见则不动)。③ **最小网格高度 54 → 81 逻辑像素**,并修掉 `m_minLaneHeight` 被二次乘 `m_scale` 的隐患(滚动条判定与实际高度改用同一换算)。验证:新增离屏 `.preflight/round3check` **31/31**(真实 MF4:128 网格、`height=101.25=81×1.25`);**A/B 禁用双击保护 ⇒ 1 项如实失败**;回归 `.preflight/editcheck` 20/20、`.preflight/check` 137/137;`make release`/`make debug` 零警告。 |
## 11. 跨设备协作须知(起源设备视角)
> **本文件是唯一权威记忆**。所有设备 / Agent 共用它,**不要再新建 `agent_mem_origin.md` 等旁支记忆文件**。
@@ -3719,6 +3721,24 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
(本轮 11ms → 26ms,普通整帧 8ms → 21ms)⇒ 断言必须用**同一时段测得的相对基准**
(普通整帧耗时 / 手动拖滚动条耗时)。⚠️ 另外 **printf 的参数必须与格式串数量一致**,
否则读到栈上垃圾值(本轮把 `130.49` 当成真实耗时,误导排查)。
151. ⚠️⚠️ **内联改名期间,「名称区域整行」都必须吸收鼠标**(用户 2026-10-19 报告):
行高大于输入框高度(上下各留 3 逻辑像素),起点落在内边距上时旧实现会继续走行点击分支
⇒ **分组被展开/收起**。✓ `SignalPanel::NameEditAbsorbRect(field)` = 输入框横向范围 × 整行高度;
`OnLButtonDown` / `OnEditMouseDown` 命中该区域一律按“文本按下”处理(把落点夹回输入框再算插入符),
`OnDoubleClick` 在编辑态命中该区域**直接返回 false**(不折叠、不再次改名)。
⚠️ 横向仍限定在名称区域:同一行的复选框 / 「N 项」/ GX 徽标 / ✕ 继续可点。
⚠️ 推论:任何“行内可编辑控件”的命中区都必须**覆盖到它所在行的高度**,否则会被行级点击抢走。
152. ⚠️ **浮动面板标题栏的「GX」徽标是独立热区**(用户 2026-10-19 要求):
`EdgePanelLayout::badge` 是**绘制 / 悬停提示 / 手型光标 / 点击联动**的唯一几何来源;
命中判定必须排在 `layout.title` **之前**(徽标位于标题栏矩形内部,否则会被判成“拖动面板”)。
点击调用 `ScrollLaneIntoView(laneIndex)`(自带“已完整可见 ⇒ 不动作” + 平滑动画)。
⚠️ 悬停提示气泡必须在**面板像素图层全部存进缓存之后**绘制(见 `PaintEdgeCursor` 末尾),
否则气泡会被烘进面板位图、鼠标移开后仍残留。
153. ⚠️⚠️ **`m_minLaneHeight` 只存逻辑像素,使用处才乘 `m_scale`**:旧实现 `Layout()`
写成 `54.0f * m_scale` 而 `BuildLaneVisuals()` 又乘一次 ⇒ 125% 缩放下实际最小高度是
`54 × 1.5625`,且与「是否需要滚动条」的判定(只乘一次)不一致 ⇒ 可能出现“网格溢出却没有滚动条”。
当前值 = **81 逻辑像素**(用户 2026-10-19 要求:原 54 的 1.5 倍,保证最小网格下也看得清跳变)。
新增任何“按最小网格高度推导”的逻辑(滚动条判定、`FitLaneHeight`、内容高度)都必须用同一个换算。
### 11.3 离屏验证方法学(本机偏好,强烈建议沿用)
@@ -6415,3 +6435,57 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
`CXX_INCLUDES_PATHS/CXX_SOURCES_PATHS`)。⚠️ 必须删掉 `Build/Release|Debug` 里对应的
旧 `*.o` / `*.d`(`.d` 里记着旧路径,会让 make 误判/漏依赖)。
- 文档里 §11.2 第 88 条已按本表更新(历史章节里出现的 `Utilities/PaintUtil.*` 等仅作历史记录)。
---
## 本轮(2026-10-19 第三轮,本机):组名编辑穿透 + 浮动面板 GX 徽标 + 最小网格高度
> 用户三项需求:① 组名处于编辑态时,在编辑框内双击 / 快速单击 / 框选会让**分组展开或收起**;
> ② 所有实验性功能浮动面板左上角的 `GX` 标签**没有悬停提示**,且点击它应把目标网格滚到视界内;
> ③ 加载 MF4 全部信号后网格太窄,最小高度提到现在的 **1.5 倍**。
> 验证:新增离屏 `.preflight/round3check` **31/31**(真实 `TestData/MF4-Record.MF4`:128 网格);
> A/B 禁用双击保护 ⇒ 1 项如实失败;回归 `.preflight/editcheck` 20/20、`.preflight/check` 137/137;
> `make release` / `make debug` 零警告。
### A. ① 的根因是**两处**“行点击抢走了文本操作”
1. **行内边距**:行高 > 输入框高度(`RowNameRect` 上下各留 3 逻辑像素)。鼠标起点落在这条内边距上时
`OnEditMouseDown` 判“不在输入框内”而返回 false ⇒ 宿主把这次按下交给 `SignalPanel::OnLButtonDown`
⇒ 命中分组行的 `BODY` ⇒ **展开/收起**(框选时之所以会折叠,就是起点偏出输入框 1~3px)。
2. **快速单击 / 双击**:窗口带 `CS_DBLCLKS`,编辑态下快速再点一次会被合成为 `WM_LBUTTONDBLCLK`。
`DoubleClickIsPlainPress` 对输入框返回 false(本意是保留“选词”语义),于是走到
`SignalPanel::OnDoubleClick` —— 而它**没有编辑态保护**,直接进入分组行双击逻辑
⇒ 快速双击进改名 / 慢双击**再切一次折叠**。
**修法**(不是给这两条路径各打一个补丁,而是给“名称编辑”划出一个明确的吸收区):
- 新增 `SignalPanel::NameEditAbsorbRect(field)` = **输入框横向范围 × 整行高度**(与 `RowRect` 同源)。
- `OnLButtonDown`:编辑态且命中吸收区 ⇒ 直接 `OnEditMouseDown(x, y)` 并 `return true`(**绝不触发行点击**)。
- `OnEditMouseDown`:命中吸收区但不在输入框内时,把落点**夹回输入框**再算插入符/开始拖拽
(保证与绘制使用同一套几何,插入符落点不偏移)。
- `OnDoubleClick`:编辑态且命中吸收区(输入框或整行)⇒ **直接返回 false**(不折叠、不再次改名)。
- ⚠️ 横向仍严格限定在名称区域:同一行的复选框 / 「N 项」/ GX 徽标 / ✕ **继续可点**(不然编辑时无法关分组)。
### B. ② 浮动面板的「GX」徽标
- `EdgePanelLayout` 新增 **`badge`** 矩形,由 `EdgePanelLayoutFor` 统一算出 ⇒
**绘制 / 悬停提示 / 手型光标 / 点击联动**共用同一个几何(此前徽标矩形只在 `Paint` 里就地算,无法命中)。
- 悬停:`PlotView::m_hoverPanelBadgeLane/Kind` 单独记录(面板像素在图层里,必须靠状态触发重绘);
徽标本身画强调色描边 + `accentSoft` 填底;`QueryCursor` 返回 `IDC_HAND`。
- 提示气泡:`PaintPanelBadgeTooltip`,锚定徽标下方(放不下则翻到上方),文案
`G12 · 点击滚动到该网格` / `G12 · 已在视界内`。
⚠️ **必须在全部面板像素存进缓存之后绘制**(`PaintEdgeCursor` 末尾),否则气泡会被烘进面板位图。
- 点击:调用 `PlotView::ScrollLaneIntoView(laneIndex)` —— 已完整可见时它自身返回 false(不动),
否则启动与“点选信号联动滚动”同源的 ease-out 平滑动画;并给一句 `正在滚动到 G12 网格…` 提示。
- ⚠️ 命中顺序:`close` → **`badge`** → `title`。徽标在标题栏矩形内部,放在 `title` 之后就会被当成“拖面板”。
- `PanelSlotKey` 也 mix 了 `badge`(几何参与像素内容)。
### C. ③ 最小网格高度 54 → 81 逻辑像素,并修掉二次缩放
- `m_minLaneHeight` 的语义是**逻辑像素**,使用处才乘 `m_scale`。旧实现 `Layout()` 里写成
`54.0f * m_scale`,而 `BuildLaneVisuals()` / `FitLaneHeight()` 又各乘一次 ⇒ 125% 缩放下
实际最小值是 `54 × 1.5625 ≈ 84.4px`(而非 54×1.25),且与“是否需要滚动条”的判定
(只乘一次)不一致 ⇒ **可能出现“网格已经溢出、却没有滚动条”**。
- 现为 `m_minLaneHeight = 81.0f`(用户要求 1.5 倍),`Layout()` 不再预先乘缩放,
滚动条判定改 `laneCount * m_minLaneHeight * m_scale`;其余两处保持 `* m_scale`。
- 实测(本机 125%、窗 1400×900 逻辑、MF4 全部 128 信号各自一格):
`lane height = 101.25px = 81 × 1.25`,`content = 12960px`,滚动条出现且可滚动。