fix(app-iced): IconButton Outlined 真的画环 + ELEVATED CARD 值串单行(M229 复审 P1)

P1-1:`icon()` 把 `StyleSpec.stroke` 写死 `0.0`,`IconVariant::Outlined` 的描边角色只到
`roles()` 为止、没画出来 —— Outlined 与 Plain 逐像素相同。改成「有描边角色就是 1px」,
并把「角色 → 填充/内容/描边」抽成 `icon_style_spec()`(`icon()` 直接吃它),单测因此能断言
**接线**而不只是断言 `icon_stroke()` 这个纯函数(第一版正是这样漏掉调用点的)。

回归门两条:纯函数/接线(`buttons/tests/icon.rs`,变异回 0.0 时红)+ 渲染层
(`gallery/probe/rows.rs`:图标按钮左缘 4px 带里 Outlined 有环墨、Plain 必须 0 px;
变异后重出图再跑会红)。环在软渲染里是 Outline 叠画布的抗锯齿混色(实测 110/124/110,
本体 123/138/123),所以判据是「这一带有墨/没墨」而不是颜色匹配。

P1-2:ELEVATED CARD 内距 24 + 值 16px 让「2026-10-29 19:22 UTC」折行,同排卡底不齐;
旧测试还断言 20/14(不变量 118+2×padding ≤ 160 变成 166 > 160,假绿)。
按塔裁决「收内距/字号、不破卡片宽度契约」:`PAD` 24 → 20(= card_padding)、值字级 → BodySmall 12。
量测:时戳 14px 墨水宽 138px(160 外框内宽 120 放不下)、12px 为 118px(放得下,余 2px)。
测试改成四条绑一起(字级 12 / 内距 20 / 160−2×PAD ≥ 118 / 2 列实际宽度也 ≥ 118),
并反证「14px 确实放不下」防登记被当噪音删掉;变异回 14px 时红。

P2(顺带):
- 截图通道的主题档由 `--screenshot-theme` 钉死(此前读机器 launcher.json,设置页主题行会漂)
- `gallery::probe` 只认 gallery sheet(必须给 `ULL_GALLERY_SECTION`,否则整页图的 chip 会被当按钮量)、
  行表 8 → 9 行(四式图标按钮)、分隔线判据只在渲染了 divider 段时执行、逐行阈值 40 → 30 并加
  「满档色像素必须少于 38% 像素」的反向判据、probe 拆出 `probe/rows.rs` 守 400 行
- FAB extended 的重复分支合成一处,右内距提取 `EXTENDED_PAD_END`;iced 0.14 无 `Length::Min`,
  「最小宽随标签增长」退化为固定宽已登记
- 五处注释谎言改真(theme/tests、general/theme、elevated、launch_args、app/tests/theme)

`buttons/tests.rs` 加用例后 402 行 ⇒ 图标按钮那组拆到 `buttons/tests/icon.rs`。
This commit is contained in:
AskaEth committed 2026-11-11 01:49:15 +08:00
1 parent 6ed26626db
commit 8958b7f67d
18 files changed
+398 -124

No files matched your search

+36 -14
View File
@@ -318,6 +318,40 @@ pub fn text<'a, M: Clone + 'a>(ui: &'a Ui, button: Button<'a, M>) -> Element<'a,
assemble(ui, Variant::Text, button)
}
/// 图标按钮的描边宽度:**有描边角色就有那一圈,没有就是 0**。
pub(crate) fn icon_stroke(variant: IconVariant) -> f32 {
if variant.roles().2.is_some() {
crate::theme::STROKE_PX
} else {
0.0
}
}
/// 图标按钮的**样式描述**(从 `icon()` 抽出来,好让它能被单测直接钉住)。
///
/// 为什么抽:`Style` 闭包不可内省,`icon()` 返回 `Element` 也看不进去——复审把
/// `StyleSpec.stroke` 写死成 `0.0` 时,`icon_stroke()` 的纯函数测试照样绿(它测的是函数,
/// 不是**接线**)。现在「角色 → 填充/内容/描边」这件事在 `icon_style_spec` 里一次成型,
/// 单测断言的就是 `icon()` 真正塞进 `style_closure` 的那个结构体。
pub(crate) fn icon_style_spec(
variant: IconVariant,
side: f32,
forced: Option<Interaction>,
) -> StyleSpec {
let (fill, content, border) = variant.roles();
StyleSpec {
fill,
content,
border,
// 方形图标按钮同样是 full 圆角(40 → 20),档位取自组件表
shape: Component::IconButton.spec().shape,
// M229 复审 P1-1:这里曾硬编码 `0.0`,于是 `Outlined` 与 `Plain` 逐像素相同。
stroke: icon_stroke(variant),
radius_side: side,
forced,
}
}
/// IconButton(standard / filled / tonal):40×40、图标 24、命中区 48。
///
/// **图标语汇纪律(视觉 reviewer P2-3)**:同一个控件里**不混用**填充字形(`IconPaint::Fill`,
@@ -334,7 +368,7 @@ pub fn icon<'a, M: Clone + 'a>(
state: Option<Interaction>,
) -> Element<'a, M> {
let geometry = IconGeometry::of(ui);
let (fill, content, border) = variant.roles();
let (fill, content, _) = variant.roles();
let forced = state.or(if on_press.is_some() {
None
} else {
@@ -350,19 +384,7 @@ pub fn icon<'a, M: Clone + 'a>(
let icon_box =
layout::centered_fixed(geometry.side, geometry.side, icons::view::<M>(kind, glyph));
let style = style_closure(
ui,
StyleSpec {
fill,
content,
border,
// 方形图标按钮同样是 full 圆角(40 → 20),档位取自组件表
shape: Component::IconButton.spec().shape,
stroke: 0.0,
radius_side: geometry.side,
forced,
},
);
let style = style_closure(ui, icon_style_spec(variant, geometry.side, forced));
let element = button(icon_box)
.width(Length::Fixed(geometry.side))