fix(app-iced): 加载器版本菜单搬进钉死区(裁切位置由钉死区高度决定,不是首屏分配)

四审点破根因:面板的裁切 y 由**钉死区高度**决定、与窗宽无关(1180 与 720 都切在同一处),
所以上一轮「把说明挪到菜单之后」只是换了被切的对象——菜单容器 642–706 只露 18px、条目文字
一个都没画,说明整块不可见。

按 tower 裁决改成结构方案:
- **滚动区**:head(版本 id + 安装按钮)+ 加载器 chips + 中性说明 + 错误行;
- **钉死区**:加载器版本菜单(标签 + 菜单,按条目数占高)+ 实例名输入框。

菜单不再依赖首屏剩余高度分配,可达性由结构保证;溢出时被滚走的是说明/错误行(非控件)。

实测(未变的部分也一并核对):01/02/05 分隔线 **481**、列表 **220px = 5 整行**;04 对照 **531 未变**;
03/06 窄窗 **268 未变**(本轮结构改动没有挤动列表);900 高加测 569 ⇒ **308px = 7 整行**。

文档按四审改正:`section` 的文档不再写「首屏能装下菜单/切点不再切字」,改成「裁切位置由钉死区
高度决定」;面板结构写清 head 在滚动区、菜单与实例名在钉死区。
This commit is contained in:
AskaEth committed 2026-11-10 19:34:14 +08:00
1 parent 0ca28d24ff
commit 6eb7132ab0
1 file changed
+28 -24
@@ -66,12 +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` 模块文档里登记的方案)。
/// `scroll` = 面板此刻是不是「自己可滚」的那一档(`rows::TailShare::Shared`)。是的话:
///
/// - **滚动区**:head(版本 id + 安装按钮)+ 加载器 chips + 中性说明 + 错误行;
/// - **钉死区**:加载器版本菜单 + 实例名输入框。
///
/// 为什么菜单也要钉(D30 四审):裁切位置由**钉死区高度**决定,与窗宽无关(1180 与 720 都切在
/// 同一个 y)。之前只钉实例名,菜单就正好落在切线上——760 下菜单容器 642–706 只有 18px 可见、
/// 条目文字一格都没画出来。把菜单一起钉住之后,它不再依赖首屏剩余高度分配,可达性由结构保证;
/// 滚动区溢出时滚的是说明/错误行,那是可以接受被滚走的内容。
pub(crate) fn section<'a>(
ui: &'a Ui,
catalog: Catalog,
@@ -110,20 +115,8 @@ pub(crate) fn section<'a>(
// 换行由 iced 的文本布局负责,长句不会横向溢出;但**视觉验收仍需截图确认**(D30)。
// 「加载器版本」这一段只在**真该有列表**时出现:原版没有、静态不兼容没有(原因在下面那行)、
// 取不到也没有(同样在原因行)。留一个空标题只会让人以为界面缺了一块。
if show_versions(options) {
body = body.push(frame::text_role(
ui,
TypeRole::LabelMedium,
false,
catalog.text(Key::DownloadOptionsLoaderVersion),
ui.color(Role::OnSurfaceVariant),
));
body = body.push(version_list(ui, catalog, options));
}
// 中性说明排在**版本菜单之后**(D30 三审 S7-clip):说明两行高,排在菜单前面会把
// 「加载器版本」这个本页唯一的加载器专属控件顶出首屏,而面板上半块是滚动区,切点会落在
// 字形上。挪到菜单后面之后 1180×760 首屏能装下 head + chips + 标签 + 菜单,切点不再切字。
// 中性说明留在**滚动区**(D30 四审裁决):它两行高、且不是控件,溢出时被滚走可以接受;
// 加载器版本菜单是控件,已经搬到钉死区(见下面的 `pinned`),不再和它抢首屏高度。
if let Some(note) = &options.note {
body = body.push(frame::text_role(
ui,
@@ -144,26 +137,37 @@ pub(crate) fn section<'a>(
));
}
// 页脚:实例名(钉死,不参与滚动)。见 `section` 的文档。
let footer = text_field(
// 钉死区:加载器版本菜单 + 实例名(都不参与滚动)。见 `section` 的文档。
let mut pinned = column![].spacing(ui.space(Space::X2)).width(Length::Fill);
if show_versions(options) {
pinned = pinned.push(frame::text_role(
ui,
TypeRole::LabelMedium,
false,
catalog.text(Key::DownloadOptionsLoaderVersion),
ui.color(Role::OnSurfaceVariant),
));
pinned = pinned.push(version_list(ui, catalog, options));
}
pinned = pinned.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 !scroll {
// 钉死档(三栏 + 无版本列表 / chips 以外的矮面板):行为与以前一致,一列内容到底。
body = body.push(Element::from(footer));
body = body.push(Element::from(pinned));
return body.into();
}
column![
scrollable(body)
.height(Length::Fill)
.style(frame::scroll_style(ui)),
footer,
pinned,
]
.spacing(ui.space(Space::X2))
.height(Length::Fill)