feat(app-iced): 禁用态合成底改成容器给的 Base(M234)
`interaction::disabled` 的四个入口一直收一个「底」,但 8 个调用点全传死 `Role::Surface`:卡片(SurfaceContainer)/ 面板(SurfaceContainerLow)里的禁用控件 把 10%(按钮族)/ 12%(面状控件族)叠在画布色上 —— M223「CR 1.05、形丢失」的根因。 - 新增 `interaction::Base`(画布 / 容器底的一等参数,`From<Role>` 兼容历史调用点); 四个入口改成 `(ui, base, …)`,内部不再出现任何写死的 `Role::Surface`。 - 容器导出自己的 `BASE`:`surfaces::elevated::BASE`(卡)、`inputs::menu::PANEL_BASE` (菜单面板,项的 38% 与状态层都改叠它)、`navigation::drawer::BASE`。 - 控件收 `.base(..)`:Button / icon_on / Field / SearchBar / Slider / Checkbox / Radio / Switch / Chip / ListItem / Tabs;Fab 与 icon() 是画布默认值。 - `feedback/action.rs` 的本地 α 合成删掉,改走 `disabled_content_over_role` 并给真实容器底 (Snackbar `InverseSurface` / Dialog `SurfaceContainerHigh`)。 - 判据:`interaction::disabled::tests`(卡片/面板/画布三档逐值)、 `buttons::tests::base`(Button::base → StyleSpec → style_closure 接线)、 `gallery` 新增 `disabledbase` 段 + `gallery::probe::bases` 精确像素断言 (±10 容差区分不了 1/255 的差,所以自己数逐位相等的像素)。 - 变异自证:assemble 的调用点改回画布底后,上面两条都红(见 artifacts/m234/README.md)。 已知未尽:`crates/app-iced/src/pages/**` 不在 scope 内,页面侧「把控件放进卡片/面板」的 调用点尚未接 `.base(..)`(行为与本 mission 之前一致);`pages/…/java/tests/marker.rs` 有一行被迫更新(入口多了一个实参,语义不变 = 画布底)。
This commit is contained in:
1 parent
a9d1cb4c44
commit
35dbefb08d
48 files changed
+1690
-496
No files matched your search
@@ -0,0 +1,212 @@
|
||||
//! 禁用态的**合成底**(M234):同一批禁用控件摆在三种底上,一眼看出「叠的是哪一层」。
|
||||
//!
|
||||
//! ## 这张图回答什么
|
||||
//!
|
||||
//! M223 记的「CR 1.05、形丢失」是这么来的:`disabled.rs` 的入口收一个 `Role` 当合成底,
|
||||
//! 而**所有调用点都传死 `Role::Surface`**——卡片(`SurfaceContainer`)与面板
|
||||
//! (`SurfaceContainerLow`)里的禁用控件于是把 10%(按钮族)/ 12%(面状控件族)那一层叠在
|
||||
//! **画布色**上,与它真正的底差一层。
|
||||
//!
|
||||
//! 这里把三列**并排**摆出来,每列一块板、板底就是控件真踩的那层底:
|
||||
//!
|
||||
//! ```text
|
||||
//! canvas Surface ← 页面底色
|
||||
//! card SurfaceContainer ← `surfaces::elevated::card` 的底(`elevated::BASE`)
|
||||
//! panel SurfaceContainerLow ← Card 层(取景板 / 面板)
|
||||
//! ```
|
||||
//!
|
||||
//! 每列都是**同一族控件**:Filled/Outlined 禁用按钮、Filled 禁用字段、禁用开关与 chip、
|
||||
//! 禁用搜索条、禁用列表项、禁用滑块、禁用 tab。控件拿的都是这一列的 [`Base`],
|
||||
//! 所以列与列之间的差别只有「底」。
|
||||
//!
|
||||
//! ## 判据(数值在 `probe/base.rs`)
|
||||
//!
|
||||
//! 像素判据判的是**精确值**:卡列里的禁用按钮容器必须等于 `on-surface@10% 叠卡片底`,
|
||||
//! 且**不等于**画布底那一档。注意量级:Ink/Silver 板上 `Surface` 与 `SurfaceContainer`
|
||||
//! 只差个位数 RGB,10% 之后差 **≈1/255**——肉眼看不出来,所以这张图的用处是**取证**,
|
||||
//! 不是「看出区别」。真判据在 `interaction::disabled::tests`(逐值)与 `probe/base.rs`(像素)。
|
||||
//!
|
||||
//! ```text
|
||||
//! ULL_GALLERY_SECTION=disabledbase $EXE --software-renderer --screen=gallery \
|
||||
//! --screenshot out.png --screenshot-theme=light
|
||||
//! ULL_GALLERY_SECTION=disabledbase ULL_GALLERY_SHOT=out.png \
|
||||
//! cargo test -p unlucky-app-iced gallery::probe
|
||||
//! ```
|
||||
|
||||
use iced::widget::{column, container, row, text};
|
||||
use iced::{Background, Border, Element, Length, Padding};
|
||||
|
||||
use unlucky_design::color::Role;
|
||||
use unlucky_design::spacing::Space;
|
||||
use unlucky_design::typography::TypeRole;
|
||||
|
||||
use crate::theme::Ui;
|
||||
|
||||
use super::super::buttons::{self, Button};
|
||||
use super::super::inputs::{self, Field, SearchBar, Slider};
|
||||
use super::super::interaction::{self, Base, Interaction};
|
||||
use super::super::navigation::{self, ListItem, Tab, Tabs};
|
||||
use super::super::selection::{self, CheckValue, Checkbox, Chip, Radio, Switch};
|
||||
use super::{heading, DemoMessage};
|
||||
|
||||
/// 一列的底(**板画什么,控件就叠什么**)。
|
||||
const COLUMNS: [(&str, Base); 3] = [
|
||||
("canvas · Surface", Base::CANVAS),
|
||||
(
|
||||
"card · SurfaceContainer",
|
||||
super::super::surfaces::elevated::BASE,
|
||||
),
|
||||
(
|
||||
"panel · SurfaceContainerLow",
|
||||
Base::of(Role::SurfaceContainerLow),
|
||||
),
|
||||
];
|
||||
|
||||
/// 板宽:3 × 360 + 2 × 8 间距 = 1096 < 1180 − 2×24 页边距。
|
||||
const PLATE_WIDTH: f32 = 360.0;
|
||||
|
||||
/// `#RRGGBB`(图上的色值要能直接和 probe 的期望值对上)。
|
||||
fn hex(color: iced::Color) -> String {
|
||||
let channel = |v: f32| (v * 255.0).round() as u8;
|
||||
format!(
|
||||
"#{:02X}{:02X}{:02X}",
|
||||
channel(color.r),
|
||||
channel(color.g),
|
||||
channel(color.b)
|
||||
)
|
||||
}
|
||||
|
||||
/// 整段(标题 + 三块并排的板)。
|
||||
pub(super) fn section<'a>(ui: &'a Ui) -> Element<'a, DemoMessage> {
|
||||
let note = ui.style(TypeRole::BodySmall);
|
||||
let columns = COLUMNS
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|(title, base)| plate(ui, title, base))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
column![
|
||||
heading(
|
||||
ui,
|
||||
"disabled · 合成底 = 所在容器的底(M234)",
|
||||
"10%(按钮族)/ 12%(面状控件族)/ 38%(内容)全部预合成成实色;\
|
||||
三列控件相同,只有**脚下的底**不同",
|
||||
),
|
||||
row(columns).spacing(ui.space(Space::X2)),
|
||||
text(
|
||||
"三列的控件完全相同,只有脚下的底不同。禁用容器保留 90% 的底差(10% 只叠 on-surface),
|
||||
所以列间的差别 = 底差的九成(浅色 Silver 底差 11/255、深色 Ink 底差 6/255)。
|
||||
这张图是取证;判据在 `interaction::disabled::tests`(逐值)与 `gallery::probe` 的 disabledbase 段(精确像素)",
|
||||
)
|
||||
.size(note.size_px)
|
||||
.font(ui.font_of(note))
|
||||
.color(ui.color(Role::OnSurfaceVariant)),
|
||||
]
|
||||
.spacing(ui.space(Space::X2))
|
||||
.into()
|
||||
}
|
||||
|
||||
/// 一块板:**底色就是这一列的 `base`**,控件全部把同一个 `base` 传进 `.base(..)`。
|
||||
///
|
||||
/// 板上写着这一列**算出来的**色值(同一入口:`disabled_button_colors` / `disabled_colors`):
|
||||
/// 三列只差 ≈1/255,肉眼判不了,图上的数字是给 reviewer 与 probe 对账用的。
|
||||
fn plate<'a>(ui: &'a Ui, title: &'a str, base: Base) -> Element<'a, DemoMessage> {
|
||||
let caption = ui.style(TypeRole::BodySmall);
|
||||
let label = text(title)
|
||||
.size(caption.size_px)
|
||||
.font(ui.font_of(caption))
|
||||
.color(ui.color(Role::OnSurface));
|
||||
let numbers = text(format!(
|
||||
"底 {} · 禁用按钮(10%) {} · 禁用字段(12%) {}",
|
||||
hex(ui.color(base.role())),
|
||||
hex(interaction::disabled_button_colors(ui, base).0),
|
||||
hex(interaction::disabled_colors(ui, base).0),
|
||||
))
|
||||
.size(caption.size_px)
|
||||
.font(ui.font_of(caption))
|
||||
.color(ui.color(Role::OnSurfaceVariant));
|
||||
|
||||
container(
|
||||
column![
|
||||
column![label, numbers].spacing(ui.space(Space::X1)),
|
||||
column_body(ui, base)
|
||||
]
|
||||
.spacing(ui.space(Space::X2)),
|
||||
)
|
||||
.width(Length::Fixed(PLATE_WIDTH))
|
||||
.padding(Padding::from(ui.space(Space::X2)))
|
||||
.style(move |_theme| container::Style {
|
||||
// 板底 = 这一列的 base;描边只为让 `canvas` 列也有边界(画布色与页面同色)。
|
||||
background: Some(Background::Color(ui.color(base.role()))),
|
||||
border: Border {
|
||||
color: ui.color(Role::OutlineVariant),
|
||||
width: crate::theme::STROKE_PX,
|
||||
radius: ui.radius_dp(unlucky_design::shape::ShapeScale::Medium),
|
||||
},
|
||||
..container::Style::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// 一列控件:**同族不同底**,每条都显式传 `base`。
|
||||
fn column_body<'a>(ui: &'a Ui, base: Base) -> Element<'a, DemoMessage> {
|
||||
let gap = ui.space(Space::X2);
|
||||
column![
|
||||
row![
|
||||
buttons::filled(ui, Button::disabled("保存").base(base)),
|
||||
buttons::outlined(ui, Button::disabled("取消").base(base)),
|
||||
]
|
||||
.spacing(gap),
|
||||
inputs::text_field(
|
||||
ui,
|
||||
Field::new("")
|
||||
.label("名称")
|
||||
.placeholder("禁用字段")
|
||||
.state(Interaction::Disabled)
|
||||
.base(base),
|
||||
|_| DemoMessage::Pressed("field"),
|
||||
),
|
||||
row![
|
||||
selection::checkbox(
|
||||
ui,
|
||||
Checkbox::disabled("勾选", CheckValue::Unchecked).base(base),
|
||||
),
|
||||
selection::radio(ui, Radio::disabled("单选", false).base(base)),
|
||||
selection::switch(ui, Switch::disabled("开关", false).base(base)),
|
||||
selection::chip(
|
||||
ui,
|
||||
Chip::filter("标签", false, DemoMessage::Pressed("chip"))
|
||||
.disabled()
|
||||
.base(base),
|
||||
),
|
||||
]
|
||||
.spacing(gap),
|
||||
inputs::search_bar(
|
||||
ui,
|
||||
SearchBar::new("")
|
||||
.placeholder("搜索")
|
||||
.state(Interaction::Disabled)
|
||||
.base(base),
|
||||
|_| DemoMessage::Pressed("search"),
|
||||
),
|
||||
navigation::list_item(
|
||||
ui,
|
||||
ListItem::new("列表项", DemoMessage::Pressed("item"))
|
||||
.disabled()
|
||||
.base(base),
|
||||
),
|
||||
inputs::slider(
|
||||
Slider::new(ui, 0.45)
|
||||
.state(Interaction::Disabled)
|
||||
.base(base),
|
||||
),
|
||||
navigation::tabs(
|
||||
ui,
|
||||
Tabs::primary()
|
||||
.tab(Tab::new("标签页", DemoMessage::Pressed("tab")).state(Interaction::Disabled),)
|
||||
.base(base),
|
||||
),
|
||||
]
|
||||
.spacing(gap)
|
||||
.into()
|
||||
}
|
||||
@@ -80,6 +80,7 @@ use super::interaction::Interaction;
|
||||
use super::layout;
|
||||
|
||||
mod buttons;
|
||||
mod disabledbase;
|
||||
mod feedback;
|
||||
mod inputs;
|
||||
mod navigation;
|
||||
@@ -97,6 +98,7 @@ pub const SECTION_ENV: &str = "ULL_GALLERY_SECTION";
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct Sections {
|
||||
buttons: bool,
|
||||
disabledbase: bool,
|
||||
sizes: bool,
|
||||
divider: bool,
|
||||
selection: bool,
|
||||
@@ -109,6 +111,7 @@ impl Sections {
|
||||
/// 七段全渲染(默认)。
|
||||
const ALL: Self = Self {
|
||||
buttons: true,
|
||||
disabledbase: true,
|
||||
sizes: true,
|
||||
divider: true,
|
||||
selection: true,
|
||||
@@ -121,6 +124,7 @@ impl Sections {
|
||||
fn only(name: &str) -> Option<Self> {
|
||||
let mut sections = Self {
|
||||
buttons: false,
|
||||
disabledbase: false,
|
||||
sizes: false,
|
||||
divider: false,
|
||||
selection: false,
|
||||
@@ -130,6 +134,7 @@ impl Sections {
|
||||
};
|
||||
match name {
|
||||
"buttons" => sections.buttons = true,
|
||||
"disabledbase" => sections.disabledbase = true,
|
||||
"sizes" => sections.sizes = true,
|
||||
"divider" => sections.divider = true,
|
||||
"selection" => sections.selection = true,
|
||||
@@ -156,7 +161,7 @@ impl Sections {
|
||||
return sections;
|
||||
}
|
||||
eprintln!(
|
||||
"[gallery] {SECTION_ENV}={raw:?} 不是已知段(all|buttons|sizes|divider|selection|inputs|feedback|navigation),已回落 all"
|
||||
"[gallery] {SECTION_ENV}={raw:?} 不是已知段(all|buttons|disabledbase|sizes|divider|selection|inputs|feedback|navigation),已回落 all"
|
||||
);
|
||||
Self::ALL
|
||||
}
|
||||
@@ -166,6 +171,7 @@ impl Sections {
|
||||
let mut names = Vec::new();
|
||||
for (on, name) in [
|
||||
(self.buttons, "buttons"),
|
||||
(self.disabledbase, "disabledbase"),
|
||||
(self.sizes, "sizes"),
|
||||
(self.divider, "divider"),
|
||||
(self.selection, "selection"),
|
||||
@@ -244,6 +250,9 @@ pub fn view<'a>(ui: &'a Ui) -> Element<'a, DemoMessage> {
|
||||
if picked.buttons {
|
||||
sections = sections.push(buttons::section(ui));
|
||||
}
|
||||
if picked.disabledbase {
|
||||
sections = sections.push(disabledbase::section(ui));
|
||||
}
|
||||
if picked.sizes {
|
||||
sections = sections.push(sizes::section(ui));
|
||||
}
|
||||
|
||||
@@ -36,10 +36,11 @@ use unlucky_design::typography::Platform;
|
||||
|
||||
use crate::theme::{Families, Ui};
|
||||
|
||||
use crate::app::components::interaction;
|
||||
use crate::app::components::interaction::{self, Base};
|
||||
|
||||
use super::scan::{matches, nearer, rgb, Shot};
|
||||
|
||||
mod bases;
|
||||
mod rows;
|
||||
|
||||
const FAMILIES: Families = Families {
|
||||
@@ -146,14 +147,19 @@ fn gallery_geometry_matches_the_tokens() {
|
||||
.trim()
|
||||
.to_ascii_lowercase();
|
||||
assert!(
|
||||
section == "buttons" || section == "divider",
|
||||
"这份 probe 只支持 `ULL_GALLERY_SECTION=buttons`(按钮矩阵)或 `divider`(分隔线);当前 {section:?}。`all` 不行:整页比视口高,分隔线段与按钮矩阵不可能同屏。"
|
||||
section == "buttons" || section == "divider" || section == "disabledbase",
|
||||
"这份 probe 只支持 `ULL_GALLERY_SECTION=buttons`(按钮矩阵)/ `divider`(分隔线)/ `disabledbase`(禁用态合成底);当前 {section:?}。`all` 不行:整页比视口高,分隔线段与按钮矩阵不可能同屏。"
|
||||
);
|
||||
// 分隔线那段是**独立出口**:`divider` 段的 sheet 里没有按钮矩阵,判据也不同。
|
||||
if section == "divider" {
|
||||
check_divider(&shot, &ui);
|
||||
return;
|
||||
}
|
||||
// 禁用态合成底(M234):三块板各按自己的底合成,判据在 `probe/base.rs`。
|
||||
if section == "disabledbase" {
|
||||
bases::check(&shot, &ui, flavor);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- 12b 1.1 / 1.2:实心按钮 40 高、药丸圆角 20 ------------------------------
|
||||
let primary = shot.bbox_of(rgb(ui.color(Role::Primary)));
|
||||
@@ -309,7 +315,7 @@ fn gallery_geometry_matches_the_tokens() {
|
||||
//
|
||||
// **按钮族**:容器 10%(官方 `*ButtonTokens.DisabledContainerOpacity = 0.1f`)、内容 38%。
|
||||
// M86 之前这里和 chip 共用同一个 12% 常量,所以断言与实现「一起错」、永远同源——拆族后才对得上。
|
||||
let (disabled_container, _) = interaction::disabled_button_colors(&ui, Role::Surface);
|
||||
let (disabled_container, _) = interaction::disabled_button_colors(&ui, Base::CANVAS);
|
||||
assert!(
|
||||
shot.bbox_of(rgb(disabled_container))
|
||||
.iter()
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
//! `ULL_GALLERY_SECTION=disabledbase` 的像素判据:**禁用态叠的是哪一层底**(M234)。
|
||||
//!
|
||||
//! ## 为什么必须用「精确匹配」而不是扫描工具的 `matches`(±10)
|
||||
//!
|
||||
//! Ink / Silver 板上 `Surface` 与 `SurfaceContainer` 只差个位数 RGB,10% 之后差 **≈1/255**:
|
||||
//! `scan::matches` 的 ±10 容差会把「叠在画布上」与「叠在卡片上」判成同一个颜色——
|
||||
//! 那样这条判据就永远绿。所以本文件自己数**逐位相等**的像素([`count_exact`]),
|
||||
//! 并且只数**板内**区域,避开页面底色。
|
||||
//!
|
||||
//! ## 量什么
|
||||
//!
|
||||
//! 1. 卡列 / 面板列里:禁用按钮容器(按钮族 10%)与禁用字段容器(面状族 12%)的像素值
|
||||
//! = 各自「@10%/12% 叠**该列板底**」;**不许**出现「叠画布底」那一档;
|
||||
//! 2. 画布列(卡列左侧、同样的纵向范围)里反过来:出现画布档、且不是卡/面板档;
|
||||
//! 3. 三档的期望值两两不同(哪一对相等,说明 `Base` 被丢了或两族 α 合流了)。
|
||||
//!
|
||||
//! ## 抓得住 / 抓不住
|
||||
//!
|
||||
//! - 抓得住:调用点把底传错(叠画布)或没传(默认 `Base::CANVAS`)——卡列里会出现画布档的像素
|
||||
//! 而缺卡片档;两族 α 合流、入口内部写死 `Role::Surface`,同样在这里红。
|
||||
//! - 抓不住:① 容器**自己**的画法(板底 vs 卡组件的 `BASE` 是否一致——那条由
|
||||
//! `surfaces::elevated::tests` 的「卡底必须是 SurfaceContainer」守);
|
||||
//! ② 页面里的真实卡片(pages/ 的调用点尚未接 `.base(..)`,见报告「没验的部分」);
|
||||
//! ③ GPU(wgpu) 档:本判据读的是 tiny-skia(`--software-renderer`) 出的图;
|
||||
//! ④ 差 <1/255 的第三层底(例如 `SurfaceContainer` 与 `SurfaceContainerHigh` 的 10%)——
|
||||
//! 那种情况本文件会退化成「两档相等」,第 3 条断言会红,**不会静默放过**。
|
||||
|
||||
use unlucky_design::color::Role;
|
||||
|
||||
use super::super::scan::{rgb, Shot};
|
||||
use crate::app::components::interaction::{self, Base};
|
||||
use crate::app::components::surfaces;
|
||||
use crate::theme::Ui;
|
||||
|
||||
/// 板子的最小尺寸(判据只认「一整列」,不许把某个控件误当成板)。
|
||||
const MIN_PLATE_W: usize = 300;
|
||||
const MIN_PLATE_H: usize = 260;
|
||||
/// 禁用按钮容器在板内的最少像素(Filled 药丸 40 高 × ≥58 宽,扣掉字形与圆角)。
|
||||
const MIN_BUTTON_PX: usize = 300;
|
||||
/// 禁用字段容器的最少像素(板宽 ~336 × 56,扣掉标签与图标)。
|
||||
const MIN_FIELD_PX: usize = 3000;
|
||||
/// 「正确档位 : 错误档位」的最小倍数。
|
||||
///
|
||||
/// 为什么不干脆要求「错误档位 = 0 px」:**抗锯齿会造出中间色**——板底与控件容器之间那一圈
|
||||
/// 1px 过渡像素会顺路撞上「另一档」的精确值(实测卡片列里 226/226/227 有 58 px,全是边缘)。
|
||||
/// 真实回归的量级完全不同(整颗按钮 ≈3000 px、整块字段 ≈18000 px),所以判据写成**倍数**:
|
||||
/// 正确档位必须比错误档位多一个数量级。
|
||||
const STRAY_RATIO: usize = 10;
|
||||
|
||||
/// 逐位相等的像素计数器(`scan::matches` 的 ±10 容差在这里不够用,见模块文档)。
|
||||
fn count_exact(shot: &Shot, color: [u8; 3], x0: usize, x1: usize, y0: usize, y1: usize) -> usize {
|
||||
(y0..y1.min(shot.height))
|
||||
.map(|y| {
|
||||
(x0..x1.min(shot.width))
|
||||
.filter(|x| shot.at(*x, y) == color)
|
||||
.count()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
/// 一块板(`left..right × top..bottom`,闭区间;`x1`/`y1` 是开区间上界)。
|
||||
type Rect = (usize, usize, usize, usize);
|
||||
|
||||
/// 按**逐位相等**的颜色切出竖列:每列 = 一段连续且够宽的 x,y 取该段内的最小/最大行。
|
||||
///
|
||||
/// 为什么不用 `scan::bbox_of`(±10 容差):本板的三档底(`Surface` / `SurfaceContainer` /
|
||||
/// `SurfaceContainerLow`)两两只差 5~11 个 RGB 单位,落在容差里会被并成一块
|
||||
/// (实测把卡片列与面板列判成同一块)。
|
||||
fn exact_columns(shot: &Shot, color: [u8; 3]) -> Vec<Rect> {
|
||||
let mut columns = Vec::new();
|
||||
let mut current: Option<(usize, usize, usize, usize)> = None;
|
||||
for x in 0..shot.width {
|
||||
let rows: Vec<usize> = (0..shot.height)
|
||||
.filter(|y| shot.at(x, *y) == color)
|
||||
.collect();
|
||||
match (&mut current, rows.first()) {
|
||||
(None, Some(top)) => {
|
||||
current = Some((x, x + 1, *top, rows[rows.len() - 1] + 1));
|
||||
}
|
||||
(Some(col), Some(top)) => {
|
||||
col.1 = x + 1;
|
||||
col.2 = col.2.min(*top);
|
||||
col.3 = col.3.max(rows[rows.len() - 1] + 1);
|
||||
}
|
||||
(Some(col), None) => {
|
||||
if col.1 - col.0 >= MIN_PLATE_W && col.3 - col.2 >= MIN_PLATE_H {
|
||||
columns.push(*col);
|
||||
}
|
||||
current = None;
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
}
|
||||
if let Some(col) = current {
|
||||
if col.1 - col.0 >= MIN_PLATE_W && col.3 - col.2 >= MIN_PLATE_H {
|
||||
columns.push(col);
|
||||
}
|
||||
}
|
||||
columns
|
||||
}
|
||||
|
||||
fn find_plate(shot: &Shot, role: Role, ui: &Ui, name: &str) -> Rect {
|
||||
let color = rgb(ui.color(role));
|
||||
let mut columns = exact_columns(shot, color);
|
||||
assert_eq!(
|
||||
columns.len(),
|
||||
1,
|
||||
"{name} 列({role:?} 底,{color:?})应当恰好是 1 列,实测 {}: 板子尺寸判据 MIN {MIN_PLATE_W}×{MIN_PLATE_H}",
|
||||
columns.len()
|
||||
);
|
||||
columns.remove(0)
|
||||
}
|
||||
|
||||
/// 判据入口(`probe.rs` 的 `disabledbase` 分支调它)。
|
||||
pub(super) fn check(shot: &Shot, ui: &Ui, flavor: unlucky_design::color::SchemeFlavor) {
|
||||
let card = Base::of(Role::SurfaceContainer);
|
||||
let panel = Base::of(Role::SurfaceContainerLow);
|
||||
|
||||
let card_rect = find_plate(shot, Role::SurfaceContainer, ui, "card");
|
||||
let panel_rect = find_plate(shot, Role::SurfaceContainerLow, ui, "panel");
|
||||
// 画布列:三列等宽等距(`disabledbase::PLATE_WIDTH` 固定 360、间距 8),所以它 =
|
||||
// 卡列左侧同样宽的一列。画布板的底与页面同色(`Surface`),**没法**靠颜色切出来。
|
||||
let step = panel_rect.0 - card_rect.0;
|
||||
assert!(
|
||||
step > MIN_PLATE_W,
|
||||
"两列间距 {step} 不合理(列宽 {MIN_PLATE_W} 起)—— 布局变了,画布列的镜像推算不再成立"
|
||||
);
|
||||
let canvas_rect = (0, card_rect.0, card_rect.2, card_rect.3);
|
||||
let canvas_left = card_rect.0.saturating_sub(step);
|
||||
|
||||
let button = |base: Base| rgb(interaction::disabled_button_colors(ui, base).0);
|
||||
let field = |base: Base| rgb(interaction::disabled_colors(ui, base).0);
|
||||
|
||||
// 三档必须两两不同(哪一对相等就说明底被丢了 / 两族 α 合流了)
|
||||
let (canvas_button, canvas_field) = (button(Base::CANVAS), field(Base::CANVAS));
|
||||
let (card_button, card_field) = (button(card), field(card));
|
||||
let (panel_button, panel_field) = (button(panel), field(panel));
|
||||
assert_ne!(
|
||||
canvas_button, card_button,
|
||||
"{flavor:?}:画布底与卡片底算出来的按钮容器相同 —— 底被丢了"
|
||||
);
|
||||
assert_ne!(
|
||||
canvas_field, card_field,
|
||||
"{flavor:?}:画布底与卡片底算出来的字段容器相同"
|
||||
);
|
||||
assert_ne!(
|
||||
card_button, panel_button,
|
||||
"{flavor:?}:卡片底与面板底必须可分"
|
||||
);
|
||||
assert_ne!(
|
||||
card_field, panel_field,
|
||||
"{flavor:?}:卡片底与面板底必须可分"
|
||||
);
|
||||
assert_eq!(
|
||||
surfaces::elevated::BASE,
|
||||
card,
|
||||
"卡片列用的底就该是 `surfaces::elevated::BASE`(容器导出的那个常量)"
|
||||
);
|
||||
|
||||
for (rect, base, wanted_button, wanted_field, name) in [
|
||||
(card_rect, card, card_button, card_field, "card"),
|
||||
(panel_rect, panel, panel_button, panel_field, "panel"),
|
||||
] {
|
||||
let (x0, x1, y0, y1) = rect;
|
||||
let hits = count_exact(shot, wanted_button, x0, x1, y0, y1);
|
||||
assert!(
|
||||
hits >= MIN_BUTTON_PX,
|
||||
"{name} 列里「叠 {:?}」的禁用按钮容器只有 {hits} px(应 ≥{MIN_BUTTON_PX}):\
|
||||
这一列的禁用控件没把真实父底传下去",
|
||||
base.role()
|
||||
);
|
||||
let hits = count_exact(shot, wanted_field, x0, x1, y0, y1);
|
||||
assert!(
|
||||
hits >= MIN_FIELD_PX,
|
||||
"{name} 列里「叠 {:?}」的禁用字段容器只有 {hits} px(应 ≥{MIN_FIELD_PX})",
|
||||
base.role()
|
||||
);
|
||||
// 反向:这一列里不许出现「叠画布底」的那一档
|
||||
let stray = count_exact(shot, canvas_button, x0, x1, y0, y1);
|
||||
assert!(
|
||||
hits > stray * STRAY_RATIO,
|
||||
"{name} 列里「叠 {:?}」的禁用按钮容器只有 {hits} px,而「叠画布」的有 {stray} px (要求前者 ≥ 后者 ×{STRAY_RATIO})—— 这一列的控件仍在按 Role::Surface 合成",
|
||||
base.role()
|
||||
);
|
||||
let stray = count_exact(shot, canvas_field, x0, x1, y0, y1);
|
||||
assert!(
|
||||
hits > stray * STRAY_RATIO,
|
||||
"{name} 列里「叠 {:?}」的禁用字段容器只有 {hits} px,而「叠画布」的有 {stray} px (要求前者 ≥ 后者 ×{STRAY_RATIO})",
|
||||
base.role()
|
||||
);
|
||||
assert!(
|
||||
base.role() != Role::Surface,
|
||||
"{name} 列不该是画布底(判据自己写错了)"
|
||||
);
|
||||
}
|
||||
|
||||
// 画布列:反过来必须出现画布档
|
||||
let (_, cx1, cy0, cy1) = canvas_rect;
|
||||
let cx0 = canvas_left;
|
||||
let canvas_hits = count_exact(shot, canvas_button, cx0, cx1, cy0, cy1);
|
||||
assert!(
|
||||
canvas_hits >= MIN_BUTTON_PX,
|
||||
"画布列里没有「叠画布」的禁用按钮容器 —— 对照组不成立"
|
||||
);
|
||||
let canvas_card_hits = count_exact(shot, card_button, cx0, cx1, cy0, cy1);
|
||||
assert!(
|
||||
canvas_hits > canvas_card_hits * STRAY_RATIO,
|
||||
"画布列里「叠画布」的禁用按钮容器 {canvas_hits} px,而「叠卡片底」的有 {canvas_card_hits} px —— 画布列的控件被传了卡片底"
|
||||
);
|
||||
|
||||
eprintln!(
|
||||
"[probe] OK section=disabledbase flavor={flavor:?}:\
|
||||
canvas 按钮 {:?} / 卡片 {} / 面板 {} ⇒ 三列各按自己的底合成",
|
||||
canvas_button, card_rect.0, panel_rect.0
|
||||
);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use unlucky_design::color::Role;
|
||||
|
||||
use super::super::scan::{rgb, Shot};
|
||||
use crate::app::components::interaction;
|
||||
use crate::app::components::interaction::Base;
|
||||
use crate::theme::Ui;
|
||||
|
||||
/// 矩阵行距(40 高按钮 + 8 间距)。
|
||||
@@ -38,7 +39,7 @@ pub(super) fn assert_matrix_rows(shot: &Shot, ui: &Ui, label_top: usize, label_l
|
||||
let y1 = y0 + ROW_HEIGHT;
|
||||
// **与实现同源**:按钮族的内容色一律走 `disabled_button_content`(38% 叠在 10% 底上)。
|
||||
// 用面状控件族的 12% 底会让断言比实现宽一档,差的 1/255 会放跑「容器对内容错」。
|
||||
let ink = interaction::disabled_button_content(ui, role, on_container);
|
||||
let ink = interaction::disabled_button_content(ui, Base::CANVAS, role, on_container);
|
||||
let count = shot.count_in(rgb(ink), column_from, shot.width, y0, y1);
|
||||
// 阈值 **30**:最窄的一行是只有 4 个字符的 `text` 变体(实测 38 px 墨迹,M229 复审后
|
||||
// 按钮内距 24→20 让标签左移,余量只剩 2 px)。角色接错时会掉到个位数甚至 0。
|
||||
|
||||
Reference in new issue
Block a user