fix(app-iced): M79 r2 —— 四条 review 修复(对齐/单行/可比对象/整行分隔线)

代码 r1 P2-1 = 视觉 r1 P2-1(同一处,必修)
- `app_bar::action`:容器只给了 `align_y` → 24px 图标贴 40×40 命中区左缘(实测前导墨迹
  `38..58`,应 `44..68`;hover 的圆形状态层也露在图标一侧)。补 `align_x(Center)`;
  同时页头内容行补 `align_y(Center)`(`Container` 默认 Top,整行原本贴顶)。
- 把 Q21 升格成规则并全目录复查(结论写进 `navigation/mod.rs` 的表):
  app_bar 命中区/页头行、tabs tab 内容、drawer 药丸 = 两轴显式居中;
  list_item 文本块与 drawer 分组标题 = 左上起排(`Start` 是对的,不需要居中)。
  回归钉子:`navigation::probe` 的 appbar 断言量「左内缩 == 右内缩 ±2」。

代码 r1 P2-2(必修):四件文本都没有单行约束,容器高度却是固定值
- `app_bar`(标题/副标题)、`list_item::line`、`drawer::line`、`tabs` 标签统一
  `.wrapping(Wrapping::None)` + 外层 `clip(true)`;页头/ListiItem/drawer 的文本块改成
  `Fill + clip`,长文本切在文本区里,不折行、不顶穿、不压 trailing 动作。
- iced 没有 ellipsis → 按 `inputs/menu.rs` 的 D19 先例登记 **D-M79-5**
  (官方要单行 ellipsis,我们单行 + 超长直接切割)。
- 取证:gallery 新增 `ULL_NAV_PART=long`(长实例名整块)+ `navigation/probe/long.rs`
  量「行高仍 44/60/76 / 页头仍 56 / 标题墨迹右缘 < trailing 左缘」。取证块按
  `selection::matrix` 的先例收进 `navigation/matrix.rs`(gallery 单文件贴 400 红线)。

视觉 r1 P2-2(必修):ListItem 三列内容不同 → 「选中 trailing = Primary」没有可比对象
- 三列都挂 `trailing(settings)`,只有 selected 列走 `Primary`(其余 `OnSurface` / 38%)。

视觉 r1 P2-2(可选,已做):Secondary 分隔线逐 tab 分段 + 0.5 圆角留了 1px 亮缝
- 改成**整行一条**:`Stack` 把一条 `Fill` 的 1px `SurfaceVariant` 压在 tab 行底下
  (底座是 tab 行本身 → 线长 = tab 行总宽,横向滚动跟着走)。
- `TabsGeometry::bar_radius(bool)` 收成 `indicator_radius()`(只有 3px 的 Primary 条要圆角),
  单测同步;probe 新增「Secondary 分隔线必须 1 段」的断言。

门禁:`cargo fmt --all --check` ✓ · `cargo build -p unlucky-app-iced --lib` 零 warning ✓
(截图/探针/测试的实时输出见完成报告;本轮宿主机被多条并行线的 rustc 打满,
bin 链接排队严重,如未取到会如实标注未完成项。)
This commit is contained in:
AskaEth committed 2026-11-02 11:56:02 +08:00
1 parent 81e34d77da
commit a36f542f28
13 files changed
+471 -88

No files matched your search

