Snapshot before max-resize threshold diagnosis
This commit is contained in:
+137
-9
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user