回退 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:
1 parent
82b5ebe4f9
commit
f845616938
10 files changed
+460
-8
No files matched your search
@@ -273,6 +273,99 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::ScrollLanesToTop()
|
||||
{
|
||||
// 已经在最上方:什么都不做(按钮此时也不该可见)。
|
||||
if (Tab().LaneScroll() <= 0.5f)
|
||||
{
|
||||
Tab().LaneScroll() = 0.0f;
|
||||
m_laneScrollAnimActive = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 复用与「点选信号滚入可视区」完全相同的平滑过渡(ease-out cubic)。
|
||||
const float distance = Tab().LaneScroll();
|
||||
const float durationMs = std::max(
|
||||
kLaneScrollAnimMinMs, std::min(kLaneScrollAnimMaxMs, distance * kLaneScrollAnimMsPerPixel));
|
||||
m_laneScrollAnimFrom = Tab().LaneScroll();
|
||||
m_laneScrollAnimTo = 0.0f;
|
||||
m_laneScrollAnimStartMs = GetTickCount64();
|
||||
m_laneScrollAnimDurationMs = static_cast<std::uint32_t>(durationMs + 0.5f);
|
||||
m_laneScrollAnimActive = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::UpdateBackToTopTarget()
|
||||
{
|
||||
// 目标可见性 = 网格需要滚动条(说明网格多到需要滚动)且已经向下滚动了一段距离。
|
||||
const bool want = m_scrollBarVisible &&
|
||||
(Tab().LaneScroll() > kBackToTopShowOffset * m_scale);
|
||||
if (want == m_backToTopFadeIn && !m_backToTopAnimating)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (want == m_backToTopFadeIn)
|
||||
{
|
||||
return false; // 方向未变且正在过渡:让它继续
|
||||
}
|
||||
m_backToTopFadeIn = want;
|
||||
m_backToTopAnimating = true;
|
||||
m_backToTopAnimStartMs = GetTickCount64();
|
||||
if (!want)
|
||||
{
|
||||
// 淡出开始时取消悬停/按下态(按钮即将消失)。
|
||||
m_backToTopHover = false;
|
||||
m_backToTopPressed = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::AdvanceBackToTopAnimation()
|
||||
{
|
||||
if (!m_backToTopAnimating)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::uint64_t now = GetTickCount64();
|
||||
const std::uint64_t elapsed =
|
||||
(now >= m_backToTopAnimStartMs) ? (now - m_backToTopAnimStartMs) : 0;
|
||||
float progress = (kBackToTopFadeMs > 0.0f)
|
||||
? static_cast<float>(elapsed) / kBackToTopFadeMs
|
||||
: 1.0f;
|
||||
if (progress > 1.0f)
|
||||
{
|
||||
progress = 1.0f;
|
||||
}
|
||||
// ease-out cubic:与其它过渡一致的手感(起步快、末尾慢)。
|
||||
const float eased = 1.0f - std::pow(1.0f - progress, 3.0f);
|
||||
const float from = m_backToTopFadeIn ? 0.0f : 1.0f;
|
||||
const float to = m_backToTopFadeIn ? 1.0f : 0.0f;
|
||||
m_backToTopAlpha = from + (to - from) * eased;
|
||||
|
||||
if (progress >= 1.0f)
|
||||
{
|
||||
m_backToTopAlpha = to;
|
||||
m_backToTopAnimating = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
gd::RectF PlotView::BackToTopRect() const
|
||||
{
|
||||
if (!m_scrollBarVisible)
|
||||
{
|
||||
return gd::RectF();
|
||||
}
|
||||
const float size = kBackToTopSize * m_scale;
|
||||
const float margin = kBackToTopMargin * m_scale;
|
||||
// ⚠️ 贴着**绘图区**右下角(`m_plotRect` 已经让出了右侧滚动条的位置,
|
||||
// 因此按钮不会压到滚动条上)。
|
||||
return gd::RectF(m_plotRect.X + m_plotRect.Width - margin - size,
|
||||
m_plotRect.Y + m_plotRect.Height - margin - size, size, size);
|
||||
}
|
||||
|
||||
bool PlotView::LaneSpansMultipleGroups(int laneIndex) const
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
|
||||
Reference in new issue
Block a user