Snapshot before max-resize threshold diagnosis

This commit is contained in:
Codex
2026-04-09 03:23:10 +08:00
parent 77a8fe568a
commit f567369300
25 changed files with 1489 additions and 36 deletions
+137 -9
View File
@@ -27,14 +27,62 @@ static const char* SxMsgName(UINT m)
}
}
static bool SxRectsIntersect(const RECT& a, const RECT& b)
{
return a.left < b.right && a.right > b.left &&
a.top < b.bottom && a.bottom > b.top;
}
bool Window::isManagedDispatchActive() const
{
return managedDispatchActive;
}
void Window::requestManagedRepaint()
/**
* requestManagedRepaint(source)
* 作用:在“事件分发期”登记一笔托管重绘请求,而不是立即绘制。
* 关键点:
* - source 是真正发生视觉变化的控件;
* - root 是后续真正安全重绘的最小层级(通常是顶层控件/容器,或 Dialog 自身);
* - coverage 记录这次变化影响的范围,用于判断哪些上层 Dialog 需要补画。
*/
void Window::requestManagedRepaint(Control* source)
{
if (!source)
return;
managedSceneDirty = true;
Control* root = source->getManagedRepaintRoot();
if (!root)
return;
RECT coverage = root->getBoundsRect();
if (root->canCommitManagedPartialRepaint())
coverage = source->getBoundsRect();
for (auto& item : managedRepaintItems)
{
if (item.root == root)
{
item.coverage.left = (std::min)(item.coverage.left, coverage.left);
item.coverage.top = (std::min)(item.coverage.top, coverage.top);
item.coverage.right = (std::max)(item.coverage.right, coverage.right);
item.coverage.bottom = (std::max)(item.coverage.bottom, coverage.bottom);
return;
}
}
ManagedRepaintItem item;
item.root = root;
item.coverage = coverage;
managedRepaintItems.push_back(item);
}
// 清空本轮托管重绘状态;通常在 flush/全场景重绘/resize 收口后调用
void Window::clearManagedRepaintState()
{
managedSceneDirty = false;
managedRepaintItems.clear();
}
void Window::drawWindowBackground()
@@ -73,17 +121,53 @@ void Window::redrawScene(bool forceControlsDirty, bool forceDialogsDirty)
}
}
/**
* flushManagedRepaint()
* 作用:提交当前事件分发阶段累计的托管重绘请求。
* 提交顺序:
* 1)先根据 coverage 找出需要补画的非模态 Dialog;
* 2)再按 Window::controls 的层级顺序提交受影响的普通 root;
* 3)最后把相交的 Dialog 补画回最上层。
* 说明:
* - 这里不做整场景重画,而是只画本轮登记的 root;
* - 之所以按 controls 顺序提交,而不是按登记顺序,是为了保持顶层控件原有的 z-order。
*/
void Window::flushManagedRepaint()
{
if (!managedSceneDirty || !hWnd)
return;
BeginBatchDraw();
redrawScene(true, true);
std::vector<Control*> overlayDialogs;
for (auto& item : managedRepaintItems)
collectManagedDialogOverlays(item.root, item.coverage, overlayDialogs);
for (auto& control : controls)
{
for (auto& item : managedRepaintItems)
{
if (item.root == control.get() && item.root && item.root->IsVisible())
{
item.root->commitManagedRepaint();
break;
}
}
}
for (auto& dialog : overlayDialogs)
{
if (!dialog || !dialog->IsVisible())
continue;
dialog->setDirty(true);
dialog->draw();
}
EndBatchDraw();
managedSceneDirty = false;
clearManagedRepaintState();
}
// 合成一条 WM_MOUSEMOVE 并直接分发给 Window 顶层控件;常用于同步 hover 状态
void Window::dispatchSyntheticMouseMoveToControls(short x, short y)
{
ExMessage mm{};
@@ -94,6 +178,42 @@ void Window::dispatchSyntheticMouseMoveToControls(short x, short y)
(*it)->handleEvent(mm);
}
/**
* collectManagedDialogOverlays(repaintRoot, coverage, overlays)
* 作用:找出在本轮提交后需要重新盖到最上层的非模态 Dialog。
* 规则:
* - 如果 repaintRoot 本身就是 Dialog,则从它自己开始往上层 Dialog 收集;
* - 如果 repaintRoot 是普通控件,则收集所有与 coverage 相交的可见 Dialog。
*/
void Window::collectManagedDialogOverlays(Control* repaintRoot, const RECT& coverage, std::vector<Control*>& overlays)
{
size_t startIdx = 0;
if (auto* dialogRoot = dynamic_cast<Dialog*>(repaintRoot))
{
for (size_t i = 0; i < dialogs.size(); ++i)
{
if (dialogs[i].get() == dialogRoot)
{
startIdx = i;
break;
}
}
}
for (size_t i = startIdx; i < dialogs.size(); ++i)
{
Control* dialog = dialogs[i].get();
if (!dialog || !dialog->IsVisible())
continue;
if (dialog == repaintRoot || SxRectsIntersect(dialog->getBoundsRect(), coverage))
{
if (std::find(overlays.begin(), overlays.end(), dialog) == overlays.end())
overlays.push_back(dialog);
}
}
}
/**
* ApplyResizableStyle
* 作用:统一设置可拉伸/裁剪样式,并按开关使用 WS_EX_COMPOSITED(合成双缓冲)。
@@ -364,6 +484,7 @@ void Window::draw()
BeginBatchDraw();
redrawScene(true, true);
EndBatchDraw();
clearManagedRepaintState();
}
/**
@@ -406,6 +527,7 @@ void Window::draw(std::string imagePath)
BeginBatchDraw();
redrawScene(true, true);
EndBatchDraw();
clearManagedRepaintState();
}
// ---------------- 事件循环 ----------------
@@ -416,7 +538,9 @@ void Window::draw(std::string imagePath)
* 关键策略:
* - WM_SIZE:始终更新 pendingW/H(即使在拉伸中也只记录不立即绘制);
* - needResizeDirty:当尺寸确实变化时置位,随后在循环尾进行一次性重绘;
* - 非模态对话框优先消费事件(顶层从后往前);再交给普通控件
* - 非模态对话框优先消费事件(顶层从后往前);再交给普通控件
* - managedDispatchActive=true 期间,控件 requestRepaint 不会立即画,而是登记到 managedRepaintItems
* - 事件尾通过 flushManagedRepaint 提交本轮 root 重绘,再按需补画 Dialog。
*/
int Window::runEventLoop()
{
@@ -473,7 +597,8 @@ int Window::runEventLoop()
continue;
}
// 输入优先:先给顶层“非模态对话框”,再传给普通控件
// 输入优先:先给顶层“非模态对话框”,再传给普通控件
// 在 managedDispatchActive 期间,控件只改状态并登记重绘 root,不直接画。
managedDispatchActive = true;
for (auto it = dialogs.rbegin(); it != dialogs.rend(); ++it)
{
@@ -513,7 +638,8 @@ int Window::runEventLoop()
managedDispatchActive = false;
}
//如果有对话框打开或者关闭强制重绘
// 对话框打开/关闭属于全局层级变化:这里仍然使用整场景重绘兜底,
// 并在结束后清空本轮托管重绘登记,避免旧的 root 请求延后提交。
bool needredraw = false;
if(dialogOpen)
{
@@ -550,10 +676,11 @@ int Window::runEventLoop()
EndBatchDraw();
needredraw = false;
dialogOpen = false;
managedSceneDirty = false;
clearManagedRepaintState();
}
// —— 统一收口(needResizeDirty 为真时执行一次性重绘)——
// resize 会改变布局和背景基线,因此仍然走整场景重绘,而不是局部 root 提交。
if (needResizeDirty)
{
SX_LOGI("Resize") << SX_T("调整窗口尺寸开始:width=","Resize settle start: width=") << width << " height=" << height;
@@ -622,9 +749,10 @@ int Window::runEventLoop()
SX_LOGI("Resize") << SX_T("尺寸调整已完成:width=","Resize settle done: width=") << width << " height=" << height;
needResizeDirty = false; // 收口完成,清标志
managedSceneDirty = false;
clearManagedRepaintState();
}
// 普通输入事件收口:只在没有 resize / 对话框开关这种全局变化时,才提交本轮托管重绘。
if (!needResizeDirty && !dialogOpen && !dialogClose)
flushManagedRepaint();
@@ -792,7 +920,7 @@ void Window::pumpResizeIfNeeded()
ValidateRect(hWnd, nullptr);
needResizeDirty = false;
managedSceneDirty = false;
clearManagedRepaintState();
}
void Window::scheduleResizeFromModal(int w, int h)
{