fix(app-iced): M68 r1 —— M69 迁移 + 守门补洞 + 药丸 56×32 + caption 图标 24 + 东侧热区回 1180
代码复审 p2-2items / 视觉复审 p2-2items 的修复轮(配色重拍等 M70 定案)。
M69 迁移(2 行)
- nav 轨宽:nav_rail_width(metrics) 函数体改用 metrics.nav_rail_width
- theme::STROKE_PX 改为 pub use unlucky_design::stroke::HAIRLINE as STROKE_PX
P2-1 零硬编码守门补洞(reviewer 造样 5 个漏网样本全部补上,并进自检正向断言)
- COLOR 调用表补 from_rgb8 / rgb8 / rgba(`from_rgb(` 子串匹配不到 `from_rgb8(` 是根因)
- 新增 `Color { r: .., g: .. }` 结构体字面量检查
- 尺寸规则遇到 Length::Fixed( / Padding::from( / Size::new( 往里剥一层再判数字
- 新增渲染层回归守门 pill_never_overrides_its_size_with_fill(禁 nav.rs 出现 center_*( ):
视觉复审 P2-1 的病根就是 center_*(Length::Fill) 把容器尺寸覆盖成 Fill
视觉 P2-1 药丸 88×32 → 56×32(实测 x=16..71)
- 去掉 center_x/center_y(Fill);居中改由父 column 的 align_x + 显式 width(轨宽) 完成
(只去掉 center_* 还不够:column 会 shrink 到 56,药丸贴左缘 x=0..55,是同一个坑的第二层)
视觉 P2-2 / 用户点名 caption 图标 46 → 24(按钮容器保持 46×48)
- button 内包 container(...).width(24).height(24).center(Length::Fill):
只设 width/height 会让容器缩到 24 并贴 button 内容左上角;center(Fill) 才既撑满又居中
视觉 P3-1 东侧/南侧缩放热区改成叠层
- 原来是 row 的兄弟列,吃掉 4px 布局宽(右留白 28、关闭键离窗角 4px);
现在用 stack! + 对齐容器叠在工作区上,布局宽回到 1180(卡片右缘 x=1155 = 右留白 24)
P3 清单
1. --data-dir 接受并忽略(翻牌前 CI 兼容;现役 smoke 命令行可原样跑,exit 0)+ 单测
2. --diff 内建 ±N px 平移扫描(--diff-scan),CI 不再依赖 numpy
3. 截图模式等 SystemTheme 到达才出图(否则暗色系统上会拍到浅色)
4. (Auto, 未编入 wgpu) 返回 tiny-skia,不再把 wgpu 写进候选列表
5. --renderer=gpu 也跳过适配器枚举(决策与探测无关,省 ~0.6~1.2s)
6. icons::handle 加 debug_assert_eq!(IconKind::ALL[kind as usize], kind)
7. 轨顶留白改 container padding-top,实测 24(原来 spacer + column spacing = 28)
顺带:shot.rs 拆成 shot/mod.rs + shot/diff.rs(≤400 行门禁)
门禁:fmt OK / clippy -D warnings 干净 / 46+5 测试全过 / cargo check --workspace --all-targets OK
体积:双后端 6,769,664 B(6.46 MiB)、仅 CPU 3,955,712 B(3.77 MiB)
截图:r1 代码验证图(r1-verify / hover / pressed / compare-nav-rail)已出;4 张交付主图待 M70 定案后重拍
This commit is contained in:
1 parent
14da7b3dec
commit
a1adf024ba
11 files changed
+599
-200
No files matched your search
@@ -72,15 +72,22 @@ fn hex_color_at(bytes: &[u8], start: usize) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
/// 带数值参数的取色构造(`Color::from_rgb(0.1, …)`、`color!(0x12, …)`)。
|
||||
const NUMERIC_COLOR_CALLS: [&str; 5] = [
|
||||
/// 带数值参数的取色构造(`Color::from_rgb(0.1, …)`、`Color::from_rgb8(103, 80, 164)`、`color!(0x12, …)`)。
|
||||
///
|
||||
/// **子串匹配要注意前后缀**:`from_rgb(` 匹配不到 `from_rgb8(`,所以 8 bit 那个必须单独列出来
|
||||
/// (reviewer 造样就是这么绕过去的)。
|
||||
const NUMERIC_COLOR_CALLS: [&str; 8] = [
|
||||
"Color::from_rgb(",
|
||||
"Color::from_rgb8(",
|
||||
"Color::from_rgba(",
|
||||
"Color::from_rgba8(",
|
||||
"Color::from_hex(",
|
||||
"Color::rgba(",
|
||||
"Color::rgb8(",
|
||||
"color!(",
|
||||
];
|
||||
|
||||
/// 取色构造后面是不是跟着数值字面量(允许 `Color::from_rgba8(color.r, …)` 这类搬运)。
|
||||
fn numeric_color_call(text: &str) -> Option<&'static str> {
|
||||
for pattern in NUMERIC_COLOR_CALLS {
|
||||
let mut from = 0;
|
||||
@@ -100,6 +107,28 @@ fn numeric_color_call(text: &str) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
/// 结构体字面量取色:`Color { r: 0.4, g: 0.3, b: 0.6, a: 1.0 }`。
|
||||
///
|
||||
/// 判据:`Color {` 之后 64 个字符内同时出现 `r:` 与 `g:` 且有数字(`Color { a: 0.0, .. }` 这种
|
||||
/// 只改 alpha 的写法不会命中,因为它没有 `r:`)。
|
||||
fn color_struct_literal(text: &str) -> bool {
|
||||
let mut from = 0;
|
||||
while let Some(found) = text[from..].find("Color {") {
|
||||
let after = from + found + "Color {".len();
|
||||
let window: String = text[after..].chars().take(64).collect();
|
||||
let has_channels = window.contains("r:") && window.contains("g:");
|
||||
let has_number = window.chars().any(|ch| ch.is_ascii_digit());
|
||||
|
||||
if has_channels && has_number {
|
||||
return true;
|
||||
}
|
||||
|
||||
from = after;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_hardcoded_colors_in_sources() {
|
||||
let mut offenders = Vec::new();
|
||||
@@ -128,6 +157,14 @@ fn no_hardcoded_colors_in_sources() {
|
||||
index + 1
|
||||
));
|
||||
}
|
||||
|
||||
if color_struct_literal(line) {
|
||||
offenders.push(format!(
|
||||
"{}:{} {line:?} -> Color {{ r: … }} 结构体字面量",
|
||||
path.display(),
|
||||
index + 1
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,8 +176,20 @@ fn no_hardcoded_colors_in_sources() {
|
||||
}
|
||||
|
||||
/// 判断某个布局调用后面是不是直接跟了尺寸字面量;`0`(= 无内边距)放行。
|
||||
///
|
||||
/// iced 里尺寸经常被包一层:`.width(Length::Fixed(123.0))`、`.padding(Padding::from([12.0, 8.0]))`、
|
||||
/// `.size(Size::new(10.0, 10.0))` —— 所以遇到这些包装要**往里剥一层**再判。
|
||||
fn size_literal(argument: &str) -> Option<String> {
|
||||
let number: String = argument
|
||||
let mut rest = argument.trim_start();
|
||||
|
||||
for wrapper in ["Length::Fixed(", "Padding::from(", "Size::new("] {
|
||||
if let Some(after) = rest.strip_prefix(wrapper) {
|
||||
rest = after.trim_start().trim_start_matches(['[', '(']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let number: String = rest
|
||||
.trim_start()
|
||||
.chars()
|
||||
.take_while(|ch| ch.is_ascii_digit() || *ch == '.')
|
||||
@@ -169,6 +218,11 @@ fn shell_takes_every_size_from_tokens() {
|
||||
let text = std::fs::read_to_string(&path).expect("读 shell 源文件");
|
||||
|
||||
for (index, line) in text.lines().enumerate() {
|
||||
// 文档注释是散文不是代码:里面写 `.width(24)` 是在解释坑,不该判违规
|
||||
if line.trim_start().starts_with("//") {
|
||||
continue;
|
||||
}
|
||||
|
||||
for pattern in [".width(", ".height(", ".size(", ".spacing(", ".padding("] {
|
||||
let mut from = 0;
|
||||
while let Some(found) = line[from..].find(pattern) {
|
||||
@@ -232,7 +286,29 @@ fn this_crate_never_depends_on_slint() {
|
||||
|
||||
#[test]
|
||||
fn the_gate_itself_catches_a_planted_violation() {
|
||||
// 自检:把违规样本喂给同一套判定,必须被抓住(否则门禁是空转)
|
||||
// 自检:把违规样本喂给同一套判定,必须被抓住(否则门禁是空转)。
|
||||
// **reviewer r1 造样验证过的 5 个漏网样本逐条钉在这里**,退化成「放行」立刻红。
|
||||
|
||||
// 1. `Color::from_rgb8(103, 80, 164)` —— 8bit 版曾因 `from_rgb(` 匹配不到而漏
|
||||
assert!(numeric_color_call("Color::from_rgb8(103, 80, 164)").is_some());
|
||||
// 2. `iced::Color::from_rgb8(...)` —— 带路径前缀的写法同样要拦
|
||||
assert!(numeric_color_call("iced::Color::from_rgb8(103, 80, 164)").is_some());
|
||||
// 3. `Color { r: 0.4, g: 0.3, b: 0.6, a: 1.0 }` —— 结构体字面量
|
||||
assert!(color_struct_literal(
|
||||
"Color { r: 0.4, g: 0.3, b: 0.6, a: 1.0 }"
|
||||
));
|
||||
// 4. `.width(Length::Fixed(123.0))` —— 尺寸被 iced 包装一层
|
||||
assert_eq!(
|
||||
size_literal("Length::Fixed(123.0))").as_deref(),
|
||||
Some("123.0")
|
||||
);
|
||||
// 5. `.padding(Padding::from([12.0, 8.0]))` —— 数组字面量
|
||||
assert_eq!(
|
||||
size_literal("Padding::from([12.0, 8.0]))").as_deref(),
|
||||
Some("12.0")
|
||||
);
|
||||
|
||||
// 原有规则不能退化
|
||||
assert!(hex_color_at(b" color: #6750A4,", 11).is_some());
|
||||
assert!(hex_color_at(b"// placeholder #RRGGBB here", 15).is_none());
|
||||
assert!(numeric_color_call("Color::from_rgb(0.1, 0.2, 0.3)").is_some());
|
||||
@@ -241,6 +317,35 @@ fn the_gate_itself_catches_a_planted_violation() {
|
||||
assert!(size_literal("40.0)").is_some());
|
||||
assert!(size_literal("0)").is_none());
|
||||
assert!(size_literal("metrics.titlebar_height)").is_none());
|
||||
assert!(size_literal("Length::Fixed(thickness))").is_none());
|
||||
assert!(size_literal("Padding::from([0.0, ui.space(Space::X4)]))").is_none());
|
||||
assert!(!color_struct_literal(
|
||||
"Color::from_rgba8(color.r, color.g, color.b, 1.0)"
|
||||
));
|
||||
assert!(!color_struct_literal("let color = Color { a: 0.5 };"));
|
||||
assert!(!manifest_has_slint_dependency("slint = \"1.18\"").is_empty());
|
||||
assert!(manifest_has_slint_dependency("# 不得依赖 slint").is_empty());
|
||||
}
|
||||
|
||||
/// 渲染层回归守门(视觉复审 P2-1 的教训):`indicator_matches_the_reference_56x32` 只测纯函数,
|
||||
/// 门禁全绿但图上的药丸实际是 88×32 —— 因为 `.center_x(Length::Fill)` 会把容器 width 覆盖成 Fill。
|
||||
/// 这里直接扫 `nav.rs`:药丸容器**不许**出现 `center_x(` / `center_y(`。
|
||||
#[test]
|
||||
fn pill_never_overrides_its_size_with_fill() {
|
||||
let nav = manifest_dir().join("src").join("shell").join("nav.rs");
|
||||
let text = std::fs::read_to_string(&nav).expect("读 nav.rs");
|
||||
|
||||
for (index, line) in text.lines().enumerate() {
|
||||
let code = line.trim_start();
|
||||
if code.starts_with("//") {
|
||||
continue;
|
||||
}
|
||||
for pattern in ["center_x(", "center_y("] {
|
||||
assert!(
|
||||
!code.contains(pattern),
|
||||
"nav.rs:{} 用了 {pattern}——`center_*(Length::Fill)` 会把容器尺寸改成 Fill, 药丸会被父级宽度覆盖(视觉复审 P2-1)。居中请用父级 `align_x`:{line}",
|
||||
index + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user