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:
1 parent
e9af27c8f1
commit
c723d1faff
5 files changed
+261
-172
No files matched your search
@@ -7,173 +7,28 @@ use iced::Element;
|
||||
|
||||
use unlucky_design::color::{Role, Scheme, SchemeFlavor, SCRIM_ALPHA};
|
||||
use unlucky_design::elevation::MaterialLayer;
|
||||
use unlucky_design::shape::ShapeScale;
|
||||
use unlucky_design::typography::Platform;
|
||||
|
||||
use crate::theme::{to_iced, Families, Ui};
|
||||
|
||||
use super::super::interaction::{self, Interaction};
|
||||
use super::dialog::scrim;
|
||||
use super::geometry::{phase_segments, CircularGeometry, LinearGeometry};
|
||||
use super::keyframes::{KeyPoint, Keyframes};
|
||||
use super::panel::{DialogGeometry, EmptyStateGeometry, SnackbarGeometry};
|
||||
use super::progress::{Amount, Progress};
|
||||
use super::snackbar::{Placement, Snackbar};
|
||||
use super::{circular, dialog, empty_state, linear, snackbar, Dialog, EmptyState};
|
||||
|
||||
mod progress;
|
||||
|
||||
const FAMILIES: Families = Families {
|
||||
text: "Segoe UI",
|
||||
display: "Segoe UI",
|
||||
mono: "Consolas",
|
||||
};
|
||||
|
||||
fn ui(flavor: SchemeFlavor) -> Ui {
|
||||
pub(super) fn ui(flavor: SchemeFlavor) -> Ui {
|
||||
Ui::with_platform(flavor, Platform::Windows, FAMILIES)
|
||||
}
|
||||
|
||||
// ---- §12 进度 ---------------------------------------------------------------
|
||||
|
||||
/// 12.1:线性厚度 **4**、圆角 full ⇒ 2(`ShapeScale::Full.radius_px(4)`)。
|
||||
#[test]
|
||||
fn linear_thickness_and_radius_come_from_the_tokens() {
|
||||
let geometry = LinearGeometry::of(&ui(SchemeFlavor::Light));
|
||||
assert_eq!(geometry.thickness, 4.0, "12.1 线性厚度 = 4(A1 Height)");
|
||||
assert_eq!(
|
||||
geometry.radius,
|
||||
ShapeScale::Full.radius_px(4.0),
|
||||
"12.1 圆角 full = 厚的一半"
|
||||
);
|
||||
assert_eq!(geometry.radius, 2.0);
|
||||
assert_eq!(
|
||||
geometry.cycle_ms, 1750,
|
||||
"不定态周期 = LinearAnimationDuration"
|
||||
);
|
||||
}
|
||||
|
||||
/// 12.3:环形 **40 / 4**,环中线半径 (40 − 4) / 2 = **18**。
|
||||
#[test]
|
||||
fn circular_geometry_matches_the_tokens() {
|
||||
let geometry = CircularGeometry::of(&ui(SchemeFlavor::Dark));
|
||||
assert_eq!(
|
||||
geometry.diameter, 40.0,
|
||||
"A1 CircularProgressIndicatorTokens.Size"
|
||||
);
|
||||
assert_eq!(geometry.thickness, 4.0, "A1 ActiveThickness");
|
||||
assert_eq!(geometry.radius, 20.0, "圆角 full = 直径的一半");
|
||||
assert_eq!(geometry.arc_radius(), 18.0);
|
||||
assert_eq!(
|
||||
geometry.cycle_ms, 6000,
|
||||
"A1 CircularAnimationProgressDuration"
|
||||
);
|
||||
}
|
||||
|
||||
/// 12.4:不定态两段**始终非负**且真的在跑(相位扫一圈,段中心单调右移)。
|
||||
#[test]
|
||||
fn indeterminate_segments_stay_ordered_and_travel_right() {
|
||||
let mut travelled = false;
|
||||
let mut previous_end = 0.0;
|
||||
for step in 0..=200 {
|
||||
let phase = step as f32 / 200.0;
|
||||
for (start, end) in phase_segments(phase) {
|
||||
assert!(
|
||||
start <= end + f32::EPSILON,
|
||||
"相位 {phase:.3}:段起点 {start} 大于终点 {end}(前缘/后缘写反就会这样)"
|
||||
);
|
||||
assert!(
|
||||
(0.0..=1.0).contains(&start) && (0.0..=1.0).contains(&end),
|
||||
"相位 {phase:.3}:段越出轨道 {start}..{end}"
|
||||
);
|
||||
}
|
||||
let first = phase_segments(phase)[0].1;
|
||||
if phase > 0.0 && phase < 0.5 && first > previous_end {
|
||||
travelled = true;
|
||||
}
|
||||
previous_end = first;
|
||||
}
|
||||
assert!(travelled, "段前沿必须随相位右移(否则就是一根不动的条)");
|
||||
|
||||
// 相位 0:两段都是空的(前沿/后缘都在 0)
|
||||
assert_eq!(phase_segments(0.0), [(0.0, 0.0), (0.0, 0.0)]);
|
||||
// 1000/1750 ≈ 0.5714:第一线前沿到头(= 1.0)
|
||||
let (_, head) = phase_segments(1000.0 / 1750.0)[0];
|
||||
assert!((head - 1.0).abs() < 1.0e-5, "第一线前沿应在 1000ms 到 1.0");
|
||||
}
|
||||
|
||||
/// 环形不定态:弧长在 0.1..0.87 之间(A1 min/max),旋转走满一圈。
|
||||
#[test]
|
||||
fn circular_indeterminate_sweeps_the_official_range() {
|
||||
let geometry = CircularGeometry::of(&ui(SchemeFlavor::Light));
|
||||
let mut min = f32::MAX;
|
||||
let mut max = f32::MIN;
|
||||
let mut rotation = f32::MIN;
|
||||
for step in 0..=600 {
|
||||
let (sweep, spin) = geometry.indeterminate(step as f32 / 600.0);
|
||||
min = min.min(sweep);
|
||||
max = max.max(sweep);
|
||||
rotation = rotation.max(spin);
|
||||
}
|
||||
assert!(
|
||||
(min - 0.1).abs() < 1.0e-3,
|
||||
"弧长下限 0.1(CircularIndeterminateMinProgress),实测 {min}"
|
||||
);
|
||||
assert!((max - 0.87).abs() < 1.0e-3, "弧长上限 0.87,实测 {max}");
|
||||
assert!(
|
||||
(rotation - 1.0).abs() < 1.0e-3,
|
||||
"旋转应走满一整圈,实测 {rotation}"
|
||||
);
|
||||
}
|
||||
|
||||
/// 确定态:越界值夹到 0..1(0% 只画轨道,100% 画满)。
|
||||
#[test]
|
||||
fn determinate_values_are_clamped() {
|
||||
assert_eq!(
|
||||
Progress::determinate(1.5).amount(),
|
||||
Amount::Determinate(1.0)
|
||||
);
|
||||
assert_eq!(
|
||||
Progress::determinate(-2.0).amount(),
|
||||
Amount::Determinate(0.0)
|
||||
);
|
||||
assert_eq!(Progress::determinate(0.5).spans(), vec![(0.0, 0.5)]);
|
||||
assert_eq!(Progress::determinate(0.0).spans(), vec![(0.0, 0.0)]);
|
||||
}
|
||||
|
||||
/// keyframes 求值器:端点保持、相位循环、越界相位按周期取模。
|
||||
#[test]
|
||||
fn keyframes_hold_ends_and_wrap() {
|
||||
const LINE: Keyframes = Keyframes {
|
||||
cycle_ms: 1000,
|
||||
points: &[
|
||||
KeyPoint {
|
||||
ms: 250,
|
||||
value: 0.0,
|
||||
easing: unlucky_design::motion::Easing::Linear,
|
||||
},
|
||||
KeyPoint {
|
||||
ms: 750,
|
||||
value: 1.0,
|
||||
easing: unlucky_design::motion::Easing::Linear,
|
||||
},
|
||||
],
|
||||
};
|
||||
assert_eq!(LINE.sample(0.0), 0.0, "第一点之前保持首值");
|
||||
assert_eq!(LINE.sample(0.25), 0.0, "正好在第一点");
|
||||
assert!((LINE.sample(0.5) - 0.5).abs() < 1.0e-5, "中点线性插值");
|
||||
assert_eq!(LINE.sample(0.75), 1.0);
|
||||
assert_eq!(LINE.sample(0.9), 1.0, "最后一点之后保持末值");
|
||||
assert_eq!(LINE.sample(1.25), 0.0, "相位 1.25 = 0.25(循环)");
|
||||
assert_eq!(
|
||||
LINE.sample(-0.75),
|
||||
0.0,
|
||||
"负相位按周期折返:-0.75 → 0.25 相位 = 250ms = 首点"
|
||||
);
|
||||
assert!(
|
||||
(LINE.sample(-0.6) - 0.3).abs() < 1.0e-5,
|
||||
"-0.6 → 0.4 相位 = 400ms = 30%"
|
||||
);
|
||||
assert_eq!(LINE.sample(f32::NAN), 0.0, "NaN 不 panic,给首值");
|
||||
}
|
||||
|
||||
// ---- §14 Snackbar -----------------------------------------------------------
|
||||
|
||||
/// 14.3 / 14.1 / 宽度上限:48 / 68 / 4 / 600。
|
||||
@@ -254,7 +109,14 @@ fn empty_state_geometry_is_the_project_defined_set() {
|
||||
assert_eq!(geometry.block, 96.0);
|
||||
assert_eq!(geometry.block_radius, 16.0, "ShapeScale::Large");
|
||||
assert_eq!(geometry.icon, 32.0, "IconSize::Empty");
|
||||
assert!(geometry.gap_title > 0.0 && geometry.gap_body > 0.0);
|
||||
// **复审 P2-1 的回归断言**:三个间距必须精确等于令牌档位。
|
||||
// 早先这里只写 `> 0.0`,所以「Column::spacing(8) 与显式 gap(16/24) 双算成 32/40」
|
||||
// 完全藏得住(渲染实测 264 vs 登记 232)。
|
||||
assert_eq!(geometry.gap_title, 16.0, "块↔标题 = Space::X4");
|
||||
assert_eq!(geometry.gap_body, 8.0, "标题↔副文案 = Space::X2");
|
||||
assert_eq!(geometry.gap_action, 24.0, "副文案↔CTA = Space::X6");
|
||||
assert_eq!(geometry.block, 96.0);
|
||||
assert_eq!(super::empty_state::block_layer(), MaterialLayer::Overlay);
|
||||
}
|
||||
|
||||
// ---- 结构 / 角色 ------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user