Files
Unlucky-Launcher/crates/app-iced/src/pages/settings/java/prefs_view.rs
T
AskaEth 3dcd5fd117 feat(app-iced): 页面层把卡片底接进禁用/交互控件(M237)
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`;设置·下载 / 更新 / 助手、账户页、内容库、下载页仍未接线。
2026-11-11 07:56:11 +08:00

339 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 三个设置键的**编辑区**(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()
}