fix(iced/feedback): 空态间距双算、图标块对比度、Dialog 宽度与动作禁用底色(P2-1/2/4)

- P2-1 空态间距双算:`Column::spacing(8)` 与显式 `gap(16/24)` 叠加 ⇒ 实际渲染 32/40、
  条目总高 264,而登记的是 16/8/24、232。现在 `spacing(0.0)`,间距只有一个来源(`gap()`);
  断言从 `gap > 0.0` 改成**精确等于令牌档位** 16/8/24。
- P2-2 空态图标块几乎没有边界:`SurfaceContainerLow` 与画布 ΔRGB 只有 11/5/0(≈1%),
  浅色下看不出块。换 `SurfaceContainerHigh`(材质层 Overlay)+ 1px `OutlineVariant` 描边,
  并用 `block_layer()` 让 probe 与组件取同一个层(不再各写一份角色)。
- P2-1 Dialog 恒 480 宽:`Length::Fixed(480)` 在窄宿主会溢出,`.max_width(560)` 对 Fixed 是
  空操作。改成 `Fill + max_width(480)` = `min(宿主, 480)`(iced 无 min-width,见 G17-d)。
- P2-4 动作禁用底色取错:`interaction::disabled_content_over_role(..., false)` 把 38% 内容色
  固定叠在 **Surface** 上,而 Snackbar 的底是 `InverseSurface`、Dialog 是 `SurfaceContainerHigh`
  ⇒ 真出现禁用态会是「38% 深字叠深底」。改为 `content_color()` 按真实容器角色合成(同令牌 α)。
- probe:Dialog 上内距判据 `(24..=40)` 收紧到 `(24..=30)`;空态新增「块↔标题 ≈16」与
  「条目总高 ≈232」两条渲染层断言。
This commit is contained in:
AskaEth committed 2026-11-02 15:56:44 +08:00
1 parent e9af27c8f1
commit c723d1faff
5 files changed
+261 -172

No files matched your search

@@ -3,8 +3,9 @@
//! ## ⚠️ 这是**项目自定**组件,官方 Material 3 没有它
//!
//! `research/14`(官方组件清单)里**没有** EmptyState 这一项,Compose `material3` 也没有
//! 对应 token。所以本文件的数值**全部是 `[估]`**(`research/12b §15` + `11b R14` + M53 的空态锚点),
//! 不得对外声称「按官方规格实现」:
//! 对应 token。所以**几何与排布是我们自定的**:下面表里标 `[估]` 的(96 块 / 三个间距 / 底色)
//! 没有任何官方出处,标 `[规格]` 的是 `11b R14` 的排版口径、标 `[源]` 的只有「CTA 就是标准按钮」
//! 这一条(按钮家族自己的 A1)。**不得整体对外声称「按官方规格实现」**:
//!
//! | # | 项 | 值 | 等级 |
//! |---|---|---|---|
@@ -75,9 +76,11 @@ pub fn empty_state<'a, M: Clone + 'a>(ui: &'a Ui, spec: EmptyState<'a, M>) -> El
icons::view::<M>(spec.icon, ui.color(Role::OnSurfaceVariant)),
);
let block_radius = iced::border::Radius::new(geometry.block_radius);
// 块底色 `[估]`:取材质层 `Card`(`SurfaceContainerLow`)——空态块是「内容卡片」层级,
// 不是浮层也不是画布;官方没有这个组件,所以这条是我们自己的口径。
let block_color = ui.layer(MaterialLayer::Card);
// 块底色 `[估]`:`SurfaceContainerHigh`(材质层 `Overlay`)。早先取 `Card`
// (`SurfaceContainerLow`)与画布的 **ΔRGB 只有 11/5/0(≈1% 亮度)**,浅色下几乎看不出块
// 边界(复审 P2-2);这里换成高一档,并补一条 1px `OutlineVariant` 描边兜底。
let block_color = ui.layer(BLOCK_LAYER);
let block_outline = ui.color(Role::OutlineVariant);
let block = container(glyph)
.width(Length::Fixed(geometry.block))
.height(Length::Fixed(geometry.block))
@@ -86,15 +89,19 @@ pub fn empty_state<'a, M: Clone + 'a>(ui: &'a Ui, spec: EmptyState<'a, M>) -> El
.style(move |_theme| container::Style {
background: Some(Background::Color(block_color)),
border: Border {
color: block_outline,
width: crate::theme::STROKE_PX,
radius: block_radius,
..Border::default()
},
..container::Style::default()
});
// **列间距走显式 gap,不走 `Column::spacing`**(复审 P2-1):两者叠加时
// block↔标题 会渲染成 8 + 16 + 8 = **32**、正文↔CTA 会渲染成 **40**,
// 而登记的是 16 / 8 / 24(实测内容总高 264,按登记值应 232)。
let mut content = column![]
.align_x(Alignment::Center)
.spacing(geometry.gap_body)
.spacing(0.0)
.push(block)
.push(gap(geometry.gap_title))
.push(
@@ -103,6 +110,7 @@ pub fn empty_state<'a, M: Clone + 'a>(ui: &'a Ui, spec: EmptyState<'a, M>) -> El
.font(ui.font_of(title_style))
.color(ui.color(Role::OnSurface)),
)
.push(gap(geometry.gap_body))
.push(
text(spec.body)
.size(body_style.size_px)
@@ -125,7 +133,17 @@ pub fn empty_state<'a, M: Clone + 'a>(ui: &'a Ui, spec: EmptyState<'a, M>) -> El
.into()
}
/// 固定高度的透明留白(列间距在 `spacing` 之外还要分档时用)。
/// 图标块的材质层(`[估]`:空态块是「抬起来的卡片」层级,不是画布也不是浮层)。
///
/// probe 与测试都从这里取,避免「组件一个色、断言另一个色」。
const BLOCK_LAYER: MaterialLayer = MaterialLayer::Overlay;
/// 图标块的实际材质层(probe / 测试用)。
pub fn block_layer() -> MaterialLayer {
BLOCK_LAYER
}
/// 固定高度的透明留白(列间距的唯一来源:`Column::spacing` 必须保持 0,否则双算)。
fn gap<'a, M: 'a>(height: f32) -> Element<'a, M> {
iced::widget::Space::new()
.width(Length::Fill)