From d2e6fdd085e4d7241d46b19b61509520e973a602 Mon Sep 17 00:00:00 2001 From: "DeepSeek V4.1 Flash" <> Date: Fri, 16 Oct 2026 04:38:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E5=B0=BE=EF=BC=9ALanePanelState=20?= =?UTF-8?q?=E6=8A=BD=E5=8F=96=20+=20=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E9=9D=9E=E6=A8=A1=E6=80=81=E5=8C=96=EF=BC=88=E6=B8=85=E7=A9=BA?= =?UTF-8?q?=E4=B8=8A=E4=B8=80=E8=BD=AE=E7=9A=84=E2=80=9C=E6=9C=AA=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E2=80=9D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一、Lane 的内聚性(上一轮建议 5,本轮完成) - 新增 struct LanePanelState { bool open = true; double x = -1.0; double y = -1.0; }; - Lane 的 12 个平铺面板字段(edgePanelOpen/X/Y 等 × 4 类实验性功能) → 4 个聚合成员 edgePanel / edgeCenterPanel / meanFilterPanel / weakPeakPanel; - 访问点全部改为 lane.edgePanel.open 形式(DataModelTab.cpp 快照拷贝、 PlotViewInteraction.cpp 面板拖拽、PlotViewPaint.cpp 面板层序绘制); - RebuildLanes 内部的 LaneCarry 同步收敛为 4 个 LanePanelState, 12 行逐字段赋值简化为 4 行整体赋值。 - 教训:批量改名不能对“声明”和“使用”跑同一组替换对, 否则声明行会变成 `bool edgePanel.open = true;` 这类非法代码。 二、错误提示与模态阻塞解耦(上一轮建议 1,本轮完成) - Application 新增 SetModalErrorsEnabled(bool) / ModalErrorsEnabled(), 成员 m_modalErrorsEnabled 默认 true(交互场景行为不变); - Initialize 最先执行 SetModalErrorsEnabled(showCommand != SW_HIDE): 隐藏窗口(自动化/离屏)下没人能点掉模态框,等于把进程钉死; - SetStatusMessage(text, isError=true):允许模态时原样 MessageBoxW, 否则改走非模态通道 m_plot.ShowPersistentHint(text)(绘图区底部浮层); 普通提示与空文本依旧被忽略; - PlotView 新增公开接口 ShowPersistentHint(text)(内部转 SetHintSticky), 不向宿主暴露粘滞/淡出等内部细节。 三、顺带修掉一个真实缺陷 - PlotView::Paint 在 m_lanes.empty() 时提前 return,而提示浮层在其后的 叠加层绘制 ⇒ 一个信号都没勾选时任何错误都显示不出来,恰恰是 “文件加载失败”最需要提示的场景;该分支补一次 PaintHint(g)。 四、验证(全部离屏:SW_HIDE + 不注入输入 + BELOW_NORMAL) - make release / make debug:通过,零警告;Build/Release/Target.exe = 764416 B; - verify 49/49、datacheck 69/69、edgecheck 46/46、panelanim 156/156; - 新增探针 .preflight/perf/errcheck 19/19:默认值 / 隐藏窗口自动降级 / 宿主显式开关; 工作线程 + 5s 超时判定“不阻塞”(旧行为会永久阻塞,判失败并 ExitProcess, 探针不会挂住);EnumWindows 统计本进程 #32770 顶层窗口数量前后不变; 错误文本进入 m_hintText / m_hintSticky / HintNeedsAnimation / m_hintShownAt; 像素级:有提示帧与无提示帧在绘图区内的差异像素 3781,且全帧差异同为 3781 (证明差异只来自提示)。 五、文档 - Documents/agent_mem.md:§9.37 新增第八节(本轮补齐)、第九节(仅剩 MF4 项)、 §11.2 新增第 93–95 条硬约束、变更日志新增一行; - Documents/refactor_report_2026-10-16.md:§7.3 / §7.4 标记完成,新增 §九 收尾补记 (含 9.1–9.5:聚合化、解耦、探针、验证结果、变更清单)。 --- Components/Application/Inc/Application.hpp | 23 ++++ Components/Application/Src/Application.cpp | 14 ++- Components/PlotView/Inc/PlotView.hpp | 10 ++ Components/PlotView/Src/PlotViewFeatures.cpp | 21 ++-- .../PlotView/Src/PlotViewInteraction.cpp | 16 +-- Components/PlotView/Src/PlotViewPaint.cpp | 45 ++++---- Documents/agent_mem.md | 60 +++++++++- Documents/refactor_report_2026-10-16.md | 105 ++++++++++++++++-- Utilities/Inc/DataModel.hpp | 54 ++++----- Utilities/Src/DataModelInternal.hpp | 56 ++++------ Utilities/Src/DataModelTab.cpp | 24 ++-- 11 files changed, 296 insertions(+), 132 deletions(-) diff --git a/Components/Application/Inc/Application.hpp b/Components/Application/Inc/Application.hpp index eb6cf38..62fb569 100644 --- a/Components/Application/Inc/Application.hpp +++ b/Components/Application/Inc/Application.hpp @@ -54,6 +54,22 @@ namespace jdi */ LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + /** + * @brief 设置错误反馈是否使用模态对话框。 + * + * 模态错误框会抢断用户当前操作;宿主在不需要打断(或无法交互)的场景下 + * 应关闭它,此时错误信息改为在绘图区浮层中非模态展示。 + * + * @param [IN] enabled true 表示允许弹出模态错误框。 + */ + void SetModalErrorsEnabled(bool enabled) { m_modalErrorsEnabled = enabled; } + + /** + * @brief 查询错误反馈是否使用模态对话框。 + * @return 允许弹出模态错误框返回 true。 + */ + bool ModalErrorsEnabled() const { return m_modalErrorsEnabled; } + private: /** * @brief 鼠标交互的作用目标。 @@ -410,6 +426,13 @@ namespace jdi bool m_hintTimerActive = false; /**< 提示浮层淡出定时器是否已启动。 */ /** 信号列表「滚动到目标信号」动画的定时器是否已启动(见 `SignalPanel::AdvanceScrollAnimation`)。 */ bool m_scrollTimerActive = false; + /** + * 错误反馈是否使用模态对话框(默认开启;`Initialize` 遇到隐藏窗口时自动关闭)。 + * + * 隐藏窗口(无人可点确定)下弹模态框等于把进程卡死,因此按启动方式自动降级为 + * 绘图区非模态浮层提示;调用方也可用 `SetModalErrorsEnabled` 显式控制。 + */ + bool m_modalErrorsEnabled = true; std::wstring m_windowTitleText; /**< 上次写入窗口标题的完整文本(用于避免重复设置)。 */ int m_tabMenuIndex = -1; /**< 标签栏右键菜单的目标标签页下标。 */ int m_plotMenuLane = -1; /**< 绘图区右键菜单的目标网格下标。 */ diff --git a/Components/Application/Src/Application.cpp b/Components/Application/Src/Application.cpp index ef009f0..a3600f7 100644 --- a/Components/Application/Src/Application.cpp +++ b/Components/Application/Src/Application.cpp @@ -53,6 +53,9 @@ namespace jdi { m_instance = instance; + // 隐藏窗口启动(自动化/后台)时没人能点掉模态错误框,会自动降级为非模态提示。 + SetModalErrorsEnabled(showCommand != SW_HIDE); + EnableDpiAwareness(); m_dpi = QuerySystemDpi(); m_scale = static_cast(m_dpi) / 96.0f; @@ -487,12 +490,19 @@ namespace jdi void Application::SetStatusMessage(const std::wstring& text, bool isError) { - // 界面不再占用底部空间显示常驻提示;仅把真实错误以对话框形式反馈给用户。 + // 界面不再占用底部空间显示常驻提示;仅把真实错误反馈给用户。 if (!isError || text.empty()) { return; } - MessageBoxW(m_hwnd, text.c_str(), kWindowTitle, MB_ICONWARNING | MB_OK); + // 模态错误框会抢断用户当前操作,因此把"是否需要打断"交给宿主决定: + // 允许时弹框;不允许时改为绘图区底部的非模态浮层提示。 + if (m_modalErrorsEnabled) + { + MessageBoxW(m_hwnd, text.c_str(), kWindowTitle, MB_ICONWARNING | MB_OK); + return; + } + m_plot.ShowPersistentHint(text); } LRESULT Application::HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) diff --git a/Components/PlotView/Inc/PlotView.hpp b/Components/PlotView/Inc/PlotView.hpp index 295b0ed..0f9646e 100644 --- a/Components/PlotView/Inc/PlotView.hpp +++ b/Components/PlotView/Inc/PlotView.hpp @@ -533,6 +533,16 @@ namespace jdi */ bool HintNeedsAnimation() const; + /** + * @brief 以**非模态**方式向用户提示一条信息(绘图区底部浮层,停留至下一条提示替换)。 + * + * 宿主(`Application`)把不宜用模态对话框打断的操作反馈(例如加载/导出错误) + * 交给本接口显示即可,也便于在无交互(隐藏窗口)场景下保持可用。 + * + * @param [IN] text 提示文本;传空串则立即清除提示。 + */ + void ShowPersistentHint(const std::wstring& text); + /** * @brief 清除已启用的时间光标。 */ diff --git a/Components/PlotView/Src/PlotViewFeatures.cpp b/Components/PlotView/Src/PlotViewFeatures.cpp index 08ab178..eaea630 100644 --- a/Components/PlotView/Src/PlotViewFeatures.cpp +++ b/Components/PlotView/Src/PlotViewFeatures.cpp @@ -509,6 +509,11 @@ namespace jdi m_hintShownAt = (text.empty()) ? 0 : GetTickCount64(); } + void PlotView::ShowPersistentHint(const std::wstring& text) + { + SetHintSticky(text); + } + void PlotView::ReleaseHint() { // 粘滞结束:从这里开始重新计时(先停留再淡出)。 @@ -1125,11 +1130,11 @@ namespace jdi lane.edgeValue = (yMin + yMax) * 0.5; lane.edgeValueValid = true; } - lane.edgePanelOpen = true; + lane.edgePanel.open = true; } else { - lane.edgePanelOpen = false; + lane.edgePanel.open = false; } return true; } @@ -1177,11 +1182,11 @@ namespace jdi } } } - lane.edgeCenterPanelOpen = true; + lane.edgeCenterPanel.open = true; } else { - lane.edgeCenterPanelOpen = false; + lane.edgeCenterPanel.open = false; } // 关闭后若本标签页**已无任何预览曲线功能**,立即释放派生曲线数据(缓存 + 后台线程的 // thread_local 工作缓冲)—— 否则关掉面板后内存不会下降(用户报告)。 @@ -1271,11 +1276,11 @@ namespace jdi } } } - lane.weakPeakPanelOpen = true; + lane.weakPeakPanel.open = true; } else { - lane.weakPeakPanelOpen = false; + lane.weakPeakPanel.open = false; } // 关闭后若本标签页**已无任何预览曲线功能**,立即释放派生曲线数据(缓存 + 后台线程的 // thread_local 工作缓冲)—— 否则关掉面板后内存不会下降(用户报告)。 @@ -1360,11 +1365,11 @@ namespace jdi } } } - lane.meanFilterPanelOpen = true; + lane.meanFilterPanel.open = true; } else { - lane.meanFilterPanelOpen = false; + lane.meanFilterPanel.open = false; } // 关闭后若本标签页**已无任何预览曲线功能**,立即释放派生曲线数据(缓存 + 后台线程的 // thread_local 工作缓冲)—— 否则关掉面板后内存不会下降(用户报告)。 diff --git a/Components/PlotView/Src/PlotViewInteraction.cpp b/Components/PlotView/Src/PlotViewInteraction.cpp index 8956f6d..64b8a0a 100644 --- a/Components/PlotView/Src/PlotViewInteraction.cpp +++ b/Components/PlotView/Src/PlotViewInteraction.cpp @@ -1282,21 +1282,21 @@ namespace jdi switch (m_edgePanelDragKind) { case EdgePanelKind::CENTER: - lane.edgeCenterPanelX = left; - lane.edgeCenterPanelY = top; + lane.edgeCenterPanel.x = left; + lane.edgeCenterPanel.y = top; break; case EdgePanelKind::MEAN: - lane.meanFilterPanelX = left; - lane.meanFilterPanelY = top; + lane.meanFilterPanel.x = left; + lane.meanFilterPanel.y = top; break; case EdgePanelKind::PEAK: - lane.weakPeakPanelX = left; - lane.weakPeakPanelY = top; + lane.weakPeakPanel.x = left; + lane.weakPeakPanel.y = top; break; case EdgePanelKind::COUNT: default: - lane.edgePanelX = left; - lane.edgePanelY = top; + lane.edgePanel.x = left; + lane.edgePanel.y = top; break; } m_dragMoved = true; diff --git a/Components/PlotView/Src/PlotViewPaint.cpp b/Components/PlotView/Src/PlotViewPaint.cpp index 23433a0..323ad80 100644 --- a/Components/PlotView/Src/PlotViewPaint.cpp +++ b/Components/PlotView/Src/PlotViewPaint.cpp @@ -693,6 +693,9 @@ namespace jdi { InvalidateSceneCache(); PaintEmptyState(g); + // ⚠️ 空状态(尚未勾选任何信号)也必须绘制提示浮层:加载失败 / 解析异常正是 + // **最需要**非模态提示的场景,若在此提前返回,宿主的错误信息将永远不可见。 + PaintHint(g); return; } @@ -4667,9 +4670,9 @@ namespace jdi const bool enabled = meanFilter ? lane.meanFilter : (weakPeak ? lane.weakPeak : (centerAxis ? lane.edgeCenter : lane.edgeCount)); - const bool open = meanFilter ? lane.meanFilterPanelOpen - : (weakPeak ? lane.weakPeakPanelOpen - : (centerAxis ? lane.edgeCenterPanelOpen : lane.edgePanelOpen)); + const bool open = meanFilter ? lane.meanFilterPanel.open + : (weakPeak ? lane.weakPeakPanel.open + : (centerAxis ? lane.edgeCenterPanel.open : lane.edgePanel.open)); if (!enabled || !open) { return layout; @@ -4748,12 +4751,12 @@ namespace jdi } // 首次使用(位置为负)时自动放到绘图区右侧,并各网格依次错开,避免完全重叠。 - const double storedX = meanFilter ? lane.meanFilterPanelX - : (weakPeak ? lane.weakPeakPanelX - : (centerAxis ? lane.edgeCenterPanelX : lane.edgePanelX)); - const double storedY = meanFilter ? lane.meanFilterPanelY - : (weakPeak ? lane.weakPeakPanelY - : (centerAxis ? lane.edgeCenterPanelY : lane.edgePanelY)); + const double storedX = meanFilter ? lane.meanFilterPanel.x + : (weakPeak ? lane.weakPeakPanel.x + : (centerAxis ? lane.edgeCenterPanel.x : lane.edgePanel.x)); + const double storedY = meanFilter ? lane.meanFilterPanel.y + : (weakPeak ? lane.weakPeakPanel.y + : (centerAxis ? lane.edgeCenterPanel.y : lane.edgePanel.y)); float left = (storedX >= 0.0) ? static_cast(storedX) : (m_plotRect.X + m_plotRect.Width - width - 16.0f * m_scale); @@ -5320,18 +5323,18 @@ namespace jdi const std::vector& lanes = m_tab->Lanes(); for (const Lane& lane : lanes) { - mixFloat(static_cast(lane.edgePanelX)); - mixFloat(static_cast(lane.edgePanelY)); - mixFloat(static_cast(lane.edgeCenterPanelX)); - mixFloat(static_cast(lane.edgeCenterPanelY)); - mixFloat(static_cast(lane.meanFilterPanelX)); - mixFloat(static_cast(lane.meanFilterPanelY)); - mixFloat(static_cast(lane.weakPeakPanelX)); - mixFloat(static_cast(lane.weakPeakPanelY)); - mix(lane.edgePanelOpen ? 1ull : 0ull); - mix(lane.edgeCenterPanelOpen ? 1ull : 0ull); - mix(lane.meanFilterPanelOpen ? 1ull : 0ull); - mix(lane.weakPeakPanelOpen ? 1ull : 0ull); + mixFloat(static_cast(lane.edgePanel.x)); + mixFloat(static_cast(lane.edgePanel.y)); + mixFloat(static_cast(lane.edgeCenterPanel.x)); + mixFloat(static_cast(lane.edgeCenterPanel.y)); + mixFloat(static_cast(lane.meanFilterPanel.x)); + mixFloat(static_cast(lane.meanFilterPanel.y)); + mixFloat(static_cast(lane.weakPeakPanel.x)); + mixFloat(static_cast(lane.weakPeakPanel.y)); + mix(lane.edgePanel.open ? 1ull : 0ull); + mix(lane.edgeCenterPanel.open ? 1ull : 0ull); + mix(lane.meanFilterPanel.open ? 1ull : 0ull); + mix(lane.weakPeakPanel.open ? 1ull : 0ull); mix(lane.edgeValueValid ? 1ull : 0ull); mixDouble(lane.edgeValue); } diff --git a/Documents/agent_mem.md b/Documents/agent_mem.md index 9b8c8ad..55e030f 100644 --- a/Documents/agent_mem.md +++ b/Documents/agent_mem.md @@ -2230,12 +2230,44 @@ Components/Shared/Src/UiControls.cpp 6. ⚠️ **拆分不会减小机器码,反而略增**:实测 754 176 → 764 416 B(+10 240 B,+1.4%), 因为 `inline` 工具函数每个引用它的 TU 各生成一份(PE 无 ICF)。 -#### 八、本轮仍**未**完成(诚实记录) +#### 八、`LanePanelState` 抽取 + 错误提示非模态化(本轮补齐,同一轮次的第二遍) -1. `Lane` 抽出 `LanePanelState`(内聚性重构,非冗余清理):未执行。`Lane` 含 4 类实验性功能的 - 状态与各自面板坐标,建议后续把「开关 + 每信号参数 + 面板位置」抽成 `LanePanelState`。 -2. `SetStatusMessage(..., isError)` 的模态阻塞解耦:未执行(需要用户拍板提示样式)。 -3. 上一轮建议的 MF4 压缩块 / `##DL` 数据列表:按用户要求继续不做。 +1. **`Lane` 的 12 个平铺面板字段**(`edgePanelOpen/X/Y` × 4 类实验性功能)→ **4 个聚合成员** + `LanePanelState edgePanel / edgeCenterPanel / meanFilterPanel / weakPeakPanel`; + `LanePanelState { bool open=true; double x=-1; double y=-1; }`。 + 访问点全部改为 `lane.edgePanel.open` 形式(`DataModelTab.cpp` 快照拷贝、 + `PlotViewInteraction.cpp` 拖拽、`PlotViewPaint.cpp` 面板层序绘制); + `RebuildLanes` 内部的 `LaneCarry` 同步收敛为 4 个 `LanePanelState`(逐字段赋值 → 整体赋值)。 + ⚠️ **教训**:批量改名脚本**不能对「声明」和「使用」跑同一组替换对** —— + `edgePanelOpen → edgePanel.open` 用在 `LaneCarry` 的**声明行**上会生成 + `bool edgePanel.open = true;`(非法)。声明处必须单独重写。 +2. **错误提示与模态阻塞解耦**:`Application::SetModalErrorsEnabled(bool)` + `ModalErrorsEnabled()`, + 成员 `m_modalErrorsEnabled` 默认 `true`;`Initialize(instance, showCommand)` **最先**执行 + `SetModalErrorsEnabled(showCommand != SW_HIDE)`(隐藏窗口下弹模态框无人能点掉 = 进程被钉死)。 + `SetStatusMessage(text, isError=true)`:允许模态 → 原样 `MessageBoxW`; + 不允许 → `m_plot.ShowPersistentHint(text)`(绘图区底部非模态浮层)。 + 普通提示与空文本仍被忽略 ⇒ **交互场景行为完全不变**。 + `PlotView` 新增公开 `ShowPersistentHint(text)`(内部转 `SetHintSticky`), + 不暴露粘滞/淡出细节。 +3. ⚠️ **顺带修掉真实缺陷**:`PlotView::Paint` 在 `m_lanes.empty()` 时提前 `return`, + 而提示浮层在其后的叠加层里绘制 ⇒ **一个信号都没勾选时任何错误都显示不出来** + (恰恰是"文件加载失败"最需要提示的场景)。修法:该分支补一次 `PaintHint(g)`。 + 这是**探针先失败、才回头发现的**。 +4. **新增探针 `.preflight/perf/errcheck`(19/19)**:默认值/隐藏窗口降级/宿主显式开关; + `SetStatusMessage` 从**工作线程**调用须 5s 内返回(旧行为永久阻塞 ⇒ 判失败并 `ExitProcess`, + 探针不会挂);`EnumWindows` 统计本进程 `#32770` 顶层窗口数量前后不变; + 错误文本进 `m_hintText`、`m_hintSticky`、`HintNeedsAnimation()`、`m_hintShownAt != 0`; + **像素级**:有/无提示两帧在绘图区内的差异像素 3781,且**全帧差异也是 3781**(差异只来自提示)。 +5. **验证**:`make release` / `make debug` 零警告,`Target.exe` **764 416 B**(结构改动,无新增膨胀); + `verify` 49/49、`datacheck` 69/69、`edgecheck` 46/46、`panelanim` 156/156、`errcheck` 19/19。 + +#### 九、本轮仍**未**完成(诚实记录) + +1. 上一轮建议的 MF4 压缩块 / `##DL` 数据列表:按用户要求继续不做。 + +> ⚠️ 排查教训(写进 §11.2):**同一组批量改名不能同时覆盖「声明」与「使用」**; +> 并且**任何"宿主级反馈通道"都必须做像素级验证** —— 通道存在 ≠ 通道可见 +>(`Paint` 的 early-return 会静默把它吃掉)。 --- @@ -2289,6 +2321,7 @@ Components/Shared/Src/UiControls.cpp | 2026-10-16 | DeepSeek-V4.1-Flash(**另一台设备**) | **分组折起/展开动画 + 筛选态可折起(项数按筛选计数)+ 悬浮组名可交互与"先滚动再折起" + 「适应视图」与「Y 轴自适应」彻底同源(详见 §9.35)**。① **折起动画**:`SignalPanel::m_groupAnims` + `GroupRowScale()` —— 每行速度倍率 `1 + 1.2×ordinal/(行数−1)`(**离组名越远越快收尽**)叠加 ease-out cubic(末段减速),总时长 `clamp(115+14×行数, 140, 300)` ms;`BuildRows` 输出**非整行高**,低于 0.75 逻辑 px 的行不再产生;宿主 `WM_TIMER` 同时驱动滚动与折起动画(`Render` 判定改为二者取或);新增 `SetRowClip/ClearRowClip` 把行内容裁到本行(否则垂直居中文本溢出到相邻行)。② **筛选态折起**:筛选模式改用**独立**的 `m_filterCollapsed`(默认展开,不污染普通列表),`ListRow::displayCount` 承载"该组内符合筛选条件的个数",筛选文本/清空/数据变化即清空。③ **悬浮组名可交互**:根因是 `HitRow` 只按滚动偏移映射行 ⇒ 点击穿透到下层;修法:`HitRow` 先用 `StickyGroupRowIndex()`+`StickyGroupRowTop()` 判定悬浮行矩形并临时用 `m_rowYOverrideActive` + 新的 `RowHitPart()` 算行内区域,并跳过高度为 0 的行;新增 `ToggleGroupFromRow()`:正悬浮时 `StartScrollAnimationTo(行内容偏移, groupIndex)`(挂起请求 `m_pendingGroupToggle`),滚动到位后才启动折起动画。④ **单点信号上两种自适应不一致**:`FitSinglePointCluster` 曾另算"半径留白 + 跨度 1"且把 `fitYToSelection` 置回 false ⇒ 点「适应视图」既改区间又**关掉 Y 轴自适应**;修法:单点簇只负责时间轴(居中 + 一大格 1s),**逐网格幅值轴与常规路径/Y 轴自适应完全同源**(`fitYToSelection` → `AutoRangeLaneToView`,否则 `autoRange=true`,再接 `ApplySinglePointValueMargins`),且**绝不改写** `fitYToSelection`。验证:新增离屏探针 `.preflight/perf/panelanim_main.cpp`(真实 `Application` + `SW_HIDE`)**94/94 通过**(连跑 3 次稳定;覆盖折起/展开的分帧过渡、单调收敛、越靠下越矮、末态精确无残留、筛选计数与独立折起状态、悬浮四个可交互区域全部命中且下层行仍可点、点击后"先滚动到位再折起"、真实 MF4 单点信号两种自适应逐位一致/往返稳定);**A/B** 三组(回退绘图区修复 / 禁用悬浮命中优先 / 动画时长置 0)分别 **4 / 13 / 8 项如实失败**;既有 `verify` **49/49**(顺带修正 4 条与远端新规则不符的过期断言)、`datacheck` **69/69**、`edgecheck` **46/46**;`make release` 零警告。 | | 2026-10-16 | DeepSeek-V4.1-Flash(**另一台设备**) | **动画改「抽拉卷尺」式刚性平移 + 筛选态折叠三角修正 + 样式区「目标」行绘制/布局修正(详见 §9.36)**。① **动画重做**(推翻上一轮的“按行缩放”):整组信号作为**刚性整体**平移,**行高永不变**、只维护一个逐行平移量 `m_rowShift` ⇒ 动画期间**无需重建行集合**(流畅关键);收起时逐条被组名栏吞掉、展开时逐条冒出;中途反向点击**从当前位移续接**不跳变;时长 `clamp(115+6×行数, 150, 320)` ms。② **藏进组名栏的裁剪**:信号行越过组名的**绘制**下边界即裁掉,且除 GDI 裁剪外**必须用 `g.SetClip(..., CombineModeIntersect)` 同时收紧 GDI+ 裁剪**(否则行内色块/复选框/徽标画到组名栏上)。③ **悬浮组名折起改为“一边滚动一边收起”**(推翻“先滚动到位再折起”):`scrollSynced` 让滚动与平移按同一进度推进 ⇒ 组名屏幕位置恒定;结束时滚动偏移恰好落在“组名正好在最上方”,并且**无跳变**;旧 `m_pendingGroupToggle` 机制删除。④ **筛选态折叠三角**:`PaintFileRow` 改读 `IsGroupCollapsed()`(筛选模式用的是独立的 `m_filterCollapsed`),修复“三角维持筛选前状态”。⑤ **「目标」行**:`PaintTargetRow` 误用列表裁剪版 `PaintRowText` ⇒ 名称与 GX 文本被完全裁掉(改用 `UiText`);GX 标签改为**右对齐到内容列右边缘**、名称无标签时占满内容列;`EditFieldRect()` 对目标行只返回名称矩形(否则改名输入框把 GX 标签包进去)。验证:离屏探针 `.preflight/perf/panelanim_main.cpp` **147/147 通过**(连跑 3 次稳定;含逐帧断言组名栏像素零变化、`m_rowsDirty` 恒 false、滚动单调且恰好落位、筛选/普通态折叠三角逐像素 0 差异、目标行文本像素存在与两端对齐);**A/B** 五组(回退筛选三角+非刚性平移 / 回退目标行绘制与布局 / 关闭同步滚动 / 去掉 GDI+ 收紧裁剪 / 不续接位移)分别 **4 / 6 / 6 / 2 / 1 项如实失败**;`verify` **49/49**、`datacheck` **69/69**、`edgecheck` **46/46**;`make clean && make all` 零警告。 | | 2026-10-16 | DeepSeek-V4.1-Flash(**另一台设备**) | **分组折起热区与改名输入框修正 + 数据模型去冗余(单一名称)+ 新增 `Components/Shared` 组件 + 过时注释/命名大清理 + 二进制瘦身 24.6% 并完成多文件拆分(详见 §9.37 与本轮报告 `Documents/refactor_report_2026-10-16.md`)**。① **折起热区**(用户要求):点分组**名称**不再折起,只有**折叠三角**或**「组X」标签**(新增 `RowPart::GROUP_TITLE`)才折起。② **分组改名输入框不再吞掉「N 项」**:`RowNameRect` 右边界改为项数左缘;新增 `GroupLabelText`/`GroupStatsText` 作为文本与宽度的唯一来源。③ **`SignalData`/`SignalGroup` 只留一个名称字段**:删除 `displayName` 与 `Title()`(31 处改用 `name`),`SetSignalDisplayName`/`SetGroupDisplayName` → **`RenameSignal`/`RenameGroup`**(空名一律拒绝);删除死成员 `Application::m_loadingBytes`。④ **新增 `Components/Shared`**(用户要求):从 `PaintUtil` 迁出全部**控件**(图标/复选框/滑块/内联输入框/颜色样块/取值胶囊配色)到 `UiControls.hpp/.cpp`,`PaintUtil` 只留低层原语;依赖方向固定 `Components/* → Components/Shared → Utilities`。⑤ **过时注释/命名**:`文件下标`→`分组下标`、`文件分组`→`分组` 等一批;形参名 `file`→`axis`/`group`;`PaintFileRow`→`PaintGroupRow`、`FileCloseRect`→`GroupCloseRect`、`OpenFileMenu`→`OpenGroupMenu`、`FileGroupLabelWidth`→`GroupLabelWidth`、`StyleEditField::FILE_NAME`→`GROUP_TITLE`;修复 `DataModel.hpp` 两处被压扁的注释块与 `NewTab` 重复文档块。⑥ **多文件拆分(本轮完成)**:`PlotView.cpp`(13 695 行)+ `SignalPanel.cpp` + `Application.cpp` + `DataModel.cpp` 四个大文件全部按功能拆成多个协作文件(私有内部头 + 具名命名空间 + 全 `inline` + 按名字分桶),最大单文件 544 KB → 276 KB。⑦ **二进制瘦身**:真因是 **ICO 里 256×256 未压缩 BMP 条目(270 KB,占整包 27%)**,改为 **PNG 存储**(观感零变化)⇒ `Target.exe` **1 013 248 → 764 416 B(−24.6%)**;实测“拆分不会减小机器码,反而 +1.4%”、`--gc-sections` 反而 +9 KB(已回退)。验证:`panelanim` **156/156**、`verify` **49/49**、`datacheck` **69/69**、`edgecheck` **46/46**;**A/B** 回退两处 UI 修复 **10 项如实失败**;`make release`/`make debug` 零警告。⚠️ **未完成(已如实记录)**:`PlotView.cpp`(13.7 K 行)与其它大文件的多文件化拆分、`LanePanelState` 抽取、模态提示解耦。 | +| 2026-10-16 | DeepSeek-V4.1-Flash(**另一台设备**) | **收尾:`LanePanelState` 抽取 + 错误提示非模态化(把上一行的「未完成」清零,详见 §9.37 第八节与报告 `Documents/refactor_report_2026-10-16.md` §九)**。① **`Lane` 的 12 个平铺面板字段**(`edgePanelOpen/X/Y` × 4 类实验性功能)→ **4 个 `LanePanelState`**(`struct LanePanelState { bool open=true; double x=-1; double y=-1; }`),访问点全部改为 `lane.edgePanel.open` 形式;`RebuildLanes` 的 `LaneCarry` 同步收敛为 4 个 `LanePanelState`(逐字段赋值 → 整体赋值)。⚠️ 教训:**批量改名脚本不能对「声明」与「使用」跑同一组替换对**(会生成 `bool edgePanel.open = true;` 非法声明),声明处须单独重写。② **错误提示与模态阻塞解耦**:新增 `Application::SetModalErrorsEnabled(bool)`/`ModalErrorsEnabled()`(成员默认 `true`),`Initialize` **最先**执行 `SetModalErrorsEnabled(showCommand != SW_HIDE)`(隐藏窗口弹模态框 = 进程被钉死);`SetStatusMessage(err,true)` 允许模态时原样 `MessageBoxW`,否则走 `m_plot.ShowPersistentHint(text)`(绘图区底部非模态浮层);`PlotView` 新增公开 `ShowPersistentHint`。普通提示/空文本仍被忽略 ⇒ **交互行为不变**。③ **顺带修掉真实缺陷**:`PlotView::Paint` 在 `m_lanes.empty()` 时提前 `return`,提示浮层在其后绘制 ⇒ **未勾选任何信号时错误永远不可见**(正是"加载失败"场景),该分支补一次 `PaintHint(g)`。④ **新增探针 `.preflight/perf/errcheck` 19/19**:默认值/隐藏降级/宿主开关、**工作线程 + 5s 超时**判"不阻塞"(旧行为会永久阻塞 ⇒ 判失败并 `ExitProcess`,探针不挂)、`EnumWindows` 数本进程 `#32770` 顶层窗口数不变、提示文本/粘滞/需重绘/起始时刻、**像素级**"有/无提示"差异 3781 且全帧差异同为 3781。验证:`make release`/`make debug` 零警告,`Target.exe` **764 416 B**;`verify` 49/49、`datacheck` 69/69、`edgecheck` 46/46、`panelanim` 156/156。 | --- @@ -2688,6 +2721,23 @@ Components/Shared/Src/UiControls.cpp 实测 `PlotView` 拆分 +5 120 B、四个文件全部拆完 +10 240 B(共 +1.4%)。 这是**可维护性换体积**的有意取舍;若日后必须压体积,优先考虑把工具函数收敛到单个 TU (用 `extern` 声明 + 单一实现)而不是撤销拆分。 +93. ⚠️⚠️ **批量改名脚本不能同时覆盖「声明」与「使用」**(`LanePanelState` 抽取时踩到): + 把 `edgePanelOpen → edgePanel.open` 这类「聚合化」替换对同时用在 `Lane`(使用处)和 + `RebuildLanes` 内部的 `LaneCarry`(**声明处**)上,会生成 + `bool edgePanel.open = true;` / `double edgePanel.x = -1.0;` 这类非法声明 + (报错为 `expected ';' at end of member declaration`)。 + 正确做法:**声明处单独重写**(改为 4 个聚合成员),只让「使用处」走替换对; + 同一个替换对**不要**在同一个文件上跑第二遍(会二次展开,例如 `a.b` → `a.b.b`)。 +94. ⚠️⚠️ **任何「宿主级反馈通道」都必须做像素级验证**(本轮实测): + `PlotView::Paint` 在 `m_lanes.empty()` 时**提前 return**,而提示浮层在 return 之后的叠加层绘制 ⇒ + **一个信号都没勾选时任何非模态错误都不可见**(恰恰是「文件加载失败」最需要提示的场景)。 + 「通道存在」(`m_hintText` 被赋值、`HintNeedsAnimation()` 为真)**不等于「通道可见」**: + 必须抓后备缓冲、比较「有提示 / 无提示」两帧的差异像素(本例 3781 像素,且全帧差异同为 3781)。 + 同理:模态对话框解耦要能**自动判定**(工作线程 + 超时,旧行为会永久阻塞 ⇒ 判失败并 `ExitProcess`, + 探针不会挂住;再用 `EnumWindows` 数本进程 `#32770` 顶层窗口数量证明没弹框)。 +95. ⚠️ **模态反馈必须是可关闭的**:`Application::SetModalErrorsEnabled` 默认 `true`, + `Initialize(instance, showCommand)` 按 `showCommand != SW_HIDE` **自动降级**为 + `m_plot.ShowPersistentHint(text)`;隐藏窗口下弹模态框没人能点掉 = 进程被钉死。 71. ⚠️ **`FitAll()` 在没有任何可见信号时不得置位 `Tab().ViewInitialized()`**: 否则用户先连点几次「适应视图」(此时无信号被勾选)再勾选第一个信号, `Application::SynchronizeView` 会**跳过初次自适应**,视界停在“无信号”那次的整段范围上, diff --git a/Documents/refactor_report_2026-10-16.md b/Documents/refactor_report_2026-10-16.md index 1fb9bca..76c6ea7 100644 --- a/Documents/refactor_report_2026-10-16.md +++ b/Documents/refactor_report_2026-10-16.md @@ -68,7 +68,7 @@ > 其余结构体(`SignalStyle` / `TabConfig` / `Lane` / `TimeAxis` / `SignalRef` / `EdgeScanState` 等) > 逐字段核对后未发现同类“同义多字段”现象;`Lane` 的“体量过大”属**内聚性**问题而非冗余, -> 见 §五 的 `LanePanelState` 建议。 +> 已按 §7.3 / §9.1 抽出 `LanePanelState`(本轮完成)。 --- @@ -263,16 +263,16 @@ Components/*(功能组件) ──▶ Components/Shared(UI 控件) ─ 组件文件数(拆分后):`PlotView` 6、`Application` 5、`SignalPanel` 5、`Shared` 2、`TabBar` 2、`Toolbar` 2、`Main` 2。 -### 7.3 `Lane` 的内聚性(上一轮建议 5) +### 7.3 `Lane` 的内聚性 —— **已完成(本轮补齐,见 §九)** -`Lane` 内含 4 类实验性功能的状态 + 各自的面板坐标,已超 200 行;建议抽出 -`LanePanelState`(开关 + 每信号参数 + 面板位置),`Lane` 只保留结构与归属。 -属**结构内聚**重构而非去冗余,需同步改 `DataModel` 与 `PlotView` 的多处访问点,本轮未做。 +`Lane` 内含 4 类实验性功能的状态 + 各自的面板坐标(12 个平铺字段);已抽出 +`LanePanelState { bool open; double x; double y; }`,`Lane` 只保留「开关 + 每信号参数 + 面板状态」 +四项成员(`edgePanel` / `edgeCenterPanel` / `meanFilterPanel` / `weakPeakPanel`)。 -### 7.4 错误提示与模态阻塞解耦(上一轮建议 1) +### 7.4 错误提示与模态阻塞解耦 —— **已完成(本轮补齐,见 §九)** -`SetStatusMessage(..., isError=true)` 仍直接 `MessageBoxW`;自动化 / 离屏场景与其耦合, -建议改为宿主级非模态提示。本轮未做(与“不影响功能”的约束存在交互设计决策,需用户拍板样式)。 +`SetStatusMessage(..., isError=true)` 不再是「无条件 `MessageBoxW`」:模态与否由宿主决定 +(`Application::SetModalErrorsEnabled`),隐藏窗口启动时**自动**降级为绘图区非模态提示。 ### 7.5 MF4 压缩块 / 数据列表(上一轮建议 2–3) @@ -318,3 +318,92 @@ Components/*(功能组件) ──▶ Components/Shared(UI 控件) ─ * `Documents/agent_mem.md` 同步更新(§9.37 + §11.2 约束 + 变更日志)。 * 上一轮报告 `Documents/refactor_report_2026-10-15.md` **已删除**(其有效内容已并入 `agent_mem.md` 与本文)。 + +--- + +## 九、收尾补记(同一轮次的第二遍:把「仍未完成」清空) + +上一版报告在 §7.3 / §7.4 留下两项「本轮未做」。这一遍把它们做完,并在过程中又发现并修掉 +一个**真实缺陷**。所有改动都是「结构内聚 + 反馈通道」类,**不改任何功能性行为**。 + +### 9.1 `LanePanelState`:`Lane` 的 12 个平铺面板字段 → 4 个聚合成员 + +```cpp +// Utilities/Inc/DataModel.hpp +struct LanePanelState +{ + bool open = true; /**< 浮动面板是否展开。 */ + double x = -1.0; /**< 面板左上角 X(-1 表示尚未定位,首次绘制时自动摆放)。 */ + double y = -1.0; /**< 面板左上角 Y。 */ +}; +``` + +* `Lane` 的 `edgePanelOpen/edgePanelX/edgePanelY` 等 **12 个字段**被 + `LanePanelState edgePanel / edgeCenterPanel / meanFilterPanel / weakPeakPanel` 取代; + 访问点全部改为 `lane.edgePanel.open` 这样的**结构内聚**形式(`DataModelTab.cpp` 的快照拷贝、 + `PlotViewInteraction.cpp` 的浮动面板拖拽、`PlotViewPaint.cpp` 的面板层序绘制)。 +* `RebuildLanes` 内部的「网格自身状态携带」结构 `LaneCarry` 同步收敛:原来 12 个平铺字段 + 改为 4 个 `LanePanelState`,逐字段赋值也简化为整体赋值(少 8 行、少 8 个易漏字段)。 +* **收益**:参数的归属一眼可辨(哪个坐标属于哪个功能)、新增一类实验性功能时只需加 1 个成员; + 同时把「3 个字段必须一起改」的隐式约束变成类型约束。 +* ⚠️ 教训(已写进 `agent_mem.md`):批量改名脚本**不能**在「声明」与「使用」上跑同一组替换对 —— + 对 `Lane` 使用的 `edgePanelOpen → edgePanel.open` 用在 `LaneCarry` 的**声明行**上会得到 + `bool edgePanel.open = true;` 这种非法声明。修正方式是**声明处单独重写**(4 个聚合成员), + 只让「使用处」走替换对。 + +### 9.2 错误反馈:模态对话框 → 可关闭(隐藏窗口自动降级为非模态提示) + +* `Application` 新增 `SetModalErrorsEnabled(bool)` / `ModalErrorsEnabled()`, + 成员 `m_modalErrorsEnabled` **默认 `true`**(保持交互场景的既有行为); + `Initialize(instance, showCommand)` 在**最先**执行 `SetModalErrorsEnabled(showCommand != SW_HIDE)`。 + 理由:隐藏窗口(自动化/离屏)下弹模态框**没有人能点掉**,等于把进程钉死。 +* `SetStatusMessage(text, /*isError*/true)` 分支化: + 允许模态 → 原样 `MessageBoxW`;不允许 → `m_plot.ShowPersistentHint(text)`(绘图区底部浮层)。 + 普通提示(`isError == false`)与空文本依旧被忽略 —— **交互体验与此前完全一致**。 +* `PlotView` 新增公开接口 `ShowPersistentHint(text)`(内部转 `SetHintSticky`), + 把「私有提示机制」变成「宿主可用的反馈通道」,且不暴露粘滞/淡出等内部细节。 +* **顺带修掉一个真实缺陷**:`PlotView::Paint` 在 `m_lanes.empty()` 时**提前 return**, + 而提示浮层在 return 之后的叠加层里绘制 ⇒ **「一个信号都没勾选」时任何错误都显示不出来**, + 恰恰是「文件加载失败」这一最需要提示的场景。修法是在该分支补一次 `PaintHint(g)`。 + 这一条是**探针先失败、才回头发现的**(见 9.3),说明「非模态通道」本身也需要像素级验证。 + +### 9.3 新增离屏探针 `.preflight/perf/errcheck`(19/19) + +模态/非模态无法用「截图看着对」来判断,因此写成可自动判定的断言: + +| 断言组 | 内容 | +| --- | --- | +| 默认与降级 | 默认 `ModalErrorsEnabled() == true`;`Initialize(..., SW_HIDE)` 后为 `false`;宿主可显式开关 | +| 不被打断 | 从**工作线程**调用 `SetStatusMessage(err,true)`,5 s 内必须返回(旧行为会永久阻塞 ⇒ 直接判失败并 `ExitProcess`,探针**不会挂住**) | +| 不产生对话框 | 用 `EnumWindows` 统计**本进程**中类名 `#32770` 的顶层窗口,调用前后数量不变 | +| 走到提示通道 | `m_hintText` 等于错误文本、`m_hintSticky == true`、`HintNeedsAnimation() == true`、`m_hintShownAt != 0` | +| **真的画出来** | 「有提示」帧与「无提示」帧在绘图区矩形内的差异像素 **3781**,且全帧差异 3781(说明差异**只**来自提示) | +| 提示不吞消息 | 成功提示 / 空文本错误不进入提示通道;第二条错误顶掉第一条 | + +### 9.4 本轮验证(全部离屏,`SW_HIDE` + 无输入注入 + `BELOW_NORMAL`) + +| 项目 | 结果 | +| --- | --- | +| `make release` / `make debug` | 通过,**零警告** | +| 产物体积 | `Build/Release/Target.exe` **764 416 B**(与拆分后一致;本轮是纯结构改动,未引入新代码膨胀) | +| `.preflight/perf/verify`(界面与像素) | **49/49** | +| `.preflight/perf/datacheck`(数据层) | **69/69** | +| `.preflight/perf/edgecheck`(边沿统计) | **46/46** | +| `.preflight/perf/panelanim`(分组折起动画 + 两处 UI 修复) | **156/156** | +| `.preflight/perf/errcheck`(本轮新增) | **19/19** | +| 上一遍的 A/B(回退两处 UI 修复) | 如实失败 **9** 项(失败文案逐条点名「点组名不应折起」「改名框不应包住条目计数」),恢复后 156/156 | + +### 9.5 本轮变更清单 + +``` +修改 Utilities/Inc/DataModel.hpp 新增 LanePanelState;Lane 的 12 个平铺面板字段 → 4 个聚合成员 +修改 Utilities/Src/DataModelInternal.hpp LaneCarry 同步收敛为 4 个 LanePanelState(含整体赋值简化) +修改 Utilities/Src/DataModelTab.cpp 面板状态快照拷贝改用 .open/.x/.y +修改 Components/PlotView/Src/PlotViewInteraction.cpp 浮动面板拖拽改用 .open/.x/.y +修改 Components/PlotView/Src/PlotViewPaint.cpp 面板层序绘制同改;空状态分支补 PaintHint(缺陷修复) +修改 Components/PlotView/Inc/PlotView.hpp 新增公开 ShowPersistentHint +修改 Components/PlotView/Src/PlotViewFeatures.cpp 实现 ShowPersistentHint +修改 Components/Application/Inc/Application.hpp SetModalErrorsEnabled / ModalErrorsEnabled / m_modalErrorsEnabled +修改 Components/Application/Src/Application.cpp Initialize 按 showCommand 自动降级;SetStatusMessage 分支化 +新增 .preflight/perf/errcheck_main.cpp 模态/非模态解耦探针(gitignored,不随产物提交) +``` diff --git a/Utilities/Inc/DataModel.hpp b/Utilities/Inc/DataModel.hpp index b70161b..4eede8e 100644 --- a/Utilities/Inc/DataModel.hpp +++ b/Utilities/Inc/DataModel.hpp @@ -701,6 +701,20 @@ namespace jdi SignalRef m_selected; /**< 当前选中信号(跨标签页共享)。 */ }; + /** + * @brief 一个实验性功能浮动面板的 UI 状态(展开开关 + 左上角位置)。 + * + * 四类实验性功能(边沿计数 / 边沿计数(中心轴)/ 均值滤波 / 弱峰调整)各自持有一个, + * 因此 `Lane` 里不必再重复 12 个(4×3)同形字段 —— 这属于「同一含义被拆成多份」的冗余。 + * 面板的**每信号参数**放在 `Lane::*Items` 里,与这里的位置/展开状态分开。 + */ + struct LanePanelState + { + bool open = true; /**< 面板是否展开(点击面板上的 ✕ 后置 false)。 */ + double x = -1.0; /**< 面板左上角横坐标(客户区像素;<0 表示自动定位)。 */ + double y = -1.0; /**< 面板左上角纵坐标(<0 表示自动定位)。 */ + }; + /** * @brief 一个波形网格(纵向泳道),可容纳多条信号叠加显示。 */ @@ -769,14 +783,8 @@ namespace jdi /** `edgeValue` 是否已经初始化(首次开启时取可见幅值范围的中点)。 */ bool edgeValueValid = false; - /** 边沿统计浮动面板是否展开(点击面板上的 ✕ 后置 false)。 */ - bool edgePanelOpen = true; - - /** 统计面板左上角位置(客户区像素;<0 表示自动定位到绘图区右侧)。 */ - double edgePanelX = -1.0; - - /** 统计面板左上角纵坐标(<0 表示自动定位)。 */ - double edgePanelY = -1.0; + /** 边沿计数浮动面板的 UI 状态(展开 + 位置)。 */ + LanePanelState edgePanel; /** * @brief 「实验性功能 → 边沿计数(中心轴)」开关。 @@ -810,14 +818,8 @@ namespace jdi return FindEdgeCenterItem(ref) != nullptr; } - /** 中心轴统计浮动面板是否展开。 */ - bool edgeCenterPanelOpen = true; - - /** 中心轴统计面板左上角位置(客户区像素;<0 表示自动定位)。 */ - double edgeCenterPanelX = -1.0; - - /** 中心轴统计面板左上角纵坐标(<0 表示自动定位)。 */ - double edgeCenterPanelY = -1.0; + /** 中心轴边沿计数浮动面板的 UI 状态(展开 + 位置)。 */ + LanePanelState edgeCenterPanel; /** * @brief 「实验性功能 → 均值滤波」开关。 @@ -850,14 +852,8 @@ namespace jdi return FindMeanFilterItem(ref) != nullptr; } - /** 均值滤波浮动面板是否展开。 */ - bool meanFilterPanelOpen = true; - - /** 均值滤波面板左上角位置(客户区像素;<0 表示自动定位)。 */ - double meanFilterPanelX = -1.0; - - /** 均值滤波面板左上角纵坐标(<0 表示自动定位)。 */ - double meanFilterPanelY = -1.0; + /** 均值滤波浮动面板的 UI 状态(展开 + 位置)。 */ + LanePanelState meanFilterPanel; /** * @brief 「实验性功能 → 弱峰调整」开关。 @@ -892,14 +888,8 @@ namespace jdi return FindWeakPeakItem(ref) != nullptr; } - /** 弱峰调整浮动面板是否展开。 */ - bool weakPeakPanelOpen = true; - - /** 弱峰调整面板左上角位置(客户区像素;<0 表示自动定位)。 */ - double weakPeakPanelX = -1.0; - - /** 弱峰调整面板左上角纵坐标(<0 表示自动定位)。 */ - double weakPeakPanelY = -1.0; + /** 弱峰调整浮动面板的 UI 状态(展开 + 位置)。 */ + LanePanelState weakPeakPanel; }; /** diff --git a/Utilities/Src/DataModelInternal.hpp b/Utilities/Src/DataModelInternal.hpp index a088325..45f9b84 100644 --- a/Utilities/Src/DataModelInternal.hpp +++ b/Utilities/Src/DataModelInternal.hpp @@ -78,24 +78,16 @@ namespace jdi bool edgeCount = false; double edgeValue = 0.0; bool edgeValueValid = false; - bool edgePanelOpen = true; - double edgePanelX = -1.0; - double edgePanelY = -1.0; + LanePanelState edgePanel; bool edgeCenter = false; std::vector edgeCenterItems; - bool edgeCenterPanelOpen = true; - double edgeCenterPanelX = -1.0; - double edgeCenterPanelY = -1.0; + LanePanelState edgeCenterPanel; bool meanFilter = false; std::vector meanFilterItems; - bool meanFilterPanelOpen = true; - double meanFilterPanelX = -1.0; - double meanFilterPanelY = -1.0; + LanePanelState meanFilterPanel; bool weakPeak = false; std::vector weakPeakItems; - bool weakPeakPanelOpen = true; - double weakPeakPanelX = -1.0; - double weakPeakPanelY = -1.0; + LanePanelState weakPeakPanel; }; // 先记下每个信号原来所在网格的网格自身状态: @@ -123,24 +115,16 @@ namespace jdi carry.edgeCount = lane.edgeCount; carry.edgeValue = lane.edgeValue; carry.edgeValueValid = lane.edgeValueValid; - carry.edgePanelOpen = lane.edgePanelOpen; - carry.edgePanelX = lane.edgePanelX; - carry.edgePanelY = lane.edgePanelY; + carry.edgePanel = lane.edgePanel; carry.edgeCenter = lane.edgeCenter; carry.edgeCenterItems = lane.edgeCenterItems; - carry.edgeCenterPanelOpen = lane.edgeCenterPanelOpen; - carry.edgeCenterPanelX = lane.edgeCenterPanelX; - carry.edgeCenterPanelY = lane.edgeCenterPanelY; + carry.edgeCenterPanel = lane.edgeCenterPanel; carry.meanFilter = lane.meanFilter; carry.meanFilterItems = lane.meanFilterItems; - carry.meanFilterPanelOpen = lane.meanFilterPanelOpen; - carry.meanFilterPanelX = lane.meanFilterPanelX; - carry.meanFilterPanelY = lane.meanFilterPanelY; + carry.meanFilterPanel = lane.meanFilterPanel; carry.weakPeak = lane.weakPeak; carry.weakPeakItems = lane.weakPeakItems; - carry.weakPeakPanelOpen = lane.weakPeakPanelOpen; - carry.weakPeakPanelX = lane.weakPeakPanelX; - carry.weakPeakPanelY = lane.weakPeakPanelY; + carry.weakPeakPanel = lane.weakPeakPanel; for (const SignalRef& ref : lane.signals) { previousCarry.emplace_back(ref, carry); @@ -165,24 +149,24 @@ namespace jdi lane.edgeCount = carry.edgeCount; lane.edgeValue = carry.edgeValue; lane.edgeValueValid = carry.edgeValueValid; - lane.edgePanelOpen = carry.edgePanelOpen; - lane.edgePanelX = carry.edgePanelX; - lane.edgePanelY = carry.edgePanelY; + lane.edgePanel.open = carry.edgePanel.open; + lane.edgePanel.x = carry.edgePanel.x; + lane.edgePanel.y = carry.edgePanel.y; lane.edgeCenter = carry.edgeCenter; lane.edgeCenterItems = carry.edgeCenterItems; - lane.edgeCenterPanelOpen = carry.edgeCenterPanelOpen; - lane.edgeCenterPanelX = carry.edgeCenterPanelX; - lane.edgeCenterPanelY = carry.edgeCenterPanelY; + lane.edgeCenterPanel.open = carry.edgeCenterPanel.open; + lane.edgeCenterPanel.x = carry.edgeCenterPanel.x; + lane.edgeCenterPanel.y = carry.edgeCenterPanel.y; lane.meanFilter = carry.meanFilter; lane.meanFilterItems = carry.meanFilterItems; - lane.meanFilterPanelOpen = carry.meanFilterPanelOpen; - lane.meanFilterPanelX = carry.meanFilterPanelX; - lane.meanFilterPanelY = carry.meanFilterPanelY; + lane.meanFilterPanel.open = carry.meanFilterPanel.open; + lane.meanFilterPanel.x = carry.meanFilterPanel.x; + lane.meanFilterPanel.y = carry.meanFilterPanel.y; lane.weakPeak = carry.weakPeak; lane.weakPeakItems = carry.weakPeakItems; - lane.weakPeakPanelOpen = carry.weakPeakPanelOpen; - lane.weakPeakPanelX = carry.weakPeakPanelX; - lane.weakPeakPanelY = carry.weakPeakPanelY; + lane.weakPeakPanel.open = carry.weakPeakPanel.open; + lane.weakPeakPanel.x = carry.weakPeakPanel.x; + lane.weakPeakPanel.y = carry.weakPeakPanel.y; }; // 收集全部可见信号引用。 diff --git a/Utilities/Src/DataModelTab.cpp b/Utilities/Src/DataModelTab.cpp index 8d37bfe..44e77ab 100644 --- a/Utilities/Src/DataModelTab.cpp +++ b/Utilities/Src/DataModelTab.cpp @@ -429,21 +429,21 @@ namespace jdi lane.edgeCount = source.edgeCount; lane.edgeValue = source.edgeValue; lane.edgeValueValid = source.edgeValueValid; - lane.edgePanelOpen = source.edgePanelOpen; - lane.edgePanelX = source.edgePanelX; - lane.edgePanelY = source.edgePanelY; + lane.edgePanel.open = source.edgePanel.open; + lane.edgePanel.x = source.edgePanel.x; + lane.edgePanel.y = source.edgePanel.y; lane.edgeCenter = source.edgeCenter; - lane.edgeCenterPanelOpen = source.edgeCenterPanelOpen; - lane.edgeCenterPanelX = source.edgeCenterPanelX; - lane.edgeCenterPanelY = source.edgeCenterPanelY; + lane.edgeCenterPanel.open = source.edgeCenterPanel.open; + lane.edgeCenterPanel.x = source.edgeCenterPanel.x; + lane.edgeCenterPanel.y = source.edgeCenterPanel.y; lane.weakPeak = source.weakPeak; - lane.weakPeakPanelOpen = source.weakPeakPanelOpen; - lane.weakPeakPanelX = source.weakPeakPanelX; - lane.weakPeakPanelY = source.weakPeakPanelY; + lane.weakPeakPanel.open = source.weakPeakPanel.open; + lane.weakPeakPanel.x = source.weakPeakPanel.x; + lane.weakPeakPanel.y = source.weakPeakPanel.y; lane.meanFilter = source.meanFilter; - lane.meanFilterPanelOpen = source.meanFilterPanelOpen; - lane.meanFilterPanelX = source.meanFilterPanelX; - lane.meanFilterPanelY = source.meanFilterPanelY; + lane.meanFilterPanel.open = source.meanFilterPanel.open; + lane.meanFilterPanel.x = source.meanFilterPanel.x; + lane.meanFilterPanel.y = source.meanFilterPanel.y; // 拆分后每个子网格只保留自己那个信号的均值滤波 / 中心轴 / 弱峰调整条目。 lane.meanFilterItems.clear(); lane.edgeCenterItems.clear();