# Fix-BUG-20260409-0001

> 适用场景：记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。

## 关联信息

- Fix ID: Fix-BUG-20260409-0001
- 关联 BUG ID: BUG-20260409-0001
- 修复目标: 收紧对话框快照、遮挡、事件分发和统一提交重绘链
- 状态：已完成 / 待持续回归
- 负责人：Codex 协作修改
- 分支 / 版本：`master`

## 根因分析

- 根因:
  - 旧实现中“作废快照”和“回贴旧背景”混在一起，resize 时容易把旧画面贴回屏幕。
  - 非模态对话框与底层控件之间缺少明确的事件阻断和 hover 清理机制。
  - 重绘长期依赖控件即时局部绘制，导致底层先画、对话框后补，容易闪烁或层级错乱。
  - 标题曾作为独立 `Label` 存在，导致额外一层背景快照，时序敏感。
- 触发条件:
  - 非模态遮挡下的 hover / click
  - 模态或非模态 resize
  - 对话框打开 / 关闭
- 为什么之前没发现：
  - 问题主要集中在“高频交互 + 对话框遮挡 + resize”组合路径

## 修复方案

- 修复思路:
  - 拆分快照语义
  - 收紧对话框事件消费
  - 让 `Dialog` 在窗口变化时只重新居中
  - 将标题绘制并入 `Dialog`
  - 把重绘推进到窗口统一收口
- 关键改动:
  - `discardBackground()` 与 `invalidateBackgroundSnapshot()` 分离
  - `Dialog` 标题改为直接绘制，不再使用独立 `Label`
  - `Dialog::handleEvent()` 对自身区域内鼠标事件统一吞掉
  - 对话框关闭及遮挡场景补发合成 `WM_MOUSEMOVE`，清理底层 hover
  - `Window` 引入托管重绘，先重绘受影响 root，再补画相交对话框
- 涉及文件 / 类 / 函数:
  - `Window.h / Window.cpp`
  - `Dialog.h / Dialog.cpp`
  - `Control.h / Control.cpp`
  - `Canvas.cpp`
  - `TabControl.cpp`
  - `Button.cpp`
  - `TextBox.cpp`
- 影响的 API / 行为:
  - `Dialog` 在窗口变化时的语义固定为“只重新居中，不拉伸”
  - 非模态对话框覆盖区域内不再允许鼠标事件穿透到底层
- 关键约束 / 不变量:
  - 对话框始终绘制在普通控件之上
  - 托管分发期间控件不直接提交绘制
  - resize 和对话框开关仍走整场景重绘兜底

## 影响评估

- 影响范围:
  - 所有包含 `Dialog`、按钮 hover、遮挡重绘的场景
- 兼容性影响：有（行为更严格，底层控件不再穿透响应）
- 行为变化：有（对话框 resize 只居中；遮挡区域 hover 不再透传）
- 性能影响：有正向变化（减少无意义整场景补画）；同时在某些兜底场景仍保留整场景重绘
- 回归风险:
  - 托管重绘 root 选择不当可能导致嵌套容器局部重绘异常
  - 需持续回归 `Canvas / TabControl / Table / Dialog` 组合场景

## 验证结果

- 验证步骤:

1. 在 `KEY == 2 / 4` 中打开非模态对话框，快速在遮挡区域附近 hover 和点击。
2. 在 `KEY == 3 / 4` 中打开模态对话框并拖动窗口大小。
3. 关闭对话框后观察底层按钮 hover 恢复。

- 验证结果:
  - 穿透、残影、关闭后 hover 不恢复等主问题已压住。
- 回归检查：
  - 保留对“快速划过多个按钮时偶发一帧双高亮”的已知限制记录。
- 验证证据：
  - 静态推演 + 多轮编译验证 + 用户回归反馈

## 落地信息

- Commit:
  - `4a6e153`
  - `7f8431a`
  - `b07a4ec`
  - 当前工作区未提交阶段
- PR：[可选]
- 发布版本：[可选]
- 备注：该修复跨多个阶段逐步收敛，不是一次性完成
