Files
Unlucky-Launcher/crates/design/src/component_map/table.rs
T
AskaEth 16bdad1b98 fix(design): M69 r1 —— SEARCH_BAR 内边距语义收口 + ComponentRadius 取值口上闸
P2-1(语义缝隙):`SEARCH_BAR` 的 pad_start / pad_end 原为 16,与
icon_offset = 4 按字面相加得 20,比官方图标可视边 16 多 4px(越过 rubric
±2px 容差);而被加数的 12 不在 CM 里,消费方只能自己写 12.0 —— 正是 G10
要消灭的字面量。改为 12(文本框内容内边距,Space::X3)+ 4,两侧同式:
图标可视边 = pad + icon_offset = 16。字段文档写明求和公式;测试钉死
pad + offset == 16 且 pad != 16(防有人把 16 写回 pad 再叠加)。
顺带给 SEARCH_BAR.gap 补 [估] 出处(官方无图标↔文字 token)。

P2-2(绕过路径):ComponentRadius::dp() 收成 pub(crate)。它不看尺寸、
无条件返回 2.0,公开等于把 D32 的尺寸闸交给自觉。对外唯一取值口是
radius_px(size) -> Option<f32>;ALL 只暴露变体名、不暴露数值。
tokext_pins 原用 dp(),改为紧邻的 radius_px(20.0) == Some(2.0),覆盖不丢。

顺手:修掉本轮新引入的 rustdoc warning(component_map 文档链到私有模块
metrics,改指 ComponentMetrics);Divider 行标注 motion/duration 为占位,
并在 ComponentSpec 的 text / motion 字段文档写明「无文字 / 无动效的组件
这里是占位值,消费方不得据此排版 / 排动效」。

门禁:test 165 全绿、fmt --check、clippy -D warnings、check --workspace
--all-targets、doc(warning 2 → 1,只剩既有的 typography→emphasized 一条)。
2026-11-01 17:05:19 +08:00

