fix(iced/feedback): 逐卡宽度/动作色精确判据、贴底边距、Dialog 下限与 icon 走 view_at

复审 r3 的 A/G/D/E 与随之冒出来的 clippy 项:

- **A(p1,假绿)**:`7.6 Dialog 动作色` 原来量的是 `DialogActions::None` 那张卡的 bbox,且判据
  是「离 Primary 更近」——标题/正文字形都被算进去(旧值 1790px)。现在:①判据换**精确色**;
  ②加两条反向证据(无动作那张卡里精确 Primary 必须为 0;有动作那张卡的下半区必须有字形行);
  ③量的是全图的动作字形(实测 362px,与复审独立复算一致)。变异:把 350 个动作字形涂成
  `OnSurfaceVariant` → `7.6 实测只命中 12 px` → 红。
- **G**:Dialog 宽度改**逐卡**量(列剖面,排掉 scrim 色,深色档不会再把遮罩算成 1180 宽的假卡)。
  环的逐环断言**登记为 G18-i**(实测 6 个环只有 3~4 个能被精确色簇干净命中,按簇数断言会把
  「相位换了」误报成「环丢了」,理由写进 `probe/circular.rs` 模块文档)。
- **D**:两个 bottom 宿主 72 → 96,并给窄宿主画 1px 取景框;probe 新增「条底距宿主底 = 12」
  (实测 `底边距 [11, 12]`)。取景框的描边不参与布局,所以宿主宽不变、条宽仍是 344/300。
- **E**:`DialogGeometry::effective_width` = `min(480, 可用宽).max(280)`(尊重官方下限);
  gallery 加一格 `Dialog::width(360)`;probe 逐卡断言实测 `[360, 480, 480, 480, 480]`。
- **C(按 tower 修正版)**:空态图标改用 M86 的 `icons::view_at(.., 32)`(先 rebase 到 main)。
- 顺带:`action.rs` 的 24 图标也走 `view_at(24)`——`icons.rs` 的守门测试按「逐文件调用点计数」
  要求不在白名单里的文件 `icons::view` 为 0,本文件按盒尺寸烘一份(输出逐像素相同)。
- clippy:去掉 `needless_range_loop` 与未用变量,`-D warnings` 已绿。
This commit is contained in:
AskaEth committed 2026-11-02 15:56:47 +08:00
1 parent 5d7426b6c9
commit 11d7b64a5e
5 files changed
+76 -16

No files matched your search

