完成 StellarX 核心重构、缺陷修复与测试收口

This commit is contained in:
Codex
2026-09-30 01:23:18 +08:00
parent 9155a86a8a
commit 568ffc40eb
85 changed files with 5011 additions and 645 deletions
+32
View File
@@ -48,6 +48,13 @@
- 日志降噪
- 模板体系与开发记录目录建立
7. 第五阶段:全量缺陷修复与自动化验证(当前工作区)
- 主要内容:
- 新增 `StellarX.Core` 核心静态库,拆分框架实现与应用入口
- 修复控件所有权、空页签、快照、几何残影、Dialog、Table、日志、MBCS、Tooltip 和事件循环问题
- 新增 Native Unit Test、x64 ASan、EasyX UI Fixture 和 PowerShell 自动化测试入口
- 完成 Debug x64 / ASan、Release x64、Debug Win32 逻辑测试,以及 UI 三轮回归
## 记录索引
### BUG
@@ -67,10 +74,17 @@
- [Feature-20260409-0003 输入事件、hover 与遮挡交互调整](./功能变更/Feature-20260409-0003-输入事件与遮挡交互调整.md)
- [Feature-20260409-0004 测试用例与可观测性调整](./功能变更/Feature-20260409-0004-测试用例与可观测性调整.md)
- [Feature-20260409-0005 开发记录与模板体系整理](./功能变更/Feature-20260409-0005-开发记录与模板体系整理.md)
- [Feature-20260915-0001 全量缺陷修复与 API 调整](./功能变更/Feature-20260915-0001-全量缺陷修复与API调整.md)
### 模块
- [Module-20260409-0001 Window 托管重绘与覆盖合成机制](./模块/Module-20260409-0001-Window托管重绘与覆盖合成机制.md)
- [Module-20260915-0001 StellarX 核心库与自动化测试基础设施](./模块/Module-20260915-0001-StellarX核心库与自动化测试基础设施.md)
### 配套文档
- [MIGRATION.md](./MIGRATION.md):破坏性 API 迁移说明
- [TESTING.md](./TESTING.md):测试入口、覆盖范围和诊断产物说明
## 覆盖关系
@@ -135,3 +149,21 @@
- 第二阶段托管重绘
- 注释补充与校正
- 记录与模板体系整理
## 第五阶段覆盖关系
- `StellarX.Core.vcxproj`
- 统一承载框架源码和公共/内部头文件。
- 关联:
- [Module-20260915-0001 StellarX 核心库与自动化测试基础设施](./模块/Module-20260915-0001-StellarX核心库与自动化测试基础设施.md)
- [Feature-20260915-0001 全量缺陷修复与 API 调整](./功能变更/Feature-20260915-0001-全量缺陷修复与API调整.md)
- `tests / scripts`
- 覆盖逻辑单元测试、ASan、EasyX UI Fixture、关键像素断言和诊断产物。
- 关联:
- [TESTING.md](./TESTING.md)
- [Module-20260915-0001 StellarX 核心库与自动化测试基础设施](./模块/Module-20260915-0001-StellarX核心库与自动化测试基础设施.md)
- `Control / Window / Canvas / Button / Label / TextBox / Table / Dialog / TabControl / SxLogger`
- 本批次高、中、低严重度问题和验证用例统一记录于:
- [Feature-20260915-0001 全量缺陷修复与 API 调整](./功能变更/Feature-20260915-0001-全量缺陷修复与API调整.md)
@@ -19,7 +19,7 @@
## 根因
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp) 的 `runEventLoop()` resize 收口中存在一段历史“跨度保护”。
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp) 的 `runEventLoop()` resize 收口中存在一段历史“跨度保护”。
- 该保护把“宽高变化超过 1000”视为异常帧,直接终止收口:
- 不再执行 `adaptiveLayout`
- 不再执行 `Resize`
@@ -0,0 +1,53 @@
# BUG-20251209-0001
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
## 基本信息
- ID: BUG-20251209-0001
- 标题: TabControl 由不可见设置为可见时绘制错乱
- 状态:已修复
- 严重性:S3
- 优先级:P2
- 模块: TabControl
- 版本 / 分支: V2.3.1
- 环境: Visual Studio / Debug / Release
- 发现人: 用户
- 关联 Fix ID: Fix-BUG-20251209-0001
## 问题描述
- 现象: 调用 `TabControl::setIsVisible()` 将选项卡由不可见变为可见时,会绘制出未激活的页面。
- 影响范围: 多页面 TabControl 在隐藏后重新显示时可能出现页面叠加,造成绘制错乱并影响用户体验。
- 期望结果: TabControl 可见后只绘制当前激活页;如果没有激活页,则不绘制任何页。
- 实际结果: 所有页面被绘制出来,导致未激活页和激活页重叠。
## 复现信息
- 前置条件:[可选] 存在至少两个页面的 TabControl。
- 复现步骤:
1. 将 TabControl 设置为不可见。
2. 通过事件或回调将 TabControl 设置为可见。
3. 观察页面绘制结果。
- 复现概率:必现
- 最小复现 Demo:[可选] TabControl 隐藏 / 显示切换场景
- 证据:PDF 历史记录描述隐藏后恢复显示会绘制所有页;初步定位到 `TabControl::setIsVisible()` 的可见性传播逻辑。
## 初步分析
- 疑似位置: `TabControl::setIsVisible()`
- 触发条件: TabControl 从不可见切换为可见时,对子页面统一传播可见状态。
- 相关线索:
- `show` 状态恢复为 true 后,非激活页也参与绘制。
- TabControl 的页面可见性没有与当前激活状态保持一致。
- 最近相关改动:[可选]
## 跟踪信息
- 首次发现时间: 2025-12-09
- 最后更新时间: 2026-09-19(从 PDF 整理入库)
- 修复版本: v2.3.2
- 验证版本:[可选] v2.3.2
- 备注:[可选] 来源原始 ID 为 `BUG-20251209`;PDF 未包含 `-XXXX` 后缀,入库时补充序号。
@@ -0,0 +1,60 @@
# BUG-20251220-0001
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
## 基本信息
- ID: BUG-20251220-0001
- 标题: TabControl::setActiveIndex 绘制前调用崩溃
- 状态:已修复
- 严重性:S1
- 优先级:P0
- 模块: TabControl
- 版本 / 分支: v2.3.1
- 环境: Visual Studio / Debug / Release
- 发现人: 用户
- 关联 Fix ID: Fix-BUG-20251220-0001
## 问题描述
- 现象: 在 TabControl 首次绘制前或进入主事件循环前调用 `setActiveIndex()`,会触发空指针访问并导致程序崩溃。
- 影响范围: 影响默认激活页设置流程,启动阶段即可崩溃,严重影响使用。
- 期望结果: `setActiveIndex()` 可在绘制前记录默认激活页,并在 TabControl 第一次绘制时完成默认激活。
- 实际结果: 绘制前调用会直接崩溃,无法设置默认激活页。
## 复现信息
- 前置条件:[可选] TabControl 尚未绘制。
- 复现步骤:
1. 创建 TabControl 对象。
2. 在首次绘制前调用 `setActiveIndex(0)`。
3. 运行程序并观察崩溃。
- 复现概率:必现
- 最小复现 Demo:
```cpp
auto ta = std::make_unique<TabControl>(10, 10, 1100, 300);
ta->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
ta->setActiveIndex(0);
```
- 证据:PDF 历史记录描述绘制前调用会造成空指针访问。
## 初步分析
- 疑似位置: `TabControl::setActiveIndex()`
- 触发条件: TabControl / 页签按钮尚未完成绘制和初始化时,提前执行激活页切换逻辑。
- 相关线索:
- 默认激活应属于延迟到首次绘制后的行为。
- 绘制前直接触发页签按钮点击回调,会访问尚未初始化的内部状态。
- 最近相关改动:[可选]
## 跟踪信息
- 首次发现时间: 2025-12-20
- 最后更新时间: 2026-09-19(从 PDF 整理入库)
- 修复版本: v2.3.2
- 验证版本:[可选] v2.3.2
- 备注:[可选] 来源原始 ID 为 `BUG-20251220`;PDF 未包含 `-XXXX` 后缀,入库时补充序号。
@@ -0,0 +1,66 @@
# BUG-20260109-0001
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
## 基本信息
- ID: BUG-20260109-0001
- 标题: TextBox::setText 进入事件循环前调用崩溃
- 状态:已修复
- 严重性:S1
- 优先级:P0
- 模块: TextBox
- 版本 / 分支: v2.3.2(基础版 EasyX + Win32)
- 环境: Windows 10/11 x64 + Visual Studio 2022(MSVC)Debug / Release
- 发现人: 用户
- 关联 Fix ID: Fix-BUG-20260109-0001
## 问题描述
- 现象: 在 Window 尚未创建或图形上下文尚未初始化时调用 `TextBox::setText()` 设置默认文本,会触发 `TextBox::draw()`,进而进入依赖 EasyX 图形上下文的绘制 / 抓屏逻辑,导致访问冲突或中断。
- 影响范围: 所有“先创建控件并设置默认文本,再创建 Window / 挂载控件树”的初始化流程。
- 期望结果: `setText()` 只更新控件状态并标记需要重绘,不应在生命周期未就绪时主动绘制。
- 实际结果: `setText()` 内部直接调用 `draw()`,绘制链路在图形上下文未就绪时崩溃。
## 复现信息
- 前置条件:[可选] Window / EasyX 图形上下文尚未初始化。
- 复现步骤:
1. 在创建 Window 或首次 `draw()` 前创建 TextBox。
2. 调用 `TextBox::setText()` 设置默认文本。
3. 随后才创建 Window 或将 TextBox 添加到 Canvas / Window。
4. 观察程序在 `setText()` 阶段崩溃。
- 复现概率:必现
- 最小复现 Demo:
```cpp
Window window(1024, 768, NULL, RGB(255, 255, 255), "StellarX Application");
auto initLibPathBox = std::make_unique<TextBox>(230, 230, 700, 30);
initLibPathBox->setText("textText");
window.addControl(std::move(initLibPathBox));
window.draw();
return window.runEventLoop();
```
- 证据:PDF 历史记录描述 `setText()` 触发 `draw()` 后进入 `saveBackground()` / `getimage()` 等图形上下文相关逻辑。
## 初步分析
- 疑似位置: `TextBox::setText()`
- 触发条件: 控件属性设置发生在 Window 绘制 / EasyX 图形上下文初始化之前。
- 相关线索:
- setter 不应带有依赖图形上下文的即时绘制副作用。
- `draw()` 会进入背景快照与图像抓取链路。
- 最近相关改动:[可选]
## 跟踪信息
- 首次发现时间: 2026-01-09
- 最后更新时间: 2026-09-19(从 PDF 整理入库)
- 修复版本: v3.0.0
- 验证版本:[可选] v3.0.0
- 备注:[可选] 来源原始 ID 为 `BUG-20260109`;同日另有 Table 重影问题,入库时按顺序拆为 `0001` 与 `0002`。
@@ -0,0 +1,60 @@
# BUG-20260109-0002
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
## 基本信息
- ID: BUG-20260109-0002
- 标题: TabControl 切换后 Table 新旧内容重叠
- 状态:已修复
- 严重性:S2
- 优先级:P1
- 模块: TabControl / Canvas / Table / 背景快照 / 脏标记重绘链路
- 版本 / 分支: 基础版(EasyX + Win32)/ v2.3.2
- 环境: Windows x64 / Visual Studio 2022 / EasyX(Debug / Release 均可能)
- 发现人: 用户
- 关联 Fix ID: Fix-BUG-20260109-0002
## 问题描述
- 现象: 运行 TabControl 示例后,切换到“按钮1”页签并触发“重置表格数据”,随后打开 / 关闭其他页签再切回“按钮1”,Table 稳定出现旧内容与新内容叠加;偶发情况下重置数据当次即出现重影。
- 影响范围: 使用背景快照擦除与局部重绘组合的容器 / 控件,尤其是 Table 透明文字背景与 Null 填充模式场景。
- 期望结果: 切换页签、关闭页签、重置数据后,Table 只显示当前数据,不残留旧像素。
- 实际结果: Table 区域出现旧行文本像素残留,与新内容叠在同一位置。
## 复现信息
- 前置条件:[可选] TabControl 示例中包含 Table 和“重置表格数据”按钮。
- 复现步骤:
1. 运行测试程序(TabControl 示例)。
2. 切换到“按钮1”页签。
3. 点击“重置表格数据”(清空并重新 `setData()`)。
4. 随意切换到其他页签,可伴随关闭页签操作。
5. 再切回“按钮1”。
6. 观察 Table 区域是否出现旧内容与新内容重叠。
- 复现概率:必现;重置数据当次直接重影为偶现。
- 最小复现 Demo:`z-testDome.cpp` 中 TabControl + Table + 重置按钮场景;Null FillMode 与透明文字背景更明显。
- 证据:日志显示 Canvas 快照刚丢弃或未就绪时,Table 可见性恢复立即触发局部重绘,随后 `saveBackground()` 把已绘制像素截入背景快照。
## 初步分析
- 疑似位置:
- `Control::setIsVisible(true)`
- `Canvas::requestRepaint()`
- `TabControl::requestRepaint()`
- 触发条件: 页签切换 / 关闭期间,父容器快照未就绪或 dirty 未清理时,子控件抢跑执行局部绘制。
- 相关线索:
- `show=true` 时立即向上 `requestRepaint()`。
- `Canvas::requestRepaint()` 在 `hasSnap=0` / `dirty=1` 状态下仍允许对子控件局部 `draw()`。
- 页签切换期间尺寸变化会触发丢背景快照和标脏。
- 最近相关改动:[可选] `Control::setIsVisible(true)` 变为立即请求重绘。
## 跟踪信息
- 首次发现时间: 2026-01-09
- 最后更新时间: 2026-09-19(从 PDF 整理入库)
- 修复版本: v3.0.0
- 验证版本:[可选] v3.0.0
- 备注:[可选] 来源原始 ID 为 `BUG-20260109`;同日另有 TextBox 生命周期问题,入库时按顺序拆为 `0001` 与 `0002`。
@@ -0,0 +1,53 @@
# BUG-20260122-0001
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
## 基本信息
- ID: BUG-20260122-0001
- 标题: Window::runEventLoop 普通控件事件分发顺序
- 状态:已修复
- 严重性:S3
- 优先级:P2
- 模块: Window
- 版本 / 分支: v3.0.0
- 环境: Windows 10/11 x64 + Visual Studio 2022(MSVC)Debug / Release
- 发现人: 用户
- 关联 Fix ID: Fix-BUG-20260122-0001
## 问题描述
- 现象: `Window::runEventLoop()` 对普通控件分发事件时采用正序遍历 `controls`,控件重叠时下层控件可能抢走点击 / 悬停事件,导致行为与绘制顺序不一致。
- 影响范围: 普通控件存在重叠时,事件命中顺序可能不符合 Z 序直觉。
- 期望结果: 上层控件优先接收事件,下层控件不应抢走事件。
- 实际结果: 正序遍历导致下层控件可能先处理事件。
## 复现信息
- 前置条件:[可选] Window 中存在相互重叠的普通控件。
- 复现步骤:
1. 创建存在重叠区域的普通控件。
2. 在重叠区域触发点击或悬停事件。
3. 观察是否由下层控件优先处理事件。
- 复现概率:必现(按 PDF 记录;实际复现未提供)
- 最小复现 Demo:[可选] 无
- 证据:PDF 历史记录指出该问题来自之前版本误将逆序分发改为正序分发。
## 初步分析
- 疑似位置: `Window::runEventLoop()`
- 触发条件: 普通控件重叠,且事件落在重叠区域。
- 相关线索:
- 普通控件绘制顺序与事件分发顺序应保持 Z 序一致。
- `Window::controls` 正序遍历会让较早加入的下层控件先处理事件。
- 最近相关改动:[可选] 某个版本中普通控件遍历顺序被错误修改。
## 跟踪信息
- 首次发现时间: 2026-01-22
- 最后更新时间: 2026-09-19(从 PDF 整理入库)
- 修复版本: v3.0.1
- 验证版本:[可选] v3.0.1
- 备注:[可选] 来源原始 ID 为 `BUG-20260122`;PDF 未包含 `-XXXX` 后缀,入库时补充序号。
@@ -0,0 +1,53 @@
# BUG-20260203-0001
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
## 基本信息
- ID: BUG-20260203-0001
- 标题: 对话框打开时错误下发合成 WM_MOUSEMOVE
- 状态:已修复
- 严重性:S3
- 优先级:P2
- 模块: Window
- 版本 / 分支: v3.0.0
- 环境: Windows 10/11 x64 + Visual Studio 2022(MSVC)Debug / Release
- 发现人: 用户
- 关联 Fix ID: Fix-BUG-20260203-0001
## 问题描述
- 现象: 对话框打开时会触发本应只在对话框关闭时执行的合成 `WM_MOUSEMOVE`,并下发给所有可见控件以刷新 hover 状态。
- 影响范围: 对话框打开路径产生额外事件下发,带来性能影响和不必要的 hover 状态刷新。
- 期望结果: 合成 `WM_MOUSEMOVE` 只在对话框被关闭后下发一次。
- 实际结果: 对话框打开和关闭时都会下发合成 `WM_MOUSEMOVE`。
## 复现信息
- 前置条件:[可选] 存在可弹出的对话框。
- 复现步骤:
1. 启动事件循环。
2. 触发对话框弹出。
3. 观察日志中是否在对话框打开阶段出现合成 `WM_MOUSEMOVE` 下发记录。
- 复现概率:必现
- 最小复现 Demo:[可选] 弹出一个对话框即可。
- 证据:PDF 日志显示对话框弹出后出现“对话框关闭,合成WM_MOUSEMOVE已下发”。
## 初步分析
- 疑似位置: `Window::runEventLoop()`
- 触发条件: 对话框打开路径触发重绘判断时,也执行了对话框关闭后才需要的鼠标移动合成逻辑。
- 相关线索:
- 相关变量为 `dialogClose`。
- 合成 `WM_MOUSEMOVE` 的执行缺少对 `dialogClose` 为真的限定。
- 最近相关改动:[可选]
## 跟踪信息
- 首次发现时间: 2026-02-03
- 最后更新时间: 2026-09-19(从 PDF 整理入库)
- 修复版本: v3.0.1
- 验证版本:[可选] v3.0.1
- 备注:[可选] 来源原始 ID 为 `BUG-20260203`;PDF 未包含 `-XXXX` 后缀,入库时补充序号。
@@ -0,0 +1,53 @@
# BUG-20260206-0001
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
## 基本信息
- ID: BUG-20260206-0001
- 标题: 对话框打开时持续触发全量重绘
- 状态:已修复
- 严重性:S3
- 优先级:P2
- 模块: Window
- 版本 / 分支: v3.0.0 / master
- 环境: Windows 10/11 x64 + Visual Studio 2022(MSVC)Debug / Release
- 发现人: 用户
- 关联 Fix ID: Fix-BUG-20260206-0001
## 问题描述
- 现象: 对话框保持打开时持续触发全量重绘,重复重绘所有打开的对话框和可见控件。
- 影响范围: 对话框打开期间产生不必要的全量绘制,造成性能影响。
- 期望结果: 只在对话框打开或关闭时触发必要重绘,确保对话框顶层显示并清除关闭残留。
- 实际结果: 重绘前以“是否有对话框可见或关闭”为依据,导致对话框保持可见时持续触发重绘。
## 复现信息
- 前置条件:[可选] 存在可弹出的对话框。
- 复现步骤:
1. 触发对话框弹出。
2. 保持对话框打开。
3. 观察日志中是否持续输出全量重绘相关记录。
- 复现概率:必现
- 最小复现 Demo:触发任意对话框即可。
- 证据:PDF 日志中同一时间片持续出现“对话框打开/关闭,触发全量重绘”。
## 初步分析
- 疑似位置: `Window::runEventLoop()`
- 触发条件: 对话框处于可见状态且事件循环持续运行。
- 相关线索:
- 相关变量为 `needredraw`。
- 重绘判断基于“是否存在可见对话框”,而不是“是否刚刚打开 / 关闭对话框”。
- 最近相关改动:[可选]
## 跟踪信息
- 首次发现时间: 2026-02-06
- 最后更新时间: 2026-09-19(从 PDF 整理入库)
- 修复版本: v3.0.1
- 验证版本:[可选] v3.0.1
- 备注:[可选] 来源原始 ID 为 `BUG-20260206`;PDF 未包含 `-XXXX` 后缀,入库时补充序号。
@@ -0,0 +1,54 @@
# BUG-20260217-0001
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
## 基本信息
- ID: BUG-20260217-0001
- 标题: TabControl::getActiveIndex 无激活页返回最后索引
- 状态:已修复
- 严重性:S2
- 优先级:P1
- 模块: TabControl
- 版本 / 分支: v3.0.0 / master
- 环境: Windows 10/11 x64 + Visual Studio 2022(MSVC)Debug / Release
- 发现人: 用户
- 关联 Fix ID: Fix-BUG-20260217-0001
## 问题描述
- 现象: 当 TabControl 的页签对列表不为空,但所有页签均未激活 / 未打开时,调用 `getActiveIndex()` 会返回最后一个页签的索引。
- 影响范围: 用户通过 `getActiveIndex()` 判断当前激活页时,可能拿到错误索引。
- 期望结果: 无页签激活时返回 `-1`;有页签激活时返回对应索引。
- 实际结果: 无页签激活时返回最后一个页签索引。
## 复现信息
- 前置条件:[可选] TabControl 页签列表不为空,但所有页签都未激活。
- 复现步骤:
1. 创建含有页签的 TabControl。
2. 保持所有页签未激活 / 未打开。
3. 调用 `TabControl::getActiveIndex()`。
4. 观察返回值。
- 复现概率:必现
- 最小复现 Demo:[可选]
- 证据:PDF 历史记录标记为代码分析发现。
## 初步分析
- 疑似位置: `TabControl::getActiveIndex()`
- 触发条件: 页签列表非空且无任何页签处于激活状态。
- 相关线索:
- 旧逻辑未处理“列表非空但全部未激活”的兜底分支。
- 返回值应能区分无激活页与最后一页激活。
- 最近相关改动:[可选]
## 跟踪信息
- 首次发现时间: 2026-02-17
- 最后更新时间: 2026-09-19(从 PDF 整理入库)
- 修复版本: v3.0.1
- 验证版本:[可选] v3.0.1
- 备注:[可选] 来源原始 ID 为 `BUG-20260217`;PDF 未包含 `-XXXX` 后缀,入库时补充序号。
@@ -48,7 +48,7 @@
## 初步分析
- 疑似位置:
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp) 的 `runEventLoop()` resize 收口逻辑
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp) 的 `runEventLoop()` resize 收口逻辑
- 可疑代码特征:
- 使用 `abs(finalW - width) > 1000 || abs(finalH - height) > 1000` 作为跳过条件
- 命中后直接:
@@ -46,7 +46,7 @@
## 初步分析
- 疑似位置:
- [`Button.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Button.cpp)
- [`Button.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Button.cpp)
- `Button::hideTooltip()`
- 触发条件:
- Tooltip 已经显示
@@ -50,9 +50,9 @@
## 初步分析
- 疑似位置:
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp)
- [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Canvas.cpp)
- [`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/TabControl.cpp)
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp)
- [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Canvas.cpp)
- [`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/TabControl.cpp)
- 触发条件:
- 局部重绘提交写入了下层像素,但没有做 overlay 兄弟补画
- 相关线索:
@@ -0,0 +1,54 @@
# BUG-20260929-0001
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
## 基本信息
- ID: BUG-20260929-0001
- 标题: Table 运行期重置后页码标签被旧分页按钮快照覆盖
- 状态:已修复
- 严重性:S2
- 优先级:P1
- 模块: Table / Label / Button / 背景快照
- 版本 / 分支: 当前工作区
- 环境: Windows / EasyX / KEY1
- 发现人: 用户
- 关联 Fix ID: Fix-BUG-20260929-0001
## 问题描述
- 现象: KEY1 页签 1 中点击页内按钮重置表头和数据后,分页区中间的页码标签只显示一部分;触发表格重绘或与其他页签按钮进行 hover 交互后恢复完整。
- 影响范围: Table 运行期数据变化导致表格宽度及分页控件位置同时变化的场景。
- 期望结果: 重置后的首帧立即完整显示新页码,不依赖后续 hover、翻页或页签重绘修复。
- 实际结果: 页码已经绘制,但随后移动的分页按钮回贴旧位置背景快照,覆盖了新页码的一部分。
## 复现信息
- 前置条件: 启动 KEY1 并进入页签 1。
- 复现步骤:
1. 点击页签 1 内用于重置表格的“按钮”。
2. 不移动鼠标,立即观察“上一页”和“下一页”之间的页码标签。
3. 对表格或页签 2 按钮进行 hover 交互,触发后续重绘。
- 复现概率:稳定复现
- 最小复现 Demo:KEY1 页签 1
- 证据:用户截图中重置后的页码标签首帧显示不全,后续重绘恢复。
## 初步分析
- 疑似位置: `Table::drawPageNum()`、`Table::drawButton()` 和内部控件 `Control::setX()` 的调用顺序。
- 触发条件: 新旧表格宽度不同,分页 Label 和分页按钮需要同时横向移动。
- 相关线索:
- 旧顺序先绘制页码,再在 `drawButton()` 中移动分页按钮。
- 分页按钮移动时会通过 `discardBackground()` 回贴旧背景。
- 旧按钮区域与新页码区域相交,导致新页码被局部擦除。
- 最近相关改动:[可选] Table 页码强制设脏和分页按钮整表重绘链已经存在,但未覆盖内部控件同帧搬移后的旧快照覆盖。
## 跟踪信息
- 首次发现时间: 2026-09-29
- 最后更新时间: 2026-09-30
- 修复版本: 当前工作区
- 验证版本: 2026-09-29 非 UI 全矩阵通过;2026-09-30 用户完成 KEY1 至 KEY6 手工回归,暂未发现明显问题
- 备注:[可选]
@@ -0,0 +1,64 @@
# Fix-BUG-20251209-0001
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
## 关联信息
- Fix ID: Fix-BUG-20251209-0001
- 关联 BUG ID: BUG-20251209-0001
- 修复目标: TabControl 由不可见变为可见时,仅绘制激活页;无激活页时不绘制任何页,避免页面重叠和脏帧。
- 状态:已完成
- 负责人: 未记录
- 分支 / 版本: master / v2.3.2
## 根因分析
- 根因: `TabControl::setIsVisible(true)` 对所有页签 / 页面统一传播 true 并置脏,导致恢复可见后本应不可绘制的非激活页也进入绘制流程。
- 触发条件: 执行 `setIsVisible(false)` 后再执行 `setIsVisible(true)`。
- 为什么之前没发现:[可选] 缺少“隐藏 -> 显示”回归用例,尤其缺少“多个页面但只允许激活页绘制”的断言。
- 关键证据:[可选] PDF 历史记录明确将疑似位置定位到 `TabControl::setIsVisible()` 与 `show` 状态传播。
## 修复方案
- 修复思路: TabControl 可见性向页面传播时按激活状态过滤;显示时只允许激活页可见,隐藏时所有页都不可见。
- 关键改动:
- `TabControl.cpp::setIsVisible()` 在参数为 true 时,根据激活页判定对子页面传递 true / false。
- 参数为 false 时,对所有页面传递 false。
- 绘制入口可选择增加“仅绘制激活页”的防御性约束。
- 涉及文件 / 类 / 函数:
- `TabControl.cpp`
- `TabControl::setIsVisible()`
- 影响的 API / 行为:[可选] 对外 API 无变化;行为层面修正错误绘制。
- 关键约束 / 不变量:[可选] 任意时刻可参与绘制的页面集合最多只有一个激活页。
- 回滚点 / 开关:[可选] 回退 TabControl 页面可见性传播逻辑。
## 影响评估
- 影响范围: TabControl 隐藏 / 显示、页面激活状态、页面绘制链路。
- 兼容性影响:无(纠错式兼容)
- 行为变化:有(非激活页不再随 TabControl 显示而被绘制)
- 性能影响:无负面影响,减少非激活页绘制后预期更优或持平。
- 回归风险:
- 如果激活判定逻辑异常导致“无激活页”,恢复显示后会表现为空白页;该行为符合修复目标。
- 如果外部代码错误依赖非激活页仍可见,可能观察到行为差异。
## 验证结果
- 验证步骤:
1. 创建含不少于 2 个页的 TabControl,并设置激活页为 0。
2. 调用 `setIsVisible(false)`,再通过事件调用 `setIsVisible(true)`。
3. 确认仅激活页显示,非激活页不绘制、不重叠。
4. 切换激活页后重复隐藏 / 显示,确认仍只绘制当前激活页。
5. 验证无激活页场景,确认不绘制任何页。
- 验证结果: PDF 历史记录标记已修复。
- 回归检查:[可选] 嵌套容器可见性、裁剪、鼠标事件命中、布局刷新、脏标记 / 快照重绘路径。
- 验证证据:[可选]
## 落地信息
- Commit: 0c1cf2938f0d6c5684eeb318f17a8443e0c8cf5a
- PR:[可选] git
- 发布版本: v2.3.2
- 备注: 来源原始 Fix 记录未包含 `-XXXX` 后缀,入库时按 BUG ID 补充为 `Fix-BUG-20251209-0001`。
@@ -0,0 +1,69 @@
# Fix-BUG-20251220-0001
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
## 关联信息
- Fix ID: Fix-BUG-20251220-0001
- 关联 BUG ID: BUG-20251220-0001
- 修复目标: 正确支持绘制前设置默认激活页,并在首次绘制时激活对应页面;运行时调用则立即切换,且不崩溃。
- 状态:已完成
- 负责人: 未记录
- 分支 / 版本: master / v2.3.2
## 根因分析
- 根因: `setActiveIndex()` 在设置默认激活时直接调用页签按钮点击回调,但绘制前页签按钮部分内部指针尚未初始化,导致空指针访问。
- 触发条件: 绘制前调用 `TabControl::setActiveIndex()`。
- 为什么之前没发现:[可选] 测试缺失,未覆盖绘制前设置默认激活页的生命周期场景。
- 关键证据:[可选] 最小复现仅需创建 TabControl 并在绘制前调用 `setActiveIndex(0)`。
## 修复方案
- 修复思路: 首次绘制前调用 `setActiveIndex()` 时只记录默认激活索引,不立即调用页签按钮点击回调;在 `TabControl::draw()` 完成基础绘制后再应用默认激活。
- 关键改动:
- `TabControl::setActiveIndex()` 在首次绘制前仅记录默认激活页索引。
- `TabControl::draw()` 绘制完成后检查是否为首次绘制、是否设置默认激活页、索引是否合法。
- 新增成员 `TabControl::IsFirstDraw`,用于判断是否首次绘制。
- 新增成员 `TabControl::defaultActivation`,用于记录默认激活索引;未设置时为 `-1`。
- 涉及文件 / 类 / 函数:
- `TabControl.h`
- `TabControl.cpp`
- `TabControl::setActiveIndex()`
- `TabControl::draw()`
- 影响的 API / 行为:[可选] 对外接口不变;内部延迟首次激活流程。
- 关键约束 / 不变量:[可选]
- 首次绘制前的默认激活只记录,不触发未初始化对象的点击回调。
- 首次绘制后调用 `setActiveIndex()` 应立即切换激活页。
- 回滚点 / 开关:[可选] 回退默认激活延迟应用逻辑。
## 影响评估
- 影响范围: TabControl 默认激活、首次绘制、运行时程序化切页。
- 兼容性影响:无(对外接口不变)
- 行为变化:有(绘制前设置默认激活页改为首次绘制后应用)
- 性能影响:无明显影响。
- 回归风险:
- 默认激活索引越界时的行为需要与既有预期保持一致。
- 开关模式下页签按钮选中态需要与激活页同步。
## 验证结果
- 验证步骤:
1. 创建页签数为 2 的 TabControl。
2. 分别在绘制前调用 `setActiveIndex(0)`、`setActiveIndex(1)`、越界索引。
3. 确认索引 0 / 1 在首次绘制时激活对应页面,按钮处于选中状态。
4. 运行过程中通过 Button 回调调用 `setActiveIndex()`,确认立即切换激活页。
5. 验证未调用 `setActiveIndex()` 时默认不激活任何页面。
- 验证结果: PDF 历史记录标记已修复。
- 回归检查:[可选] 页签点击切换、程序化切换、默认不激活场景、开关模式选中态、页签数量变化与越界索引行为。
- 验证证据:[可选]
## 落地信息
- Commit: 0c1cf2938f0d6c5684eeb318f17a8443e0c8cf5a
- PR:[可选] git 推送
- 发布版本: v2.3.2
- 备注: 来源原始 Fix 记录未包含 `-XXXX` 后缀,入库时按 BUG ID 补充为 `Fix-BUG-20251220-0001`。
@@ -0,0 +1,65 @@
# Fix-BUG-20260109-0001
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
## 关联信息
- Fix ID: Fix-BUG-20260109-0001
- 关联 BUG ID: BUG-20260109-0001
- 修复目标: 修复 TextBox 在 Window 创建 / 绘制前调用 `setText()` 设置默认文本时因即时绘制导致的崩溃。
- 状态:已完成
- 负责人: 未记录
- 分支 / 版本: master / v3.0.0
## 根因分析
- 根因: `TextBox::setText()` 将“状态更新”和“立即绘制”耦合在一起。窗口尚未 `initgraph()`、EasyX 图形上下文未就绪时,`draw()` 内部进入 `Control::saveBackground()`,调用 `SetWorkingImage()` / `getimage()` 等依赖有效图形环境的逻辑,最终崩溃。
- 触发条件: 创建 TextBox 后,在 Window 初始化 / 首次绘制前调用 `TextBox::setText()`。
- 为什么之前没发现:[可选] 常规示例多为 Window 完成初始化后再设置文本,缺少控件生命周期前置调用的最小回归用例。
- 关键证据:[可选] 调用链为 `TextBox::setText()` -> `TextBox::draw()` -> `Control::saveBackground()` -> EasyX 图形上下文相关 API。
## 修复方案
- 修复思路: `TextBox::setText()` 改为仅执行文本赋值和脏标记更新,不再在 setter 内直接调用 `draw()`;绘制统一交给 Window / 容器重绘流程。
- 关键改动:
- 移除 `TextBox::setText(std::string text)` 中的直接 `draw()` 调用。
- 保留长度裁剪、文本赋值、`dirty=true`。
- 将可视更新延后到后续统一重绘周期。
- 涉及文件 / 类 / 函数:
- `TextBox.cpp`
- `TextBox::setText()`
- 影响的 API / 行为:[可选] 对外 API 不变;`setText()` 不再保证即时绘制。
- 关键约束 / 不变量:[可选]
- setter 不得直接调用 `draw()` 或任何依赖图形上下文的 API。
- `saveBackground()` / `getimage()` / `putimage()` / `SetWorkingImage()` 只能在 Window 图形上下文就绪并进入统一绘制流程后执行。
- 回滚点 / 开关:[可选] 回退 setter 内即时绘制逻辑。
## 影响评估
- 影响范围: TextBox 默认文本设置、程序化文本更新、绘制时序。
- 兼容性影响:有(如果外部代码依赖 `setText()` 的即时绘制副作用,需要调整为触发一次重绘或等待绘制周期)
- 行为变化:有(`setText()` 语义收敛为更新状态并标脏)
- 性能影响:正向或无影响,减少 setter 内重复绘制与抓屏开销。
- 回归风险:
- 程序化调用 `setText()` 后,如果应用没有触发任何重绘路径,界面更新可能延迟。
- 依赖即时刷新语义的旧代码可能出现短暂视觉滞后。
## 验证结果
- 验证步骤:
1. 构造最小复现:在 Window 创建 / `draw()` 前创建 TextBox 并调用 `setText("default")`,确认不再崩溃。
2. Window 初始化后,通过事件输入或直接调用 `setText()` 更新文本,确认后续一次重绘时文本正确显示。
3. 覆盖 INPUT / READONLY / PASSWORD 三种模式,确认长度裁剪与显示逻辑不受影响。
4. 覆盖窗口 resize,确认背景快照机制仍按既有逻辑工作,无残影或错位。
- 验证结果: PDF 历史记录标记已修复。
- 回归检查:[可选] TextBox 三模式输入与显示、背景快照保存 / 恢复 / 丢弃、Window 首次绘制、对话框打开 / 关闭强制重绘、resize 收口重绘。
- 验证证据:[可选]
## 落地信息
- Commit: 5cb59b36525cde93de83313551296c6458694110
- PR:[可选] git
- 发布版本: v3.0.0
- 备注: 来源原始 Fix 记录未包含 `-XXXX` 后缀,入库时按 BUG ID 补充为 `Fix-BUG-20260109-0001`。
@@ -0,0 +1,69 @@
# Fix-BUG-20260109-0002
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
## 关联信息
- Fix ID: Fix-BUG-20260109-0002
- 关联 BUG ID: BUG-20260109-0002
- 修复目标: 修复 TabControl 页签切换与 Table 重置数据后出现的新旧内容像素叠加,确保快照机制不被污染。
- 状态:已完成
- 负责人: 未记录
- 分支 / 版本: master / v3.0.0
## 根因分析
- 根因: `Control::setIsVisible(true)` 立即向上 `requestRepaint()`,导致页签切换 / 关闭过程中,父容器可能处于 `hasSnap=0`、`dirty=1` 或刚 `discardBackground()` 的快照未就绪状态。此时 `Canvas::requestRepaint(partial)` 直接对子控件 Table 调用 `draw()`,随后 `saveBackground()` 又把已经包含旧表格像素的屏幕区域截入快照,形成背景快照污染。
- 触发条件: 切换到按钮1页签 -> 重置表格数据 -> 切换 / 关闭其他页签 -> 再切回按钮1;或重置数据当次容器快照未就绪时触发局部重绘。
- 为什么之前没发现:[可选] 历史版本 `setIsVisible(true)` 不会立即 `requestRepaint()`;常规交互未必同时满足页签切换 / 表格重置 / Null 填充 / 透明文字背景的组合条件。
- 关键证据:[可选] 日志中出现 Canvas 丢弃快照后,Table `show=1` 立即触发局部重绘,随后保存 Table 背景快照。
## 修复方案
- 修复思路: 回退 `show=true` 立即请求重绘的行为,并为 Canvas / TabControl 的局部重绘增加快照就绪护栏;当容器快照未就绪或 dirty 未清理时,禁止 partial fast-path,降级为 full draw。
- 关键改动:
- `Control::setIsVisible()` 中 `show=true` 时不再立即 `requestRepaint()`,改为仅置脏;`show=false` 保持擦除逻辑。
- `Canvas::requestRepaint()` 增加护栏,`dirty` / 无快照 / 无缓存图时将 partial 降级为 full draw。
- `TabControl::requestRepaint()` 同步增加局部重绘护栏。
- 可选修正 `Table::setData(vector<string>)` 列补齐 off-by-one,降低潜在越界或异常噪声。
- 涉及文件 / 类 / 函数:
- `Control.cpp`
- `Canvas.cpp`
- `TabControl.cpp`
- `Table::setData()`(可选)
- 影响的 API / 行为:[可选] 对外 API 不变;显示时序与局部重绘降级策略发生内部调整。
- 关键约束 / 不变量:[可选]
- 局部重绘前,父容器必须持有有效快照且不处于 dirty 未清理状态。
- `setIsVisible(true)` 不得在快照未就绪阶段触发子控件抢跑绘制。
- 回滚点 / 开关:[可选] 回退 visible 置脏与 partial 降级护栏。
## 影响评估
- 影响范围: TabControl 页签切换、Canvas 局部重绘、Table 数据重置、背景快照捕获。
- 兼容性影响:无 API 变化;若外部依赖 `show=true` 即刻刷新,需要确保后续绘制收口会触发重绘。
- 行为变化:有(快照未就绪时局部重绘降级为全量重绘)
- 性能影响:在快照未就绪阶段单次重绘成本增加;稳定交互阶段仍保留局部重绘路径。
- 回归风险:
- 降级条件过宽可能导致频繁全量重绘。
- 显示状态更新后,如果外部没有触发任何重绘,可能存在视觉延迟。
- 程序化页签激活流程可能受重绘时序影响,需要回归。
## 验证结果
- 验证步骤:
1. 按稳定复现路径:按钮1 -> 重置表格数据 -> 切换 / 关闭其他页签 -> 切回按钮1,重复 50 次以上。
2. 在按钮1停留时连续快速点击重置 100 次,并穿插页签切换、窗口最小化 / 恢复、拖拽 / resize。
3. 反复对 Table / Canvas 调用 `setIsVisible(false / true)`,确认隐藏擦除正确、显示后无残影。
4. 分别在 TableFillMode 为 Null 与非 Null 模式下验证。
- 验证结果: PDF 历史记录标记已修复。
- 回归检查:[可选] TabControl 切换 / 关闭 / 重复打开、Canvas 添加 / 移除控件与 resize、Table clearData / setData / 翻页、全局窗口 resize / 最小化恢复。
- 验证证据:[可选] PDF 记录中关联 `QQ202619-144430.mp4`。
## 落地信息
- Commit: 5cb59b36525cde93de83313551296c6458694110
- PR:[可选] git 推送
- 发布版本: v3.0.0
- 备注: 来源原始 Fix 记录未包含 `-XXXX` 后缀,入库时按 BUG ID 补充为 `Fix-BUG-20260109-0002`。
@@ -0,0 +1,58 @@
# Fix-BUG-20260122-0001
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
## 关联信息
- Fix ID: Fix-BUG-20260122-0001
- 关联 BUG ID: BUG-20260122-0001
- 修复目标: 将 `Window::runEventLoop()` 中普通控件事件分发顺序由正序遍历改为逆序遍历,以保持 Z 序直觉。
- 状态:已完成
- 负责人: 未记录
- 分支 / 版本: master / v3.0.1
## 根因分析
- 根因: 之前版本修改 `Window::runEventLoop()` 时,将原本的逆序事件分发错误改成了正序遍历。
- 触发条件: 普通控件重叠时,事件落在多个控件的命中区域。
- 为什么之前没发现:[可选] 测试缺失,提交时未进行详细代码审查。
- 关键证据:[可选] PDF 历史记录说明该问题属于代码审查发现的遍历顺序错误。
## 修复方案
- 修复思路: 对 `controls` 使用逆序遍历,让绘制顺序更靠上的控件优先处理事件。
- 关键改动:
- 将 `for (auto& c : controls)` 调整为 `for (auto it = controls.rbegin(); it != controls.rend(); ++it)`。
- 涉及文件 / 类 / 函数:
- `Window.cpp`
- `Window::runEventLoop()`
- 影响的 API / 行为:[可选] 无对外 API 变化;重叠控件事件命中顺序修正。
- 关键约束 / 不变量:[可选] 普通控件事件分发应保持逆序遍历,避免下层控件抢事件。
- 回滚点 / 开关:[可选] 回退普通控件事件遍历顺序。
## 影响评估
- 影响范围: 普通控件点击、悬停、重叠控件事件命中。
- 兼容性影响:无(内部逻辑修改)
- 行为变化:有(重叠控件由上层优先处理事件)
- 性能影响:无明显影响。
- 回归风险: 控件重叠时的事件分发行为变化,需要确认符合绘制 Z 序。
## 验证结果
- 验证步骤:
1. 创建重叠普通控件。
2. 在重叠区域触发点击与悬停。
3. 确认上层控件优先收到事件。
- 验证结果: PDF 历史记录标记已修复;原记录未提供详细验证证据。
- 回归检查:[可选] 普通控件事件分发、重叠按钮点击、悬停状态变化。
- 验证证据:[可选] 无
## 落地信息
- Commit: b765e54eff28ebcd0bd99a81dec80b3ac0769337
- PR:[可选] git 推送
- 发布版本: v3.0.1
- 备注: PDF 原文合入分支写作 `mster`,本次按仓库语境整理为 `master`。
@@ -0,0 +1,59 @@
# Fix-BUG-20260203-0001
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
## 关联信息
- Fix ID: Fix-BUG-20260203-0001
- 关联 BUG ID: BUG-20260203-0001
- 修复目标: 避免对话框打开时错误合成并下发 `WM_MOUSEMOVE`,只在对话框关闭后刷新可见控件 hover 状态。
- 状态:已完成
- 负责人: 未记录
- 分支 / 版本: master / v3.0.1
## 根因分析
- 根因: 合成 `WM_MOUSEMOVE` 的代码位于 `if (needredraw || dialogClose)` 范围内,但执行合成下发时没有再判断 `dialogClose` 是否为真。
- 触发条件: 打开对话框触发重绘判断。
- 为什么之前没发现:[可选] 早期未引入日志系统,隐藏的事件下发问题不易被发现。
- 关键证据:[可选] 对话框打开日志中出现“对话框关闭,合成WM_MOUSEMOVE已下发”。
## 修复方案
- 修复思路: 合成 `WM_MOUSEMOVE` 前显式判断 `dialogClose`,只有对话框关闭路径才执行合成并下发。
- 关键改动:
- 在 `Window::runEventLoop()` 中,对合成 `WM_MOUSEMOVE` 下发逻辑增加 `dialogClose == true` 条件。
- 涉及文件 / 类 / 函数:
- `Window.cpp`
- `Window::runEventLoop()`
- 影响的 API / 行为:[可选] 无对外 API 变化。
- 关键约束 / 不变量:[可选] `dialogClose == true` 才能合成并下发 `WM_MOUSEMOVE`。
- 回滚点 / 开关:[可选] 回退 `dialogClose` 条件判断。
## 影响评估
- 影响范围: 对话框打开 / 关闭后的 hover 状态刷新、普通控件事件下发。
- 兼容性影响:无
- 行为变化:有(对话框打开阶段不再下发合成 `WM_MOUSEMOVE`)
- 性能影响:持平或更优,减少不必要事件下发。
- 回归风险: 暂无;需确认对话框关闭后 hover 状态仍能正确刷新。
## 验证结果
- 验证步骤:
1. 创建包含对话框的最小 Demo。
2. 启动日志系统。
3. 触发对话框弹出并观察日志。
4. 关闭对话框并确认只在关闭阶段出现合成 `WM_MOUSEMOVE` 下发。
- 验证结果: PDF 历史记录标记已修复。
- 回归检查:[可选] 非模态对话框事件处理、关闭后 hover 状态刷新、普通控件悬停状态。
- 验证证据:[可选] PDF 记录包含 2026-02-10 运行日志。
## 落地信息
- Commit: b765e54eff28ebcd0bd99a81dec80b3ac0769337
- PR:[可选] git 推送
- 发布版本: v3.0.1
- 备注: 来源原始 Fix 记录未包含 `-XXXX` 后缀,入库时按 BUG ID 补充为 `Fix-BUG-20260203-0001`。
@@ -0,0 +1,63 @@
# Fix-BUG-20260206-0001
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
## 关联信息
- Fix ID: Fix-BUG-20260206-0001
- 关联 BUG ID: BUG-20260206-0001
- 修复目标: 只在对话框打开或关闭时触发一次必要全量重绘,避免对话框保持打开期间持续全量重绘。
- 状态:已完成
- 负责人: 未记录
- 分支 / 版本: master / v3.0.1
## 根因分析
- 根因: 对话框打开状态判断基于“是否有对话框可见”,导致只要有对话框保持打开,`needredraw` 就持续为 true。
- 触发条件: 触发对话框打开并保持可见。
- 为什么之前没发现:[可选] 之前没有引入日志系统,该问题属于性能类隐藏问题,不容易观察。
- 关键证据:[可选] 日志中连续出现“对话框打开/关闭,触发全量重绘”。
## 修复方案
- 修复思路: Window 新增 `dialogOpen` 状态,只有对话框刚打开时才触发打开相关重绘;完成重绘后立即复位。
- 关键改动:
- `Window` 新增变量 `dialogOpen`。
- 对话框弹出时设置 `dialogOpen=true`。
- `needredraw` 判断逻辑改为只在 `dialogOpen` 或关闭事件发生时触发。
- 重绘后立即将 `dialogOpen` 复位为 false。
- 涉及文件 / 类 / 函数:
- `Window.cpp`
- `Dialog.cpp`
- `Window::runEventLoop()`
- 影响的 API / 行为:[可选] 无对外 API 变化。
- 关键约束 / 不变量:[可选] `dialogOpen == true` 才触发对话框打开相关重绘判断;重绘后必须复位。
- 回滚点 / 开关:[可选] 回退 `dialogOpen` 状态门控。
## 影响评估
- 影响范围: 对话框打开 / 关闭后的全量重绘、主事件循环性能。
- 兼容性影响:无
- 行为变化:有(对话框保持打开时不再持续触发全量重绘)
- 性能影响:持平或更优,减少重复全量绘制。
- 回归风险: 未知;需确认对话框打开时仍能置顶并正确清理关闭残留。
## 验证结果
- 验证步骤:
1. 触发对话框弹出。
2. 观察日志,确认打开时只触发一次必要全量重绘。
3. 保持对话框打开,确认不再持续刷全量重绘日志。
4. 关闭对话框,确认关闭路径仍能重绘清理残留。
- 验证结果: PDF 历史记录标记已修复。
- 回归检查:[可选] 对话框打开 / 关闭、非模态对话框、普通控件重绘、背景快照刷新。
- 验证证据:[可选] PDF 记录包含 2026-02-15 修复后运行日志。
## 落地信息
- Commit: b765e54eff28ebcd0bd99a81dec80b3ac0769337
- PR:[可选] git 推送
- 发布版本: v3.0.1
- 备注: 来源原始 Fix 记录未包含 `-XXXX` 后缀,入库时按 BUG ID 补充为 `Fix-BUG-20260206-0001`。
@@ -0,0 +1,58 @@
# Fix-BUG-20260217-0001
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
## 关联信息
- Fix ID: Fix-BUG-20260217-0001
- 关联 BUG ID: BUG-20260217-0001
- 修复目标: `TabControl::getActiveIndex()` 在页签列表为空或无页签激活时返回 `-1`,有页签激活时返回对应索引。
- 状态:已完成
- 负责人: 未记录
- 分支 / 版本: master / v3.0.1
## 根因分析
- 根因: `getActiveIndex()` 未处理“页签列表不为空但所有页签都未激活”的情况。
- 触发条件: 调用 `TabControl::getActiveIndex()` 时,页签列表非空且没有任何激活页签。
- 为什么之前没发现:[可选] 测试缺失,未覆盖无激活页但列表非空的返回值场景。
- 关键证据:[可选] 代码分析确认旧逻辑会返回最后一个页签索引。
## 修复方案
- 修复思路: 增加无激活页兜底逻辑;页签列表为空或遍历后未找到激活页时统一返回 `-1`。
- 关键改动:
- 修改 `TabControl::getActiveIndex()`,在无激活页时返回 `-1`。
- 涉及文件 / 类 / 函数:
- `TabControl.cpp`
- `TabControl::getActiveIndex()`
- 影响的 API / 行为:[可选] 修正返回值语义;调用方可可靠用 `-1` 判断无激活页。
- 关键约束 / 不变量:[可选] 无激活页必须返回 `-1`,不能返回任意有效索引。
- 回滚点 / 开关:[可选] 回退无激活页兜底返回逻辑。
## 影响评估
- 影响范围: TabControl 激活页查询、依赖 `getActiveIndex()` 的业务判断。
- 兼容性影响:无(修复 BUG)
- 行为变化:有(无激活页从错误有效索引变为 `-1`)
- 性能影响:无明显影响。
- 回归风险: 低;需确认有激活页和空列表场景返回值正确。
## 验证结果
- 验证步骤:
1. 调用 `TabControl::getActiveIndex()`,验证页签列表为空时返回 `-1`。
2. 验证页签列表不为空且所有页签未激活时返回 `-1`。
3. 验证存在激活页时返回对应索引。
- 验证结果: PDF 历史记录标记已修复。
- 回归检查:[可选] TabControl 默认激活、页签切换、无激活页状态。
- 验证证据:[可选] 无
## 落地信息
- Commit: b765e54eff28ebcd0bd99a81dec80b3ac0769337
- PR:[可选] git
- 发布版本: v3.0.1
- 备注: 来源原始 Fix 记录未包含 `-XXXX` 后缀,入库时按 BUG ID 补充为 `Fix-BUG-20260217-0001`。
@@ -62,7 +62,7 @@
- 验证方式:
1. 在 `>1000` 旧保护分支中先加 `DEBUG` 日志,确认 `KEY == 1 / 2` 命中。
2. 替换为非法尺寸保护后,编译 [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp)。
2. 替换为非法尺寸保护后,编译 [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp)。
3. 按 `KEY == 1 ~ 4` 做逻辑推演。
- 理论结果:
- `KEY == 1 / 2`:现在应打印“大跨度但继续执行收口”日志,并继续完成 `尺寸调整已完成`。
@@ -24,7 +24,7 @@
- 之前更关注 Tooltip 的显示、Hover 切换和对话框遮挡链路
- “逻辑状态已隐藏,但屏幕区域无人回贴”的问题只有在当前托管重绘路径下才更稳定暴露
- 关键证据:
- [`Button.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Button.cpp) 中 `hideTooltip()` 在托管分发阶段只作废快照,不回贴 Tooltip 自身区域
- [`Button.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Button.cpp) 中 `hideTooltip()` 在托管分发阶段只作废快照,不回贴 Tooltip 自身区域
## 修复方案
@@ -36,7 +36,7 @@
- 只要 `tipVisible == true`,统一调用 `tipLabel.hide()`
- 保留 `tipHoverTick` 重置逻辑
- 涉及文件 / 类 / 函数:
- [`Button.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Button.cpp)
- [`Button.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Button.cpp)
- `Button::hideTooltip()`
- 影响的 API / 行为:
- 无公开 API 变化
@@ -64,7 +64,7 @@
- 验证步骤:
1. 修改 [`Button.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Button.cpp) 中 `Button::hideTooltip()` 的 Tooltip 隐藏分支
1. 修改 [`Button.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Button.cpp) 中 `Button::hideTooltip()` 的 Tooltip 隐藏分支
2. 编译 `Button.cpp`
3. 编译 `z-testDome.cpp` 并指定 `KEY=4`,确认 Tooltip 相关 demo 入口可正常编译
@@ -34,9 +34,9 @@
- `TabControl`:按自己的真实绘制顺序补画页签按钮与页面
- overlay 补画前统一 `invalidateBackgroundSnapshot()`,避免旧快照反贴旧背景
- 涉及文件 / 类 / 函数:
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp)
- [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Canvas.cpp)
- [`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/TabControl.cpp)
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp)
- [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Canvas.cpp)
- [`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/TabControl.cpp)
- 影响的 API / 行为:[可选]
- 无对外 API 变化
- 局部重绘行为更严格遵守视觉合成顺序
@@ -62,8 +62,8 @@
- 验证步骤:
1. 编译 [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp)、[`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Canvas.cpp)、[`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/TabControl.cpp)
2. 编译 [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/z-testDome.cpp) `KEY=5`
1. 编译 [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp)、[`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Canvas.cpp)、[`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/TabControl.cpp)
2. 编译 [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/app/z-testDome.cpp) `KEY=5`
3. 回归 `Table` 翻页、相交 `Canvas`、`TabControl` 页签/页面遮挡场景
- 验证结果:
@@ -0,0 +1,66 @@
# Fix-BUG-20260929-0001
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
## 关联信息
- Fix ID: Fix-BUG-20260929-0001
- 关联 BUG ID: BUG-20260929-0001
- 修复目标: 避免 Table 运行期重置后的分页按钮旧快照覆盖新页码
- 状态:已完成
- 负责人: Codex
- 分支 / 版本: 当前工作区
## 根因分析
- 根因: `Table::draw()` 先通过 `drawPageNum()` 绘制新页码,随后 `drawButton()` 才重新定位上一页和下一页按钮。按钮的 `setX()/setY()` 会回贴旧位置背景快照;当旧按钮区域与新页码位置相交时,新页码被局部擦除。
- 触发条件: 运行期替换表头或数据后,表格宽度和分页控件位置发生变化。
- 为什么之前没发现: 既有检查覆盖普通翻页重绘,但没有覆盖“表格结构尺寸变化 + 三个分页控件同帧搬移”的首帧顺序。
- 关键证据: 后续 hover 只触发重绘、不再改变分页控件位置,此时页码立即恢复完整,说明页码内容和设脏逻辑本身有效。
## 修复方案
- 修复思路: 将分页内部控件的布局刷新前移到 Table 新一帧绘制之前。先更新页码文本,再统一移动页码 Label 和两个分页按钮并清理旧快照,最后绘制表体、页码和按钮。
- 关键改动:
- 集中生成当前页码文本,避免布局阶段和绘制阶段使用不同字符串。
- 当分页布局失效时,在 Table 回贴并重绘自身之前完成页码 Label 的尺寸、位置和文本更新。
- 在同一预布局阶段完成上一页、下一页按钮的位置更新,确保旧快照不会覆盖本帧已经绘制的分页元素。
- 保留最终“表体 -> 页码 -> 分页按钮”的绘制顺序。
- 涉及文件 / 类 / 函数:
- `src/StellarX/Table.cpp`
- `Table::draw()`
- `Table::drawPageNum()`
- 影响的 API / 行为: 无公共 API 变化;仅修正运行期重置后的首帧绘制结果。
- 关键约束 / 不变量: Table 内部控件因布局变化产生的旧快照回贴必须发生在父 Table 绘制新内容之前。
- 回滚点 / 开关: 回退 `Table::draw()` 中的分页预布局块及统一页码文本生成函数。
## 影响评估
- 影响范围: Table 数据、表头、每页行数变化后的分页区重绘。
- 兼容性影响:无
- 行为变化:有(重置后的首帧直接显示完整页码)
- 性能影响:可忽略,仅在分页布局失效时提前执行原有位置刷新。
- 回归风险:
- 透明 Table 背景下的分页区快照恢复顺序变化。
- 页码位数变化造成 Label 宽度变化时,需要确认左右按钮仍与页码保持正确间距。
- 隐藏分页按钮后再恢复显示时,需要确认按钮仍能初始化并正确定位。
## 验证结果
- 验证步骤:
1. 执行 `scripts/test.ps1 -Suite NonUI`。
2. KEY1 页签 1 点击页内按钮后,不移动鼠标立即检查完整页码。
3. 连续重置三次,并执行上一页、下一页切换。
4. hover 页签 2、页签 3 并切换后返回,确认分页区无残影。
- 验证结果: 2026-09-29 执行 `scripts/test.ps1 -Suite NonUI`,Debug x64、Debug x64 ASan、Release x64、Debug Win32 均为 15/15 通过;ASan 未报告内存错误;KEY1 至 KEY6 均编译、链接通过。用户于 2026-09-30 完成 KEY1 至 KEY6 手工回归,暂未发现明显问题。
- 回归检查: KEY1 表格重置与翻页;KEY5 Table 分页、页码和上层浮层。
- 验证证据:[可选]
## 落地信息
- Commit: 未提交
- PR:[可选]
- 发布版本:[可选]
- 备注: UI 像素结果已由用户按 `TESTING.md` 完成人工验收。
+74
View File
@@ -0,0 +1,74 @@
# StellarX API 迁移说明
本次变更包含不兼容的所有权与文本样式 API 调整。GUI 仍限定在主线程,字符集仍为 MBCS。
## Window 与 Canvas 所有权
不再公开可变的 `std::vector<std::unique_ptr<Control>>`。调用方必须保存稳定句柄,而不是保存容器迭代器或假设控件地址永久有效。
```cpp
auto button = std::make_unique<Button>(10, 10, 100, 32, "run");
const StellarX::ControlHandle handle = window.addControl(std::move(button));
Control* control = window.findControl(handle);
const bool removed = window.removeControl(handle);
```
新接口:
- `ControlHandle addControl(std::unique_ptr<Control>)`
- `bool removeControl(ControlHandle)`
- `void clearControls()`
- `Control* findControl(ControlHandle)`
- `std::vector<ControlHandle> getControlHandles() const`
已删除:
- `Window::getControls()`
- `Canvas::getControls()`
- `Canvas::clearAllControls()`
事件、绘制、tick 和按钮回调遍历期间的新增、删除与清空会延迟到最外层事务结束后提交。回调结束后不要继续使用已请求删除的裸指针。
## 字体名称
`ControlText::lpszFace` 已替换为拥有型字符串 `ControlText::faceName`。
```cpp
StellarX::ControlText style;
style.faceName = "微软雅黑";
```
不再传入临时 `c_str()` 指针。字体样式比较按字符串内容进行,复制赋值使用默认值语义。
## TabControl
页签身份改为索引,重复标题合法。
```cpp
const size_t index = tabs.addTab(std::move(button), std::move(page));
auto rejected = tabs.tryAddToPage(index, std::move(control));
if (rejected) {
// 添加失败,调用方仍拥有控件。
}
```
替换关系:
- `add(pair<Button, Canvas>)` 改为 `addTab(button, page)`,返回页签索引。
- `add(tabText, control)` 改为 `tryAddToPage(index, control)`;文本重载只匹配第一个同名页签。
- `setActiveIndex(int)` 现在返回 `bool`,运行期非法索引返回 `false`。
空 TabControl 可预设首次激活索引;首次绘制不会访问空容器。索引超过首批页签范围时首次激活最后一个可用页签。
## 日志配置
`SxLogger::setConfig` 现在返回 `bool`:
```cpp
if (!StellarX::SxLogger::Get().setConfig(config)) {
// 候选文件 sink 打开失败,旧配置和旧 sink 保持有效。
}
```
文件输出禁用、路径、追加模式或滚动阈值变化会同步更新 sink。滚动重命名失败时继续追加原文件,不再截断。
+119
View File
@@ -0,0 +1,119 @@
# StellarX 自动化测试
## 依赖
- Visual Studio 2022 MSVC v143、Windows SDK、Native Unit Test Framework。
- EasyX 已安装到当前 MSVC 环境。
- UI 套件需要已解锁的交互式 Windows 桌面,会短暂切换前台窗口并移动鼠标,结束后恢复鼠标位置。
默认工具路径为 `D:\vs2022`。其他安装位置可设置:
- `STELLARX_MSBUILD`:`MSBuild.exe` 的完整路径。
- `STELLARX_VSTEST`:`vstest.console.exe` 的完整路径。
## 命令
```powershell
.\scripts\test.ps1 -Suite Unit -Configuration Debug -Platform x64
.\scripts\test.ps1 -Suite NonUI
.\scripts\test.ps1 -Suite Render
.\scripts\test.ps1 -Suite UI
.\scripts\test.ps1 -Suite All
.\scripts\test.ps1 -Suite CI
```
- `Unit`:构建核心静态库并运行 VS Native Unit Test。
- `NonUI`:运行 Debug x64、Debug x64 ASan、Release x64、Debug Win32 和 KEY1 至 KEY6 编译,不启动或操作任何窗口。
- `Render`:运行确定性 EasyX fixture 和关键像素断言;当前与单轮 UI fixture 共用实现。
- `UI`:使用 `SendInput` 驱动 ownership、geometry、tooltip、tab、table、dialog 六类真实窗口场景。
- `All`:Debug x64、Debug x64 ASan、Release x64、Debug Win32、KEY1 至 KEY6 编译、UI 连续三轮。
- `CI`:默认运行 Debug x64、Release x64 和 Debug Win32;设置 `STELLARX_UI_TESTS=1` 后增加单轮 UI。
Windows MSVC AddressSanitizer 不支持 LeakSanitizer,因此脚本使用 `detect_leaks=0:halt_on_error=1`。ASan 产物独立写入 `build\x64\Debug-asan`,不会污染普通 Debug 构建。
`scripts/test.ps1` 使用 UTF-8 BOM 保存,确保 Windows PowerShell 5.1 能正确读取中文注释,不会把注释后的语句误判为注释内容。
## 覆盖范围
逻辑测试覆盖:
- 稳定句柄与回调期间 self/sibling/add/remove/clear 所有权变更。
- Tab 延迟新增、失败返还所有权、重复标题和页面激活状态。
- 字体名拥有权与内容比较。
- MBCS 密码掩码、非法尾字节容错。
- Tooltip 单调时钟 tick、禁用和移出清理。
- 1000 条消息的 128 条批处理上限、空闲休眠和关闭顺序。
- 日志配置原子切换与滚动行完整性。
UI/像素测试覆盖:
- 回调删除自身后旧区域恢复。
- Button、Label、TextBox、Canvas 的移动、缩放和隐藏残影。
- 静止 Tooltip 到期显示及移出清除。
- 同名页签唯一选中、唯一页面可见。
- 极窄 Table 的最小宽度和 coverage 外像素不变量。
- 两层异步 Dialog 先后关闭、下层可见且可继续交互。
## 诊断产物
所有产物位于未跟踪的 `TestResults\`:
- 单元测试:TRX。
- UI 成功:场景 JSON、`screen.png`、fixture 标准输出和错误日志。
- UI 失败:`driver-error.json`、`failure.png`,以及已生成的场景中间截图。
UI fixture 按 PID 和精确标题枚举 `EasyXWnd`,使用 `PrintWindow(PW_CLIENTONLY)` 获取不受遮挡影响的客户区像素,不使用 DPI 和字体敏感的全图 golden image。
## 人工 UI 验收
本轮收口不自动启动 UI。发布或推送前,由人工依次验证 KEY1、KEY2、KEY4、KEY5、KEY6;KEY3 只做启动兼容性冒烟。
### 构建与启动
推荐双击仓库根目录的 `Run-Key-Test.cmd`,输入 `1` 至 `6` 后,脚本会重新构建并启动对应场景。关闭测试窗口后会返回选择菜单,输入 `0` 退出;脚本只负责构建和启动,不会自动操作 UI。
也可以从 PowerShell 直接启动指定场景:
```powershell
.\scripts\run-key.ps1 -Key 5
```
若启动器不可用,将下面命令中的 `KeyScenario` 改为待测编号。每次构建完成后手动启动生成的程序:
```powershell
$msbuild = 'D:\vs2022\MSBuild\Current\Bin\MSBuild.exe'
& $msbuild .\imGui-easyX.vcxproj /m /t:Rebuild /p:Configuration=Debug /p:Platform=x64 /p:KeyScenario=1 /p:EnableAsan=false /v:minimal
.\build\x64\Debug\imGui-easyX.exe
```
### 通用通过标准
- 窗口可正常启动、缩放、最大化、还原和关闭,无崩溃、卡死或断言。
- Hover、按下、释放和 Tooltip 状态及时变化,鼠标移出后不残留高亮、文字或浮层。
- 切页、分页、移动或缩放后无旧像素、黑块、重影、错层和控件穿透。
- 一次点击只触发一次结果;禁用控件不触发回调;模态对话框阻止底层交互。
### 必测场景
| 场景 | 操作 | 通过标准 |
|---|---|---|
| KEY1 | 进入页签 1,点击页内“按钮”重置表格,不移动鼠标立即观察分页区;连续重置三次并执行上一页、下一页;点击外部 `test` 多次激活同一页,再依次切换三个页签 | 重置后的首帧立即完整显示“第1页/共2页”,不依赖 hover 或后续重绘恢复;重复激活不闪烁;Table 超出页面区域的内容在切页后完全清除;无残影或层级错乱 |
| KEY2 | 拉宽、缩窄、最大化并还原窗口;点击多个位按钮,执行位取反、左移、右移、一键置 0/1 和有符号切换 | 32 位“位号 + 按钮”始终对齐;五个区块铺满且不重叠;十六进制、十进制、上次值、本次值立即同步 |
| KEY4 | 打开非模态对话框,在顶部按钮、页签和分页区快速移动鼠标;再打开模态对话框并拖动窗口,随后关闭 | 遮挡区不发生 Hover/Tooltip 穿透;标题、关闭按钮和底层背景无残影;关闭后底层 Hover 立即恢复 |
| KEY5 | 在三层 Canvas、两个页签及其页内按钮间移动和点击;触发各处 Tooltip;操作 Table 分页按钮并观察粉色浮层 | 深层按钮状态正常;Tooltip 出现和清除完整;页签与页面层级正确;Table 重绘不会擦除或覆盖上层浮层 |
| KEY6 | 点击圆形和椭圆按钮的内部及外接矩形角落;点击禁用按钮;切换右侧页签并操作页内控件;分别验证 YesNo、RetryCancel、AbortRetryIgnore | 形状外区域不误触发;禁用按钮不改变状态;右侧页签布局稳定;消息框结果正确,非模态关闭后无残留且底层 Hover 恢复 |
| KEY3 | 启动后完成登录页和旧业务页的基本切换 | 能正常进入和切换,无明显布局或交互倒退 |
### 本轮验收记录
- 验收日期:2026-09-30
- 验收方式:用户按上述清单手工执行 KEY1 至 KEY6。
- 验收结论:全部场景已完成,当前快照暂未发现明显问题。
- 自动复验:2026-09-30 使用 Windows PowerShell 5.1 执行 `NonUI`,四种配置均为 15/15 通过,KEY1 至 KEY6 编译链接成功。
- 说明:该结论代表本轮人工回归通过,不等同于对所有运行环境和未覆盖路径作无缺陷保证。
### 失败留证
- 记录 KEY 编号、窗口尺寸、操作顺序和实际现象。
- 截取包含整个客户区的截图;闪烁或延迟问题录制短视频。
- 若崩溃,保留 Visual Studio 调用栈和当次日志,不继续把该快照标记为 UI 通过。
@@ -0,0 +1,70 @@
# 功能变更 ID: Feature-20251220-0001
> 适用场景:记录小到中等规模的功能、接口、行为、默认值或配置变化。
> 不适用场景:新增核心模块、重大模块重构、架构级设计,请使用“新增功能模块”模板。
## 基本信息
- ID: Feature-20251220-0001
- 标题: Table 运行期重置表头与数据 API
- 状态:已完成(历史记录;本次仅归档,未重新验证)
- 类型:新增
- 级别:L2 中等
- 模块: Table
- 版本 / 分支: v2.3.2 / master
- 环境: Windows / Visual Studio 2022 / Debug、Release / EasyX
- 负责人: Ysm-04(历史实现)
## 变更背景
- 背景: 原有 Table 可以设置表头和数据,但缺少明确的运行期清空与整体重置入口。
- 目标: 允许同一个 Table 实例在运行过程中切换不同的数据集合,并在重置后重新计算单元格尺寸、分页信息和绘制状态。
- 不做什么:[可选]
- 不改变原有 `setHeaders()`、`setData()` 的调用方式。
- 不引入新的表格数据模型或外部依赖。
## 变更内容
- 变更摘要: 新增清空表头、清空数据以及同时清空二者的公共 API。
- 新增项:[可选]
- `void Table::clearHeaders()`
- `void Table::clearData()`
- `void Table::resetTable()`
- 修改项:[可选]
- 清空表头后,重新标记单元格尺寸、表头以及分页按钮/页码信息需要计算或绘制。
- 清空数据后,将 `currentPage`、`totalPages` 重置为 1,并重新标记尺寸和分页信息。
- `resetTable()` 依次复用 `clearHeaders()` 与 `clearData()`。
- 删除 / 废弃项:[可选]
- 无。
- 受影响的文件 / 类 / 函数:
- 历史路径:`include/StellarX/table.h`
- 历史路径:`src/table.cpp`
- `Table::clearHeaders()`、`Table::clearData()`、`Table::resetTable()`
- 对外 API / 属性变化:[可选]
- 新增上述三个公共方法;旧 API 保持不变。
## 行为对照
- 变更前: Table 创建后缺少专用的清空接口,调用方难以明确地复用同一对象展示另一组表头和数据。
- 变更后:
- `clearHeaders()` 清空表头并触发后续尺寸、表头和分页布局更新。
- `clearData()` 清空数据、复位页码状态并触发后续重算。
- `resetTable()` 同时清空表头与数据,调用方随后可重新设置内容。
- 兼容性说明:兼容
- 迁移说明:[可选]
- 无需迁移旧代码;需要复用 Table 时可在重新调用 `setHeaders()` / `setData()` 前使用新增接口。
## 验证与落地
- 验证方式:
- 历史记录使用按钮回调分别执行“清空并重设表头”“清空并重设数据”“同时清空并重设表头与数据”。
- 观察表头、数据、表格尺寸和分页区域是否同步更新。
- 验证结果:
- PDF 历史记录将上述三个手工场景记为通过。
- 本次归档未构建 v2.3.2,也未重新运行 GUI 用例;历史运行结果未在本次复验。
- 关联 BUG / Fix:[可选]
- Commit: `0c1cf2938f0d6c5684eeb318f17a8443e0c8cf5a`
- PR:[可选]
- 发布版本:[可选] v2.3.2
- 备注:[可选]
- 资料来源:`StellarX功能_模块变更.pdf` 第 19-21 页,并以对应提交和历史 `CHANGELOG.md` 交叉核对。
@@ -0,0 +1,66 @@
# 功能变更 ID: Feature-20251220-0002
> 适用场景:记录小到中等规模的功能、接口、行为、默认值或配置变化。
> 不适用场景:新增核心模块、重大模块重构、架构级设计,请使用“新增功能模块”模板。
## 基本信息
- ID: Feature-20251220-0002
- 标题: TextBox 密码模式
- 状态:已完成(历史记录;本次仅归档,未重新验证)
- 类型:新增
- 级别:L2 中等
- 模块: TextBox / CoreTypes
- 版本 / 分支: v2.3.2 / master
- 环境: Windows / Visual Studio 2022 / Debug、Release / EasyX
- 负责人: Ysm-04(历史实现)
## 变更背景
- 背景: TextBox 原有输入模式会直接绘制用户输入,不适合密码等需要隐藏显示内容的场景。
- 目标: 保存真实输入值,但在界面上使用掩码字符显示,并允许调用方通过 `getText()` 获取原始文本。
- 不做什么:[可选]
- 不提供密码加密、散列、数据库校验或安全存储能力。
- 不改变既有输入模式的行为。
## 变更内容
- 变更摘要: 为 `TextBoxmode` 增加 `PASSWORD_MODE`,并在 TextBox 输入和绘制路径中处理该模式。
- 新增项:[可选]
- `StellarX::TextBoxmode::PASSWORD_MODE`
- 修改项:[可选]
- `TextBox::draw()` 在密码模式下构造掩码文本并绘制掩码,不直接绘制真实内容。
- `TextBox::handleEvent()` 在密码模式下获取输入并保存真实文本。
- 删除 / 废弃项:[可选]
- 无。
- 受影响的文件 / 类 / 函数:
- 历史路径:`include/StellarX/CoreTypes.h`
- 历史路径:`src/textBox.cpp`
- `TextBoxmode`、`TextBox::draw()`、`TextBox::handleEvent()`、`TextBox::getText()`
- 对外 API / 属性变化:[可选]
- `TextBoxmode` 新增枚举值 `PASSWORD_MODE`。
## 行为对照
- 变更前: TextBox 只能按普通输入或只读模式展示文本,输入内容会直接显示。
- 变更后: 设置为 `PASSWORD_MODE` 后,真实内容仍保存在 TextBox 中,绘制层使用 `*` 作为掩码,`getText()` 返回真实内容。
- 兼容性说明:兼容
- 迁移说明:[可选]
- 原有模式无需迁移;密码输入场景显式选择 `PASSWORD_MODE`。
- 此功能只隐藏界面显示,不应被视为密码安全存储方案。
## 验证与落地
- 验证方式:
- 创建密码模式 TextBox,输入内容,确认界面显示掩码且 `getText()` 返回原始文本。
- 将 `getText()` 结果交给业务层进行密码比对。
- 验证结果:
- PDF 历史记录将普通密码输入和业务比对两个手工场景记为通过。
- 历史实现按 `std::string::size()` 生成掩码,即按字节而非明确按多字节逻辑字符计数;中文等 MBCS 输入的掩码数量不能据此认定为已正确验证。
- 本次归档未构建 v2.3.2,也未重新运行 GUI 用例。
- 关联 BUG / Fix:[可选]
- Commit: `0c1cf2938f0d6c5684eeb318f17a8443e0c8cf5a`
- PR:[可选]
- 发布版本:[可选] v2.3.2
- 备注:[可选]
- 资料来源:`StellarX功能_模块变更.pdf` 第 22-23 页,并以对应提交和历史 `CHANGELOG.md` 交叉核对。
@@ -0,0 +1,67 @@
# 功能变更 ID: Feature-20260109-0001
> 适用场景:记录小到中等规模的功能、接口、行为、默认值或配置变化。
> 不适用场景:新增核心模块、重大模块重构、架构级设计,请使用“新增功能模块”模板。
## 基本信息
- ID: Feature-20260109-0001
- 标题: TabControl 页签切换时序调整
- 状态:已完成(历史记录;本次仅归档,未重新验证)
- 类型:修改
- 级别:L2 中等
- 模块: TabControl
- 版本 / 分支: v3.0.0 / master
- 环境: Windows 11 / Visual Studio 2022 / Debug、Release / EasyX
- 负责人: Ysm-04(历史实现)
## 变更背景
- 背景: 原页签切换回调先显示目标页,再关闭其他页,在复杂容器和背景快照链路中可能短暂形成多页同时可见的状态。
- 目标: 将切换顺序收敛为先停用原页,再激活目标页,降低重绘和快照时序的不确定性。
- 不做什么:[可选]
- 不改变 TabControl 的公共调用接口。
- 不改变页签按钮与页面 Canvas 的配对模型。
## 变更内容
- 变更摘要: 调整 `TabControl::add()` 注册的 Toggle 回调执行顺序。
- 新增项:[可选]
- 激活日志记录原页索引、目标页索引和页签文本。
- 修改项:[可选]
- 激活目标页前,先找到当前可见页面并停用其他已点击页签。
- 其他页签的 ToggleOff 回调负责隐藏对应页面。
- 旧页停用后,再更新目标页布局、显示目标页并标记 TabControl 为脏。
- 删除 / 废弃项:[可选]
- 无。
- 受影响的文件 / 类 / 函数:
- 历史路径:`src/TabControl.cpp`
- `TabControl::add()` 内的 ToggleOn / ToggleOff 回调
- 对外 API / 属性变化:[可选]
- 无,属于内部切换时序调整。
## 行为对照
- 变更前: 先显示目标页面,再遍历关闭其他页签和页面。
- 变更后: 先通过其他页签的关闭回调隐藏旧页面,再显示并激活目标页面。
- 兼容性说明:兼容
- 迁移说明:[可选]
- 调用方无需改动;若业务代码依赖切换过程中两个页面短暂同时可见的副作用,应移除该依赖。
## 验证与落地
- 验证方式:
- 分别创建包含 3 个和 5 个页签的 TabControl,连续点击不同页签。
- 检查日志顺序,确认旧页先关闭、目标页后激活。
- 重复触发同一页签,观察激活和关闭行为。
- 验证结果:
- PDF 历史记录将 3 页签、5 页签和同页签重复触发场景记为正常。
- 本次归档未重新构建 v3.0.0,也未运行 GUI 回归,因此不将历史手工观察视为当前版本验证结果。
- 关联 BUG / Fix:[可选]
- 与页签切换后的绘制一致性相关:[`BUG-20260109-0002-TabControl切换后Table新旧内容重叠.md`](../BUG/BUG-20260109-0002-TabControl切换后Table新旧内容重叠.md)
- 后续修复记录:[`Fix-BUG-20260109-0002-TabControl切换后Table新旧内容重叠.md`](../Fix/Fix-BUG-20260109-0002-TabControl切换后Table新旧内容重叠.md)
- Commit: `5cb59b36525cde93de83313551296c6458694110`
- PR:[可选]
- 发布版本:[可选] v3.0.0
- 备注:[可选]
- 资料来源:`StellarX功能_模块变更.pdf` 第 13-15 页,并以对应提交和历史 `CHANGELOG.md` 交叉核对。
@@ -0,0 +1,66 @@
# 功能变更 ID: Feature-20260109-0002
> 适用场景:记录小到中等规模的功能、接口、行为、默认值或配置变化。
> 不适用场景:新增核心模块、重大模块重构、架构级设计,请使用“新增功能模块”模板。
## 基本信息
- ID: Feature-20260109-0002
- 标题: 删除 Button 尺寸别名 API
- 状态:已完成(历史记录;本次仅归档,未重新验证)
- 类型:删除
- 级别:L2 中等
- 模块: Button / Control
- 版本 / 分支: v3.0.0 / master
- 环境: Windows 11 / Visual Studio 2022 / Debug、Release / EasyX
- 负责人: Ysm-04(历史实现)
## 变更背景
- 背景: Button 自身提供的 `getButtonWidth()` / `getButtonHeight()` 与基类 `Control::getWidth()` / `getHeight()` 的语义和返回值重复。
- 目标: 同一尺寸语义只保留基类正式入口,减少 API 冗余和派生类维护成本。
- 不做什么:[可选]
- 不改变 Button 的宽高字段、尺寸计算或绘制行为。
- 不提供旧名称的兼容包装函数。
## 变更内容
- 变更摘要: 删除 Button 层重复的尺寸获取方法,统一使用 Control 基类接口。
- 新增项:[可选]
- 无。
- 修改项:[可选]
- Button 宽高获取入口统一为 `getWidth()` / `getHeight()`。
- 删除 / 废弃项:[可选]
- `int Button::getButtonWidth() const`
- `int Button::getButtonHeight() const`
- 受影响的文件 / 类 / 函数:
- 历史路径:`include/StellarX/Button.h`
- `Button`、`Control::getWidth()`、`Control::getHeight()`
- 对外 API / 属性变化:[可选]
- 删除两个 Button 公共方法,属于源码级破坏性变更。
## 行为对照
- 变更前: 调用方既可通过 Button 专用别名,也可通过继承自 Control 的接口获取相同尺寸。
- 变更后: 只保留 `getWidth()` 和 `getHeight()`;读取的仍是同一组 `width` / `height` 状态。
- 兼容性说明:不兼容
- 迁移说明:[可选]
- `getButtonWidth()` -> `getWidth()`
- `getButtonHeight()` -> `getHeight()`
- 源码调用必须替换;若使用预编译库,升级后应重新编译和链接。
## 验证与落地
- 验证方式:
- 创建 Button 并设置尺寸,检查 `getWidth()` / `getHeight()` 返回值。
- 全量搜索并编译调用点,确认不存在已删除 API 的残留引用。
- 运行 Button 示例,观察布局、命中区域和重绘行为。
- 验证结果:
- PDF 记录了预期验证方式,但没有附带构建日志、测试报告或截图。
- 本次归档确认历史提交和 v3.0.0 更新日志包含该删除项,但没有重新构建旧版本。
- 关联 BUG / Fix:[可选]
- Commit: `5cb59b36525cde93de83313551296c6458694110`
- PR:[可选]
- 发布版本:[可选] v3.0.0
- 备注:[可选]
- 资料来源:`StellarX功能_模块变更.pdf` 第 16-18 页,并以对应提交和历史 `CHANGELOG.md` 交叉核对。
@@ -0,0 +1,71 @@
# 功能变更 ID: Feature-20260317-0001
> 适用场景:记录小到中等规模的功能、接口、行为、默认值或配置变化。
> 不适用场景:新增核心模块、重大模块重构、架构级设计,请使用“新增功能模块”模板。
## 基本信息
- ID: Feature-20260317-0001
- 标题: TextBox 文本溢出截断
- 状态:已完成(历史记录;本次仅归档,未重新验证)
- 类型:修改
- 级别:L2 中等
- 模块: TextBox
- 版本 / 分支: v3.0.1 / master
- 环境: Windows / Visual Studio 2022 / Debug、Release / EasyX
- 负责人: Ysm-04(历史实现)
## 变更背景
- 背景: TextBox 原先只限制存储字符数,文本像素宽度仍可能超过控件可绘制区域。
- 目标: 绘制时根据实际像素宽度生成带省略号的显示文本,避免文字覆盖相邻控件,同时保持内部原文不变。
- 不做什么:[可选]
- 不修改 `getText()` / `setText()` 的签名。
- 不截断或覆盖 TextBox 内部保存的原始文本。
- 不新增持久缓存或第三方依赖。
## 变更内容
- 变更摘要: `TextBox::draw()` 将存储文本或密码掩码复制到临时 `displayText`,超出可用宽度时逐步缩短并追加 `...`。
- 新增项:[可选]
- 绘制期临时变量 `displayText`。
- 绘制期截断状态 `isTextTruncated`(历史实现中仅作为局部标记,未形成公共状态)。
- 修改项:[可选]
- 使用 `width - 20` 作为保留左右边距后的可用宽度。
- 普通模式和密码模式统一通过 `displayText` 计算尺寸并绘制。
- 删除 / 废弃项:[可选]
- 无。
- 受影响的文件 / 类 / 函数:
- 历史路径:`src/textBox.cpp`
- `TextBox::draw()`
- 对外 API / 属性变化:[可选]
- 无;显示行为增强,`getText()` 仍返回完整原文。
## 行为对照
- 变更前: 文本字符数未超过 `maxCharLen` 时仍可能因字体或内容宽度过大而绘制到 TextBox 边界之外。
- 变更后: 正常宽度条件下,绘制文本会缩短到可用区域并追加省略号,内部原文不受影响。
- 兼容性说明:兼容
- 迁移说明:[可选]
- 无需迁移;调用方如需完整内容,继续使用 `getText()`。
## 验证与落地
- 验证方式:
- 普通文本、空文本、长文本、大字号和密码模式下检查显示边界。
- 调整控件宽度,检查截断与恢复后的显示。
- 对照 `getText()`,确认内部原文没有被截断。
- 验证结果:
- PDF 描述了目标、算法和测试方向,但“验证证据”仍是待填占位内容,没有附带截图、日志或自动化报告。
- 历史源码确认普通路径采用逐字节 `pop_back()` 并重复调用 `textwidth()`;因此不能仅凭原记录认定中文等多字节文本一定不会被截在半个字符上。
- 当 `availableWidth <= 0` 时,历史条件不会进入截断分支,极小控件宽度也不能认定为已经正确覆盖。
- 循环最多删除 n 个字节,但每轮重新测量字符串宽度;若宽度计算与字符串长度相关,最坏时间成本可能接近 O(n²),而不是原记录笼统声称的 O(n)。
- 本次归档未重新构建 v3.0.1,也未运行 GUI 用例。
- 关联 BUG / Fix:[可选]
- Commit:
- 实现提交:`c66cad3423a46ec4ef902965baa4c1816d10148a`
- 发布记录提交:`b765e54eff28ebcd0bd99a81dec80b3ac0769337`
- PR:[可选]
- 发布版本:[可选] v3.0.1
- 备注:[可选]
- 资料来源:`StellarX功能_模块变更.pdf` 第 3-9 页,并以对应提交和历史 `CHANGELOG.md` 交叉核对。
@@ -0,0 +1,77 @@
# 功能变更 ID: Feature-20260317-0002
> 适用场景:记录小到中等规模的功能、接口、行为、默认值或配置变化。
> 不适用场景:新增核心模块、重大模块重构、架构级设计,请使用“新增功能模块”模板。
## 基本信息
- ID: Feature-20260317-0002
- 标题: TextBox::setText 运行期刷新语义调整
- 状态:已完成(历史记录;本次仅归档,未重新验证)
- 类型:修改
- 级别:L2 中等
- 模块: TextBox / Repaint
- 版本 / 分支: v3.0.1 / master
- 环境: Windows 11 / Visual Studio 2022 / Debug、Release / EasyX
- 负责人: Ysm-04(历史实现)
## 变更背景
- 背景:
- 早期 `setText()` 直接调用 `draw()`,在图形上下文尚未初始化时可能崩溃。
- v3.0.0 将 Setter 收敛为更新状态并标脏,解决初始化阶段的生命周期问题,但运行期程序化设置文本可能要等后续事件或重绘周期才能显示。
- 目标: 在不破坏初始化阶段安全性的前提下,让已经绘制过的 TextBox 在运行期更新文本后请求或执行刷新。
- 不做什么:[可选]
- 不改变 `setText(std::string)` 的函数签名。
- 不在没有有效背景快照时强制进入绘制链路。
## 变更内容
- 变更摘要: `setText()` 根据文本是否变化、最大长度、父控件和有效快照状态决定仅标脏、向父控件请求重绘或直接绘制。
- 新增项:[可选]
- 文本未变化时立即返回,避免无效更新。
- 修改项:[可选]
- 文本变化后始终更新内部值并标记为脏。
- 有父控件且 `hasSnap` 为真时,调用 `requestRepaint(parent)`。
- 无父控件且 `hasSnap` 为真时,直接调用 `draw()`。
- `hasSnap` 为假时只保留脏标记,等待首次绘制或统一重绘流程。
- 删除 / 废弃项:[可选]
- 无。
- 受影响的文件 / 类 / 函数:
- 历史路径:`src/textBox.cpp`
- `TextBox::setText(std::string)`
- 对外 API / 属性变化:[可选]
- 函数签名不变;运行期可见刷新时机发生变化。
## 行为对照
- 变更前(v3.0.0): `setText()` 更新内部文本并标脏,不保证调用后立即触发可见重绘。
- 变更后(v3.0.1):
- 初始化/首次绘制前没有有效快照,仅更新文本并标脏,不触发依赖图形上下文的绘制。
- 已绘制且存在有效快照时,通过父容器请求重绘;没有父控件时直接绘制。
- 文本未变化时不执行重复重绘。
- 兼容性说明:部分兼容
- 迁移说明:[可选]
- 若旧代码在 `setText()` 后手动调用 `draw()`,升级到 v3.0.1 后应移除重复绘制,避免闪烁或重复刷新。
- 初始化阶段仍可先设置默认文本,由首次统一绘制完成显示。
## 验证与落地
- 验证方式:
- 创建 TextBox,在窗口和图形上下文建立前调用 `setText()`,确认不会进入绘制链路或中断。
- 窗口运行后,在按钮回调中调用 `setText()`,确认无需额外鼠标事件即可刷新。
- 重复设置相同文本,确认不会产生无效刷新。
- 验证结果:
- PDF 历史记录将“初始化前设置默认文本”和“按钮回调中运行期更新”记为正常。
- 本次归档通过历史提交确认了 `hasSnap` 分支逻辑,但未构建 v3.0.1,也未重新运行 GUI 用例。
- `hasSnap` 是历史实现用于判断是否已具备绘制条件的代理状态;本记录不把它扩展解释为所有图形上下文状态的通用保证。
- 关联 BUG / Fix:[可选]
- 前序问题:[`BUG-20260109-0001-TextBox-setText进入事件循环前调用崩溃.md`](../BUG/BUG-20260109-0001-TextBox-setText进入事件循环前调用崩溃.md)
- v3.0.0 修复:[`Fix-BUG-20260109-0001-TextBox-setText进入事件循环前调用崩溃.md`](../Fix/Fix-BUG-20260109-0001-TextBox-setText进入事件循环前调用崩溃.md)
- Commit:
- 实现提交:`c66cad3423a46ec4ef902965baa4c1816d10148a`
- 发布记录提交:`b765e54eff28ebcd0bd99a81dec80b3ac0769337`
- PR:[可选]
- 发布版本:[可选] v3.0.1
- 备注:[可选]
- 资料来源:`StellarX功能_模块变更.pdf` 第 10-12 页,并以对应提交和历史 `CHANGELOG.md` 交叉核对。
@@ -44,7 +44,7 @@
- 删除 / 废弃项:[可选]
- 删除过长、重复的说明文案。
- 受影响的文件 / 类 / 函数:
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/z-testDome.cpp)
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/app/z-testDome.cpp)
- 对外 API / 属性变化:[可选]
- 无
@@ -46,7 +46,7 @@
- 废弃旧的“位标签和位按钮分别做比例位移”的选择区组织方式
- 废弃散在多个回调中的局部显示刷新
- 受影响的文件 / 类 / 函数:
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/z-testDome.cpp)
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/app/z-testDome.cpp)
- 对外 API / 属性变化:[可选]
- 无框架级 API 变化,属于测试用例迁移
@@ -41,7 +41,7 @@
- 删除 / 废弃项:[可选]
- 无
- 受影响的文件 / 类 / 函数:
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/z-testDome.cpp)
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/app/z-testDome.cpp)
- 对外 API / 属性变化:[可选]
- 无
@@ -60,7 +60,7 @@
## 验证与落地
- 验证方式:
- 对照当前 [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/z-testDome.cpp) 中 `KEY1 ~ KEY5` 的实际结构与近期修复范围进行人工核对。
- 对照当前 [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/app/z-testDome.cpp) 中 `KEY1 ~ KEY5` 的实际结构与近期修复范围进行人工核对。
- 验证结果:
- 当前矩阵与现有测试入口和阶段主线一致。
- 关联 BUG / Fix:[可选]
@@ -47,9 +47,9 @@
- 删除 / 废弃项:[可选]
- 无
- 受影响的文件 / 类 / 函数:
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/Control.h)
- [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Control.cpp)
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/z-testDome.cpp)
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Control.h)
- [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Control.cpp)
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/app/z-testDome.cpp)
- 对外 API / 属性变化:[可选]
- `setHorizontalLayoutSpec(...)`
- `setVerticalLayoutSpec(...)`
@@ -0,0 +1,137 @@
# 功能变更 ID: Feature-20260915-0001
> 适用场景:记录小到中等规模的功能、接口、行为、默认值或配置变化。
> 不适用场景:新增核心模块、重大模块重构、架构级设计,请使用“新增功能模块”模板。
## 基本信息
- ID: Feature-20260915-0001
- 标题: 全量缺陷修复与 API 调整
- 状态:非 UI 与人工 UI 已验证,待提交
- 类型:修改 / 删除 / 重命名
- 级别:L3 重大
- 模块: Control / Window / Canvas / Dialog / TabControl / Table / SxLogger / Test
- 版本 / 分支: 当前工作区
- 环境: Windows + Visual Studio 2022 + MSVC v143 + EasyX
- 负责人: Codex 协作修改
## 变更背景
- 背景:
- 代码审查发现控件回调期间所有权变化、空 TabControl、快照尺寸、几何残影、Dialog 关闭、Table coverage、日志滚动、MBCS 文本、Tooltip 和事件循环等问题。
- 原有测试主要依赖 `KEY` 场景和手工观察,缺少不创建窗口的逻辑验证、ASan 验证以及可重复的外部 UI 断言。
- 目标:
- 修复高、中、低严重度缺陷,消除明确的 UAF、越界、残影、数据截断和所有权丢失风险。
- 通过稳定句柄和延迟变更事务收口控件容器生命周期。
- 建立可重复的逻辑、像素不变量和真实 UI 回归验证。
- 明确破坏性 API 的迁移方式。
- 不做什么:[可选]
- 不迁移 Unicode,继续使用 MBCS/GBK 风格字符处理。
- 不把 GUI 改造成多线程模型。
- 不在本次将 `StellarX.Core` 改为 DLL,也不处理安装包制作。
## 变更内容
- 变更摘要:
- 修复 14 项高、中、低严重度问题。
- 将核心框架独立为 `StellarX.Core` 静态库,并让应用、单元测试和 UI Fixture 共用该实现。
- 新增 Native Unit Test、ASan、EasyX UI Fixture 和 PowerShell 自动化入口。
- 新增项:[可选]
- `StellarX::ControlHandle` 及受控控件所有权接口。
- `MutationTransaction` 延迟变更机制。
- `SxEventLoopInternal.h` 和 `SxTextInternal.h` 内部测试辅助。
- `StellarX.Tests`、`StellarX.UiFixture` 和 `scripts/test.ps1`。
- 修改项:[可选]
- Button/Label/TextBox/Canvas/Table/TabControl 的 geometry、snapshot、coverage 和绘制提交逻辑。
- Dialog 关闭和 overlay 收口逻辑。
- SxLogger sink 配置和滚动逻辑。
- Tooltip tick、MBCS 密码掩码和事件循环批处理。
- 删除 / 废弃项:[可选]
- 删除 `Window::getControls()` 和 `Canvas::getControls()` 的 mutable 容器入口。
- 删除 `Canvas::clearAllControls()`。
- `ControlText::lpszFace` 废弃并替换为 `faceName`。
- 旧的按文本/旧参数添加 Tab 控件方式不再作为首选接口。
- 受影响的文件 / 类 / 函数:
- 工程:`StellarX.Core.vcxproj`、`imGui-easyX.vcxproj`、`imGui-easyX.sln`。
- 所有权和事务:`CoreTypes.h`、`Control.h/.cpp`、`Window.h/.cpp`、`Canvas.h/.cpp`。
- 控件和重绘:`Button.h/.cpp`、`Label.h/.cpp`、`TextBox.cpp`、`Table.cpp`、`Dialog.cpp`、`TabControl.h/.cpp`。
- 日志:`SxLog.h/.cpp`。
- 示例入口:`z-testDome.cpp`。
- 测试:`tests/StellarXTests.cpp`、`tests/UiFixture.cpp`、`scripts/test.ps1`、`scripts/run-ui-tests.ps1`。
- 对外 API / 属性变化:[可选]
- `ControlHandle addControl(std::unique_ptr<Control>)`
- `bool removeControl(ControlHandle)`、`void clearControls()`
- `Control* findControl(ControlHandle)`、`std::vector<ControlHandle> getControlHandles() const`
- `ControlText::faceName` 替代 `lpszFace`。
- `bool SxLogger::setConfig(const SxLogConfig&)`。
- `size_t TabControl::addTab(...)`、`bool setActiveIndex(int)`、`tryAddToPage(...)`。
## 行为对照
- 变更前:
- 回调中直接删除当前或兄弟控件可能使遍历中的裸指针失效。
- 空 TabControl 激活和绘制路径可能访问不存在的页签。
- 控件几何变化、Tooltip、Dialog 和 Table 的局部 coverage 可能留下旧像素或污染快照。
- 日志滚动失败时可能截断原日志;字体名可能引用悬空内存;密码掩码可能按字节而非逻辑字符生成。
- 事件循环每轮只处理一条消息并固定休眠,消息积压时响应和重绘收口不稳定。
- 变更后:
- 控件增删清空在最外层遍历事务结束后提交,回调期间不会释放正在使用的遍历对象。
- 空页签路径安全返回,待激活索引在首个页签加入后应用,非法运行期索引返回 `false`。
- 几何 setter 先恢复旧 coverage,再登记旧新区域并集;快照、绘制和 coverage 使用一致的矩形语义。
- 日志 sink 候选打开成功后才原子替换,滚动重命名失败时继续 append 原文件。
- MBCS 文本按逻辑字符处理,Tooltip 由 tick 链驱动到期和清除。
- 兼容性说明:不兼容
- 迁移说明:[可选]
- 调用方应使用稳定 `ControlHandle`,不再保存可变控件容器引用。
- 将 `lpszFace` 改为 `faceName`,将旧 Tab 添加路径迁移到索引 API。
- 具体示例见 [`MIGRATION.md`](D:/programming/imGUI-easyX/imGui-easyX/开发记录/MIGRATION.md)。
## 全量缺陷修复矩阵
| 严重度 | 问题 | 根因与修复 | 主要验证 |
|---|---|---|---|
| 高 | 回调删除当前控件导致 UAF | 引入稳定 `ControlHandle` 和 `MutationTransaction`;事件、绘制、按钮回调期间的 Add/Remove/Clear 延迟提交。 | self/sibling/remove/clear/add 回调测试;x64 ASan |
| 高 | 空 TabControl 激活索引越界 | 空容器绘制不访问末元素;待激活索引延迟到首个页签加入后应用;非法索引返回 `false`。 | 空容器设置 0/大索引并绘制;随后新增页签 |
| 中 | Button 快照尺寸恒不匹配 | 提取统一快照矩形计算,比较、保存和 coverage 使用相同宽高。 | hover/click/redraw/hide 像素不变量 |
| 中 | 运行时几何 setter 遗留旧像素 | setter 记录旧 coverage、恢复并释放旧快照,再更新矩形并登记旧新区域并集。 | Button/Label/TextBox/Canvas/Table/Tab 移动、缩放、隐藏 |
| 中 | Dialog 清理触发额外全量重绘 | `Close()` 统一登记 overlay 关闭,清理只释放对象和快照,Window 在 sweep 后统一重绘。 | 两层异步 Dialog 关闭上层后下层可见、可交互 |
| 中 | Table 宽度、列宽和 coverage 不一致 | 计算最小宽度并夹紧请求值;列宽总和严格等于有效内容宽;按 MBCS 边界省略文本。 | 极窄 Table、多列长文本、分页按钮和 coverage |
| 中 | 日志滚动失败截断原日志 | 滚动名加入毫秒和递增序号;检查 rename 错误;失败时重新 append 原文件,不执行 trunc。 | 同毫秒滚动、同名文件、无效路径 |
| 中 | ControlText 字体名悬空和地址比较 | 使用拥有型 `std::string faceName`;EasyX/GDI 调用使用 `.c_str()`;比较按字符串内容。 | 临时字符串析构、相同内容不同地址、内容变化测量 |
| 中 | `setConfig()` 不同步文件 sink | 锁内构造并打开候选 sink,成功后替换;禁用、路径、append 和 rotate 变化同步重开。 | 启用/禁用、切换文件、无效路径和输出一致性 |
| 中 | 同名页签可同时激活 | 通过索引排除当前页签,不按标题排除;允许重复标题但只保留一个激活页。 | 两个同名页签来回切换 |
| 低 | Tooltip 静止时不会到期显示 | 增加 Control tick 链,Window 传入单调时钟;到期时置脏并登记重绘;禁用和移出统一 hide。 | 假时钟、静止鼠标、到期显示和移出清除 |
| 低 | 密码掩码按字节生成 | 复用 MBCS 字符边界遍历,每个逻辑字符生成一个 `*`,非法尾字节仍保证前进。 | ASCII、GBK 双字节、非法尾字节 |
| 低 | 事件循环每轮只处理一条消息并固定 Sleep | 提取批处理器,每轮最多 128 条;活动轮不休眠,空闲轮 `Sleep(1)`;批次尾统一收口 resize/重绘。 | 一次注入 1000 条消息;关闭和 resize 顺序 |
| 低 | 按文本添加页控件失败时静默销毁 | `tryAddToPage` 失败原样返还 `unique_ptr`;索引重载为首选,文本重载只匹配首个同名页签。 | 不存在标题、非法索引和同名标题 |
## 验证与落地
- 验证方式:
- 使用 VS Native Unit Test 对所有权、Tab、文本、Tooltip、事件循环和日志逻辑进行测试。
- 使用 Debug x64 ASan 对回调生命周期和核心逻辑进行内存安全验证。
- 使用真实 EasyX UI Fixture 和 Win32 `SendInput`、`SetWindowPos` 驱动窗口。
- 使用 JSON 状态、关键像素、后备缓冲 PNG 和 Fixture 日志进行断言。
- 验证结果:
- 2026-09-29 执行 `scripts/test.ps1 -Suite NonUI`,Debug x64、Debug x64 ASan、Release x64、Debug Win32 均为 15/15 通过。
- x64 ASan 未报告内存错误;KEY1 至 KEY6 均编译、链接通过。
- 2026-09-30 为 `scripts/test.ps1` 补充 UTF-8 BOM 后,使用 Windows PowerShell 5.1 重跑 `NonUI`,结果保持通过。
- 本轮按约定未启动自动 UI Fixture;用户于 2026-09-30 按 `TESTING.md` 手工完成 KEY1 至 KEY6,暂未发现明显问题。
- 2026-07-22 的 UI 六类场景连续三轮通过记录保留为历史证据;当前快照以 2026-09-30 的人工验收结果为准。
- TRX 等诊断产物位于未跟踪的 `TestResults/`。
- 关联 BUG / Fix:[可选]
- 历史重绘和 Tab 相关记录见 `开发记录/BUG`、`开发记录/Fix` 目录。
- 本次新增内容以本记录的严重度矩阵统一收口,避免为同一批次重复创建 14 组 BUG/Fix 文档。
- Commit:
- 当前工作区未提交
- PR:[可选]
- 发布版本:[可选]
- 备注:[可选]
- 当前 `Render` 入口复用 UI Fixture,尚未形成完全独立的进程内 Native Render 测试类别。
- GUI 仍限定在主线程,字符集仍为 MBCS。
## 相关文档
- [`Module-20260915-0001-StellarX核心库与自动化测试基础设施.md`](D:/programming/imGUI-easyX/imGui-easyX/开发记录/模块/Module-20260915-0001-StellarX核心库与自动化测试基础设施.md)
- [`MIGRATION.md`](D:/programming/imGUI-easyX/imGui-easyX/开发记录/MIGRATION.md)
- [`TESTING.md`](D:/programming/imGUI-easyX/imGui-easyX/开发记录/TESTING.md)
@@ -0,0 +1,127 @@
# 新增功能模块 / 模块重构
> 适用场景:新增模块、重大模块重构、核心架构能力演进。
> 不适用场景:小接口或轻量功能变更,请使用“功能变更”模板。
## 基本信息
- 模块 ID:Module-20260109-0001
- 模块名称: SxLog 轻量日志系统
- 状态:已完成(历史记录;本次仅归档,未重新验证)
- 类型:新增模块
- 所属系统 / 子系统: StellarX / Logging / Diagnostics
- 版本 / 分支: v3.0.0 / master
- 环境: Windows x64 / Visual Studio 2022 / Debug、Release / 控制台模式
- 负责人: Ysm-04(历史实现)
## 背景与目标
- 背景: Canvas、TabControl、Table 以及快照重绘链路逐渐复杂,仅依靠断点难以还原事件、重绘和状态变化顺序。
- 当前痛点:
- 缺少统一的日志级别、Tag 过滤和输出渠道。
- 分散日志难以控制噪声,也难以保存为可回放证据。
- 协作和对外输出需要中英文文本选择能力。
- 目标:
- 提供统一日志入口和宏封装。
- 支持级别控制、Tag 白名单/黑名单、控制台输出、文件输出和按大小滚动。
- 提供 `SX_T` / `SxT` 双语文本选择以及作用域耗时记录。
- 非目标:[可选]
- 不负责字符编码转换。
- 不依赖 WinAPI 调试输出通道,不引入第三方日志库。
- 不保证高频同步 I/O 场景下无性能影响。
## 模块边界
- 职责:
- `SxLogger` 负责配置、过滤、格式化与 Sink 分发。
- `ConsoleSink`、`FileSink` 负责具体输出。
- `SxLogLine` 通过 RAII 完成流式拼接和行提交。
- `SxLogScope` 记录作用域耗时。
- 不负责什么:
- 不负责日志采集服务、网络上报、结构化查询或跨进程聚合。
- 不对业务敏感信息进行脱敏。
- 外部依赖:
- C++ 标准库的流、文件、互斥量、原子变量和时间工具。
- `setGBK()` 面向 Windows 控制台环境。
- 对外能力 / API:
- `SxLogger::Get()`、`setMinLevel()`、`setTagFilter()`、`clearTagFilter()`。
- `enableConsole()`、`enableFile()`、`disableFile()`。
- `setLanguage()`、`setGBK()`。
- `SX_LOG_TRACE`、`SX_LOGD/I/W/E/F`、`SX_TRACE_SCOPE`、`SX_T` / `SxT`。
- 关键数据 / 状态:
- `SxLogConfig` 保存级别、格式、Tag 策略以及文件配置。
- Logger 单例持有配置和 Sink;语言选择使用原子状态。
## 设计说明
- 核心流程:
1. 日志宏先调用 `shouldLog()` 判断级别、Tag 和可用 Sink。
2. 命中条件时构造 `SxLogLine` 并进行流式拼接。
3. `SxLogLine` 析构时提交完整日志行。
4. `SxLogger::logLine()` 在互斥保护下格式化并分发给控制台和文件 Sink。
- 关键对象 / 类关系:
- `SxLogger`:中心配置与分发器。
- `ILogSink`:输出目标抽象。
- `ConsoleSink` / `FileSink`:具体输出实现。
- `SxLogLine` / `SxLogScope`:调用端 RAII 辅助对象。
- 生命周期:
- Logger 为进程内单例;Sink 随配置启用或关闭。
- 日志行对象只在命中过滤条件时短暂存在,并在析构时提交。
- 事件 / 渲染 / 数据流:[按模块类型填写]
- 调用点 -> 宏短路 -> 日志行拼接 -> Logger 过滤/格式化 -> Sink 输出。
- 关键不变量:
- `shouldLog()` 返回 false 时不应构造日志行或执行字符串拼接。
- 单条日志必须作为整体写出,不能被其他线程插入破坏。
- 语言切换只选择文本,不进行编码转换。
- 未启用任何 Sink 时应直接短路。
- 降级 / 回退策略:[可选]
- 将最低级别设为 Off,或关闭控制台和文件 Sink。
- 必要时通过编译期开关关闭日志宏。
## 实现与影响
- 关键实现点:
- Tag 过滤使用适合少量 Tag 的线性查找。
- 文件输出支持追加和按字节阈值滚动。
- 输出阶段使用互斥量保证行级串行;语言状态使用原子变量。
- 涉及文件 / 类 / 函数:
- 历史路径:`include/StellarX/SxLog.h`
- 历史路径:`src/SxLog.cpp`
- `SxLogger`、`SxLogConfig`、`ILogSink`、`ConsoleSink`、`FileSink`、`SxLogLine`、`SxLogScope`
- 兼容性影响:
- 新增独立模块,不改变既有控件公共 API;调用方可选择是否接入。
- 性能影响:
- 未命中过滤条件时由宏短路降低开销。
- 命中时存在格式化、互斥和 I/O 成本;高频 Trace 输出可能成为瓶颈并快速增大日志文件。
- 风险点:
- 文件权限、文件占用或滚动重命名失败可能影响持续写入。
- 控制台代码页差异可能导致中文显示不一致。
- 日志内容如果包含敏感数据,需要由调用方自行控制。
## 测试与验证
- 测试范围:
- 日志级别、Tag 白名单/黑名单、控制台和文件输出、文件滚动、语言切换、不可写路径。
- 验证步骤:
1. 启用控制台并依次输出不同级别,检查格式、级别和语言文本。
2. 启用文件输出,验证追加写、Tag 过滤以及不可写路径的返回值。
3. 使用较小的滚动阈值持续输出,检查旧文件重命名和新文件续写。
- 验证结果:
- PDF 给出了上述测试方案,但“验证证据”仍是待填占位内容,没有附带日志、截图或性能数据。
- 本次只确认模块源码、公共接口、提交和发布记录存在,未重新执行运行时测试。
- 已知限制 / 遗留问题:[可选]
- 同步互斥和文件 I/O 不适合无约束的高频热路径。
- `setGBK()` 只调整终端解释输出的方式,不进行字符串编码转换。
## 落地信息
- 关联功能变更 ID:[可选]
- 关联 BUG / Fix:[可选]
- Commit: `5cb59b36525cde93de83313551296c6458694110`
- PR:[可选]
- 发布版本:[可选] v3.0.0
- 相关文档:[可选]
- `StellarX功能_模块变更.pdf` 第 24-28 页。
- 历史 `CHANGELOG.md` 的 v3.0.0 条目。
@@ -69,11 +69,11 @@
- 再将局部矩形映射为世界坐标矩形
- 由控件内部受控路径应用运行态矩形
- 关键对象 / 类关系:
- [`Control`](D:/programming/imGUI-easyX/imGui-easyX/Control.h) 作为统一布局规格与基础解算入口
- [`Window`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp) 负责顶层控件统一收口
- [`Canvas`](D:/programming/imGUI-easyX/imGui-easyX/Canvas.cpp) 负责容器子控件重映射
- [`TabControl`](D:/programming/imGUI-easyX/imGui-easyX/TabControl.cpp) 外层接入统一解算,内部页签栏 / 页面区仍自管
- [`Table`](D:/programming/imGUI-easyX/imGui-easyX/Table.cpp) 通过 `LayoutCapability` 显式禁止 `Y` 轴 Stretch
- [`Control`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Control.h) 作为统一布局规格与基础解算入口
- [`Window`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp) 负责顶层控件统一收口
- [`Canvas`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Canvas.cpp) 负责容器子控件重映射
- [`TabControl`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/TabControl.cpp) 外层接入统一解算,内部页签栏 / 页面区仍自管
- [`Table`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Table.cpp) 通过 `LayoutCapability` 显式禁止 `Y` 轴 Stretch
- 生命周期:
- 设计态矩形 `local*` 在普通 resize / 重排过程中不自动回写
- 运行态矩形由统一解算器产出,再通过内部路径应用
@@ -102,17 +102,17 @@
- 将 `Table` 的 `Y` 轴固定能力边界收回控件自身
- 在 `z-testDome.cpp` 增加 `KEY == 5` 布局专项回归
- 涉及文件 / 类 / 函数:
- [`CoreTypes.h`](D:/programming/imGUI-easyX/imGui-easyX/CoreTypes.h)
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/Control.h)
- [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Control.cpp)
- [`Canvas.h`](D:/programming/imGUI-easyX/imGui-easyX/Canvas.h)
- [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Canvas.cpp)
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp)
- [`TabControl.h`](D:/programming/imGUI-easyX/imGui-easyX/TabControl.h)
- [`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/TabControl.cpp)
- [`Table.h`](D:/programming/imGUI-easyX/imGui-easyX/Table.h)
- [`Table.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Table.cpp)
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/z-testDome.cpp)
- [`CoreTypes.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/CoreTypes.h)
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Control.h)
- [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Control.cpp)
- [`Canvas.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Canvas.h)
- [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Canvas.cpp)
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp)
- [`TabControl.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/TabControl.h)
- [`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/TabControl.cpp)
- [`Table.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Table.h)
- [`Table.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Table.cpp)
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/app/z-testDome.cpp)
- 兼容性影响:
- 向后兼容旧锚点 API
- 当前阶段未删除旧 getter / setter
@@ -67,12 +67,12 @@
- 内容驱动控件在自己的受控路径里刷新运行态尺寸。
- 局部重绘提交后,由父容器按实际绘制顺序补画 coverage 上方的 overlay 兄弟。
- 关键对象 / 类关系:
- [`Control`](D:/programming/imGUI-easyX/imGui-easyX/Control.h):几何语义、解算入口、设计基线提交入口。
- [`Label`](D:/programming/imGUI-easyX/imGui-easyX/Label.cpp):内容驱动尺寸,`draw()` 只消费运行态矩形。
- [`Canvas`](D:/programming/imGUI-easyX/imGui-easyX/Canvas.cpp):管理直接子控件的世界坐标映射与局部 overlay 补画。
- [`TabControl`](D:/programming/imGUI-easyX/imGui-easyX/TabControl.cpp):外层接统一解算,内部页签栏 / 页面区继续自管。
- [`Table`](D:/programming/imGUI-easyX/imGui-easyX/Table.cpp):当前版本 `X Stretch / Y Fixed`,并保留内部受控结构尺寸基线刷新。
- [`Window`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp):顶层托管重绘收口与 overlay 兄弟补画。
- [`Control`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Control.h):几何语义、解算入口、设计基线提交入口。
- [`Label`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Label.cpp):内容驱动尺寸,`draw()` 只消费运行态矩形。
- [`Canvas`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Canvas.cpp):管理直接子控件的世界坐标映射与局部 overlay 补画。
- [`TabControl`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/TabControl.cpp):外层接统一解算,内部页签栏 / 页面区继续自管。
- [`Table`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Table.cpp):当前版本 `X Stretch / Y Fixed`,并保留内部受控结构尺寸基线刷新。
- [`Window`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp):顶层托管重绘收口与 overlay 兄弟补画。
- 生命周期:
- 普通 resize / 父容器重排不自动回写 `local*`。
- 显式设计基线提交只通过 `commitCurrentGeometryAsDesignRect()` 或控件内部受控结构刷新点发生。
@@ -93,21 +93,21 @@
## 实现与影响
- 关键实现点:
- 在 [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Control.cpp) 增加布局降级日志、能力边界拦截日志、显式设计基线提交日志。
- 在 [`Label.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Label.cpp) 收口内容驱动尺寸刷新,并显式关闭双轴 Stretch。
- 在 [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Canvas.cpp)、[`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/TabControl.cpp)、[`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp) 收口 overlay 补画。
- 在 [`Table.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Table.cpp) 收口 `Y Fixed`、分页按钮视觉链与页码重绘。
- 在 [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Control.cpp) 增加布局降级日志、能力边界拦截日志、显式设计基线提交日志。
- 在 [`Label.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Label.cpp) 收口内容驱动尺寸刷新,并显式关闭双轴 Stretch。
- 在 [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Canvas.cpp)、[`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/TabControl.cpp)、[`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp) 收口 overlay 补画。
- 在 [`Table.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Table.cpp) 收口 `Y Fixed`、分页按钮视觉链与页码重绘。
- 涉及文件 / 类 / 函数:
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/Control.h)
- [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Control.cpp)
- [`Label.h`](D:/programming/imGUI-easyX/imGui-easyX/Label.h)
- [`Label.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Label.cpp)
- [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Canvas.cpp)
- [`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/TabControl.cpp)
- [`Table.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Table.cpp)
- [`TextBox.cpp`](D:/programming/imGUI-easyX/imGui-easyX/TextBox.cpp)
- [`Dialog.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Dialog.cpp)
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp)
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Control.h)
- [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Control.cpp)
- [`Label.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Label.h)
- [`Label.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Label.cpp)
- [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Canvas.cpp)
- [`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/TabControl.cpp)
- [`Table.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Table.cpp)
- [`TextBox.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/TextBox.cpp)
- [`Dialog.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Dialog.cpp)
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp)
- 兼容性影响:
- 对旧锚点 API 保持兼容。
- `Label::textStyle` 仍为公开字段,但使用约定更严格。
@@ -61,12 +61,12 @@
- 内容驱动控件在自身受控路径中刷新运行态尺寸;设计基线不得在普通布局过程中自动漂移。
- 托管局部重绘按“脏子树提交 -> 直接分支 coverage -> 传递式 overlay 补画”收口,确保嵌套容器、Tooltip、上层兄弟遮挡链闭合。
- 关键对象 / 类关系:
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/Control.h):统一布局解算、设计基线提交、托管重绘底座
- [`Label.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Label.cpp):内容驱动尺寸刷新前移,`draw()` 只消费运行态矩形
- [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Canvas.cpp):直接子树映射、脏子树提交、局部 overlay 补画
- [`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/TabControl.cpp):外层统一布局,内部页签栏/页面区自管,绘制顺序与局部提交顺序统一
- [`Table.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Table.cpp):当前版本 `X Stretch / Y Fixed`、分页按钮视觉与页码重绘修复
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp):顶层托管重绘、传递式 overlay 补画、顶层 coverage 收口
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Control.h):统一布局解算、设计基线提交、托管重绘底座
- [`Label.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Label.cpp):内容驱动尺寸刷新前移,`draw()` 只消费运行态矩形
- [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Canvas.cpp):直接子树映射、脏子树提交、局部 overlay 补画
- [`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/TabControl.cpp):外层统一布局,内部页签栏/页面区自管,绘制顺序与局部提交顺序统一
- [`Table.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Table.cpp):当前版本 `X Stretch / Y Fixed`、分页按钮视觉与页码重绘修复
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp):顶层托管重绘、传递式 overlay 补画、顶层 coverage 收口
- 生命周期:
- 普通 resize / 重排不自动回写 `local*`
- 公开 setter 只写运行态,不隐式提交设计基线
@@ -94,26 +94,26 @@
- 修复 Tab 页签按钮与页面绘制顺序不一致导致 Tooltip 被页面覆盖的问题
- 修复重复激活已激活页签时的残影 / 快照链扰动问题
- 涉及文件 / 类 / 函数:
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/Control.h)
- [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Control.cpp)
- [`Label.h`](D:/programming/imGUI-easyX/imGui-easyX/Label.h)
- [`Label.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Label.cpp)
- [`Button.h`](D:/programming/imGUI-easyX/imGui-easyX/Button.h)
- [`Button.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Button.cpp)
- [`Canvas.h`](D:/programming/imGUI-easyX/imGui-easyX/Canvas.h)
- [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Canvas.cpp)
- [`TabControl.h`](D:/programming/imGUI-easyX/imGui-easyX/TabControl.h)
- [`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/TabControl.cpp)
- [`Table.h`](D:/programming/imGUI-easyX/imGui-easyX/Table.h)
- [`Table.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Table.cpp)
- [`TextBox.h`](D:/programming/imGUI-easyX/imGui-easyX/TextBox.h)
- [`TextBox.cpp`](D:/programming/imGUI-easyX/imGui-easyX/TextBox.cpp)
- [`Dialog.h`](D:/programming/imGUI-easyX/imGui-easyX/Dialog.h)
- [`Dialog.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Dialog.cpp)
- [`Window.h`](D:/programming/imGUI-easyX/imGui-easyX/Window.h)
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Window.cpp)
- [`MessageBox.h`](D:/programming/imGUI-easyX/imGui-easyX/MessageBox.h)
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/z-testDome.cpp)
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Control.h)
- [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Control.cpp)
- [`Label.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Label.h)
- [`Label.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Label.cpp)
- [`Button.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Button.h)
- [`Button.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Button.cpp)
- [`Canvas.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Canvas.h)
- [`Canvas.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Canvas.cpp)
- [`TabControl.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/TabControl.h)
- [`TabControl.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/TabControl.cpp)
- [`Table.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Table.h)
- [`Table.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Table.cpp)
- [`TextBox.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/TextBox.h)
- [`TextBox.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/TextBox.cpp)
- [`Dialog.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Dialog.h)
- [`Dialog.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Dialog.cpp)
- [`Window.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Window.h)
- [`Window.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Window.cpp)
- [`MessageBox.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/MessageBox.h)
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/app/z-testDome.cpp)
- 兼容性影响:
- 旧锚点 API 仍可用,内部实现已完全转到新布局模型
- `Label::textStyle` 仍为公开字段,但要求样式修改后显式 `setDirty(true)`
@@ -72,9 +72,9 @@
- 新 API 被调用后,控件自动切到 `AnchorToEdges` 布局模式。
- 运行时统一解算继续以 `layoutSpec` 为唯一依据。
- 关键对象 / 类关系:
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/Control.h):公开布局策略 API 的对外入口。
- [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Control.cpp):新 API 的实现、新旧 API 映射共存规则。
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/z-testDome.cpp):`KEY2` 迁移示例与验证入口。
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Control.h):公开布局策略 API 的对外入口。
- [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Control.cpp):新 API 的实现、新旧 API 映射共存规则。
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/app/z-testDome.cpp):`KEY2` 迁移示例与验证入口。
- 生命周期:
- 新 API 只影响运行态布局策略,不自动提交设计基线。
- 设计基线仍由显式提交路径或控件内部受控路径维护。
@@ -97,9 +97,9 @@
- 保留 `setAnchor(...)` 与旧 getter,不破坏兼容路径。
- 以 `KEY2` 位选择区、功能区、显示区和配置区作为迁移样例,验证新 API 的可用性。
- 涉及文件 / 类 / 函数:
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/Control.h)
- [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/Control.cpp)
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/z-testDome.cpp)
- [`Control.h`](D:/programming/imGUI-easyX/imGui-easyX/include/StellarX/Control.h)
- [`Control.cpp`](D:/programming/imGUI-easyX/imGui-easyX/src/StellarX/Control.cpp)
- [`z-testDome.cpp`](D:/programming/imGUI-easyX/imGui-easyX/app/z-testDome.cpp)
- 兼容性影响:
- 兼容旧 `setAnchor(...)` 调用。
- 新 API 为新增能力,不影响现有二进制接口使用方式。
@@ -0,0 +1,149 @@
# 新增功能模块 / 模块重构
> 适用场景:新增模块、重大模块重构、核心架构能力演进。
> 不适用场景:小接口或轻量功能变更,请使用“功能变更”模板。
## 基本信息
- 模块 ID:Module-20260915-0001
- 模块名称:StellarX 核心库与自动化测试基础设施
- 状态:非 UI 与人工 UI 已验证,待提交
- 类型:模块重构 / 架构演进
- 所属系统 / 子系统:StellarX Core / Unit Test / UI Regression
- 版本 / 分支:当前工作区
- 环境:Windows + Visual Studio 2022 + MSVC v143 + EasyX
- 负责人:Codex 协作修改
## 背景与目标
- 背景:
- 原应用工程同时承担框架实现、示例入口和回归验证职责,框架代码与测试代码之间缺少明确边界。
- 本次全量缺陷修复涉及控件生命周期、重绘、事件循环、MBCS 文本和日志等多个核心模块,需要让应用、单元测试和真实 UI Fixture 复用完全相同的实现。
- 当前痛点:
- 核心框架源文件分散在应用工程中,难以单独复用和验证。
- 仅依赖手工启动窗口,无法稳定覆盖回调期间所有权变化、ASan 内存安全和多配置构建。
- UI 控件基于 EasyX 绘制,不是原生子窗口,UIA/WinAppDriver 无法直接按控件语义定位。
- 目标:
- 将框架实现集中编译为 `StellarX.Core` 静态库。
- 建立纯逻辑单元测试、真实 EasyX Fixture 和外部 Win32 UI 驱动三层验证。
- 让 Debug、Release、Win32、x64 和 x64 ASan 使用统一的核心实现。
- 非目标:[可选]
- 不在本次工作中将静态库改为 DLL。
- 不迁移到 Unicode,不改变 GUI 只能在主线程使用的约束。
- 不将 UI 测试改造成依赖 DPI 或字体的全图 golden image 测试。
## 模块边界
- 职责:
- `StellarX.Core.vcxproj` 编译 `Window`、`Control`、`Canvas`、各类控件、Dialog、TabControl 和日志实现。
- 主应用、Native Unit Test 和 UI Fixture 通过 `ProjectReference` 使用同一份核心实现。
- `scripts/test.ps1` 统一组织 Unit、Render、UI、All 和 CI 测试入口。
- `scripts/run-ui-tests.ps1` 负责启动 Fixture、注入鼠标输入、等待结果并保存诊断文件。
- 不负责什么:
- 不负责最终应用资源、业务示例和安装包制作。
- 不把 `StellarX.Core.lib` 作为普通 GUI 应用用户的运行时 DLL。
- 不替代 EasyX、Visual C++ Runtime 等外部依赖的发布判断。
- 外部依赖:
- Visual Studio 2022、MSVC v143、Windows SDK。
- EasyX 图形库。
- Visual Studio Native Unit Test Framework。
- UI 套件需要可交互的 Windows 桌面,并会短暂切换前台窗口和移动鼠标。
- 对外能力 / API:
- 核心库对外 API 通过 `StellarX.h` 汇总。
- `StellarX.Core` 本身是构建期静态库,不新增运行时动态加载接口。
- 测试入口通过 PowerShell 参数和 Fixture 场景提供,不属于框架公共 API。
- 关键数据 / 状态:
- `StellarX.Core.lib`:框架实现的静态链接产物。
- `StellarX.Tests.dll`:Native Unit Test 产物。
- `StellarX.UiFixture.exe`:真实 EasyX UI 场景进程。
- `TestResults/`:TRX、JSON、PNG 和日志诊断产物。
## 设计说明
- 核心流程:
1. `StellarX.Core.vcxproj` 编译框架头文件和源文件。
2. `imGui-easyX.vcxproj` 通过 `ProjectReference` 链接核心静态库,应用入口保留在 `z-testDome.cpp`。
3. `StellarX.Tests` 和 `StellarX.UiFixture` 同样引用核心工程,避免测试复制另一份框架代码。
4. Release 构建将核心代码静态链接进目标可执行文件;运行时不要求单独加载 `StellarX.Core.dll`。
- 关键对象 / 类关系:
- `StellarX.Core.vcxproj`:核心实现的构建边界。
- `Window / Canvas / Control`:窗口托管、控件所有权、事件和重绘基础。
- `SxEventLoopInternal.h`:批量事件处理测试辅助。
- `SxTextInternal.h`:MBCS 字符边界和密码掩码测试辅助。
- `StellarX.Tests`:不创建窗口的逻辑测试。
- `StellarX.UiFixture`:创建确定性 EasyX 窗口并导出状态和像素证据。
- 生命周期:
- 核心库在构建阶段生成,静态链接进应用或测试可执行文件。
- Unit 测试进程独立运行;涉及 UAF 的回归可在 ASan 配置中隔离执行。
- UI Fixture 按场景创建和关闭图形上下文,失败时由脚本保存证据并发送 `WM_CLOSE`。
- 事件 / 渲染 / 数据流:
- 逻辑测试使用 FakeControl、假时钟、假消息源和临时目录验证状态转移。
- UI 驱动程序通过 `SendInput` 注入鼠标事件,通过 `SetWindowPos` 驱动窗口变化。
- Fixture 导出 JSON 状态和 EasyX 后备缓冲像素,脚本按关键像素不变量判断结果。
- 关键不变量:
- 应用、逻辑测试和 UI Fixture 必须链接同一份核心实现。
- ASan 产物与普通 Debug 产物使用独立目录。
- UI 场景串行执行,避免多个 EasyX 图形上下文争用桌面。
- UI 断言不依赖 DPI、字体抗锯齿和完整截图的像素一致性。
- 降级 / 回退策略:[可选]
- 没有交互桌面时,CI 只运行逻辑测试;设置 `STELLARX_UI_TESTS=1` 且环境满足条件时才执行 UI。
- 全局 resize、Dialog 开关等场景保留整场景重绘兜底。
## 实现与影响
- 关键实现点:
- 新增 `StellarX.Core` 静态库工程和解决方案项目引用。
- 新增 `StellarX.Tests`、`StellarX.UiFixture`、PowerShell 测试脚本和诊断输出约定。
- 新增 `SxEventLoopInternal.h`、`SxTextInternal.h` 两个仅供内部实现和测试使用的头文件。
- 核心控件采用稳定句柄、延迟变更事务和统一 geometry/coverage 处理。
- 涉及文件 / 类 / 函数:
- 工程:`StellarX.Core.vcxproj`、`imGui-easyX.vcxproj`、`imGui-easyX.sln`。
- 核心:`Control`、`Window`、`Canvas`、`Button`、`Label`、`TextBox`、`Table`、`Dialog`、`TabControl`、`SxLogger`。
- 测试:`tests/StellarXTests.cpp`、`tests/UiFixture.cpp`、`scripts/test.ps1`、`scripts/run-ui-tests.ps1`。
- 兼容性影响:
- 工程内部从直接编译框架源文件调整为引用 `StellarX.Core`。
- 本阶段同时包含破坏性公共 API 调整,迁移方式见 [`MIGRATION.md`](D:/programming/imGUI-easyX/imGui-easyX/开发记录/MIGRATION.md)。
- 性能影响:
- 事件循环单轮最多处理 128 条消息,活动轮不固定休眠,空闲轮短暂休眠。
- 静态库不引入运行时动态分发开销;托管重绘仍按覆盖范围选择性提交并保留全局兜底。
- 风险点:
- EasyX UI 测试依赖前台交互桌面,不能作为无桌面 CI 的默认测试。
- 当前 `Render` 入口复用 UI Fixture,尚未形成完全独立的进程内 Native Render 测试类别。
- 编译仍存在 EasyX `C4201`、未使用变量和名称遮蔽等非阻断警告。
## 测试与验证
- 测试范围:
- 所有权事务、空 TabControl、Tab 索引、字体拥有权、MBCS 掩码、Tooltip tick、事件批处理和日志配置。
- Button、Label、TextBox、Canvas、Table、Dialog、TabControl 的真实 EasyX 重绘和 coverage 不变量。
- Debug x64、Debug x64 ASan、Release x64、Debug Win32,以及 KEY1 至 KEY6 编译。
- 验证步骤:
1. 使用 `scripts/test.ps1 -Suite NonUI` 验证四套逻辑配置、ASan 和 KEY1 至 KEY6 编译。
2. 按 `TESTING.md` 的人工 UI 清单验证 KEY1、KEY2、KEY4、KEY5、KEY6。
3. 检查 TRX 和人工验收记录;只有明确允许自动 UI 时才使用 `scripts/test.ps1 -Suite All`。
- 验证结果:
- 2026-09-29 的 Debug x64、Debug x64 ASan、Release x64、Debug Win32 均为 15/15 通过。
- x64 ASan 未报告内存错误;KEY1 至 KEY6 均编译、链接通过。
- 2026-09-30 为 `scripts/test.ps1` 补充 UTF-8 BOM 后,Windows PowerShell 5.1 下的 `NonUI` 全矩阵复验通过。
- 本轮未启动自动 UI Fixture;用户于 2026-09-30 手工完成 KEY1 至 KEY6,暂未发现明显问题。
- 2026-07-22 的 UI 六类场景三轮通过记录保留为历史证据;当前快照以 2026-09-30 的人工验收结果为准。
- 已知限制 / 遗留问题:[可选]
- `Render` 当前复用单轮真实 UI Fixture;后续可单独建立进程内渲染测试类别。
- UI 自动化仍需要已解锁的交互式桌面。
- 本次未清理已有非阻断编译警告。
## 落地信息
- 关联功能变更 ID:[可选]
- `Feature-20260915-0001`
- 关联 BUG / Fix:[可选]
- 本次以全量缺陷修复矩阵统一记录,未为每个历史问题重新创建重复 BUG/Fix 文档。
- Commit:
- 当前工作区未提交
- PR:[可选]
- 发布版本:[可选]
- 相关文档:[可选]
- [`TESTING.md`](D:/programming/imGUI-easyX/imGui-easyX/开发记录/TESTING.md)
- [`MIGRATION.md`](D:/programming/imGUI-easyX/imGui-easyX/开发记录/MIGRATION.md)