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` 有一行被迫更新(入口多了一个实参,语义不变 = 画布底)。
This commit is contained in:
1 parent
a9d1cb4c44
commit
35dbefb08d
48 files changed
+1690
-496
No files matched your search
@@ -2,6 +2,9 @@
|
||||
//!
|
||||
//! 单独成文件的原因:`mod.rs` 已经是「对外契约 + 变体角色表」,把绘制细节放一起会顶到 400 行红线;
|
||||
//! 而且这条产线是**唯一**会把「令牌 → 像素」翻一次的地方,独立出来才好审。
|
||||
//!
|
||||
//! 禁用态的合成底([`Base`],M234)也从这里往下传:`Button::base(..)` → [`StyleSpec::base`] →
|
||||
//! `disabled_button_colors` / `disabled_button_content`。装配侧没有第二个猜底色的地方。
|
||||
|
||||
use iced::widget::text::LineHeight;
|
||||
use iced::widget::{button, text, Row};
|
||||
@@ -14,9 +17,9 @@ use unlucky_design::shape::ShapeScale;
|
||||
use crate::icons;
|
||||
use crate::theme::Ui;
|
||||
|
||||
use super::super::interaction::{self, Interaction};
|
||||
use super::super::interaction::{self, Base, Interaction};
|
||||
use super::super::layout;
|
||||
use super::{Geometry, Variant};
|
||||
use super::{Button, Geometry, Variant};
|
||||
|
||||
/// 一个按钮样式要的全部输入(避免 `style_closure` 长出七个位置参数)。
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -33,6 +36,12 @@ pub(crate) struct StyleSpec {
|
||||
pub stroke: f32,
|
||||
/// 圆角折算用的边长(药丸 = 可见高)。
|
||||
pub radius_side: f32,
|
||||
/// **脚下那一层的底**(M234):禁用态的 10% 容器与 38% 内容都叠在它上面。
|
||||
///
|
||||
/// 由构造点给(卡片 = `surfaces::elevated::BASE`、菜单项 = `inputs::menu::PANEL_BASE`、
|
||||
/// 直接摆在画布上 = [`Base::CANVAS`])。M229 之前这里恒为 `Role::Surface`,
|
||||
/// 卡片里的禁用按钮因此把 10% 叠在画布色上(M223 的「形丢失」)。
|
||||
pub base: Base,
|
||||
/// 强制态(gallery 取证);`None` = 跟随运行期的 `button::Status`。
|
||||
///
|
||||
/// **`Some(Enabled)` 与 `None` 不是一回事**:截图取证必须能强制「静息」,否则光标恰好停在
|
||||
@@ -45,15 +54,11 @@ pub(crate) struct StyleSpec {
|
||||
pub(super) fn assemble<'a, M: Clone + 'a>(
|
||||
ui: &'a Ui,
|
||||
variant: Variant,
|
||||
spec: super::Button<'a, M>,
|
||||
spec: Button<'a, M>,
|
||||
) -> Element<'a, M> {
|
||||
let geometry = Geometry::sized(ui, variant, spec.leading.is_some(), spec.size);
|
||||
let (fill, content, border) = variant.roles();
|
||||
let forced = spec.state.or(if spec.on_press.is_none() {
|
||||
Some(Interaction::Disabled)
|
||||
} else {
|
||||
None
|
||||
});
|
||||
let (fill, content, _border) = variant.roles();
|
||||
let forced = spec.forced();
|
||||
|
||||
// 按钮家族没有浮层级高程:L1(Elevated)只用容器色调差表达(rubric §7 不画阴影)
|
||||
debug_assert!(
|
||||
@@ -67,7 +72,14 @@ pub(super) fn assemble<'a, M: Clone + 'a>(
|
||||
// 早先这里恒取 `ui.color(content)`,再被 `text(...).color(..)` 钉死,`Style.text_color`
|
||||
// 里的 38% 永远下发不出去 —— 表现为「text / standard icon 的 enabled 格与 disabled 格
|
||||
// 逐像素相同」(视觉 reviewer 实测 changed_px = 0;filled 禁用标签 1.23:1)。
|
||||
let ink = content_color(ui, forced, content, fill, variant.disabled_content());
|
||||
let ink = content_color(
|
||||
ui,
|
||||
spec.base,
|
||||
forced,
|
||||
content,
|
||||
fill,
|
||||
variant.disabled_content(),
|
||||
);
|
||||
|
||||
// 标签排版跟尺寸档走:`ControlSize::label()` 已把「字号跟字级、字重固定 500」定死
|
||||
// (XS/S = LabelLarge 14、M = BodyLarge 16、L = HeadlineSmall 24、XL = HeadlineLarge 32)。
|
||||
@@ -106,18 +118,7 @@ pub(super) fn assemble<'a, M: Clone + 'a>(
|
||||
.height(Length::Fill)
|
||||
.align_y(iced::Alignment::Center);
|
||||
|
||||
let style = style_closure(
|
||||
ui,
|
||||
StyleSpec {
|
||||
fill,
|
||||
content,
|
||||
border,
|
||||
shape: variant.shape(),
|
||||
stroke: geometry.stroke,
|
||||
radius_side: geometry.height,
|
||||
forced,
|
||||
},
|
||||
);
|
||||
let style = style_closure(ui, text_style_spec(ui, variant, &spec));
|
||||
|
||||
let element = button(content_box)
|
||||
.height(Length::Fixed(geometry.height))
|
||||
@@ -130,6 +131,26 @@ pub(super) fn assemble<'a, M: Clone + 'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// `Button` → 样式描述:**装配的唯一接线点**(`assemble` 与单测都走这里)。
|
||||
///
|
||||
/// 抽出来的理由与 [`super::icon_style_spec`] 同源:`button::Style` 是闭包、`Element` 是黑盒,
|
||||
/// 两者都不可内省,所以「禁用态的底到底传了哪一个」只能在**结构体**上断言。
|
||||
/// 写死一个 `StyleSpec` 再喂给 `style_closure` 的测试证明不了接线(M229 复审 P1-1 踩过)。
|
||||
pub(super) fn text_style_spec<M>(ui: &Ui, variant: Variant, spec: &Button<'_, M>) -> StyleSpec {
|
||||
let (fill, content, border) = variant.roles();
|
||||
let geometry = Geometry::sized(ui, variant, spec.leading.is_some(), spec.size);
|
||||
StyleSpec {
|
||||
fill,
|
||||
content,
|
||||
border,
|
||||
shape: variant.shape(),
|
||||
stroke: geometry.stroke,
|
||||
radius_side: geometry.height,
|
||||
base: spec.base,
|
||||
forced: spec.forced(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 内容色(文字与图标共用):禁用态走令牌的 38%,其余态就是角色色。
|
||||
///
|
||||
/// 单独成函数是为了让「内容色跟态走」这件事只有一处实现——`assemble`(文字按钮)与 `icon`
|
||||
@@ -139,19 +160,20 @@ pub(super) fn assemble<'a, M: Clone + 'a>(
|
||||
/// 其余是 `OnSurfaceVariant`,不能在这里统一写死。
|
||||
pub(super) fn content_color(
|
||||
ui: &Ui,
|
||||
base: Base,
|
||||
forced: Option<Interaction>,
|
||||
content: Role,
|
||||
fill: Option<Role>,
|
||||
disabled_content: Role,
|
||||
) -> iced::Color {
|
||||
match forced {
|
||||
// 有容器:38% 叠在**按钮家族 10% 容器**上(Filled / Tonal / Elevated)
|
||||
// 有容器:38% 叠在**按钮家族 10% 容器**上(Filled / Tonal / Elevated),容器再叠在 `base` 上
|
||||
Some(Interaction::Disabled) if fill.is_some() => {
|
||||
interaction::disabled_button_content(ui, disabled_content, true)
|
||||
interaction::disabled_button_content(ui, base, disabled_content, true)
|
||||
}
|
||||
// 无容器:38% 直接叠在 **surface** 上(Text / Outlined / standard IconButton)
|
||||
// 无容器:38% 直接叠在 **base** 上(Text / Outlined / standard IconButton)
|
||||
Some(Interaction::Disabled) => {
|
||||
interaction::disabled_button_content(ui, disabled_content, false)
|
||||
interaction::disabled_button_content(ui, base, disabled_content, false)
|
||||
}
|
||||
_ => ui.color(content),
|
||||
}
|
||||
@@ -168,28 +190,35 @@ pub(super) fn style_closure(
|
||||
.forced
|
||||
.unwrap_or_else(|| Interaction::from_status(status));
|
||||
|
||||
let (background, text_color) = if state.is_disabled() {
|
||||
let (container, content) = interaction::disabled_button_colors(ui, Role::Surface);
|
||||
// 透明容器在禁用态依然透明(官方只改内容透明度)
|
||||
(spec.fill.map(|_| container), content)
|
||||
} else {
|
||||
// 有填充:状态层叠在**容器角色**上;透明容器:叠在表面色上(否则 hover 看不见)
|
||||
let background = match spec.fill {
|
||||
Some(role) => match state.layer() {
|
||||
Some(_) => interaction::overlay_color(ui, state, spec.content, role)
|
||||
.or(Some(ui.color(role))),
|
||||
None => Some(ui.color(role)),
|
||||
},
|
||||
None => transparent_state_color(ui, state, spec.content),
|
||||
};
|
||||
(background, ui.color(spec.content))
|
||||
// 禁用那一对颜色**只求值一次**:背景与描边用的是同一对值(早先调了两次)
|
||||
let disabled = state
|
||||
.is_disabled()
|
||||
.then(|| interaction::disabled_button_colors(ui, spec.base));
|
||||
|
||||
let (background, text_color) = match disabled {
|
||||
Some((container, content)) => {
|
||||
// 透明容器在禁用态依然透明(官方只改内容透明度)
|
||||
(spec.fill.map(|_| container), content)
|
||||
}
|
||||
None => {
|
||||
// 有填充:状态层叠在**容器角色**上;透明容器:叠在表面色上(否则 hover 看不见)
|
||||
let background = match spec.fill {
|
||||
Some(role) => match state.layer() {
|
||||
Some(_) => interaction::overlay_color(ui, state, spec.content, role)
|
||||
.or(Some(ui.color(role))),
|
||||
None => Some(ui.color(role)),
|
||||
},
|
||||
None => transparent_state_color(ui, state, spec.content),
|
||||
};
|
||||
(background, ui.color(spec.content))
|
||||
}
|
||||
};
|
||||
|
||||
let border_color = if state.is_disabled() {
|
||||
interaction::disabled_button_colors(ui, Role::Surface).0
|
||||
} else {
|
||||
spec.border
|
||||
.map_or_else(|| ui.color(Role::OutlineVariant), |role| ui.color(role))
|
||||
let border_color = match disabled {
|
||||
Some((container, _content)) => container,
|
||||
None => spec
|
||||
.border
|
||||
.map_or_else(|| ui.color(Role::OutlineVariant), |role| ui.color(role)),
|
||||
};
|
||||
|
||||
button::Style {
|
||||
|
||||
Reference in new issue
Block a user