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
@@ -35,6 +35,32 @@
|
||||
//! ```
|
||||
//!
|
||||
//! `--screenshot` 不带主题时**默认 Light**(可复现;见 `cli::Options::capture_flavor`)。
|
||||
//!
|
||||
//! ## 段落过滤:环境变量 `ULL_GALLERY_SECTION`(M76 起)
|
||||
//!
|
||||
//! 五段拼成一列、外面套 `scrollable`,而截图只截**首屏** `1180×760`。段数一多,后面的家族
|
||||
//! 就落到折叠线以下(实测:selection 段从 y≈735 起,只剩标题一行)——那条线的
|
||||
//! 「渲染层像素断言」就没有一个像素可量。所以这里加一个**段落选择器**:
|
||||
//!
|
||||
//! ```text
|
||||
//! ULL_GALLERY_SECTION=all # 默认:五段全渲染(行为与加开关之前逐像素一致)
|
||||
//! ULL_GALLERY_SECTION=buttons # 只渲染 buttons 段
|
||||
//! ULL_GALLERY_SECTION=selection # 只渲染 selection 段(本 mission 的取证与 probe 用它)
|
||||
//! ```
|
||||
//!
|
||||
//! 取值:`all` / `buttons` / `divider` / `selection` / `inputs` / `feedback` / `navigation`。
|
||||
//! 语义是「**不构建**没被选中的段」(不是构建后隐藏):`column` 里根本不 push 它,
|
||||
//! 于是该家族的 `view` 一行都不跑。未识别的值**回落 `all`** 并在 stderr 打一行提示
|
||||
//! (静默回落会让人拿错图还以为拍的是那一段)。
|
||||
//!
|
||||
//! 为什么用环境变量而不是 `--screen=gallery:<family>`:`cli.rs` 不归组件线(M82 在改它),
|
||||
//! 而环境变量既能被测试(`cargo test` 里 `std::env::set_var`)又不用跟别人的 CLI 解析抢文件。
|
||||
//! 各家族的 `demo()` 签名**不因此改变**——本文件只决定「push 哪几段」。
|
||||
//!
|
||||
//! 实际渲染了哪几段会打一行 `[gallery] section=... → 渲染 ...`(首帧一次),
|
||||
//! 拿它当出图日志的证据。
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use iced::widget::{column, container, scrollable, text};
|
||||
use iced::{Alignment, Background, Element, Length};
|
||||
@@ -62,6 +88,107 @@ mod probe;
|
||||
mod scan;
|
||||
mod selection;
|
||||
|
||||
/// 段落过滤的环境变量名(语义见模块文档「段落过滤」)。
|
||||
pub const SECTION_ENV: &str = "ULL_GALLERY_SECTION";
|
||||
|
||||
/// 段落开关:**构建**哪几段(`false` = 这一段连 `view` 都不跑)。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct Sections {
|
||||
buttons: bool,
|
||||
divider: bool,
|
||||
selection: bool,
|
||||
inputs: bool,
|
||||
feedback: bool,
|
||||
navigation: bool,
|
||||
}
|
||||
|
||||
impl Sections {
|
||||
/// 五段全渲染(默认)。
|
||||
const ALL: Self = Self {
|
||||
buttons: true,
|
||||
divider: true,
|
||||
selection: true,
|
||||
inputs: true,
|
||||
feedback: true,
|
||||
navigation: true,
|
||||
};
|
||||
|
||||
/// 只有一段。
|
||||
fn only(name: &str) -> Option<Self> {
|
||||
let mut sections = Self {
|
||||
buttons: false,
|
||||
divider: false,
|
||||
selection: false,
|
||||
inputs: false,
|
||||
feedback: false,
|
||||
navigation: false,
|
||||
};
|
||||
match name {
|
||||
"buttons" => sections.buttons = true,
|
||||
"divider" => sections.divider = true,
|
||||
"selection" => sections.selection = true,
|
||||
"inputs" => sections.inputs = true,
|
||||
"feedback" => sections.feedback = true,
|
||||
"navigation" => sections.navigation = true,
|
||||
_ => return None,
|
||||
}
|
||||
Some(sections)
|
||||
}
|
||||
|
||||
/// 读 `ULL_GALLERY_SECTION`。
|
||||
///
|
||||
/// 未设置 / 空 / `all`(大小写不敏感)→ 全渲染;**未识别的值 → 回落全渲染 + stderr 一行**。
|
||||
fn from_env() -> Self {
|
||||
let Ok(raw) = std::env::var(SECTION_ENV) else {
|
||||
return Self::ALL;
|
||||
};
|
||||
let name = raw.trim().to_ascii_lowercase();
|
||||
if name.is_empty() || name == "all" {
|
||||
return Self::ALL;
|
||||
}
|
||||
if let Some(sections) = Self::only(&name) {
|
||||
return sections;
|
||||
}
|
||||
eprintln!(
|
||||
"[gallery] {SECTION_ENV}={raw:?} 不是已知段(all|buttons|divider|selection|inputs|feedback|navigation),已回落 all"
|
||||
);
|
||||
Self::ALL
|
||||
}
|
||||
|
||||
/// 本次渲染的段名(stderr 日志 / 测试断言用)。
|
||||
fn names(self) -> Vec<&'static str> {
|
||||
let mut names = Vec::new();
|
||||
for (on, name) in [
|
||||
(self.buttons, "buttons"),
|
||||
(self.divider, "divider"),
|
||||
(self.selection, "selection"),
|
||||
(self.inputs, "inputs"),
|
||||
(self.feedback, "feedback"),
|
||||
(self.navigation, "navigation"),
|
||||
] {
|
||||
if on {
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
names
|
||||
}
|
||||
}
|
||||
|
||||
/// `[gallery] ... 渲染 ...` 只打一次(`view` 每帧都会跑)。
|
||||
static SECTION_LOGGED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn log_sections(sections: Sections) {
|
||||
if SECTION_LOGGED.swap(true, Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
let raw = std::env::var(SECTION_ENV).unwrap_or_else(|_| "all".to_owned());
|
||||
eprintln!(
|
||||
"[gallery] section={raw} → 渲染 {} 段:{}",
|
||||
sections.names().len(),
|
||||
sections.names().join(", ")
|
||||
);
|
||||
}
|
||||
|
||||
/// gallery 自己的消息(与业务 `Message` 解耦,契约 §2)。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DemoMessage {
|
||||
@@ -100,16 +227,30 @@ pub fn view<'a>(ui: &'a Ui) -> Element<'a, DemoMessage> {
|
||||
|
||||
// 小节之间**不画分隔线**:rubric(15 §22.4)要求同屏同类线 ≤3,
|
||||
// 而验收图里要给分隔线自己留证据(见 `divider_proof`)。分组靠 32px 留白 + 标题。
|
||||
let sections = column![
|
||||
buttons::section(ui),
|
||||
divider_proof(ui),
|
||||
selection::section(ui),
|
||||
inputs::section(ui),
|
||||
feedback::section(ui),
|
||||
navigation::section(ui),
|
||||
]
|
||||
.spacing(ui.space(Space::X8))
|
||||
.width(Length::Fill);
|
||||
//
|
||||
// 段落过滤(`ULL_GALLERY_SECTION`):**没被选中的段根本不 push**,于是该家族的
|
||||
// `view` 一行都不跑——不是「构建后隐藏」。默认 `all` 与加开关之前逐像素一致。
|
||||
let picked = Sections::from_env();
|
||||
log_sections(picked);
|
||||
let mut sections = column![].spacing(ui.space(Space::X8)).width(Length::Fill);
|
||||
if picked.buttons {
|
||||
sections = sections.push(buttons::section(ui));
|
||||
}
|
||||
if picked.divider {
|
||||
sections = sections.push(divider_proof(ui));
|
||||
}
|
||||
if picked.selection {
|
||||
sections = sections.push(selection::section(ui));
|
||||
}
|
||||
if picked.inputs {
|
||||
sections = sections.push(inputs::section(ui));
|
||||
}
|
||||
if picked.feedback {
|
||||
sections = sections.push(feedback::section(ui));
|
||||
}
|
||||
if picked.navigation {
|
||||
sections = sections.push(navigation::section(ui));
|
||||
}
|
||||
|
||||
// 滚动条:默认是主题给的 10px 深灰 thumb,压过组件本体(视觉 reviewer P2-4)。
|
||||
// 这里把它改成 **透明轨 + OutlineVariant thumb**;宽度 10px 是 `scrollable` 的内部常量
|
||||
@@ -184,7 +325,7 @@ fn divider_proof<'a>(ui: &'a Ui) -> Element<'a, DemoMessage> {
|
||||
///
|
||||
/// 五个家族文件共用(`pub(super)`),保证五段的节奏一致——这也是 rubric D4
|
||||
/// 「一屏字号档 ≤4」的落点:小节只有 `TitleMedium` + `BodySmall` 两档。
|
||||
pub(super) fn heading<'a>(ui: &'a Ui, title: &'a str, note: &'a str) -> Element<'a, DemoMessage> {
|
||||
pub(crate) fn heading<'a>(ui: &'a Ui, title: &'a str, note: &'a str) -> Element<'a, DemoMessage> {
|
||||
let note_style = ui.style(TypeRole::BodySmall);
|
||||
column。
|
||||
pub(super) fn state_cells(ui: &Ui) -> Vec<(&'static str, f32)> {
|
||||
pub(crate) fn state_cells(ui: &Ui) -> Vec<(&'static str, f32)> {
|
||||
Interaction::ALL
|
||||
.iter()
|
||||
.map(|state| (state.name(), cell_width(ui)))
|
||||
@@ -215,12 +356,12 @@ pub(super) fn state_cells(ui: &Ui) -> Vec<(&'static str, f32)> {
|
||||
/// 160 = `Space::X8 × 5`:5 列 + 1 个标签列 + 4 个列间距 + 页左右各 24
|
||||
/// = 6×160 + 4×8 + 48 = **1040 < 1180**,所以第 5 列(disabled)不再被窗口右缘裁掉。
|
||||
/// 早先是 192(×6),总宽 1232 撑破窗口,disabled 列被裁掉一半(视觉 reviewer P2-3)。
|
||||
pub(super) fn cell_width(ui: &Ui) -> f32 {
|
||||
pub(crate) fn cell_width(ui: &Ui) -> f32 {
|
||||
ui.space(Space::X8) * 5.0
|
||||
}
|
||||
|
||||
/// 一个矩阵行的左侧标签(变体名)。
|
||||
pub(super) fn row_label<'a>(ui: &'a Ui, name: &'a str) -> Element<'a, DemoMessage> {
|
||||
pub(crate) fn row_label<'a>(ui: &'a Ui, name: &'a str) -> Element<'a, DemoMessage> {
|
||||
let style = ui.style(TypeRole::BodySmall);
|
||||
container(
|
||||
text(name)
|
||||
|
||||
Reference in new issue
Block a user