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:
1 parent
6ed26626db
commit
8958b7f67d
18 files changed
+398
-124
No files matched your search
@@ -20,7 +20,7 @@ use super::{Geometry, Variant};
|
||||
|
||||
/// 一个按钮样式要的全部输入(避免 `style_closure` 长出七个位置参数)。
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(super) struct StyleSpec {
|
||||
pub(crate) struct StyleSpec {
|
||||
/// 容器填充角色;`None` = 透明(Outlined / Text / StandardIcon)。
|
||||
pub fill: Option<Role>,
|
||||
/// 内容(文字 + 图标)角色。
|
||||
|
||||
@@ -38,6 +38,10 @@ use crate::theme::Ui;
|
||||
use super::super::interaction::{self, Interaction};
|
||||
use super::super::layout;
|
||||
|
||||
/// 扩展态右侧内距(px)。规格卡只量到起始 28(`FabSize::pad_start_px`),右侧没有量;
|
||||
/// 取同值 **28** 让「图标 + 标签」在 160 宽里左右对称(`[估]`,与起始同源的唯一对称解)。
|
||||
pub const EXTENDED_PAD_END: f32 = 28.0;
|
||||
|
||||
/// 一颗 FAB:图标必有,标签只有扩展态有;`on_press = None` 即禁用。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Fab<'a, M> {
|
||||
@@ -126,7 +130,7 @@ pub fn fab<'a, M: Clone + 'a>(ui: &'a Ui, spec: Fab<'a, M>) -> Element<'a, M> {
|
||||
.align_y(Alignment::Center)
|
||||
.padding(Padding {
|
||||
top: 0.0,
|
||||
right: 28.0,
|
||||
right: EXTENDED_PAD_END,
|
||||
bottom: 0.0,
|
||||
left: size.pad_start_px(),
|
||||
})
|
||||
@@ -174,12 +178,13 @@ pub fn fab<'a, M: Clone + 'a>(ui: &'a Ui, spec: Fab<'a, M>) -> Element<'a, M> {
|
||||
}
|
||||
};
|
||||
|
||||
let width = if matches!(size, FabSize::Extended) {
|
||||
// 扩展态是最小宽(160):标签更长就撑开,短了就顶到最小宽
|
||||
Length::Fixed(size.width_px())
|
||||
} else {
|
||||
Length::Fixed(size.width_px())
|
||||
};
|
||||
// 四档的宽都由令牌给(方态是边长、扩展态是**最小**宽 160)。
|
||||
//
|
||||
// ⚠️ iced 0.14 的 `Length` 只有 `Fill/FillPortion/Shrink/Fixed`,**没有 Min**(本仓 G15 / G17-d
|
||||
// 已登记同一堵墙),所以「最小宽随标签撑开」在这里退化成**固定宽**:标签比 160 内距容得下的
|
||||
// 更长时会被裁,短时也不会缩。登记为偏离(见 `FabSize::width_px` 的文档);真要 Min 语义得等
|
||||
// iced 给 `Length::Min`,或在组件里先量文本再算宽——那要引字体度量,不在本轮。
|
||||
let width = Length::Fixed(size.width_px());
|
||||
|
||||
let element = button(content)
|
||||
.width(width)
|
||||
|
||||
@@ -38,3 +38,17 @@ fn the_glyph_grows_with_the_smaller_states() {
|
||||
assert!(FabSize::Default.icon_px() < FabSize::Medium.icon_px());
|
||||
assert_eq!(FabSize::Extended.icon_px(), 24.0);
|
||||
}
|
||||
|
||||
/// 扩展态右内距是常量(不是散在 `Padding` 里的字面量 28),且与起始内距对称。
|
||||
#[test]
|
||||
fn the_extended_pad_end_is_a_named_constant() {
|
||||
use super::super::fab::EXTENDED_PAD_END;
|
||||
use unlucky_design::inkcat::size::FabSize;
|
||||
|
||||
assert_eq!(EXTENDED_PAD_END, 28.0);
|
||||
assert_eq!(
|
||||
EXTENDED_PAD_END,
|
||||
FabSize::Extended.pad_start_px(),
|
||||
"左右对称:规格卡只量到起始 28,右侧取同值"
|
||||
);
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -122,6 +122,8 @@ fn icon_button_geometry_matches_the_reference() {
|
||||
);
|
||||
}
|
||||
|
||||
mod icon;
|
||||
|
||||
/// 视觉 reviewer P2-3:同一控件里不许混用填充字形与描边字形(线宽语汇会打架)。
|
||||
///
|
||||
/// gallery 的样例图标(前置/后置)必须同为 `IconPaint::Stroke`;允许实心字形,但只能作为
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//! 图标按钮的单测(M229 复审 P1-1 从 `buttons/tests.rs` 拆出,守 400 行硬线)。
|
||||
//!
|
||||
//! 拆出来的只有「四式图标按钮的描边」这一组:`icon()` 曾经把 `StyleSpec.stroke` 写死 `0.0`,
|
||||
//! 于是 `Outlined` 与 `Plain` 逐像素相同——判据在这里,渲染级的那一半在 `gallery/probe/rows.rs`。
|
||||
|
||||
use super::*;
|
||||
|
||||
/// **M229 复审 P1-1 的回归门**:四种图标按钮里**只有 `Outlined` 有描边**。
|
||||
///
|
||||
/// 抓得住:`icon()` 里把 `stroke` 写死成 0(那样 Outlined 与 Plain 逐像素相同——正是复审量到的现场)。
|
||||
/// 抓不住:描边颜色对不对(那是 `IconVariant::roles` 的事,上面已有变体表测试)。
|
||||
///
|
||||
/// 渲染级的第二道在 `components/gallery/probe.rs`(`buttons` 段的 sheet 上数 Outlined 行的环像素、
|
||||
/// 并断言 Plain 行没有环),这一条是纯函数的、不依赖出图。
|
||||
#[test]
|
||||
fn only_the_outlined_icon_variant_draws_a_ring() {
|
||||
use super::super::{icon_stroke, icon_style_spec};
|
||||
|
||||
assert_eq!(
|
||||
icon_stroke(IconVariant::Outlined),
|
||||
crate::theme::STROKE_PX,
|
||||
"Outlined 必须画 1px 环(否则与 Plain 逐像素相同)"
|
||||
);
|
||||
assert_eq!(
|
||||
icon_stroke(IconVariant::Outlined),
|
||||
1.0,
|
||||
"全站描边 1px(rubric G7)"
|
||||
);
|
||||
for plain in [IconVariant::Plain, IconVariant::Filled, IconVariant::Tonal] {
|
||||
assert_eq!(icon_stroke(plain), 0.0, "{plain:?} 不该有描边");
|
||||
}
|
||||
// 与角色表同源:有描边角色 ⇔ 有环。
|
||||
for variant in IconVariant::ALL {
|
||||
assert_eq!(
|
||||
icon_stroke(variant) > 0.0,
|
||||
variant.roles().2.is_some(),
|
||||
"{variant:?} 的描边与角色表不一致"
|
||||
);
|
||||
// **接线断言**:`icon()` 真正塞进 `style_closure` 的那个结构体(`icon_style_spec`)。
|
||||
// 只测 `icon_stroke()` 抓不住「调用点写死 0」——第一版就是这样漏的(变异测试证明过)。
|
||||
let spec = icon_style_spec(variant, 40.0, None);
|
||||
assert_eq!(
|
||||
spec.stroke,
|
||||
icon_stroke(variant),
|
||||
"{variant:?}:icon() 用的描边宽度与角色表不一致(调用点被写死了?)"
|
||||
);
|
||||
assert_eq!(
|
||||
spec.border.is_some(),
|
||||
icon_stroke(variant) > 0.0,
|
||||
"{variant:?}:描边角色与线宽必须同生同灭"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user