diff --git a/crates/app-iced/src/pages/download/install.rs b/crates/app-iced/src/pages/download/install.rs index c656643..e985a1e 100644 --- a/crates/app-iced/src/pages/download/install.rs +++ b/crates/app-iced/src/pages/download/install.rs @@ -105,18 +105,37 @@ pub(super) fn panel_state(model: &Download) -> PanelState<'_> { } /// 底部的安装面板。 +/// +/// `shared` = 这一档列表与面板对半分(`rows::TailShare::Shared`)。此时**选项区自己滚**: +/// 上半块(加载器 chip / 说明 / 版本列表 / 错误行)收进 `scrollable` 吃 `Fill`,实例名那一格 +/// 钉成页脚——D30 r2 S7:面板变高后实例名会被挤出首屏,钉住之后可达性由结构保证。 pub(super) fn panel<'a>(ui: &'a Ui, catalog: Catalog, model: &'a Download) -> Element<'a, Message> { + panel_with(ui, catalog, model, false) +} + +/// 同 [`panel`],但显式给「对半分档」标志(只有 `rows::content` 需要)。 +pub(super) fn panel_with<'a>( + ui: &'a Ui, + catalog: Catalog, + model: &'a Download, + shared: bool, +) -> Element<'a, Message> { let content = match panel_state(model) { PanelState::Install(install) => installing(ui, catalog, install), // 选中版本之后**先给选项,再点「安装」**(M105:以前是选中就直接开工,等于没有加载器维度) - PanelState::Options(options) => super::options::section(ui, catalog, options), + PanelState::Options(options) => super::options::section(ui, catalog, options, shared), PanelState::Hint => hint(ui, catalog), }; - container(content) + let panel = container(content) .width(Length::Fill) - .padding(Padding::from([ui.space(Space::X3), 0.0])) - .into() + .padding(Padding::from([ui.space(Space::X3), 0.0])); + // 对半分档:面板吃满自己那一半,选项区的 `scrollable` 才有确定高度可吃。 + if shared { + panel.height(Length::Fill).into() + } else { + panel.into() + } } /// 没选中任何版本:给一句「选一个版本再安装」(不静默禁用,也不画一个点不动的按钮)。 diff --git a/crates/app-iced/src/pages/download/options/view.rs b/crates/app-iced/src/pages/download/options/view.rs index 8150c2a..260aff4 100644 --- a/crates/app-iced/src/pages/download/options/view.rs +++ b/crates/app-iced/src/pages/download/options/view.rs @@ -66,11 +66,17 @@ pub(super) fn version_menu_height(items: usize) -> f32 { rows * ITEM + Space::X2.dp() * 2.0 } -/// 选项区(面板的下半块)。 +/// 选项区(面板的下半块):**可滚的上半块 + 钉死的页脚**。 +/// +/// `scroll` = 面板此刻是不是「自己可滚」的那一档(`rows::TailShare::Shared`)。是的话上半块 +/// 收进 `scrollable` 吃 `Fill`,**实例名那一格留在页脚、永远在屏**——这条是 D30 r2 S7 的修法: +/// 面板变高(多了加载器版本列表)之后,原先实例名会被挤到首屏外(1180×760 实测要滚 1 屏), +/// 钉成页脚之后可达性由**结构**保证,不依赖滚轮(`install.rs` 模块文档里登记的方案)。 pub(crate) fn section<'a>( ui: &'a Ui, catalog: Catalog, options: &'a OptionsView, + scroll: bool, ) -> Element<'a, Message> { let head = row![ container(frame::text_role( @@ -125,15 +131,6 @@ pub(crate) fn section<'a>( body = body.push(version_list(ui, catalog, options)); } - body = body.push(text_field( - ui, - Field::new(options.instance_name.as_str()) - .variant(FieldVariant::Outlined) - .label(catalog.text(Key::DownloadOptionsInstanceName)) - .placeholder(catalog.text(Key::DownloadOptionsInstanceNameHint)), - Message::SetInstanceName, - )); - if let Some(problem) = &options.problem { body = body.push(frame::text_role( ui, @@ -144,7 +141,31 @@ pub(crate) fn section<'a>( )); } - body.into() + // 页脚:实例名(钉死,不参与滚动)。见 `section` 的文档。 + let footer = text_field( + ui, + Field::new(options.instance_name.as_str()) + .variant(FieldVariant::Outlined) + .label(catalog.text(Key::DownloadOptionsInstanceName)) + .placeholder(catalog.text(Key::DownloadOptionsInstanceNameHint)), + Message::SetInstanceName, + ); + + if !scroll { + // 钉死档(三栏 + 无版本列表 / chips 以外的矮面板):行为与以前一致,一列内容到底。 + body = body.push(Element::from(footer)); + return body.into(); + } + column![ + scrollable(body) + .height(Length::Fill) + .style(frame::scroll_style(ui)), + footer, + ] + .spacing(ui.space(Space::X2)) + .height(Length::Fill) + .width(Length::Fill) + .into() } /// 加载器版本这一段要不要画(见 `section` 的注释)。 diff --git a/crates/app-iced/src/pages/download/rows.rs b/crates/app-iced/src/pages/download/rows.rs index 2de9976..fbca25b 100644 --- a/crates/app-iced/src/pages/download/rows.rs +++ b/crates/app-iced/src/pages/download/rows.rs @@ -74,7 +74,7 @@ use super::super::frame; use super::super::layout::rails::{LIST_DATE_WIDTH, LIST_KIND_WIDTH, ROW_GAP, ROW_ICON_BUTTON}; use super::super::layout::VersionRailTier; use super::super::Message; -use super::install::{self, PanelState}; +use super::install::{self, panel_state, PanelState}; use super::model::{Download, VersionRow}; use super::states; @@ -102,19 +102,28 @@ pub(super) fn content<'a>( // 列表与尾部面板之间**总是**有一条分隔线(三态一致):M123 的取证图里「选好了版本」 // 那一态原先没有它,版本行与选项区第一行(版本号 + 安装按钮)读起来像同一块内容。 - let tail: Element<'a, Message> = column![divider::full(ui), install::panel(ui, catalog, model)] - .spacing(ui.space(Space::X3)) - .width(Length::Fill) - .into(); + let shared = matches!(share, TailShare::Shared); + let tail: Element<'a, Message> = column![ + divider::full(ui), + install::panel_with(ui, catalog, model, shared) + ] + .spacing(ui.space(Space::X3)) + .width(Length::Fill) + .into(); - // chips 档:面板收回自己的一半、内部可滚(它原先按内容高钉死,把列表压成 18px 的缝)。 - let tail: Element<'a, Message> = match share { - TailShare::Pinned => tail, - TailShare::Shared => scrollable(tail) + // chips 档 / 带版本列表的选项区:面板收回自己的一半。 + // + // `PanelState::Options` 走**面板内部**的滚动(上半块可滚 + 实例名钉页脚,见 `install::panel`), + // 所以这里不再整块套一层 scrollable——那一层曾让窄窗出现两条并列滚动条,也因为把实例名 + // 卷进滚动区而让它掉到首屏外(D30 r2 S7)。其它面板(安装中/失败)内容短,保留外层滚动。 + let tail: Element<'a, Message> = match (share, panel_state(model)) { + (TailShare::Shared, PanelState::Options(_)) => tail, + (TailShare::Shared, _) => scrollable(tail) .id(iced::widget::Id::new(PANEL_SCROLL_ID)) .height(Length::Fill) .style(frame::scroll_style(ui)) .into(), + (TailShare::Pinned, _) => tail, }; column![ @@ -171,10 +180,14 @@ impl TailShare { (VersionRailTier::Chips, PanelState::Options(_) | PanelState::Install(_)) => { Self::Shared } - // M222 D30 r1 S1:**带加载器版本列表**的选项区在任何档位都太高。三栏档实测 - // (1180×760)面板从 204px 涨到 372px,版本列表从 6.0 行(278px)被压到 2.4 行 - // (110px)——列表底缘还切在第三行图标上。改成 Shared:两半各 ≈215px, - // 列表回到 4.6 行,面板自己可滚([`Self::Shared`] 那条 `scrollable` 分支已存在)。 + // M222 D30 r1 S1 / r2 S7:**带加载器版本列表**的选项区在任何档位都太高。 + // 三栏档(1180×760)实测:修前面板 204→372px,版本列表 6.0 行(278px)被压到 + // 2.4 行(110px,底缘切在第三行图标上);改成 Shared 之后版本列表 **245px = 5.29 行**、 + // 面板 237px(分隔线 y=498)。面板因此变矮,实例名会被挤出首屏 ⇒ 同一轮把它 + // **钉成面板页脚**(`install::panel_with` 的 `shared` 分支 + `options::section`)。 + // 仍欠一项:245px 不是行高整数倍,列表底缘仍切在第 6 行中间(S1b/S9)。 + // 修它要把「分给列表的高度」从 `pages/download.rs` 传进来做 floor 取整 + // (`responsive` 在 Fill 子项上拿不到确定高度,实测无效,已回退)。 (_, PanelState::Options(options)) if options.has_loader_versions() => Self::Shared, _ => Self::Pinned, } diff --git a/crates/i18n/src/catalog/download.rs b/crates/i18n/src/catalog/download.rs index ea82f24..ea02d03 100644 --- a/crates/i18n/src/catalog/download.rs +++ b/crates/i18n/src/catalog/download.rs @@ -103,7 +103,7 @@ macro_rules! download_entries { // 不是错误(不禁用确认键、不套错误色)——所以它不走 `problem` 那条通道。 DownloadOptionsLiteLoaderVanillaOnly => ( "LiteLoader stacks on vanilla only — for Forge, run: ull install --loader liteloader", - "LiteLoader 只叠在原版上 —— 要叠 Forge 请运行:ull install --loader liteloader" + "LiteLoader 只叠在原版上 —— 要叠 Forge 请运行:ull install --loader liteloader" ), DownloadCancelled => ( "Install cancelled — finished files are kept, the instance was not created", diff --git a/crates/i18n/src/catalog/golden/tail_ai.rs b/crates/i18n/src/catalog/golden/tail_ai.rs index e99f673..a60e932 100644 --- a/crates/i18n/src/catalog/golden/tail_ai.rs +++ b/crates/i18n/src/catalog/golden/tail_ai.rs @@ -106,7 +106,7 @@ pub(super) const ROWS: &[(&str, &str, &str)] = &[ ("DownloadOptionsInstanceNameHint", "Empty = use the version id", "留空就用版本号"), ("DownloadOptionsIncompatible", "{loader} has no builds for {version}", "{loader} 没有适配 {version} 的构建"), ("DownloadOptionsNeedsAtLeast", "{loader} needs Minecraft {min} or newer", "{loader} 需要 Minecraft {min} 或更新"), - ("DownloadOptionsLiteLoaderVanillaOnly", "LiteLoader stacks on vanilla only — for Forge, run: ull install --loader liteloader", "LiteLoader 只叠在原版上 —— 要叠 Forge 请运行:ull install --loader liteloader"), + ("DownloadOptionsLiteLoaderVanillaOnly", "LiteLoader stacks on vanilla only — for Forge, run: ull install --loader liteloader", "LiteLoader 只叠在原版上 —— 要叠 Forge 请运行:ull install --loader liteloader"), ("DownloadCancelled", "Install cancelled — finished files are kept, the instance was not created", "安装已取消 —— 已下好的文件保留,实例未登记"), ("LogsSubtitleIdle", "Nothing is running right now", "当前没有运行中的实例"), ("LogsStatus", "{instance} · {when} · {outcome}", "{instance}|{when}|{outcome}"),