feat(app-iced): Ink Cat / Silver Cat 换肤 —— 双色板默认 + 规格卡组件几何(M229)

产品出厂外观从 tone 派生的 `Scheme::baseline` 切到品牌色板:深色 = Ink(淡绿)、
浅色 = Silver(钢蓝),D38 取色链(背景图 / 壁纸)仍然优先,取色失败回落到色板。

主题三档接进设置页「通用 → 主题」:Ink / Silver / 跟随系统(落盘仍是
`appearance.theme_mode` 的 dark / light / auto,不新增配置键)。

组件几何按规格卡对齐(M228 的 `inkcat` 登记项逐条落地):
- 卡片圆角 12 → 16;实例元信息卡改用新的 ELEVATED CARD(无描边、SurfaceContainer
  底、内距 24、上标注 11/600)
- Snackbar 48/4 → 52/16(双行 72)
- Checkbox 20/rx2 → 18/rx4;Radio 20 → 18 Ø(内点仍 10)
- 按钮加 XS~XL 五档(产品仍只用 S=40),内距改走 ControlSize::pad_x
- 新增 FAB 四态(40/56/80/160×56)与图标按钮第四式 outlined
- 语言与主题两行 chips 统一成规格卡 chip(32 高 / rx8)

`theme.rs` 原本正好 400 行,先把单测纯搬移到 `theme/tests.rs` 再加色板字段;
`app/tests.rs`、`settings/general.rs` 同样按纯搬移拆分守 400 行硬线。

验证:cargo fmt --all --check、cargo check -p unlucky-app-iced --all-targets、
cargo test --workspace(除 clippy 前的一轮 flake,见报告)、cargo clippy --workspace
--all-targets -- -D warnings、全仓 400 行扫描。视觉矩阵 42 张在
DevDocs/artifacts/m229/(gitignored,不进提交)。
This commit is contained in:
AskaEth committed 2026-11-11 01:49:13 +08:00
1 parent a06b7ff4ec
commit db541c820e
44 files changed
+1439 -300

No files matched your search

+38 -9
View File
@@ -47,6 +47,7 @@ use unlucky_design::color::Role;
use unlucky_design::component_map::Component;
use unlucky_design::elevation::Elevation;
use unlucky_design::icons::IconKind;
use unlucky_design::inkcat::size::ControlSize;
use unlucky_design::shape::ShapeScale;
use crate::icons;
@@ -56,8 +57,10 @@ use super::interaction::Interaction;
use super::layout;
mod assemble;
mod fab;
mod geometry;
pub use fab::{fab, Fab};
pub use geometry::{Geometry, IconGeometry};
use assemble::{assemble, style_closure, StyleSpec};
@@ -160,35 +163,47 @@ impl Variant {
}
}
/// IconButton 的三种容器变体(12b §2.5)。
/// 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 {
/// 标准:容器透明、图标 `OnSurfaceVariant`(各态图标色不变,12b 2.4)。
Standard,
/// 纯图标(规格卡 `PLAIN`):容器透明、图标 `OnSurfaceVariant`(各态图标色不变,12b 2.4)。
Plain,
/// 实心:`Primary` + `OnPrimary`。
Filled,
/// Tonal:`SecondaryContainer` + `OnSecondaryContainer`。
Tonal,
/// 描边:透明容器 + 1px `Outline`(规格卡第三颗,M229 新增)。
Outlined,
}
impl IconVariant {
/// 三式全序(gallery 顺序)。
pub const ALL: [IconVariant; 3] = [
IconVariant::Standard,
/// 旧名字的别名:`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::Standard => "standard",
IconVariant::Plain => "plain",
IconVariant::Filled => "filled",
IconVariant::Tonal => "tonal",
IconVariant::Outlined => "outlined",
}
}
/// **禁用态图标色**:三式都是 `OnSurface` @0.38 —— A1
/// **禁用态图标色**:四式都是 `OnSurface` @0.38 —— A1
/// (`StandardIconButtonTokens.DisabledColor` / `FilledIconButtonTokens.DisabledColor`)。
pub(crate) const fn disabled_content(self) -> Role {
Role::OnSurface
@@ -197,13 +212,14 @@ impl IconVariant {
/// 色彩角色三元组(同 [`Variant::roles`] 的口径)。
pub(crate) const fn roles(self) -> (Option<Role>, Role, Option<Role>) {
match self {
IconVariant::Standard => (None, Role::OnSurfaceVariant, None),
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)),
}
}
}
@@ -220,6 +236,8 @@ pub struct Button<'a, M> {
leading: Option<IconKind>,
trailing: Option<IconKind>,
state: Option<Interaction>,
/// 尺寸档(规格卡 `BUTTONS / XS - XL`)。产品默认 **S(40 高)**,与 M3 `control_height` 同值。
size: ControlSize,
}
impl<'a, M> Button<'a, M> {
@@ -231,6 +249,7 @@ impl<'a, M> Button<'a, M> {
leading: None,
trailing: None,
state: None,
size: ControlSize::S,
}
}
@@ -242,9 +261,19 @@ impl<'a, M> Button<'a, M> {
leading: None,
trailing: None,
state: None,
size: ControlSize::S,
}
}
/// 换尺寸档(`XS 32 / S 40 / M 56 / L 96 / XL 136`,圆角一律药丸)。
///
/// 产品页只用 **S**(= M3 `control_height` 40);M / L / XL 是规格卡的梯子档,给
/// 空态主行动与展示型入口留着,`--screen=gallery` 的矩阵会把五档一起拍出来。
pub fn size(mut self, size: ControlSize) -> Self {
self.size = size;
self
}
/// 前置图标(12b 1.4:带图标时左内边距收到 16)。
pub fn leading(mut self, kind: IconKind) -> Self {
self.leading = Some(kind);