发布前托管重绘与布局封版收口

收口 Dialog/overlay 后鼠标状态同步、Tooltip 临时 coverage 与持久 coverage 拆分、跨 root 脏区补提交、TextBox/Button 绘制副作用修复,并补充 KEY6 回归用例和 BUG/Fix/Feature 开发记录。
This commit is contained in:
Codex
2026-05-17 00:26:08 +08:00
parent 2388f22c99
commit 9155a86a8a
26 changed files with 1355 additions and 175 deletions
+62 -15
View File
@@ -22,6 +22,12 @@ static const char* SxCanvasMsgName(UINT m)
namespace
{
enum class SxCanvasOverlayRedrawMode
{
None,
RefreshSnapshot
};
bool SxCanvasRectValid(const RECT& rc)
{
return rc.right > rc.left && rc.bottom > rc.top;
@@ -209,9 +215,18 @@ bool Canvas::handleEvent(const ExMessage& msg)
consumed = true;
}
}
else if (c->clearTransientMouseState())
else
{
anyVisualChanged = true;
// 后续兄弟只走临时状态清理,不会再进入自己的 handleEvent()。
// Tooltip 隐藏会先回贴旧快照,再改变 coverage;因此必须先保存旧覆盖范围,
// 避免登记重绘时丢失旧 Tooltip 区域,导致上层 overlay 补画判断不完整。
const RECT previousCoverage = c->getManagedRepaintCoverageRect();
if (c->clearTransientMouseState())
{
if (Window* host = getHostWindow())
host->requestManagedRepaint(c, previousCoverage);
anyVisualChanged = true;
}
}
}
}
@@ -403,9 +418,11 @@ void Canvas::requestRepaint(Control* parent)
SX_LOG_TRACE("Dirty") << SX_T("Canvas 请求局部重绘:id=", "Canvas::requestRepaint(partial): id=") << id;
RECT coverage{};
bool hasCoverage = false;
auto commitManagedChild = [&](Control* child, bool forceOverlayRedraw)
RECT paintCoverage{};
bool hasPaintCoverage = false;
RECT persistentCoverage{};
bool hasPersistentCoverage = false;
auto commitManagedChild = [&](Control* child, SxCanvasOverlayRedrawMode overlayMode)
{
if (!child || !child->IsVisible())
return;
@@ -413,10 +430,10 @@ void Canvas::requestRepaint(Control* parent)
const bool directDirty = child->isDirty();
const bool subtreeDirty = child->hasManagedDirtySubtree();
if (forceOverlayRedraw)
if (overlayMode == SxCanvasOverlayRedrawMode::RefreshSnapshot)
{
// overlay 补画必须先作废旧快照:
// 下层兄弟刚刚已经写过像素,若继续沿用旧快照,会把旧背景再贴回来。
// 下层兄弟的持久内容刚刚已经写过像素,若继续沿用旧快照,会把旧背景再贴回来。
child->invalidateBackgroundSnapshot();
child->setDirty(true);
child->draw();
@@ -437,15 +454,26 @@ void Canvas::requestRepaint(Control* parent)
return;
}
const RECT childRect = child->getManagedRepaintCoverageRect();
if (!hasCoverage)
const RECT childPaintRect = child->getManagedRepaintCoverageRect();
if (!hasPaintCoverage)
{
coverage = childRect;
hasCoverage = true;
paintCoverage = childPaintRect;
hasPaintCoverage = true;
}
else
{
coverage = SxCanvasUnionRect(coverage, childRect);
paintCoverage = SxCanvasUnionRect(paintCoverage, childPaintRect);
}
const RECT childPersistentRect = child->getManagedRepaintPersistentCoverageRect();
if (!hasPersistentCoverage)
{
persistentCoverage = childPersistentRect;
hasPersistentCoverage = true;
}
else
{
persistentCoverage = SxCanvasUnionRect(persistentCoverage, childPersistentRect);
}
};
@@ -457,12 +485,17 @@ void Canvas::requestRepaint(Control* parent)
if (child->hasManagedDirtySubtree())
{
commitManagedChild(child, false);
commitManagedChild(child, SxCanvasOverlayRedrawMode::None);
}
else if (hasCoverage && SxCanvasRectsIntersect(child->getManagedRepaintCoverageRect(), coverage))
else if (hasPaintCoverage && SxCanvasRectsIntersect(child->getManagedRepaintCoverageRect(), paintCoverage))
{
// 位于本次累计 coverage 上方、且发生相交的兄弟控件,需要补画回最上层。
commitManagedChild(child, true);
// 但只有下层“持久内容”影响到它时,才允许作废并重新抓背景快照;
// 如果只是被 Tooltip 等临时浮层覆盖,则跳过兄弟补画,避免透明控件回贴旧快照擦掉 Tooltip。
const bool persistentHit = hasPersistentCoverage &&
SxCanvasRectsIntersect(child->getManagedRepaintPersistentCoverageRect(), persistentCoverage);
if (persistentHit)
commitManagedChild(child, SxCanvasOverlayRedrawMode::RefreshSnapshot);
}
}
@@ -502,6 +535,20 @@ RECT Canvas::getManagedRepaintCoverageRect() const
return coverage;
}
RECT Canvas::getManagedRepaintPersistentCoverageRect() const
{
// 持久 coverage 只描述会进入背景快照语义的范围。
// 子控件 Tooltip 等临时浮层不会被并入,避免兄弟控件补画时抓到临时像素。
RECT coverage = getBoundsRect();
for (const auto& child : controls)
{
if (!child->IsVisible())
continue;
coverage = SxCanvasUnionRect(coverage, child->getManagedRepaintPersistentCoverageRect());
}
return coverage;
}
bool Canvas::canCommitManagedPartialRepaint() const
{
// Canvas 只有在“自己本体不脏 + 仍持有有效背景快照”时,