fix(app-iced): M79 r2 —— 四条 review 修复(对齐/单行/可比对象/整行分隔线)
代码 r1 P2-1 = 视觉 r1 P2-1(同一处,必修) - `app_bar::action`:容器只给了 `align_y` → 24px 图标贴 40×40 命中区左缘(实测前导墨迹 `38..58`,应 `44..68`;hover 的圆形状态层也露在图标一侧)。补 `align_x(Center)`; 同时页头内容行补 `align_y(Center)`(`Container` 默认 Top,整行原本贴顶)。 - 把 Q21 升格成规则并全目录复查(结论写进 `navigation/mod.rs` 的表): app_bar 命中区/页头行、tabs tab 内容、drawer 药丸 = 两轴显式居中; list_item 文本块与 drawer 分组标题 = 左上起排(`Start` 是对的,不需要居中)。 回归钉子:`navigation::probe` 的 appbar 断言量「左内缩 == 右内缩 ±2」。 代码 r1 P2-2(必修):四件文本都没有单行约束,容器高度却是固定值 - `app_bar`(标题/副标题)、`list_item::line`、`drawer::line`、`tabs` 标签统一 `.wrapping(Wrapping::None)` + 外层 `clip(true)`;页头/ListiItem/drawer 的文本块改成 `Fill + clip`,长文本切在文本区里,不折行、不顶穿、不压 trailing 动作。 - iced 没有 ellipsis → 按 `inputs/menu.rs` 的 D19 先例登记 **D-M79-5** (官方要单行 ellipsis,我们单行 + 超长直接切割)。 - 取证:gallery 新增 `ULL_NAV_PART=long`(长实例名整块)+ `navigation/probe/long.rs` 量「行高仍 44/60/76 / 页头仍 56 / 标题墨迹右缘 < trailing 左缘」。取证块按 `selection::matrix` 的先例收进 `navigation/matrix.rs`(gallery 单文件贴 400 红线)。 视觉 r1 P2-2(必修):ListItem 三列内容不同 → 「选中 trailing = Primary」没有可比对象 - 三列都挂 `trailing(settings)`,只有 selected 列走 `Primary`(其余 `OnSurface` / 38%)。 视觉 r1 P2-2(可选,已做):Secondary 分隔线逐 tab 分段 + 0.5 圆角留了 1px 亮缝 - 改成**整行一条**:`Stack` 把一条 `Fill` 的 1px `SurfaceVariant` 压在 tab 行底下 (底座是 tab 行本身 → 线长 = tab 行总宽,横向滚动跟着走)。 - `TabsGeometry::bar_radius(bool)` 收成 `indicator_radius()`(只有 3px 的 Primary 条要圆角), 单测同步;probe 新增「Secondary 分隔线必须 1 段」的断言。 门禁:`cargo fmt --all --check` ✓ · `cargo build -p unlucky-app-iced --lib` 零 warning ✓ (截图/探针/测试的实时输出见完成报告;本轮宿主机被多条并行线的 rustc 打满, bin 链接排队严重,如未取到会如实标注未完成项。)
This commit is contained in:
1 parent
81e34d77da
commit
a36f542f28
13 files changed
+471
-88
No files matched your search
@@ -1,35 +1,28 @@
|
||||
//! gallery 里的导航类小节:四块状态矩阵(**断言在 `navigation::probe`,不在这里**)。
|
||||
//! gallery 里的导航类小节:四块状态矩阵 + 一块长文案块(**断言在 `navigation::probe`**)。
|
||||
//!
|
||||
//! ## 为什么再切一层 `ULL_NAV_PART`
|
||||
//! ## 块选择器 `ULL_NAV_PART`
|
||||
//!
|
||||
//! `ULL_GALLERY_SECTION=navigation` 只解决「别的家族」;本家族自己就有四块矩阵,
|
||||
//! 竖排起来比 760 还高,一屏只拍得到第一块。所以这里加**块选择器**(契约 §9.2 的同一套规则:
|
||||
//! 环境变量 + 默认向后兼容 + 未识别值回落并打 stderr):
|
||||
//! `ULL_GALLERY_SECTION=navigation` 只隔离家族;本家族自己就有四块矩阵 + 长文案块,竖排比 760 高,
|
||||
//! 一屏只拍得到第一块。所以按契约 §9.2 的同一套规则再切一层(环境变量 + 默认兼容 + 未知值回落并打
|
||||
//! stderr):`ULL_NAV_PART=list|tabs|drawer|appbar|long|all`(默认 `all`)。
|
||||
//!
|
||||
//! ```text
|
||||
//! ULL_GALLERY_SECTION=navigation ULL_NAV_PART=list # 只渲染 ListItem 矩阵
|
||||
//! ULL_NAV_PART=tabs | drawer | appbar | all(默认 all)
|
||||
//! ```
|
||||
//!
|
||||
//! 出图命令(worktree 根目录):
|
||||
//!
|
||||
//! ```text
|
||||
//! ULL_GALLERY_SECTION=navigation ULL_NAV_PART=list ./target/debug/unlucky-launcher-iced.exe \
|
||||
//! --software-renderer --screen=gallery --screenshot target/m79/list-light.png --screenshot-theme=light
|
||||
//! ULL_GALLERY_SECTION=navigation ULL_NAV_PART=list ./target/debug/unlucky-launcher-iced.exe //! --software-renderer --screen=gallery --screenshot target/m79/list-light.png --screenshot-theme=light
|
||||
//! ULL_NAV_DIR=target/m79 cargo test -p unlucky-app-iced navigation::probe
|
||||
//! ```
|
||||
//!
|
||||
//! ## 矩阵形状
|
||||
//!
|
||||
//! ```text
|
||||
//! ListItem 行 = 一 / 两 / 三行(44 / 60 / 76) 列 = 选中 / 未选中 / 禁用
|
||||
//! Tabs 两行 Primary(短文案 / 长文案各选中一次)+ 一行 Secondary + 一行五态
|
||||
//! Drawer rail 88 与 drawer 240 **同尺度并排**(D33:同一容器两个宽度),各 3 项、第 2 项选中
|
||||
//! AppBar 静息 / 滚动两态(56 高;滚动换容器色 + L2)
|
||||
//! ListItem 行 = 一 / 两 / 三行(44 / 60 / 76) 列 = 选中 / 未选中 / 禁用(三列槽位完全相同)
|
||||
//! Tabs 三行 Primary(选中第 1/2/3 个)+ 一行 Secondary + 一行五态
|
||||
//! Drawer rail 88 与 drawer 240 **同尺度并排**(D33),各 3 项、第 2 项 hover、第 3 项禁用
|
||||
//! AppBar 静息 / 滚动两态(56 高;滚动只换容器色 + L2)
|
||||
//! long 长文案:页头 + 三行 ListItem + drawer(单行约束 D-M79-5 的取证)
|
||||
//! ```
|
||||
//!
|
||||
//! 「画在取景板(`SurfaceContainerLow`)上」不是装饰:`Surface` 的页头/导航与页面底色同色,
|
||||
//! 不垫一层板子,截图里量不到边界(probe 就是靠这块板的 bbox 量 88 / 240 的)。
|
||||
//! 取景板(`SurfaceContainerLow`)不是装饰:`Surface` 的页头/导航与页面底色同色,不垫一层板子,
|
||||
//! 截图里量不到边界(probe 就是靠板量 88 / 240 的)。
|
||||
|
||||
use iced::widget::{column, container, row, text};
|
||||
use iced::{Background, Element, Length};
|
||||
@@ -43,7 +36,7 @@ use crate::theme::Ui;
|
||||
|
||||
use super::super::interaction::Interaction;
|
||||
use super::super::navigation::{
|
||||
app_bar, list_item, navigation, tabs, AppBar, AppBarAction, ListItem, NavGroup, NavItem,
|
||||
self, app_bar, list_item, navigation, tabs, AppBar, AppBarAction, ListItem, NavGroup, NavItem,
|
||||
NavLayout, Navigation, Tab, Tabs, TabsVariant,
|
||||
};
|
||||
use super::{heading, DemoMessage};
|
||||
@@ -58,9 +51,11 @@ fn part() -> String {
|
||||
};
|
||||
let name = raw.trim().to_ascii_lowercase();
|
||||
match name.as_str() {
|
||||
"" | "all" | "list" | "tabs" | "drawer" | "appbar" => name,
|
||||
"" | "all" | "list" | "tabs" | "drawer" | "appbar" | "long" => name,
|
||||
_ => {
|
||||
eprintln!("[gallery] {PART_ENV}={raw:?} 不是已知块(all|list|tabs|drawer|appbar),已回落 all");
|
||||
eprintln!(
|
||||
"[gallery] {PART_ENV}={raw:?} 不是已知块(all|list|tabs|drawer|appbar|long),已回落 all"
|
||||
);
|
||||
"all".to_owned()
|
||||
}
|
||||
}
|
||||
@@ -128,6 +123,9 @@ pub(super) fn section<'a>(ui: &'a Ui) -> Element<'a, DemoMessage> {
|
||||
if picked == "all" || picked == "appbar" {
|
||||
body = body.push(app_bar_part(ui));
|
||||
}
|
||||
if picked == "long" {
|
||||
body = body.push(navigation::matrix::long_part(ui, pressed));
|
||||
}
|
||||
body.into()
|
||||
}
|
||||
|
||||
@@ -162,8 +160,11 @@ fn list_part<'a>(ui: &'a Ui) -> Element<'a, DemoMessage> {
|
||||
for (index, lines) in ["one line", "two lines", "three lines"].iter().enumerate() {
|
||||
let mut cells = row![].spacing(ui.space(Space::X2));
|
||||
for state in states {
|
||||
// 三列的**内容完全相同**(同样的槽位),只有态不同——否则「选中 vs 未选中」没法逐像素比
|
||||
let mut item = ListItem::new(lines, pressed()).leading(icon("instances"));
|
||||
// 三列的**内容完全相同**(同样的槽位),只有态不同——否则「选中 trailing = Primary」
|
||||
// 这条结论没有可比对象(视觉 r1 P2-2 点名)
|
||||
let mut item = ListItem::new(lines, pressed())
|
||||
.leading(icon("instances"))
|
||||
.trailing(icon("settings"));
|
||||
if index >= 1 {
|
||||
item = item.supporting("3 versions · 1.21.4");
|
||||
}
|
||||
@@ -171,7 +172,7 @@ fn list_part<'a>(ui: &'a Ui) -> Element<'a, DemoMessage> {
|
||||
item = item.overline("RECENT");
|
||||
}
|
||||
item = match state {
|
||||
"selected" => item.selected().trailing(icon("settings")),
|
||||
"selected" => item.selected(),
|
||||
// 禁用是修饰器:这里只清消息,槽位全部保留
|
||||
"disabled" => item.disabled(),
|
||||
_ => item.state(Interaction::Enabled),
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
//! - 不做 `Medium` / `Large` 的**折叠(collapsing)**形态——那是另外两个组件(`AppBarMediumTokens`
|
||||
//! / `AppBarLargeTokens`),本 mission 的 §16 只要求 small。
|
||||
|
||||
use iced::widget::text::LineHeight;
|
||||
use iced::widget::text::{LineHeight, Wrapping};
|
||||
use iced::widget::{button, container, text};
|
||||
use iced::{
|
||||
Alignment, Background, Border, Color, Element, Length, Padding, Pixels, Shadow, Vector,
|
||||
@@ -138,10 +138,14 @@ fn action<'a, M: Clone + 'a>(
|
||||
icons::view::<M>(action.icon, color),
|
||||
);
|
||||
let hit = ui.metrics().control_height;
|
||||
// 图标要落在 40×40 命中区的**正中**:`Container` 默认 `horizontal_alignment: Left`
|
||||
// (`iced_widget-0.14.2/src/container.rs`),只给 `align_y` 会让 24px 图标贴左缘——
|
||||
// 视觉 r1 实测前导墨迹 `38..58`(应 `44..68`)。所以**两轴都显式给**(规则 Q21)。
|
||||
let button = button(
|
||||
container(icon)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.align_x(Alignment::Center)
|
||||
.align_y(Alignment::Center),
|
||||
)
|
||||
.width(Length::Fixed(hit))
|
||||
@@ -182,6 +186,8 @@ pub fn app_bar<'a, M: Clone + 'a>(ui: &'a Ui, bar: AppBar<'a, M>) -> Element<'a,
|
||||
text(bar.title)
|
||||
.size(title_style.size_px)
|
||||
.line_height(LineHeight::Absolute(Pixels(title_style.line_height_px)))
|
||||
// 单行:页头高固定 56,长标题折行会顶穿页头并与 trailing 动作重叠(偏离 D-M79-5)
|
||||
.wrapping(Wrapping::None)
|
||||
.font(ui.font_of(title_style))
|
||||
.color(ui.color(Role::OnSurface)),
|
||||
);
|
||||
@@ -191,6 +197,7 @@ pub fn app_bar<'a, M: Clone + 'a>(ui: &'a Ui, bar: AppBar<'a, M>) -> Element<'a,
|
||||
text(subtitle)
|
||||
.size(style.size_px)
|
||||
.line_height(LineHeight::Absolute(Pixels(style.line_height_px)))
|
||||
.wrapping(Wrapping::None)
|
||||
.font(ui.font_of(style))
|
||||
.color(ui.color(Role::OnSurfaceVariant)),
|
||||
);
|
||||
@@ -203,9 +210,10 @@ pub fn app_bar<'a, M: Clone + 'a>(ui: &'a Ui, bar: AppBar<'a, M>) -> Element<'a,
|
||||
// 16.5:leading 图标 `OnSurface`
|
||||
row = row.push(action(ui, leading, geometry, ui.color(Role::OnSurface)));
|
||||
}
|
||||
row = row.push(texts);
|
||||
// 标题与后置动作之间必须有 Fill:否则标题会被推到右边或右侧动作贴住标题(M77 Q8 同款)
|
||||
row = row.push(iced::widget::space::horizontal());
|
||||
// 文本块吃掉剩余宽度并**裁切**:这一条同时解决两件事——
|
||||
// ① 标题与 trailing 之间必须有 Fill(M77 Q8 同款),② 单行超长标题切在动作按钮之前,
|
||||
// 不会重叠(`clip(true)` 是 iced 里唯一能裁子元素越界的方式;没有 ellipsis,见 D-M79-5)。
|
||||
row = row.push(container(texts).width(Length::Fill).clip(true));
|
||||
for action_item in bar.trailing {
|
||||
// 16.5:trailing 图标 `OnSurfaceVariant`
|
||||
row = row.push(action(
|
||||
@@ -239,6 +247,10 @@ pub fn app_bar<'a, M: Clone + 'a>(ui: &'a Ui, bar: AppBar<'a, M>) -> Element<'a,
|
||||
container(row)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(geometry.height))
|
||||
// 内容行(40 高的动作按钮 + 文本块)必须落在 56 页头的**垂直中线**上:
|
||||
// `Container` 的 vertical_alignment 默认 Top,只给 `width` 会让整行贴顶
|
||||
// (视觉 r1 实测前导图标墨迹 y=204..223,居中应是 209..227)。
|
||||
.align_y(Alignment::Center)
|
||||
.padding(Padding {
|
||||
top: 0.0,
|
||||
right: geometry.edge,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! 拆出去的是「一项长什么样」(`NavItem` / `NavGroup` + 颜色求值 + 一行布局),
|
||||
//! `drawer.rs` 只留「容器长什么样」(宽度、圆角、分组标题、分隔线)。
|
||||
|
||||
use iced::widget::text::LineHeight;
|
||||
use iced::widget::text::{LineHeight, Wrapping};
|
||||
use iced::widget::{button, container, text};
|
||||
use iced::{Alignment, Background, Border, Color, Element, Length, Padding, Pixels, Shadow};
|
||||
|
||||
@@ -164,6 +164,8 @@ pub(super) fn line<'a, M: 'a>(
|
||||
text(value)
|
||||
.size(style.size_px)
|
||||
.line_height(LineHeight::Absolute(Pixels(style.line_height_px)))
|
||||
// 单行(项高固定 52/56):长分组名/实例名不许折行(偏离 D-M79-5)
|
||||
.wrapping(Wrapping::None)
|
||||
.font(ui.font_of(style))
|
||||
.color(color)
|
||||
.into()
|
||||
@@ -217,12 +219,18 @@ pub(super) fn nav_row<'a, M: Clone + 'a>(
|
||||
.width(Length::Fixed(geometry.width))
|
||||
.align_x(Alignment::Center);
|
||||
stack = stack.push(pill(icon));
|
||||
stack = stack.push(line::<M>(
|
||||
ui,
|
||||
item.label,
|
||||
TypeRole::LabelMedium,
|
||||
colors.label,
|
||||
));
|
||||
// 标签要裁到轨宽:单行超长时切在 88 内,不会溢出到内容区(D-M79-5)
|
||||
stack = stack.push(
|
||||
container(line::<M>(
|
||||
ui,
|
||||
item.label,
|
||||
TypeRole::LabelMedium,
|
||||
colors.label,
|
||||
))
|
||||
.width(Length::Fixed(geometry.width))
|
||||
.align_x(Alignment::Center)
|
||||
.clip(true),
|
||||
);
|
||||
stack.into()
|
||||
}
|
||||
NavLayout::Drawer => {
|
||||
@@ -236,15 +244,20 @@ pub(super) fn nav_row<'a, M: Clone + 'a>(
|
||||
bottom: 0.0,
|
||||
left: geometry.pad_start,
|
||||
});
|
||||
// label 吃掉剩余宽度并裁切(单行 + 不压到药丸右内边距,D-M79-5)
|
||||
pill(Element::from(
|
||||
row.push(icon)
|
||||
.push(line::<M>(
|
||||
ui,
|
||||
item.label,
|
||||
TypeRole::LabelLarge,
|
||||
colors.label,
|
||||
))
|
||||
.push(iced::widget::space::horizontal()),
|
||||
.push(
|
||||
container(line::<M>(
|
||||
ui,
|
||||
item.label,
|
||||
TypeRole::LabelLarge,
|
||||
colors.label,
|
||||
))
|
||||
.width(Length::Fill)
|
||||
.clip(true),
|
||||
)
|
||||
.width(Length::Fill),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -197,9 +197,12 @@ impl TabsGeometry {
|
||||
}
|
||||
}
|
||||
|
||||
/// 指示条圆角:`RoundedCornerShape(3dp)` 落在 3px 高的条上,与 `Full`(=1.5 半径)渲染结果相同。
|
||||
pub fn bar_radius(&self, primary: bool) -> Radius {
|
||||
Radius::new(self.bar(primary) / 2.0)
|
||||
/// Primary 指示条圆角:`RoundedCornerShape(3dp)` 落在 3px 高的条上,与 `Full`(=1.5 半径)渲染结果相同。
|
||||
///
|
||||
/// Secondary 的 1px 分隔线**没有圆角**:A1 是一条直线;早先给 0.5 圆角,相邻 tab 的分段之间
|
||||
/// 因此留下 1px 亮缝(视觉 r1 P2-2 实测 `25..101 | 103..191 | 193..265`)。
|
||||
pub fn indicator_radius(&self) -> Radius {
|
||||
Radius::new(self.primary_bar / 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,8 +115,7 @@ fn tabs_geometry_matches_the_reference() {
|
||||
assert_eq!(geometry.bar(true), 3.0, "ActiveIndicatorHeight");
|
||||
assert_eq!(geometry.bar(false), 1.0, "DividerHeight");
|
||||
// RoundedCornerShape(3dp) 落在 3px 高的条上 = 半径 1.5(与 Full 同渲染)
|
||||
assert_eq!(geometry.bar_radius(true).top_left, 1.5);
|
||||
assert_eq!(geometry.bar_radius(false).top_left, 0.5);
|
||||
assert_eq!(geometry.indicator_radius().top_left, 1.5);
|
||||
}
|
||||
|
||||
/// 16.1 / 16.5:AppBar 56(LM)、图标 24、左右 4、头像 32。
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
//! `SecondaryContainer` 表示「当前项」——三处保持同一语言,故 ListItem 也填 `SecondaryContainer`
|
||||
//! + `OnSecondaryContainer`。**这是实质偏离**:A1 的 ListItem 选中行不换底色。
|
||||
|
||||
use iced::widget::text::LineHeight;
|
||||
use iced::widget::{button, container, space, text};
|
||||
use iced::widget::text::{LineHeight, Wrapping};
|
||||
use iced::widget::{button, container, text};
|
||||
use iced::{Alignment, Background, Border, Color, Element, Length, Padding, Pixels, Shadow};
|
||||
|
||||
use unlucky_design::color::Role;
|
||||
@@ -193,6 +193,10 @@ fn line<'a, M: 'a>(
|
||||
text(value)
|
||||
.size(style.size_px)
|
||||
.line_height(LineHeight::Absolute(Pixels(style.line_height_px)))
|
||||
// **单行**:行高是固定值(44/60/76),`Text` 默认 `Wrapping::Word` 会把长实例名
|
||||
// 折成两行、把三行块顶穿。官方要的是单行 ellipsis,iced 没有 ellipsis(D19 先例)
|
||||
// → 单行 + 超长由外层 `clip(true)` 直接切(偏离 D-M79-5)。
|
||||
.wrapping(Wrapping::None)
|
||||
.font(ui.font_of(style))
|
||||
.color(color)
|
||||
.into()
|
||||
@@ -246,10 +250,9 @@ pub fn list_item<'a, M: Clone + 'a>(ui: &'a Ui, item: ListItem<'a, M>) -> Elemen
|
||||
icons::view::<M>(kind, colors.leading),
|
||||
));
|
||||
}
|
||||
content = content.push(body);
|
||||
// 文本块吃掉剩余宽度并**裁切**:单行超长时切在行内边距上,不会压到 trailing
|
||||
content = content.push(container(body).width(Length::Fill).clip(true));
|
||||
if let Some(kind) = item.trailing {
|
||||
// trailing 必须在行尾:label 与 trailing 之间插一条水平 Fill(`inputs/menu.rs` 的现行写法)
|
||||
content = content.push(space::horizontal());
|
||||
content = content.push(layout::fixed(
|
||||
geometry.icon,
|
||||
geometry.icon,
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
//! 导航家族的长文案取证块(D-M79-5):**单行 + 裁切**,页面真接长实例名时不许折行。
|
||||
//!
|
||||
//! 为什么不放在 `gallery/navigation.rs`:那个文件已经贴到 400 行红线(AGENTS.md §3-1),
|
||||
//! 而它属于 harness 单文件、scope 只授权一个路径。按 `selection::matrix` 的先例,把**取证块**
|
||||
//! 收进组件目录、对消息类型泛型(`pressed: impl Fn() -> M`),gallery 只做一次 `.map`。
|
||||
|
||||
use iced::widget::{column, container, text};
|
||||
use iced::{Background, Element, Length};
|
||||
|
||||
use unlucky_design::color::Role;
|
||||
use unlucky_design::icons::IconKind;
|
||||
use unlucky_design::spacing::Space;
|
||||
use unlucky_design::typography::TypeRole;
|
||||
|
||||
use crate::theme::Ui;
|
||||
|
||||
use super::{
|
||||
app_bar, list_item, navigation, tabs, AppBar, AppBarAction, ListItem, NavGroup, NavItem,
|
||||
Navigation, Tab, Tabs,
|
||||
};
|
||||
|
||||
/// 一行小字图例。
|
||||
fn note<'a, M: 'a>(ui: &'a Ui, value: &'a str) -> Element<'a, M> {
|
||||
let style = ui.style(TypeRole::BodySmall);
|
||||
text(value)
|
||||
.size(style.size_px)
|
||||
.font(ui.font_of(style))
|
||||
.color(ui.color(Role::OnSurfaceVariant))
|
||||
.into()
|
||||
}
|
||||
|
||||
/// 取景板(`SurfaceContainerLow`):`Surface` 的页头与页面底色同色,要垫一层才量得到边界。
|
||||
fn plate<'a, M: 'a>(ui: &'a Ui, pad: f32, content: impl Into<Element<'a, M>>) -> Element<'a, M> {
|
||||
container(content)
|
||||
.padding(pad)
|
||||
.style(move |_theme| container::Style {
|
||||
background: Some(Background::Color(ui.color(Role::SurfaceContainerLow))),
|
||||
..container::Style::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn icon(name: &str) -> IconKind {
|
||||
match name {
|
||||
"download" => IconKind::Download,
|
||||
"logs" => IconKind::Logs,
|
||||
"settings" => IconKind::Settings,
|
||||
_ => IconKind::Instances,
|
||||
}
|
||||
}
|
||||
|
||||
/// 长文案块(D-M79-5 的取证):单行 + 裁切——不折行、不顶穿 56 的页头、不压 trailing。
|
||||
/// 矩阵图全用短文案看不出这个坑,真实实例名(`All the Mods 10 - ATM10 …`)一接就中招。
|
||||
pub fn long_part<'a, M: Clone + 'a>(ui: &'a Ui, pressed: impl Fn() -> M) -> Element<'a, M> {
|
||||
const LONG: &str = "All the Mods 10 - ATM10 - 1.21.1 - NeoForge";
|
||||
|
||||
let bar = app_bar(
|
||||
ui,
|
||||
AppBar::new(LONG)
|
||||
.subtitle(LONG)
|
||||
.leading(AppBarAction::inert(icon("instances")))
|
||||
.trailing(AppBarAction::new(icon("settings"), pressed()))
|
||||
.trailing(AppBarAction::new(icon("logs"), pressed())),
|
||||
);
|
||||
|
||||
let row = |lines: usize| {
|
||||
let mut item = ListItem::new(LONG, pressed())
|
||||
.leading(icon("instances"))
|
||||
.trailing(icon("settings"));
|
||||
if lines >= 2 {
|
||||
item = item.supporting(LONG);
|
||||
}
|
||||
if lines >= 3 {
|
||||
item = item.overline(LONG);
|
||||
}
|
||||
container(list_item(ui, item.selected()))
|
||||
.width(Length::Fixed(ui.metrics().nav_drawer_width))
|
||||
};
|
||||
|
||||
let pane = navigation(
|
||||
ui,
|
||||
Navigation::drawer().group(
|
||||
NavGroup::titled(LONG)
|
||||
.item(NavItem::new(LONG, icon("instances"), pressed()).selected())
|
||||
.item(NavItem::new("Settings", icon("settings"), pressed())),
|
||||
),
|
||||
);
|
||||
|
||||
// Tabs:长标签不折行,tab 自己变宽(横向滚动兜住溢出)
|
||||
let tabs_row = tabs(
|
||||
ui,
|
||||
Tabs::primary()
|
||||
.tab(Tab::new(LONG, pressed()).selected())
|
||||
.tab(Tab::new("Settings", pressed())),
|
||||
);
|
||||
|
||||
column![
|
||||
note(
|
||||
ui,
|
||||
"长文案(D-M79-5):单行 + 裁切 —— 不折行、页头仍 56、不压 trailing"
|
||||
),
|
||||
plate(ui, ui.space(Space::X2), bar),
|
||||
column![row(1), row(2), row(3)].spacing(ui.space(Space::X4)),
|
||||
tabs_row,
|
||||
container(container(pane).height(Length::Fixed(ui.space(Space::X8) * 6.0)))
|
||||
.width(Length::Fixed(ui.metrics().nav_drawer_width)),
|
||||
]
|
||||
.spacing(ui.space(Space::X2))
|
||||
.into()
|
||||
}
|
||||
@@ -24,6 +24,32 @@
|
||||
//! | Tabs | `OnSurface`@38% | `[估]`(同上;A1 的 Tabs 两套 token 只有 Active/Inactive) |
|
||||
//! | AppBar | — (本轮页头不带禁用动作) | — |
|
||||
//!
|
||||
//! ## Q21:要给居中就**两轴都显式给**(第三轮 review 的规则化)
|
||||
//!
|
||||
//! `Container` 的 `horizontal_alignment` 默认 `Left`、`vertical_alignment` 默认 `Top`
|
||||
//! (`iced_widget-0.14.2/src/container.rs`)。只给 `align_y` 会让内容贴左缘——AppBar 的 40×40
|
||||
//! 动作按钮里 24px 图标曾因此左内缩 4 / 右内缩 20(视觉 r1 实测前导墨迹 `38..58`,应 `44..68`),
|
||||
//! 而且 hover 的圆形状态层会露在图标一侧。
|
||||
//!
|
||||
//! 本目录的复查结论(逐处):
|
||||
//!
|
||||
//! | 位置 | 需要 | 写法 |
|
||||
//! |---|---|---|
|
||||
//! | `app_bar::action` 命中区里的图标 | 两轴居中 | `align_x` + `align_y` |
|
||||
//! | `app_bar` 页头内容行 | 垂直居中(水平由 `Fill` 决定) | `.align_y(Center)` |
|
||||
//! | `tabs::tab_cell` 的内容 | 两轴居中 | `align_x` + `align_y` |
|
||||
//! | `drawer/item` 的药丸 | 两轴居中 | `align_x` + `align_y` |
|
||||
//! | `list_item` 的文本块 / `drawer` 的分组标题 | **左上起排**(`Start` 是对的,不需要居中) | 不设对齐,靠 `padding` 定起点 |
|
||||
//!
|
||||
//! 回归钉子:`navigation::probe` 的 appbar 断言量「左内缩 == 右内缩 ±2」——`align_x` 再漏一次就红。
|
||||
//!
|
||||
//! ## 单行约束(偏离 D-M79-5)
|
||||
//!
|
||||
//! 四件的文本一律 `Wrapping::None` + 外层 `clip(true)`:容器高度都是固定值(44/60/76、48/64、56),
|
||||
//! `Text` 默认 `Wrapping::Word` 会把长实例名折成 2~3 行,顶穿页头、压住 trailing。官方要的是
|
||||
//! **单行 ellipsis**,iced 没有 ellipsis(`inputs/menu.rs` 的 D19 先例)→ 我们单行 + 超长直接切。
|
||||
//! 取证:`ULL_NAV_PART=long` 出图 + `navigation::probe::long` 量「行高不变 / 页头仍 56 / 标题不压动作」。
|
||||
//!
|
||||
//! ## 令牌缺口(G 编号,续 `research/15` §25 的 G13;G16~G20 已被其他家族占用)
|
||||
//!
|
||||
//! - **G22** Tabs 容器高 48(带图标 64)——`LayoutMetrics` 无此档;
|
||||
@@ -42,6 +68,7 @@ pub mod app_bar;
|
||||
pub mod drawer;
|
||||
pub mod geometry;
|
||||
pub mod list_item;
|
||||
pub mod matrix;
|
||||
#[cfg(test)]
|
||||
mod probe;
|
||||
pub mod tabs;
|
||||
|
||||
@@ -35,11 +35,12 @@ use unlucky_design::spacing::Space;
|
||||
|
||||
use crate::app::components::interaction;
|
||||
|
||||
mod long;
|
||||
mod scan;
|
||||
use scan::{rgb, ui, BBox, Rgb, Shot};
|
||||
|
||||
/// `ULL_NAV_DIR` → 截图目录(相对路径按**工作区根**解释:`cargo test` 的 cwd 是 crate 目录)。
|
||||
fn shot_dir() -> Option<PathBuf> {
|
||||
pub(super) fn shot_dir() -> Option<PathBuf> {
|
||||
let raw = std::env::var_os("ULL_NAV_DIR")?;
|
||||
let path = PathBuf::from(raw);
|
||||
if path.is_absolute() || path.exists() {
|
||||
@@ -64,7 +65,7 @@ macro_rules! require_dir {
|
||||
};
|
||||
}
|
||||
|
||||
fn shot(dir: &Path, part: &str, flavor: SchemeFlavor) -> Shot {
|
||||
pub(super) fn shot(dir: &Path, part: &str, flavor: SchemeFlavor) -> Shot {
|
||||
let slug = match flavor {
|
||||
SchemeFlavor::Light => "light",
|
||||
SchemeFlavor::Dark => "dark",
|
||||
@@ -197,6 +198,19 @@ fn tabs_indicator_tracks_the_selected_tab() {
|
||||
);
|
||||
measured.push((bar.width(), ink));
|
||||
}
|
||||
// 6.6:Secondary 的分隔线必须是**一条连续直线**(逐 tab 分段画会在相邻段之间留 1px 亮缝,
|
||||
// 视觉 r1 P2-2 实测 `25..101 | 103..191 | 193..265`)
|
||||
let divider = rgb(ui.color(Role::SurfaceVariant));
|
||||
let segments = shot
|
||||
.blocks(divider)
|
||||
.into_iter()
|
||||
.filter(|b| b.height() <= 2 && b.width() >= 20)
|
||||
.count();
|
||||
assert_eq!(
|
||||
segments, 1,
|
||||
"Secondary 分隔线应是一条直线,实测 {segments} 段(分段画会留缝)"
|
||||
);
|
||||
|
||||
let (short_bar, long_bar) = (measured[0].0, measured[1].0);
|
||||
assert!(
|
||||
long_bar > short_bar + 20,
|
||||
@@ -338,5 +352,37 @@ fn app_bar_is_56_high_in_both_states() {
|
||||
"64 是 A1 AppBarSmallTokens.ContainerHeight;本项目按骨架令牌取 56(D-M79-2)"
|
||||
);
|
||||
|
||||
eprintln!("[navigation::probe] appbar: 高度 {measured:?}");
|
||||
// Q21:动作图标必须在 40×40 命中区正中出现(`Container` 默认 Left/Top,
|
||||
// 只给 `align_y` 会贴左缘——视觉 r1 实测前导墨迹 `38..58`,应 `44..68`)。
|
||||
// 判据:左内缩(bar.left → 前导墨迹左缘)== 右内缩(尾随墨迹右缘 → bar.right),±2px。
|
||||
let bars = shot
|
||||
.blocks(resting)
|
||||
.into_iter()
|
||||
.filter(|b| b.width() > 300 && (54..=58).contains(&b.height()))
|
||||
.collect::<Vec<_>>();
|
||||
let bar = bars[0];
|
||||
let leading_ink = rgb(ui.color(Role::OnSurface));
|
||||
let trailing_ink = rgb(ui.color(Role::OnSurfaceVariant));
|
||||
let leading = shot
|
||||
.band_extent(leading_ink, bar.top, bar.bottom, bar.left, bar.left + 48)
|
||||
.expect("页头左侧应有前导图标墨迹");
|
||||
let trailing = shot
|
||||
.band_extent(
|
||||
trailing_ink,
|
||||
bar.top,
|
||||
bar.bottom,
|
||||
bar.right.saturating_sub(48),
|
||||
bar.right,
|
||||
)
|
||||
.expect("页头右侧应有尾随图标墨迹");
|
||||
let left_inset = leading.0 as i32 - bar.left as i32;
|
||||
let right_inset = bar.right as i32 - trailing.1 as i32;
|
||||
assert!(
|
||||
(left_inset - right_inset).abs() <= 2,
|
||||
"动作图标不在命中区正中:左内缩 {left_inset} / 右内缩 {right_inset}(Q21:`align_x` 漏给会差 8px)"
|
||||
);
|
||||
|
||||
eprintln!(
|
||||
"[navigation::probe] appbar: 高度 {measured:?},图标内缩 左 {left_inset} / 右 {right_inset}"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
//! 长文案的渲染层断言(**D-M79-5**:我们的单行约束是否真的成立)。
|
||||
//!
|
||||
//! 矩阵图全用短文案("Instances" / "Instance details"),`Text` 默认 `Wrapping::Word` 的坑
|
||||
//! 在图上根本看不出来——页面一接真实实例名(`All the Mods 10 - ATM10 - 1.21.1 - NeoForge`)
|
||||
//! 就会折成 2~3 行、顶穿 56 的页头、压住 trailing 动作。所以这里用 `ULL_NAV_PART=long`
|
||||
//! 那张图单独量三件事:
|
||||
//!
|
||||
//! | 断言 | 抓的错误 |
|
||||
//! |---|---|
|
||||
//! | ListItem 三行高仍 44/60/76 | 长文本折行把行撑高 / 被固定行高裁掉 |
|
||||
//! | AppBar 仍 56 高 | 标题折行顶穿页头 |
|
||||
//! | 标题墨迹右缘 < trailing 图标左缘 | 长标题覆盖到动作按钮上 |
|
||||
//!
|
||||
//! 跑法见 `navigation/probe.rs` 的模块文档(`ULL_NAV_DIR=target/m79`,图名 `long-light.png`)。
|
||||
|
||||
use unlucky_design::color::{Role, SchemeFlavor};
|
||||
|
||||
use super::scan::{rgb, ui};
|
||||
use super::{shot, shot_dir};
|
||||
|
||||
#[test]
|
||||
fn long_text_stays_single_line() {
|
||||
let Some(dir) = shot_dir() else {
|
||||
eprintln!(
|
||||
"[navigation::probe::long] SKIPPED:未设置 ULL_NAV_DIR(需要先出 long-light.png)"
|
||||
);
|
||||
return;
|
||||
};
|
||||
let shot = shot(&dir, "long", SchemeFlavor::Light);
|
||||
let ui = ui(SchemeFlavor::Light);
|
||||
|
||||
// ---- ① ListItem:长实例名不折行 → 行高仍是 44 / 60 / 76 ----
|
||||
let selected = rgb(ui.color(Role::SecondaryContainer));
|
||||
let mut rows = shot
|
||||
.blocks(selected)
|
||||
.into_iter()
|
||||
.filter(|b| (238..=242).contains(&b.width()) && b.height() > 30)
|
||||
.collect::<Vec<_>>();
|
||||
rows.sort_by_key(|b| b.top);
|
||||
assert_eq!(
|
||||
rows.len(),
|
||||
3,
|
||||
"长文案块应有 3 条 ListItem(实 {})",
|
||||
rows.len()
|
||||
);
|
||||
let heights = rows.iter().map(|b| b.height()).collect::<Vec<_>>();
|
||||
for (measured, expected) in heights.iter().zip([44usize, 60, 76]) {
|
||||
assert!(
|
||||
(expected - 1..=expected).contains(measured),
|
||||
"长文案把行高撑成 {heights:?}(应为 44/60/76 ±1)——说明折行了"
|
||||
);
|
||||
}
|
||||
|
||||
// ---- ② AppBar:长标题不顶穿页头(仍 56 高)----
|
||||
let bar = shot
|
||||
.blocks(rgb(ui.color(Role::Surface)))
|
||||
.into_iter()
|
||||
.filter(|b| b.width() > 300 && (54..=58).contains(&b.height()))
|
||||
.next()
|
||||
.expect("长文案块应有一条 56 高的页头");
|
||||
assert!(
|
||||
(55..=57).contains(&bar.height()),
|
||||
"长标题把页头撑成 {} 高(应 56)——说明折行了",
|
||||
bar.height()
|
||||
);
|
||||
|
||||
// ---- ③ 标题不许压到 trailing 动作上 ----
|
||||
let title_ink = shot
|
||||
.band_extent(
|
||||
rgb(ui.color(Role::OnSurface)),
|
||||
bar.top,
|
||||
bar.bottom,
|
||||
bar.left + 40,
|
||||
bar.right.saturating_sub(90),
|
||||
)
|
||||
.expect("页头里应有标题墨迹");
|
||||
let trailing = shot
|
||||
.band_extent(
|
||||
rgb(ui.color(Role::OnSurfaceVariant)),
|
||||
bar.top,
|
||||
bar.bottom,
|
||||
bar.right.saturating_sub(90),
|
||||
bar.right,
|
||||
)
|
||||
.expect("页头右侧应有 trailing 图标墨迹");
|
||||
assert!(
|
||||
title_ink.1 + 2 < trailing.0,
|
||||
"长标题墨迹右缘 {} 压到 trailing 图标左缘 {} 上了(应被裁在文本区里)",
|
||||
title_ink.1,
|
||||
trailing.0
|
||||
);
|
||||
|
||||
eprintln!(
|
||||
"[navigation::probe] long: 行高 {heights:?},页头 {} 高,标题右缘 {} < trailing {}",
|
||||
bar.height(),
|
||||
title_ink.1,
|
||||
trailing.0
|
||||
);
|
||||
}
|
||||
@@ -155,6 +155,29 @@ impl Shot {
|
||||
best.map(|(_, pixel)| pixel)
|
||||
}
|
||||
|
||||
/// 矩形里某颜色墨迹的左右范围(量图标/文字位置用)。
|
||||
pub(super) fn band_extent(
|
||||
&self,
|
||||
color: Rgb,
|
||||
y0: usize,
|
||||
y1: usize,
|
||||
x0: usize,
|
||||
x1: usize,
|
||||
) -> Option<(usize, usize)> {
|
||||
let mut span: Option<(usize, usize)> = None;
|
||||
for y in y0..=y1.min(self.height - 1) {
|
||||
for x in x0..=x1.min(self.width - 1) {
|
||||
if matches(self.at(x, y), color) {
|
||||
span = Some(match span {
|
||||
Some((l, r)) => (l.min(x), r.max(x)),
|
||||
None => (x, x),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
span
|
||||
}
|
||||
|
||||
/// 矩形内「与底色不同」的像素的左右范围(量文本墨迹用)。
|
||||
///
|
||||
/// 为什么不按精确文字色找:小字号抗锯齿后几乎没有像素落在**精确**色上(容差 3 时实测
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
//! tab 宽度因此是**内容宽**而不是「行宽 ÷ n」——官方 `ScrollableTabRow` 的口径。
|
||||
//! `TabMinWidth = 90` **本轮不施加**(iced 无 min-width,登记在 G23)。
|
||||
|
||||
use iced::widget::text::LineHeight;
|
||||
use iced::widget::text::{LineHeight, Wrapping};
|
||||
use iced::widget::{button, container, scrollable, space, text, Row, Stack};
|
||||
use iced::{Alignment, Background, Border, Color, Element, Length, Padding, Pixels, Shadow};
|
||||
|
||||
@@ -181,25 +181,26 @@ fn content_color(ui: &Ui, primary: bool, state: Interaction, selected: bool) ->
|
||||
}
|
||||
}
|
||||
|
||||
/// 指示条 / 分隔线的颜色(Primary 只给选中项;Secondary 每项都有)。
|
||||
fn bar_color(ui: &Ui, primary: bool, state: Interaction, selected: bool) -> Option<Color> {
|
||||
if primary {
|
||||
// 禁用项的指示条也降档(否则「点不动却亮着」)
|
||||
if !selected {
|
||||
return None;
|
||||
}
|
||||
if state.is_disabled() {
|
||||
return Some(interaction::disabled_content_over_role(
|
||||
ui,
|
||||
Role::OnSurface,
|
||||
false,
|
||||
));
|
||||
}
|
||||
Some(ui.color(Role::Primary))
|
||||
} else {
|
||||
// 6.6:Secondary 的分隔线 `SurfaceVariant`(不是 OutlineVariant——那是 §22 的分隔线)
|
||||
Some(ui.color(Role::SurfaceVariant))
|
||||
/// Primary 指示条的颜色(只有选中项有;禁用项降档,否则「点不动却亮着」)。
|
||||
fn bar_color(ui: &Ui, state: Interaction, selected: bool) -> Option<Color> {
|
||||
if !selected {
|
||||
return None;
|
||||
}
|
||||
if state.is_disabled() {
|
||||
return Some(interaction::disabled_content_over_role(
|
||||
ui,
|
||||
Role::OnSurface,
|
||||
false,
|
||||
));
|
||||
}
|
||||
Some(ui.color(Role::Primary))
|
||||
}
|
||||
|
||||
/// 6.6:Secondary 的分隔线色 = **`SurfaceVariant`**(不是 `OutlineVariant`——那是 §22 的分隔线)。
|
||||
///
|
||||
/// 色值只在这里出现一次:整行那条线与逐 tab 的实现都从这里取。
|
||||
fn secondary_line_color(ui: &Ui) -> Color {
|
||||
ui.color(Role::SurfaceVariant)
|
||||
}
|
||||
|
||||
/// 一行标签页(自带横向滚动)。
|
||||
@@ -220,7 +221,31 @@ pub fn tabs<'a, M: Clone + 'a>(ui: &'a Ui, tabs: Tabs<'a, M>) -> Element<'a, M>
|
||||
row = row.push(tab_cell(ui, tab, geometry, primary, height));
|
||||
}
|
||||
|
||||
scrollable(row)
|
||||
let body: Element<'a, M> = if primary {
|
||||
row.into()
|
||||
} else {
|
||||
// A1 的 Secondary 分隔线是**整行一条直线**,不是每个 tab 一段:
|
||||
// 逐段画会在相邻段之间留缝(视觉 r1 P2-2)。这里用 `Stack` 把一条 Fill 线压在整行底下,
|
||||
// 底座是 tab 行本身(Shrink → 内容宽),所以线长 = tab 行总宽,横向滚动时跟着走。
|
||||
let line = container(space::vertical())
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(geometry.secondary_bar))
|
||||
.style(move |_theme| container::Style {
|
||||
background: Some(Background::Color(secondary_line_color(ui))),
|
||||
..container::Style::default()
|
||||
});
|
||||
Stack::new()
|
||||
.push(container(row))
|
||||
.push(
|
||||
container(line)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.align_y(Alignment::End),
|
||||
)
|
||||
.into()
|
||||
};
|
||||
|
||||
scrollable(body)
|
||||
.direction(scrollable::Direction::Horizontal(
|
||||
scrollable::Scrollbar::hidden(),
|
||||
))
|
||||
@@ -249,6 +274,8 @@ fn tab_cell<'a, M: Clone + 'a>(
|
||||
let label = text(tab.label)
|
||||
.size(style.size_px)
|
||||
.line_height(LineHeight::Absolute(Pixels(style.line_height_px)))
|
||||
// 单行(容器高固定 48/64):长标签让 tab 变宽 + 横向滚动,不折行(偏离 D-M79-5)
|
||||
.wrapping(Wrapping::None)
|
||||
.font(ui.font_of(style))
|
||||
.color(color);
|
||||
|
||||
@@ -266,6 +293,8 @@ fn tab_cell<'a, M: Clone + 'a>(
|
||||
// 底层:内容自己决定宽度(Shrink),底部留出条的高度 —— 未选中的 Primary tab 也占同样的高
|
||||
let base = container(content)
|
||||
.height(Length::Fixed(height))
|
||||
// Q21:要给居中就两轴都显式给(`Container` 默认 Left/Top)
|
||||
.align_x(Alignment::Center)
|
||||
.align_y(Alignment::Center)
|
||||
.padding(Padding {
|
||||
top: 0.0,
|
||||
@@ -274,8 +303,13 @@ fn tab_cell<'a, M: Clone + 'a>(
|
||||
left: geometry.pad,
|
||||
});
|
||||
|
||||
let bar = bar_color(ui, primary, state, selected);
|
||||
let radius = geometry.bar_radius(primary);
|
||||
// Primary:逐 tab 画条(要跟随该 tab 的宽度);Secondary:整行一条,在 `tabs()` 里画
|
||||
let bar = if primary {
|
||||
bar_color(ui, state, selected)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let radius = geometry.indicator_radius();
|
||||
let mut stack = Stack::new().push(base);
|
||||
if let Some(color) = bar {
|
||||
// 上层:Fill —— `Stack` 先按底层定尺寸,所以这里的 Fill = 这个 tab 的宽(不是整行)
|
||||
|
||||
@@ -7,6 +7,8 @@ use unlucky_design::typography::Platform;
|
||||
|
||||
use crate::theme::Families;
|
||||
|
||||
use super::super::super::interaction;
|
||||
|
||||
fn ui() -> Ui {
|
||||
Ui::with_platform(
|
||||
SchemeFlavor::Light,
|
||||
@@ -62,23 +64,34 @@ fn inactive_states_lift_to_on_surface() {
|
||||
}
|
||||
}
|
||||
|
||||
/// 6.2 / 6.6:Primary 只有选中项有 3px `Primary` 条;Secondary 每项都有 1px `SurfaceVariant`。
|
||||
/// 6.2:Primary 只有选中项有 3px `Primary` 条;禁用项降档。
|
||||
#[test]
|
||||
fn bars_follow_the_variant() {
|
||||
fn primary_bar_only_on_the_selected_tab() {
|
||||
let ui = ui();
|
||||
assert_eq!(bar_color(&ui, true, Interaction::Enabled, false), None);
|
||||
assert_eq!(bar_color(&ui, Interaction::Enabled, false), None);
|
||||
assert_eq!(
|
||||
bar_color(&ui, true, Interaction::Enabled, true),
|
||||
bar_color(&ui, Interaction::Enabled, true),
|
||||
Some(ui.color(Role::Primary))
|
||||
);
|
||||
assert_eq!(
|
||||
bar_color(&ui, false, Interaction::Enabled, false),
|
||||
Some(ui.color(Role::SurfaceVariant)),
|
||||
"6.6:分隔线是 SurfaceVariant"
|
||||
bar_color(&ui, Interaction::Disabled, true),
|
||||
Some(interaction::disabled_content_over_role(
|
||||
&ui,
|
||||
Role::OnSurface,
|
||||
false
|
||||
)),
|
||||
"禁用项「点不动却亮着」是错的"
|
||||
);
|
||||
}
|
||||
|
||||
/// 6.6:Secondary 的分隔线整行一条,颜色 `SurfaceVariant`(不是 §22 的 `OutlineVariant`)。
|
||||
#[test]
|
||||
fn secondary_line_is_surface_variant() {
|
||||
let ui = ui();
|
||||
assert_eq!(secondary_line_color(&ui), ui.color(Role::SurfaceVariant));
|
||||
assert_ne!(
|
||||
bar_color(&ui, false, Interaction::Enabled, false),
|
||||
Some(ui.color(Role::OutlineVariant)),
|
||||
secondary_line_color(&ui),
|
||||
ui.color(Role::OutlineVariant),
|
||||
"OutlineVariant 是 §22 分隔线的角色,Tabs 不用它"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user