feat(launch): M157 实例级启动参数(五项回落 + graphicsBackend 三态)
core: 新增 game/launch_overrides(LaunchOverrides 五项 All-Option + 三级回落 + 文本补丁校验)与 launch/overrides(拼装落位:全局 java.extra_args 进 user.jvm、实例 jvmArgs 进 override_jvm、gameArgs 进 user.game、memory 进 -Xmx、backend 进 --graphicsBackend 并复用既有版本门槛告警);实例 schema 登记 launch.* 五键。 presenter: dto/LaunchArgsView+LaunchArgsPatch,主机侧读/写与门槛判定(backend_allowed),AppCommand::SetInstanceLaunchArgs。 cli: 新命令 instances launch-args(读/写/--clear),launch 装配链消费回落值。 app-iced: 实例详情页「启动参数」区(JVM/游戏参数、内存、Java、三态后端 chips + 灰化提示 + 保存/恢复),表单状态归 App、i18n 中英文案同步。
This commit is contained in:
1 parent
d68523b02a
commit
e2021645d7
56 files changed
+3776
-607
No files matched your search
@@ -0,0 +1,218 @@
|
||||
//! 实例详情页的「启动参数」区(M157)。
|
||||
//!
|
||||
//! | 控件 | 语义 | 消息 |
|
||||
//! |---|---|---|
|
||||
//! | JVM 参数(多行) | 实例层,与全局 `java.extra_args` **叠加**(实例段最高优先级) | [`Message::Jvm`] |
|
||||
//! | 游戏参数(多行) | 实例层,追加在命令行最后 | [`Message::Game`] |
|
||||
//! | 最大内存(单行数字) | 实例层;留空 = 跟随全局默认 | [`Message::Memory`] |
|
||||
//! | Java 可执行文件(单行) | 实例层;留空 = 跟随全局 → 自动挑 | [`Message::Java`] |
|
||||
//! | 图形后端(三态 chips) | `default` / `opengl` / `vulkan`;**版本不够时整体灰化 + 提示** | [`Message::Backend`] |
|
||||
//!
|
||||
//! 两条判据刻意不在这里重算:
|
||||
//! 1. **能不能用 `--graphicsBackend`** 来自 [`LaunchFacts::allowed`](core 的 `graphics_backend_allowed`,
|
||||
//! presenter 的 host 已经算好)——UI 不复制版本号比较;
|
||||
//! 2. **生效值**(内存/Java/后端)同样来自 facts,表单里画的是实例层原文。
|
||||
//!
|
||||
//! 文案全走 [`Texts`](守门见 `instances/tests/i18n.rs` 的白名单与死 key 扫描)。
|
||||
|
||||
use iced::widget::{column, container, row};
|
||||
use iced::{Background, 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;
|
||||
|
||||
use crate::app::components::buttons::{self, Button};
|
||||
use crate::app::components::inputs::{text_field, Field};
|
||||
use crate::app::components::selection::{chip, Chip};
|
||||
|
||||
use crate::theme::Ui;
|
||||
|
||||
use super::super::model::LaunchFacts;
|
||||
use super::super::Message as PageMessage;
|
||||
use super::state::{LaunchForm, Message};
|
||||
use super::Texts;
|
||||
|
||||
/// 页内消息 → 页面消息(本文件的控件树统一画 `pages::Message`)。
|
||||
fn page(message: Message) -> PageMessage {
|
||||
PageMessage::Instances(message)
|
||||
}
|
||||
|
||||
/// 「启动参数」区(在详情列的正文段之后、动作行之前)。
|
||||
pub(super) fn section<'a>(
|
||||
ui: &'a Ui,
|
||||
facts: &'a LaunchFacts,
|
||||
form: &'a LaunchForm,
|
||||
texts: Texts,
|
||||
) -> Element<'a, PageMessage> {
|
||||
let mut body = column![frame_title(ui, texts.launch_title())]
|
||||
.spacing(ui.space(Space::X3))
|
||||
.width(Length::Fill);
|
||||
|
||||
body = body.push(args_field(
|
||||
ui,
|
||||
texts.launch_jvm(),
|
||||
texts.launch_jvm_hint(),
|
||||
form.jvm.as_str(),
|
||||
|value| page(Message::Jvm(value)),
|
||||
));
|
||||
body = body.push(args_field(
|
||||
ui,
|
||||
texts.launch_game(),
|
||||
texts.launch_game_hint(),
|
||||
form.game.as_str(),
|
||||
|value| page(Message::Game(value)),
|
||||
));
|
||||
body = body.push(text_field_with_hint(
|
||||
ui,
|
||||
texts.launch_memory(),
|
||||
form.memory.as_str(),
|
||||
super::super::frame::text_role_owned(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
texts.launch_memory_hint(facts.memory_effective_mb),
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
),
|
||||
|value| page(Message::Memory(value)),
|
||||
));
|
||||
body = body.push(text_field_with_hint(
|
||||
ui,
|
||||
texts.launch_java(),
|
||||
form.java.as_str(),
|
||||
super::super::frame::text_role_owned(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
texts.launch_java_hint().to_string(),
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
),
|
||||
|value| page(Message::Java(value)),
|
||||
));
|
||||
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()
|
||||
}
|
||||
|
||||
fn frame_title<'a>(ui: &'a Ui, title: &'a str) -> Element<'a, PageMessage> {
|
||||
super::super::frame::text_role(
|
||||
ui,
|
||||
TypeRole::LabelMedium,
|
||||
true,
|
||||
title,
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
)
|
||||
}
|
||||
|
||||
/// 一段参数输入 + 一行提示(单行受控输入;参数按空白切分,与 core 的 `tokenize` 同口径)。
|
||||
fn args_field<'a>(
|
||||
ui: &'a Ui,
|
||||
label: &'a str,
|
||||
hint: &'a str,
|
||||
value: &'a str,
|
||||
on_input: impl Fn(String) -> PageMessage + 'a,
|
||||
) -> Element<'a, PageMessage> {
|
||||
column![
|
||||
text_field(ui, Field::new(value).label(label), on_input),
|
||||
super::super::frame::text_role(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
hint,
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
),
|
||||
]
|
||||
.spacing(ui.space(Space::X1))
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// 单行输入 + 一行提示(提示是**拥有的**字符串,所以不能走 `Field::supporting` 的借用口径)。
|
||||
fn text_field_with_hint<'a>(
|
||||
ui: &'a Ui,
|
||||
label: &'a str,
|
||||
value: &'a str,
|
||||
hint: Element<'a, PageMessage>,
|
||||
on_input: impl Fn(String) -> PageMessage + 'a,
|
||||
) -> Element<'a, PageMessage> {
|
||||
column![
|
||||
text_field(ui, Field::new(value).label(label), on_input),
|
||||
hint,
|
||||
]
|
||||
.spacing(ui.space(Space::X1))
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// 三态后端:版本不够时整体禁用(chips 不带 `on_press`)+ 一行门槛提示。
|
||||
fn backend_row<'a>(
|
||||
ui: &'a Ui,
|
||||
facts: &'a LaunchFacts,
|
||||
form: &'a LaunchForm,
|
||||
texts: Texts,
|
||||
) -> Element<'a, PageMessage> {
|
||||
let allowed = facts.allowed;
|
||||
let mut chips = row![].spacing(ui.space(Space::X2));
|
||||
for id in &facts.options {
|
||||
let label = texts.backend_label(id).unwrap_or(id.as_str());
|
||||
let selected = form.backend == *id;
|
||||
let spec = if allowed {
|
||||
Chip::filter(label, selected, page(Message::Backend(id.clone())))
|
||||
} else {
|
||||
Chip::filter(label, selected, page(Message::Backend(id.clone()))).disabled()
|
||||
};
|
||||
chips = chips.push(chip(ui, spec));
|
||||
}
|
||||
|
||||
let mut meta = column![
|
||||
super::super::frame::text_role(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
texts.launch_backend(),
|
||||
ui.color(Role::OnSurface),
|
||||
),
|
||||
chips,
|
||||
super::super::frame::text_role_owned(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
texts.launch_effective(&facts.backend_effective),
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
),
|
||||
]
|
||||
.spacing(ui.space(Space::X1))
|
||||
.width(Length::Fill);
|
||||
|
||||
if !allowed {
|
||||
// 灰化 + 明确提示:既不说「已生效」,也不静默吞掉用户的选择。
|
||||
meta = meta.push(super::super::frame::text_role_owned(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
texts.launch_backend_gated(&facts.mc_version),
|
||||
ui.color(Role::Error),
|
||||
));
|
||||
}
|
||||
meta.into()
|
||||
}
|
||||
|
||||
fn actions_row<'a>(ui: &'a Ui, texts: Texts) -> Element<'a, PageMessage> {
|
||||
row![
|
||||
buttons::filled(ui, Button::new(texts.launch_save(), page(Message::Save))),
|
||||
buttons::text(ui, Button::new(texts.launch_reset(), page(Message::Reset))),
|
||||
]
|
||||
.spacing(ui.space(Space::X2))
|
||||
.into()
|
||||
}
|
||||
Reference in new issue
Block a user