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
@@ -53,15 +53,17 @@ use unlucky_design::shape::ShapeScale;
|
||||
use crate::icons;
|
||||
use crate::theme::Ui;
|
||||
|
||||
use super::interaction::Interaction;
|
||||
use super::interaction::{Base, Interaction};
|
||||
use super::layout;
|
||||
|
||||
mod assemble;
|
||||
mod fab;
|
||||
mod geometry;
|
||||
mod icon_variant;
|
||||
|
||||
pub use fab::{fab, Fab};
|
||||
pub use geometry::{Geometry, IconGeometry};
|
||||
pub use icon_variant::IconVariant;
|
||||
|
||||
use assemble::{assemble, style_closure, StyleSpec};
|
||||
|
||||
@@ -163,67 +165,6 @@ impl Variant {
|
||||
}
|
||||
}
|
||||
|
||||
/// IconButton 的**四式**(规格卡 `ICON BUTTONS / FILLED - TONAL - OUTLINED - PLAIN`)。
|
||||
///
|
||||
/// 规格卡画了四颗:实心 / 色调 / 描边 / 纯图标。12b §2.5 只有前三式(Standard / Filled / Tonal),
|
||||
/// M229 按规格卡把 Standard 拆成 **Plain**(纯图标)与 **Outlined**(1px 描边圆圈),
|
||||
/// `Standard` 这个名字保留为 `Plain` 的别名语义,避免既有调用点全改。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum IconVariant {
|
||||
/// 纯图标(规格卡 `PLAIN`):容器透明、图标 `OnSurfaceVariant`(各态图标色不变,12b 2.4)。
|
||||
Plain,
|
||||
/// 实心:`Primary` + `OnPrimary`。
|
||||
Filled,
|
||||
/// Tonal:`SecondaryContainer` + `OnSecondaryContainer`。
|
||||
Tonal,
|
||||
/// 描边:透明容器 + 1px `Outline`(规格卡第三颗,M229 新增)。
|
||||
Outlined,
|
||||
}
|
||||
|
||||
impl IconVariant {
|
||||
/// 旧名字的别名:`Standard` = [`IconVariant::Plain`](既有调用点不必改语义)。
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const Standard: IconVariant = IconVariant::Plain;
|
||||
|
||||
/// 四式全序(gallery 顺序 = 规格卡从左到右)。
|
||||
pub const ALL: [IconVariant; 4] = [
|
||||
IconVariant::Filled,
|
||||
IconVariant::Tonal,
|
||||
IconVariant::Outlined,
|
||||
IconVariant::Plain,
|
||||
];
|
||||
|
||||
/// 稳定名字。
|
||||
pub const fn name(self) -> &'static str {
|
||||
match self {
|
||||
IconVariant::Plain => "plain",
|
||||
IconVariant::Filled => "filled",
|
||||
IconVariant::Tonal => "tonal",
|
||||
IconVariant::Outlined => "outlined",
|
||||
}
|
||||
}
|
||||
|
||||
/// **禁用态图标色**:四式都是 `OnSurface` @0.38 —— A1
|
||||
/// (`StandardIconButtonTokens.DisabledColor` / `FilledIconButtonTokens.DisabledColor`)。
|
||||
pub(crate) const fn disabled_content(self) -> Role {
|
||||
Role::OnSurface
|
||||
}
|
||||
|
||||
/// 色彩角色三元组(同 [`Variant::roles`] 的口径)。
|
||||
pub(crate) const fn roles(self) -> (Option<Role>, Role, Option<Role>) {
|
||||
match self {
|
||||
IconVariant::Plain => (None, Role::OnSurfaceVariant, None),
|
||||
IconVariant::Filled => (Some(Role::Primary), Role::OnPrimary, None),
|
||||
IconVariant::Tonal => (
|
||||
Some(Role::SecondaryContainer),
|
||||
Role::OnSecondaryContainer,
|
||||
None,
|
||||
),
|
||||
IconVariant::Outlined => (None, Role::OnSurfaceVariant, Some(Role::Outline)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 一个按钮的装配描述(五式共用;**不持有任何状态所有权**)。
|
||||
///
|
||||
/// `on_press: Option<M>` 就是 M3 的 enabled/disabled 开关——`None` = 禁用,
|
||||
@@ -238,6 +179,8 @@ pub struct Button<'a, M> {
|
||||
state: Option<Interaction>,
|
||||
/// 尺寸档(规格卡 `BUTTONS / XS - XL`)。产品默认 **S(40 高)**,与 M3 `control_height` 同值。
|
||||
size: ControlSize,
|
||||
/// **脚下那一层的底**(M234):禁用态的 10% 容器与 38% 内容叠在它上面。
|
||||
base: Base,
|
||||
}
|
||||
|
||||
impl<'a, M> Button<'a, M> {
|
||||
@@ -250,6 +193,7 @@ impl<'a, M> Button<'a, M> {
|
||||
trailing: None,
|
||||
state: None,
|
||||
size: ControlSize::S,
|
||||
base: Base::CANVAS,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,6 +206,7 @@ impl<'a, M> Button<'a, M> {
|
||||
trailing: None,
|
||||
state: None,
|
||||
size: ControlSize::S,
|
||||
base: Base::CANVAS,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +236,28 @@ impl<'a, M> Button<'a, M> {
|
||||
self.state = Some(state);
|
||||
self
|
||||
}
|
||||
|
||||
/// **告诉按钮它现在踩在什么底上**(M234):禁用态的 10% 容器与 38% 内容都叠在这个底上。
|
||||
///
|
||||
/// 放进卡片/面板/浮层里**必须**给——不给就按 [`Base::CANVAS`] 合成,禁用态会与容器底
|
||||
/// 脱节(M223 的「CR 1.05、形丢失」)。容器自己的 `BASE` 常量就是标准答案:
|
||||
/// 卡片 = [`super::surfaces::elevated::BASE`]、菜单项 = `super::inputs::menu::PANEL_BASE`。
|
||||
///
|
||||
/// iced 没有环境上下文(样式闭包只拿得到 `&Theme`),所以这条信息只能显式传:
|
||||
/// 这是「组件不猜宿主」的代价,也是 M229 那条「参数在、全传死值」的修法。
|
||||
pub fn base(mut self, base: impl Into<Base>) -> Self {
|
||||
self.base = base.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// 视觉态:强制的先,否则「没有消息就是禁用」。
|
||||
pub(super) fn forced(&self) -> Option<Interaction> {
|
||||
self.state.or(if self.on_press.is_none() {
|
||||
Some(Interaction::Disabled)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Filled 按钮(实心 `Primary`)。
|
||||
@@ -337,6 +304,7 @@ pub(crate) fn icon_style_spec(
|
||||
variant: IconVariant,
|
||||
side: f32,
|
||||
forced: Option<Interaction>,
|
||||
base: Base,
|
||||
) -> StyleSpec {
|
||||
let (fill, content, border) = variant.roles();
|
||||
StyleSpec {
|
||||
@@ -348,6 +316,7 @@ pub(crate) fn icon_style_spec(
|
||||
// M229 复审 P1-1:这里曾硬编码 `0.0`,于是 `Outlined` 与 `Plain` 逐像素相同。
|
||||
stroke: icon_stroke(variant),
|
||||
radius_side: side,
|
||||
base,
|
||||
forced,
|
||||
}
|
||||
}
|
||||
@@ -360,6 +329,8 @@ pub(crate) fn icon_style_spec(
|
||||
/// gallery 的样例因此统一用描边图标(`Download` + `ChevronRight`),并有单测钉住。
|
||||
///
|
||||
/// `on_press: Option<M>`(`None` = 禁用)与 `state` 分开给:前者是语义,后者是取证开关。
|
||||
///
|
||||
/// 这是 **画布版**(禁用底 = [`Base::CANVAS`]):放进卡片/面板里请用 [`icon_on`] 给真实父底。
|
||||
pub fn icon<'a, M: Clone + 'a>(
|
||||
ui: &'a Ui,
|
||||
variant: IconVariant,
|
||||
@@ -367,6 +338,20 @@ pub fn icon<'a, M: Clone + 'a>(
|
||||
on_press: Option<M>,
|
||||
state: Option<Interaction>,
|
||||
) -> Element<'a, M> {
|
||||
icon_on(ui, variant, kind, on_press, state, Base::CANVAS)
|
||||
}
|
||||
|
||||
/// 带**父底**的 IconButton(M234):禁用态的 10% 容器与 38% 内容叠在 `base` 上。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn icon_on<'a, M: Clone + 'a>(
|
||||
ui: &'a Ui,
|
||||
variant: IconVariant,
|
||||
kind: IconKind,
|
||||
on_press: Option<M>,
|
||||
state: Option<Interaction>,
|
||||
base: impl Into<Base>,
|
||||
) -> Element<'a, M> {
|
||||
let base = base.into();
|
||||
let geometry = IconGeometry::of(ui);
|
||||
let (fill, content, _) = variant.roles();
|
||||
let forced = state.or(if on_press.is_some() {
|
||||
@@ -380,11 +365,12 @@ pub fn icon<'a, M: Clone + 'a>(
|
||||
// 这里把盒子做成整个 40×40(而不是 24×24):`button::layout` 把内容放左上角、不居中,
|
||||
// 所以「居中」这件事必须由容器自己写死,别指望 button。
|
||||
// 内容色跟态走(禁用 = on-surface@38%),见 `assemble::content_color` 的说明
|
||||
let glyph = assemble::content_color(ui, forced, content, fill, variant.disabled_content());
|
||||
let glyph =
|
||||
assemble::content_color(ui, base, forced, content, fill, variant.disabled_content());
|
||||
let icon_box =
|
||||
layout::centered_fixed(geometry.side, geometry.side, icons::view::<M>(kind, glyph));
|
||||
|
||||
let style = style_closure(ui, icon_style_spec(variant, geometry.side, forced));
|
||||
let style = style_closure(ui, icon_style_spec(variant, geometry.side, forced, base));
|
||||
|
||||
let element = button(icon_box)
|
||||
.width(Length::Fixed(geometry.side))
|
||||
|
||||
Reference in new issue
Block a user