代码复审 p2-2items / 视觉复审 p2-2items 的修复轮(配色重拍等 M70 定案)。
M69 迁移(2 行)
- nav 轨宽:nav_rail_width(metrics) 函数体改用 metrics.nav_rail_width
- theme::STROKE_PX 改为 pub use unlucky_design::stroke::HAIRLINE as STROKE_PX
P2-1 零硬编码守门补洞(reviewer 造样 5 个漏网样本全部补上,并进自检正向断言)
- COLOR 调用表补 from_rgb8 / rgb8 / rgba(`from_rgb(` 子串匹配不到 `from_rgb8(` 是根因)
- 新增 `Color { r: .., g: .. }` 结构体字面量检查
- 尺寸规则遇到 Length::Fixed( / Padding::from( / Size::new( 往里剥一层再判数字
- 新增渲染层回归守门 pill_never_overrides_its_size_with_fill(禁 nav.rs 出现 center_*( ):
视觉复审 P2-1 的病根就是 center_*(Length::Fill) 把容器尺寸覆盖成 Fill
视觉 P2-1 药丸 88×32 → 56×32(实测 x=16..71)
- 去掉 center_x/center_y(Fill);居中改由父 column 的 align_x + 显式 width(轨宽) 完成
(只去掉 center_* 还不够:column 会 shrink 到 56,药丸贴左缘 x=0..55,是同一个坑的第二层)
视觉 P2-2 / 用户点名 caption 图标 46 → 24(按钮容器保持 46×48)
- button 内包 container(...).width(24).height(24).center(Length::Fill):
只设 width/height 会让容器缩到 24 并贴 button 内容左上角;center(Fill) 才既撑满又居中
视觉 P3-1 东侧/南侧缩放热区改成叠层
- 原来是 row 的兄弟列,吃掉 4px 布局宽(右留白 28、关闭键离窗角 4px);
现在用 stack! + 对齐容器叠在工作区上,布局宽回到 1180(卡片右缘 x=1155 = 右留白 24)
P3 清单
1. --data-dir 接受并忽略(翻牌前 CI 兼容;现役 smoke 命令行可原样跑,exit 0)+ 单测
2. --diff 内建 ±N px 平移扫描(--diff-scan),CI 不再依赖 numpy
3. 截图模式等 SystemTheme 到达才出图(否则暗色系统上会拍到浅色)
4. (Auto, 未编入 wgpu) 返回 tiny-skia,不再把 wgpu 写进候选列表
5. --renderer=gpu 也跳过适配器枚举(决策与探测无关,省 ~0.6~1.2s)
6. icons::handle 加 debug_assert_eq!(IconKind::ALL[kind as usize], kind)
7. 轨顶留白改 container padding-top,实测 24(原来 spacer + column spacing = 28)
顺带:shot.rs 拆成 shot/mod.rs + shot/diff.rs(≤400 行门禁)
门禁:fmt OK / clippy -D warnings 干净 / 46+5 测试全过 / cargo check --workspace --all-targets OK
体积:双后端 6,769,664 B(6.46 MiB)、仅 CPU 3,955,712 B(3.77 MiB)
截图:r1 代码验证图(r1-verify / hover / pressed / compare-nav-rail)已出;4 张交付主图待 M70 定案后重拍
101 lines
3.4 KiB
Rust
101 lines
3.4 KiB
Rust
//! 截图通道(D30 验收用):`iced::window::screenshot` 的真窗口出图 → PNG,以及两图逐像素对比。
|
||
//!
|
||
//! ## 为什么用真窗口而不是 `iced_test::Emulator`
|
||
//!
|
||
//! 任务书首选路径①:真开窗截图覆盖了标题栏/DPI/窗口管理的真实行为,离屏通道不覆盖
|
||
//! (M60 §Q6 的边界说明)。本机与 Windows CI 都有交互会话,所以走路径①。
|
||
//!
|
||
//! ## 关键事实(M60 踩坑 Q3)
|
||
//!
|
||
//! `Screenshot` 的字节字段在 0.14 叫 **`rgba`**(0.13 是 `bytes`),`size` 是**物理像素**,
|
||
//! 另带 `scale_factor`(换算逻辑坐标用)。落盘用 `image::save_buffer` + `ExtendedColorType::Rgba8`。
|
||
//!
|
||
//! ## 为什么自己带一个 diff
|
||
//!
|
||
//! D30 的硬项是「GPU 档与 CPU 档**几何逐像素一致**,只有颜色/材质不同」。CI 里不该假设有
|
||
//! Python/PIL/ImageMagick,所以把「读两张 PNG → 数差异像素 → 给最大/平均通道差」做进本程序
|
||
//! (`--diff a.png b.png`),复用同一个二进制。
|
||
|
||
use std::path::{Path, PathBuf};
|
||
|
||
use iced::window;
|
||
|
||
use crate::theme;
|
||
|
||
/// 把一帧截图写到 `path`(自动建父目录)。
|
||
///
|
||
/// # Errors
|
||
/// 目录建不出来、PNG 编码失败、字节数与尺寸对不上时返回人话(截图通道要能一眼看出哪一步断)。
|
||
pub fn save(screenshot: &window::Screenshot, path: &Path) -> Result<Summary, String> {
|
||
let width = screenshot.size.width;
|
||
let height = screenshot.size.height;
|
||
let bytes = screenshot.rgba.as_ref();
|
||
|
||
let expected = width as usize * height as usize * 4;
|
||
if bytes.len() != expected {
|
||
return Err(format!(
|
||
"截图字节数对不上:{width}x{height} 应该 {expected} B,实际 {} B",
|
||
bytes.len()
|
||
));
|
||
}
|
||
|
||
write_rgba(bytes, width, height, path)?;
|
||
|
||
Ok(Summary {
|
||
width,
|
||
height,
|
||
scale_factor: screenshot.scale_factor,
|
||
path: path.to_path_buf(),
|
||
})
|
||
}
|
||
|
||
fn write_rgba(bytes: &[u8], width: u32, height: u32, path: &Path) -> Result<(), String> {
|
||
if let Some(parent) = path.parent() {
|
||
if !parent.as_os_str().is_empty() {
|
||
std::fs::create_dir_all(parent)
|
||
.map_err(|error| format!("建目录 {} 失败:{error}", parent.display()))?;
|
||
}
|
||
}
|
||
|
||
image::save_buffer(path, bytes, width, height, image::ExtendedColorType::Rgba8)
|
||
.map_err(|error| format!("写 PNG {} 失败:{error}", path.display()))
|
||
}
|
||
|
||
/// 一次截图的结果摘要(打日志与报告用)。
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub struct Summary {
|
||
/// 物理宽(px)。
|
||
pub width: u32,
|
||
/// 物理高(px)。
|
||
pub height: u32,
|
||
/// 窗口缩放因子(100% DPI = 1.0)。
|
||
pub scale_factor: f32,
|
||
/// 落盘路径。
|
||
pub path: PathBuf,
|
||
}
|
||
|
||
impl Summary {
|
||
/// 一行日志。
|
||
pub fn describe(&self) -> String {
|
||
format!(
|
||
"[shot] {}x{} scale={} -> {}",
|
||
self.width,
|
||
self.height,
|
||
self.scale_factor,
|
||
self.path.display()
|
||
)
|
||
}
|
||
}
|
||
|
||
mod diff;
|
||
|
||
pub use diff::{compare, diff_files, diff_files_with_scan, shift_scan, Diff, Image};
|
||
|
||
/// 主题名 → 归档文件名片段(`visual-acceptance-protocol.md` §7 的命名)。
|
||
pub fn flavor_slug(ui: &theme::Ui) -> &'static str {
|
||
match ui.flavor() {
|
||
unlucky_design::color::SchemeFlavor::Light => "light",
|
||
unlucky_design::color::SchemeFlavor::Dark => "dark",
|
||
}
|
||
}
|