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:
1 parent
a06b7ff4ec
commit
db541c820e
44 files changed
+1439
-300
No files matched your search
@@ -0,0 +1,126 @@
|
||||
//! **ELEVATED CARD**(规格卡 `CARD / DIALOG / SNACKBAR / LIST` 的第一个方框)。
|
||||
//!
|
||||
//! ## 规格卡原值(`DevDocs/artifacts/components-ink.svg`,`[spec]`)
|
||||
//!
|
||||
//! | 项 | 值 | 出处 |
|
||||
//! |---|---|---|
|
||||
//! | 方框 | 300×150、**rx=16**(`inkcat::metrics::CARD`) | `rect x=64 y=1312 rx=16` |
|
||||
//! | 底色 | **`SurfaceContainer`**(Ink 板上比画布 `Surface` 亮一档) | 规格卡卡底那一格的 `fill` |
|
||||
//! | 内边距 | **24**(文字 `x=88` − 框 `x=64`) | 同上 |
|
||||
//! | 描边 | **无**(`stroke="none"`) | 同上 |
|
||||
//! | 上标注 | **11 / 600**(`LabelSmall` + 半粗,字距 1) | `ELEVATED CARD` 那行 |
|
||||
//! | 标题 | **16 / 400**(`BodyLarge`) | `Night bridge, bright rails.` 那行 |
|
||||
//! | 副文案 | 13 / 400 → **14**(`BodyMedium`;13 落在 12/14 之间,按 `inkcat::type_scale` 取较大档) | `night / bridge / cat` 那行 |
|
||||
//!
|
||||
//! ## 与 `pages::frame::card`(描边卡)的分工
|
||||
//!
|
||||
//! - [`card`]:**靠层级差站立**的卡(无描边、`SurfaceContainer` 底、16 圆角)。规格卡的
|
||||
//! `ELEVATED CARD` 就是这一档,实例页的元信息卡用它;
|
||||
//! - `pages::frame::card`:描边卡(`OutlineVariant` 1px + 卡片层底),大面积表单容器用它。
|
||||
//!
|
||||
//! 两种卡并存是**有意的**:规格卡只画了 elevated 一种,而桌面端的长表单需要「弱一点的容器」
|
||||
//! (满屏都是 16 圆角 + 容器底,分隔线会看起来像浮块)。哪种卡用在哪儿由本节决定,不是随手挑。
|
||||
|
||||
use iced::widget::text::LineHeight;
|
||||
use iced::widget::{column, container, text};
|
||||
use iced::{Background, Border, Element, Length, Padding, Pixels};
|
||||
|
||||
use unlucky_design::color::Role;
|
||||
use unlucky_design::spacing::Space;
|
||||
use unlucky_design::typography::{TypeRole, TypeStyle, Weight};
|
||||
|
||||
use crate::theme::Ui;
|
||||
|
||||
/// ELEVATED CARD 的内边距(规格卡 88 − 64 = **24**)。
|
||||
///
|
||||
/// 与 `LayoutMetrics::card_padding`(描边卡的 21)不是一回事:那个数服务长表单,这个是规格卡量出来的。
|
||||
pub const PAD: f32 = 24.0;
|
||||
|
||||
/// 一个 ELEVATED CARD 方框(只管壳:底、圆角、内边距;**无描边**)。
|
||||
///
|
||||
/// 消息类型与被包的内容同型,所以它能被任何页面复用(实例页 / 外观页 / 账户页各用自己的
|
||||
/// `Message`)——这是「组件不 import 业务消息」那条契约的落地方式。
|
||||
pub fn card<'a, M: 'a>(ui: &'a Ui, content: impl Into<Element<'a, M>>) -> Element<'a, M> {
|
||||
let shape = ui.card_shape();
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.padding(Padding::from([PAD, PAD]))
|
||||
.style(move |_theme| container::Style {
|
||||
// 规格卡的卡底是 **surface-container**(不是 `surface-container-low`):
|
||||
// 层级靠与画布(`surface`)的 tone 差表达,不靠描边。
|
||||
background: Some(Background::Color(ui.color(Role::SurfaceContainer))),
|
||||
border: Border {
|
||||
radius: ui.radius_dp(shape),
|
||||
..Border::default()
|
||||
},
|
||||
..container::Style::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// 一张规格卡的三段文字:上标注(可选)/ 标题 / 副文案(可选)。
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Texts<'a> {
|
||||
/// 上标注(`ELEVATED CARD` 那种小字标签)。
|
||||
pub overline: Option<&'a str>,
|
||||
/// 标题(16/400)。
|
||||
pub title: &'a str,
|
||||
/// 副文案(14/400)。
|
||||
pub support: Option<&'a str>,
|
||||
}
|
||||
|
||||
/// 三段式内容(排版按规格卡:11/600 + 16/400 + 14/400,行间 4)。
|
||||
pub fn texts<'a, M: 'a>(ui: &'a Ui, parts: Texts<'a>) -> Element<'a, M> {
|
||||
let mut stack = column![].spacing(ui.space(Space::X1));
|
||||
|
||||
if let Some(overline) = parts.overline {
|
||||
stack = stack.push(role_text(
|
||||
ui,
|
||||
overline_style(),
|
||||
overline,
|
||||
Role::OnSurfaceVariant,
|
||||
));
|
||||
}
|
||||
stack = stack.push(role_text(
|
||||
ui,
|
||||
ui.style(TypeRole::BodyLarge),
|
||||
parts.title,
|
||||
Role::OnSurface,
|
||||
));
|
||||
if let Some(support) = parts.support {
|
||||
stack = stack.push(role_text(
|
||||
ui,
|
||||
ui.style(TypeRole::BodyMedium),
|
||||
support,
|
||||
Role::OnSurfaceVariant,
|
||||
));
|
||||
}
|
||||
|
||||
stack.width(Length::Fill).into()
|
||||
}
|
||||
|
||||
/// 上标注的排版(`LabelSmall` 11/16,字重 **600**:规格卡 `font-weight=600`;
|
||||
/// M3 的 `LabelSmall` 是 500,`emphasized` 是 700 —— 两个都不是 600,所以在这里显式给 600)。
|
||||
pub fn overline_style() -> TypeStyle {
|
||||
TypeStyle {
|
||||
weight: Weight::SemiBold,
|
||||
..TypeRole::LabelSmall.style()
|
||||
}
|
||||
}
|
||||
|
||||
fn role_text<'a, M: 'a>(
|
||||
ui: &'a Ui,
|
||||
style: TypeStyle,
|
||||
content: &'a str,
|
||||
role: Role,
|
||||
) -> Element<'a, M> {
|
||||
text(content)
|
||||
.size(style.size_px)
|
||||
.line_height(LineHeight::Absolute(Pixels(style.line_height_px)))
|
||||
.font(ui.font_of(style))
|
||||
.color(ui.color(role))
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
Reference in new issue
Block a user