完成 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
@@ -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)