Files
Unlucky-Launcher/crates/app-iced/src/components/selection/matrix.rs
T
AskaEth b69a2cdd7f feat(app-iced): M76 选择类家族 —— Checkbox / Radio / Switch / Chip(15 §18/§19 + 12b §10/§13)
- selection/:geometry(令牌→像素,缺档登记 G16)/ paint(Interaction×选中×错误→角色)/
  shell(48 透明外壳 + 帧内状态通道 + 勾形 SVG)/ checkbox·radio·switch·chip / matrix(12×5 状态矩阵)/ tests
- gallery/selection.rs:小节 + **渲染层像素断言**(20×20 选中与未选中同尺寸 · Radio 内点 10 ·
  Ø40 状态层是圆 · 勾形 14.8 · 短横 · 轨道 52×32 · Chip 32 · 禁用 38%),按颜色+墨迹块定位、不假设行号
- gallery/mod.rs:新增 ULL_GALLERY_SECTION 段落过滤(tower 批准,M77 零成本复用)+ 四个 helper 放宽到 pub(crate)

A1 证据(本机 AAR javap):未选中 hover/focus/pressed 描边是 OnSurface(不是 OnSurfaceVariant)、
Radio 选中态 hover 仍 Primary、Checkbox SelectedDisabledIconColor=Surface、Switch hover 滑块 PrimaryContainer。
2026-11-02 06:11:21 +08:00

