fix(m206): 「当前默认」改用选中的语汇,不再套禁用配色
Java 组用 `Chip::badge` 表示「哪个 JDK 是默认」,而 badge 走的是「无消息 ⇒ 禁用」 那条路(`OnSurface`@38% 叠 surface):M204 实测字/底只有 1.84:1(浅)/ 2.28:1(深), 成了整页最不可读的一段,而它正是「能不能改默认 Java」这个问题的答案本身。 同一张设置页里相邻的「下载」组表达「选中」用的是 `SecondaryContainer` + `OnSecondaryContainer`(7.21:1)——两个相邻页面对同一个语义用了两套语汇。 改法(不动禁用语义): 1. 新增 `Chip::current(label)` + `paint::current_item`:**与选中 chip 同一套颜色**, 但没有消息、不吃五态(没有可点性就没有反馈)。它不等于「未选中」,也不等于 「禁用」——第三档,判据表写在 `components/selection/chip.rs` 的模块文档里。 2. `Chip::paint(&self, ui)` 成为「哪个档位 → 什么颜色」的唯一入口,`chip()` 用它求 静息色,断言也用它——档位的判据只有一处,「改回旧配色」的变异会被直接抓住。 3. Java 页的动作位抽成 `view::action_chip`,当前默认走 `Chip::current`,其余仍是可点 的 `Chip::filter(.., false, ..)`。 4. 下载组**不改**:它本来就是参照语汇(M201 已验收),改完两页逐字段同色。 断言(`pages/settings/java/tests/marker.rs`,浅深两档都跑): 对比度 ≥4.5:1(实测 7.21/7.21)、与下载组选中 chip 逐字段相等、以及反向断言 「禁用态仍走 38% 字 + 12% 容器且与当前项肉眼可分」。两条变异都在报告里复现过: `action_chip` 换回 `Chip::badge` ⇒ 对比度断言红(只有 1.84:1);`Chip::current` 里 `marker: true` 改 `false` ⇒ 三条全红。 举证图(离屏 `--screenshot`,浅/深 + 改前/改后 + 下载组对照)与实测色值表见 `DevDocs/artifacts/m206/`(gitignore,不入库)。
This commit is contained in:
1 parent
46559910c9
commit
b7a1abb18a
6 files changed
+357
-35
No files matched your search
@@ -14,6 +14,24 @@
|
||||
//! M3 的 chip **没有圆形状态层**:hover / pressed 就是把状态层叠在**自己的容器**上,
|
||||
//! 而容器尺寸 = 命中区尺寸。所以 chip 直接走 `button` 的样式闭包(与按钮家族同一条产线),
|
||||
//! 不需要「48 外壳 + 40 圆」那套。
|
||||
//!
|
||||
//! ## 「选中」与「当前项」:两个档位,一套颜色(M206)
|
||||
//!
|
||||
//! 这两件事**不是**「选中 / 未选中」,而是「可点的选中」与「不可点的当前项」——它们必须长得
|
||||
//! **一样**(同一个视觉语汇),差别只在**有没有反馈**:
|
||||
//!
|
||||
//! | 档位 | 构造器 | 容器 / 文字 | 对比度下限 | 五态反馈 | 什么时候用 |
|
||||
//! |---|---|---|---|---|---|
|
||||
//! | A 可点选中 | [`Chip::filter`] `(label, true, msg)` | `SecondaryContainer` / `OnSecondaryContainer` | **≥ 4.5:1**(WCAG AA 正文;实测 7.21:1 浅 / 7.21:1 深) | 有(hover/pressed 状态层) | 点它**会改变**选中项(下载组的分段单选、预置项开关) |
|
||||
//! | B 不可点当前项 | [`Chip::current`] | **同 A,一个像素都不差** | **≥ 4.5:1**(同 7.21:1) | **无**(没有可点性就没有反馈) | 「这就是当前生效的那个」,再点一次没有动作(Java 组的「默认」) |
|
||||
//! | (不是档位)暂不可用 | [`Chip::badge`] / [`.disabled()`](Chip::disabled) | `OnSurface`@12% / @38% | **不适用**:它是禁用语汇(实测 1.84:1 浅 / 2.28:1 深) | — | 真的点不了(下载进行中把「安装」灰掉、LTS/基线这类标签) |
|
||||
//!
|
||||
//! **判据只有一句**:这个 chip 点下去**会不会改变**它表示的值?
|
||||
//! 会 → A;不会(它已经是当前值)→ B;**根本不能点**(忙/不可用)→ 禁用档。
|
||||
//! 尤其:**不许拿 `badge` 表示「当前默认」**——那是把「不可用」的语汇贴在「答案」上,
|
||||
//! M204 实测出来就是整页最不可读的一段(1.84:1)。这一条不靠调用方记住,`Chip::current`
|
||||
//! 与 `paint::current_item` 的文档 + `pages::settings::java::tests::marker` 的三条断言
|
||||
//! (对比度 / 同族 / 禁用语义)一起把它钉在组件层。
|
||||
|
||||
use iced::widget::{button, container, row, text};
|
||||
use iced::{Alignment, Background, Border, Element, Length, Padding, Shadow};
|
||||
@@ -38,6 +56,8 @@ pub struct Chip<'a, M> {
|
||||
trailing: Option<IconKind>,
|
||||
on_press: Option<M>,
|
||||
state: Option<Interaction>,
|
||||
/// 「当前项」标记([`Chip::current`]):选中配色照画,但**不**降成禁用配色、也不吃五态。
|
||||
marker: bool,
|
||||
}
|
||||
|
||||
impl<'a, M> Chip<'a, M> {
|
||||
@@ -58,8 +78,12 @@ impl<'a, M> Chip<'a, M> {
|
||||
|
||||
/// **纯展示**的 filter chip(选中态、无消息 ⇒ 永远不可交互)。
|
||||
///
|
||||
/// 与 `filter(label, true, msg).disabled()` 等价,但**不要求调用方编一条永远收不到的消息**:
|
||||
/// 本轮 Java 页那颗「默认」标记就是这么用的(`Message::Noop` 已删,别再拿一条真消息当占位)。
|
||||
/// 与 `filter(label, true, msg).disabled()` 等价,但**不要求调用方编一条永远收不到的消息**。
|
||||
///
|
||||
/// ⚠️ **它不是「当前项」**(M206 纠正):`badge` 走的是**禁用**配色(`OnSurface`@38% 叠
|
||||
/// surface,实测 **1.84:1 浅 / 2.28:1 深**),那是「暂不可用」的语汇。要表达「这行就是当前
|
||||
/// 生效的那个」,用 [`Chip::current`]——它和下载组的选中 chip 同一套颜色,7.21:1。
|
||||
/// 仍然用 `badge` 的两处都是**真禁用语义**:下载进行中把「安装」灰掉、以及 LTS/基线/推荐这类标签。
|
||||
pub fn badge(label: &'a str) -> Self {
|
||||
Self {
|
||||
label,
|
||||
@@ -69,6 +93,29 @@ impl<'a, M> Chip<'a, M> {
|
||||
trailing: None,
|
||||
on_press: None,
|
||||
state: None,
|
||||
marker: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// **当前项**标记:`SecondaryContainer` + `OnSecondaryContainer`(与可点的选中 chip
|
||||
/// **同一套配色**),没有消息 ⇒ 也不吃 hover / pressed / 禁用态。
|
||||
///
|
||||
/// 语义:「这一行就是当前生效的那个」。它与 [`Chip::filter`]`(label, true, msg)` 的差别
|
||||
/// **只在有没有动作**——点当前值不会改变任何东西,所以给它一个永远不响的消息是撒谎
|
||||
/// (`Message::Noop` 已删,见 `Chip::badge` 的旧注)。判据表见本文档开头的「选中与当前项」。
|
||||
///
|
||||
/// 为什么不能写成「`filter(.., true, ..)` + `.disabled()`」:`disabled()` 会走禁用配色
|
||||
/// (38% 文字、12% 容器),当前项就又变成不可读了——这正是 M206 要修的那件事。
|
||||
pub fn current(label: &'a str) -> Self {
|
||||
Self {
|
||||
label,
|
||||
variant: ChipVariant::Filter,
|
||||
selected: true,
|
||||
leading: None,
|
||||
trailing: None,
|
||||
on_press: None,
|
||||
state: None,
|
||||
marker: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +139,7 @@ impl<'a, M> Chip<'a, M> {
|
||||
trailing: None,
|
||||
on_press: Some(on_press),
|
||||
state: None,
|
||||
marker: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +160,25 @@ impl<'a, M> Chip<'a, M> {
|
||||
self.state = Some(state);
|
||||
self
|
||||
}
|
||||
|
||||
/// 静息时的外观——**唯一**把「这颗 chip 属于哪个档位」翻成颜色的地方。
|
||||
///
|
||||
/// `chip()` 用它求文字/图标色,测试也用它量对比度:档位的判据(当前项 / 选中 / 禁用)
|
||||
/// 只有这一处,所以「实现改回旧配色」这类变异会被
|
||||
/// `pages::settings::java::tests::marker` 的对比度断言直接抓住。
|
||||
pub fn paint(&self, ui: &Ui) -> paint::ChipPaint {
|
||||
let radius = ChipGeometry::of(ui).radius;
|
||||
if self.marker {
|
||||
return paint::current_item(ui, radius);
|
||||
}
|
||||
// 没有消息 = 禁用(`Chip::badge` 与 `.disabled()` 都走这里)。
|
||||
let state = if self.on_press.is_none() {
|
||||
Interaction::Disabled
|
||||
} else {
|
||||
self.state.unwrap_or(Interaction::Enabled)
|
||||
};
|
||||
paint::chip(ui, self.variant, self.selected, state, radius)
|
||||
}
|
||||
}
|
||||
|
||||
/// 画一个 chip。
|
||||
@@ -119,22 +186,14 @@ pub fn chip<'a, M: Clone + 'a>(ui: &'a Ui, spec: Chip<'a, M>) -> Element<'a, M>
|
||||
let geometry = ChipGeometry::of(ui);
|
||||
let variant = spec.variant;
|
||||
let selected = spec.selected;
|
||||
let disabled = spec.on_press.is_none();
|
||||
let marker = spec.marker;
|
||||
let forced = spec.state;
|
||||
|
||||
// 文字/图标色只看「选中 + 禁用」(A1:Filter/Input 的 label 色在 hover/focus/pressed 不变),
|
||||
// 所以这里静态求值;容器(含状态层)在样式闭包里跟态走。
|
||||
let at_rest = paint::chip(
|
||||
ui,
|
||||
variant,
|
||||
selected,
|
||||
if disabled {
|
||||
Interaction::Disabled
|
||||
} else {
|
||||
forced.unwrap_or(Interaction::Enabled)
|
||||
},
|
||||
geometry.radius,
|
||||
);
|
||||
// 所以这里静态求值;容器(含状态层)在样式闭包里跟态走。档位的判据在 `Chip::paint` 一处
|
||||
// (M206:当前项标记走 `paint::current_item`——**不是**禁用态。它的不可点性与「忙/不可用」
|
||||
// 是两件事:前者是「已经选中的那个」(选中配色),后者是「暂不可用」(38% 文字))。
|
||||
let at_rest = spec.paint(ui);
|
||||
|
||||
let mut content = row![].align_y(Alignment::Center).spacing(geometry.gap);
|
||||
if let Some(kind) = spec.leading {
|
||||
@@ -177,8 +236,13 @@ pub fn chip<'a, M: Clone + 'a>(ui: &'a Ui, spec: Chip<'a, M>) -> Element<'a, M>
|
||||
left: geometry.pad,
|
||||
})
|
||||
.style(move |_theme, status| {
|
||||
let state = forced.unwrap_or_else(|| Interaction::from_status(status));
|
||||
let paint = paint::chip(ui, variant, selected, state, geometry.radius);
|
||||
// 标记不吃五态:没有可点性就没有反馈(`--force-state` 也不该把它画成 hover)。
|
||||
let paint = if marker {
|
||||
paint::current_item(ui, geometry.radius)
|
||||
} else {
|
||||
let state = forced.unwrap_or_else(|| Interaction::from_status(status));
|
||||
paint::chip(ui, variant, selected, state, geometry.radius)
|
||||
};
|
||||
button::Style {
|
||||
background: paint.frame.fill.map(Background::Color),
|
||||
text_color: paint.label,
|
||||
|
||||
@@ -77,3 +77,30 @@ pub fn chip(
|
||||
icon: ui.color(icon_role),
|
||||
}
|
||||
}
|
||||
|
||||
/// 「**当前项**」标记的外观(M206):**就是选中 chip 的那一套**,一个像素都不另起一套。
|
||||
///
|
||||
/// 为什么要一个具名入口而不是让调用点自己拼 `chip(.., Filter, true, Enabled, ..)`:
|
||||
///
|
||||
/// 1. 这个语义(「这行就是当前生效的那个」)此前被画成了 [`super::super::Chip::badge`] 的
|
||||
/// **禁用**配色(`OnSurface`@38% 叠 surface,M204 实测 **1.84:1 浅 / 2.28:1 深**)——那是
|
||||
/// 「暂不可用」的语汇,而当前项不是不可用,它是**已经选中的那个**。修法不是给禁用配色调亮,
|
||||
/// 而是让当前项改用**选中**的语汇;
|
||||
/// 2. 与左侧导航里相邻的「下载」组的选中 chip(`Chip::filter(.., true, ..)`,容器
|
||||
/// `SecondaryContainer`、文字 `OnSecondaryContainer`,实测 **7.21:1 浅 / 7.21:1 深**)
|
||||
/// **同族**:两页表达「选中」只用一套颜色,用户不用在两页之间切换心智模型。身份由
|
||||
/// `pages::settings::java::tests::marker` 的
|
||||
/// `the_current_default_marker_speaks_the_download_groups_language` 钉住——谁把其中一页
|
||||
/// 改成另一套配色,那条立刻红。具体的 8 bit 色值不写在这里:`tests/no_hardcoded_tokens.rs`
|
||||
/// 连注释里的十六进制色值也拦,色值只该在报告与令牌表里出现。
|
||||
///
|
||||
/// 与选中 chip 的唯一差别**不在颜色**:标记没有消息(`on_press == None`),所以它不吃 hover /
|
||||
/// pressed / disabled 五态(见 `Chip::current` 的文档)。对比度下限是 [`crate::components::selection::paint`]
|
||||
/// 同一条正文口径(WCAG AA 4.5:1,`unlucky_design::palette::wcag::AA_TEXT`)。
|
||||
///
|
||||
/// 与选中 chip 的唯一差别**不在颜色**:标记没有消息(`on_press == None`),所以它不吃 hover /
|
||||
/// pressed / disabled 五态(见 `Chip::current` 的文档)。对比度下限是 [`crate::components::selection::paint`]
|
||||
/// 同一条正文口径(WCAG AA 4.5:1,`unlucky_design::palette::wcag::AA_TEXT`)。
|
||||
pub fn current_item(ui: &Ui, radius: f32) -> ChipPaint {
|
||||
chip(ui, ChipVariant::Filter, true, Interaction::Enabled, radius)
|
||||
}
|
||||
@@ -35,7 +35,7 @@ mod radio;
|
||||
mod switch;
|
||||
|
||||
pub use check::{checkbox, checkbox_layer_role};
|
||||
pub use chip::{chip, ChipVariant};
|
||||
pub use chip::{chip, current_item, ChipVariant};
|
||||
pub use radio::{radio, radio_layer_role};
|
||||
pub use switch::{switch, switch_layer};
|
||||
|
||||
|
||||
@@ -299,3 +299,7 @@ mod runtime;
|
||||
|
||||
#[path = "tests/prefs.rs"]
|
||||
mod prefs;
|
||||
|
||||
// M206:「当前默认」那颗标记的可读性(对比度 / 同族 / 禁用语义)断言。
|
||||
#[path = "tests/marker.rs"]
|
||||
mod marker;
|
||||
@@ -0,0 +1,218 @@
|
||||
//! 「当前默认」那颗标记的**可读性**断言(M206)。
|
||||
//!
|
||||
//! 起因是 M204 的视觉复测量出来的两个数:Java 页用 `Chip::badge` 表示「哪个 JDK 是默认」,
|
||||
//! 而 `badge` 走的是**禁用**配色(`OnSurface`@38% 叠 surface)⇒ 字/底只有 **1.84:1(浅)/
|
||||
//! 2.28:1(深)**,成了整页最不可读的一段;而同一张设置页里相邻的「下载」组表达「选中」用的是
|
||||
//! `SecondaryContainer` + `OnSecondaryContainer`(**7.21:1**)。同一个「选中」在两页是两种语汇。
|
||||
//!
|
||||
//! 这里钉三件事(每条都写过「抓得住 / 抓不住」):
|
||||
//!
|
||||
//! | 断言 | 抓得住 | 抓不住 |
|
||||
//! |---|---|---|
|
||||
//! | [`the_current_default_marker_clears_wcag_aa`] | 当前项标记的字/底对比度掉到 4.5:1 以下(例如 `view::action_chip` 改回 `Chip::badge`、或 `Chip::current` 的 `marker` 位丢了) | 字体粗细 / 字号是否够大(`LabelLarge` 是正文口径,本来就得 4.5);真截图上的抗锯齿边缘 |
|
||||
//! | [`the_current_default_marker_speaks_the_download_groups_language`] | 两页的「选中」被拆成两套配色(谁再另起一套立刻红) | 「两页在同一张图上看起来协调」这种整体观感(那要视觉 reviewer 判) |
|
||||
//! | [`the_disabled_chip_still_looks_disabled`] | 有人为了让当前项好看,顺手把 `badge` / `.disabled()` 也接到选中配色上(「忙/不可用」于是看起来可点) | 禁用态够不够灰的美学判断——只钉到令牌:字 38%、容器 12% |
|
||||
//!
|
||||
//! **它们都不碰像素**:颜色是「角色 → 令牌 → 对比度」这条链上的值断言,真图另存
|
||||
//! `DevDocs/artifacts/m206/`(D30 §2.1 三件套)。
|
||||
|
||||
use iced::Color;
|
||||
|
||||
use unlucky_design::color::{Color as TokenColor, Role, SchemeFlavor};
|
||||
use unlucky_design::palette::wcag::AA_TEXT;
|
||||
use unlucky_design::typography::Platform;
|
||||
use unlucky_i18n::Lang;
|
||||
|
||||
use crate::app::components::interaction;
|
||||
use crate::app::components::selection::paint::ChipPaint;
|
||||
use crate::app::components::selection::Chip;
|
||||
use crate::theme::{Families, Ui};
|
||||
|
||||
use super::super::seed;
|
||||
use super::super::state::State;
|
||||
use super::super::view::action_chip;
|
||||
|
||||
const FAMILIES: Families = Families {
|
||||
text: "Segoe UI",
|
||||
display: "Segoe UI",
|
||||
mono: "Consolas",
|
||||
};
|
||||
|
||||
const FLAVORS: [SchemeFlavor; 2] = [SchemeFlavor::Light, SchemeFlavor::Dark];
|
||||
|
||||
fn ui(flavor: SchemeFlavor) -> Ui {
|
||||
Ui::with_platform(flavor, Platform::Windows, FAMILIES)
|
||||
}
|
||||
|
||||
/// iced 的 sRGB 分量 → 令牌色([`crate::theme::to_iced`] 的逆;`to_iced` 是从 8 bit 来的,
|
||||
/// 所以来回一趟无误差)。
|
||||
fn token(color: Color) -> TokenColor {
|
||||
let q = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u8;
|
||||
TokenColor::rgba(q(color.r), q(color.g), q(color.b), q(color.a))
|
||||
}
|
||||
|
||||
/// 一颗 chip 的「字色 vs 底色」。容器为空(透明)时按 surface 算——chip 的透明态本来就压在
|
||||
/// 卡片底上,这条口径与 `wcag::check` 的 `(OnSurfaceVariant, Surface)` 一致。
|
||||
fn ink_ratio(paint: &ChipPaint, ui: &Ui) -> f32 {
|
||||
let background = paint.frame.fill.unwrap_or_else(|| ui.color(Role::Surface));
|
||||
token(paint.label).contrast_ratio(token(background))
|
||||
}
|
||||
|
||||
/// 截图种子里那两行 JDK(`ULL_JAVA_STATE=two` 用的同一份),其中**第二行是当前默认**。
|
||||
fn seeded() -> State {
|
||||
seed::build("two").expect("种子 two 必须存在")
|
||||
}
|
||||
|
||||
// ------------------------------------------------- ① 对比度:当前项标记必须过 AA 正文
|
||||
|
||||
/// 「当前默认」那颗标记的字色与底色 ≥ **4.5:1**(WCAG 2.x AA 正文口径),浅深两档都要过。
|
||||
///
|
||||
/// 改前实测(M204,`Chip::badge` 的禁用配色):**1.84:1 浅 / 2.28:1 深**;改后(本断言钉的):
|
||||
/// **7.21:1 浅 / 7.21:1 深**——与下载组的选中 chip 同一个数,因为就是同一套角色。
|
||||
///
|
||||
/// 变异证明(只改实现侧):把 `view::action_chip` 的 `Chip::current` 换回 `Chip::badge`,
|
||||
/// 这条立刻红(比值掉回 1.84 / 2.28);把 `Chip::current` 里的 `marker: true` 改成 `false`
|
||||
/// 也一样红(它会退回「无消息 ⇒ 禁用」那条路)。
|
||||
#[test]
|
||||
fn the_current_default_marker_clears_wcag_aa() {
|
||||
let catalog = Lang::ZhCn.catalog();
|
||||
let state = seeded();
|
||||
|
||||
for flavor in FLAVORS {
|
||||
let ui = ui(flavor);
|
||||
// 种子里选中的是第二行(`C:\Program Files\Microsoft\jdk-21.0.4`)。
|
||||
let marker_row = state
|
||||
.runtimes
|
||||
.iter()
|
||||
.find(|runtime| super::super::state::same_path(&runtime.binary, &state.selected))
|
||||
.expect("种子里必须有一行是当前默认");
|
||||
let paint = action_chip(catalog, &state, marker_row).paint(&ui);
|
||||
|
||||
// 先量数(这条是任务书点名要的那条):谁把配色改回禁用灰,红在这里。
|
||||
let ratio = ink_ratio(&paint, &ui);
|
||||
assert!(
|
||||
ratio >= AA_TEXT,
|
||||
"{flavor:?}:「当前默认」的字/底只有 {ratio:.2}:1,低于 AA 正文 {AA_TEXT}:1 —— \
|
||||
这正是 M206 要修的那段(改前 1.84 浅 / 2.28 深)"
|
||||
);
|
||||
|
||||
// 再钉角色名:比值恰好过线也可能是碰巧换了个色,这里把「用的是哪两个角色」写死。
|
||||
assert_eq!(
|
||||
paint.frame.fill,
|
||||
Some(ui.color(Role::SecondaryContainer)),
|
||||
"{flavor:?}:当前项标记的容器必须是 SecondaryContainer(与下载组选中 chip 同族),\
|
||||
不是禁用态的 12% 灰"
|
||||
);
|
||||
assert_eq!(
|
||||
paint.label,
|
||||
ui.color(Role::OnSecondaryContainer),
|
||||
"{flavor:?}:当前项标记的字色必须是 OnSecondaryContainer,不是 OnSurface@38%"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------- ② 同族:两页表达「选中」只用一套颜色
|
||||
|
||||
/// Java 的「当前默认」与下载组的选中 chip **必须逐字段同色**。
|
||||
///
|
||||
/// 下载组那一侧(M201 已验收)是 `Chip::filter(option, selected=true, msg)`;这里照原样构造一颗
|
||||
/// ——**不是**照着角色名手抄一遍,所以谁把其中一页换套配色,这条就红。
|
||||
///
|
||||
/// 变异证明:把 `Chip::current` 的容器改成 `PrimaryContainer`(或任何另一个角色),红。
|
||||
#[test]
|
||||
fn the_current_default_marker_speaks_the_download_groups_language() {
|
||||
let catalog = Lang::ZhCn.catalog();
|
||||
let state = seeded();
|
||||
|
||||
for flavor in FLAVORS {
|
||||
let ui = ui(flavor);
|
||||
let marker_row = state
|
||||
.runtimes
|
||||
.iter()
|
||||
.find(|runtime| super::super::state::same_path(&runtime.binary, &state.selected))
|
||||
.expect("种子里必须有一行是当前默认");
|
||||
let marker = action_chip(catalog, &state, marker_row).paint(&ui);
|
||||
|
||||
// 下载组的分段单选:选中的那颗就是 `filter(.., true, ..)`。
|
||||
let download_selected = Chip::<()>::filter("8", true, ());
|
||||
assert_eq!(
|
||||
marker,
|
||||
download_selected.paint(&ui),
|
||||
"{flavor:?}:两页的「选中」必须是同一套视觉语汇(M206)"
|
||||
);
|
||||
|
||||
// 反面:另一行(非默认)是可点的「设为默认」——未选中 filter,且**不是**禁用灰。
|
||||
let other = state
|
||||
.runtimes
|
||||
.iter()
|
||||
.find(|runtime| !super::super::state::same_path(&runtime.binary, &state.selected))
|
||||
.expect("种子里必须有一行不是默认");
|
||||
let actionable = action_chip(catalog, &state, other).paint(&ui);
|
||||
assert_eq!(
|
||||
actionable.label,
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
"{flavor:?}:「设为默认」是可点的未选中 filter(OnSurfaceVariant),不是禁用"
|
||||
);
|
||||
assert_ne!(
|
||||
actionable.frame.fill, marker.frame.fill,
|
||||
"{flavor:?}:可点与当前项必须分得开(一个透明描边 / 一个填充)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------- ③ 反向:禁用态仍然要看起来禁用
|
||||
|
||||
/// 修当前项**不许**顺手把禁用语义改亮:`Chip::badge` / `.disabled()` 仍走
|
||||
/// 「字 `OnSurface`@38% + 容器 `OnSurface`@12%」,且与当前项标记**肉眼可分的两档**。
|
||||
///
|
||||
/// 变异证明:把 `Chip::paint` 里「没消息 = 禁用」那条改成走选中配色(或把 `badge` 改成
|
||||
/// `marker: true`),这条红——那会让「下载进行中,安装按钮灰掉」看起来像可点。
|
||||
#[test]
|
||||
fn the_disabled_chip_still_looks_disabled() {
|
||||
let ui_light = ui(SchemeFlavor::Light);
|
||||
let ui_dark = ui(SchemeFlavor::Dark);
|
||||
|
||||
for ui in [&ui_light, &ui_dark] {
|
||||
let flavor = ui.flavor();
|
||||
// 下载组里「忙 ⇒ 灰掉」的那颗与 Java 的「默认」标记在语义上相反,必须画得不一样。
|
||||
let disabled = Chip::<()>::filter("安装", true, ()).disabled().paint(ui);
|
||||
let marker = Chip::<()>::current("默认").paint(ui);
|
||||
|
||||
let ink38 = interaction::disabled_content_over_role(ui, Role::OnSurface, false);
|
||||
let container12 = interaction::disabled_colors(ui, Role::Surface).0;
|
||||
|
||||
assert_eq!(
|
||||
disabled.label, ink38,
|
||||
"{flavor:?}:禁用字仍是 OnSurface@38%"
|
||||
);
|
||||
assert_eq!(
|
||||
disabled.frame.fill,
|
||||
Some(container12),
|
||||
"{flavor:?}:禁用容器仍是 OnSurface@12%(选中禁用的填充走 38%,见 selection::tests)"
|
||||
);
|
||||
assert_ne!(
|
||||
disabled.label, marker.label,
|
||||
"{flavor:?}:禁用与当前项的文字色不许相同"
|
||||
);
|
||||
assert_ne!(
|
||||
disabled.frame.fill, marker.frame.fill,
|
||||
"{flavor:?}:禁用与当前项的容器色不许相同"
|
||||
);
|
||||
|
||||
// 禁用档**不**承诺 4.5:1(它是「用不了」的语汇,官方 α 就是 38%):这条只记数值,
|
||||
// 不当门槛——否则将来有人真去调亮禁用态时会被一条假门禁拦住。
|
||||
let ratio = ink_ratio(&disabled, ui);
|
||||
assert!(
|
||||
ratio < AA_TEXT,
|
||||
"{flavor:?}:禁用档意外地过 AA 了({ratio:.2}:1)——若是有意调亮,请同步改这条注释与 \
|
||||
`interaction::disabled` 的令牌口径,别让「禁用」和「当前项」两档混成一个"
|
||||
);
|
||||
}
|
||||
|
||||
// `Chip::badge` 仍是禁用档(它就是「没消息 ⇒ 禁用」那条路的产物)。
|
||||
let badge = Chip::<()>::badge("LTS").paint(&ui_light);
|
||||
let disabled = Chip::<()>::filter("LTS", true, ())
|
||||
.disabled()
|
||||
.paint(&ui_light);
|
||||
assert_eq!(badge, disabled, "`Chip::badge` 与 `.disabled()` 必须同色");
|
||||
}
|
||||
@@ -227,23 +227,7 @@ fn runtime_row<'a>(
|
||||
None => (String::new(), ui.color(Role::OnSurfaceVariant)),
|
||||
};
|
||||
|
||||
let action = if is_default {
|
||||
chip(
|
||||
ui,
|
||||
// 已选中那颗是**纯展示**的 badge(没有消息,不可交互):r2 复审要求清掉
|
||||
// 「禁用态 chip 上还挂着一条真消息」的残留。
|
||||
Chip::badge(catalog.text(Key::SettingsJavaDefault)),
|
||||
)
|
||||
} else {
|
||||
chip(
|
||||
ui,
|
||||
Chip::filter(
|
||||
catalog.text(Key::SettingsJavaSetDefault),
|
||||
false,
|
||||
msg(JavaMessage::SelectRuntime(runtime.binary.clone())),
|
||||
),
|
||||
)
|
||||
};
|
||||
let action = chip(ui, action_chip(catalog, state, runtime));
|
||||
|
||||
let mut block = column![row![
|
||||
container(
|
||||
@@ -290,3 +274,28 @@ fn runtime_row<'a>(
|
||||
}
|
||||
block.into()
|
||||
}
|
||||
|
||||
/// 一行右侧的动作位(M206 把它抽成纯函数,好让断言直接对着它量对比度):
|
||||
///
|
||||
/// - **当前默认**那一行 → [`Chip::current`]:与下载组选中 chip **同一套颜色**
|
||||
/// (`SecondaryContainer` + `OnSecondaryContainer`),但没有消息、不吃五态;
|
||||
/// - 其余 → 可点的 [`Chip::filter`]`(…, false, …)`「设为默认」。
|
||||
///
|
||||
/// 为什么不用 `Chip::badge` 表示当前默认:`badge` 走的是**禁用**配色(`OnSurface`@38% 叠 surface,
|
||||
/// M204 实测 **1.84:1 浅 / 2.28:1 深**)。把「暂不可用」的语汇贴在「哪个 JDK 是默认」这个
|
||||
/// **答案本身**上,就是 M206 要修的那件事。档位判据表见 `components::selection::chip` 的模块文档。
|
||||
pub(super) fn action_chip<'a>(
|
||||
catalog: Catalog,
|
||||
state: &State,
|
||||
runtime: &'a RuntimeRow,
|
||||
) -> Chip<'a, Message> {
|
||||
if super::state::same_path(&runtime.binary, &state.selected) {
|
||||
Chip::current(catalog.text(Key::SettingsJavaDefault))
|
||||
} else {
|
||||
Chip::filter(
|
||||
catalog.text(Key::SettingsJavaSetDefault),
|
||||
false,
|
||||
msg(JavaMessage::SelectRuntime(runtime.binary.clone())),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user