fix(app-iced): 复审 r1 四条 P2 收口(M234)

- P2-1 判据恒绿:`gallery/probe/bases.rs` 的「反向检查」把字段档计数(`:172` 遮蔽后的
  `hits`)拿去比按钮档 stray ⇒ 恒绿。改成 `button_hits` / `field_hits` 两个计数,各比各的
  stray,失败信息的数值与单位对齐量测对象。变异自证:卡列里留一颗忘了 `.base(..)` 的按钮
  ⇒ 该断言单独红(2137 px vs 3178 px,退出码 101)。
- P2-2 FAB 的底错了:禁用态背景仍是满档 `PrimaryContainer`(`Disabled.layer() == None`),
  38% 内容却合在画布上。改成 `Base::of(PrimaryContainer)` + `on_container = false`
  (那一层 10% 在 FAB 身上不存在),两处调用收进一个 `content_color()`;新增值判据
  `disabled_fab_ink_composites_over_its_own_container`(修前 #8CA0AA / 修后 #548FBC 量级)。
- P2-3 漏改:`selection::paint::state_layer()`(hover/focus/pressed)与 `list_item` 的
  状态层叠底仍写死 `Role::Surface`,改成跟 `base`;同类两处(`assemble::transparent_state_color`
  的 Outlined/Text hover、drawer 项药丸)一并跟上。
- P2-4 文档失真:`feedback/{action,mod}.rs` 删掉「本地 α 合成 / 规则只认两档」的旧描述
  与失效链接;`list_item` 的旧签名;三处 `probe/base.rs` → `probe/bases.rs`;
  新增 `surfaces::elevated::tests::the_card_base_constant_is_the_role_the_card_paints`
  真断言(原来只在文档里声称那条测试守 `BASE`),并把这句描述改准。
- nit:`disabledbase.rs` 字符串里塌成一行的续行空格、probe 汇总行把列左缘横坐标说成
  「像素计数」的措辞。

门禁(CARGO_TARGET_DIR=C:/Temp/ull-target/wt-234):fmt / check --all-targets /
test -p unlucky-app-iced(896 + 6 通过,0 失败)/ clippy --workspace --all-targets -D warnings /
文件 ≤400 全绿;probe 浅色与深色都过。
This commit is contained in:
AskaEth committed 2026-11-11 06:34:24 +08:00
1 parent 35dbefb08d
commit 9bb3c33b8c
17 files changed
+190 -72

No files matched your search

@@ -208,7 +208,7 @@ pub(super) fn style_closure(
.or(Some(ui.color(role))),
None => Some(ui.color(role)),
},
None => transparent_state_color(ui, state, spec.content),
None => transparent_state_color(ui, spec.base, state, spec.content),
};
(background, ui.color(spec.content))
}
@@ -246,14 +246,18 @@ pub(super) fn style_closure(
}
/// 透明容器的状态层:`Outlined` / `Text` / `StandardIcon` 没有填充,可见反馈只能来自
/// 叠在**内容角色**上的状态层——把它们当作「叠在表面色上」求值,得到不透明实色。
/// 叠在**内容角色**上的状态层——把它们当作「叠在 `base`(脚下那一层)上」求值,得到不透明实色。
///
/// 之所以单独一个函数:这是 M3 里唯一「容器不存在但状态层存在」的情形,
/// 三个变体共用一条口径,免得各自发挥。
///
/// M234 复审 P2-3:`base` 之前写死 `Role::Surface`,卡片里的 Text/Outlined 按钮抬起时
/// 状态层叠在画布色上(与禁用支的口径不一致)。
pub(super) fn transparent_state_color(
ui: &Ui,
base: Base,
state: Interaction,
content: Role,
) -> Option<iced::Color> {
interaction::overlay_color(ui, state, content, Role::Surface)
interaction::overlay_color(ui, state, content, base.role())
}
+31 -12
View File
@@ -86,6 +86,34 @@ impl<'a, M> Fab<'a, M> {
}
}
/// 禁用态的内容色:`OnPrimaryContainer` @38% 叠在 **FAB 自己的容器**(`PrimaryContainer`)上。
///
/// ## 为什么底是 FAB 自己的容器,不是画布(M234 复审 P2-2)
///
/// 五式按钮的禁用容器是「`OnSurface`@10% 叠在脚下那一层」——透明的那种甚至没有容器;所以它们的
/// 内容要叠 `0.10 容器 → 父底`。FAB 不一样:`Interaction::Disabled.layer()` 是 `None`,
/// 样式闭包走 `None => Some(ui.color(Role::PrimaryContainer))`,**禁用时背景仍是满档
/// `PrimaryContainer`**(官方 `FloatingActionButtonTokens` 也没有 DisabledContainerOpacity)。
/// 所以内容只能叠 `PrimaryContainer` 本身,`on_container = false`——那一层 10% 在 FAB 身上
/// 根本不存在,给了反而多算一层灰。
///
/// 修前传 `Base::CANVAS`:38% 的 `OnPrimaryContainer` 合在画布色上,出来是一块与 FAB 底无关的灰
/// (Silver 浅色实测是一块灰 `rgb(0.549,0.627,0.667)`),而不是「深色字压在淡蓝容器上」
/// 的应有结果(同板上正确值是 `rgb(0.329,0.561,0.737)`;数字见 artifacts/m234 的 README)。
///
/// `button()` 与样式闭包**共用这一个函数**:早先两处各写一遍,两次都传了同一个错值。
fn content_color(ui: &Ui, forced: Option<Interaction>) -> iced::Color {
match forced {
Some(Interaction::Disabled) => interaction::disabled_button_content(
ui,
Base::of(Role::PrimaryContainer),
Role::OnPrimaryContainer,
false,
),
_ => ui.color(Role::OnPrimaryContainer),
}
}
/// 画一颗 FAB。
pub fn fab<'a, M: Clone + 'a>(ui: &'a Ui, spec: Fab<'a, M>) -> Element<'a, M> {
let size = spec.size;
@@ -94,13 +122,8 @@ pub fn fab<'a, M: Clone + 'a>(ui: &'a Ui, spec: Fab<'a, M>) -> Element<'a, M> {
} else {
None
});
let ink = match forced {
// 禁用:38% 的内容色叠在容器上(与按钮族的容器档同一口径)
Some(Interaction::Disabled) => {
interaction::disabled_button_content(ui, Base::CANVAS, Role::OnPrimaryContainer, true)
}
_ => ui.color(Role::OnPrimaryContainer),
};
// 内容色(含禁用档)只走 `content_color` 一处
let ink = content_color(ui, forced);
let glyph = layout::fixed(
size.icon_px(),
@@ -159,11 +182,7 @@ pub fn fab<'a, M: Clone + 'a>(ui: &'a Ui, spec: Fab<'a, M>) -> Element<'a, M> {
.or(Some(ui.color(Role::PrimaryContainer))),
None => Some(ui.color(Role::PrimaryContainer)),
};
let text_color = if state.is_disabled() {
interaction::disabled_button_content(ui, Base::CANVAS, Role::OnPrimaryContainer, true)
} else {
ui.color(Role::OnPrimaryContainer)
};
let text_color = content_color(ui, Some(state));
button::Style {
background: background.map(Background::Color),
@@ -1,9 +1,29 @@
//! FAB 的几何断言(四档尺寸/圆角/图标/内距,全部对着规格卡原值)。
//!
//! 抓得住:档位与规格卡错位(例如把 Default 写成 48)、圆角被写成半高、扩展态内距悄悄改成 M3 的 16。
//! 抓不住:配色与状态层(那走 `interaction` 的既有测试)、以及图上好不好看(D30)。
//! 抓不住:图上好不好看(D30),以及 hover/pressed 的状态层(那走 `interaction` 的既有测试)。
//!
//! 配色只钉**一条**(禁用内容色,M234 复审 P2-2):FAB 没有禁用容器档,所以 38% 必须合在
//! 自己的 `PrimaryContainer` 上,而不是画布色。其余配色仍由 `interaction` 的测试覆盖。
use unlucky_design::color::{Role, SchemeFlavor};
use unlucky_design::inkcat::size::FabSize;
use unlucky_design::typography::Platform;
use super::super::super::interaction::Interaction;
use crate::theme::{to_iced, Families, Ui};
fn ui(flavor: SchemeFlavor) -> Ui {
Ui::with_platform(
flavor,
Platform::Windows,
Families {
text: "Segoe UI",
display: "Segoe UI",
mono: "Consolas",
},
)
}
#[test]
fn the_four_states_are_the_spec_sizes() {
@@ -52,3 +72,44 @@ fn the_extended_pad_end_is_a_named_constant() {
"左右对称:规格卡只量到起始 28,右侧取同值"
);
}
/// **M234 复审 P2-2 的判据**:禁用 FAB 的内容色必须合在**自己的容器**上。
///
/// 走的是真实现(`fab::content_color`,`button()` 与样式闭包共用它)。抓得住:底又改回画布
/// (那就是修前那块与 FAB 底无关的灰)、或有人把 `on_container` 打开(会多算一层
/// 10% 的 `OnSurface` 灰)。抓不住:容器本身该不该在禁用时保持满档 `PrimaryContainer`
/// (那是 M3 FAB token 的口径问题,不由本测试裁决)。
#[test]
fn disabled_fab_ink_composites_over_its_own_container() {
for flavor in [SchemeFlavor::Light, SchemeFlavor::Dark] {
let ui = ui(flavor);
let scheme = unlucky_design::color::BrandBoard::for_flavor(flavor).scheme();
// 期望值独立算:38% 的 OnPrimaryContainer 叠在满档 PrimaryContainer 上
let expected = to_iced(
scheme
.get(Role::OnPrimaryContainer)
.with_alpha(0.38)
.over(scheme.get(Role::PrimaryContainer)),
);
let actual = super::content_color(&ui, Some(Interaction::Disabled));
assert_eq!(
actual, expected,
"{flavor:?}:禁用 FAB 的内容色 = on-primary-container@38% 叠 PrimaryContainer"
);
// 反向:不许等于「叠画布」那一档(修前的错值)
let over_canvas = to_iced(
scheme
.get(Role::OnPrimaryContainer)
.with_alpha(0.38)
.over(scheme.get(Role::Surface)),
);
assert_ne!(actual, over_canvas, "{flavor:?}:又合到画布上了");
// 非禁用档不受影响
assert_eq!(
super::content_color(&ui, Some(Interaction::Enabled)),
ui.color(Role::OnPrimaryContainer)
);
}
}