Files
Unlucky-Launcher/crates/app-iced/src/components/gallery/probe/rows.rs
T
AskaEth 35dbefb08d feat(app-iced): 禁用态合成底改成容器给的 Base(M234)
`interaction::disabled` 的四个入口一直收一个「底」,但 8 个调用点全传死
`Role::Surface`:卡片(SurfaceContainer)/ 面板(SurfaceContainerLow)里的禁用控件
把 10%(按钮族)/ 12%(面状控件族)叠在画布色上 —— M223「CR 1.05、形丢失」的根因。

- 新增 `interaction::Base`(画布 / 容器底的一等参数,`From<Role>` 兼容历史调用点);
  四个入口改成 `(ui, base, …)`,内部不再出现任何写死的 `Role::Surface`。
- 容器导出自己的 `BASE`:`surfaces::elevated::BASE`(卡)、`inputs::menu::PANEL_BASE`
  (菜单面板,项的 38% 与状态层都改叠它)、`navigation::drawer::BASE`。
- 控件收 `.base(..)`:Button / icon_on / Field / SearchBar / Slider / Checkbox / Radio /
  Switch / Chip / ListItem / Tabs;Fab 与 icon() 是画布默认值。
- `feedback/action.rs` 的本地 α 合成删掉,改走 `disabled_content_over_role` 并给真实容器底
  (Snackbar `InverseSurface` / Dialog `SurfaceContainerHigh`)。
- 判据:`interaction::disabled::tests`(卡片/面板/画布三档逐值)、
  `buttons::tests::base`(Button::base → StyleSpec → style_closure 接线)、
  `gallery` 新增 `disabledbase` 段 + `gallery::probe::bases` 精确像素断言
  (±10 容差区分不了 1/255 的差,所以自己数逐位相等的像素)。
- 变异自证:assemble 的调用点改回画布底后,上面两条都红(见 artifacts/m234/README.md)。

已知未尽:`crates/app-iced/src/pages/**` 不在 scope 内,页面侧「把控件放进卡片/面板」的
调用点尚未接 `.base(..)`(行为与本 mission 之前一致);`pages/…/java/tests/marker.rs`
有一行被迫更新(入口多了一个实参,语义不变 = 画布底)。
2026-11-11 06:34:23 +08:00

