Files
Unlucky-Launcher/crates/app-iced/src/components/selection/paint/chip.rs
T
AskaEth 8144714f30 docs(ui): M299 补三处与行为对齐的文档(chip 选中支、tabs 状态层、分组导航的半径实测)
只改注释,零行为变化:cargo test -p unlucky-app-iced --all-targets 全绿(1040 + 6)。
2026-11-13 14:56:20 +08:00

126 lines
5.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Chip 的角色映射(`research/12b` §13.5 / §13.6 / §13.7)。
use unlucky_design::color::Role;
use crate::theme::{Ui, STROKE_PX};
use super::super::super::interaction::{self, Base, Interaction};
use super::{disabled_container, disabled_ink, state_layer, ChipPaint, Frame};
/// Chip 的三种(§13.6 / §13.7):Assist / Filter / Input。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChipVariant {
/// Assist:**永远未选中**,前置图标 `Primary`(`AssistChipTokens.IconColor`)。
Assist,
/// Filter:可选中(`SecondaryContainer`),选中态无描边。
Filter,
/// Input:可选中 + 后置关闭图标。
Input,
}
/// Chip 的外观。
///
/// 与 Checkbox/Radio 的区别:Chip 的**状态层就是它自己的容器**(M3 的 chip 没有圆形状态层),
/// 所以未选中 + hover 的 fill 是「状态层叠在 surface 上」的实色。
///
/// **选中也一样吃状态层**(M299):底是实色 `SecondaryContainer`,hover / pressed 叠在它上面
/// ——走 [`interaction::selected_fill`] 那个唯一入口。M299 之前这一支写死实色,`state` 参数
/// 在选中分支里根本没用上(gallery 的 `chip·filter·on` 一行三态逐像素相同)。
pub fn chip(
ui: &Ui,
base: Base,
variant: ChipVariant,
selected: bool,
state: Interaction,
radius: f32,
) -> ChipPaint {
let selected = selected && !matches!(variant, ChipVariant::Assist);
let label_role = if selected {
Role::OnSecondaryContainer
} else if matches!(variant, ChipVariant::Assist) {
Role::OnSurface
} else {
Role::OnSurfaceVariant
};
if state.is_disabled() {
let ink = disabled_ink(ui, base, Role::OnSurface);
return ChipPaint {
frame: Frame {
fill: selected.then(|| disabled_container(ui, base)),
outline: (!selected).then(|| disabled_container(ui, base)),
stroke: STROKE_PX,
radius,
},
label: ink,
icon: ink,
};
}
let outline = (!selected).then(|| ui.color(Role::OutlineVariant));
let fill = if selected {
// 选中的 chip 也一样吃状态层(M299):M3 的 chip 状态层就是容器自己,
// 选中后底是实色 `SecondaryContainer`,hover / pressed 叠在它上面。
Some(interaction::selected_fill(
ui,
state,
Role::OnSecondaryContainer,
Role::SecondaryContainer,
))
} else {
state_layer(ui, base, state, label_role)
};
// Assist / 选中态的前置图标是 `Primary`(`AssistChipTokens.IconColor` /
// `InputChipTokens.SelectedLeadingIconColor`);其余跟文字同色。
let icon_role = if matches!(variant, ChipVariant::Assist) || selected {
Role::Primary
} else {
label_role
};
ChipPaint {
frame: Frame {
fill,
outline,
stroke: STROKE_PX,
radius,
},
label: ui.color(label_role),
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,
Base::CANVAS,
ChipVariant::Filter,
true,
Interaction::Enabled,
radius,
)
}