Files
cmshoppe/docs/tasks/T-629.md
T

92 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: T-629
title: ②封面画廊切换记录后候选勾选区自适应布局修复
phase: 7
deps: [T-626]
status: TODO
created: 2026-07-14
---
## 问题 / 背景
②「AI生成」列表双击任务行后会打开封面画廊。当前记录首次显示时,右侧生成图及其勾选框位置正常;按 `↑ / ↓` 切换到上一条或下一条记录后,候选图下方的勾选框可能进入图片显示区域,造成控件与图片重叠。
已核实的代码现状:
- T-626 让候选图片尺寸根据 viewport 和候选数量在 `180~360px` 内变化,但候选 item 仍由“图片 + `QRadioButton` + 说明文字”共同组成,整体高度没有使用各子控件的实际尺寸统一计算。
- `_candidate_thumbnail_target_size()` 使用固定 `CANDIDATE_TEXT_HEIGHT = 72` 预留图片下方区域;实际勾选框和说明文字高度会受候选名称、自动换行、字体及 Windows DPI 影响,固定值不能代表下一条记录的真实高度。
- `_rebuild_candidate_items()` 切换任务时先沿用上一条记录的 `candidate_thumbnail_size` 创建新 item,再依赖约 `60ms` 的 resize timer 延迟重算;切换瞬间可能出现“新内容 + 旧尺寸/旧高度”的布局组合。
- `_update_candidate_thumbnail_layout()` 主要更新 item 宽度和图片 label 的正方形尺寸,没有显式把 item 最小高度同步为本轮布局的真实 `sizeHint()`。
- 候选滚动区关闭垂直滚动;内容高度估算不足时没有垂直空间兜底,控件会被压缩、裁切或表现为进入图片区域。
- 现有 GUI 测试验证了候选图片尺寸、横向内容宽度和任务切换,但测试样本的候选数量较接近,也没有断言图片、勾选框和说明文字之间的几何关系,因此未覆盖本缺陷。
## 方案
### 1. 按完整候选卡片计算布局
- 修改 `app/gui/tabs/generate.py` 的 `CoverGalleryDialog`,将每个候选项作为完整卡片管理:图片、勾选框、说明文字和布局间距必须参与同一次尺寸计算。
- 推荐抽出内部候选卡片组件或等价 helper,提供 `set_thumbnail_size(size)`;更新图片尺寸后主动使内部布局失效并重新计算几何尺寸。
- 不再把固定 `CANDIDATE_TEXT_HEIGHT = 72` 作为图片下方区域的唯一高度依据。图片目标尺寸应根据 viewport 扣除候选卡片实际控制区高度后计算;固定值最多只能作为最小预留值。
- 控制区实际高度至少包含:
- `QRadioButton.sizeHint().height()`;
- 说明文字在当前 item 宽度下的 `heightForWidth()` 或布局实际 `sizeHint()`;
- 布局 margins 和 spacing。
- 每轮更新后,候选 item 的最小高度必须不小于内部布局实际所需高度;`candidate_content` 的最小高度和实际高度必须不小于所有候选 item 的最大所需高度。
- 勾选框必须处于独立的图片下方布局行,不使用绝对坐标或覆盖层放置。
### 2. 切换任务时同步重排
- `switch_task()` / `_reload_task_and_candidates()` 重建候选列表后,应基于新任务的候选数量和当前 viewport 立即执行一次强制尺寸计算及布局同步。
- 新候选 item 不应等待 resize timer 才从上一条记录尺寸切换到当前记录尺寸。现有约 `60ms` timer 可继续用于合并窗口连续 resize 事件,但不能作为任务切换正确性的唯一保障。
- 同步更新候选 item、图片 label、候选内容容器的宽高后,调用必要的 `invalidate()`、`activate()`、`updateGeometry()` 或等价 Qt 布局刷新接口。
- 切换记录后把候选横向滚动位置重置到起点,避免下一条记录打开时停留在上一条的横向偏移;不得改变当前候选选择、未保存确认及 `↑ / ↓` 导航语义。
### 3. 候选文案与防御性布局
- 候选项可见名称保持简短稳定,例如「当前生效」「候选 2」;完整文件名放入中文 tooltip,避免长文件名通过换行无上限撑高控制区。
- 元信息保持简洁,优先单行或受控换行;图片读取失败占位仍占用与正常图片相同的图片区域。
- 正常路径应通过正确的内容高度消除重叠,不以开启垂直滚动条作为主要修复。若保留防御性垂直滚动,仍必须保证每个候选卡片内部不重叠。
- 画廊处于最小支持窗口或 Windows 高 DPI 环境时,可以缩小图片或出现横向滚动,但不能让图片覆盖勾选框和说明文字。
### 4. 回归测试
- 更新 `tests/test_gui.py`,构造候选数量和文案高度明显不同的两条任务,覆盖:
- 单候选切换到 3 张以上候选;
- 多候选切换回单候选;
- 连续按 `↑ / ↓` 往返切换;
- 当前生效候选、历史候选、长文件名及损坏图片占位。
- 测试应在弹窗显示并处理 Qt 事件后检查真实几何关系,至少断言:
- 勾选框顶部位于对应图片底部和布局间距之后;
- 说明文字位于勾选框之后;
- item 最小高度覆盖内部布局 `sizeHint()`;
- `candidate_content` 高度覆盖最高候选 item;
- 所有候选 item 的横向范围不互相重叠。
- 分别检查任务切换同步完成后的状态,以及 resize timer 触发后的状态,防止只在延迟重排后暂时恢复。
- 保留 T-572、T-573、T-574、T-626 的方向键导航、横向滚动、保存和响应式缩放回归测试。
## 验收要点
- 双击②列表任意行打开封面画廊后,按 `↓` 或 `↑` 切换记录,所有候选图的勾选框始终完整位于图片下方,不进入图片、遮挡图片或被裁切。
- 在单候选、多候选、长候选名称、图片读取失败及候选数量变化场景下,图片、勾选框和说明文字均不重叠。
- 切换记录完成后立即获得正确布局,不出现可感知的先重叠再跳回现象。
- 调整弹窗大小后,候选图片继续按 T-626 的范围自适应,候选卡片整体高度同步刷新。
- 在常见 `1366×768`、`1920×1080` 及 Windows 100% / 125% / 150% 显示缩放下,控件无重叠和明显布局抖动。
- 不影响候选选择、保存提示、重置图片、原图查看、未保存确认和 `↑ / ↓` 切换记录。
- 验证命令:
- `py -3.10 -m unittest tests.test_gui`
- `python -m ruff check app tests main.py`
- `py -3.10 -m compileall app main.py`
- `py -3.10 -m unittest discover -s tests`
- `git diff --check`
## 边界(不改什么)
- 不修改候选图发现、归档、排序、选择或保存语义。
- 不修改 SQLite schema、任务生成状态、提示词、AI/cmhub 请求、生图并发或图片保存格式。
- 不修改 Excel、③更新蝦皮、CDP 或蝦皮网页操作逻辑。
- 不通过增大固定窗口、硬编码更大的文字区高度或隐藏勾选框规避布局问题。
## 执行记录
- 待实现。