fix(app-iced): M75 复审 P1-A/P1-B/P2 —— 内容色跟态走 + 断言收紧 + 矩阵排版
P1-A 禁用内容色没下发(视觉 reviewer:text/standard 的 enabled 与 disabled 逐像素相同): assemble 里内容色恒取 ui.color(content) 再被 text().color() 钉死,Style.text_color 的 38% 永远下发不出去。新增 assemble::content_color(ui, forced, content),文字按钮与 IconButton 共用;probe 补两条像素断言(禁用 38% 墨迹必须存在、禁用容器内不得有满档 OnPrimary)。 P1-B 渲染层断言抓不住 2px 级回归(reviewer 变异验证:高度 42 仍绿): 高度改成精确 == 40;补「带图标左内边距 16(墨迹 ≈21)」断言;分隔线从「行数」改成 「墨量覆盖率 ≈1.0」(1.5dp 会到 ≈1.5)。 P2-1 probe 给了图却读不到时硬失败(只有完全没给 ULL_GALLERY_SHOT 才跳过)。 P2-2 Geometry::stroke 真正接到 Border.width;IconGeometry::icon 改由 side−2×padding 推导。 P2-3 矩阵 cell 宽 192→160(总宽 1040<1180,disabled 列不再被裁);禁用格保留前后图标。 P2-4 补注:snap 仅 wgpu 实现,CPU 验收图不受它影响。
This commit is contained in:
1 parent
8072f1cc7e
commit
e905fa3922
8 files changed
+195
-68
No files matched your search
@@ -35,6 +35,50 @@ fn ui(flavor: SchemeFlavor) -> Ui {
|
||||
}
|
||||
|
||||
/// `ULL_GALLERY_SHOT` → 截图路径(相对路径按**工作区根**解释:`cargo test` 的 cwd 是 crate 目录)。
|
||||
/// 一条 1dp 细线在竖直方向的「墨量覆盖率」:各行相对底色的不透明度之和(1dp ⇒ ≈1.0)。
|
||||
fn line_coverage(shot: &Shot, surface: [u8; 3], line: [u8; 3]) -> f32 {
|
||||
let mut total = 0.0;
|
||||
for y in 0..shot.height {
|
||||
let mut sum = 0.0f32;
|
||||
let mut count = 0usize;
|
||||
for x in 0..shot.width {
|
||||
let pixel = shot.at(x, y);
|
||||
if !matches(pixel, surface) {
|
||||
count += 1;
|
||||
sum += delta_ratio(pixel, surface, line);
|
||||
}
|
||||
}
|
||||
if count > 300 {
|
||||
total += sum / count as f32;
|
||||
}
|
||||
}
|
||||
total
|
||||
}
|
||||
|
||||
/// `pixel` 相对 `surface` 的偏移占「满档 line 色偏移」的比例(0..=1)。
|
||||
fn delta_ratio(pixel: [u8; 3], surface: [u8; 3], line: [u8; 3]) -> f32 {
|
||||
let channel = |i: usize| {
|
||||
let full = f32::from(line[i]) - f32::from(surface[i]);
|
||||
if full.abs() < 1.0 {
|
||||
return 0.0;
|
||||
}
|
||||
((f32::from(pixel[i]) - f32::from(surface[i])) / full).clamp(0.0, 1.0)
|
||||
};
|
||||
let (dr, dg, db) = (channel(0), channel(1), channel(2));
|
||||
(dr + dg + db) / 3.0
|
||||
}
|
||||
|
||||
/// 图上某个精确色的像素数(带容差)。
|
||||
fn count_pixels(shot: &Shot, color: [u8; 3]) -> usize {
|
||||
(0..shot.height)
|
||||
.map(|y| {
|
||||
(0..shot.width)
|
||||
.filter(|x| matches(shot.at(*x, y), color))
|
||||
.count()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn shot_path() -> Option<std::path::PathBuf> {
|
||||
let raw = std::env::var_os("ULL_GALLERY_SHOT")?;
|
||||
let path = std::path::PathBuf::from(raw);
|
||||
@@ -138,7 +182,33 @@ fn gallery_geometry_matches_the_tokens() {
|
||||
"药丸圆角应 = 20(12b 1.2):顶部行左内缩应 ≈14,实测 {top_inset}(8 圆角 ≈4,直角 0)"
|
||||
);
|
||||
|
||||
// ---- Q9:IconButton 的字形不许撑满 40 盒 --- // ---- Q9:IconButton 的字形不许撑满 40 盒 -----------------------------------
|
||||
// ---- 12b 1.4 / 1.6:内边距 16、图标↔文字 8(用带前置图标的 Filled 格量)----
|
||||
let ink = shot.ink_inside(&label_button, [255, 255, 255]);
|
||||
if let Some(ink) = ink {
|
||||
let icon_pad = ink.left - label_button.left;
|
||||
// 图标盒左缘 = 16;字形在 24 viewBox 里自带 ≈5.3 的左边距 ⇒ 墨迹落在 21 附近
|
||||
assert!(
|
||||
(19..=23).contains(&icon_pad),
|
||||
"12b 1.4:带图标左内边距应 16(墨迹 ≈21),实测 {icon_pad}"
|
||||
);
|
||||
} else {
|
||||
panic!("Filled 格应带前置图标(量内边距用)");
|
||||
}
|
||||
|
||||
// ---- Q9:IconButton 的字形不许撑满 40 盒 --- // ---- 12b 1.4 / 1.6:内边距 16、图标↔文字 8(用带前置图标的 Filled 格量)----
|
||||
let ink = shot.ink_inside(&label_button, [255, 255, 255]);
|
||||
if let Some(ink) = ink {
|
||||
let icon_pad = ink.left - label_button.left;
|
||||
// 图标盒左缘 = 16;字形在 24 viewBox 里自带 ≈5.3 的左边距 ⇒ 墨迹落在 21 附近
|
||||
assert!(
|
||||
(19..=23).contains(&icon_pad),
|
||||
"12b 1.4:带图标左内边距应 16(墨迹 ≈21),实测 {icon_pad}"
|
||||
);
|
||||
} else {
|
||||
panic!("Filled 格应带前置图标(量内边距用)");
|
||||
}
|
||||
|
||||
// ---- Q9:IconButton 的字形不许撑满 40 盒 -----------------------------------
|
||||
let tonal = rgb(ui.color(Role::SecondaryContainer));
|
||||
let tonal_boxes = shot.bbox_of(tonal);
|
||||
let icon_states = tonal_boxes
|
||||
@@ -191,10 +261,18 @@ fn gallery_geometry_matches_the_tokens() {
|
||||
runs += 1;
|
||||
}
|
||||
}
|
||||
// 两条分隔线 × 各摊在 1~2 行上(抗锯齿)⇒ 2..=6 行;>6 说明线粗了或画了第三条
|
||||
// 两条线 × 各摊 1~2 行 ⇒ 2..=4 行
|
||||
assert!(
|
||||
(2..=6).contains(&runs),
|
||||
"分隔线应 2 条 × 1px(抗锯齿时各占 2 行),实测 {runs} 行"
|
||||
(2..=4).contains(&runs),
|
||||
"分隔线应 2 条(每条 1px,抗锯齿最多摊 2 行)⇒ 2..=4 行,实测 {runs} 行"
|
||||
);
|
||||
// **墨量覆盖率**:一条 1dp 的线,其各行相对底色的不透明覆盖率之和应 ≈1.0。
|
||||
// 1.5dp 的线会到 ≈1.5,2dp 会到 ≈2.0 —— 这是「容忍 1.5 倍粗」的补丁。
|
||||
let full = rgb(ui.color(Role::OutlineVariant));
|
||||
let coverage = line_coverage(&shot, surface, full);
|
||||
assert!(
|
||||
(0.8..=1.3).contains(&coverage),
|
||||
"分隔线墨量覆盖率应 ≈1.0(= 1dp),实测 {coverage:.2}(1.5dp 会到 ~1.5)"
|
||||
);
|
||||
|
||||
// ---- 12b 1.8:禁用格必须与静息格不同 ------------------------------------
|
||||
@@ -207,20 +285,47 @@ fn gallery_geometry_matches_the_tokens() {
|
||||
"没找到禁用态实心按钮(12b 1.8:容器 = on-surface@12%)"
|
||||
);
|
||||
|
||||
// ---- 12b 1.8(视觉 reviewer 的 P1-A):禁用态的内容色必须真的下发 ----
|
||||
let (disabled_container, disabled_ink) =
|
||||
crate::app::components::interaction::disabled_colors(&ui, Role::Surface);
|
||||
let ink_px = count_pixels(&shot, rgb(disabled_ink));
|
||||
assert!(
|
||||
ink_px > 200,
|
||||
"禁用内容色(on-surface@38%)在图上只出现 {ink_px} 像素: 说明 text / icon 的字形没走 38%(旧实现 enabled 与 disabled 逐像素相同)"
|
||||
);
|
||||
|
||||
// filled 禁用容器里不许出现满档 OnPrimary(旧实现是 #FFFFFF 压在 12% 容器上 = 1.23:1)
|
||||
let full_content = rgb(ui.color(Role::OnPrimary));
|
||||
for bbox in shot.bbox_of(rgb(disabled_container)) {
|
||||
if bbox.width() < 60 {
|
||||
continue;
|
||||
}
|
||||
for y in (bbox.top + 2)..=(bbox.bottom - 2) {
|
||||
for x in (bbox.left + 2)..=(bbox.right - 2) {
|
||||
assert!(
|
||||
!matches(shot.at(x, y), full_content),
|
||||
"禁用容器内出现满档 OnPrimary @({x},{y}):禁用标签没降到 38%"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"[probe] OK flavor={:?}:
|
||||
Filled 药丸 {}×{}(参考 高 40 / 宽 ≥58)
|
||||
顶行左内缩 {}(r=20 应 ≈14;8 圆角 ≈4;直角 0)
|
||||
实心块 {} 个(含 40×40 IconButton)
|
||||
分隔线长行 {} 行(每条 1px)
|
||||
禁用容器 (on-surface@12%) 命中
|
||||
禁用容器 (on-surface@12%) 命中 · 禁用内容 38% 墨迹 {} px
|
||||
分隔线墨量覆盖率 {:.2}(1dp 应 ≈1.0)
|
||||
字形墨迹 {}px(24 盒应 16~26)",
|
||||
flavor,
|
||||
label_button.width(),
|
||||
true_height,
|
||||
top_inset,
|
||||
primary.len(),
|
||||
runs,
|
||||
ink_px,
|
||||
coverage,
|
||||
glyph_width
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user