Files
Just-DataInspector/Documents/agent_mem.md
T
DeepSeek V4.1 Flash 2f7a6bc2d2 修复右键网格菜单后悬停提示残留;修复实验性功能预览曲线导致的平移/拖光标卡顿
1) 右键弹出网格菜单前清空绘图区悬停状态(并让 OnMouseLeave 一并清 m_hoverLegend),菜单打开后信号光晕与「可拖拽」提示气泡不再残留。
2) 时间光标处派生取值胶囊原先把请求窗口取成「可见区间 ∪ 光标下标」的并集:光标在视界外时窗口达上千万采样,超缓存上限后每帧重算整段多次迭代滑动平均(实测单帧 300ms+),并随平移/拖滑块/拖光标/改色一起变卡。改为只请求光标所在采样点的小窗口,取值依赖由 CurveSpanFor 保护带覆盖,取值逐位一致、显示效果零变化。
离屏实测 VERY_BIG_DATA:均值滤波平移 326ms→16~23ms,中心轴边沿计数平移 300ms→20ms。
2026-10-11 03:47:43 +08:00

198 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Just DataInspector — Agent 记忆与项目须知
> 本文件是本仓库的**权威上下文备忘录**,供任何接手的 AI Agent 在动手前阅读。
> **每次完成任务后必须同步更新本文件**(补充新需求、修复记录、约定变更等)。
> 保持内容**精确、明细、简练**;多台设备协作时,可将各自的记忆合并到本文件。
---
## 1. 项目目标
- 使用 **C++17 + Win32 API(GDI/GDI+)** 从零构建的桌面数据查看器,项目名 **Just DataInspector**。
- 定位:替代 MATLAB/Simulink 的 `Simulink.sdi.view` 仿真数据检查器,用于更方便地查看**带时间轴的 CSV** 仿真数据。
- 目标数据特征(参考 MathWorks 导入 CSV 的说明):CSV 中存在**一个或多个时间轴列**,其余列表示各时间点上的信号值。
- 产物为 **GUI 程序**(非 CUI)。
---
## 2. 构建与工具链
- 工具链:**MinGW-w64**(`x86_64-win32-seh-rev0`, MinGW-Builds 16.1.0),可直接用 `make`。
- 构建入口:根目录 `Makefile`(C / C++ / windres 混合构建)。
- 常用命令:
- `make release` → `Build/Release/Target.exe`(`-Os`,strip:`-s`)。
- `make debug` → `Build/Debug/Target.exe`(`-O0 -g`)。
- `make` → 同时构建 Release 与 Debug。
- `make clean` → 删除 `Build/`。
- 关键编译设置:`-std=c++17`;`-finput-charset=UTF-8 -fexec-charset=GBK -fwide-exec-charset=UTF-16LE`;`-static`;`-mwindows`(GUI);链接 `gdi32 gdiplus comdlg32 d3d11 dwmapi d3dcompiler`。
- **新增组件/工具目录后,必须在 `Makefile` 的 `CXX_INCLUDES_PATHS` 与 `CXX_SOURCES_PATHS` 中登记**(源文件按目录通配自动收集)。
- 构建产物统一在 `Build/`,已由 `.gitignore` 忽略;同时忽略 `*.o/*.d/*.lst/*.map/*.exe` 与 `TestData/`。
---
## 3. 目录结构与约定
```
Components/ 功能组件块(每块 Inc/ 放 .hpp,Src/ 放 .cpp)
Application/ 应用主框架:窗口、布局、消息分发、标签页、绘制合成
Main/ 程序入口(WinMain → Application::Run)
TabBar/ 顶部多标签栏
Toolbar/ 顶部工具栏(导入文件 / 适应视图 / 时间轴自适应 / Y 轴自适应 / 选区放大 / 光标 / 合并·拆分·清空网格)
SignalPanel/ 左侧信号面板:按文件分组的信号树、可见性勾选、样式编辑
PlotView/ 波形绘图区:网格、时间轴、光标、缩放、拖拽、实验性功能面板
Utilities/ 工具与辅助模块(Inc/ + Src/)
Application/ ... (见上)
CsvTable/ CSV 流式解析(列优先)
DataModel/ 数据层:DataSet / DataFile / SignalData / 显示配置
PaintUtil/ GDI+ 绘制与文本/控件辅助(含 UiPointInRect、UiMeasureTextWidth 等)
PopupMenu/ 主题化自绘弹出菜单(含二级子菜单、内联编辑)
ColorPickerPopup/ 浮动拾色器
TextEdit/ 内联文本编辑模型
Theme/ 主题色板(Solarised Light)与波形默认配色
Utf8Util/ 编码转换(UTF-8 ↔ UTF-16 等)
Resources/ 图标、清单(.manifest)、版本信息(.rc)、ResourceIds.h
TestData/ 本地测试 CSV(**不纳入版本控制**)
Documents/ 项目文档(含本文件)
```
- **命名硬性约束**:源文件/头文件**不得含空格**,只能字母与下划线组合。
- **头文件后缀**:C 用 `.h`,C++ 用 `.hpp`;C 源 `.c`,C++ 源 `.cpp`。
- **防重复包含**:每个头文件必须 `#ifndef __XXX_XX_HPP__ / #define __XXX_XX_HPP__ / ... / #endif`;宏名 = 文件名的双边双下划线 + 全大写。
- **宏定义与枚举常量必须全大写 + 下划线**。
- 所有对外函数/方法声明用 Doxygen 风格注释:`@brief`、`@param [IN/OUT]`、`@return`(中文说明)。
- 代码风格统一,强调**解耦**与清晰的分层。
---
## 4. 架构要点
- **数据层共享、视图配置分标签页**:全局 `DataSet`(已加载文件、信号、时间轴)被所有标签页共享;每个标签页仅持有各自的 `TabConfig`(信号可见性、配色、分组、网格等),互不影响。
- `Application` 是总编排者:创建窗口、`Relayout` 布局、`HandleMessage` 分发消息、合成绘制、把菜单/拾色器结果派发回组件。
- 自定义绘制的浮动控件(`PopupMenu`、`ColorPickerPopup`)**不创建独立窗口**,作为宿主绘制的**最上层内容**直接画进后备缓冲区,保证外观与主界面统一(不出现系统默认菜单样式)。
- 消息优先级:菜单打开 > 拾色器打开 > 常规组件交互(见 `Application::HandleMessage`)。
- DPI 感知:`ApplyDpi` 重建字体与布局;浮动控件通过 `SetScale` 缩放。
### 数据模型(`Utilities/Inc/DataModel.hpp`)
- `SignalData`:单条信号元数据 + 采样值。
- `DataFile`:一个已加载文件及其**自有时间轴**(`timeColumnName` / `time` / `minTime` / `maxTime` / `timeMonotonic`)。
- `DataSet`:文件集合与全局时间范围查询(`MinTime/MaxTime`、最近采样点查找等)。
- `DrawMode`:`LINE`(插值折线) / `SCATTER`(散点) / `LINE_SCATTER`(两者)。
- `LaneMode`:`PER_SIGNAL` / `MERGED` / `CUSTOM`(网格划分策略)。
- `PlotCursorMode`:`NONE` / `SINGLE` / `DOUBLE`(时间光标三态)。
- 各网格自身状态(Y 轴范围、自适应开关、实验性功能开关等)随信号继承。
### CSV 解析(`Utilities/Src/DataModel.cpp`)
- 列优先存储(每条信号一整块连续内存):避免百万行级按行存储的堆分配放大,并支持整列**移动**进 `SignalData`。
- 流式分块读取,可携带真实字节进度(`CsvProgressInfo`),支持取消。
- **时间列判定** `LooksLikeTimeHeader`:小写匹配 `t / time / timestamp / simulation time`,或包含 `time` / `时间`;未命中时回退第 0 列。
- 缺失值以 `NaN` 表示;要求时间列有效数值 ≥ 2,否则报错。
- 分隔符自动探测(默认 `,`)。
---
## 5. 功能需求清单(来自需求交互,持续补充)
### 5.1 基础功能(首轮构建目标,已实现)
- 波形网格(多网格);时间轴。
- 光标(单/双光标,含差值读数)。
- 缩放与自适应(适应视图、Y 轴自适应、选区放大)。
- 导入并加载符合要求(带时间轴列)的 CSV,**解析出信号名**。
- 信号样式:**修改颜色、粗细、散点/插值线段显示方式**(`DrawMode`)。
- 信号可绘制到**同一网格**或**不同网格**。
### 5.2 后续扩展(已在代码中实现,供接手参考)
- 多标签页(新建/关闭/重命名/拖拽排序)。
- 工具栏:合并网格 / 拆分网格 / 清空网格;时间轴自适应。
- 信号面板:按文件分组、勾选可见性、仅显示/全部显示/隐藏全部、移除文件。
- 绘图区拖拽:平移、选区放大(按首个明显位移方向决定横/纵轴)、网格滚动、光标拖拽、**把信号曲线拖到其它网格**、网格图例 GX 徽标上下重排。
- 图例:悬停高亮、内联重命名、信号删除按钮。
### 5.3 「实验性功能」二级菜单(网格右键菜单内,横线上方)
包含且仅包含以下可勾选项(每网格独立状态):
- **边沿计数**
- **边沿计数(中心轴)**
- **均值滤波**
- **弱波增强**
这些功能开关会联动绘图区的浮动参数面板;部分支持「构建为新信号」。
> 注意:新增/改名实验性功能时,需同步 `PlotView.hpp` 的 `PLOT_CMD_LANE_*`、`Application.cpp` 的菜单构建与派发、`PlotView`/`DataModel` 的状态位与面板绘制。
---
## 6. UI / 交互设计语言
- 参考 **VS Code** 与 **WinUI** 的设计语言;配色为 **VS Code 的 Solarised Light** 方案(`Theme::SolarizedLight()`,强调色 `accent`)。
- 全部界面与交互用 **Win32 API** 自绘,不用系统默认控件样式。
- 文本对齐、菜单内边距、圆角、阴影等在各浮动控件内保持统一风格。
---
## 7. 交付与 Git 规范
- 工作区由**本地 git** 管辖。
- **仅在功能完善(可验证)后才提交**;提交信息用简洁中文描述修改内容。
- 提交时**仅对本工作区**生效(`git config --local`),**不得影响全局设置**:
- `user.name` = 当前模型全称。
- `user.email` = `<>`(留空尖括号)。
- `TestData/`、`Build/` 等不提交(见 `.gitignore`)。
---
## 8. Agent 工作流要求
1. 接手前先**探索工作区/提交历史/代码**建立认知(本文件应作为第一参考)。
2. 完成需求后**编译验证**(`make release` 至少成功),必要时用 `TestData/` 数据功能验证。
3. **验证与探索时尽量不占用用户的鼠标/屏幕/键盘**(后台构建为主)。
4. 本地仓库提交完成后,**不要在未经指示的情况下继续探索或优化**,等待用户下一步指令。
5. **每次完成任务后更新本文件**(需求、修复、约定变更)。
---
## 9. 已知问题与修复记录
### 9.1 网格右键菜单「实验性功能」二级菜单瞬间关闭(已修复)
- **症状**:在网格右键菜单里把鼠标从「实验性功能」移向右侧二级菜单的途中,只要指针短暂离开父项交互区域,二级菜单立即关闭,用户来不及完成选择。旧设备 CPU 较慢、鼠标移动事件稀疏,指针可能"跳"进二级菜单而未暴露该问题;CPU 更快、事件更密集时必然复现。
- **根因**(`Utilities/Src/PopupMenu.cpp` 的 `PopupMenu::OnMouseMove`):
- 子菜单展开后,只要指针**不在子菜单矩形内**且**未命中任何可点击条目**,就无条件 `CloseSubmenu()`。
- 主菜单条目矩形的右缘为 `menu.right - kPadH`,而子菜单左缘为 `menu.right - kSubOverlap`,两者之间存在约 3px 的**内边距"死区"**(位于主菜单矩形内但落在任何条目之外)。鼠标事件密集时指针必然落入该窄带,从而触发误关闭。
- **修复**:子菜单展开期间,若指针仍位于「主菜单 ∪ 子菜单」矩形范围内、且尚未落到**其它可点击条目**上(例如掠过内边距窄带、分隔线、禁用项),则保持子菜单展开并维持父项高亮;只有明确移动到其它条目或完全离开上述范围时才收起。
### 9.2 打开网格右键菜单后,悬停光晕与「可拖拽」提示气泡不消失(已修复)
- **症状**:鼠标停在信号曲线上(出现光晕高亮 + 「XXX · 按住拖拽可移入其它网格」提示气泡)时右键弹出网格菜单,高亮与气泡**残留在原地**,不随菜单打开而消失。
- **根因**:菜单打开后鼠标移动被菜单独占(`Application::HandleMessage` 在 `m_menu.IsOpen()` 时只把事件交给菜单),绘图区再也收不到 `WM_MOUSEMOVE`;而打开菜单时**没有清空**绘图区的悬停状态(`m_hoverSignal` / `m_hoverLegend`),因此 `PaintHoverHighlight` 与 `PaintSignalInteraction` 继续把高亮与气泡画出来。
- **修复**:
- `Application` 在绘图区右键弹出网格菜单前调用 `m_plot.OnMouseLeave()`,清空悬停状态;
- `PlotView::OnMouseLeave()` 补上清空 `m_hoverLegend`(原先只清 `m_hoverLane/m_hoverSignal` 等,图例半高亮会残留)。
### 9.3 性能:启用带预览曲线的实验性功能后,平移/拖动光标异常卡顿(已修复)
- **症状**(以 `VERY_BIG_DATA.CSV` 为例,加载并显示全部信号后):时间轴选区放大到某些区间时,左键/中键平移网格视界、或启用「均值滤波 / 中心轴边沿计数」后再平移、甚至只是左右拖动时间光标看数值,都会**异常卡顿**(实测单帧 300ms+)。
- **排查方法**:在 `.preflight/perf/`(gitignored)用离屏窗口(不占用鼠标/键盘/屏幕)直接驱动 `PlotView`,用 `QueryPerformanceCounter` 测 `Paint()`,并对 `CurveSpanFor` / `ComputeMovingAverageIterated` / `PaintDerivedCurve` 分段埋点计时。
- **根因**(`Components/PlotView/Src/PlotView.cpp` 的 `PaintCursor` → `paintDerivedCapsule` lambda):
- 绘制时间光标处的**派生曲线取值胶囊**时,把请求窗口取成「**可见区间 ∪ 光标采样下标**」的并集;
- 当光标不在当前视界内(平移视界会把光标甩到视界外)时,这个并集会变成 `[光标下标, 视界右端]` 或 `[视界左端, 光标下标]` —— 上千万采样的**巨大窗口**;
- 该窗口超过 `kCurveSpanCacheMaxSamples`(4M) → 走一次性缓冲、**不进缓存** → **每一帧都重算整段多次迭代滑动平均**(实测 5 条曲线合计 300ms+)。
- 这条路径同样被平移、拖滑块、拖动光标、改色等所有「每帧重绘」的交互触发,因此它们一起变卡。
- **修复**:`paintDerivedCapsule` 只需要光标所在采样点(以及双光标另一端)的派生取值,因此把请求窗口收窄为「**需要的那几个下标**」(`[sampleIndex, sampleIndex+1]`,双光标时并入另一端)。均值滤波半径造成的取值依赖由 `CurveSpanFor` 自身的保护带(`guard = clamp(max(窗口/8, (X+1)*N+64), 4096, 65536)`)负责覆盖 —— 对一切 `reach ≤ 65536` 的参数,取值与「整条曲线」**逐位一致**,显示效果零变化。
- **效果**(离屏实测,`VERY_BIG_DATA`,5 网格):
- 均值滤波平移(2% 跨度):**326ms → 约 16–23ms**;
- 均值滤波平移(0.4%/0.08%/0.016%):**约 305ms → 4–6ms**;
- 中心轴边沿计数平移(2%):**约 300ms → 约 20ms**;
- 光标左右移动(静态场景):命中场景缓存,仅叠加层重绘,约 9ms。
- **不影响显示的说明**:本次**未**改动任何几何/绘制算法(没有引入曾导致"棘状细节被并成实心块"的 ±1 列扩展),只收窄了「为了取时间光标处派生取值而请求的曲线窗口」,因此画面与实际取值完全不变。
### 9.4 性能经验与已知残留热点(供后续参考)
- **本仓库的性能黄金法则**:交互期间(`m_dragMode != NONE`)走「快速帧」(关闭波形/派生曲线抗锯齿),松手后补一帧全质量;静止画面走「静态场景缓存」(BitBlt 贴图 ~1ms)。任何**只在鼠标移动/拖动参数时改变**的内容都必须画在贴图之后(叠加层),绝不能进静态缓存,也不能每帧触发整条曲线/整段采样的重算。
- **已知残留热点**(本次未动,均为"非拖拽的整幅重绘",实际拖拽时因快速帧而很快):
- 原始波形的「逐像素列包络带」用 GDI+ `FillPath` 填充;在某些跨度下包络带不被判为「梳状」,抗锯齿填充仍约 20–27ms/帧(`VERY_BIG_DATA` 的全量重绘)。这是 GDI+ 抗锯齿路径的固有代价;如需进一步提速,**只能**降低列分辨率或对该形态关闭抗锯齿 —— 两者都会改变显示效果,需用户确认后再动。
- 「构建为新信号」等一次性操作会真正计算整条派生曲线(约 1 帧的秒级耗时,正常)。
- **测量工具**:`.preflight/perf/perf.exe <csv> [mode]`(离屏,不弹窗)。`mode=0` 跑各交互场景耗时;`mode=1` 导出确定性渲染 PNG 以便逐像素比对(用于验证"显示效果不变")。该目录已由 `.gitignore` 忽略,不会入库。
---
## 10. 变更日志(本文件维护记录)
| 日期 | 设备/模型 | 摘要 |
| --- | --- | --- |
| 2026-10-11 | DeepSeek-V4.1-Flash | 新建本文件;记录项目要点与规范;修复「实验性功能」二级菜单瞬间关闭问题。 |
| 2026-10-11 | DeepSeek-V4.1-Flash | 修复右键网格菜单后悬停光晕/提示气泡残留;修复「启用带预览曲线的实验性功能后平移/拖光标 300ms+ 卡顿」(`paintDerivedCapsule` 巨窗每帧重算,收窄请求窗口,显示效果零变化)。 |