diff --git a/crates/app-iced/src/pages/instances/demo.rs b/crates/app-iced/src/pages/instances/demo.rs index 7d3c2d6..4d10619 100644 --- a/crates/app-iced/src/pages/instances/demo.rs +++ b/crates/app-iced/src/pages/instances/demo.rs @@ -103,7 +103,7 @@ fn demo_view() -> LaunchArgsView { LaunchArgsView { instance_id: DEMO[SELECTED].name.to_string(), jvm_args: "-XX:+UseG1GC".to_string(), - game_args: String::new(), + game_args: "--fullscreen".to_string(), memory_mb: (!gated).then_some(4096), memory_effective_mb: if gated { 2048 } else { 4096 }, java_path: String::new(), diff --git a/crates/app-iced/src/pages/instances/detail.rs b/crates/app-iced/src/pages/instances/detail.rs index 4c291b5..cd5599b 100644 --- a/crates/app-iced/src/pages/instances/detail.rs +++ b/crates/app-iced/src/pages/instances/detail.rs @@ -4,8 +4,8 @@ //! → 一条分隔线 → 正文段 / 运行状态行 → 动作行(Duplicate / Open folder / Delete)。 //! 窄窗(<840)时列表退成上一屏,本文件只画详情 + 一个「返回」按钮。 -use iced::widget::{column, container, row}; -use iced::{Alignment, Element, Length}; +use iced::widget::{column, container, row, scrollable}; +use iced::{Alignment, Element, Length, Padding}; use unlucky_design::color::Role; use unlucky_design::spacing::Space; @@ -48,8 +48,13 @@ pub(super) fn detail_column<'a>( // M157:启动参数区在正文段之后、动作行之前(详情列 424px 内,卡片式)。 body = body.push(super::launch_args::section(ui, &detail.launch, form, texts)); - // 页头 + 元信息网格 + **一条**分隔线以下才是正文与操作区(M84 那两条线是占位时期的排版残留) - frame::left_aligned(metrics.column, body.push(action_row(ui, forced, texts))) + // 页头 + 元信息网格 + **一条**分隔线以下才是正文与操作区(M84 那两条线是占位时期的排版残留)。 + // + // M157 r2(D30 复审):详情列的内容(正文段 + 启动参数卡 + 动作行)在 1180×1000 下**超出一屏**, + // 而这里原来没有滚动区 ⇒ Max memory 以下的控件被静默裁掉、够不着。现在整列套一个滚动区 + // (与左列列表同一套滚动条样式与宽度),被裁内容可达;滚动条是唯一入口,没有第二份状态。 + let content = body.push(action_row(ui, forced, texts)); + frame::left_aligned(metrics.column, scroller(ui, content)) } pub(super) fn narrow_detail<'a>( @@ -89,7 +94,30 @@ pub(super) fn narrow_detail<'a>( ui.color(Role::OnSurfaceVariant), )); - frame::body(metrics, body.push(action_row(ui, forced, texts))) + frame::body( + metrics, + scroller(ui, body.push(action_row(ui, forced, texts))), + ) +} + +/// 详情列的滚动区(M157 r2):与左列列表同一套滚动条几何(宽 6 / 内缩 2 / 同一 `style`)。 +/// +/// 右侧留 `Space::X2` 内缩:滚动条是**覆盖**在内容上的,不留这条缝时卡片的 1dp 描边会被 +/// 滚动条压住(D30 复审里「卡片右边框缺一截」就是它)。 +fn scroller<'a>(ui: &'a Ui, content: impl Into>) -> Element<'a, Message> { + let content = container(content) + .width(Length::Fill) + .padding(Padding::default().right(ui.space(Space::X2))); + 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) + .style(frame::scroll_style(ui)) + .into() } /// 正文段(演示数据才有)与运行状态行(真数据才有)——两者都是可选的,顺序固定。 diff --git a/crates/app-iced/src/pages/instances/launch_args.rs b/crates/app-iced/src/pages/instances/launch_args.rs index 51daf33..6774f60 100644 --- a/crates/app-iced/src/pages/instances/launch_args.rs +++ b/crates/app-iced/src/pages/instances/launch_args.rs @@ -15,12 +15,10 @@ //! //! 文案全走 [`Texts`](守门见 `instances/tests/i18n.rs` 的白名单与死 key 扫描)。 -use iced::widget::{column, container, row}; -use iced::{Background, Element, Length}; +use iced::widget::{column, row}; +use iced::{Element, Length}; use unlucky_design::color::Role; -use unlucky_design::elevation::MaterialLayer; -use unlucky_design::shape::ShapeScale; use unlucky_design::spacing::Space; use unlucky_design::typography::TypeRole; @@ -47,8 +45,10 @@ pub(super) fn section<'a>( form: &'a LaunchForm, texts: Texts, ) -> Element<'a, PageMessage> { + // 段间距 X2(8dp):卡内 5 个字段 + 三态 + 动作行在 424 宽下已经很长, + // 用 X3 会让详情列更长一屏;行内间距仍是 X1(4dp),读起来不挤。 let mut body = column![frame_title(ui, texts.launch_title())] - .spacing(ui.space(Space::X3)) + .spacing(ui.space(Space::X2)) .width(Length::Fill); body = body.push(args_field( @@ -94,22 +94,17 @@ pub(super) fn section<'a>( body = body.push(backend_row(ui, facts, form, texts)); body = body.push(actions_row(ui, texts)); - container(body) - .width(Length::Fill) - .padding(ui.space(Space::X3)) - .style(move |_theme| container::Style { - background: Some(Background::Color(ui.layer(MaterialLayer::Card))), - border: ui.outlined(ShapeScale::Medium), - ..container::Style::default() - }) - .into() + // 与相邻信息卡**同一套**卡壳(`frame::card`:`card_padding` 21dp + OutlinedCard 的圆角/描边)。 + super::super::frame::card(ui, body) } +/// 卡片标题:**与信息卡的标签同款**(`LabelMedium` 不加粗、`OnSurfaceVariant`), +/// 复审 ③⑤ 抓的就是这里(原来用了 `emphasized = true`,比同屏其它标签重一档)。 fn frame_title<'a>(ui: &'a Ui, title: &'a str) -> Element<'a, PageMessage> { super::super::frame::text_role( ui, TypeRole::LabelMedium, - true, + false, title, ui.color(Role::OnSurfaceVariant), ) diff --git a/crates/app-iced/src/pages/instances/tests/i18n.rs b/crates/app-iced/src/pages/instances/tests/i18n.rs index 61fdfc5..8973176 100644 --- a/crates/app-iced/src/pages/instances/tests/i18n.rs +++ b/crates/app-iced/src/pages/instances/tests/i18n.rs @@ -61,7 +61,7 @@ const PAGE_SOURCES: [(&str, &str); 6] = [ /// | `isolated` / `shared` | core 的隔离档位**稳定 id**(`IsolationMode::as_str`),`isolation()` 只认这两档,其余原样上屏 | /// | `pid` / `reason` | `Catalog::render` 的**占位符名**(与 catalog 里的 `{pid}` / `{reason}` 对应) | /// | `Instances` | `Texts::keys()` 的**族名前缀**(`Key::ALL` 的过滤条件,不是文案) | -const ALLOWED_LITERALS: [&str; 17] = [ +const ALLOWED_LITERALS: [&str; 18] = [ "{date} {time} UTC", "isolated", "shared", @@ -79,6 +79,8 @@ const ALLOWED_LITERALS: [&str; 17] = [ // M157:`instances/demo.rs` 的**出图夹具**(用户数据的形状,不是界面文案): // 一个示例 JVM 参数、两个示例 MC 版本、一个示例 Java 路径,以及夹具开关的环境变量名与取值。 "-XX:+UseG1GC", + // M157 r2:示例游戏参数(同上,是夹具不是文案)。 + "--fullscreen", "26.2-snapshot-2", "1.20.4", "C:/Java/temurin-21/bin/java.exe", diff --git a/crates/presenter/src/host/launch_args.rs b/crates/presenter/src/host/launch_args.rs index edbb68d..404be34 100644 --- a/crates/presenter/src/host/launch_args.rs +++ b/crates/presenter/src/host/launch_args.rs @@ -203,6 +203,7 @@ mod tests { /// 最小的 tempdir(与本 crate 其它 host 测试同款:不引第三方 tempfile)。 mod tempdir { use std::path::{Path, PathBuf}; + use std::sync::atomic::{AtomicU64, Ordering}; pub struct Dir { pub path: PathBuf, @@ -210,7 +211,14 @@ mod tests { impl Dir { pub fn new(tag: &str) -> Self { - let path = std::env::temp_dir().join(format!("{tag}-{}", std::process::id())); + // 每个用例一个目录:同一进程里跑多个用例时,只用 PID 命名会互相 `remove_dir_all` + // (并行跑就是「系统找不到指定的路径」——M157 r2 实测)。 + static COUNTER: AtomicU64 = AtomicU64::new(0); + let seq = COUNTER.fetch_add(1, Ordering::Relaxed); + let path = std::env::temp_dir().join(format!( + "{tag}-{}-{seq}", + std::process::id() + )); let _ = std::fs::remove_dir_all(&path); std::fs::create_dir_all(&path).expect("建临时目录"); Self { path }