回退 5b52eef 并修复「屏幕不刷新」根因:拖动滚动条卡顿 / 悬停高亮卡住 / 信号拖拽无效果

根因(代码级确认)
- Render() 把界面画进后备缓冲区后用 GetDC + BitBlt 回送窗口,5b52eef 把回送写成
  `if (fullFrame) { BitBlt(整窗) } else if (panelOnly) { BitBlt(本帧重绘区域并集) }`,没有兜底 else。
- 「鼠标只在绘图区内移动」的帧(WM_MOUSEMOVE 的 plotOnlyMove 路径只 InvalidatePlotRect)恰好
  fullFrame == false && panelOnly == false ⇒ 后备缓冲区被正确重绘,窗口 DC 一个像素都不回送。
- 这一个根因同时造成用户报告的三个症状:悬停光晕卡住(移开不消失)、拖动滚动条不跟手/非常卡顿、
  拖拽信号换网格看不到任何结果。

修法
- git reset --hard HEAD~1 撤销 5b52eef(保留 backup-bad-commit 标签供 A/B)。
- 只挑回该提交里与更新区域无关的功能改动(用户明确要求过、本身正确):
  * SignalPanel::ExpandGroupForSignal():联动滚动前立刻展开折起的分组(不播展开动画);
  * FitAll() / FitSinglePointCluster() 不再复位 Tab().LaneScroll()(适应视图不影响滚动条);
  * 右下角「回到最上方」浮动按钮 + 网格滚动到可视区的平滑动画。
- Application.cpp 只保留 3 处最小改动:Render 里把 panelAnimating 计入整帧脏、WM_TIMER 推进
  AdvanceBackToTopAnimation() 且保持整窗失效、定时器存活判定带 BackToTopAnimationActive()。
  Render 的绘制与 BitBlt 路径逐字节等同于 82b5ebe。
- 彻底删除 m_paintPlot / m_panelDirty / panelOnly / InvalidatePanelRect / 并集回送。

验证(全部离屏执行,不移动真实光标、不抢焦点、不截屏)
- .preflight/diag(新增离屏探针,19/19):窗口移到屏幕外 + WS_EX_TOOLWINDOW + SW_SHOWNOACTIVATE,
  保证有真实更新区域与 WM_PAINT 但不影响用户。断言滚动条拖动(滑块与绘图区都在后备缓冲区被重绘)、
  悬停光晕出现与消退(像素完全回到基线)、在曲线上按下进入 MOVE_SIGNAL 且拖到另一网格后归属正确。
- .preflight/check 137/137。
- .preflight/diag/verify_blit.ps1(新增源码级护栏,10/10):断言 Render() 的回送块必须有无条件 else、
  不得出现 else if、m_paintPlot/m_panelDirty/panelOnly 必须不存在、失效只允许整窗或仅绘图区。
  A/B:换回 backup-bad-commit ⇒ 4 项如实失败;换回修复版 ⇒ 10/10。
- make release 零错误零警告。

文档:Documents/agent_mem.md 新增 §9.41、§11.2 第 108–116 条(含 §9.40 与旧 108–114 条的作废标注)
与变更日志一行。

教训(写进记忆):① 离屏探针无法验证「窗口回送 / 合成」(屏幕外 DWM 不合成、窗口 DC 恒为空白)⇒
回送类约束必须有源码级护栏;② 离屏窗口 + TrackMouseEvent 会立刻收到 WM_MOUSELEAVE,探针须丢弃该消息;
③ 网格回收会让下标左移,断言要用锚信号。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-16 17:33:08 +08:00
1 parent 82b5ebe4f9
commit f845616938
10 files changed
+460 -8

No files matched your search

+52
View File
@@ -662,6 +662,10 @@ namespace jdi
BuildLaneVisuals();
// 「回到最上方」浮动按钮:目标可见性由当前滚动位置决定(过渡由宿主定时器推进)。
// 在绘制前刷新一次,保证本帧就画出正确的过渡形态。
UpdateBackToTopTarget();
// 「边沿计数」的扫描预算**按帧重置**(见 `m_edgeScanFrame` 的说明):
// 旧实现按 `GetTickCount64()` 分桶,帧率一高(`PostMessage` 自持重绘会把它拉高)
// 就会在同一系统 tick 内连画多帧而**完全不推进**统计 —— 用户报告
@@ -735,6 +739,8 @@ namespace jdi
PaintEditField(g);
}
PaintScrollBar(g);
// 右下角「回到最上方」浮动按钮:在滚动条之上绘制(随 `m_backToTopAlpha` 淡入淡出)。
PaintBackToTop(g);
// 绘图区外框(比网格内部分界线更重,形成明确整体边界)。
// 用 GDI 画 4 条线:GDI+ 的 DrawRectangle 带抗锯齿时单帧要花掉 1ms 以上。
@@ -6380,5 +6386,51 @@ namespace jdi
UiFillRoundRect(g, thumb, kScrollBarThickness * m_scale * 0.5f, m_theme->borderStrong);
}
void PlotView::PaintBackToTop(gd::Graphics& g)
{
if (m_backToTopAlpha <= 0.01f)
{
return;
}
const gd::RectF rect = BackToTopRect();
if (rect.Width <= 1.0f)
{
return;
}
// 过渡:整个按钮(底色/描边/箭头/投影)按 `m_backToTopAlpha` 淡入淡出。
const float alpha = std::min(1.0f, std::max(0.0f, m_backToTopAlpha));
const auto faded = [alpha](std::uint32_t color) -> std::uint32_t
{
const std::uint32_t a =
static_cast<std::uint32_t>(static_cast<float>((color >> 24) & 0xFFu) * alpha);
return (color & 0x00FFFFFFu) | ((a & 0xFFu) << 24);
};
const float radius = rect.Width * 0.5f; // 圆形按钮(与图例托底/GX 徽标同一套配色)
// 投影:两层递减透明度的圆角矩形(与弹出菜单/浮动面板一致,不引入系统阴影 API)。
UiFillRoundRect(g, gd::RectF(rect.X + 1.0f * m_scale, rect.Y + 2.0f * m_scale, rect.Width,
rect.Height),
radius, faded(0x1A000000u));
UiFillRoundRect(g, gd::RectF(rect.X, rect.Y + 1.0f * m_scale, rect.Width, rect.Height), radius,
faded(0x14000000u));
const bool hot = m_backToTopHover || m_backToTopPressed;
UiFillRoundRect(g, rect, radius, faded(hot ? m_theme->accentSoft : m_theme->cardBg));
UiDrawRoundRect(g, rect, radius, faded(hot ? m_theme->accent : m_theme->borderStrong),
hot ? 1.6f : 1.0f);
// 向上箭头:竖杆 + 两条斜线组成的箭头(自绘,不依赖字体)。
const float cx = rect.X + rect.Width * 0.5f;
const float cy = rect.Y + rect.Height * 0.5f;
const float depth = rect.Width * kBackToTopArrowDepth;
const float halfWidth = rect.Width * kBackToTopArrowHalfWidth;
const std::uint32_t ink = faded(hot ? m_theme->accent : m_theme->textStrong);
const float stroke = std::max(1.6f, 2.0f * m_scale);
UiDrawLine(g, cx, cy + depth, cx, cy - depth, ink, stroke);
UiDrawLine(g, cx, cy - depth, cx - halfWidth, cy - depth * 0.15f, ink, stroke);
UiDrawLine(g, cx, cy - depth, cx + halfWidth, cy - depth * 0.15f, ink, stroke);
}
} // namespace jdi