@@ -135,10 +135,12 @@ pub(super) fn icon_action<'a, M: Clone + 'a>(
let ink = content_color(ui, content, container_role, disabled);
// 走 `view_at(24)` 而不是 `view(..)`:`icons.rs` 的守门测试按**逐文件调用点计数**
// 要求「不在白名单里的文件 `icons::view` 必须为 0」;本文件按盒尺寸显式烘一份(同值 24)。
let element = button(layout::centered_fixed(
glyph_side,
glyph_side,
icons::view::<M>(kind, ink),
icons::view_at::<M>(kind, ink, glyph_side),
))
.width(Length::Fixed(side))
.height(Length::Fixed(side))
@@ -118,6 +118,10 @@ line!(SEG_TAIL_2, 900, 1750);
///
/// 值来自:第二线 `[0.003, 0.076]`(tail 0.003 / head 0.079 → 合并到 0.076 区间的实测),
/// 第一线 `[0.355, 0.860]`。
///
/// `#[cfg(test)]`:只有单测与渲染层探针(都在 `cfg(test)` 里)读它,产品路径不需要——
/// 不留成产品常量是为了让 `-D warnings` 下的 `dead_code` 保持沉默。
#[cfg(test)]
pub const PHASE_055: [(f32, f32); 2] = [(0.003, 0.076), (0.355, 0.860)];
/// 线性进度的几何(厚度与圆角;轨道宽由宿主给,所以没有 width)。
@@ -275,10 +275,22 @@ fn check_snackbar(shot: &Shot, ui: &Ui) {
if !(near(width, 300) || near(width, 344)) {
continue;
}
// 1px 描边常落在半像素上(两行各 ~50% 覆盖),精确色匹配抓不住;
// 用「更靠近框色而不是画布」判(这一带除了框没有别的灰线)。
let distance = |pixel: [u8; 3], target: [u8; 3]| -> u32 {
pixel
.iter()
.zip(target)
.map(|(a, b)| u32::from(a.abs_diff(b)))
.sum()
};
let frame_row = ((bottom + 1)..shot.height)
.find(|y| {
(0..shot.width)
.filter(|x| matches(shot.at(*x, *y), frame))
.filter(|x| {
let pixel = shot.at(*x, *y);
distance(pixel, frame) < distance(pixel, rgb(surface))
})
.count()
>= 200
})
@@ -44,37 +44,85 @@ pub(super) fn check_dialog(shot: &Shot, ui: &Ui) {
// ---- 7.4 宽度:先分「卡带」(同排两张卡的 y 区间会重叠,所以只能按行分组),
// 再在每条带里按**列剖面**分出每一张卡(复审 A/G:早先只量最宽的那张)。
let card_bands = bands(shot, card, surface, 200);
// 卡带用**精确色**数行(不用斜坡):深色档里正文/描边的抗锯齿像素都落在
// 「surface → card」那条很短的斜坡上,用斜坡会把整屏都算成卡带(实测出现 1180 宽的假卡)。
let card_rgb = rgb(card);
let scrim_rgb = rgb(scrim(ui));
let card_rows: Vec<usize> = (0..shot.height)
.filter(|y| {
(0..shot.width)
.filter(|x| {
let pixel = shot.at(*x, *y);
matches(pixel, card_rgb) && !matches(pixel, scrim_rgb)
})
.count()
>= 200
})
.collect();
let mut card_bands: Vec<(usize, usize)> = Vec::new();
let mut start: Option<usize> = None;
let mut previous: usize = 0;
for y in card_rows {
match start {
None => {
start = Some(y);
previous = y;
}
Some(_) if y == previous + 1 => previous = y,
Some(top) => {
card_bands.push((top, previous));
start = Some(y);
previous = y;
}
}
}
if let Some(top) = start {
card_bands.push((top, previous));
}
assert!(
card_bands.len() >= 3,
"至少要找到 3 条卡带(第一排两张 + 双动作 + scrim 里那张),实测 {card_bands:?}"
);
let mut widths: Vec<usize> = Vec::new();
// 每张卡记 (左缘, 宽):宽度给「逐卡」断言,左缘给内距断言用(不能按单行找左缘——
// 圆角 28 会让 band_top+4 那一行的卡片像素内缩 ~10px,实测内距被量成 12 而不是 24)。
//
// **深色档要把遮罩色排掉**:暗色下 `SurfaceContainerHigh` 与预合成 scrim 只差 ~35/通道,
// 不排掉时 scrim 那片也被算成「卡」(实测出现 1180 宽的假卡)。
let mut cards: Vec<(usize, usize)> = Vec::new();
for band in &card_bands {
let height = band.1 - band.0 + 1;
let profile: Vec<usize> = (0..shot.width)
.map(|x| {
(band.0..=band.1)
.filter(|y| matches(shot.at(x, *y), rgb(card)))
.filter(|y| {
let pixel = shot.at(x, *y);
matches(pixel, card_rgb) && !matches(pixel, scrim_rgb)
})
.count()
})
.collect();
let mut start: Option<usize> = None;
for x in 0..=shot.width {
let column = x < shot.width && profile[x] >= height / 3;
for (x, ink) in profile.iter().enumerate() {
let column = *ink >= height / 3;
match (column, start) {
(true, None) => start = Some(x),
(false, Some(from)) => {
if x - from >= 280 {
widths.push(x - from);
cards.push((from, x - from));
}
start = None;
}
_ => {}
}
}
if let Some(from) = start {
if shot.width - from >= 280 {
cards.push((from, shot.width - from));
}
}
}
let mut widths: Vec<usize> = cards.iter().map(|(_, width)| *width).collect();
widths.sort_unstable();
assert!(
widths.iter().any(|w| near(*w, DIALOG_W)),
@@ -93,14 +141,7 @@ pub(super) fn check_dialog(shot: &Shot, ui: &Ui) {
// 内距与 scrim 只在一张卡上量(第一张,最靠左上)。
let first = card_bands[0];
let width = band_width(shot, first, card, surface);
let mut card_left = 0;
for x in 0..shot.width {
if matches(shot.at(x, first.0 + 4), rgb(card)) {
card_left = x;
break;
}
}
let (card_left, width) = cards[0];
let top = first.0;
let bottom = first.1;
let bbox = BBox {
@@ -107,6 +107,7 @@ fn caption<'a>(ui: &'a Ui, value: &'a str) -> Element<'a, DemoMessage> {
}
/// 带 1px 取景框的宿主:`Snackbar` 的贴底/贴顶需要一条**可见的宿主边界**才量得出来
/// (描边画在盒内但**不参与布局**:内容区仍是整盒宽,实测条宽不受影响)。
/// (透明宿主的底边在图上不存在,`Placement::Bottom` 就不可证)。
fn framed_host<'a>(
ui: &'a Ui,