From 5174845cda7638203c3b42077a9a520f428a0e77 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 12 Nov 2026 23:32:45 +0800 Subject: [PATCH] =?UTF-8?q?fix(app-iced):=20=E5=88=97=E8=A1=A8=E8=A1=8C?= =?UTF-8?q?=E5=9C=86=E8=A7=92=E6=94=B9=E4=BB=8E=E7=BB=84=E4=BB=B6=E8=A1=A8?= =?UTF-8?q?=E5=8F=96=EF=BC=88M287=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `list_item.rs` 硬写 `ui.radius_dp(ShapeScale::None)`,旁边还写着「4.x:ListItem 的形状 是 `CornerNone`(直角),没有圆角档」——而令牌表 `component_map::table` 的 `ListItem` 是 `Small(8)`。产品侧根本没读表,注释在替错误站台。 改为 `row_border(ui)` → `ui.radius_dp(Component::ListItem.spec().shape)`,数值只留在 令牌表。变异实测(只改表的那一行 `Small→Large`):行圆角 8 → 16,240×60 选中行的顶行 缩进 6 → 13;改回后 11 张图逐字节复原(`DevDocs/artifacts/listradius/`,不入库)。 同 commit 改掉那条直角注释、模块文档补 4.9 行,并加两条单测:渲染路径的 `Border.radius` = 表值折算;源码里不许再出现硬编码的 `ShapeScale::*` 档。 外观影响的边界(写进取证报告):行只在 选中/悬停/按下 时有容器色,未选中静息与禁用 两态 `fill` 为 `None`,那两态逐字节不变是**正确**的,不是没生效。 --- .../src/components/navigation/list_item.rs | 31 +++++++++-- .../components/navigation/list_item/tests.rs | 54 +++++++++++++++++++ 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/crates/app-iced/src/components/navigation/list_item.rs b/crates/app-iced/src/components/navigation/list_item.rs index 0fd2cfc..59b3876 100644 --- a/crates/app-iced/src/components/navigation/list_item.rs +++ b/crates/app-iced/src/components/navigation/list_item.rs @@ -11,6 +11,7 @@ //! | 4.5 | 字级 | 主 `BodyLarge 16/24`、辅助 `BodyMedium 14/20`、overline `LabelSmall 11/16` | `TypeRole` | //! | 4.7 | 状态 | hover/focus/pressed **文本与图标色不变**,只叠状态层(8/10/10%) | [`interaction`] | //! | 4.8 | 选中 trailing | 图标 → **`Primary`** | `Role::Primary` | +//! | 4.9 | 圆角 | 官方 `ListTokens` 是 `CornerNone`(直角);本项目**登记偏离**取 **8**(`Small`) | `Component::ListItem.spec().shape`(M287) | //! //! ## 禁用是**修饰器**,不是构造函数(照 `selection/chip.rs` 复审 P2-1 的结论) //! @@ -44,6 +45,7 @@ use iced::widget::{button, container, text}; use iced::{Alignment, Background, Border, Color, Element, Length, Padding, Pixels, Shadow}; use unlucky_design::color::Role; +use unlucky_design::component_map::Component; use unlucky_design::icons::IconKind; use unlucky_design::shape::ShapeScale; @@ -193,6 +195,28 @@ fn palette(ui: &Ui, base: Base, state: Interaction, selected: bool) -> ItemPalet } } +/// 一行的圆角档位:**只从组件表取**(`Component::ListItem.spec().shape`)。 +/// +/// 官方 `ListTokens` 是 `CornerNone`(直角行),本项目取 `Small`(8)——列表行在启动器里是 +/// 卡片内的**独立可点块**,不是贴边通栏行,圆角与卡片同族更协调(偏离登记见 +/// `crates/design/src/component_map/table.rs` 的 `ListItem` 条目)。 +/// +/// 数值归令牌表,**产品侧不许在这里再写一份**:改表值 ⇒ 行圆角跟着变(M287 的判据)。 +fn shape_scale() -> ShapeScale { + Component::ListItem.spec().shape +} + +/// 一行的描边:只画圆角,四边宽度与色都不画(`Border::default()` 的其余字段)。 +/// +/// 单独成函数是为了让「圆角真的来自组件表」可被单测直接量到 +/// (`tests::row_corner_comes_from_the_component_table`)。 +fn row_border(ui: &Ui) -> Border { + Border { + radius: ui.radius_dp(shape_scale()), + ..Border::default() + } +} + /// 一行文本(显式行高:三行块的 16+24+20 必须能精确等于 60,靠的就是它)。 fn line<'a, M: 'a>( ui: &'a Ui, @@ -285,17 +309,14 @@ pub fn list_item<'a, M: Clone + 'a>(ui: &'a Ui, item: ListItem<'a, M>) -> Elemen let selected = item.selected; let base = item.base; + let border = row_border(ui); let style = move |_theme: &iced::Theme, status: button::Status| { let state = forced.unwrap_or_else(|| Interaction::from_status(status)); let colors = palette(ui, base, state, selected); button::Style { background: colors.fill.map(Background::Color), text_color: colors.headline, - // 4.x:ListItem 的形状是 `CornerNone`(直角),没有圆角档 - border: Border { - radius: ui.radius_dp(ShapeScale::None), - ..Border::default() - }, + border, shadow: Shadow::default(), snap: true, } diff --git a/crates/app-iced/src/components/navigation/list_item/tests.rs b/crates/app-iced/src/components/navigation/list_item/tests.rs index 95e76e4..8009142 100644 --- a/crates/app-iced/src/components/navigation/list_item/tests.rs +++ b/crates/app-iced/src/components/navigation/list_item/tests.rs @@ -98,6 +98,60 @@ fn interaction_states_keep_the_content_colors() { assert_eq!(resting.fill, None, "静息态不叠状态层"); } +/// 行的圆角**读组件表**(`Component::ListItem.spec().shape`),不是在这里另写一个常量。 +/// +/// 抓得住:渲染路径用的 `Border.radius` = 组件表里 `ListItem` 那一行的 `shape` 折算出的 dp; +/// 表值不是 `None`(直角),而 `Small` = 8。 +/// 抓不住:有人把 `row_border` 换成一个**数值恰好相同**的字面常量(`ShapeScale::Small`)—— +/// 见 `render_path_reads_the_table` 的源码结构断言,以及 M287 的变异图(改表值 ⇒ 图跟着变)。 +#[test] +fn row_corner_comes_from_the_component_table() { + let ui = ui(); + let spec = Component::ListItem.spec(); + assert_eq!(spec.shape, ShapeScale::Small, "组件表的 ListItem 档位"); + assert_eq!(spec.micro_radius, None, "ListItem 不走 D32 的微圆角层"); + assert_eq!( + row_border(&ui), + Border { + radius: ui.radius_dp(spec.shape), + ..Border::default() + }, + "行的圆角必须就是组件表那一档折算出来的" + ); + assert_eq!(row_border(&ui).radius.top_left, 8.0, "Small = 8dp"); + assert_ne!( + row_border(&ui), + Border { + radius: ui.radius_dp(ShapeScale::None), + ..Border::default() + }, + "M287 之前这里是 `ShapeScale::None`(直角):退回去会红" + ); +} + +/// 源码结构(`Element` 没有内省接口,见 `pages::layout::source` 的同一条纪律): +/// 渲染路径必须**从组件表取**圆角,文件里不许再出现硬编码的圆角档。 +#[test] +fn render_path_reads_the_table() { + let source = include_str!("../list_item.rs"); + assert!( + source.contains("Component::ListItem.spec().shape"), + "行的圆角必须读组件表(`Component::ListItem.spec().shape`)" + ); + for hardcoded in [ + "ShapeScale::None", + "ShapeScale::Small", + "ShapeScale::ExtraSmall", + "ShapeScale::Medium", + "ShapeScale::Large", + ] { + assert!( + !source.contains(hardcoded), + "{hardcoded} 是硬编码圆角档:ListRow 的圆角只许从组件表取" + ); + } +} + /// 三档行高与 `LM.row_height` 的关系**钉死**(有人按 A1 改回 56 时这里会红)。 #[test] fn heights_are_pinned_to_the_row_height_token() {