feat(app-iced): 下载页在选 LiteLoader 时给中性说明(GUI 只叠原版、Forge 组合走 CLI)

新增 Key::DownloadOptionsLiteLoaderVanillaOnly(中英各一句,不含花括号占位符);
金表按 Key::ALL 的真实位置插入(DownloadOptionsNeedsAtLeast 之后、DownloadCancelled 之前),
只加新键不重排旧键。说明走 OptionsView 的新字段 note,不塞 problem(那是错误色 + 禁用确认)。
This commit is contained in:
AskaEth committed 2026-11-10 19:34:10 +08:00
1 parent 7ed954f7fa
commit c8bf536c37
5 files changed
+91

No files matched your search

@@ -215,6 +215,12 @@ pub struct OptionsView {
pub instance_name: String,
/// 不兼容 / 取不到的原因(`Some` ⇒ 确认键按下不去)
pub problem: Option<String>,
/// 加载器相关的**中性说明**(不是错误:不套错误色、不禁用确认键)。
///
/// 目前只有 LiteLoader 有:下载页只能装「原版 + 加载器」这一层,叠在 Forge 上要走命令行
/// (M222 的边界,见 [`Key::DownloadOptionsLiteLoaderVanillaOnly`])。把它塞进 `problem`
/// 会让用户以为这个组合不能装、还会按不下去确认键,所以单独一条。
pub note: Option<String>,
/// 本地已装(确认键给「已安装」禁用态)
pub installed: bool,
/// 现在能不能点「安装」
@@ -276,6 +282,13 @@ impl OptionsView {
(Ok(()), _) => None,
};
// 中性说明:只解释「这一页能做到哪一层」,不参与确认键判定。
let note = (loader == Loader::LiteLoader).then(|| {
catalog
.text(Key::DownloadOptionsLiteLoaderVanillaOnly)
.to_string()
});
// 四条同时成立才按得下去:不兼容没有、列表真的就位(原版不需要列表)、没装过。
// 「列表没到位」既包括 `Loading` 也包括 `Failed` 与空表——它们都已经在 `problem` 里说了原因。
let ready = loader == Loader::Vanilla
@@ -289,6 +302,7 @@ impl OptionsView {
loader_version,
instance_name: draft.instance_name.clone(),
problem,
note,
installed: row.installed,
can_confirm,
}
@@ -272,3 +272,61 @@ fn installed_versions_cannot_be_confirmed_again() {
assert!(options.installed);
assert!(!options.can_confirm);
}
/// 中性说明(M222):只有 LiteLoader 带「这里只叠原版」那条,且它**不影响**确认键。
///
/// 抓得住:说明被塞进 `problem`(会让用户以为不能装、确认键按不下去);或者说明跑到别家
/// 加载器下面(Forge/Fabric 没有这条边界)。
#[test]
fn liteloader_carries_a_neutral_note_without_blocking_confirm() {
let state = state_with(
LoaderKind::LiteLoader,
"1.12.2",
Ok(vec!["1.12.2-SNAPSHOT"]),
);
let options = OptionsView::of(
&row("1.12.2", VersionKind::Release),
&state,
&draft(Loader::LiteLoader),
Lang::En.catalog(),
);
let note = options.note.as_deref().expect("LiteLoader 应带中性说明");
assert!(note.contains("vanilla"), "{note}");
assert!(
note.contains("--loader liteloader"),
"要给出可执行的下一步:{note}"
);
assert!(options.problem.is_none(), "中性说明不能变成错误");
assert!(options.can_confirm, "说明不该禁用确认键");
// 中文目录同样给中文说明,且仍然不禁用确认键。
let zh = state_with(
LoaderKind::LiteLoader,
"1.12.2",
Ok(vec!["1.12.2-SNAPSHOT"]),
);
let options = OptionsView::of(
&row("1.12.2", VersionKind::Release),
&zh,
&draft(Loader::LiteLoader),
Lang::ZhCn.catalog(),
);
let note = options.note.as_deref().expect("中文也要有说明");
assert!(note.contains("原版"), "{note}");
assert!(options.can_confirm);
// 别家加载器不带这条说明。
for loader in [Loader::Vanilla, Loader::Fabric, Loader::Forge] {
let state = state_with(loader.kind(), "1.20.1", Ok(Vec::new()));
let options = OptionsView::of(
&row("1.20.1", VersionKind::Release),
&state,
&draft(loader),
Lang::En.catalog(),
);
assert!(
options.note.is_none(),
"{loader:?} 不该带 LiteLoader 的说明"
);
}
}
@@ -93,6 +93,18 @@ pub(crate) fn section<'a>(
.spacing(ui.space(Space::X2))
.width(Length::Fill);
// 中性说明(M222):只解释这一页能装到哪一层,不套错误色、不影响确认键。
// 换行由 iced 的文本布局负责,长句不会横向溢出;但**视觉验收仍需截图确认**(D30)。
if let Some(note) = &options.note {
body = body.push(frame::text_role(
ui,
TypeRole::BodySmall,
false,
note.as_str(),
ui.color(Role::OnSurfaceVariant),
));
}
// 「加载器版本」这一段只在**真该有列表**时出现:原版没有、静态不兼容没有(原因在下面那行)、
// 取不到也没有(同样在原因行)。留一个空标题只会让人以为界面缺了一块。
if show_versions(options) {