Files
Unlucky-Launcher/crates/app-iced/src/components/feedback/panel.rs
T
AskaEth 5d7426b6c9 fix(iced/feedback): 空态图标改用 M86 的 view_at 渲染 32(复审 C 修正版)
tower 修正指令:M86(e377989)把 `icons::view_at(kind, ink, px)` 并进 main 了,所以 C 项
从「登记值改成 24」改为「登记值保持 32 + 用 view_at 真渲 32」:

- `empty_state.rs`:`icons::view(..)` → `icons::view_at(.., geometry.icon)`,盒仍 96、
  图标 32;`view_at` 按 32 **重新烘焙 stroke**,视觉线宽仍是 1.5px(不会把描边拉粗)。
  早先 `view` 把 svg 钉成 `Fixed(24)`,32 的盒子只居中不拉伸 —— 实渲 24、墨迹 20×20。
- `panel.rs`:`EMPTY_ICON = 32.0`(`IconSize::Empty`),删掉 G18-h 欠账说明(已消解)。
- `probe/panels.rs`:图标墨迹判据 16..26 → **22..34**(32 盒)。
- `tests.rs`:`geometry.icon == 32` 保留,并写明它守的是「登记值 = 交给 view_at 的 px」,
  抓不住「盒子 32 而 svg 仍钉 24」那类(渲染层由 probe 的墨迹断言兜)。
- 顺带清掉 `panels.rs` 里两个未用 import(`-D warnings` 会红)。

已 `git rebase main`(1f4bc1b,含 M86),无冲突。
2026-11-02 15:56:47 +08:00

147 lines
5.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 三件「面」的几何:Snackbar / Dialog / EmptyState(数值与出处见 [`super::geometry`] 的 G17 表)。
//!
//! 为什么与进度分家:进度那两个是**带节奏的图形**(keyframes 与相位),这三件是**静态盒子**
//! (宽高/内距/圆角)。合在一个文件里会顶到 400 行红线(AGENTS.md §3-1)。
use unlucky_design::component_map::Component;
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 {
/// 可用宽 → 实际渲染宽:`min(本项目 480, 可用宽)`,再**尊重官方下限 280**。
///
/// 复审 E:早先 `Dialog::width(200)` 会真的画 200(官方 `DialogMinWidth = 280` 只在测试里)。
/// 可用宽 < 280 时仍画 280(**溢出宿主**)—— iced 没有 min-width,这条是 G17-d 之二。
pub fn effective_width(&self, available: f32) -> f32 {
available.min(self.width).max(self.min_width)
}
/// 从令牌求值(内边距与三个间距全部落在 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),
}
}
}
/// 空态图标边长(`IconSize::Empty` = 32)。
///
/// M86 起 `icons::view_at` 会按请求尺寸**重新烘焙 stroke**,所以 32 渲染仍是视觉 1.5px 线宽
/// (早先 `view` 把 svg 钉成 24,32 的盒子只是居中不拉伸,实渲 24 —— 那个欠账已随 M86 消解)。
pub const EMPTY_ICON: f32 = 32.0;
/// 空态的几何(**项目自定**:官方没有这个组件;`[估]` 几何 + `[规格]` 排版 + `[源]` CTA)。
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EmptyStateGeometry {
/// 图标块边长(96)。
pub block: f32,
/// 图标块圆角(16,`ShapeScale::Large`)。
pub block_radius: f32,
/// 块内图标(**32**,`IconSize::Empty`;由 `icons::view_at` 按 32 烘焙,stroke 同步重算)。
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: EMPTY_ICON,
gap_title: ui.space(Space::X4),
gap_body: ui.space(Space::X2),
gap_action: ui.space(Space::X6),
}
}
}