diff --git a/Utilities/Inc/ColorPickerPopup.hpp b/Components/Shared/Inc/ColorPickerPopup.hpp similarity index 100% rename from Utilities/Inc/ColorPickerPopup.hpp rename to Components/Shared/Inc/ColorPickerPopup.hpp diff --git a/Utilities/Inc/PaintUtil.hpp b/Components/Shared/Inc/PaintUtil.hpp similarity index 100% rename from Utilities/Inc/PaintUtil.hpp rename to Components/Shared/Inc/PaintUtil.hpp diff --git a/Utilities/Inc/PopupMenu.hpp b/Components/Shared/Inc/PopupMenu.hpp similarity index 100% rename from Utilities/Inc/PopupMenu.hpp rename to Components/Shared/Inc/PopupMenu.hpp diff --git a/Utilities/Inc/TextEdit.hpp b/Components/Shared/Inc/TextEdit.hpp similarity index 100% rename from Utilities/Inc/TextEdit.hpp rename to Components/Shared/Inc/TextEdit.hpp diff --git a/Utilities/Inc/Theme.hpp b/Components/Shared/Inc/Theme.hpp similarity index 100% rename from Utilities/Inc/Theme.hpp rename to Components/Shared/Inc/Theme.hpp diff --git a/Utilities/Src/ColorPickerPopup.cpp b/Components/Shared/Src/ColorPickerPopup.cpp similarity index 100% rename from Utilities/Src/ColorPickerPopup.cpp rename to Components/Shared/Src/ColorPickerPopup.cpp diff --git a/Utilities/Src/PaintUtil.cpp b/Components/Shared/Src/PaintUtil.cpp similarity index 100% rename from Utilities/Src/PaintUtil.cpp rename to Components/Shared/Src/PaintUtil.cpp diff --git a/Utilities/Src/PopupMenu.cpp b/Components/Shared/Src/PopupMenu.cpp similarity index 100% rename from Utilities/Src/PopupMenu.cpp rename to Components/Shared/Src/PopupMenu.cpp diff --git a/Utilities/Src/TextEdit.cpp b/Components/Shared/Src/TextEdit.cpp similarity index 100% rename from Utilities/Src/TextEdit.cpp rename to Components/Shared/Src/TextEdit.cpp diff --git a/Utilities/Src/Theme.cpp b/Components/Shared/Src/Theme.cpp similarity index 100% rename from Utilities/Src/Theme.cpp rename to Components/Shared/Src/Theme.cpp diff --git a/Components/SignalPanel/Inc/SignalPanel.hpp b/Components/SignalPanel/Inc/SignalPanel.hpp index 9daa747..ea2f8ce 100644 --- a/Components/SignalPanel/Inc/SignalPanel.hpp +++ b/Components/SignalPanel/Inc/SignalPanel.hpp @@ -1077,6 +1077,14 @@ namespace jdi */ gd::RectF EditFieldRect(); + /** + * @brief 当前输入框绘制与命中/插入符换算所使用的字体。 + * @return 字体句柄(分组名用 `uiSmall`,其余用 `ui`)。 + * @note ⚠️ 绘制(`PaintGroupRow` / `PaintSignalRow`)与鼠标定位(`UiEditOffsetAt`) + * **必须使用同一个字体**,否则点哪儿插入符就落到哪儿这个不变量会被破坏。 + */ + HFONT EditFieldFont() const; + /** * @brief 计算「筛选信号」输入框的内容矩形(编辑与显示共用,保证不跳动)。 * @return 内容矩形。 diff --git a/Components/SignalPanel/Src/SignalPanel.cpp b/Components/SignalPanel/Src/SignalPanel.cpp index 7fe62d2..66ced31 100644 --- a/Components/SignalPanel/Src/SignalPanel.cpp +++ b/Components/SignalPanel/Src/SignalPanel.cpp @@ -1186,6 +1186,17 @@ namespace jdi } } + HFONT SignalPanel::EditFieldFont() const + { + if (m_fonts == nullptr) + { + return nullptr; + } + // 分组名的编辑框在行内是用 `uiSmall` 绘制的(分组行字号更小),其余输入框用 `ui`。 + // 绘制、命中、插入符换算三处必须一致,否则鼠标落点与插入符位置会错开。 + return (m_editField == StyleEditField::GROUP_TITLE) ? m_fonts->uiSmall : m_fonts->ui; + } + const SignalStyle* SignalPanel::StyleOf(const SignalRef& ref) const { return (m_dataSet != nullptr) ? m_dataSet->Style(ref) : nullptr; diff --git a/Components/SignalPanel/Src/SignalPanelInteraction.cpp b/Components/SignalPanel/Src/SignalPanelInteraction.cpp index 436bfa7..6ba42e5 100644 --- a/Components/SignalPanel/Src/SignalPanelInteraction.cpp +++ b/Components/SignalPanel/Src/SignalPanelInteraction.cpp @@ -821,7 +821,7 @@ namespace jdi { return false; } - return UiEditFieldMouseDown(m_fonts->ui, EditFieldRect(), m_edit, m_scale, x, y); + return UiEditFieldMouseDown(EditFieldFont(), EditFieldRect(), m_edit, m_scale, x, y); } bool SignalPanel::OnEditMouseMove(int x) @@ -834,7 +834,7 @@ namespace jdi { return false; } - return UiEditFieldMouseMove(m_fonts->ui, EditFieldRect(), m_edit, m_scale, x); + return UiEditFieldMouseMove(EditFieldFont(), EditFieldRect(), m_edit, m_scale, x); } bool SignalPanel::OnEditMouseUp(int x, int y) diff --git a/Components/SignalPanel/Src/SignalPanelPaint.cpp b/Components/SignalPanel/Src/SignalPanelPaint.cpp index 7d9f9b4..f5a7db5 100644 --- a/Components/SignalPanel/Src/SignalPanelPaint.cpp +++ b/Components/SignalPanel/Src/SignalPanelPaint.cpp @@ -326,6 +326,13 @@ namespace jdi if (editing) { mixText(m_edit.Text()); + // ⚠️ 插入符与选区必须进键:行位图缓存只按“行的可见内容”生效, + // 而编辑态下插入符位置 / 选区范围也是可见内容。漏掉它们时, + // 方向键与鼠标框选虽然改变了 `TextEdit` 状态,但键不变 ⇒ 贴回的仍是旧位图, + // 屏幕上看就是“无法部分选择、光标不动”(用户 2026-10-19 报告)。 + mix(static_cast(m_edit.Caret()) + 1ull); + mix(static_cast(m_edit.SelectionBegin()) + 1ull); + mix(static_cast(m_edit.SelectionEnd()) + 1ull); } return hash; } @@ -350,6 +357,10 @@ namespace jdi if (editing) { mixText(m_edit.Text()); + // 同分组行:插入符 / 选区也是行的可见内容(见 `GROUP_TITLE` 分支的注释)。 + mix(static_cast(m_edit.Caret()) + 1ull); + mix(static_cast(m_edit.SelectionBegin()) + 1ull); + mix(static_cast(m_edit.SelectionEnd()) + 1ull); } return hash; } diff --git a/Documents/agent_mem.md b/Documents/agent_mem.md index e96e73a..7e45daa 100644 --- a/Documents/agent_mem.md +++ b/Documents/agent_mem.md @@ -2995,6 +2995,8 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ } | 2026-10-17 | DeepSeek-V4.1-Flash(**本机**) | **多层加载拖拽卡顿根治 + 高亮命中线段中段 / 散点高亮 + 选区放大也高亮 + 自动点线判定与规则修正(详见 §9.45)**。① **拖拽卡顿(39.2→8.2ms 均值、47.9→10.3ms 最坏)**:**(a)** `RenderLaneLayerRegion` 每次补条都调两次 `BuildLaneVisuals()`(O(网格数))⇒ 253 网格时 `RollWideBuffer` 单帧 **26.8ms**;改为快照/拷回 `m_lanes`;**(b)** 横向“重新居中”纯属优化 ⇒ `kLayerRollQuantumX=32` + `kLayerRollsPerFrame=3`;**(c)** 位移过大时旧实现**同步整块重画**(巨型信号 20~130ms)⇒ `kLayerRollMaxShiftPixels=160` 作废图层交给底衬;`RollWideBuffer` 跳过离屏网格;**(d)** 包络带路径也支持补条裁剪(巨型信号一条 32px 竖带 5ms → 亚毫秒)。② **高亮命中**:`HitTestSignal` 横向窗口少算一个顶点 ⇒ 放大后两点**中间的连线**完全点不中(0/11 → 11/11);散点模式新增“数据点高亮环”(不含连线),列表/图例联动同源。③ **选区放大**恢复悬停高亮(仍不可拖信号),提示只显示信号名。④ **自动点线误判**:判定范围未限制在信号有数据的时段 ⇒ 长跨度文件 + 短信号时 MF4 信号默认视图全部误判(248→0);并改为**只对 `DrawMode::LINE` 生效**(SCATTER 不切换、用户手动改过不切换)。验证:新增 `.preflight/perf/hoverdrag_main.cpp` **20/20**;`verify` 49/49、`panelanim` 158/158、`datacheck` 69/69、`edgecheck` 46/46、`errcheck` 19/19、`gridanim` 93/93、`lanescroll` 23/23;`make release`/`make debug` 零警告。⚠️ 探针的绝对毫秒阈值改为**相对基准**(同一基线代码在不同时段可差 2~3 倍)。§11.2 新增第 141–150 条。 | +| 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` 零警告。 | + ## 11. 跨设备协作须知(起源设备视角) > **本文件是唯一权威记忆**。所有设备 / Agent 共用它,**不要再新建 `agent_mem_origin.md` 等旁支记忆文件**。 @@ -3360,8 +3362,18 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ } (名称不再有“回退值”)。 88. ⚠️ **通用 UI 控件属于 `Components/Shared`**(用户 2026-10-16 要求建此组件): 图标 / 复选框 / 滑块 / 内联输入框 / 颜色样块 / 取值胶囊配色都在 - `Components/Shared/Inc/UiControls.hpp`;`Utilities/PaintUtil.*` **只保留低层绘制原语**。 - 依赖方向固定 `Components/* → Components/Shared → Utilities`,**不得反向**。 + `Components/Shared/Inc/UiControls.hpp`。 + ⚠️ **【2026-10-19 更新:UI 基础层整体迁入 `Shared`】**用户指出 `Utilities` 里那些“会绘制/承载 + UI”的模块不属于纯粹工具范畴,现全部位于 **`Components/Shared/{Inc,Src}`**: + `Theme.*`(调色板与 `ToGdiColor/MakeArgb/WithAlpha/MixColor`)、`PaintUtil.*`(低层绘制原语)、 + `TextEdit.*`(内联编辑模型)、`PopupMenu.*`、`ColorPickerPopup.*`。 + ⇒ **`Utilities/` 只剩纯数据/逻辑模块**(`DataModel`、`DataTable`、`CsvLoader`、`DataLoader`、 + `Mf4Loader`、`MatH5`、`MatLoader`、`Utf8Util`),**不得**再往 `Utilities` 放任何包含 + `gdiplus.h` 的绘制/控件代码。 + 依赖方向仍是单向:**`Components/* → Components/Shared → Utilities`**(`Shared` 可以消费 + `Utilities` 的 `Utf8Util` 等,反向禁止)。搬移时**只需 `git mv`**:`#include "X.hpp"` 是平铺写法, + 两个目录都在 `CXX_INCLUDES_PATHS` 里,**Makefile 无需改动**(但要清掉 `Build/*` 里那几个 + `Theme.o/PaintUtil.o/TextEdit.o/PopupMenu.o/ColorPickerPopup.o` 及其 `.d`,否则旧依赖文件指向旧路径)。 新增组件目录要同时登记 `Makefile`(`CXX_INCLUDES_PATHS` / `CXX_SOURCES_PATHS`)与 `.preflight/perf/build.ps1`。 89. ⚠️⚠️ **新增标识符前必须确认不是 Windows 头里的宏**:`windows.h` 会引入 `nb30.h` 的 @@ -6356,3 +6368,50 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ } 用探针从**真实产物** `LoadImageW` 逐尺寸校验,而不是只看文件能编译。 5. **任何"长时间解析"都要按阶段回报进度**(读字节 + 每个解码单元), 且 `hasBytes` 只在真正有字节可报的阶段置真,否则遮罩层会停在 0%。 + +--- + +## 本轮(2026-10-19 第二轮,本机):列表内联编辑看不到插入符/选区 + UI 基础层迁入 `Components/Shared` + +> 用户报告两件事:① 信号列表里**组名**双击进入编辑后与其它输入框状态不一致 —— +> 无法部分选择、鼠标框选无反应、左右方向键也不移动插入符;② `Utilities/{Inc,Src}` 里的 +> `ColorPickerPopup` / `PopupMenu` / `TextEdit` / `Theme` 等其实是会绘制的 UI 组件,应归到 +> `Components/Shared`。 +> 验证:新增离屏 `.preflight/editcheck`(`SW_HIDE` + 合成消息 + `BELOW_NORMAL`)**20/20**; +> **A/B:修复前 6 项如实失败**(键不变 + 屏幕不重绘)。`make release` / `make debug` 零警告。 + +### A. ① 的根因:行位图缓存的键漏了「插入符与选区」 + +- 信号列表为了流畅性把**整行**渲染进离屏 DIB 缓存(`SignalPanel::PaintRowCached`,`24f0bf8` 引入), + 命中时一次 `BitBlt`。但 `RowCacheKey()` 在编辑态只混合了 **`editing` 标志 + `m_edit.Text()`**。 +- 于是 **方向键 / 鼠标框选只改变 `TextEdit` 的 `caret / anchor`,文本没变 ⇒ 键没变 ⇒ 贴回旧位图** + ⇒ 屏幕上插入符与选区完全不动(**输入字符**时才刷新,所以只表现为“不能部分选择/光标不动”)。 + 绘图区的光标时间输入没有行缓存,所以一直正常 —— 这正是用户说的“状态不一致”。 + ⚠️ 信号名编辑走同一套行缓存,同样有此缺陷(只是用户先注意到组名)。 +- ✓ 修法:两个分支(`GROUP_TITLE` / `SIGNAL_NAME`)在编辑态把 + `Caret()` / `SelectionBegin()` / `SelectionEnd()` 一并 mix 进键。 +- ✓ 顺带统一字体:新增 `SignalPanel::EditFieldFont()`(分组名 = `uiSmall`,其余 = `ui`), + 供 `OnEditMouseDown` / `OnEditMouseMove` 的 `UiEditOffsetAt` 使用 —— + 绘制(`PaintGroupRow` 用 `uiSmall`、`PaintSignalRow` 用 `ui`)与**插入符换算必须同字体**, + 否则“点哪儿插入符落在哪儿”这个不变量会被破坏。 + +### B. ② 的落地:`Utilities` 只保留纯数据/逻辑 + +| 旧位置 | 新位置 | +| --- | --- | +| `Utilities/Inc/Theme.hpp` + `Utilities/Src/Theme.cpp` | `Components/Shared/{Inc,Src}/Theme.*` | +| `Utilities/Inc/PaintUtil.hpp` + `Utilities/Src/PaintUtil.cpp` | `Components/Shared/{Inc,Src}/PaintUtil.*` | +| `Utilities/Inc/TextEdit.hpp` + `Utilities/Src/TextEdit.cpp` | `Components/Shared/{Inc,Src}/TextEdit.*` | +| `Utilities/Inc/PopupMenu.hpp` + `Utilities/Src/PopupMenu.cpp` | `Components/Shared/{Inc,Src}/PopupMenu.*` | +| `Utilities/Inc/ColorPickerPopup.hpp` + `Utilities/Src/ColorPickerPopup.cpp` | `Components/Shared/{Inc,Src}/ColorPickerPopup.*` | + +- **为什么连 `PaintUtil` 一起搬**:`PaintUtil.hpp` 依赖 `Theme.hpp`(`ToGdiColor` 等)。 + 若只搬 `Theme`/`TextEdit`,`Utilities` 反而要包含 `Shared` 的头文件 ⇒ **反向依赖**, + 违反第 88 条的单向分层。搬完之后 `Shared` 是自洽的“UI 基础层”: + `Theme`(设计令牌/颜色)→ `PaintUtil`(低层原语)→ `UiControls`(控件)→ `PopupMenu`/`ColorPickerPopup`。 +- 依赖方向不变:**`Components/* → Components/Shared → Utilities`**;`Utilities` 现在只剩 + `DataModel` / `DataTable` / 各 `*Loader` / `MatH5` / `Utf8Util`,**不得**再放 `gdiplus.h` 系代码。 +- 搬移手法:`git mv`(保留历史)+ **Makefile 无需改动**(`#include "X.hpp"` 平铺,两个目录都已在 + `CXX_INCLUDES_PATHS/CXX_SOURCES_PATHS`)。⚠️ 必须删掉 `Build/Release|Debug` 里对应的 + 旧 `*.o` / `*.d`(`.d` 里记着旧路径,会让 make 误判/漏依赖)。 +- 文档里 §11.2 第 88 条已按本表更新(历史章节里出现的 `Utilities/PaintUtil.*` 等仅作历史记录)。