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。
This commit is contained in:
1 parent
35345f3399
commit
b69a2cdd7f
12 files changed
+2585
-56
No files matched your search
@@ -0,0 +1,188 @@
|
||||
//! Chip(`research/12b` §13):高 **32** / 圆角 **8** / 水平内边距 **8** / 元素间距 **8** /
|
||||
//! 图标 **16**(官方 18 → **G14**,与按钮家族同一登记)/ 描边 1px `OutlineVariant`。
|
||||
//!
|
||||
//! ## 三类(§13 的 Assist / Filter / Input)
|
||||
//!
|
||||
//! | 类 | 选中态 | 容器 | 文字 | 前置图标 |
|
||||
//! |---|---|---|---|---|
|
||||
//! | Assist | **无**(一次动作,不是开关) | 透明 + 1px 描边 | `OnSurface` | `Primary` |
|
||||
//! | Filter | 有 | 未选透明+描边;选中 `SecondaryContainer` **无描边** | `OnSurfaceVariant` / `OnSecondaryContainer` | 选中态才给勾 |
|
||||
//! | Input | 有 | 同 Filter | 同 Filter | 可给 + 后置关闭图标 |
|
||||
//!
|
||||
//! ## 为什么 Chip 不用 `selection::shell` 那条透明外壳
|
||||
//!
|
||||
//! M3 的 chip **没有圆形状态层**:hover / pressed 就是把状态层叠在**自己的容器**上,
|
||||
//! 而容器尺寸 = 命中区尺寸。所以 chip 直接走 `button` 的样式闭包(与按钮家族同一条产线),
|
||||
//! 不需要「48 外壳 + 40 圆」那套。
|
||||
|
||||
use iced::widget::{button, container, row, text};
|
||||
use iced::{Alignment, Background, Border, Element, Length, Padding, Shadow};
|
||||
|
||||
use unlucky_design::icons::IconKind;
|
||||
use unlucky_design::typography::TypeRole;
|
||||
|
||||
use crate::icons;
|
||||
use crate::theme::Ui;
|
||||
|
||||
use super::super::interaction::Interaction;
|
||||
use super::geometry::ChipGeometry;
|
||||
use super::paint::{self, ChipVariant};
|
||||
|
||||
/// 一个 chip(可带前后图标;`on_press` 为 `None` = 禁用)。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Chip<'a, M> {
|
||||
label: &'a str,
|
||||
variant: ChipVariant,
|
||||
selected: bool,
|
||||
leading: Option<IconKind>,
|
||||
trailing: Option<IconKind>,
|
||||
on_press: Option<M>,
|
||||
state: Option<Interaction>,
|
||||
}
|
||||
|
||||
impl<'a, M> Chip<'a, M> {
|
||||
/// Assist chip:辅助动作,**没有**选中态。
|
||||
pub fn assist(label: &'a str, on_press: M) -> Self {
|
||||
Self::new(label, ChipVariant::Assist, false, on_press)
|
||||
}
|
||||
|
||||
/// Filter chip:筛选开关(`selected` 决定容器是否 `SecondaryContainer`)。
|
||||
pub fn filter(label: &'a str, selected: bool, on_press: M) -> Self {
|
||||
Self::new(label, ChipVariant::Filter, selected, on_press)
|
||||
}
|
||||
|
||||
/// Input chip:用户输入的实体(可选中 + 可带关闭图标)。
|
||||
pub fn input(label: &'a str, on_press: M) -> Self {
|
||||
Self::new(label, ChipVariant::Input, false, on_press)
|
||||
}
|
||||
|
||||
/// 禁用的 chip(无消息)。
|
||||
pub fn disabled(label: &'a str, variant: ChipVariant) -> Self {
|
||||
Self {
|
||||
label,
|
||||
variant,
|
||||
selected: false,
|
||||
leading: None,
|
||||
trailing: None,
|
||||
on_press: None,
|
||||
state: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn new(label: &'a str, variant: ChipVariant, selected: bool, on_press: M) -> Self {
|
||||
Self {
|
||||
label,
|
||||
variant,
|
||||
selected,
|
||||
leading: None,
|
||||
trailing: None,
|
||||
on_press: Some(on_press),
|
||||
state: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 前置图标(Assist / Filter 用)。
|
||||
pub fn leading(mut self, kind: IconKind) -> Self {
|
||||
self.leading = Some(kind);
|
||||
self
|
||||
}
|
||||
|
||||
/// 后置图标(Input chip 的关闭键)。
|
||||
pub fn trailing(mut self, kind: IconKind) -> Self {
|
||||
self.trailing = Some(kind);
|
||||
self
|
||||
}
|
||||
|
||||
/// 强制视觉态(gallery 取证用;运行期不要调)。
|
||||
pub fn state(mut self, state: Interaction) -> Self {
|
||||
self.state = Some(state);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// 画一个 chip。
|
||||
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 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,
|
||||
);
|
||||
|
||||
let mut content = row![].align_y(Alignment::Center).spacing(geometry.gap);
|
||||
if let Some(kind) = spec.leading {
|
||||
// Q9:图标一律经固定尺寸容器
|
||||
content = content.push(super::super::layout::centered_fixed(
|
||||
geometry.icon,
|
||||
geometry.icon,
|
||||
icons::view::<M>(kind, at_rest.icon),
|
||||
));
|
||||
}
|
||||
|
||||
let style = ui.style(TypeRole::LabelLarge);
|
||||
content = content.push(
|
||||
text(spec.label)
|
||||
.size(style.size_px)
|
||||
.font(ui.font_of(ui.emphasized(TypeRole::LabelLarge)))
|
||||
.color(at_rest.label),
|
||||
);
|
||||
|
||||
if let Some(kind) = spec.trailing {
|
||||
content = content.push(super::super::layout::centered_fixed(
|
||||
geometry.icon,
|
||||
geometry.icon,
|
||||
icons::view::<M>(kind, at_rest.icon),
|
||||
));
|
||||
}
|
||||
|
||||
// 垂直居中必须在内容侧做(`button` 把内容放左上角)
|
||||
let body = container(content)
|
||||
.height(Length::Fill)
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
let element = button(body)
|
||||
.height(Length::Fixed(geometry.height))
|
||||
.padding(Padding {
|
||||
top: 0.0,
|
||||
right: geometry.pad,
|
||||
bottom: 0.0,
|
||||
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);
|
||||
button::Style {
|
||||
background: paint.frame.fill.map(Background::Color),
|
||||
text_color: paint.label,
|
||||
border: Border {
|
||||
color: paint.frame.outline.unwrap_or(iced::Color::TRANSPARENT),
|
||||
width: if paint.frame.outline.is_some() {
|
||||
paint.frame.stroke
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
radius: iced::border::Radius::new(paint.frame.radius),
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
}
|
||||
});
|
||||
|
||||
match spec.on_press {
|
||||
Some(message) => element.on_press(message).into(),
|
||||
None => element.into(),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user