修复二级菜单移向途中被误关闭;新增项目记忆文档 agent_mem.md
网格右键菜单「实验性功能」展开二级菜单后,鼠标从父项移向子菜单途中经过内边距窄带会被立即收起。改为子菜单展开期间只要指针仍在主菜单∪子菜单范围内且未落到其它可点击条目即保持展开并高亮父项。
This commit is contained in:
1 parent
7608e85cf0
commit
2322e76d8c
2 files changed
+189
-4
No files matched your search
@@ -0,0 +1,166 @@
|
||||
# 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 的**内边距"死区"**(位于主菜单矩形内但落在任何条目之外)。鼠标事件密集时指针必然落入该窄带,从而触发误关闭。
|
||||
- **修复**:子菜单展开期间,若指针仍位于「主菜单 ∪ 子菜单」矩形范围内、且尚未落到**其它可点击条目**上(例如掠过内边距窄带、分隔线、禁用项),则保持子菜单展开并维持父项高亮;只有明确移动到其它条目或完全离开上述范围时才收起。
|
||||
|
||||
---
|
||||
|
||||
## 10. 变更日志(本文件维护记录)
|
||||
|
||||
| 日期 | 设备/模型 | 摘要 |
|
||||
| --- | --- | --- |
|
||||
| 2026-10-11 | DeepSeek-V4.1-Flash | 新建本文件;记录项目要点与规范;修复「实验性功能」二级菜单瞬间关闭问题。 |
|
||||
@@ -678,10 +678,12 @@ namespace jdi
|
||||
return false;
|
||||
}
|
||||
|
||||
const float fx = static_cast<float>(x);
|
||||
const float fy = static_cast<float>(y);
|
||||
bool changed = false;
|
||||
|
||||
// 鼠标在展开的子菜单内:只更新子菜单的悬停项(主菜单父项保持高亮)。
|
||||
if (m_subOpen && UiPointInRect(m_subRect, static_cast<float>(x), static_cast<float>(y)))
|
||||
if (m_subOpen && UiPointInRect(m_subRect, fx, fy))
|
||||
{
|
||||
const int sub = SubItemAtPoint(x, y);
|
||||
if (sub != m_subHoverIndex)
|
||||
@@ -694,6 +696,25 @@ namespace jdi
|
||||
|
||||
int swatchIndex = -1;
|
||||
const int index = ItemAtPoint(x, y, swatchIndex);
|
||||
const bool onSubmenuParent =
|
||||
(index >= 0) && (m_items[static_cast<std::size_t>(index)].kind == MenuItemKind::SUBMENU);
|
||||
|
||||
// 子菜单展开期间,鼠标只要仍停留在「主菜单 ∪ 子菜单」的范围内且尚未落到其它可点击
|
||||
// 条目上(例如掠过父项右缘与子菜单之间的内边距窄带、分隔线或禁用项),就不收起子菜单。
|
||||
// 这样可避免处理器速度较快、鼠标移动事件过密时,用户还没把指针移到子菜单上,
|
||||
// 子菜单就被中途误关闭;只有明确移到其它条目或完全离开该范围时才收起。
|
||||
if (m_subOpen && !onSubmenuParent && index < 0 &&
|
||||
(UiPointInRect(m_rect, fx, fy) || UiPointInRect(m_subRect, fx, fy)))
|
||||
{
|
||||
// 保持在父项高亮,视觉上指明子菜单的来源。
|
||||
if (m_hoverIndex != m_subParentIndex || m_hoverSwatch != -1)
|
||||
{
|
||||
m_hoverIndex = m_subParentIndex;
|
||||
m_hoverSwatch = -1;
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
if (index != m_hoverIndex || swatchIndex != m_hoverSwatch)
|
||||
{
|
||||
@@ -702,9 +723,7 @@ namespace jdi
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// 悬停到带子菜单的项 → 向右展开;移开到其它条目 → 收起。
|
||||
const bool onSubmenuParent =
|
||||
(index >= 0) && (m_items[static_cast<std::size_t>(index)].kind == MenuItemKind::SUBMENU);
|
||||
// 悬停到带子菜单的项 → 向右展开;移开到其它条目或离开菜单 → 收起。
|
||||
if (onSubmenuParent)
|
||||
{
|
||||
if (!m_subOpen || m_subParentIndex != index)
|
||||
|
||||
Reference in new issue
Block a user