diff --git a/Components/PlotView/Inc/PlotViewInternal.hpp b/Components/PlotView/Inc/PlotViewInternal.hpp index c450220..cacc637 100644 --- a/Components/PlotView/Inc/PlotViewInternal.hpp +++ b/Components/PlotView/Inc/PlotViewInternal.hpp @@ -1081,6 +1081,103 @@ namespace jdi /** 实验性功能浮动面板的种类数(遍历与命中测试统一用它,避免新增功能时漏改)。 */ inline const int kEdgePanelKindCount = static_cast(sizeof(kEdgePanelKinds) / sizeof(kEdgePanelKinds[0])); + // ---- 面板状态 / 滚动量的**唯一派发点** -------------------------------------- + // + // ⚠️⚠️ 为什么必须有这一组函数:`Lane` 里每种实验性功能各有一份 + // `LanePanelState` 与滚动偏移(7 种 × 2 = 14 个字段)。旧实现在**每个**需要 + // 按种类取用这些字段的地方各写一遍多层三元式或 `switch`: + // * `EdgePanelLayoutFor` 的 `enabled / open / collapsed` 各一条 7 层嵌套三元式 + // (历史多次因为“多/少一个右括号”编译失败); + // * 标题栏拖拽、✕ 关闭、滚轮滚动、折叠切换、缓存键……各自再写一遍。 + // 新增一种面板要改 20+ 处,漏一处就是“面板拖不动 / 关不掉 / 滚轮滚不到底” + //(`MARK`、`RESAMPLE`、`CLIP` 都真实踩过)。 + // + // 现在这些字段只在这一组函数里按种类分派;**调用方一律用它**。 + // ⚠️ 这些 `switch` **不得写 `default:`** —— 让 `-Wswitch` 在新增 + // `EdgePanelKind` 而漏登记时直接报警(编译期保护,0 运行时代价)。 + + /** @brief 取某种面板的 UI 状态(展开/折起/位置)——可写版本。 */ + inline LanePanelState& LanePanelStateOf(Lane& lane, EdgePanelKind kind) + { + switch (kind) + { + case EdgePanelKind::COUNT: + return lane.edgePanel; + case EdgePanelKind::CENTER: + return lane.edgeCenterPanel; + case EdgePanelKind::MEAN: + return lane.meanFilterPanel; + case EdgePanelKind::PEAK: + return lane.weakPeakPanel; + case EdgePanelKind::MARK: + return lane.freeMarkerPanel; + case EdgePanelKind::RESAMPLE: + return lane.resamplePanel; + case EdgePanelKind::CLIP: + return lane.clipPanel; + } + // 枚举值越界(只可能来自非法强制转换):返回一个安全的占位对象。 + return lane.edgePanel; + } + + /** @brief 取某种面板的 UI 状态——只读版本。 */ + inline const LanePanelState& LanePanelStateOf(const Lane& lane, EdgePanelKind kind) + { + return LanePanelStateOf(const_cast(lane), kind); + } + + /** @brief 取某种面板的**内容滚动偏移**(客户区像素)——可写版本。 */ + inline float& LanePanelScrollOf(Lane& lane, EdgePanelKind kind) + { + switch (kind) + { + case EdgePanelKind::COUNT: + return lane.edgeCountScroll; + case EdgePanelKind::CENTER: + return lane.edgeCenterScroll; + case EdgePanelKind::MEAN: + return lane.meanFilterScroll; + case EdgePanelKind::PEAK: + return lane.weakPeakScroll; + case EdgePanelKind::MARK: + return lane.freeMarkerScroll; + case EdgePanelKind::RESAMPLE: + return lane.resampleScroll; + case EdgePanelKind::CLIP: + return lane.clipScroll; + } + return lane.meanFilterScroll; + } + + /** @brief 取某种面板的**内容滚动偏移**——只读版本。 */ + inline const float& LanePanelScrollOf(const Lane& lane, EdgePanelKind kind) + { + return LanePanelScrollOf(const_cast(lane), kind); + } + + /** @brief 该面板对应的功能是否已启用(右键菜单里的开关)。 */ + inline bool LanePanelEnabled(const Lane& lane, EdgePanelKind kind) + { + switch (kind) + { + case EdgePanelKind::COUNT: + return lane.edgeCount; + case EdgePanelKind::CENTER: + return lane.edgeCenter; + case EdgePanelKind::MEAN: + return lane.meanFilter; + case EdgePanelKind::PEAK: + return lane.weakPeak; + case EdgePanelKind::MARK: + return lane.freeMarker; + case EdgePanelKind::RESAMPLE: + return lane.resample; + case EdgePanelKind::CLIP: + return lane.clip; + } + return false; + } + /** * @brief 计算一条派生曲线在给定采样窗口内的取值(同步路径与后台线程共用)。 * diff --git a/Components/PlotView/Src/PlotViewFeatures.cpp b/Components/PlotView/Src/PlotViewFeatures.cpp index bb6b4f1..f9604ab 100644 --- a/Components/PlotView/Src/PlotViewFeatures.cpp +++ b/Components/PlotView/Src/PlotViewFeatures.cpp @@ -2186,38 +2186,13 @@ namespace jdi return false; } Lane& lane = m_tab->Lanes()[static_cast(laneIndex)]; - LanePanelState* state = nullptr; - switch (kind) - { - case EdgePanelKind::COUNT: - state = &lane.edgePanel; - break; - case EdgePanelKind::CENTER: - state = &lane.edgeCenterPanel; - break; - case EdgePanelKind::MEAN: - state = &lane.meanFilterPanel; - break; - case EdgePanelKind::PEAK: - state = &lane.weakPeakPanel; - break; - case EdgePanelKind::MARK: - state = &lane.freeMarkerPanel; - break; - case EdgePanelKind::RESAMPLE: - state = &lane.resamplePanel; - break; - case EdgePanelKind::CLIP: - state = &lane.clipPanel; - break; - default: - return false; - } - if (state->collapsed == collapsed) + // ⚠️ 面板状态字段的唯一派发点(见 `LanePanelStateOf` 的说明)。 + LanePanelState& state = LanePanelStateOf(lane, kind); + if (state.collapsed == collapsed) { return false; } - state->collapsed = collapsed; + state.collapsed = collapsed; return true; } @@ -2228,25 +2203,7 @@ namespace jdi return false; } const Lane& lane = m_tab->Lanes()[static_cast(laneIndex)]; - switch (kind) - { - case EdgePanelKind::COUNT: - return lane.edgePanel.collapsed; - case EdgePanelKind::CENTER: - return lane.edgeCenterPanel.collapsed; - case EdgePanelKind::MEAN: - return lane.meanFilterPanel.collapsed; - case EdgePanelKind::PEAK: - return lane.weakPeakPanel.collapsed; - case EdgePanelKind::MARK: - return lane.freeMarkerPanel.collapsed; - case EdgePanelKind::RESAMPLE: - return lane.resamplePanel.collapsed; - case EdgePanelKind::CLIP: - return lane.clipPanel.collapsed; - default: - return false; - } + return LanePanelStateOf(lane, kind).collapsed; } bool PlotView::ToggleLanePanelCollapsed(int laneIndex, EdgePanelKind kind) diff --git a/Components/PlotView/Src/PlotViewInteraction.cpp b/Components/PlotView/Src/PlotViewInteraction.cpp index 6088fcd..e3782dc 100644 --- a/Components/PlotView/Src/PlotViewInteraction.cpp +++ b/Components/PlotView/Src/PlotViewInteraction.cpp @@ -1736,42 +1736,12 @@ namespace jdi top = std::max(m_plotRect.Y + 2.0f * m_scale, std::min(top, m_plotRect.Y + m_plotRect.Height - panel.Height - 2.0f * m_scale)); Lane& lane = m_tab->Lanes()[static_cast(m_edgePanelDragLane)]; - switch (m_edgePanelDragKind) - { - case EdgePanelKind::CENTER: - lane.edgeCenterPanel.x = left; - lane.edgeCenterPanel.y = top; - break; - case EdgePanelKind::MEAN: - lane.meanFilterPanel.x = left; - lane.meanFilterPanel.y = top; - break; - case EdgePanelKind::PEAK: - lane.weakPeakPanel.x = left; - lane.weakPeakPanel.y = top; - break; - case EdgePanelKind::MARK: - // ⚠️ 漏掉这一支会让「自由标记」面板**拖不动**(位置写进了 edgePanel, - // 下一帧又从 freeMarkerPanel 读到旧值 ⇒ 面板纹丝不动)。 - lane.freeMarkerPanel.x = left; - lane.freeMarkerPanel.y = top; - break; - case EdgePanelKind::RESAMPLE: - // ⚠️ 同类缺陷:漏掉这一支会让「重采样」面板**拖不动**(用户 2026-11-03 报告)。 - // 位置必须写进它自己的 `resamplePanel`,布局也只从那里读。 - lane.resamplePanel.x = left; - lane.resamplePanel.y = top; - break; - case EdgePanelKind::CLIP: - lane.clipPanel.x = left; - lane.clipPanel.y = top; - break; - case EdgePanelKind::COUNT: - default: - lane.edgePanel.x = left; - lane.edgePanel.y = top; - break; - } + // ⚠️ 面板位置必须写进**它自己的** `LanePanelState`(见 `LanePanelStateOf` 的说明): + // 漏写会让面板“纹丝不动”(位置写进别的面板、布局又从自己那里读)。 + // 历史上 `MARK` 与 `RESAMPLE` 都踩过这个坑,因此这里只保留唯一派发点。 + LanePanelState& dragState = LanePanelStateOf(lane, m_edgePanelDragKind); + dragState.x = left; + dragState.y = top; m_dragMoved = true; return true; } @@ -1850,6 +1820,9 @@ namespace jdi const float ratio = (thumbTop - layout.scrollTrack.Y) / travel; const float target = ratio * maxScroll; bool scrolled = false; + // ⚠️ 穷举所有面板种类且**不写 `default:`**:将来新增面板而忘登记时, + // `-Wswitch`(属 `-Wall`)会直接报错,而不是静默落到其它面板上。 + // (「自由标记」在上面的独立分支处理,不会到达这里。) switch (m_meanDragKind) { case EdgePanelKind::COUNT: @@ -1858,18 +1831,20 @@ namespace jdi case EdgePanelKind::CENTER: scrolled = SetLaneEdgeCenterScroll(m_markerScrollDragLane, target); break; + case EdgePanelKind::MEAN: + scrolled = SetLaneMeanFilterScroll(m_markerScrollDragLane, target); + break; case EdgePanelKind::PEAK: scrolled = SetLaneWeakPeakScroll(m_markerScrollDragLane, target); break; + case EdgePanelKind::MARK: + break; case EdgePanelKind::RESAMPLE: scrolled = SetLaneResampleScroll(m_markerScrollDragLane, target); break; case EdgePanelKind::CLIP: scrolled = SetLaneClipScroll(m_markerScrollDragLane, target); break; - default: - scrolled = SetLaneMeanFilterScroll(m_markerScrollDragLane, target); - break; } if (scrolled) { @@ -3419,8 +3394,12 @@ namespace jdi if (layout.close.Width > 0.0f && UiPointInRect(layout.close, static_cast(x), static_cast(y))) { + // ⚠️ 穷举所有面板种类且**不写 `default:`**(理由同上:漏登记时编译器报警)。 switch (kind) { + case EdgePanelKind::COUNT: + SetLaneEdgeCount(lane, false); + break; case EdgePanelKind::CENTER: SetLaneEdgeCenterCount(lane, false); break; @@ -3434,17 +3413,11 @@ namespace jdi SetLaneFreeMarker(lane, false); break; case EdgePanelKind::RESAMPLE: - // ⚠️ 漏掉这一支时 ✕ 点了没反应(落进 `default` 关掉的是「边沿计数」), - // 用户只能改走右键菜单取消功能(用户 2026-11-03 报告)。 SetLaneResample(lane, false); break; case EdgePanelKind::CLIP: SetLaneClip(lane, false); break; - case EdgePanelKind::COUNT: - default: - SetLaneEdgeCount(lane, false); - break; } changed = true; } @@ -3569,11 +3542,11 @@ namespace jdi panelLane, panelKind)) { const EdgePanelKind hoveredKind = static_cast(panelKind); - // 「均值滤波 / 中心轴边沿计数 / 弱峰调整 / 边沿计数」面板:内容区超高时用滚轮翻页 - //(上百个信号合并时必备)。 - if (hoveredKind == EdgePanelKind::MEAN || hoveredKind == EdgePanelKind::CENTER || - hoveredKind == EdgePanelKind::PEAK || hoveredKind == EdgePanelKind::COUNT || - hoveredKind == EdgePanelKind::RESAMPLE || hoveredKind == EdgePanelKind::CLIP) + // 「均值滤波 / 中心轴边沿计数 / 弱峰调整 / 边沿计数 / 重采样 / 选区截取」面板: + // 内容区超高时用滚轮翻页(上百个信号合并时必备)。 + // ⚠️ 除「自由标记」外的所有面板都走这一条(它有自己的独立分支,见下)—— + // 写成“逐个列举 kind”会让新增面板默默失去滚轮(历史踩过)。 + if (hoveredKind != EdgePanelKind::MARK) { const EdgePanelLayout layout = EdgePanelLayoutFor(panelLane, hoveredKind); if (layout.content.Width > 0.0f && @@ -3586,17 +3559,11 @@ namespace jdi { const Lane& wheelLane = Tab().Lanes()[static_cast(panelLane)]; - const float current = (hoveredKind == EdgePanelKind::CENTER) - ? wheelLane.edgeCenterScroll - : (hoveredKind == EdgePanelKind::PEAK) - ? wheelLane.weakPeakScroll - : (hoveredKind == EdgePanelKind::COUNT) - ? wheelLane.edgeCountScroll - : (hoveredKind == EdgePanelKind::RESAMPLE) - ? wheelLane.resampleScroll - : (hoveredKind == EdgePanelKind::CLIP) - ? wheelLane.clipScroll - : wheelLane.meanFilterScroll; + // ⚠️ 读取当前滚动量必须按**同一种分派规则**(`LanePanelScrollOf`): + // 旧实现这里是一条三元式链,漏掉新面板时会退化成 + // `meanFilterScroll`(恒 0)⇒ 每次滚轮都从 0 开始算、 + // 永远滚不到底(用户 2026-11-03 报告「选区截取」的正是这一处)。 + const float current = LanePanelScrollOf(wheelLane, hoveredKind); const float next = current - static_cast(notches * 40.0 * m_scale); const float target = std::max(0.0f, std::min(next, maxScroll)); switch (hoveredKind) @@ -3616,9 +3583,12 @@ namespace jdi case EdgePanelKind::CLIP: SetLaneClipScroll(panelLane, target); break; - default: + case EdgePanelKind::MEAN: SetLaneMeanFilterScroll(panelLane, target); break; + case EdgePanelKind::MARK: + // 「自由标记」由下面的独立分支处理(它的内容高度来自布局)。 + break; } return true; } diff --git a/Components/PlotView/Src/PlotViewPanels.cpp b/Components/PlotView/Src/PlotViewPanels.cpp index 6535aee..307c145 100644 --- a/Components/PlotView/Src/PlotViewPanels.cpp +++ b/Components/PlotView/Src/PlotViewPanels.cpp @@ -343,38 +343,18 @@ namespace jdi const bool listStyle = (meanFilter || centerAxis || weakPeak || resamplePanel || clipPanel); const bool countTable = (kind == EdgePanelKind::COUNT); const bool hasButton = (meanFilter || weakPeak || resamplePanel || clipPanel); - const bool enabled = meanFilter ? lane.meanFilter - : (weakPeak ? lane.weakPeak - : (freeMarker ? lane.freeMarker - : (resamplePanel ? lane.resample - : (clipPanel ? lane.clip - : (centerAxis ? lane.edgeCenter - : lane.edgeCount))))); - const bool open = meanFilter ? lane.meanFilterPanel.open - : (weakPeak ? lane.weakPeakPanel.open - : (freeMarker ? lane.freeMarkerPanel.open - : (resamplePanel - ? lane.resamplePanel.open - : (clipPanel - ? lane.clipPanel.open - : (centerAxis - ? lane.edgeCenterPanel.open - : lane.edgePanel.open))))); + // ⚠️ 面板的「启用 / 展开 / 折起」状态一律通过 `LanePanelEnabled` / + // `LanePanelStateOf` 取值(见 `PlotViewInternal.hpp` 的说明): + // 旧实现是三条 7 层嵌套三元式,新增面板时极容易漏改或写错括号。 + const bool enabled = LanePanelEnabled(lane, kind); + const LanePanelState& panelState = LanePanelStateOf(lane, kind); + const bool open = panelState.open; if (!enabled || !open) { return layout; } // 面板的**折起**状态(只显示标题栏)。折起时仍然算宽度与位置,但内容区全部省略。 - const bool collapsed = meanFilter ? lane.meanFilterPanel.collapsed - : (weakPeak ? lane.weakPeakPanel.collapsed - : (freeMarker ? lane.freeMarkerPanel.collapsed - : (resamplePanel - ? lane.resamplePanel.collapsed - : (clipPanel - ? lane.clipPanel.collapsed - : (centerAxis - ? lane.edgeCenterPanel.collapsed - : lane.edgePanel.collapsed))))); + const bool collapsed = panelState.collapsed; layout.collapsed = collapsed; int rowCount = 0; @@ -597,26 +577,8 @@ namespace jdi //(用户 2026-10-17 报告:“在当前范围内的网格开启实验性功能,面板首次出现在右下角”)。 // 改用**可见序数**:它的量级只有一屏的网格数(约 20),再夹到“面板上沿不超过绘图区 // 中间偏下”以内,于是面板始终停**右上区域**,同时保留“依次错开”的观感。 - const double storedX = meanFilter ? lane.meanFilterPanel.x - : (weakPeak ? lane.weakPeakPanel.x - : (freeMarker ? lane.freeMarkerPanel.x - : (resamplePanel - ? lane.resamplePanel.x - : (clipPanel - ? lane.clipPanel.x - : (centerAxis - ? lane.edgeCenterPanel.x - : lane.edgePanel.x))))); - const double storedY = meanFilter ? lane.meanFilterPanel.y - : (weakPeak ? lane.weakPeakPanel.y - : (freeMarker ? lane.freeMarkerPanel.y - : (resamplePanel - ? lane.resamplePanel.y - : (clipPanel - ? lane.clipPanel.y - : (centerAxis - ? lane.edgeCenterPanel.y - : lane.edgePanel.y))))); + const double storedX = panelState.x; + const double storedY = panelState.y; int openPanelOrdinal = 0; { // ⚠️ 错开量用「**当前真正显示出来的面板**中排在本面板之前的个数」: @@ -626,37 +588,18 @@ namespace jdi // ⚠️ 不能用 `laneIndex`(上百个网格时会把面板推到右下角); // ⚠️ 也不能只看 `*Panel.open`:该标志**默认就是 true**,必须同时看功能是否启用 // (与 `EdgePanelLayoutFor` 的 `enabled && open` 判定一致)。 - const auto shown = [](const Lane& lane, EdgePanelKind panelKind) -> bool + const auto shown = [](const Lane& panelLane, EdgePanelKind panelKind) -> bool { - switch (panelKind) - { - case EdgePanelKind::COUNT: - return lane.edgeCount && lane.edgePanel.open; - case EdgePanelKind::CENTER: - return lane.edgeCenter && lane.edgeCenterPanel.open; - case EdgePanelKind::MEAN: - return lane.meanFilter && lane.meanFilterPanel.open; - case EdgePanelKind::PEAK: - return lane.weakPeak && lane.weakPeakPanel.open; - case EdgePanelKind::MARK: - return lane.freeMarker && lane.freeMarkerPanel.open; - case EdgePanelKind::RESAMPLE: - return lane.resample && lane.resamplePanel.open; - case EdgePanelKind::CLIP: - return lane.clip && lane.clipPanel.open; - } - return false; + return LanePanelEnabled(panelLane, panelKind) && + LanePanelStateOf(panelLane, panelKind).open; }; - static const EdgePanelKind kOrder[7] = {EdgePanelKind::COUNT, EdgePanelKind::CENTER, - EdgePanelKind::MEAN, EdgePanelKind::PEAK, - EdgePanelKind::MARK, EdgePanelKind::RESAMPLE, - EdgePanelKind::CLIP}; + // ⚠️ 面板错开序数按 `kEdgePanelKinds` 的固定顺序统计(与绘制 / 命中同源)。 int openSeen = 0; bool found = false; for (std::size_t lane = 0; lane < m_lanes.size() && !found; ++lane) { const Lane& tabLane = Tab().Lanes()[lane]; - for (const EdgePanelKind panelKind : kOrder) + for (const EdgePanelKind panelKind : kEdgePanelKinds) { if (!shown(tabLane, panelKind)) { diff --git a/Documents/Code_Review_Progress.md b/Documents/Code_Review_Progress.md index c58e905..6cfa834 100644 --- a/Documents/Code_Review_Progress.md +++ b/Documents/Code_Review_Progress.md @@ -54,3 +54,28 @@ **验证**:release + debug **全量重建**(删 `*.o`/`*.d`)零 error 零 warning; map 中 `cp-demangle.o` 已无 `.text` 段分配。 +### P2 Release 关闭异步展开表 + 修正 Debug 构建参数 —— ✅ 完成(提交 `94fc1c4`) + +**改动** + +1. 新增 `RELEASE_ONLY_OPT = -fno-asynchronous-unwind-tables`,**只**加在 Release 的两条 + 编译规则上。`-fno-exceptions` 下绝大多数函数不再需要 `.pdata`/`.xdata`。 + * 代价(已评估接受):Release 崩溃时 WER 拿不到可靠调用栈;不打印栈回溯的程序 + 功能不受影响,Debug 构建保留完整展开表。 + * `-fno-unwind-tables`(全关)会链接失败,不采用。 +2. **修正真实缺陷**:`DEBUG_OPT = -O0` 定义了却**从未被使用** —— Debug 的两条编译规则 + 误用 `$(RELEASE_OPT)`(`-Oz`),且全程没有 `-g`。所谓 "Debug 构建" 其实只是 + "不 strip 的 Release",根本无法调试。现改用 `$(DEBUG_OPT) -g`,链接也加 `-g`。 + +**实测** + +| 指标 | 改前 | 改后 | +| --- | --- | --- | +| Release | 829,440 B | **793,088 B**(−36,352) | +| `.pdata` | 30,420 B | 12,684 B | +| `.xdata` | 28,904 B | 10,156 B | +| Debug | 1,734,578 B | 5,992,064 B(真实 `-O0 -g`,属预期) | + +**验证**:release 与 debug 均全量重建,零 error 零 warning(`-O0` 下也没有新警告)。 + +