fix(app-iced): 三处 Pixel 偏离按角色语义收敛(M248)
P2:Elevated 的容器改由高程令牌推导(Elevation::Level1.surface_role),解析值仍是 SurfaceContainerLow,图上逐像素不变;「比 Surface 高一档」从此只有 elevation.rs 一处定义, 组件里不再手抄角色名。 P3:Outlined 的 1dp 描边 OutlineVariant → Outline。design/color/mod.rs 的对比度纪律里 Outline 才是「非文字元素(目标 3:1)」,OutlineVariant 是纯装饰分隔线;图标按钮那一式本来 就是 Outline,文字按钮此前与它不一致(全站两套描边)。实测 Outline/Surface:Ink 5.08、 Silver 4.24(OutlineVariant 1.97 / 1.62)。 P4:进度轨道(线性 + 环形)SecondaryContainer → SurfaceContainerHighest——轨道是「还剩多少」 的背景尺,强调色只属于指示条。与 A1(LinearProgressIndicatorTokens.TrackColor)的差异在 progress.rs 模块文档与 geometry.rs 的 12.2 行登记,gallery 图注同步改成角色名。 判据:新增 buttons/tests/roles.rs 与进度轨道用例,断言的是「取到的角色」与 Ink / Silver / Scheme::from_seed 三套方案下解析出的颜色,不钉任何 hex;三处各做一次变异自证(改回旧值即红)。 另修 feedback probe 的定位判据:轨道换成中性容器面后,「画布→轨道」的混色轴穿过中性文字, 按「计数」会把图注行数成轨道,改按「连续段」判(bands_by_run);顺带修掉 indeterminate_row 把标题行当主色带的既有问题(用改前的图 + 改前的探针复现过,与本次换色无关)。
This commit is contained in:
1 parent
81710ba38c
commit
8f5dac2ed6
10 files changed
+402
-34
No files matched your search
@@ -12,7 +12,8 @@
|
|||||||
//! | 1.6 | 图标 18 + 图标↔文字 8 | **18** = `IconSize::Button`(AAR `ButtonDefaults.IconSize`);间距 `Space::X2` | [`Geometry`] |
|
//! | 1.6 | 图标 18 + 图标↔文字 8 | **18** = `IconSize::Button`(AAR `ButtonDefaults.IconSize`);间距 `Space::X2` | [`Geometry`] |
|
||||||
//! | 1.7 | 最小宽 58 | **G15**:iced 的 `Length` 无 min-width,不施加(实测见报告) | — |
|
//! | 1.7 | 最小宽 58 | **G15**:iced 的 `Length` 无 min-width,不施加(实测见报告) | — |
|
||||||
//! | 1.8 | 状态层 + 禁用 | hover 8 / focus 10 / pressed 10;容器 **10%**(官方 `DisabledContainerOpacity`;面状控件的 12% 是另一类)& 内容 38%;**内容角色按变体**([`Variant::disabled_content`]) | [`super::interaction`] |
|
//! | 1.8 | 状态层 + 禁用 | hover 8 / focus 10 / pressed 10;容器 **10%**(官方 `DisabledContainerOpacity`;面状控件的 12% 是另一类)& 内容 38%;**内容角色按变体**([`Variant::disabled_content`]) | [`super::interaction`] |
|
||||||
//! | 1.9 | 五式色彩角色 | Filled `Primary`/`OnPrimary`;Tonal `SecondaryContainer`/`OnSecondaryContainer`;Elevated `SurfaceContainerLow`/`Primary`;Outlined 透明 + 1dp `OutlineVariant` + `OnSurfaceVariant`;Text 透明 + `OnSurfaceVariant` | [`Variant::roles`] |
|
//! | 1.9 | 五式色彩角色 | Filled `Primary`/`OnPrimary`;Tonal `SecondaryContainer`/`OnSecondaryContainer`;Elevated = **`Elevation::Level1.surface_role()`**(= `SurfaceContainerLow`)/`Primary`;Outlined 透明 + 1dp `Outline` + `OnSurfaceVariant`;Text 透明 + `OnSurfaceVariant` | [`Variant::roles`] |
|
||||||
|
//! | 1.9b | 与 A1 的登记差异(产品口径,M248) | Outlined 描边 A1 是 `OutlineVariant`,本项目取 **`Outline`**:1dp 边界属于**非文字元素**(目标 3:1,`design/color/mod.rs` 的对比度纪律),`OutlineVariant` 是装饰分隔线的角色;且 `IconVariant::Outlined` 一直是 `Outline`,两族由此统一 | [`Variant::roles`] |
|
||||||
//! | 2.1 | IconButton 容器 | **40×40**、圆角 full | [`IconGeometry`] |
|
//! | 2.1 | IconButton 容器 | **40×40**、圆角 full | [`IconGeometry`] |
|
||||||
//! | 2.2 | IconButton 图标 / 边距 | 图标 **24**、边距 **8** | [`IconGeometry`] |
|
//! | 2.2 | IconButton 图标 / 边距 | 图标 **24**、边距 **8** | [`IconGeometry`] |
|
||||||
//! | 2.4 | Standard 各态**图标色不变** | 只有状态层变化 | [`Variant::roles`] 与态无关 |
|
//! | 2.4 | Standard 各态**图标色不变** | 只有状态层变化 | [`Variant::roles`] 与态无关 |
|
||||||
@@ -77,9 +78,9 @@ pub enum Variant {
|
|||||||
Filled,
|
Filled,
|
||||||
/// 次强调(tonal):`SecondaryContainer`。
|
/// 次强调(tonal):`SecondaryContainer`。
|
||||||
Tonal,
|
Tonal,
|
||||||
/// 带高程:`SurfaceContainerLow` + `Primary` 文本(L1 用色调差,不画阴影)。
|
/// 带高程:L1 的容器面(`Elevation::Level1.surface_role()` = `SurfaceContainerLow`)+ `Primary` 文本。
|
||||||
Elevated,
|
Elevated,
|
||||||
/// 描边:透明容器 + 1dp `OutlineVariant`。
|
/// 描边:透明容器 + 1dp `Outline`(非文字元素角色;装饰分隔线才用 `OutlineVariant`)。
|
||||||
Outlined,
|
Outlined,
|
||||||
/// 纯文本:最低权重。
|
/// 纯文本:最低权重。
|
||||||
Text,
|
Text,
|
||||||
@@ -158,8 +159,12 @@ impl Variant {
|
|||||||
Role::OnSecondaryContainer,
|
Role::OnSecondaryContainer,
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
Variant::Elevated => (Some(Role::SurfaceContainerLow), Role::Primary, None),
|
// 容器面由**高程令牌**推导(L1 → `SurfaceContainerLow`):不许在组件里再抄一遍角色名,
|
||||||
Variant::Outlined => (None, Role::OnSurfaceVariant, Some(Role::OutlineVariant)),
|
// 换层级口径只改 `elevation.rs` 一处(M248:P2 的落地方式是改来源,不是改色值)。
|
||||||
|
Variant::Elevated => (Some(self.elevation().surface_role()), Role::Primary, None),
|
||||||
|
// 1dp 边界取 `Outline`(非文字元素、目标 3:1);`OutlineVariant` 是装饰分隔线的角色,
|
||||||
|
// 拿它画边界在 Ink/Silver 上都到不了 3:1,也和 `IconVariant::Outlined` 不一致(M248:P3)。
|
||||||
|
Variant::Outlined => (None, Role::OnSurfaceVariant, Some(Role::Outline)),
|
||||||
Variant::Text => (None, Role::OnSurfaceVariant, None),
|
Variant::Text => (None, Role::OnSurfaceVariant, None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,12 +275,12 @@ pub fn tonal<'a, M: Clone + 'a>(ui: &'a Ui, button: Button<'a, M>) -> Element<'a
|
|||||||
assemble(ui, Variant::Tonal, button)
|
assemble(ui, Variant::Tonal, button)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Elevated 按钮(`SurfaceContainerLow` + `Primary` 文本,L1 用色调差)。
|
/// Elevated 按钮(L1 的容器面 + `Primary` 文本,用色调差表达高程、不画阴影)。
|
||||||
pub fn elevated<'a, M: Clone + 'a>(ui: &'a Ui, button: Button<'a, M>) -> Element<'a, M> {
|
pub fn elevated<'a, M: Clone + 'a>(ui: &'a Ui, button: Button<'a, M>) -> Element<'a, M> {
|
||||||
assemble(ui, Variant::Elevated, button)
|
assemble(ui, Variant::Elevated, button)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Outlined 按钮(透明 + 1dp `OutlineVariant`)。
|
/// Outlined 按钮(透明 + 1dp `Outline`)。
|
||||||
pub fn outlined<'a, M: Clone + 'a>(ui: &'a Ui, button: Button<'a, M>) -> Element<'a, M> {
|
pub fn outlined<'a, M: Clone + 'a>(ui: &'a Ui, button: Button<'a, M>) -> Element<'a, M> {
|
||||||
assemble(ui, Variant::Outlined, button)
|
assemble(ui, Variant::Outlined, button)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ fn icon_button_geometry_matches_the_reference() {
|
|||||||
|
|
||||||
mod base;
|
mod base;
|
||||||
mod icon;
|
mod icon;
|
||||||
|
mod roles;
|
||||||
|
|
||||||
/// 视觉 reviewer P2-3:同一控件里不许混用填充字形与描边字形(线宽语汇会打架)。
|
/// 视觉 reviewer P2-3:同一控件里不许混用填充字形与描边字形(线宽语汇会打架)。
|
||||||
///
|
///
|
||||||
@@ -188,8 +189,9 @@ fn five_variants_have_distinct_colour_roles() {
|
|||||||
(
|
(
|
||||||
None,
|
None,
|
||||||
unlucky_design::color::Role::OnSurfaceVariant,
|
unlucky_design::color::Role::OnSurfaceVariant,
|
||||||
Some(unlucky_design::color::Role::OutlineVariant)
|
Some(unlucky_design::color::Role::Outline)
|
||||||
)
|
),
|
||||||
|
"描边取 Outline(M248:非文字元素角色;OutlineVariant 是装饰分隔线的)"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Variant::Text.roles(),
|
Variant::Text.roles(),
|
||||||
@@ -275,7 +277,7 @@ fn outlined_style_draws_hairline_border_and_full_radius() {
|
|||||||
assert_eq!(idle.border.radius.top_left, 20.0, "full 圆角");
|
assert_eq!(idle.border.radius.top_left, 20.0, "full 圆角");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
idle.border.color,
|
idle.border.color,
|
||||||
ui.color(unlucky_design::color::Role::OutlineVariant)
|
ui.color(unlucky_design::color::Role::Outline)
|
||||||
);
|
);
|
||||||
assert_eq!(idle.shadow, Shadow::default(), "L0 不许有阴影");
|
assert_eq!(idle.shadow, Shadow::default(), "L0 不许有阴影");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
//! 角色语义 + 「可主题化」断言(M248:Elevated 底 / Outlined 描边)。
|
||||||
|
//!
|
||||||
|
//! **判据不是「等于某个 hex」**:品牌色板本来就会被换(D38 的取色方案会整表接管 49 个角色),
|
||||||
|
//! 钉死色值等于把「配色可自定义」这条路焊上。所以这里钉两件互相补位的事:
|
||||||
|
//!
|
||||||
|
//! 1. [`role_table_is_named_roles_not_colours`]:`Variant::roles()` 吐的是**角色名**;Elevated 的
|
||||||
|
//! 容器还必须由**高程令牌**推导(`Elevation::Level1.surface_role()`),不许在组件里手抄一遍;
|
||||||
|
//! 2. [`styles_follow_the_scheme_across_boards_and_custom_accents`]:同一变体在 Ink / Silver /
|
||||||
|
//! 自定义种子方案(`Scheme::from_seed`)三套方案下,`button::Style` 里解出的颜色**分别等于
|
||||||
|
//! 各自方案的该角色色值**,且三者互不相同。把「取角色」改成「取死值(`Role::Surface`)」
|
||||||
|
//! 或「写死某套方案」都会被这条打红。
|
||||||
|
//!
|
||||||
|
//! 抓不住什么:
|
||||||
|
//! - **令牌表里两个角色同值**时,换角色的变异只有第 1 条会红;
|
||||||
|
//! - **像素**:角色写对了、图上却是别的颜色(样式闭包被别处覆盖)——那是 `gallery/probe` 与出图的事。
|
||||||
|
|
||||||
|
use iced::widget::button;
|
||||||
|
use iced::Background;
|
||||||
|
|
||||||
|
use unlucky_design::color::{BrandBoard, Role, Scheme, SchemeFlavor};
|
||||||
|
use unlucky_design::elevation::Elevation;
|
||||||
|
use unlucky_design::typography::Platform;
|
||||||
|
|
||||||
|
use super::super::{style_closure, IconVariant, Variant};
|
||||||
|
use super::spec_of;
|
||||||
|
use crate::theme::{Ui, STROKE_PX};
|
||||||
|
|
||||||
|
/// 三套配色来源:两块出厂品牌板 + 一套 `Scheme::from_seed` 自定义种子方案。
|
||||||
|
///
|
||||||
|
/// 第三套是关键:品牌板是固化数据,**换不了**;只有「同一份组件代码吃进另一套方案就换色」
|
||||||
|
/// 才叫可自定义(D38 的取色链就是这么接管 49 个角色的)。种子用 design 里的
|
||||||
|
/// [`DEFAULT_SEED`](取色链默认的天蓝)——它是令牌,不是本文件写下的色值字面量
|
||||||
|
/// (`tests/no_hardcoded_tokens.rs` 拦 `#RRGGBB`,这条门禁是对的)。
|
||||||
|
fn themes() -> Vec<(&'static str, Ui)> {
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
"ink",
|
||||||
|
Ui::with_board(BrandBoard::Ink, Platform::Windows, super::FAMILIES),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"silver",
|
||||||
|
Ui::with_board(BrandBoard::Silver, Platform::Windows, super::FAMILIES),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"custom",
|
||||||
|
Ui::from_scheme(
|
||||||
|
Scheme::from_seed(SchemeFlavor::Dark, unlucky_design::color::DEFAULT_SEED),
|
||||||
|
super::FAMILIES,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn role_table_is_named_roles_not_colours() {
|
||||||
|
// ---- P2:Elevated 的容器是**高程令牌的推论**,不是一个手抄的角色名 ----------
|
||||||
|
assert_eq!(Variant::Elevated.elevation(), Elevation::Level1, "L1");
|
||||||
|
assert_eq!(
|
||||||
|
Variant::Elevated.roles().0,
|
||||||
|
Some(Elevation::Level1.surface_role()),
|
||||||
|
"Elevated 的底必须由高程令牌推导(L1 → surface_role = SurfaceContainerLow),\
|
||||||
|
组件里不许再写一遍这个角色名"
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
Elevation::Level1.surface_role(),
|
||||||
|
Role::Surface,
|
||||||
|
"L1 的容器面必须比画布高一档(否则 Elevated 与画布同色)"
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- P3:1dp 边界取 `Outline`;`OutlineVariant` 是装饰分隔线的角色 ----------
|
||||||
|
assert_eq!(
|
||||||
|
Variant::Outlined.roles(),
|
||||||
|
(None, Role::OnSurfaceVariant, Some(Role::Outline)),
|
||||||
|
"Outlined 的 1dp 描边取 Outline(`design/color/mod.rs` 的对比度纪律:\
|
||||||
|
Outline 是「非文字元素」角色、目标 3:1;OutlineVariant 是纯装饰分隔线)"
|
||||||
|
);
|
||||||
|
// 同族一致性:图标按钮那一式一直是 Outline,此前文字按钮是 OutlineVariant ⇒ 全站两套描边
|
||||||
|
assert_eq!(
|
||||||
|
IconVariant::Outlined.roles().2,
|
||||||
|
Some(Role::Outline),
|
||||||
|
"图标按钮 Outlined 与文字按钮 Outlined 必须是同一个描边角色"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 非文字元素的对比度下限 3:1(WCAG 1.4.11;`design/color/mod.rs` 的对比度纪律同一口径)。
|
||||||
|
///
|
||||||
|
/// 这条是 P3 的**理由**而不只是判据:`OutlineVariant` 在深/浅两块板上都到不了 3:1,
|
||||||
|
/// 拿它画按钮边界等于「看得见形状、量不出边界」。别把下限写死成某个角色的实测值——
|
||||||
|
/// 换品牌板或换取色方案时,比例是**算出来的**,不是抄出来的。
|
||||||
|
#[test]
|
||||||
|
fn outlined_boundary_meets_the_non_text_contrast_bar() {
|
||||||
|
for (name, ui) in themes() {
|
||||||
|
let scheme = ui.scheme();
|
||||||
|
let boundary = scheme.contrast(Role::Outline, Role::Surface);
|
||||||
|
assert!(
|
||||||
|
boundary >= 3.0,
|
||||||
|
"{name}: Outline/Surface = {boundary:.2},非文字元素下限是 3:1"
|
||||||
|
);
|
||||||
|
// 报告用:装饰角色差多少(`--nocapture` 可见),不改判据
|
||||||
|
eprintln!(
|
||||||
|
"[{name}] Outline/Surface={boundary:.2} · OutlineVariant/Surface={:.2}",
|
||||||
|
scheme.contrast(Role::OutlineVariant, Role::Surface)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn styles_follow_the_scheme_across_boards_and_custom_accents() {
|
||||||
|
let elevated = spec_of(Variant::Elevated, Some(super::Interaction::Enabled));
|
||||||
|
let outlined = spec_of(Variant::Outlined, Some(super::Interaction::Enabled));
|
||||||
|
|
||||||
|
let mut elevated_fills = Vec::new();
|
||||||
|
let mut outlined_strokes = Vec::new();
|
||||||
|
|
||||||
|
for (name, ui) in themes() {
|
||||||
|
let style = style_closure(&ui, elevated);
|
||||||
|
let idle = style(&ui.theme(), button::Status::Active);
|
||||||
|
let expected = ui.color(Elevation::Level1.surface_role());
|
||||||
|
assert_eq!(
|
||||||
|
idle.background,
|
||||||
|
Some(Background::Color(expected)),
|
||||||
|
"{name}: Elevated 的底 = 本方案的 L1 容器面"
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
idle.background,
|
||||||
|
Some(Background::Color(ui.color(Role::Surface))),
|
||||||
|
"{name}: Elevated 退化成画布色了(取死值 Surface 的变异会打红这条)"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
idle.text_color,
|
||||||
|
ui.color(Role::Primary),
|
||||||
|
"{name}: Elevated 标签 = Primary"
|
||||||
|
);
|
||||||
|
|
||||||
|
let style = style_closure(&ui, outlined);
|
||||||
|
let idle = style(&ui.theme(), button::Status::Active);
|
||||||
|
assert_eq!(
|
||||||
|
idle.border.color,
|
||||||
|
ui.color(Role::Outline),
|
||||||
|
"{name}: Outlined 描边 = Outline"
|
||||||
|
);
|
||||||
|
assert_eq!(idle.border.width, STROKE_PX, "{name}: 描边 1dp");
|
||||||
|
assert_eq!(
|
||||||
|
idle.text_color,
|
||||||
|
ui.color(Role::OnSurfaceVariant),
|
||||||
|
"{name}: Outlined 标签 = OnSurfaceVariant"
|
||||||
|
);
|
||||||
|
|
||||||
|
elevated_fills.push(expected);
|
||||||
|
outlined_strokes.push(idle.border.color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 三套方案两两不同 ⇒「跟着配色来源走」这件事有实据(不是三套恰好同色)
|
||||||
|
for (index, (name, _)) in themes().iter().enumerate() {
|
||||||
|
for other in index + 1..elevated_fills.len() {
|
||||||
|
assert_ne!(
|
||||||
|
elevated_fills[index], elevated_fills[other],
|
||||||
|
"{name} 与第 {other} 套方案的 Elevated 底同色,可主题化没有实据"
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
outlined_strokes[index], outlined_strokes[other],
|
||||||
|
"{name} 与第 {other} 套方案的 Outlined 描边同色"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 基线条:Ink(深)与 Silver(浅)两块**出厂**板必须给出不同的 Elevated 底与 Outlined 描边。
|
||||||
|
///
|
||||||
|
/// 与上一条分开写:上一条的三套方案里有一套是自定义的,这条只钉出厂外观——
|
||||||
|
/// 哪怕有人把 `themes()` 改成只跑自定义方案,出厂这块的回归也还在。
|
||||||
|
#[test]
|
||||||
|
fn ink_and_silver_resolve_the_same_roles_to_different_colours() {
|
||||||
|
let ink = Ui::with_board(BrandBoard::Ink, Platform::Windows, super::FAMILIES);
|
||||||
|
let silver = Ui::with_board(BrandBoard::Silver, Platform::Windows, super::FAMILIES);
|
||||||
|
for role in [
|
||||||
|
Elevation::Level1.surface_role(),
|
||||||
|
Role::Outline,
|
||||||
|
Role::OnSurfaceVariant,
|
||||||
|
] {
|
||||||
|
assert_ne!(
|
||||||
|
ink.color(role),
|
||||||
|
silver.color(role),
|
||||||
|
"{role:?} 在 Ink 与 Silver 上同色,说明它没跟着色板走"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 浅色板的 L1 容器必须比画布暗、深色板必须比画布亮(层级方向不能反)
|
||||||
|
assert!(silver.color(Role::Surface).r > silver.color(Role::SurfaceContainerLow).r);
|
||||||
|
assert!(ink.color(Role::Surface).r < ink.color(Role::SurfaceContainerLow).r);
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
//! | # | 量 | 参考值 | 落点 |
|
//! | # | 量 | 参考值 | 落点 |
|
||||||
//! |---|---|---|---|
|
//! |---|---|---|---|
|
||||||
//! | 12.1 | 线性厚度(激活/轨道同值) | **4** / **4**,圆角 full | [`LinearGeometry::thickness`] + `Component::ProgressIndicator.spec().shape` |
|
//! | 12.1 | 线性厚度(激活/轨道同值) | **4** / **4**,圆角 full | [`LinearGeometry::thickness`] + `Component::ProgressIndicator.spec().shape` |
|
||||||
//! | 12.2 | 线性颜色 | 激活 `Primary`、轨道 `SecondaryContainer` | 组件层取角色 |
|
//! | 12.2 | 线性颜色 | 激活 `Primary`、轨道 **`SurfaceContainerHighest`**(A1 `TrackColor` 是 `SecondaryContainer`;M248 按「轨道是背景尺」改口径,登记差异;旧值留作对照) | 组件层取角色 |
|
||||||
//! | 12.3 | 环形直径 / 厚度 / 环↔轨间隙 | **40 / 4 / 4** | [`CircularGeometry`] |
|
//! | 12.3 | 环形直径 / 厚度 / 环↔轨间隙 | **40 / 4 / 4** | [`CircularGeometry`] |
|
||||||
//! | 12.4 | 不定态节奏 | 线性 **1750ms** 双段;环形 **6000ms**(弧长 0.1→0.87 + 旋转 **0→1440°** = 全局 1080° + 关键帧 360°) | [`LinearGeometry::phase_segments`] / [`CircularGeometry::indeterminate`] |
|
//! | 12.4 | 不定态节奏 | 线性 **1750ms** 双段;环形 **6000ms**(弧长 0.1→0.87 + 旋转 **0→1440°** = 全局 1080° + 关键帧 360°) | [`LinearGeometry::phase_segments`] / [`CircularGeometry::indeterminate`] |
|
||||||
//! | 14.1 | Snackbar 圆角 | 规格卡 **16**(`inkcat::metrics::SNACKBAR`;官方 `CornerExtraSmall` 是 4,M229 换肤按规格卡) | `panel::SnackbarGeometry::radius` |
|
//! | 14.1 | Snackbar 圆角 | 规格卡 **16**(`inkcat::metrics::SNACKBAR`;官方 `CornerExtraSmall` 是 4,M229 换肤按规格卡) | `panel::SnackbarGeometry::radius` |
|
||||||
|
|||||||
@@ -209,6 +209,50 @@ pub(super) fn band_width(shot: &Shot, band: (usize, usize), fill: Color, under:
|
|||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 按「**每行最长连续段** ≥ `min_run`」聚成的竖带。
|
||||||
|
///
|
||||||
|
/// 与 [`bands`] 的区别只在「一行算不算墨」的判法:`bands` 数**覆盖率 ≥50% 的像素个数**,
|
||||||
|
/// 本函数要求这些像素连成一长段。M248 换了进度轨道的角色之后,这个区别变成了分水岭:
|
||||||
|
///
|
||||||
|
/// - 轨道从强调色族(`SecondaryContainer`)改成中性容器面(`SurfaceContainerHighest`)后,
|
||||||
|
/// 「画布 → 轨道」这条混色轴**正好穿过中性文字与中性容器面**:文字像素的投影 `t` 会 clamp 到
|
||||||
|
/// 1.0、残差只剩 <8,于是整行文字都能被数成「轨道墨」(浅色板实测:命中行数从 2 条涨到 5 条,
|
||||||
|
/// 多出来的三条是图注文字,高度 6 / 2 / 6 行);
|
||||||
|
/// - 按**连续段**判就不受影响:一行 12px 的字形边缘凑不出 200px 的连续同色段,而 360px 的轨道
|
||||||
|
/// 一眼就能认出来。
|
||||||
|
///
|
||||||
|
/// 阈值的取值口径:轨道带最窄的整段是 200px 以上(线性进度宿主 320/360 宽,指示条压在轨道上
|
||||||
|
/// 会把轨道切成几段,最长的那段仍 ≥200——不加指示条的确定态那一格是整条 360)。
|
||||||
|
pub(super) fn bands_by_run(
|
||||||
|
shot: &Shot,
|
||||||
|
fill: Color,
|
||||||
|
under: Color,
|
||||||
|
min_run: usize,
|
||||||
|
) -> Vec<(usize, usize)> {
|
||||||
|
let mut found: Vec<(usize, usize)> = Vec::new();
|
||||||
|
let mut current: Option<usize> = None;
|
||||||
|
for y in 0..shot.height {
|
||||||
|
let longest = runs(shot, y, fill, under)
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, len)| len)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
let hit = longest >= min_run;
|
||||||
|
match (hit, current) {
|
||||||
|
(true, None) => current = Some(y),
|
||||||
|
(false, Some(top)) => {
|
||||||
|
found.push((top, y - 1));
|
||||||
|
current = None;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(top) = current {
|
||||||
|
found.push((top, shot.height - 1));
|
||||||
|
}
|
||||||
|
found
|
||||||
|
}
|
||||||
|
|
||||||
/// 某种颜色的**精确色**连通块(只用于「找着一大块实心」这种粗定位)。
|
/// 某种颜色的**精确色**连通块(只用于「找着一大块实心」这种粗定位)。
|
||||||
pub(super) fn blobs(shot: &Shot, color: Color, min_side: usize) -> Vec<BBox> {
|
pub(super) fn blobs(shot: &Shot, color: Color, min_side: usize) -> Vec<BBox> {
|
||||||
let target = rgb(color);
|
let target = rgb(color);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ use unlucky_design::color::{Role, SchemeFlavor};
|
|||||||
use unlucky_design::typography::Platform;
|
use unlucky_design::typography::Platform;
|
||||||
|
|
||||||
use super::geometry::PHASE_055;
|
use super::geometry::PHASE_055;
|
||||||
use super::pixel::{band_width, bands, matches, near, rgb, runs, shot_path, Shot};
|
use super::pixel::{band_width, bands, bands_by_run, matches, near, rgb, runs, shot_path, Shot};
|
||||||
use crate::theme::{Families, Ui};
|
use crate::theme::{Families, Ui};
|
||||||
|
|
||||||
const FAMILIES: Families = Families {
|
const FAMILIES: Families = Families {
|
||||||
@@ -54,6 +54,17 @@ const TRACK3_X0: usize = 688;
|
|||||||
/// 单条轨道宽(gallery 的 `TRACK_HOST`)。
|
/// 单条轨道宽(gallery 的 `TRACK_HOST`)。
|
||||||
const TRACK3_W: usize = 320;
|
const TRACK3_W: usize = 320;
|
||||||
|
|
||||||
|
/// 认定为「轨道行」所需的**连续**同色段长度(见 `pixel::bands_by_run`)。
|
||||||
|
///
|
||||||
|
/// 确定态那一格是整条 360 宽,取 200 留出「指示条压在轨道上把它切段」的余量;
|
||||||
|
/// 文字行的最长连续段只有 20px 上下。
|
||||||
|
const TRACK_MIN_RUN: usize = 200;
|
||||||
|
|
||||||
|
/// 认定为「主色进度行」所需的连续主色段长度。
|
||||||
|
///
|
||||||
|
/// phase 0.55 的两段分别是 ≈24px 与 ≈165px,取 60 既收得住第二段、又挡得住文字行(≤20px)。
|
||||||
|
const INDETERMINATE_MIN_RUN: usize = 60;
|
||||||
|
|
||||||
/// 一条测试覆盖当前截图对应的那一段(段由 `ULL_FEEDBACK_PART` 指定,默认 `all`)。
|
/// 一条测试覆盖当前截图对应的那一段(段由 `ULL_FEEDBACK_PART` 指定,默认 `all`)。
|
||||||
#[test]
|
#[test]
|
||||||
fn feedback_geometry_matches_the_reference() {
|
fn feedback_geometry_matches_the_reference() {
|
||||||
@@ -106,14 +117,22 @@ fn check_linear(shot: &Shot, ui: &Ui) {
|
|||||||
// 参考值写字面量(A1):**不能**从 `LinearGeometry` 读回期望值——那样把 4 改成 6 之后
|
// 参考值写字面量(A1):**不能**从 `LinearGeometry` 读回期望值——那样把 4 改成 6 之后
|
||||||
// 「实测 6 == 期望 6」会照样绿,等于没有断言。
|
// 「实测 6 == 期望 6」会照样绿,等于没有断言。
|
||||||
let surface = ui.color(Role::Surface);
|
let surface = ui.color(Role::Surface);
|
||||||
let track = ui.color(Role::SecondaryContainer);
|
// M248:轨道角色改成容器面(`SurfaceContainerHighest`),参考值仍写**字面角色**——
|
||||||
|
// 不从 `progress::progress_roles()` 读回期望值,否则「把角色改成别的」会两边一起变、照样绿。
|
||||||
|
let track = ui.color(Role::SurfaceContainerHighest);
|
||||||
let active = ui.color(Role::Primary);
|
let active = ui.color(Role::Primary);
|
||||||
|
|
||||||
// ---- 12.1 厚度:轨道带恰好 4 行(宿主 320 宽 ⇒ 每行 ~320 个轨道墨点)--------
|
// ---- 12.1 厚度:轨道带恰好 4 行(宿主 320 宽 ⇒ 每行 ~320 个轨道墨点)--------
|
||||||
let bar_bands = bands(shot, track, surface, 300);
|
//
|
||||||
|
// 用 [`bands_by_run`] 而不是 [`bands`]:M248 把轨道换成中性容器面之后,「画布 → 轨道」
|
||||||
|
// 这条混色轴穿过中性文字,按**计数**判会把图注行也数成轨道(浅色板实测多出 3 条假带:6/2/6 行),
|
||||||
|
// 按**连续段**判就不受影响(详见 `pixel::bands_by_run` 的文档)。
|
||||||
|
// 于是这条断言现在只覆盖「确定态那一条」(360 宽整段);不定态那一条的轨道被指示条切成
|
||||||
|
// 最长 ~88px 的几段,它由下面的 12.4 两段判据覆盖。
|
||||||
|
let bar_bands = bands_by_run(shot, track, surface, TRACK_MIN_RUN);
|
||||||
assert!(
|
assert!(
|
||||||
!bar_bands.is_empty(),
|
!bar_bands.is_empty(),
|
||||||
"没找到线性进度轨道(宿主宽 320,应有 ≥300 个墨点的行)"
|
"没找到线性进度轨道(宿主宽 320,应有 ≥{TRACK_MIN_RUN} 个连续墨点的行)"
|
||||||
);
|
);
|
||||||
for (top, bottom) in &bar_bands {
|
for (top, bottom) in &bar_bands {
|
||||||
let height = bottom - top + 1;
|
let height = bottom - top + 1;
|
||||||
@@ -187,8 +206,13 @@ fn check_linear(shot: &Shot, ui: &Ui) {
|
|||||||
/// 线性不定态那一行的 y:主色墨迹在**第一条轨道**里出现、且不在确定态行的最上一组。
|
/// 线性不定态那一行的 y:主色墨迹在**第一条轨道**里出现、且不在确定态行的最上一组。
|
||||||
///
|
///
|
||||||
/// 不想硬编码坐标(gallery 一改间距就失准),所以按「主色横带 + 排除最高那条带」找。
|
/// 不想硬编码坐标(gallery 一改间距就失准),所以按「主色横带 + 排除最高那条带」找。
|
||||||
|
///
|
||||||
|
/// **按连续段判,不按计数判**(M248 修):标题与图注的行里主色像素能凑到 20 个以上,
|
||||||
|
/// 按旧口径 `bands(.., 20)` 会把标题行(深色板实测 y≈64)当成第一组主色带,`found[1]`
|
||||||
|
/// 于是落在文字上、`runs` 只返回一个 11px 的段 —— 这条在**改前**就红(用改前的图 + 改前的
|
||||||
|
/// 探针复现过,与换轨道颜色无关)。轨道行是 60px 以上的**连续**主色段,文字行不是。
|
||||||
fn indeterminate_row(shot: &Shot, active: iced::Color, surface: iced::Color) -> usize {
|
fn indeterminate_row(shot: &Shot, active: iced::Color, surface: iced::Color) -> usize {
|
||||||
let found = bands(shot, active, surface, 20);
|
let found = bands_by_run(shot, active, surface, INDETERMINATE_MIN_RUN);
|
||||||
assert!(
|
assert!(
|
||||||
found.len() >= 2,
|
found.len() >= 2,
|
||||||
"至少要找到确定态行与不定态行两组主色横带,实测 {found:?}"
|
"至少要找到确定态行与不定态行两组主色横带,实测 {found:?}"
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ fn is_ink(pixel: [u8; 3], fill: Color, under: Color) -> bool {
|
|||||||
/// 环形那一排的判据:逐窗测「有没有一圈 40×40 的墨迹」。
|
/// 环形那一排的判据:逐窗测「有没有一圈 40×40 的墨迹」。
|
||||||
pub(super) fn check(shot: &Shot, ui: &Ui) {
|
pub(super) fn check(shot: &Shot, ui: &Ui) {
|
||||||
let active = ui.color(Role::Primary);
|
let active = ui.color(Role::Primary);
|
||||||
let track = ui.color(Role::SecondaryContainer);
|
// M248:轨道改成容器面角色(与线性同源),参考值写字面角色,不从 `progress_roles()` 读回。
|
||||||
|
let track = ui.color(Role::SurfaceContainerHighest);
|
||||||
let surface = ui.color(Role::Surface);
|
let surface = ui.color(Role::Surface);
|
||||||
|
|
||||||
// 环带的 y:以第一个窗(0% 环,只有轨道色)的**圆环签名**定位 —— 中间那几十行在窗的
|
// 环带的 y:以第一个窗(0% 环,只有轨道色)的**圆环签名**定位 —— 中间那几十行在窗的
|
||||||
|
|||||||
@@ -4,9 +4,16 @@
|
|||||||
//!
|
//!
|
||||||
//! | 形态 | 确定态 | 不定态 |
|
//! | 形态 | 确定态 | 不定态 |
|
||||||
//! |---|---|---|
|
//! |---|---|---|
|
||||||
//! | [`linear`] | 轨道 `SecondaryContainer` + 指示条 `Primary`,厚 **4**、圆角 full | 官方 1750ms **双段**追逐([`phase_segments`]) |
|
//! | [`linear`] | 轨道 `SurfaceContainerHighest` + 指示条 `Primary`,厚 **4**、圆角 full | 官方 1750ms **双段**追逐([`phase_segments`]) |
|
||||||
//! | [`circular`] | 40 环 + 4 线宽,弧长 = 数值 × 360° | 官方 6000ms(弧长 0.1↔0.87 + 旋转 **0→1440°**:全局 1080° 线性 + 关键帧 360°,[`CircularGeometry::indeterminate`]) |
|
//! | [`circular`] | 40 环 + 4 线宽,弧长 = 数值 × 360° | 官方 6000ms(弧长 0.1↔0.87 + 旋转 **0→1440°**:全局 1080° 线性 + 关键帧 360°,[`CircularGeometry::indeterminate`]) |
|
||||||
//!
|
//!
|
||||||
|
//! **轨道角色为什么是容器面(M248,与 A1 的登记差异)**:A1 的
|
||||||
|
//! `LinearProgressIndicatorTokens.TrackColor` 是 `SecondaryContainer`,本项目取
|
||||||
|
//! `SurfaceContainerHighest`。口径是「轨道 = 进度还剩多少的**背景尺**,属于表面层级;
|
||||||
|
//! 强调色只属于指示条」。取角色而不是色值,所以换品牌板 / 换 D38 取色方案它跟着变
|
||||||
|
//! (判据见 `tests/progress.rs` 的 `track_is_the_highest_container_step_and_follows_the_scheme`)。
|
||||||
|
//! 12b §12.2 的旧值仍留在 `geometry.rs` 的参考值表里当对照。
|
||||||
|
//!
|
||||||
//! ## 为什么不定态要调用方给 `phase`
|
//! ## 为什么不定态要调用方给 `phase`
|
||||||
//!
|
//!
|
||||||
//! 契约 §2:组件不持有时钟。`phase`(0..1 的一个周期)由页面按 `Subscription` 推进;
|
//! 契约 §2:组件不持有时钟。`phase`(0..1 的一个周期)由页面按 `Subscription` 推进;
|
||||||
@@ -158,14 +165,36 @@ pub fn portion_shares(items: &[(bool, f32)]) -> Vec<(bool, u16)> {
|
|||||||
shares
|
shares
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 进度家族的取色角色 `(轨道, 指示条)`。
|
||||||
|
///
|
||||||
|
/// **为什么要这个函数**:`Element` 是黑盒、`container::Style` 与 `svg::Style` 都是闭包,
|
||||||
|
/// 两者都不可内省(同 `buttons::icon_style_spec` 的教训:把 `stroke` 写死成 0 时纯函数测试照样绿)。
|
||||||
|
/// 取色点收成这一对角色,单测才能钉住「取的是哪个角色」而不只是「渲染没 panic」。
|
||||||
|
///
|
||||||
|
/// 轨道 = `SurfaceContainerHighest`(比画布高一档以上的容器面,与输入框底同层);指示条 = `Primary`。
|
||||||
|
/// 环形与线性共用同一对:一根进度条两套配色口径就没人说得清哪个才是「进度」。
|
||||||
|
pub(crate) const fn progress_roles() -> (Role, Role) {
|
||||||
|
(Role::SurfaceContainerHighest, Role::Primary)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `linear()` 真正塞进样式闭包的两个颜色(单测断言的就是它,口径同 `StyleSpec`)。
|
||||||
|
pub(crate) fn linear_colors(ui: &Ui) -> (iced::Color, iced::Color) {
|
||||||
|
let (track, active) = progress_roles();
|
||||||
|
(ui.color(track), ui.color(active))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 环形进度的取色(与 [`linear_colors`] 同源)。
|
||||||
|
pub(crate) fn circular_colors(ui: &Ui) -> (iced::Color, iced::Color) {
|
||||||
|
linear_colors(ui)
|
||||||
|
}
|
||||||
|
|
||||||
/// 线性进度条(轨道 + 指示条,厚 4、圆角 full)。
|
/// 线性进度条(轨道 + 指示条,厚 4、圆角 full)。
|
||||||
///
|
///
|
||||||
/// 轨道宽 `Fill`(由宿主决定),指示条按占比分到 `FillPortion` 上——**不需要知道轨道像素宽**,
|
/// 轨道宽 `Fill`(由宿主决定),指示条按占比分到 `FillPortion` 上——**不需要知道轨道像素宽**,
|
||||||
/// 所以 50% 在任何宿主宽度下都精确是半条。
|
/// 所以 50% 在任何宿主宽度下都精确是半条。
|
||||||
pub fn linear<'a, M: 'a>(ui: &'a Ui, progress: Progress) -> Element<'a, M> {
|
pub fn linear<'a, M: 'a>(ui: &'a Ui, progress: Progress) -> Element<'a, M> {
|
||||||
let geometry = LinearGeometry::of(ui);
|
let geometry = LinearGeometry::of(ui);
|
||||||
let track_color = ui.color(Role::SecondaryContainer);
|
let (track_color, active_color) = linear_colors(ui);
|
||||||
let active_color = ui.color(Role::Primary);
|
|
||||||
|
|
||||||
let mut bar = row![]
|
let mut bar = row![]
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
@@ -218,26 +247,19 @@ pub fn circular<'a, M: 'a>(ui: &'a Ui, progress: Progress) -> Element<'a, M> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let track = glyph(
|
let (track_color, active_color) = circular_colors(ui);
|
||||||
&geometry,
|
let track = glyph(&geometry, ring_source(&geometry), track_color);
|
||||||
ring_source(&geometry),
|
|
||||||
ui.color(Role::SecondaryContainer),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 0% 只画轨道(不画弧,也不画圆头小点);100% 画整环(`A` 命令的首尾重合画不出整圆)
|
// 0% 只画轨道(不画弧,也不画圆头小点);100% 画整环(`A` 命令的首尾重合画不出整圆)
|
||||||
let active = if sweep < MIN_SWEEP_DEG {
|
let active = if sweep < MIN_SWEEP_DEG {
|
||||||
None
|
None
|
||||||
} else if sweep >= FULL_SWEEP_DEG {
|
} else if sweep >= FULL_SWEEP_DEG {
|
||||||
Some(glyph(
|
Some(glyph(&geometry, ring_source(&geometry), active_color))
|
||||||
&geometry,
|
|
||||||
ring_source(&geometry),
|
|
||||||
ui.color(Role::Primary),
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
Some(glyph(
|
Some(glyph(
|
||||||
&geometry,
|
&geometry,
|
||||||
arc_source(&geometry, sweep, rotation),
|
arc_source(&geometry, sweep, rotation),
|
||||||
ui.color(Role::Primary),
|
active_color,
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,17 @@
|
|||||||
//! - [`circular_indeterminate_rotation_is_four_turns_and_continuous`]:每周期 4 圈、跨周期不跳变
|
//! - [`circular_indeterminate_rotation_is_four_turns_and_continuous`]:每周期 4 圈、跨周期不跳变
|
||||||
//! (代码 r1 P1-B:旧实现只有 1 圈,且末尾倒跳 270°)。
|
//! (代码 r1 P1-B:旧实现只有 1 圈,且末尾倒跳 270°)。
|
||||||
|
|
||||||
use unlucky_design::color::SchemeFlavor;
|
use unlucky_design::color::{BrandBoard, Role, Scheme, SchemeFlavor};
|
||||||
use unlucky_design::shape::ShapeScale;
|
use unlucky_design::shape::ShapeScale;
|
||||||
|
use unlucky_design::typography::Platform;
|
||||||
|
|
||||||
use super::super::geometry::{phase_segments, CircularGeometry, LinearGeometry, PHASE_055};
|
use super::super::geometry::{phase_segments, CircularGeometry, LinearGeometry, PHASE_055};
|
||||||
use super::super::keyframes::{KeyPoint, Keyframes};
|
use super::super::keyframes::{KeyPoint, Keyframes};
|
||||||
use super::super::progress::{layout_spans, portion_shares, Amount, Progress};
|
use super::super::progress::{
|
||||||
|
circular_colors, layout_spans, linear_colors, portion_shares, progress_roles, Amount, Progress,
|
||||||
|
};
|
||||||
use super::ui;
|
use super::ui;
|
||||||
|
use crate::theme::Ui;
|
||||||
|
|
||||||
// ---- §12 进度 ---------------------------------------------------------------
|
// ---- §12 进度 ---------------------------------------------------------------
|
||||||
|
|
||||||
@@ -279,3 +283,79 @@ fn keyframes_hold_ends_and_wrap() {
|
|||||||
);
|
);
|
||||||
assert_eq!(LINE.sample(f32::NAN), 0.0, "NaN 不 panic,给首值");
|
assert_eq!(LINE.sample(f32::NAN), 0.0, "NaN 不 panic,给首值");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- M248:进度轨道的角色语义与「可主题化」 ---------------------------------
|
||||||
|
|
||||||
|
/// 轨道的配色判据**不是某个 hex**:两块出厂品牌板 + 一套自定义种子方案(`Scheme::from_seed`),轨道都必须解出
|
||||||
|
/// 「本方案的容器面角色」,且三套互不相同。把取角色改成取死值(`Role::Surface`)或写死
|
||||||
|
/// `SecondaryContainer`,这条都会红。
|
||||||
|
///
|
||||||
|
/// 抓不住什么:**像素**(角色对、图上却画错了)——那是 `super::super::probe` 的事;
|
||||||
|
/// 也抓不住「两个角色在令牌表里恰好同值」的情形,那条由这一个用例里的角色 pin 兜。
|
||||||
|
#[test]
|
||||||
|
fn track_is_the_highest_container_step_and_follows_the_scheme() {
|
||||||
|
// 角色 pin:轨道 = 比画布高一档的容器面;指示条 = 强调色。
|
||||||
|
// (A1 `LinearProgressIndicatorTokens.TrackColor` 是 `SecondaryContainer`,M248 按
|
||||||
|
// 「轨道是背景尺、强调色只属于指示条」改口径,差异登记在 `geometry.rs` 的 12.2 行。)
|
||||||
|
assert_eq!(
|
||||||
|
progress_roles(),
|
||||||
|
(Role::SurfaceContainerHighest, Role::Primary),
|
||||||
|
"进度:轨道 SurfaceContainerHighest + 指示条 Primary"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 第三套方案用**取色链的默认种子**(`DEFAULT_SEED`,天蓝):design 的令牌,不是色值字面量。
|
||||||
|
let themes = [
|
||||||
|
(
|
||||||
|
"ink",
|
||||||
|
Ui::with_board(BrandBoard::Ink, Platform::Windows, super::FAMILIES),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"silver",
|
||||||
|
Ui::with_board(BrandBoard::Silver, Platform::Windows, super::FAMILIES),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"custom",
|
||||||
|
Ui::from_scheme(
|
||||||
|
Scheme::from_seed(SchemeFlavor::Dark, unlucky_design::color::DEFAULT_SEED),
|
||||||
|
super::FAMILIES,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut tracks = Vec::new();
|
||||||
|
for (name, theme) in &themes {
|
||||||
|
let (track, active) = linear_colors(theme);
|
||||||
|
assert_eq!(
|
||||||
|
track,
|
||||||
|
theme.color(Role::SurfaceContainerHighest),
|
||||||
|
"{name}: 轨道 = 本方案的最高容器面"
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
track,
|
||||||
|
theme.color(Role::Surface),
|
||||||
|
"{name}: 轨道退化成画布色了(取死值 Surface 的变异会打红这条)"
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
track,
|
||||||
|
theme.color(Role::SecondaryContainer),
|
||||||
|
"{name}: 轨道又借回强调色族了"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
active,
|
||||||
|
theme.color(Role::Primary),
|
||||||
|
"{name}: 指示条 = Primary"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
circular_colors(theme),
|
||||||
|
(track, active),
|
||||||
|
"{name}: 环形取色与线性同源"
|
||||||
|
);
|
||||||
|
tracks.push(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_ne!(tracks[0], tracks[1], "Ink 与 Silver 的轨道必须不同色");
|
||||||
|
assert_ne!(
|
||||||
|
tracks[0], tracks[2],
|
||||||
|
"换一套种子方案,轨道必须跟着变(可主题化的证据)"
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -190,7 +190,7 @@ fn progress_block<'a>(ui: &'a Ui) -> Element<'a, DemoMessage> {
|
|||||||
column![
|
column![
|
||||||
caption(
|
caption(
|
||||||
ui,
|
ui,
|
||||||
"Linear · determinate 0% / 50% / 100%(轨道 SecondaryContainer · 指示条 Primary)"
|
"Linear · determinate 0% / 50% / 100%(轨道 SurfaceContainerHighest · 指示条 Primary)"
|
||||||
),
|
),
|
||||||
determinate,
|
determinate,
|
||||||
caption(
|
caption(
|
||||||
|
|||||||
Reference in new issue
Block a user