Finalize layout stage 2 fixes and refresh regression scenes

This commit is contained in:
Codex
2026-04-16 11:40:39 +08:00
parent b7ad960518
commit 738cf035bb
20 changed files with 1470 additions and 308 deletions
+178 -21
View File
@@ -33,6 +33,9 @@ static bool SxRectsIntersect(const RECT& a, const RECT& b)
a.top < b.bottom && a.bottom > b.top;
}
static void collectManagedControlOverlays(const std::vector<std::unique_ptr<Control>>& controls,
Control* repaintRoot, const RECT& coverage, std::vector<Control*>& overlays);
bool Window::isManagedDispatchActive() const
{
return managedDispatchActive;
@@ -56,9 +59,20 @@ void Window::requestManagedRepaint(Control* source)
if (!root)
return;
RECT coverage = root->getBoundsRect();
RECT coverage = root->getManagedRepaintCoverageRect();
if (root->canCommitManagedPartialRepaint())
coverage = source->getBoundsRect();
{
// 对支持局部提交的 root,coverage 不能再盯着最深处的 source;
// 否则像“三层 Canvas 里的按钮变色”这种情况,只会登记成一个很小的叶子矩形,
// 顶层 root 提交时既容易漏掉那条直接脏分支,也会低估后续 overlay 补画范围。
Control* branch = source->getManagedRepaintDirectBranch(root);
coverage = branch ? branch->getManagedRepaintCoverageRect() : source->getManagedRepaintCoverageRect();
const RECT sourceCoverage = source->getManagedRepaintCoverageRect();
coverage.left = (std::min)(coverage.left, sourceCoverage.left);
coverage.top = (std::min)(coverage.top, sourceCoverage.top);
coverage.right = (std::max)(coverage.right, sourceCoverage.right);
coverage.bottom = (std::max)(coverage.bottom, sourceCoverage.bottom);
}
for (auto& item : managedRepaintItems)
{
@@ -138,29 +152,108 @@ void Window::flushManagedRepaint()
return;
BeginBatchDraw();
std::vector<Control*> overlayDialogs;
auto unionCoverage = [](RECT& lhs, const RECT& rhs)
{
lhs.left = (std::min)(lhs.left, rhs.left);
lhs.top = (std::min)(lhs.top, rhs.top);
lhs.right = (std::max)(lhs.right, rhs.right);
lhs.bottom = (std::max)(lhs.bottom, rhs.bottom);
};
for (auto& item : managedRepaintItems)
collectManagedDialogOverlays(item.root, item.coverage, overlayDialogs);
auto redrawOverlayUnit = [](Control* unit)
{
if (!unit || !unit->IsVisible())
return;
// overlay 补画不是“沿用旧快照再贴回去”,而是重新站到当前顶层场景上合成一遍。
unit->invalidateBackgroundSnapshot();
unit->setDirty(true);
unit->draw();
};
auto processManagedRoot = [&](Control* root, const RECT& initialCoverage)
{
if (!root || !root->IsVisible())
return;
root->commitManagedRepaint();
RECT workingCoverage = initialCoverage;
size_t controlStartIdx = controls.size();
for (size_t i = 0; i < controls.size(); ++i)
{
if (controls[i].get() == root)
{
controlStartIdx = i + 1;
break;
}
}
// 顶层普通控件的 overlay 补画必须是“传递式”的:
// 如果 A 重画后把上层按钮 B 也重画出来,而 B 自己又伸进更上层的 C,
// 那么 C 也必须继续补画回来,而不能只看最初 root 的 coverage 一跳收集。
if (controlStartIdx < controls.size())
{
for (size_t i = controlStartIdx; i < controls.size(); ++i)
{
Control* current = controls[i].get();
if (!current || !current->IsVisible())
continue;
const RECT currentCoverage = current->getManagedRepaintCoverageRect();
if (!SxRectsIntersect(currentCoverage, workingCoverage))
continue;
redrawOverlayUnit(current);
unionCoverage(workingCoverage, current->getManagedRepaintCoverageRect());
}
}
size_t dialogStartIdx = 0;
for (size_t i = 0; i < dialogs.size(); ++i)
{
if (dialogs[i].get() == root)
{
dialogStartIdx = i + 1;
break;
}
}
// Dialog 永远位于普通顶层控件之上,也要使用扩张后的 coverage 做传递式补画。
for (size_t i = dialogStartIdx; i < dialogs.size(); ++i)
{
Control* dialog = dialogs[i].get();
if (!dialog || !dialog->IsVisible())
continue;
const RECT dialogCoverage = dialog->getManagedRepaintCoverageRect();
if (!SxRectsIntersect(dialogCoverage, workingCoverage))
continue;
redrawOverlayUnit(dialog);
unionCoverage(workingCoverage, dialog->getManagedRepaintCoverageRect());
}
};
for (auto& control : controls)
{
for (auto& item : managedRepaintItems)
{
if (item.root == control.get() && item.root && item.root->IsVisible())
if (item.root == control.get())
{
item.root->commitManagedRepaint();
processManagedRoot(item.root, item.coverage);
break;
}
}
}
for (auto& dialog : overlayDialogs)
for (auto& dialog : dialogs)
{
if (!dialog || !dialog->IsVisible())
continue;
dialog->setDirty(true);
dialog->draw();
for (auto& item : managedRepaintItems)
{
if (item.root == dialog.get())
{
processManagedRoot(item.root, item.coverage);
break;
}
}
}
EndBatchDraw();
@@ -214,6 +307,44 @@ void Window::collectManagedDialogOverlays(Control* repaintRoot, const RECT& cove
}
}
/**
* collectManagedControlOverlays(repaintRoot, coverage, overlays)
* 作用:找出在本轮 root 提交后,需要重新补画回上层的普通顶层控件。
* 规则:
* - 只处理 Window::controls 这一层的直接兄弟;
* - 从 repaintRoot 在 controls 中的位置之后开始收集;
* - 仅收集可见且与 coverage 相交的控件。
*/
static void collectManagedControlOverlays(const std::vector<std::unique_ptr<Control>>& controls,
Control* repaintRoot, const RECT& coverage, std::vector<Control*>& overlays)
{
size_t startIdx = controls.size();
for (size_t i = 0; i < controls.size(); ++i)
{
if (controls[i].get() == repaintRoot)
{
startIdx = i + 1;
break;
}
}
if (startIdx > controls.size())
return;
for (size_t i = startIdx; i < controls.size(); ++i)
{
Control* control = controls[i].get();
if (!control || !control->IsVisible())
continue;
if (SxRectsIntersect(control->getBoundsRect(), coverage))
{
if (std::find(overlays.begin(), overlays.end(), control) == overlays.end())
overlays.push_back(control);
}
}
}
/**
* ApplyResizableStyle
* 作用:统一设置可拉伸/裁剪样式,并按开关使用 WS_EX_COMPOSITED(合成双缓冲)。
@@ -621,19 +752,45 @@ int Window::runEventLoop()
}
if (!consume)
{
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
Control* firstConsumer = nullptr;
if (msg.message == WM_MOUSEMOVE)
{
Control* current = it->get();
consume = current->handleEvent(msg);
if (consume)
// 顶层普通控件的 hover/tooltip 清理规则:
// - 第一个命中的兄弟分支收到真实 WM_MOUSEMOVE
// - 后续兄弟不再重新命中,只清理旧 hover / tooltip 等瞬时鼠标状态。
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
{
if (!SxIsNoisyMsg(msg.message))
SX_LOGD("Event") << SX_T("事件被控件处理:", "Event consumed by control: ")
<< SxMsgName(msg.message)
<< SX_T(" id=", " id=") << current->getId();
break;
Control* current = it->get();
if (!consume)
{
consume = current->handleEvent(msg);
if (consume)
firstConsumer = current;
}
else
{
current->clearTransientMouseState();
}
}
}
else
{
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
{
Control* current = it->get();
consume = current->handleEvent(msg);
if (consume)
{
firstConsumer = current;
break;
}
}
}
if (firstConsumer && !SxIsNoisyMsg(msg.message))
SX_LOGD("Event") << SX_T("事件被控件处理:", "Event consumed by control: ")
<< SxMsgName(msg.message)
<< SX_T(" id=", " id=") << firstConsumer->getId();
}
managedDispatchActive = false;
}