From a566d98bab1edc4f6e1d53f797040ce32a4fce11 Mon Sep 17 00:00:00 2001 From: "DeepSeek V4.1 Flash" <> Date: Thu, 8 Oct 2026 21:51:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=A4=E4=BA=92=E4=BF=AE=E6=AD=A3=EF=BC=9A?= =?UTF-8?q?=E6=82=AC=E5=81=9C=E6=8F=90=E7=A4=BA=E8=B7=9F=E9=9A=8F=E9=BC=A0?= =?UTF-8?q?=E6=A0=87=E3=80=81=E4=BF=A1=E5=8F=B7=E6=8B=96=E6=8B=BD=E6=89=8B?= =?UTF-8?q?=E6=8E=8C/=E6=8A=93=E6=8F=A1=E5=85=89=E6=A0=87=E3=80=81?= =?UTF-8?q?=E5=A2=9E=E9=87=8F=E5=8F=AA=E6=98=BE=E7=A4=BA=E5=9C=A8=E5=8F=B3?= =?UTF-8?q?=E4=BE=A7=E5=85=89=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 信号悬停提示改为真正跟随鼠标(PaintSignalInteraction 用 m_mouseX/m_mouseY 定位, 删除 m_hoverAnchorX/m_hoverAnchorY)。 根因:OnMouseMove 只在状态变化时返回 changed,仅鼠标位置变化时返回 false, 宿主不重绘 → 提示停在上一帧位置。现补上 `if (m_hoverSignal.IsValid() && mouseMoved) changed = true;`(mouseMoved 由前后坐标比较得出), 鼠标在可拖拽区域内持续移动时提示条会跟随。 - 可拖拽信号的光标响应参考 Photoshop 平移元素: 悬停时用手掌光标(IDC_HAND),按住左键拖拽途中用抓握光标(IDC_SIZEALL)。 - 双光标增量差值:改为**只**由"右侧光标"(时间较大的那条)显示, 按差值显式给出正负号(+ / -,不再固定用 ±);把右光标拖到左侧后, 增量会自动改由新的右侧光标显示。(判定改用时间比较,不再用 m_activeCursor。) - 光标时间读数标签与 Δt 差值标签:悬停(未按下)时不再改变鼠标光标, 真正拖拽时统一使用左右箭头(IDC_SIZEWE)。 两个标签都叠在光标线正上方,因此在 QueryCursor 中必须先于光标线判定返回 nullptr, 否则会命中光标线给出 SIZEWE。 - Δt 差值文案由 "Δt " 改为 "Δt:"(全角冒号)。 - 一并移除遗留的 TEMP-PROBE 调试输出(HitTestSignal / PaintSignalInteraction / OnMouseMove 探针)与已不再使用的 m_dragOnLabel。 验证(TestData/VERY_BIG_DATA.CSV,编码器A相密集方波,125%、1725x996): - 提示跟随:悬停 (700,250)→(700,400)→(760,620)→(900,700) 后, 提示条出现在最后一次鼠标位置旁(截图核对),不再停留在首次出现处。 - 光标形态探测:信号悬停 = HAND;拖拽信号 = SIZEALL; 时间读数标签/Δt 标签悬停 = ARROW(不改变);Δt 标签拖拽 = SIZEWE; 时间读数标签拖拽 = SIZEWE;光标线上方悬停 = SIZEWE。 - 增量归属:默认双光标左 4.9417(无增量)/ 右 4.9425 , +0.0008; 把右光标拖到左侧后变为左 0.288688(无增量)/ 右 4.9417 , +4.65301; 把右光标拖到低电平处得到 4.80724 , -0.134458(负号正确)。 - Δt 文案:截图确认显示 "Δt: 15s 457.214ms"。 - 回归([1].CSV 双光标):左 426.316(无增量)/ 右 538.583 , +112.267; Δt:151ms 200us。 - make clean && make all 无警告无错误。 --- Components/PlotView/Inc/PlotView.hpp | 3 - Components/PlotView/Src/PlotView.cpp | 111 ++++++++------------------- 2 files changed, 34 insertions(+), 80 deletions(-) diff --git a/Components/PlotView/Inc/PlotView.hpp b/Components/PlotView/Inc/PlotView.hpp index 75a65b3..d4d0f7a 100644 --- a/Components/PlotView/Inc/PlotView.hpp +++ b/Components/PlotView/Inc/PlotView.hpp @@ -913,7 +913,6 @@ namespace jdi int m_spanDragStartX = 0; /**< 差值标签拖拽起点横坐标。 */ double m_spanDragTime1 = 0.0; /**< 差值标签拖拽起始时两条光标的时间。 */ double m_spanDragTime2 = 0.0; - bool m_dragOnLabel = false; /**< 本次光标拖拽是从时间/差值标签上抓起的(用抓握光标)。 */ int m_cursorGrabOffset = 0; /**< 从光标时间标签上抓起时的横向偏移(像素)。 */ PlotEditTarget m_editTarget = PlotEditTarget::NONE; /**< 当前内联输入目标。 */ TextEdit m_edit; /**< 输入框编辑模型(光标/选区/剪贴板)。 */ @@ -927,8 +926,6 @@ namespace jdi int m_hoverLane = -1; /**< 当前悬停网格下标。 */ int m_mouseX = 0; /**< 最近一次鼠标横坐标(用于悬停提示定位)。 */ int m_mouseY = 0; /**< 最近一次鼠标纵坐标(用于悬停提示定位)。 */ - int m_hoverAnchorX = 0; /**< 信号悬停提示的锚点横坐标(首次进入可拖拽区域时确定,不随鼠标移动)。 */ - int m_hoverAnchorY = 0; /**< 信号悬停提示的锚点纵坐标。 */ bool m_spanHover = false; /**< 鼠标是否悬停在双光标差值标签上。 */ std::wstring m_hintText; /**< 临时提示文本(空表示不显示)。 */ std::uint64_t m_hintShownAt = 0; /**< 提示出现的时刻(毫秒,GetTickCount64)。 */ diff --git a/Components/PlotView/Src/PlotView.cpp b/Components/PlotView/Src/PlotView.cpp index 93dbeb9..c46f3a8 100644 --- a/Components/PlotView/Src/PlotView.cpp +++ b/Components/PlotView/Src/PlotView.cpp @@ -185,13 +185,13 @@ namespace jdi } /** - * @brief 双光标差值的完整显示文本("Δt " + 复合单位间隔)。 + * @brief 双光标差值的完整显示文本("Δt:" + 复合单位间隔)。 * @param [IN] seconds 时间间隔(秒)。 - * @return 形如 "Δt 1h 1min 2s" 的文本。 + * @return 形如 "Δt:1h 1min 2s" 的文本。 */ std::wstring FormatSpanText(double seconds) { - return L"Δt " + FormatDurationCompound(seconds); + return L"Δt:" + FormatDurationCompound(seconds); } /** @@ -1677,17 +1677,6 @@ namespace jdi SignalRef best; float bestDistance = tolerance; - // TEMP-PROBE - { - FILE* fp = fopen(".preflight\\hit.log", "a"); - if (fp != nullptr) - { - fprintf(fp, "query=(%d,%d) plot=(%.0f,%.0f,%.0f,%.0f) lane=%d\n", x, y, m_plotRect.X, - m_plotRect.Y, m_plotRect.Width, m_plotRect.Height, LaneAtY(y)); - fclose(fp); - } - } - // 只为当前网格内的可见信号做精确的点到线段距离检测,避免跨网格误命中。 for (const SignalRef& ref : m_tab->Lanes()[static_cast(laneIndex)].signals) { @@ -1729,34 +1718,8 @@ namespace jdi } if (windowFirst >= windowLast) { - // TEMP-PROBE - FILE* fp = fopen(".preflight\\hit.log", "a"); - if (fp != nullptr) - { - fprintf(fp, " window empty first=%zu last=%zu pts=%zu fx=%.1f tol=%.1f\n", windowFirst, - windowLast, points.size(), fx, tolerance); - fclose(fp); - } continue; } - // TEMP-PROBE - { - FILE* fp = fopen(".preflight\\hit.log", "a"); - if (fp != nullptr) - { - float minY = 1.0e9f; - float maxY = -1.0e9f; - for (std::size_t i = windowFirst; i < windowLast; ++i) - { - minY = std::min(minY, points[i].Y); - maxY = std::max(maxY, points[i].Y); - } - fprintf(fp, " window first=%zu last=%zu pts=%zu yRange=%.1f..%.1f fy=%.1f\n", - windowFirst, windowLast, points.size(), minY, maxY, fy); - fclose(fp); - } - } - // 散点模式下把采样点本身也视为可命中目标。 if (resolved.style->mode == DrawMode::SCATTER) { @@ -2208,13 +2171,13 @@ namespace jdi case PlotDragMode::SCROLL_LANES: return LoadCursorW(nullptr, IDC_SIZENS); case PlotDragMode::CURSOR_MOVE: - // 从时间读数标签上抓起的拖拽统一用抓握(移动)图标; - // 直接从光标线上抓起的拖拽保持左右箭头。 - return LoadCursorW(nullptr, m_dragOnLabel ? IDC_SIZEALL : IDC_SIZEWE); + // 光标拖拽(无论是抓住光标线还是抓住时间读数标签)都沿时间轴移动 → 左右箭头。 + return LoadCursorW(nullptr, IDC_SIZEWE); case PlotDragMode::CURSOR_SPAN_MOVE: - // 差值标签拖拽:统一使用抓握图标(不是左右箭头)。 - return LoadCursorW(nullptr, IDC_SIZEALL); + // 差值标签拖拽:两条光标整体沿时间轴平移 → 左右箭头。 + return LoadCursorW(nullptr, IDC_SIZEWE); case PlotDragMode::MOVE_SIGNAL: + // 拖动信号(参考 Photoshop 平移元素的抓握光标)。 return LoadCursorW(nullptr, IDC_SIZEALL); default: break; @@ -2226,18 +2189,18 @@ namespace jdi return LoadCursorW(nullptr, IDC_SIZENS); } - // 双光标差值标签:悬停时用"手形"表示可交互(与拖拽中的抓握图标区分)。 + // 光标时间读数标签与双光标差值标签:**悬停(未按下)时不改变鼠标形状**。 + // 两个标签都叠在光标线的正上方,因此必须先于"光标线"判定返回 nullptr, + // 由调用方回退成箭头;只有真正拖拽时才切成左右箭头(见 CURSOR_MOVE / CURSOR_SPAN_MOVE)。 if (IsOnCursorSpanLabel(x, y)) { - return LoadCursorW(nullptr, IDC_HAND); + return nullptr; } - - // 光标时间读数标签同样可拖拽:悬停用手形。 { int labelWhich = 0; if (IsOnCursorTimeLabel(x, y, labelWhich)) { - return LoadCursorW(nullptr, IDC_HAND); + return nullptr; } } @@ -2251,10 +2214,10 @@ namespace jdi if (UiPointInRect(m_plotRect, static_cast(x), static_cast(y))) { - // 命中信号曲线时给出可拖拽提示光标(仅在常规模式下)。 + // 悬停命中可拖拽信号时给出"手掌"提示光标;按下拖拽过程中改用抓握光标。 if (Tab().ZoomMode() == PlotZoomMode::NONE && HitTestSignal(x, y).IsValid()) { - return LoadCursorW(nullptr, IDC_SIZEALL); + return LoadCursorW(nullptr, IDC_HAND); } // 选区放大模式下**未开始框选时保持默认箭头**(不改变鼠标形状); @@ -2266,6 +2229,7 @@ namespace jdi bool PlotView::OnMouseMove(int x, int y) { + const bool mouseMoved = (x != m_mouseX) || (y != m_mouseY); m_mouseX = x; m_mouseY = y; @@ -2456,13 +2420,6 @@ namespace jdi } if (hoverSignal != m_hoverSignal) { - if (hoverSignal.IsValid()) - { - // 悬停提示固定在"刚刚进入可拖拽区域"的位置:之后鼠标即使继续在该区域内 - // 移动,提示也只停留在原地(不再跟随鼠标)。 - m_hoverAnchorX = x; - m_hoverAnchorY = y; - } m_hoverSignal = hoverSignal; changed = true; } @@ -2474,6 +2431,13 @@ namespace jdi m_spanHover = spanHover; changed = true; } + + // 悬停提示跟随鼠标:只要提示可见,鼠标移动本身就要触发重绘; + // 否则鼠标在可拖拽区域内继续移动时,提示会停在上一帧的位置。 + if (m_hoverSignal.IsValid() && mouseMoved) + { + changed = true; + } return changed; } @@ -2494,7 +2458,6 @@ namespace jdi m_dragMoved = false; m_rubberRect = gd::RectF(); m_rubberAxis = PlotRubberAxis::UNDECIDED; - m_dragOnLabel = false; m_cursorGrabOffset = 0; // 新的交互开始:清掉上一次留下的提示(拖动过程中会重新设置粘滞提示)。 SetHint(std::wstring()); @@ -2520,7 +2483,6 @@ namespace jdi if (IsOnCursorSpanLabel(x, y)) { m_dragMode = PlotDragMode::CURSOR_SPAN_MOVE; - m_dragOnLabel = true; m_spanDragStartX = x; m_spanDragTime1 = Tab().CursorTime(); m_spanDragTime2 = Tab().CursorTimeAlt(); @@ -2533,7 +2495,6 @@ namespace jdi if (IsOnCursorTimeLabel(x, y, labelWhich)) { m_dragMode = PlotDragMode::CURSOR_MOVE; - m_dragOnLabel = true; m_hoverCursor = labelWhich; m_activeCursor = labelWhich; m_cursorHover = true; @@ -2658,7 +2619,6 @@ namespace jdi m_dragLane = -1; m_dragMoved = false; m_rubberRect = gd::RectF(); - m_dragOnLabel = false; m_cursorGrabOffset = 0; // 松开左键:粘滞提示结束计时,开始"停留后淡出"。 ReleaseHint(); @@ -4220,6 +4180,10 @@ namespace jdi // 重叠时把"最近操作过的那条光标"的内容画在上层,因此先画另一条。 const int order[2] = { (m_activeCursor == 2) ? 1 : 2, (m_activeCursor == 2) ? 2 : 1 }; + // 增量差值**只**显示在"右侧光标"上。右侧 = 时间较大的那条,随拖拽实时变化: + // 把右光标拖到左边后,增量会自动改由新的右侧光标显示。 + const int rightCursor = (Tab().CursorTime() <= Tab().CursorTimeAlt()) ? 2 : 1; + // ---- 光标竖线 + 顶部把手 ---- for (int pass = 0; pass < cursorCount; ++pass) { @@ -4254,7 +4218,8 @@ namespace jdi const int which = doubleMode ? order[pass] : 1; const double time = (which == 2) ? Tab().CursorTimeAlt() : Tab().CursorTime(); const double otherTime = (which == 2) ? Tab().CursorTime() : Tab().CursorTimeAlt(); - const bool showDelta = doubleMode && (which == order[1]); + // 只有右侧光标显示增量差值(左光标只显示自身取值)。 + const bool showDelta = doubleMode && (which == rightCursor); // 光标在可视区外时,把标注"钉"在对应的网格边上,保证取值仍然可见。 const float pinX = CursorDisplayX(which); @@ -4331,10 +4296,9 @@ namespace jdi if (SampleFileAt(*resolved.file, *resolved.data, otherTime, interpolate, otherValue)) { const double delta = value - otherValue; - // 增量为右侧光标减去左侧光标;正值用 ±,负值直接用负号, - // 避免出现 “±-0.001” 这种双符号写法。 + // 增量 = 右侧光标取值 − 左侧光标取值,按差值显式给出正负号。 text += L" , "; - text += (delta < 0.0) ? L"-" : L"±"; + text += (delta < 0.0) ? L"-" : L"+"; text += FormatValue(std::fabs(delta)); } } @@ -4619,13 +4583,6 @@ namespace jdi // 悬停提示:光标贴近曲线时给出可拖拽说明(与时间光标的提示方式一致)。 if (!m_hoverSignal.IsValid()) { - // TEMP-PROBE - FILE* fp0 = fopen(".preflight\\paint.log", "a"); - if (fp0 != nullptr) - { - fprintf(fp0, "hover-invalid dragMode=%d\n", static_cast(m_dragMode)); - fclose(fp0); - } return; } const int laneIndex = m_tab->LaneOfSignal(m_hoverSignal); @@ -4642,17 +4599,17 @@ namespace jdi const LaneVisual& lane = m_lanes[static_cast(laneIndex)]; const std::wstring text = resolved.data->Title() + L" · 按住拖拽可移入其它网格"; - // 提示锚定在"首次进入可拖拽区域"的位置,鼠标在该区域内继续移动时提示不会跟随。 + // 提示跟随鼠标:在可拖拽区域内持续移动时,提示条也要跟着走。 const float textWidth = UiTextWidth(g, text, m_fonts->uiSmall) + 16.0f * m_scale; const float height = 20.0f * m_scale; - float left = static_cast(m_hoverAnchorX) + 12.0f * m_scale; + float left = static_cast(m_mouseX) + 12.0f * m_scale; if (left + textWidth > m_plotRect.X + m_plotRect.Width) { left = m_plotRect.X + m_plotRect.Width - textWidth; } left = std::max(m_plotRect.X, left); - float top = static_cast(m_hoverAnchorY) + 14.0f * m_scale; + float top = static_cast(m_mouseY) + 14.0f * m_scale; top = std::min(top, m_plotRect.Y + m_plotRect.Height - height - 2.0f * m_scale); top = std::max(top, lane.rect.Y + 2.0f * m_scale);