Files
Unlucky-Launcher/crates/app-iced/src/components/selection/matrix.rs
T
AskaEth c5765c57e7 fix(selection): Chip 禁用改成修饰器,只清消息、不删图标与选中态(复审 P2-1)
- 旧 API Chip::disabled(label, variant) 强制 leading/trailing/selected = None/false:实测
  assist 78→54、filter·on 74→50(连填充和勾都没了),禁用列里 filter·on 与 filter 逐像素同形
- 新 API Chip::assist(label, msg).leading(k).disabled():只清 on_press,视觉信息全保留
  (research/12b §13 的禁用态是「容器/描边 12%、文字与图标 38%」——只降不删)
- 矩阵里禁用格改用修饰器,图标与选中态一并保留
2026-11-02 06:55:28 +08:00

212 lines
8.2 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",
};
// 禁用**只清消息**(`Chip::disabled()` 是修饰器):图标与选中态必须保留,
// 否则禁用列里 `filter·on` 与 `filter` 会逐像素同形(复审 P2-1)。
let chip = 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),
};
let chip = if disabled { chip.disabled() } else { chip };
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()
}