视觉复审 r2 的 P2-A 与两个顺手的 p3。
**P2-A 摘要页脚占比过大(3:2 → 4:2)**:1180×760 下诊断卡 338px,摘要页脚固定吃掉 ~150px
(标签+按钮 40 + 摘要折 5 行 80 + 间距/分割 30),滚动视口只剩 141px,而一条完整结论
(分组标题 → 建议动作 → 置信度)要 165px ⇒ 首屏看不到「建议动作」,那是任务清单 T1/T3 要求的
可见项。改成 `DIAGNOSIS_SHARE = 4`(`LOGS_SHARE` 仍 2)。**数值实测**(`r3-crash-light.png`
逐像素量,非估算):诊断卡 376px、滚动视口 **178px**(y 201–378,两侧 1px 分割线的位置)、
一条完整结论块 **165px**、底部余量 **13px** —— 建议动作与 `Confidence 85%` 整块在首屏可见。
**p3 演示数据自相矛盾**:`live` 状态页头写「Finished normally」却配崩溃日志尾行
(`启动失败:exit code 1`)。新增 `sample_live.log`(一场正常跑完的会话:加载 → 进世界 →
停服),`State::Live` 用它,不再复用崩溃样例。
**p3 `demo::parse` 对非 ASCII 首字符 panic**:`raw.split_at(1)` 会切在多字节序列中间。
改成按字符取前缀(`chars().next()` + `chars.as_str()`);顺带把它提成 `pub(super)` 以便测。
新增 `the_fixture_parser_survives_a_non_ascii_first_char`:非 ASCII 首字符不 panic(静默跳过),
并钉住 `I/W/E` 前缀与注释/空行的解析。
测试:**278 passed / 0 failed**(lib)+ 6(`no_hardcoded_tokens`)+ 23(i18n)全绿;
`fmt --check` 通过;`clippy -p unlucky-app-iced -p unlucky-i18n --all-targets -D warnings` 干净。
出图:`C:\Temp\ull-shots-m95\r3-{crash-light,crash-dark,live-light,empty-light}.png`(1180×760)。
90 lines
3.4 KiB
Rust
90 lines
3.4 KiB
Rust
//! 两块的共用外壳:**定高的卡片 + 内部独立滚动**。
|
||
//!
|
||
//! ## 为什么必须定高(M95 r1 的实拍 bug)
|
||
//!
|
||
//! 第一版把诊断卡按内容自然高度画、日志卡吃剩余高度。三条结论在 424×540 的正文里就要
|
||
//! ~500px,结果:诊断卡被裁掉尾部(一行摘要整块消失)、日志卡拿到 **0** 高度(整块不见)。
|
||
//! 截图见报告里的「r1 崩了」。
|
||
//!
|
||
//! 所以两块都**由调用方给定高**(`FillPortion`),各自内部滚动:
|
||
//!
|
||
//! | 块 | 高度 | 为什么 |
|
||
//! |---|---|---|
|
||
//! | 诊断 | `FillPortion(4)` | 崩溃时先读结论;份额要够 177px 的滚动视口,才放得下**一条完整结论**(含建议动作与置信度) |
|
||
//! | 日志 | `FillPortion(2)` | 日志是长流水,滚就是了 |
|
||
//!
|
||
//! 两者都是**兄弟**滚动区(不是嵌套):滚轮归属清楚,不存在「滚动条套滚动条」。
|
||
|
||
use iced::widget::{container, scrollable};
|
||
use iced::{Background, Element, Length};
|
||
|
||
use unlucky_design::component_map::Component;
|
||
use unlucky_design::elevation::MaterialLayer;
|
||
|
||
use crate::theme::Ui;
|
||
|
||
use super::super::frame;
|
||
use super::super::Message;
|
||
|
||
/// 定高卡片(`Card` 材质层 + `OutlinedCard` 形状与 **1px 描边**,与 `frame::card` 同一套令牌:
|
||
/// 实例页 / 下载页的卡都带这条描边,日志页的两张主卡不能例外)。
|
||
pub fn card<'a>(
|
||
ui: &'a Ui,
|
||
height: Length,
|
||
content: impl Into<Element<'a, Message>>,
|
||
) -> Element<'a, Message> {
|
||
let shape = Component::OutlinedCard.spec().shape;
|
||
|
||
container(content)
|
||
.width(Length::Fill)
|
||
.height(height)
|
||
.padding(ui.metrics().card_padding)
|
||
.style(move |_theme| container::Style {
|
||
background: Some(Background::Color(ui.layer(MaterialLayer::Card))),
|
||
border: ui.outlined(shape),
|
||
..container::Style::default()
|
||
})
|
||
.into()
|
||
}
|
||
|
||
/// 卡片内的竖向滚动区(贴顶:诊断卡从头读)。
|
||
pub fn scroll<'a>(ui: &'a Ui, content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
|
||
scroller(ui, content, false)
|
||
}
|
||
|
||
/// 卡片内的竖向滚动区(**贴底**:日志卡看最后几行)。
|
||
///
|
||
/// `anchor_bottom` = `Anchor::End`:滚动偏移按「距底多远」记,所以**内容增长时视图保持在
|
||
/// 距底同一位置 ⇒ 天然跟随尾部**。用户上滚后偏移 = 距底 N 像素,新内容到达时仍按「距底 N」
|
||
/// 呈现(视图跟着内容下移),也就是说**这里没有「上滚即冻结」**——那需要页面自己持有滚动
|
||
/// 状态 + `on_scroll` 消息 + app 层回灌,属于另一件事(已登记在报告里)。
|
||
pub fn scroll_tail<'a>(
|
||
ui: &'a Ui,
|
||
content: impl Into<Element<'a, Message>>,
|
||
) -> Element<'a, Message> {
|
||
scroller(ui, content, true)
|
||
}
|
||
|
||
fn scroller<'a>(
|
||
ui: &'a Ui,
|
||
content: impl Into<Element<'a, Message>>,
|
||
tail: bool,
|
||
) -> Element<'a, Message> {
|
||
let view = scrollable(content)
|
||
.direction(scrollable::Direction::Vertical(
|
||
scrollable::Scrollbar::new()
|
||
.width(frame::SCROLLBAR_WIDTH)
|
||
.scroller_width(frame::SCROLLBAR_WIDTH)
|
||
.margin(frame::SCROLLBAR_MARGIN),
|
||
))
|
||
.height(Length::Fill)
|
||
.width(Length::Fill)
|
||
.style(frame::scroll_style(ui));
|
||
|
||
if tail {
|
||
view.anchor_bottom().into()
|
||
} else {
|
||
view.into()
|
||
}
|
||
}
|