From 97710db9e389830d3baee9ee6d6122c2cee64bb7 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 9 Apr 2026 04:19:56 +0800 Subject: [PATCH] Fix maximize resize guard and add records --- Window.cpp | 46 +++++++++-- .../01-补充记录-20260409-窗口最大化收口保护.md | 55 +++++++++++++ ...0003-直接最大化触发收口保护导致黑背景与残影.md | 68 ++++++++++++++++ ...0003-直接最大化触发收口保护导致黑背景与残影.md | 80 +++++++++++++++++++ .../Feature-20260409-0006-窗口最大化收口保护调整.md | 54 +++++++++++++ 5 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 开发记录/01-补充记录-20260409-窗口最大化收口保护.md create mode 100644 开发记录/BUG/BUG-20260409-0003-直接最大化触发收口保护导致黑背景与残影.md create mode 100644 开发记录/Fix/Fix-BUG-20260409-0003-直接最大化触发收口保护导致黑背景与残影.md create mode 100644 开发记录/功能变更/Feature-20260409-0006-窗口最大化收口保护调整.md diff --git a/Window.cpp b/Window.cpp index 73ab84b..2fc1805 100644 --- a/Window.cpp +++ b/Window.cpp @@ -661,7 +661,6 @@ int Window::runEventLoop() if (GetCursorPos(&pt)) { ScreenToClient(this->hWnd, &pt); - ExMessage mm; // 只分发给 window 层控件(因为 dialog 已经关闭或即将关闭) managedDispatchActive = true; dispatchSyntheticMouseMoveToControls((short)pt.x, (short)pt.y); @@ -692,17 +691,52 @@ int Window::runEventLoop() int actualWidth = clientRect.right - clientRect.left; int actualHeight = clientRect.bottom - clientRect.top; + const int virtualScreenWidth = (std::max)(1, GetSystemMetrics(SM_CXVIRTUALSCREEN)); + const int virtualScreenHeight = (std::max)(1, GetSystemMetrics(SM_CYVIRTUALSCREEN)); + const int maxReasonableWidth = (std::max)(10000, virtualScreenWidth * 2); + const int maxReasonableHeight = (std::max)(10000, virtualScreenHeight * 2); + + // 仅拦截“明显非法”的客户区尺寸,不再按“变化跨度”误杀正常最大化。 + if (actualWidth <= 0 || actualHeight <= 0 + || actualWidth > maxReasonableWidth || actualHeight > maxReasonableHeight) + { + SX_LOGD("Resize") + << SX_T("尺寸调整被非法尺寸保护跳过:old=(", "Resize settle skipped by invalid-size guard: old=(") + << width << "x" << height + << SX_T(") pending=(", ") pending=(") + << pendingW << "x" << pendingH + << SX_T(") actual=(", ") actual=(") + << actualWidth << "x" << actualHeight + << SX_T(") virtual=(", ") virtual=(") + << virtualScreenWidth << "x" << virtualScreenHeight + << SX_T(") maxAllowed=(", ") maxAllowed=(") + << maxReasonableWidth << "x" << maxReasonableHeight + << SX_T(")", ")"); + needResizeDirty = false; + continue; + } + const int finalW = (std::max)(minClientW, actualWidth); const int finalH = (std::max)(minClientH, actualHeight); - // 变化过大/异常场景保护 if (finalW != width || finalH != height) { - if (abs(finalW - width) > 1000 || abs(finalH - height) > 1000) + const int diffW = abs(finalW - width); + const int diffH = abs(finalH - height); + if (diffW > 1000 || diffH > 1000) { - // 认为是异常帧,跳过本次(不改变任何状态) - needResizeDirty = false; - continue; + SX_LOGD("Resize") + << SX_T("检测到大跨度尺寸调整,继续执行收口:old=(", "Large-span resize detected; continue settle: old=(") + << width << "x" << height + << SX_T(") new=(", ") new=(") + << finalW << "x" << finalH + << SX_T(") diff=(", ") diff=(") + << diffW << "x" << diffH + << SX_T(") actual=(", ") actual=(") + << actualWidth << "x" << actualHeight + << SX_T(") virtual=(", ") virtual=(") + << virtualScreenWidth << "x" << virtualScreenHeight + << SX_T(")", ")"); } // 再次冻结窗口更新,保证批量绘制的原子性 diff --git a/开发记录/01-补充记录-20260409-窗口最大化收口保护.md b/开发记录/01-补充记录-20260409-窗口最大化收口保护.md new file mode 100644 index 0000000..1ef28a1 --- /dev/null +++ b/开发记录/01-补充记录-20260409-窗口最大化收口保护.md @@ -0,0 +1,55 @@ +# 补充记录-20260409-窗口最大化收口保护 + +## 说明 + +- 本文件用于补充记录 2026-04-09 当天新增的窗口最大化收口问题。 +- 对应修改发生在 `f567369` 快照之后,当前仍位于工作区未提交状态。 + +## 问题概述 + +- 现象: + - 某些窗口初始尺寸下,直接点击最大化会出现黑背景、控件残影。 + - 若先手动拖拽窗口略微放大,再最大化,则问题消失。 +- 影响用例: + - `KEY == 1` + - `KEY == 2` +- 不受影响用例: + - `KEY == 3` + - `KEY == 4` + +## 根因 + +- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp) 的 `runEventLoop()` resize 收口中存在一段历史“跨度保护”。 +- 该保护把“宽高变化超过 1000”视为异常帧,直接终止收口: + - 不再执行 `adaptiveLayout` + - 不再执行 `Resize` + - 不再执行 `redrawScene(true, true)` +- 因此新暴露区域不会被完整刷新,最终表现为黑背景与残影。 + +## 本次新增记录 + +- BUG: + - [`BUG-20260409-0003 直接最大化触发收口保护导致黑背景与残影`](./BUG/BUG-20260409-0003-直接最大化触发收口保护导致黑背景与残影.md) +- Fix: + - [`Fix-BUG-20260409-0003 直接最大化触发收口保护导致黑背景与残影`](./Fix/Fix-BUG-20260409-0003-直接最大化触发收口保护导致黑背景与残影.md) +- 功能变更: + - [`Feature-20260409-0006 窗口最大化收口保护调整`](./功能变更/Feature-20260409-0006-窗口最大化收口保护调整.md) + +## 修改摘要 + +- 将“跨度过大即跳过”改为“仅非法尺寸才跳过”。 +- 非法尺寸标准: + - `actualWidth <= 0` + - `actualHeight <= 0` + - `actualWidth > max(10000, virtualScreenWidth * 2)` + - `actualHeight > max(10000, virtualScreenHeight * 2)` +- 对“大跨度但合法”的 resize 保留 `DEBUG` 日志,但继续执行完整收口。 +- 顺手删除了 `Window.cpp` 中未使用的 `ExMessage mm;` 遗留变量。 + +## 当前状态 + +- 代码修改:已完成,待提交 +- 文档记录:已补充 +- 验证状态: + - 已完成源码级编译验证 + - 待用户做 GUI 回归 diff --git a/开发记录/BUG/BUG-20260409-0003-直接最大化触发收口保护导致黑背景与残影.md b/开发记录/BUG/BUG-20260409-0003-直接最大化触发收口保护导致黑背景与残影.md new file mode 100644 index 0000000..beb4cb8 --- /dev/null +++ b/开发记录/BUG/BUG-20260409-0003-直接最大化触发收口保护导致黑背景与残影.md @@ -0,0 +1,68 @@ +# BUG-20260409-0003 + +> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。 + +## 基本信息 + +- ID: BUG-20260409-0003 +- 标题: 直接最大化触发收口保护,导致黑背景与控件残影 +- 状态:已修复 / 待回归验证 +- 严重性:S2 +- 优先级:P0 +- 模块:Window / Resize / Scene Redraw +- 版本 / 分支:`master` +- 环境:Windows 桌面 + EasyX + VS2022 +- 发现人:用户反馈 +- 关联 Fix ID:`Fix-BUG-20260409-0003` + +## 问题描述 + +- 现象: + - 某些用例在窗口初始大小下直接点击最大化后,新暴露出的区域显示为黑色背景。 + - 已有控件可能出现残留、残影或旧内容未被覆盖。 + - 若先手动拖动边框略微放大,再执行最大化,则问题消失。 +- 影响范围: + - 顶层窗口 resize 收口 + - 背景完整重绘 + - 控件 resize 后的快照/标脏收口 +- 期望结果: + - 无论是拖拽 resize 还是直接最大化,只要 `WM_SIZE` 进入收口流程,都应完成一次完整 redraw。 +- 实际结果: + - 某些“大跨度但合法”的最大化尺寸变化,会在收口中途被直接跳过,导致背景和控件未完成刷新。 + +## 复现信息 + +- 复现条件: + - 初始窗口尺寸与最大化后的客户区尺寸跨度较大。 +- 典型用例: + 1. `KEY == 1`:`1200x400 -> 2160x1417` + 2. `KEY == 2`:`700x510 -> 2160x1417` + 3. `KEY == 3 / 4`:初始尺寸较大时不复现 +- 复现概率:高 +- 证据: + - `WM_SIZE:待处理=(2160x1417), isSizing=0` + - `调整窗口尺寸开始:width=... height=...` + - `尺寸调整被跨度保护跳过:old=(...) new=(...) diff=(...)` + - 异常日志中缺失 `尺寸调整已完成` + +## 初步分析 + +- 疑似位置: + - [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp) 的 `runEventLoop()` resize 收口逻辑 +- 可疑代码特征: + - 使用 `abs(finalW - width) > 1000 || abs(finalH - height) > 1000` 作为跳过条件 + - 命中后直接: + - `needResizeDirty = false` + - `continue` +- 根因判断: + - 该分支把“跨度较大的合法最大化”误判为异常帧 + - 导致后续 `Resize / adaptiveLayout / redrawScene / done log` 全部被跳过 + +## 跟踪信息 + +- 首次发现时间:2026-04-09 +- 最后更新时间:2026-04-09 +- 修复版本:当前工作区 +- 验证版本:待用户回归 +- 备注: + - 当前证据表明问题与 DPI 不是同一级根因,更直接的是跨度阈值误杀。 diff --git a/开发记录/Fix/Fix-BUG-20260409-0003-直接最大化触发收口保护导致黑背景与残影.md b/开发记录/Fix/Fix-BUG-20260409-0003-直接最大化触发收口保护导致黑背景与残影.md new file mode 100644 index 0000000..8487ecb --- /dev/null +++ b/开发记录/Fix/Fix-BUG-20260409-0003-直接最大化触发收口保护导致黑背景与残影.md @@ -0,0 +1,80 @@ +# Fix-BUG-20260409-0003 + +> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。 + +## 关联信息 + +- Fix ID: `Fix-BUG-20260409-0003` +- 关联 BUG ID: `BUG-20260409-0003` +- 修复目标: 取消对合法大跨度最大化的误拦截,仅保留非法尺寸保护 +- 状态:已完成 / 待用户回归 +- 负责人:Codex 协作修改 +- 分支 / 版本:`master` + +## 根因分析 + +- 旧逻辑问题: + - `runEventLoop()` 的 resize 收口中存在“跨度保护”: + - `abs(finalW - width) > 1000 || abs(finalH - height) > 1000` + - 命中后直接跳过整个收口流程。 +- 为什么会出问题: + - 该判断关注的是“变化跨度”,而不是“尺寸是否合法”。 + - 在大屏、窗口初始尺寸较小、直接最大化等正常场景下,跨度超过 `1000` 很常见。 +- 为什么之前未必稳定暴露: + - 当初始尺寸较大时,最大化跨度较小,不会命中阈值。 + - 先手动拉大一点后再最大化,也会避开该阈值。 + +## 修复方案 + +- 修复思路: + - 删除“跨度过大即跳过”的保护语义。 + - 改成“仅非法客户区尺寸才跳过”。 + - 保留关键 `DEBUG` 日志,方便追踪极端尺寸帧。 +- 关键改动: + - 删除旧的 `>1000` 误拦截分支。 + - 引入虚拟桌面尺寸: + - `SM_CXVIRTUALSCREEN` + - `SM_CYVIRTUALSCREEN` + - 仅在以下情况下跳过本次收口: + - `actualWidth <= 0` + - `actualHeight <= 0` + - `actualWidth > max(10000, virtualScreenWidth * 2)` + - `actualHeight > max(10000, virtualScreenHeight * 2)` + - 对“大跨度但合法”的 resize 增加 `DEBUG` 日志,但继续执行收口。 +- 涉及文件 / 类 / 函数: + - `Window.cpp` + - `Window::runEventLoop()` +- 输出日志: + - `尺寸调整被非法尺寸保护跳过` + - `检测到大跨度尺寸调整,继续执行收口` + +## 影响评估 + +- 对外行为变化: + - 有。直接最大化不再被 `>1000` 阈值拦截。 +- 兼容性影响: + - 正向兼容。正常拖拽 resize、正常最大化都仍然走原有收口流程。 +- 风险: + - 如果系统偶发产生一次极端异常客户区尺寸,可能会更多依赖当前“非法尺寸保护”而不是原来的跨度阈值。 + - 但相较于误杀合法最大化,这个风险更合理。 + +## 验证结果 + +- 验证方式: + 1. 在 `>1000` 旧保护分支中先加 `DEBUG` 日志,确认 `KEY == 1 / 2` 命中。 + 2. 替换为非法尺寸保护后,编译 [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp)。 + 3. 按 `KEY == 1 ~ 4` 做逻辑推演。 +- 理论结果: + - `KEY == 1 / 2`:现在应打印“大跨度但继续执行收口”日志,并继续完成 `尺寸调整已完成`。 + - `KEY == 3 / 4`:原本正常,行为保持一致。 +- 备注: + - 当前这轮无法直接替用户跑 GUI 自动化,只能做逻辑推演和源码级编译验证。 + +## 落地信息 + +- Commit: + - `f567369`:修改前快照 + - 当前工作区:已替换跨度保护,待提交 +- 发布版本:待定 +- 备注: + - 同轮顺手删除了 `Window.cpp` 中一个未使用的 `ExMessage mm;` 遗留变量。 diff --git a/开发记录/功能变更/Feature-20260409-0006-窗口最大化收口保护调整.md b/开发记录/功能变更/Feature-20260409-0006-窗口最大化收口保护调整.md new file mode 100644 index 0000000..1d5ad4e --- /dev/null +++ b/开发记录/功能变更/Feature-20260409-0006-窗口最大化收口保护调整.md @@ -0,0 +1,54 @@ +# Feature-20260409-0006 + +> 适用场景:记录小到中等规模的接口、行为、默认值、交互和内部机制变化。 + +## 基本信息 + +- ID: `Feature-20260409-0006` +- 标题: 窗口最大化收口保护从跨度阈值改为非法尺寸判断 +- 类型:修改 +- 级别:L2 +- 模块:Window / Resize +- 状态:已完成 / 待用户回归 +- 关联: + - `BUG-20260409-0003` + - `Fix-BUG-20260409-0003` + +## 背景 + +- 旧实现把“大跨度尺寸变化”视为异常帧。 +- 在初始窗口较小、显示器较大时,正常最大化会被误伤。 + +## 变更内容 + +- 删除旧的跨度保护语义: + - 不再以 `abs(finalW - width)` 或 `abs(finalH - height)` 决定是否跳过。 +- 新增非法尺寸保护语义: + - 仅拦截 `<= 0` 的客户区尺寸 + - 仅拦截明显超出虚拟桌面合理范围的客户区尺寸 +- 新增两类调试日志: + - 非法尺寸跳过日志 + - 大跨度但继续执行日志 + +## 对外影响 + +- 直接最大化的行为更稳定。 +- 之前会黑背景/残影的场景,现在理论上应恢复为正常收口。 + +## 兼容性 + +- 向后兼容。 +- 不涉及公开 API 变化。 + +## 验证方式 + +- 用户回归 `KEY == 1 ~ 4` +- 日志检查是否继续打印 `尺寸调整已完成` +- 单文件编译验证 `Window.cpp` + +## 落地信息 + +- 涉及文件: + - `Window.cpp` +- 落地状态: + - 当前工作区已完成,待提交