feat(iced): 反馈类家族四件(Progress / Snackbar / Dialog / EmptyState)
D39 五家族并行的反馈类线(M78 / COMPFEED)。全部对消息类型泛型、不 import app::Message, 数值来自令牌或登记为 G17(令牌层没有反馈类的 ComponentMetrics 与骨架位)。 - Progress:linear(确定态 FillPortion 占比 + 官方 1750ms 双段不定态 keyframes)与 circular(40/4 环,弧长 = 数值×360°,不定态走官方 6000ms 弧长+旋转 keyframes)。 环形用几何 SVG(canvas feature 没开),组件不计时,相位由调用方给。 - Snackbar:48/68 两种高、上限宽 600(A1 ContainerMaxWidth)、12 外边距、贴顶/贴底、 action(InversePrimary)+ 关闭键(InverseOnSurface);**组件不计时**,超时归调用方。 iced 的 Length 没有 min-width ⇒ 344 的最小宽由宿主保证(登记 G17-d,与按钮 G15 同墙)。 - Dialog:scrim(黑@0.32 预合成实色,D27)+ R28 + SurfaceContainerHigh + pad 24 + 标题/正文/动作三区 + 无动作/单动作/双动作;官方 280..560 区间内取 480。 - EmptyState:**官方没有这个组件**,96 图标块/32 图标/HeadlineSmall 全部标 [估]。 - action.rs:Dialog/Snackbar 共用的文字/图标动作(内容角色作参数,状态层仍走 interaction)。 - gallery/feedback.rs:状态矩阵;家族内再切一段 ULL_FEEDBACK_PART(all|progress|snackbar| dialog|empty),开关本身住在 feedback/gallery_parts.rs,不动 gallery/mod.rs。 - feedback/probe.rs:对着真截图量像素(渲染层断言),四类几何各逐项钉住。 - feedback/tests.rs:几何/节奏/角色/scrim 的纯函数断言。 gates: fmt --all --check · clippy -p unlucky-app-iced --all-targets -D warnings · test -p unlucky-app-iced(144 通过 / 0 失败)
This commit is contained in:
1 parent
27f0d2a551
commit
36f58d7904
14 files changed
+2900
-39
No files matched your search
@@ -0,0 +1,133 @@
|
||||
//! 三件「面」的几何:Snackbar / Dialog / EmptyState(数值与出处见 [`super::geometry`] 的 G17 表)。
|
||||
//!
|
||||
//! 为什么与进度分家:进度那两个是**带节奏的图形**(keyframes 与相位),这三件是**静态盒子**
|
||||
//! (宽高/内距/圆角)。合在一个文件里会顶到 400 行红线(AGENTS.md §3-1)。
|
||||
|
||||
use unlucky_design::component_map::Component;
|
||||
use unlucky_design::icons::IconSize;
|
||||
use unlucky_design::spacing::Space;
|
||||
|
||||
use crate::theme::Ui;
|
||||
|
||||
/// Snackbar 的几何(G17-c/d + A1 高/内距/上限)。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct SnackbarGeometry {
|
||||
/// 最小宽度(344,`[估]`;iced 无 min-width,见组件文档)。
|
||||
pub min_width: f32,
|
||||
/// 最大宽度(600,`[源]` `SnackbarKt.ContainerMaxWidth`)。
|
||||
pub max_width: f32,
|
||||
/// 单行高(48)。
|
||||
pub single_height: f32,
|
||||
/// 双行高(68)。
|
||||
pub two_height: f32,
|
||||
/// 距宿主边距(12,`[规格]`)。
|
||||
pub margin: f32,
|
||||
/// 文本水平内距(16)。
|
||||
pub pad_h: f32,
|
||||
/// 文本垂直内距(6)。
|
||||
pub pad_v: f32,
|
||||
/// 文本 ↔ 动作间距(8)。
|
||||
pub gap: f32,
|
||||
/// 圆角(4)。
|
||||
pub radius: f32,
|
||||
}
|
||||
|
||||
impl SnackbarGeometry {
|
||||
/// 从令牌求值。
|
||||
pub fn of(ui: &Ui) -> Self {
|
||||
let single_height = 48.0;
|
||||
Self {
|
||||
min_width: 344.0,
|
||||
max_width: 600.0,
|
||||
single_height,
|
||||
two_height: 68.0,
|
||||
margin: ui.space(Space::X3),
|
||||
pad_h: ui.space(Space::X4),
|
||||
pad_v: 6.0,
|
||||
gap: ui.space(Space::X2),
|
||||
radius: Component::Snackbar.spec().shape.radius_px(single_height),
|
||||
}
|
||||
}
|
||||
|
||||
/// 按行数取高:`lines >= 2` → 68,否则 48(官方 `heightIn(min=...)` 的等价口径)。
|
||||
pub fn height_for(&self, lines: u8) -> f32 {
|
||||
if lines >= 2 {
|
||||
self.two_height
|
||||
} else {
|
||||
self.single_height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dialog 的几何。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct DialogGeometry {
|
||||
/// 本项目宽度(480,落在官方 280..560 内)。
|
||||
pub width: f32,
|
||||
/// 官方下限(280)。
|
||||
pub min_width: f32,
|
||||
/// 官方上限(560)。
|
||||
pub max_width: f32,
|
||||
/// 圆角(28)。
|
||||
pub radius: f32,
|
||||
/// 四边内边距(24)。
|
||||
pub padding: f32,
|
||||
/// 标题下间距(16)。
|
||||
pub title_gap: f32,
|
||||
/// 正文下间距(24)。
|
||||
pub body_gap: f32,
|
||||
/// 动作主轴间距(8)。
|
||||
pub action_gap: f32,
|
||||
/// 动作交叉间距(12)。
|
||||
pub action_cross: f32,
|
||||
}
|
||||
|
||||
impl DialogGeometry {
|
||||
/// 从令牌求值(内边距与三个间距全部落在 4px 栅格上 ⇒ 走 `Space`)。
|
||||
pub fn of(ui: &Ui) -> Self {
|
||||
let width = 480.0;
|
||||
Self {
|
||||
width,
|
||||
min_width: 280.0,
|
||||
max_width: 560.0,
|
||||
radius: Component::Dialog.spec().shape.radius_px(width),
|
||||
padding: ui.space(Space::X6),
|
||||
title_gap: ui.space(Space::X4),
|
||||
body_gap: ui.space(Space::X6),
|
||||
action_gap: ui.space(Space::X2),
|
||||
action_cross: ui.space(Space::X3),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 空态的几何(**全部 `[估]`**:官方没有这个组件)。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct EmptyStateGeometry {
|
||||
/// 图标块边长(96)。
|
||||
pub block: f32,
|
||||
/// 图标块圆角(16,`ShapeScale::Large`)。
|
||||
pub block_radius: f32,
|
||||
/// 块内图标(32,`IconSize::Empty`)。
|
||||
pub icon: f32,
|
||||
/// 块 ↔ 标题(16)。
|
||||
pub gap_title: f32,
|
||||
/// 标题 ↔ 副文案(8)。
|
||||
pub gap_body: f32,
|
||||
/// 副文案 ↔ 动作(24)。
|
||||
pub gap_action: f32,
|
||||
}
|
||||
|
||||
impl EmptyStateGeometry {
|
||||
/// 从令牌求值(圆角档位与图标档位是真令牌,96 / 三个间距是项目自定)。
|
||||
pub fn of(ui: &Ui) -> Self {
|
||||
let block = 96.0;
|
||||
Self {
|
||||
block,
|
||||
block_radius: Component::EmptyState.spec().shape.radius_px(block),
|
||||
icon: IconSize::Empty.px(),
|
||||
gap_title: ui.space(Space::X4),
|
||||
gap_body: ui.space(Space::X2),
|
||||
gap_action: ui.space(Space::X6),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user