213 lines
8.1 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.
//! 选择类家族的**状态矩阵**(D30:浅/深 × 五态):12 行 × 5 列。
//!
//! ```text
//! | enabled | hovered | pressed | focused | disabled |
//! checkbox·off/on/indet/error · radio·off/on · switch·off/on
//! chip·assist / chip·filter / chip·filter·on / chip·input
//! ```
//!
//! 每格都**强制**成该态(`Checkbox::state(..)` 等取证开关):截图时不能让 60 个控件同时 hover,
//! 而强制值只影响「画什么」,不动任何状态机——所以矩阵图里的五态与运行期五态逐像素同源。
//!
//! 为什么住在 `selection/` 而不是 `gallery/`:`gallery/<family>.rs` 只负责「怎么摆」,本文件负责
//! 「摆什么」,混在一起会顶到 400 行红线(像素断言在 `gallery/selection.rs`)。消息类型是 `()`
//! (矩阵只供视觉验收,点击不产生业务效果),由 gallery 侧一次 `.map(..)` 接成 `DemoMessage`。
use iced::widget::{column, container, row, text};
use iced::{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::super::gallery::{cell_width, heading, row_label, state_cells};
use super::super::interaction::Interaction;
use super::{self as selection, CheckValue, Checkbox, Chip, ChipVariant, Radio, Switch};
/// 整段矩阵(列头 + 12 行);消息类型是 `()`。
pub fn matrix<'a>(ui: &'a Ui) -> Element<'a, ()> {
let mut rows = column![header_row(ui)].spacing(ui.space(Space::X1));
for spec in Row::ALL {
let cells = Interaction::ALL
.iter()
.map(|state| cell(ui, draw(ui, spec, *state)))
.collect::<Vec<_>>();
rows = rows.push(
row![
row_label(ui, spec.name()).map(|_| ()),
row(cells).spacing(ui.space(Space::X2))
]
.spacing(ui.space(Space::X2)),
);
}
rows.into()
}
/// 家族小节的标题 + 判据说明(与矩阵分开,好让 gallery 侧决定顺序)。
pub fn heading_and_note<'a>(ui: &'a Ui) -> Element<'a, ()> {
heading(
ui,
"selection · checkbox / radio / switch / chip",
"15 §18/§19 + 12b §10/§13:checkbox 20×20 圆角 2 描边 2(向内收)勾 12 方头 · \
radio 外圈 20 环线 2 内点 10 · switch 轨道 52×32 滑块 24/16 · chip 高 32 圆角 8 · \
Ø40 状态层 · 命中 48",
)
.map(|_| ())
}
/// 图例(一行说明五态口径)。
pub fn legend<'a>(ui: &'a Ui) -> Element<'a, ()> {
let style = ui.style(TypeRole::BodySmall);
text(
"图例:列 = 五态(focus 与 pressed 同 α 10%,看起来一样是规范要求);行 = 组件×取值。\
复选框第三行是 indeterminate(勾形变成居中短横,不换图标);第四行是错误态。",
)
.size(style.size_px)
.font(ui.font_of(style))
.color(ui.color(Role::OnSurfaceVariant))
.into()
}
/// 矩阵的一行(组件 × 取值)。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Row {
/// 三态复选框;布尔位 = 错误态。
Checkbox(CheckValue, bool),
/// 单选框;布尔位 = 选中。
Radio(bool),
/// 开关;布尔位 = 打开。
Switch(bool),
/// Chip;`(类, 选中)`。
Chip(ChipVariant, bool),
}
impl Row {
/// 行序(矩阵自上而下)。
const ALL: [Row; 12] = [
Row::Checkbox(CheckValue::Unchecked, false),
Row::Checkbox(CheckValue::Checked, false),
Row::Checkbox(CheckValue::Indeterminate, false),
Row::Checkbox(CheckValue::Unchecked, true),
Row::Radio(false),
Row::Radio(true),
Row::Switch(false),
Row::Switch(true),
Row::Chip(ChipVariant::Assist, false),
Row::Chip(ChipVariant::Filter, false),
Row::Chip(ChipVariant::Filter, true),
Row::Chip(ChipVariant::Input, false),
];
/// 左栏标签。
fn name(self) -> &'static str {
match self {
Row::Checkbox(CheckValue::Unchecked, false) => "checkbox·off",
Row::Checkbox(CheckValue::Checked, false) => "checkbox·on",
Row::Checkbox(CheckValue::Indeterminate, false) => "checkbox·indet",
Row::Checkbox(..) => "checkbox·error",
Row::Radio(false) => "radio·off",
Row::Radio(true) => "radio·on",
Row::Switch(false) => "switch·off",
Row::Switch(true) => "switch·on",
Row::Chip(ChipVariant::Assist, _) => "chip·assist",
Row::Chip(ChipVariant::Filter, false) => "chip·filter",
Row::Chip(ChipVariant::Filter, true) => "chip·filter·on",
Row::Chip(..) => "chip·input",
}
}
}
/// 一格的组件(强制成 `state`)。
fn draw(ui: &Ui, spec: Row, state: Interaction) -> Element<'_, ()> {
let disabled = state.is_disabled();
match spec {
// 五态共用同一套装配:只有「有没有消息」随禁用变(语义上的禁用,不是画成禁用)
Row::Checkbox(value, error) => {
let mut checkbox = match (disabled, value) {
(true, value) => Checkbox::disabled("", value),
(false, CheckValue::Indeterminate) => Checkbox::tri("", value, ()),
(false, CheckValue::Checked) => Checkbox::new("", true, ()),
(false, CheckValue::Unchecked) => Checkbox::new("", false, ()),
}
.state(state);
if error {
checkbox = checkbox.error();
}
selection::checkbox(ui, checkbox)
}
Row::Radio(selected) => {
let radio = if disabled {
Radio::disabled("", selected)
} else {
Radio::new("", selected, ())
};
selection::radio(ui, radio.state(state))
}
Row::Switch(value) => {
let switch = if disabled {
Switch::disabled("", value)
} else {
Switch::new("", value, ())
};
selection::switch(ui, switch.state(state))
}
Row::Chip(variant, selected) => {
let label = match variant {
ChipVariant::Assist => "Assist",
ChipVariant::Filter => "Filter",
ChipVariant::Input => "Input",
};
let chip = if disabled {
Chip::disabled(label, variant)
} else {
match variant {
ChipVariant::Assist => Chip::assist(label, ()).leading(IconKind::Download),
// Filter 的勾只在**选中**时给(选中态才需要「已筛选」的强提示)
ChipVariant::Filter if selected => {
Chip::filter(label, true, ()).leading(IconKind::Check)
}
ChipVariant::Filter => Chip::filter(label, false, ()).leading(IconKind::Filter),
// Input chip:前置图标 + 后置关闭键(avatar 位用图标代替)
ChipVariant::Input => Chip::input(label, ())
.leading(IconKind::Folder)
.trailing(IconKind::Close),
}
};
selection::chip(ui, chip.state(state))
}
}
}
/// 列头(五态名)。
fn header_row(ui: &Ui) -> Element<'_, ()> {
let style = ui.style(TypeRole::LabelSmall);
let mut head = row![row_label(ui, "").map(|_| ())].spacing(ui.space(Space::X2));
for (name, width) in state_cells(ui) {
head = head.push(
container(
text(name)
.size(style.size_px)
.font(ui.font_of(style))
.color(ui.color(Role::OnSurfaceVariant)),
)
.width(Length::Fixed(width)),
);
}
head.into()
}
/// 一个矩阵单元格(固定宽,内容左对齐)。
fn cell<'a>(ui: &Ui, content: Element<'a, ()>) -> Element<'a, ()> {
container(content)
.width(Length::Fixed(cell_width(ui)))
.into()
}