@@ -0,0 +1,99 @@
//! 长文案的渲染层断言(**D-M79-5**:我们的单行约束是否真的成立)。
//!
//! 矩阵图全用短文案("Instances" / "Instance details"),`Text` 默认 `Wrapping::Word` 的坑
//! 在图上根本看不出来——页面一接真实实例名(`All the Mods 10 - ATM10 - 1.21.1 - NeoForge`)
//! 就会折成 2~3 行、顶穿 56 的页头、压住 trailing 动作。所以这里用 `ULL_NAV_PART=long`
//! 那张图单独量三件事:
//!
//! | 断言 | 抓的错误 |
//! |---|---|
//! | ListItem 三行高仍 44/60/76 | 长文本折行把行撑高 / 被固定行高裁掉 |
//! | AppBar 仍 56 高 | 标题折行顶穿页头 |
//! | 标题墨迹右缘 < trailing 图标左缘 | 长标题覆盖到动作按钮上 |
//!
//! 跑法见 `navigation/probe.rs` 的模块文档(`ULL_NAV_DIR=target/m79`,图名 `long-light.png`)。
use unlucky_design::color::{Role, SchemeFlavor};
use super::scan::{rgb, ui};
use super::{shot, shot_dir};
#[test]
fn long_text_stays_single_line() {
let Some(dir) = shot_dir() else {
eprintln!(
"[navigation::probe::long] SKIPPED:未设置 ULL_NAV_DIR(需要先出 long-light.png)"
);
return;
};
let shot = shot(&dir, "long", SchemeFlavor::Light);
let ui = ui(SchemeFlavor::Light);
// ---- ① ListItem:长实例名不折行 → 行高仍是 44 / 60 / 76 ----
let selected = rgb(ui.color(Role::SecondaryContainer));
let mut rows = shot
.blocks(selected)
.into_iter()
.filter(|b| (238..=242).contains(&b.width()) && b.height() > 30)
.collect::<Vec<_>>();
rows.sort_by_key(|b| b.top);
assert_eq!(
rows.len(),
3,
"长文案块应有 3 条 ListItem(实 {})",
rows.len()
);
let heights = rows.iter().map(|b| b.height()).collect::<Vec<_>>();
for (measured, expected) in heights.iter().zip([44usize, 60, 76]) {
assert!(
(expected - 1..=expected).contains(measured),
"长文案把行高撑成 {heights:?}(应为 44/60/76 ±1)——说明折行了"
);
}
// ---- ② AppBar:长标题不顶穿页头(仍 56 高)----
let bar = shot
.blocks(rgb(ui.color(Role::Surface)))
.into_iter()
.filter(|b| b.width() > 300 && (54..=58).contains(&b.height()))
.next()
.expect("长文案块应有一条 56 高的页头");
assert!(
(55..=57).contains(&bar.height()),
"长标题把页头撑成 {} 高(应 56)——说明折行了",
bar.height()
);
// ---- ③ 标题不许压到 trailing 动作上 ----
let title_ink = shot
.band_extent(
rgb(ui.color(Role::OnSurface)),
bar.top,
bar.bottom,
bar.left + 40,
bar.right.saturating_sub(90),
)
.expect("页头里应有标题墨迹");
let trailing = shot
.band_extent(
rgb(ui.color(Role::OnSurfaceVariant)),
bar.top,
bar.bottom,
bar.right.saturating_sub(90),
bar.right,
)
.expect("页头右侧应有 trailing 图标墨迹");
assert!(
title_ink.1 + 2 < trailing.0,
"长标题墨迹右缘 {} 压到 trailing 图标左缘 {} 上了(应被裁在文本区里)",
title_ink.1,
trailing.0
);
eprintln!(
"[navigation::probe] long: 行高 {heights:?},页头 {} 高,标题右缘 {} < trailing {}",
bar.height(),
title_ink.1,
trailing.0
);
}
@@ -155,6 +155,29 @@ impl Shot {
best.map(|(_, pixel)| pixel)
}
/// 矩形里某颜色墨迹的左右范围(量图标/文字位置用)。
pub(super) fn band_extent(
&self,
color: Rgb,
y0: usize,
y1: usize,
x0: usize,
x1: usize,
) -> Option<(usize, usize)> {
let mut span: Option<(usize, usize)> = None;
for y in y0..=y1.min(self.height - 1) {
for x in x0..=x1.min(self.width - 1) {
if matches(self.at(x, y), color) {
span = Some(match span {
Some((l, r)) => (l.min(x), r.max(x)),
None => (x, x),
});
}
}
}
span
}
/// 矩形内「与底色不同」的像素的左右范围(量文本墨迹用)。
///
/// 为什么不按精确文字色找:小字号抗锯齿后几乎没有像素落在**精确**色上(容差 3 时实测