fix(design): M61 r1 收口 —— 官方 emphasized/弹簧/高程 + 冻结测试
reviewer r1 判 p2-2items(fix-then-merge),本轮按条目收口: P2-1 冻结测试补全(变异实测证明原来可静默漂移): - tests/type_scale_pins.rs:baseline 15 档 (size,line_height,weight,tracking,family) 与 Expressive 强调 15 档逐值对拍,期望值独立于实现重写 - tests/token_pins.rs:49 条 tone(light+dark 全表)、7 条 easing 曲线参数、 16 档时长、9 组弹簧、28 个 IconKind 下标、形状阶梯 - 自证:TitleMedium 16→17 与 EmphasizedDecelerate 换曲线两次变异均 FAILED P2-2 emphasized 口径事实错误(用户点名): - 取回 A 级官方值(androidx TypeScaleTokens v0_103 的 *Emphasized* 四元组): label-*/title-medium/title-small 强调是 700(此前给 600)、body-large 0.5→0.15、 title-medium 0.2→0.15、body-medium 0.2→0.25、display-large -0.2→0 - TypeRole::emphasized() 改为官方表(typography/emphasized.rs); 原「只加一档字重」规则改名 emphasized_simplified() 并标注非官方 - 顺带按官方订正 baseline tracking(display-large -0.2、body-medium 0.2、 title-medium 0.2)与 TitleLarge 的 Brand 族归属 - crate 文档新增「baseline MD3 vs M3 Expressive 差异清单」+ 未支持能力 gap 弹簧改为两套 token 结构(用户追加令): - motion::spring(scheme × category × speed) 二维表 12 格: standard spatial ζ=0.9 (k 1400/700/300)、expressive spatial ζ=0.6~0.8 (k 800/380/200)、 effects 两套同值 ζ=1.0 (k 3800/1600/800),均取 A 级 MotionTokens 原值 - Spring 改存阻尼比(与 Compose 令牌同口径),9 个具名常量 中文字重覆盖(用户追加令): - FontFace 带 WeightCoverage(可变区间/静态表),Platform::resolve_weight 给 Native/Nearest/Synthetic 三档结论;雅黑无 500、苹方无 700 两处 gap 显式登记 component_map 高程按官方回调(P2 并入 finding): - FilledCard L2→L0、OutlinedCard L1→L0、Menu L4→L2、Dialog L5→L3(Menu 圆角同时 按 MenuTokens 改 4dp);新增官方值 pin 测试 + R4「全屏都在飘」守门断言 P3 四条: - 同族容器撞色:彩度按共享基准等比压缩(Palettes.reference_chroma), primary/secondary-container 不再同值,且强调色覆盖时表面逐像素不变 - femtovg 引用改函数名(行号已漂);删掉 contrast_aa 的死代码后门 - 骨架数值跟 M63:nav 56→44、titlebar 40→48,control 40/touch 48 换算依据入文档 - 图标折算口径订正为「居中描边,视觉外扩 <1px」
This commit is contained in:
1 parent
65ca0ed29a
commit
83e19e58f0
20 files changed
+1367
-234
No files matched your search
@@ -1,23 +1,31 @@
|
||||
//! M3 type scale(15 档)+ 三平台字体族回退栈。
|
||||
//! M3 type scale(15 档,baseline + Expressive 强调两套)+ 三平台字体族回退栈。
|
||||
//!
|
||||
//! **数值来源 A 级**:`androidx.compose.material3.tokens.TypeScaleTokens`(v0_103)。
|
||||
//! baseline 四元组取 `<Role>{Size,LineHeight,Tracking,Weight}`,Expressive 强调档取
|
||||
//! `<Role>Emphasized{...}`(见 [`emphasized`] 模块)。注意 Compose 这一代 token 的 tracking 与
|
||||
//! m3.material.io 网页表格有 0.05px 级别的差异(display-large −0.2 vs −0.25、body-medium 0.2 vs
|
||||
//! 0.25、title-medium 0.2 vs 0.15),本项目跟 **Compose(实际渲染值)**,差异登记在
|
||||
//! crate 文档「baseline vs Expressive 差异清单」里。
|
||||
//!
|
||||
//! **只写字体族名,不内嵌任何字体文件**(Segoe UI / SF Pro / Roboto 都受各自许可约束,
|
||||
//! 内嵌再分发是违规的;引用系统已安装的族名是合规且体积为零的做法——这是 D14 与 D28 的共同要求)。
|
||||
//! 中文由系统字体匹配:栈里显式列出 CJK 族名,纯英文环境不会去加载任何中文字体(D28)。
|
||||
|
||||
mod emphasized;
|
||||
mod fonts;
|
||||
|
||||
pub use fonts::{FontFamily, Platform};
|
||||
pub use fonts::{FontChoice, FontFace, FontFamily, Platform, WeightCoverage, WeightMatch};
|
||||
|
||||
/// 字重(M3 只用这四档;可变字体按连续值渲染时取 [`Weight::value`])。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Weight {
|
||||
/// 400:正文。
|
||||
Regular,
|
||||
/// 500:label / title-medium。
|
||||
/// 500:label / title-medium / Expressive 强调的正文与展示类。
|
||||
Medium,
|
||||
/// 600:强调标题(本项目的「区块标题」档)。
|
||||
/// 600:本项目的「区块标题」档(介于 Medium 与 Bold 之间)。
|
||||
SemiBold,
|
||||
/// 700:页面标题 / 空态主文案。
|
||||
/// 700:页面标题 / 空态主文案 / Expressive 强调的 label 与 title-small-medium。
|
||||
Bold,
|
||||
}
|
||||
|
||||
@@ -32,7 +40,7 @@ impl Weight {
|
||||
}
|
||||
}
|
||||
|
||||
/// 再重一档(Expressive 的 emphasized 用得上,最重封顶 Bold)。
|
||||
/// 再重一档(最重封顶 Bold)。
|
||||
pub const fn heavier(self) -> Self {
|
||||
match self {
|
||||
Weight::Regular => Weight::Medium,
|
||||
@@ -69,10 +77,15 @@ impl TypeStyle {
|
||||
}
|
||||
}
|
||||
|
||||
/// 该样式的字体族回退栈。
|
||||
pub fn font_stack(self, platform: Platform) -> &'static [&'static str] {
|
||||
/// 该样式的字体族回退栈(栈项带字重覆盖,见 [`FontFace`])。
|
||||
pub fn font_stack(self, platform: Platform) -> &'static [FontFace] {
|
||||
platform.font_stack(self.family)
|
||||
}
|
||||
|
||||
/// 该样式在这台平台上「字重最终落到哪个族」——中文缺档时看它判断是否走降级。
|
||||
pub fn resolve(self, platform: Platform) -> FontChoice {
|
||||
platform.resolve_weight(self.family, self.weight)
|
||||
}
|
||||
}
|
||||
|
||||
/// M3 type scale 的 15 个角色。
|
||||
@@ -151,12 +164,10 @@ impl TypeRole {
|
||||
}
|
||||
}
|
||||
|
||||
/// M3 基线样式(字号 / 行高 / 字重 / 字距,官方 type scale 值)。
|
||||
/// **baseline MD3** 样式(官方 type scale 值:字号 / 行高 / 字重 / 字距 + 字体族分类)。
|
||||
pub const fn style(self) -> TypeStyle {
|
||||
match self {
|
||||
TypeRole::DisplayLarge => {
|
||||
style(57.0, 64.0, Weight::Regular, -0.25, FontFamily::Display)
|
||||
}
|
||||
TypeRole::DisplayLarge => style(57.0, 64.0, Weight::Regular, -0.2, FontFamily::Display),
|
||||
TypeRole::DisplayMedium => style(45.0, 52.0, Weight::Regular, 0.0, FontFamily::Display),
|
||||
TypeRole::DisplaySmall => style(36.0, 44.0, Weight::Regular, 0.0, FontFamily::Display),
|
||||
TypeRole::HeadlineLarge => style(32.0, 40.0, Weight::Regular, 0.0, FontFamily::Display),
|
||||
@@ -164,11 +175,12 @@ impl TypeRole {
|
||||
style(28.0, 36.0, Weight::Regular, 0.0, FontFamily::Display)
|
||||
}
|
||||
TypeRole::HeadlineSmall => style(24.0, 32.0, Weight::Regular, 0.0, FontFamily::Display),
|
||||
TypeRole::TitleLarge => style(22.0, 28.0, Weight::Regular, 0.0, FontFamily::Text),
|
||||
TypeRole::TitleMedium => style(16.0, 24.0, Weight::Medium, 0.15, FontFamily::Text),
|
||||
// 官方 TitleLarge 用 Brand 族(与 Display 同栈),其余 title 用 Plain(Text)。
|
||||
TypeRole::TitleLarge => style(22.0, 28.0, Weight::Regular, 0.0, FontFamily::Display),
|
||||
TypeRole::TitleMedium => style(16.0, 24.0, Weight::Medium, 0.2, FontFamily::Text),
|
||||
TypeRole::TitleSmall => style(14.0, 20.0, Weight::Medium, 0.1, FontFamily::Text),
|
||||
TypeRole::BodyLarge => style(16.0, 24.0, Weight::Regular, 0.5, FontFamily::Text),
|
||||
TypeRole::BodyMedium => style(14.0, 20.0, Weight::Regular, 0.25, FontFamily::Text),
|
||||
TypeRole::BodyMedium => style(14.0, 20.0, Weight::Regular, 0.2, FontFamily::Text),
|
||||
TypeRole::BodySmall => style(12.0, 16.0, Weight::Regular, 0.4, FontFamily::Text),
|
||||
TypeRole::LabelLarge => style(14.0, 20.0, Weight::Medium, 0.1, FontFamily::Text),
|
||||
TypeRole::LabelMedium => style(12.0, 16.0, Weight::Medium, 0.5, FontFamily::Text),
|
||||
@@ -176,12 +188,20 @@ impl TypeRole {
|
||||
}
|
||||
}
|
||||
|
||||
/// Expressive 强调档:**字重升一级,字号/行高/字距不变**。
|
||||
/// **M3 Expressive** 官方强调档(15/15 全有值,A 级来源)。
|
||||
///
|
||||
/// 口径声明:M3 Expressive 确实有 emphasized 排版档,但官方没公开与这 15 档一一对应的数值;
|
||||
/// 这里定的是**我们自己的规则**(加粗一档),目的是「同一 role 的强调态只改一个字重」,
|
||||
/// 保证页面不会因为强调而改变布局高度。要更强的层级请换更大 role,不要继续加字重。
|
||||
/// 规则(官方):字号与行高不变;字重升到 500(展示/正文类)或 700(label 与小标题);
|
||||
/// 字距按官方表微调(display-large −0.2→0、body-large 0.5→0.15、title-medium 0.2→0.15、
|
||||
/// body-medium 0.2→0.25)。这是**官方值,不是我们的近似**。
|
||||
pub const fn emphasized(self) -> TypeStyle {
|
||||
emphasized::style_of(self)
|
||||
}
|
||||
|
||||
/// **简化强调规则(非官方)**:只把字重升一级,字号/行高/字距全不动。
|
||||
///
|
||||
/// 用途:需要在「同一 role 内加强语气但不想引入任何字距变化」的场合(例如同屏多行标题
|
||||
/// 需要严格对齐基线网格时)。**优先用 [`TypeRole::emphasized`]**——那才是 Expressive 的定义。
|
||||
pub const fn emphasized_simplified(self) -> TypeStyle {
|
||||
let base = self.style();
|
||||
TypeStyle {
|
||||
weight: base.weight.heavier(),
|
||||
@@ -286,16 +306,34 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emphasized_only_bumps_weight() {
|
||||
fn simplified_emphasis_only_bumps_weight() {
|
||||
for role in TypeRole::ALL {
|
||||
let base = role.style();
|
||||
let emph = role.emphasized();
|
||||
let emph = role.emphasized_simplified();
|
||||
assert_eq!(base.size_px, emph.size_px);
|
||||
assert_eq!(base.line_height_px, emph.line_height_px);
|
||||
assert_eq!(base.letter_spacing_px, emph.letter_spacing_px);
|
||||
assert!(emph.weight.value() > base.weight.value() || base.weight == Weight::Bold);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn official_emphasis_is_at_least_as_heavy_as_baseline() {
|
||||
for role in TypeRole::ALL {
|
||||
assert!(
|
||||
role.emphasized().weight.value() >= role.style().weight.value(),
|
||||
"{} 的官方强调档不该比 baseline 更轻",
|
||||
role.name()
|
||||
);
|
||||
assert_ne!(
|
||||
role.emphasized(),
|
||||
role.style(),
|
||||
"{} 强调档与 baseline 相同",
|
||||
role.name()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scaling_is_proportional_and_bounded() {
|
||||
let base = TypeRole::BodyMedium.style();
|
||||
|
||||
Reference in new issue
Block a user