93 lines
5.1 KiB
Rust
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.
//! 矩阵**逐行**判据:禁用态内容色(12b 1.8)× 9 行 + 图标按钮第四式(Outlined)的 1px 环。
//!
//! 从 `probe.rs` 纯搬移(M229 复审 P2:加第 9 行与环断言后 `probe.rs` 409 行,超 400 硬线)。
//! 判据一行未改,只有入口从「行内代码」变成 [`assert_matrix_rows`]。
use unlucky_design::color::Role;
use super::super::scan::{rgb, Shot};
use crate::app::components::interaction;
use crate::app::components::interaction::Base;
use crate::theme::Ui;
/// 矩阵行距(40 高按钮 + 8 间距)。
pub(super) const ROW_STEP: usize = 48;
/// 行内扫描高度(可见 40 + 上下各 2 的余量)。
const ROW_HEIGHT: usize = 44;
/// 矩阵行数(5 个文字变体 + 4 个图标变体):probe 的汇总日志用它。
pub(super) const ROW_COUNT: usize = 9;
/// 逐行断言:①每一行的禁用内容色(A1 逐变体不同)②Outlined 图标行有环、Plain 行没有。
pub(super) fn assert_matrix_rows(shot: &Shot, ui: &Ui, label_top: usize, label_left: usize) {
// 行序 = `gallery/buttons.rs` 先 `Variant::ALL`(5 行)再 `IconVariant::ALL`(**4 行**,M229 起
// outlined 是第四式):共 **9 行**。`on_container` 必须与 `assemble::content_color` 一致
// ——图标按钮的 filled/tonal 有容器(`fill.is_some()`),outlined/plain 没有。
let rows: [(usize, Role, bool, &str); 9] = [
(0, Role::OnSurfaceVariant, true, "filled"),
(1, Role::OnSurface, true, "tonal"), // A1:FilledTonal 是例外
(2, Role::OnSurfaceVariant, true, "elevated"),
(3, Role::OnSurfaceVariant, false, "outlined"),
(4, Role::OnSurfaceVariant, false, "text"),
(5, Role::OnSurface, true, "icon·filled"), // A1:IconButton 全是 OnSurface
(6, Role::OnSurface, true, "icon·tonal"),
(7, Role::OnSurface, false, "icon·outlined"),
(8, Role::OnSurface, false, "icon·plain"),
];
let column_from = shot.width * 2 / 3; // 矩阵最右列 = disabled 列
for (index, role, on_container, name) in rows {
let y0 = label_top + index * ROW_STEP;
let y1 = y0 + ROW_HEIGHT;
// **与实现同源**:按钮族的内容色一律走 `disabled_button_content`(38% 叠在 10% 底上)。
// 用面状控件族的 12% 底会让断言比实现宽一档,差的 1/255 会放跑「容器对内容错」。
let ink = interaction::disabled_button_content(ui, Base::CANVAS, role, on_container);
let count = shot.count_in(rgb(ink), column_from, shot.width, y0, y1);
// 阈值 **30**:最窄的一行是只有 4 个字符的 `text` 变体(实测 38 px 墨迹,M229 复审后
// 按钮内距 24→20 让标签左移,余量只剩 2 px)。角色接错时会掉到个位数甚至 0。
assert!(
count > 30,
"{name} 行的禁用内容({role:?}@38%)在 disabled 列里只有 {count} px —— 这一行没走 A1 的禁用角色"
);
// 反向判据(不靠阈值):**满档**内容色不许比 38% 的还多——那样说明这一行没降透明度。
let full = shot.count_in(rgb(ui.color(role)), column_from, shot.width, y0, y1);
assert!(
full < count,
"{name} 行里满档 {role:?} 有 {full} px、38% 只有 {count} px:这一行的禁用透明度没生效"
);
}
// ---- M229 复审 P1-1:Outlined 图标按钮必须有 1px 环,Plain 必须没有 ----
//
// 只有渲染层能证:`icon()` 曾把 `StyleSpec.stroke` 写死 0,Outlined 与 Plain 逐像素相同。
// 纯函数那条在 `buttons::tests::only_the_outlined_icon_variant_draws_a_ring`(证明「传了 1.0」),
// 这条证明「画出来了」。
// **只能扫「环带」,且只能按「有没有墨」判**:1px 环在软渲染里是 Outline 叠画布的抗锯齿
// 混色(实测左缘 `(110,124,110)`,而 Outline 本体是 `(123,138,123)`)——按颜色匹配会漏,
// 而且 `Outline` 与「字形压在画布上的边缘」在容差内几乎同色(扫整格会把 Plain 行数成 16 px 假环)。
//
// 环带的判据是**几何 + 墨量**:40×40 圆按钮的左缘在 x = left…left+4(半径 20 的弧垂直中线处
// 贴左),而 24×24 字形从 left+8 才开始 —— Plain 行这一段实测是纯画布色 0 px。
let surface = rgb(ui.color(Role::Surface));
let is_ink = |pixel: [u8; 3]| pixel.iter().zip(surface).any(|(a, b)| a.abs_diff(b) > 8);
let band_ink = |y0: usize| {
let mut n = 0;
for y in (y0 + 14)..(y0 + 26) {
for x in label_left..(label_left + 4) {
if is_ink(shot.at(x, y)) {
n += 1;
}
}
}
n
};
let outlined = band_ink(label_top + 7 * ROW_STEP);
let plain = band_ink(label_top + 8 * ROW_STEP);
assert!(
outlined >= 4,
"Outlined 图标按钮这一行左缘没有环墨({outlined} px):`icon()` 的 stroke 又被写死了?"
);
assert_eq!(
plain, 0,
"Plain 图标按钮这一行左缘出现了 {plain} px 环墨(Plain 不该有描边)"
);
}