M234 把 `interaction::Base` 与组件侧的 `.base(..)` 接好了,但页面层仍是默认的画布底: 卡片(`Role::SurfaceContainerLow`)里的禁用按钮 / chip / 开关把 10% / 12% / 38% 叠在画布色上。 M223 记的「卡片里禁用控件 CR 1.05、形丢失」因此只在 gallery 里修好,产品页没修。 - 导出容器底:`pages::frame::BASE = Base::of(MaterialLayer::Card.surface_role())` —— 取的是 `card()` **真正画出来的那一层**(不是「反正是 surface」);`logs::panel::BASE` 是同一个常量的别名(两张卡画的是同一个材质层)。 - 接线(改前一律 `Base::CANVAS`,改后 `frame::BASE`):设置·Java(扫描 / 取消 / 设为默认 chip / 三个输入框 / 添加 / 保存 / 预置项 chip / 下载徽标);设置·通用(语言三档、主题三档、日志行 两个手写按钮 → 状态层叠 `BASE.role()`);实例详情(MODS 开关、SAVES 的重试与打开文件夹、 启动参数区的两个输入框 + 后端 chip + 保存 / 恢复);日志页(诊断卡页脚 `Copy summary`)。 详情头的启动 / 停止 / 动作行**不在卡里**,保持画布底并加注释。 - 判据:`pages/frame/tests.rs` 逐值断言「`BASE` == 卡片画出来的颜色」+ 源码结构盯 `card()` 不许改画别的材质层;`pages/settings/tests/java_shot.rs` 是**页面级像素判据**(读 1180×760 真截图、逐位匹配:卡区里「叠卡底」3136 px vs「叠画布」21 px,深色 3128 / 1); `pages/tests/base_wiring.rs` 数接线点,守像素够不到的页面(实例详情 MODS / SAVES)。 - 变异自证:撤掉「立即扫描」的 `.base(BASE)` ⇒ 像素判据红(73 px vs 3083 px,退出码 101); 撤掉诊断卡页脚的 `.base(..)` ⇒ 接线判据红(0 vs 1,退出码 101)。均已还原。 - 门禁:fmt / check -p unlucky-app-iced --all-targets / test --workspace(3344 通过,0 失败)/ clippy --workspace --all-targets -D warnings / 文件 ≤400(最大 389),全部本地过。 - 已知未尽:面板 α<1 的半透明卡(`Base` 只装一个 `Role`)、GPU 档、实例详情与日志页在 1180×760 出图里没有可量测的禁用控件(夹具缺 running,已报 finding);app_bar / adopt pick / 设置页分组列仍未导出 `BASE`;设置·下载 / 更新 / 助手、账户页、内容库、下载页仍未接线。
339 lines
11 KiB
Rust
339 lines
11 KiB
Rust
//! 三个设置键的**编辑区**(M197):`java.installRoot` / `java.vendorPriority` / `java.auto_install`。
|
||
//!
|
||
//! 每一段都是同一种形状:**能改的控件 + 现在生效什么 + 这个生效值从哪一层来**。第三件事是任务书
|
||
//! 点名的(「两者都要能解释『生效值现在是什么、从哪一层来』」),所以三段的最后一行永远不是空的:
|
||
//!
|
||
//! | 段 | 控件 | 生效值从哪来 |
|
||
//! |---|---|---|
|
||
//! | 安装目录 | 文本框(可以留空) | `java.installRoot` 或 `core.data_mode` |
|
||
//! | 发行版顺序 | 4 行 + 上移 / 下移 + 恢复默认 | `java.vendorPriority` 或地区默认序 |
|
||
//! | 缺 JDK 时自动准备 | 开 / 关两片 | 设置本身(出厂开) |
|
||
//!
|
||
//! 文案一律走 [`Catalog`](本组要 `Lang`,走 `settings/mod.rs` 的按下标分发);渲染器自己不判断
|
||
//! 层级,`RootLayer` / `OrderLayer` 是 app 层算好的结论。
|
||
|
||
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::app::components::selection::{chip, Chip};
|
||
use crate::pages::frame::{divider, text_role_owned, BASE};
|
||
use crate::pages::Message;
|
||
use crate::theme::Ui;
|
||
|
||
use super::prefs::{Effective, OrderLayer, RootLayer};
|
||
use super::state::State;
|
||
use super::view::msg;
|
||
use super::Message as JavaMessage;
|
||
|
||
/// 三段 + 保存按钮(整块的标题由设置页的外壳给,这里不再画)。
|
||
pub(super) fn section<'a>(ui: &'a Ui, catalog: Catalog, state: &'a State) -> Element<'a, Message> {
|
||
let mut block = column![].spacing(ui.space(Space::X2)).width(Length::Fill);
|
||
|
||
block = block.push(install_root(ui, catalog, state));
|
||
block = block.push(divider(ui));
|
||
block = block.push(vendor_order(ui, catalog, state));
|
||
block = block.push(divider(ui));
|
||
block = block.push(auto_install(ui, catalog, state));
|
||
block = block.push(actions(ui, catalog, state));
|
||
block.into()
|
||
}
|
||
|
||
/// `java.installRoot`:文本框 + 生效落点 + 来源层。
|
||
fn install_root<'a>(ui: &'a Ui, catalog: Catalog, state: &'a State) -> Element<'a, Message> {
|
||
let prefs = &state.prefs;
|
||
let field = Field::new(&prefs.install_root)
|
||
.variant(FieldVariant::Outlined)
|
||
.label(catalog.text(Key::SettingsJavaInstallRoot))
|
||
.placeholder(catalog.text(Key::SettingsJavaInstallRootPlaceholder))
|
||
.supporting(catalog.text(Key::SettingsJavaInstallRootHint))
|
||
.base(BASE);
|
||
let field = if prefs.validate().is_err() {
|
||
field.error()
|
||
} else {
|
||
field
|
||
};
|
||
let effective = effective_line(ui, catalog, &state.effective);
|
||
column![
|
||
text_field(ui, field, |value| msg(JavaMessage::EditInstallRoot(value))),
|
||
effective,
|
||
]
|
||
.spacing(ui.space(Space::X1))
|
||
.width(Length::Fill)
|
||
.into()
|
||
}
|
||
|
||
/// 「生效:模式 · 路径」+「从哪一层来」(解析不出标准目录时说清下一步)。
|
||
fn effective_line<'a>(
|
||
ui: &'a Ui,
|
||
catalog: Catalog,
|
||
effective: &'a Effective,
|
||
) -> Element<'a, Message> {
|
||
if effective.root_unresolved {
|
||
return text_role_owned(
|
||
ui,
|
||
TypeRole::BodySmall,
|
||
false,
|
||
catalog
|
||
.text(Key::SettingsJavaInstallRootUnresolved)
|
||
.to_string(),
|
||
ui.color(Role::Error),
|
||
);
|
||
}
|
||
let text = catalog.render(
|
||
Key::SettingsJavaInstallRootEffective,
|
||
&[
|
||
("mode", effective.root_mode),
|
||
("path", effective.root.as_str()),
|
||
],
|
||
);
|
||
let layer = match effective.root_layer {
|
||
RootLayer::Setting => catalog
|
||
.text(Key::SettingsJavaInstallRootLayerSetting)
|
||
.to_string(),
|
||
RootLayer::DataMode => catalog.render(
|
||
Key::SettingsJavaInstallRootLayerDataMode,
|
||
&[("mode", effective.root_mode)],
|
||
),
|
||
};
|
||
column![
|
||
text_role_owned(
|
||
ui,
|
||
TypeRole::BodySmall,
|
||
false,
|
||
text,
|
||
ui.color(Role::OnSurface),
|
||
),
|
||
text_role_owned(
|
||
ui,
|
||
TypeRole::BodySmall,
|
||
false,
|
||
layer,
|
||
ui.color(Role::OnSurfaceVariant),
|
||
),
|
||
]
|
||
.spacing(ui.space(Space::X1))
|
||
.width(Length::Fill)
|
||
.into()
|
||
}
|
||
|
||
/// `java.vendorPriority`:4 行顺序 + 上移 / 下移 + 恢复地区默认序。
|
||
fn vendor_order<'a>(ui: &'a Ui, catalog: Catalog, state: &'a State) -> Element<'a, Message> {
|
||
let prefs = &state.prefs;
|
||
let order = prefs.order(state.effective.domestic);
|
||
let mut list = column![text_role_owned(
|
||
ui,
|
||
TypeRole::BodyMedium,
|
||
true,
|
||
catalog.text(Key::SettingsJavaVendorPriority).to_string(),
|
||
ui.color(Role::OnSurface),
|
||
),]
|
||
.spacing(ui.space(Space::X2))
|
||
.width(Length::Fill);
|
||
|
||
for (index, vendor) in order.iter().enumerate() {
|
||
list = list.push(vendor_row(ui, catalog, index, *vendor));
|
||
}
|
||
|
||
let joined = state.effective.order.join(" → ");
|
||
let layer = match state.effective.order_layer {
|
||
OrderLayer::Setting => catalog
|
||
.text(Key::SettingsJavaVendorOrderLayerSetting)
|
||
.to_string(),
|
||
OrderLayer::Region => catalog.render(
|
||
Key::SettingsJavaVendorOrderLayerRegion,
|
||
&[(
|
||
"region",
|
||
catalog
|
||
.text(if state.effective.domestic {
|
||
Key::SettingsJavaRegionDomestic
|
||
} else {
|
||
Key::SettingsJavaRegionGlobal
|
||
})
|
||
.to_string()
|
||
.as_str(),
|
||
)],
|
||
),
|
||
};
|
||
list = list.push(text_role_owned(
|
||
ui,
|
||
TypeRole::BodySmall,
|
||
false,
|
||
catalog.render(
|
||
Key::SettingsJavaVendorOrderEffective,
|
||
&[("order", joined.as_str())],
|
||
),
|
||
ui.color(Role::OnSurface),
|
||
));
|
||
list = list.push(text_role_owned(
|
||
ui,
|
||
TypeRole::BodySmall,
|
||
false,
|
||
layer,
|
||
ui.color(Role::OnSurfaceVariant),
|
||
));
|
||
// 「恢复地区默认序」只在真的排过顺序时才有意义(已经跟地区走时它是个空操作)。
|
||
if !prefs.priority.is_empty() {
|
||
list = list.push(buttons::outlined(
|
||
ui,
|
||
Button::new(
|
||
catalog.text(Key::SettingsJavaVendorReset),
|
||
msg(JavaMessage::ResetPriority),
|
||
)
|
||
.base(BASE),
|
||
));
|
||
}
|
||
list.into()
|
||
}
|
||
|
||
/// 一家发行版一行:名 + 它发哪些大版本 + 上移 / 下移。
|
||
fn vendor_row<'a>(
|
||
ui: &'a Ui,
|
||
catalog: Catalog,
|
||
index: usize,
|
||
vendor: unlucky_core::game::java::JdkVendor,
|
||
) -> Element<'a, Message> {
|
||
let majors = vendor
|
||
.hint_majors()
|
||
.iter()
|
||
.map(u32::to_string)
|
||
.collect::<Vec<_>>()
|
||
.join("/");
|
||
let supporting = catalog.render(
|
||
Key::SettingsJavaVendorSupports,
|
||
&[("majors", majors.as_str())],
|
||
);
|
||
// 第一条的「上移」与最后一条的「下移」都是空操作:照旧可点,点了不改顺序也不标脏
|
||
// (比灰掉更省心——灰掉的 chip 在 424 宽的列里同样占位,用户还得去猜为什么灰)。
|
||
let up = chip(
|
||
ui,
|
||
Chip::filter(
|
||
catalog.text(Key::SettingsJavaVendorUp),
|
||
false,
|
||
msg(JavaMessage::MoveVendor { index, up: true }),
|
||
)
|
||
.base(BASE),
|
||
);
|
||
let down = chip(
|
||
ui,
|
||
Chip::filter(
|
||
catalog.text(Key::SettingsJavaVendorDown),
|
||
false,
|
||
msg(JavaMessage::MoveVendor { index, up: false }),
|
||
)
|
||
.base(BASE),
|
||
);
|
||
row![
|
||
container(
|
||
column![
|
||
text_role_owned(
|
||
ui,
|
||
TypeRole::BodyMedium,
|
||
index == 0,
|
||
format!("{}. {}", index + 1, vendor.display()),
|
||
ui.color(Role::OnSurface),
|
||
),
|
||
text_role_owned(
|
||
ui,
|
||
TypeRole::BodySmall,
|
||
false,
|
||
supporting,
|
||
ui.color(Role::OnSurfaceVariant),
|
||
),
|
||
]
|
||
.spacing(ui.space(Space::X1))
|
||
.width(Length::Fill),
|
||
)
|
||
.width(Length::Fill),
|
||
up,
|
||
down,
|
||
]
|
||
.spacing(ui.space(Space::X1))
|
||
.width(Length::Fill)
|
||
.align_y(Alignment::Center)
|
||
.into()
|
||
}
|
||
|
||
/// `java.auto_install`:开 / 关两片 + 「它到底做什么」那句。
|
||
fn auto_install<'a>(ui: &'a Ui, catalog: Catalog, state: &'a State) -> Element<'a, Message> {
|
||
let on = state.prefs.auto_install;
|
||
let chips = row![
|
||
chip(
|
||
ui,
|
||
Chip::filter(
|
||
catalog.text(Key::SettingsJavaAutoInstallOn),
|
||
on,
|
||
msg(JavaMessage::ToggleAutoInstall(true)),
|
||
)
|
||
.base(BASE),
|
||
),
|
||
chip(
|
||
ui,
|
||
Chip::filter(
|
||
catalog.text(Key::SettingsJavaAutoInstallOff),
|
||
!on,
|
||
msg(JavaMessage::ToggleAutoInstall(false)),
|
||
)
|
||
.base(BASE),
|
||
),
|
||
]
|
||
.spacing(ui.space(Space::X1));
|
||
column![
|
||
text_role_owned(
|
||
ui,
|
||
TypeRole::BodyMedium,
|
||
true,
|
||
catalog.text(Key::SettingsJavaAutoInstall).to_string(),
|
||
ui.color(Role::OnSurface),
|
||
),
|
||
chips,
|
||
text_role_owned(
|
||
ui,
|
||
TypeRole::BodySmall,
|
||
false,
|
||
catalog.text(Key::SettingsJavaAutoInstallHint).to_string(),
|
||
ui.color(Role::OnSurfaceVariant),
|
||
),
|
||
]
|
||
.spacing(ui.space(Space::X1))
|
||
.width(Length::Fill)
|
||
.into()
|
||
}
|
||
|
||
/// 保存按钮 + 「有未保存改动」那行。
|
||
fn actions<'a>(ui: &'a Ui, catalog: Catalog, state: &'a State) -> Element<'a, Message> {
|
||
let label = catalog.text(Key::SettingsJavaPrefsSave);
|
||
let save = if state.prefs.dirty() && state.storage {
|
||
buttons::filled(
|
||
ui,
|
||
Button::new(label, msg(JavaMessage::SavePrefs)).base(BASE),
|
||
)
|
||
} else {
|
||
buttons::filled(ui, Button::disabled(label).base(BASE))
|
||
};
|
||
let hint = if state.prefs.dirty() {
|
||
catalog.text(Key::SettingsJavaPrefsUnsaved).to_string()
|
||
} else {
|
||
String::new()
|
||
};
|
||
column![
|
||
save,
|
||
text_role_owned(
|
||
ui,
|
||
TypeRole::BodySmall,
|
||
false,
|
||
hint,
|
||
ui.color(Role::OnSurfaceVariant),
|
||
),
|
||
]
|
||
.spacing(ui.space(Space::X1))
|
||
.width(Length::Fill)
|
||
.into()
|
||
}
|