feat(m175): Java 设置页接真运行时(扫描 / 手动路径 / 默认 JDK / 内存 / JVM 参数)
用户实测四条抱怨逐条对应: - 「没有自动检测本机 Java」:首屏自动扫一次盘(托管根 + JAVA_HOME + PATH + 标准安装位置 + 注册表),之后由「立即扫描」手动触发;扫描在后台线程里跑,带 done/total 进度与取消 - 「显示 Temurin 21.0.4 不知道是不是硬编码」:演示行与绊线测试一起删掉,列表来自真读盘 - 「没一个能改」:默认内存(MB,512~65536,范围与 schema 同源)与 JVM 参数可真改、真落盘、读回 - 「不能手动指定 Java 路径 / 不能改默认版本」:手动添加走严格校验(真跑 java -version), 设为默认写 java.selected_path 并给出「为什么选它」(当前实例的版本门槛 + 每行满足与否) core:java::manual(严格探测 + 厂商识别 + 名单持久化)、java::staged(候选收集 + 带进度与 取消的扫描)、java::proc(带超时且可打断的子进程执行:卡住的 java -version 会被 kill)。 i18n:catalog/settings/java.rs 55 条中英 + 金表按装配顺序插入。 复审 r1(p2-5items)全修: - P2-1 「设为默认」不再清脏:只写 java.selected_path,内存/JVM 草稿一个字节都不动 - P2-2 错误文案全部走 i18n(三档「写不进去」/「路径太长」/「名单写不进去」各自一个 key), 不再把 Rust 枚举名当人话 - P2-3 手动名单落盘有正面断言(加一条 → 从盘上新读一份 → 在) - P2-4 版本号显示到小版本(17.0.13 / 17.0.9 可区分),解析不出来时给原始版本串 - P2-5 每候选 8 秒超时并 kill、取消打断正在跑的探测、扫描槽挂在 State 上、线程活着期间 不许起第二个(界面显示「正在取消…」) 复审 r2(p2-2items)全修: - P2-1 空态不再指向不存在的「Java 页」,改成真实存在的 `ull java install 21` 命令行入口 - P2-2 `auto_scan_due` 增加 `!report.cancelled`:取消过一次之后本进程不再自动扫, 配置文件再变(存内存 / 改主题)也不会把取消无声撤销;手动「立即扫描」不受影响 - 顺带:proc 的 kill+wait 补「子进程真死了」的回归守卫(标记文件从未出现)、 core 的 Schema 错误改成页面自己的 key(不再把中文 Display 直出给英文界面)、 默认态 chip 用新的 `Chip::badge`(不再挂一条永远收不到的消息)
This commit is contained in:
1 parent
30b61e2684
commit
df331f6a60
26 files changed
+4030
-85
No files matched your search
@@ -0,0 +1,246 @@
|
||||
//! 「Java」分组的**编辑区**(M175):手动路径 / 默认内存 / JVM 参数 / 保存与回执。
|
||||
//!
|
||||
//! 从 `view.rs` 拆出来的(那个文件加了守卫之后贴到 438 行,越过 400 行硬线):这里全是
|
||||
//! 「用户能改的东西」,`view.rs` 只剩「扫到了什么」与版面。两半共用 `view::msg` 这一个消息口,
|
||||
//! 所以「一条控件对应哪条消息」不会各写一遍。
|
||||
|
||||
use iced::widget::{column, container, row};
|
||||
use iced::{Alignment, Element, Length};
|
||||
|
||||
use unlucky_design::color::Role;
|
||||
use unlucky_design::spacing::Space;
|
||||
use unlucky_design::typography::TypeRole;
|
||||
use unlucky_i18n::{Catalog, Key};
|
||||
|
||||
use crate::app::components::buttons::{self, Button};
|
||||
use crate::app::components::inputs::{text_field, Field, FieldVariant};
|
||||
use crate::pages::frame::text_role_owned;
|
||||
use crate::pages::Message;
|
||||
use crate::theme::Ui;
|
||||
|
||||
use super::state::{Layer, Notice, Refusal, State, MAX_MEMORY_MB, MIN_MEMORY_MB};
|
||||
use super::view::msg;
|
||||
use super::Message as JavaMessage;
|
||||
|
||||
/// 手动添加一行:输入框 + 「添加」按钮 + 原因。
|
||||
pub(super) fn add_row<'a>(ui: &'a Ui, catalog: Catalog, state: &'a State) -> Element<'a, Message> {
|
||||
let field = Field::new(&state.add_draft)
|
||||
.variant(FieldVariant::Outlined)
|
||||
.label(catalog.text(Key::SettingsJavaAdd))
|
||||
.placeholder(catalog.text(Key::SettingsJavaAddPlaceholder))
|
||||
.supporting(catalog.text(Key::SettingsJavaAddHint));
|
||||
let add = if state.add_draft.trim().is_empty() || !state.storage {
|
||||
buttons::outlined(
|
||||
ui,
|
||||
Button::disabled(catalog.text(Key::SettingsJavaAddAction)),
|
||||
)
|
||||
} else {
|
||||
buttons::outlined(
|
||||
ui,
|
||||
Button::new(
|
||||
catalog.text(Key::SettingsJavaAddAction),
|
||||
msg(JavaMessage::Add),
|
||||
),
|
||||
)
|
||||
};
|
||||
row![
|
||||
container(text_field(ui, field, |value| msg(JavaMessage::AddDraft(
|
||||
value
|
||||
))))
|
||||
.width(Length::Fill),
|
||||
add,
|
||||
]
|
||||
.spacing(ui.space(Space::X2))
|
||||
.width(Length::Fill)
|
||||
.align_y(Alignment::Center)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// 默认内存:数字框 + 范围 + 「生效值来自哪一层」。
|
||||
pub(super) fn memory_field<'a>(
|
||||
ui: &'a Ui,
|
||||
catalog: Catalog,
|
||||
state: &'a State,
|
||||
) -> Element<'a, Message> {
|
||||
let range = catalog.render(
|
||||
Key::SettingsJavaMemoryHint,
|
||||
&[
|
||||
("min", MIN_MEMORY_MB.to_string().as_str()),
|
||||
("max", MAX_MEMORY_MB.to_string().as_str()),
|
||||
],
|
||||
);
|
||||
let field = Field::new(&state.memory)
|
||||
.variant(FieldVariant::Outlined)
|
||||
.label(catalog.text(Key::SettingsJavaMemory));
|
||||
// 校验不过就把输入框本身标红(不只是下面那行字):用户一眼看到「是这个框有问题」。
|
||||
let field = match state.validate() {
|
||||
Err(Refusal::MemoryEmpty | Refusal::MemoryNotNumber | Refusal::MemoryRange) => {
|
||||
field.error()
|
||||
}
|
||||
_ => field,
|
||||
};
|
||||
let effective = match state.layer() {
|
||||
Layer::Global => catalog.render(
|
||||
Key::SettingsJavaLayerGlobal,
|
||||
&[("mb", state.saved_memory.to_string().as_str())],
|
||||
),
|
||||
Layer::Instance { instance, mb } => catalog.render(
|
||||
Key::SettingsJavaLayerInstance,
|
||||
&[
|
||||
("mb", mb.to_string().as_str()),
|
||||
("instance", instance.as_str()),
|
||||
],
|
||||
),
|
||||
};
|
||||
column![
|
||||
text_field(ui, field, |value| msg(JavaMessage::EditMemory(value))),
|
||||
text_role_owned(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
effective,
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
),
|
||||
text_role_owned(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
range,
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
),
|
||||
]
|
||||
.spacing(ui.space(Space::X1))
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// JVM 额外参数:文本框 + 「会拼到每次启动」的说明。
|
||||
pub(super) fn jvm_field<'a>(
|
||||
ui: &'a Ui,
|
||||
catalog: Catalog,
|
||||
state: &'a State,
|
||||
) -> Element<'a, Message> {
|
||||
let field = Field::new(&state.jvm)
|
||||
.variant(FieldVariant::Outlined)
|
||||
.label(catalog.text(Key::SettingsJavaJvm))
|
||||
.placeholder(catalog.text(Key::SettingsJavaJvmPlaceholder))
|
||||
.supporting(catalog.text(Key::SettingsJavaJvmHint));
|
||||
let field = if matches!(state.validate(), Err(Refusal::JvmQuotes)) {
|
||||
field.error()
|
||||
} else {
|
||||
field
|
||||
};
|
||||
// 已保存值:空的时候说「还没有保存任何额外参数」,而不是让「已保存的值:」后面空着。
|
||||
let saved = if state.saved_jvm.trim().is_empty() {
|
||||
catalog.text(Key::SettingsJavaJvmSavedNone).to_string()
|
||||
} else {
|
||||
catalog.render(
|
||||
Key::SettingsJavaJvmSaved,
|
||||
&[("value", state.saved_jvm.as_str())],
|
||||
)
|
||||
};
|
||||
column![
|
||||
text_field(ui, field, |value| msg(JavaMessage::EditJvm(value))),
|
||||
text_role_owned(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
saved,
|
||||
ui.color(Role::OnSurfaceVariant),
|
||||
),
|
||||
]
|
||||
.spacing(ui.space(Space::X1))
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub(super) fn actions_row<'a>(
|
||||
ui: &'a Ui,
|
||||
catalog: Catalog,
|
||||
state: &'a State,
|
||||
) -> Element<'a, Message> {
|
||||
let label = catalog.text(Key::SettingsJavaSave);
|
||||
if state.can_save() {
|
||||
buttons::filled(ui, Button::new(label, msg(JavaMessage::Save)))
|
||||
} else {
|
||||
buttons::filled(ui, Button::disabled(label))
|
||||
}
|
||||
}
|
||||
|
||||
/// 按钮为什么灰着 / 下一步做什么——**永远说清**。
|
||||
pub(super) fn hint_line<'a>(
|
||||
ui: &'a Ui,
|
||||
catalog: Catalog,
|
||||
state: &'a State,
|
||||
) -> Element<'a, Message> {
|
||||
let (text, error) = if !state.storage {
|
||||
(catalog.text(Key::SettingsJavaNoStorage).to_string(), true)
|
||||
} else if state.dirty {
|
||||
(catalog.text(Key::SettingsJavaUnsaved).to_string(), false)
|
||||
} else {
|
||||
(String::new(), false)
|
||||
};
|
||||
text_role_owned(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
text,
|
||||
ui.color(if error {
|
||||
Role::Error
|
||||
} else {
|
||||
Role::OnSurfaceVariant
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn notice_line<'a>(
|
||||
ui: &'a Ui,
|
||||
catalog: Catalog,
|
||||
notice: &Notice,
|
||||
) -> Element<'a, Message> {
|
||||
let (text, error) = match notice {
|
||||
Notice::Saved => (catalog.text(Key::SettingsJavaSaved).to_string(), false),
|
||||
Notice::Refused(reason) => (
|
||||
catalog.render(
|
||||
Key::SettingsJavaRefused,
|
||||
&[("reason", catalog.text(refusal_key(*reason)))],
|
||||
),
|
||||
true,
|
||||
),
|
||||
Notice::Unwritable(reason) => (
|
||||
catalog.render(
|
||||
Key::SettingsJavaRefused,
|
||||
&[("reason", catalog.text(reason.label()))],
|
||||
),
|
||||
true,
|
||||
),
|
||||
Notice::WriteFailed => (
|
||||
catalog.render(
|
||||
Key::SettingsJavaRefused,
|
||||
&[("reason", catalog.text(Key::SettingsJavaReasonWriteFailed))],
|
||||
),
|
||||
true,
|
||||
),
|
||||
Notice::NoStorage => (catalog.text(Key::SettingsJavaNoStorage).to_string(), true),
|
||||
Notice::DefaultSet(path) => (
|
||||
catalog.render(Key::SettingsJavaDefaultSet, &[("path", path.as_str())]),
|
||||
false,
|
||||
),
|
||||
Notice::Added(path) => (
|
||||
catalog.render(Key::SettingsJavaAdded, &[("path", path.as_str())]),
|
||||
false,
|
||||
),
|
||||
};
|
||||
text_role_owned(
|
||||
ui,
|
||||
TypeRole::BodySmall,
|
||||
false,
|
||||
text,
|
||||
ui.color(if error { Role::Error } else { Role::OnSurface }),
|
||||
)
|
||||
}
|
||||
|
||||
/// 拒绝原因 → 文案 key(`Refusal::label` 已是这张表,这里只是给 `match` 一个名字)。
|
||||
pub(super) fn refusal_key(refusal: Refusal) -> Key {
|
||||
refusal.label()
|
||||
}
|
||||
Reference in new issue
Block a user