372 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 组件档位数据表(**下标必须等于 `Component as usize`**,由测试钉死)。
//!
//! 一行一个组件。改值就是在改全站观感——同步协作文档再改。
//!
//! **末尾 5 行(M69 G9)** 的来源是 `research/15` §18~§23(同一套 `material3-android:1.4.0` 源码):
//! Checkbox / Radio / Search / NavigationDrawer / Divider。其中 Checkbox 的 **2dp** 圆角走
//! [`ComponentRadius`](D32 第二层,不塞进 `ShapeScale`),Divider 的厚度是一像素线(G12)而不在这张表里。
//!
//! **高程/形状取值来源(A 级)**:`androidx.compose.material3.tokens` 的各组件 `<X>Tokens.kt`
//! (FilledCard/OutlinedCard/ElevatedCard/Menu/Dialog/Snackbar/AppBar/List)。已登记的两处偏离:
//! - `Tooltip`:`PlainTooltipTokens.kt` 只有 Corner 行、**没有 Elevation 行**,Table 的 Level2 是
//! B 级(沿用 M3 旧规范「浮层 3dp」的直觉),有官方值后以官方为准;
//! - `ListItem`:官方 `ListTokens` 用 `CornerNone`(直角行),本项目取 `Small(8)`——列表行在本
//! 启动器里是**卡片内的独立可点块**(不是贴边通栏行),圆角与卡片同族更协调;偏差 8dp 已登记。
use super::{Component, ComponentSpec};
use crate::elevation::Elevation;
use crate::motion::{
DurationToken, Easing, SPRING_EXPRESSIVE_SPATIAL_DEFAULT, SPRING_EXPRESSIVE_SPATIAL_FAST,
};
use crate::shape::{ComponentRadius, ShapeMorph, ShapeScale};
use crate::typography::TypeRole;
/// 一个组件的档位行(`const fn` 便捷构造,字段名在 [`ComponentSpec`] 里)。
const fn spec(
component: Component,
shape: ShapeScale,
elevation: Elevation,
text: TypeRole,
support: Option<TypeRole>,
motion: Easing,
duration: DurationToken,
morph: Option<ShapeMorph>,
) -> ComponentSpec {
ComponentSpec {
component,
shape,
micro_radius: None,
elevation,
text,
support,
motion,
duration,
morph,
}
}
/// 圆角走「微控件层」(D32)的档位行:`shape` 自动填占位 `None`,半径取白名单值。
///
/// 为什么单独一个构造器:`micro_radius` 与 `shape = None` 必须成对出现,用构造器保证配对,
/// 不让 25 行表里出现「填了微半径忘了把 shape 清掉」。
const fn spec_micro(
component: Component,
micro_radius: ComponentRadius,
elevation: Elevation,
text: TypeRole,
support: Option<TypeRole>,
motion: Easing,
duration: DurationToken,
morph: Option<ShapeMorph>,
) -> ComponentSpec {
ComponentSpec {
component,
shape: ShapeScale::None,
micro_radius: Some(micro_radius),
elevation,
text,
support,
motion,
duration,
morph,
}
}
/// 25 个组件的档位表。
pub(super) const SPECS: [ComponentSpec; 25] = [
spec(
Component::TopAppBar,
ShapeScale::None,
Elevation::Level0,
TypeRole::TitleLarge,
Some(TypeRole::BodySmall),
Easing::Standard,
DurationToken::Medium2,
None,
),
spec(
Component::NavigationItem,
ShapeScale::Full,
Elevation::Level0,
TypeRole::LabelLarge,
None,
Easing::Emphasized,
DurationToken::Medium2,
Some(ShapeMorph::new(
ShapeScale::Large,
ShapeScale::Full,
SPRING_EXPRESSIVE_SPATIAL_FAST,
)),
),
spec(
Component::PrimaryButton,
ShapeScale::Full,
Elevation::Level0,
TypeRole::LabelLarge,
None,
Easing::Emphasized,
DurationToken::Short4,
None,
),
spec(
Component::SecondaryButton,
ShapeScale::Full,
Elevation::Level0,
TypeRole::LabelLarge,
None,
Easing::Standard,
DurationToken::Short4,
None,
),
spec(
Component::TextButton,
ShapeScale::Full,
Elevation::Level0,
TypeRole::LabelLarge,
None,
Easing::Standard,
DurationToken::Short4,
None,
),
spec(
Component::IconButton,
ShapeScale::Full,
Elevation::Level0,
TypeRole::LabelMedium,
None,
Easing::Standard,
DurationToken::Short3,
Some(ShapeMorph::new(
ShapeScale::Medium,
ShapeScale::Full,
SPRING_EXPRESSIVE_SPATIAL_FAST,
)),
),
// FilledCardTokens.ContainerElevation = Level0(hover 才到 L1、dragged 到 L3)。
spec(
Component::OutlinedCard,
ShapeScale::Medium,
Elevation::Level0,
TypeRole::TitleMedium,
Some(TypeRole::BodySmall),
Easing::Standard,
DurationToken::Medium1,
None,
),
spec(
Component::FilledCard,
ShapeScale::Medium,
Elevation::Level0,
TypeRole::TitleMedium,
Some(TypeRole::BodySmall),
Easing::Standard,
DurationToken::Medium1,
Some(ShapeMorph::new(
ShapeScale::Medium,
ShapeScale::Large,
SPRING_EXPRESSIVE_SPATIAL_DEFAULT,
)),
),
spec(
Component::ListItem,
ShapeScale::Small,
Elevation::Level0,
TypeRole::BodyLarge,
Some(TypeRole::BodySmall),
Easing::Standard,
DurationToken::Short4,
None,
),
spec(
Component::TextField,
ShapeScale::ExtraSmall,
Elevation::Level0,
TypeRole::BodyLarge,
Some(TypeRole::BodySmall),
Easing::Standard,
DurationToken::Short4,
None,
),
spec(
Component::SegmentedButton,
ShapeScale::Full,
Elevation::Level0,
TypeRole::LabelLarge,
None,
Easing::Emphasized,
DurationToken::Medium2,
None,
),
spec(
Component::Chip,
ShapeScale::Small,
Elevation::Level0,
TypeRole::LabelMedium,
None,
Easing::Standard,
DurationToken::Short3,
Some(ShapeMorph::new(
ShapeScale::ExtraSmall,
ShapeScale::Small,
SPRING_EXPRESSIVE_SPATIAL_FAST,
)),
),
spec(
Component::Switch,
ShapeScale::Full,
Elevation::Level0,
TypeRole::BodyMedium,
Some(TypeRole::BodySmall),
Easing::Emphasized,
DurationToken::Medium2,
None,
),
spec(
Component::Slider,
ShapeScale::Full,
Elevation::Level0,
TypeRole::BodyMedium,
Some(TypeRole::BodySmall),
Easing::Standard,
DurationToken::Short2,
None,
),
spec(
Component::ProgressIndicator,
ShapeScale::Full,
Elevation::Level0,
TypeRole::LabelMedium,
None,
Easing::Linear,
DurationToken::Long2,
None,
),
// DialogTokens.ContainerElevation = Level3,CornerExtraLarge = 28。
spec(
Component::Dialog,
ShapeScale::ExtraLarge,
Elevation::Level3,
TypeRole::HeadlineSmall,
Some(TypeRole::BodyMedium),
Easing::EmphasizedDecelerate,
DurationToken::Long4,
Some(ShapeMorph::new(
ShapeScale::Large,
ShapeScale::ExtraLarge,
SPRING_EXPRESSIVE_SPATIAL_DEFAULT,
)),
),
// MenuTokens:CornerExtraSmall(4) + Level2。
spec(
Component::Menu,
ShapeScale::ExtraSmall,
Elevation::Level2,
TypeRole::BodyMedium,
None,
Easing::StandardDecelerate,
DurationToken::Short4,
None,
),
spec(
Component::Snackbar,
ShapeScale::ExtraSmall,
Elevation::Level3,
TypeRole::BodyMedium,
None,
Easing::EmphasizedDecelerate,
DurationToken::Medium2,
None,
),
spec(
Component::Tooltip,
ShapeScale::ExtraSmall,
Elevation::Level2,
TypeRole::BodySmall,
None,
Easing::StandardDecelerate,
DurationToken::Short2,
None,
),
spec(
Component::EmptyState,
ShapeScale::Large,
Elevation::Level0,
TypeRole::HeadlineSmall,
Some(TypeRole::BodySmall),
Easing::Standard,
DurationToken::Medium2,
None,
),
// ---- M69 G9:末尾追加的 5 个(`research/15` §18~§23;前 20 行次序不许动)----
// Checkbox:圆角 **2dp** 不在 `ShapeScale` 刻度里 → 走 `spec_micro`(D32 第二层)。
// 高程 L0:`CheckboxTokens` 没有 Elevation 行,容器是「描边/填充」两态而非浮层。
// 字级 `BodyLarge` 是**团队口径**:官方没有「框 + 标签」组合 token(§18.9),取列表行同一档。
// 动效取 M3 motion scheme 的 fast-effects(100ms standard)——[推导],非 token 原文。
spec_micro(
Component::Checkbox,
ComponentRadius::Micro,
Elevation::Level0,
TypeRole::BodyLarge,
None,
Easing::Standard,
DurationToken::Short2,
None,
),
// Radio:真圆(`RadioButtonTokens.IconSize = 20` + `CornerFull`),内点/环线宽见
// `ComponentMetrics::RADIO`。字级同 Checkbox(团队口径)。
spec(
Component::Radio,
ShapeScale::Full,
Elevation::Level0,
TypeRole::BodyLarge,
None,
Easing::Standard,
DurationToken::Short2,
None,
),
// Search bar:圆角 `CornerFull` / `SearchBarCornerRadius = 56/2 = 28`。
// **高程取 L0**——`SearchBarTokens.ContainerElevation = Level3` 与
// `SearchBarDefaults.TonalElevation/ShadowElevation = Level0` 冲突(§26.2 矛盾 ②),
// 判据按观感取「无可见阴影 + tone 分层」,这条已在 `design-令牌约定` 登记。
// 无 shape morph:bar ⇄ docked 展开时**半径不变**(28 → 28),只长面积(§20C)。
spec(
Component::Search,
ShapeScale::Full,
Elevation::Level0,
TypeRole::BodyLarge,
Some(TypeRole::BodySmall),
Easing::Emphasized,
DurationToken::Medium2,
None,
),
// Navigation drawer:官方 `ContainerShape = CornerLargeEnd`(只有靠内容那侧两角 16,
// 贴边侧直角)。`ShapeScale` 表达不了「部分角」,这里记内侧角的 `Large(16)`,
// 哪两个角由组件按侧位决定;写四角 16 = 变成浮动卡片(§21.2 判据)。
// 常驻形态 `Surface` + L0(模态才用 `SurfaceContainerLow` + scrim,本项目只在 <600 窗宽兜底)。
spec(
Component::NavigationDrawer,
ShapeScale::Large,
Elevation::Level0,
TypeRole::LabelLarge,
Some(TypeRole::TitleSmall),
Easing::Emphasized,
DurationToken::Medium2,
None,
),
// Divider:无高程、无圆角、**无文字**(`text` 是占位值,消费方不得据此排版);
// 厚度是一像素线 `LayoutMetrics::divider_thickness`,颜色 `OutlineVariant`
// (search view 头部分隔是 `Outline`,两个不同角色,§20.8)。
// **`motion` / `duration` 也是占位值**(`ComponentSpec.motion` 不是 `Option`):Divider 没有
// 官方状态过渡令牌,消费方**不得据此排动效**;下轮把该字段改成 `Option` 更彻底。
spec(
Component::Divider,
ShapeScale::None,
Elevation::Level0,
TypeRole::BodySmall,
None,
Easing::Standard,
DurationToken::Short2,
None,
),
];