CI / fmt (rustfmt --check) (push) Waiting to run
CI / clippy (ubuntu-latest) (push) Waiting to run
CI / clippy (windows-latest) (push) Waiting to run
CI / test (ubuntu-latest) (push) Waiting to run
CI / test (windows-latest) (push) Waiting to run
CI / build release (ubuntu-latest) (push) Waiting to run
CI / build release (windows-latest) (push) Waiting to run
CI / runtime smoke (ubuntu x64, xvfb) (push) Waiting to run
CI / runtime smoke (macos arm64, probe) (push) Skipped
CI / update-e2e (windows x64) (push) Waiting to run
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Waiting to run
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Waiting to run
Guards / core 零 UI 守门 (push) Waiting to run
Guards / presenter 零 UI 守门 (push) Waiting to run
Guards / design 零第三方依赖守门 (push) Waiting to run
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Waiting to run
Guards / 文件行数 ≤400 守门 (push) Waiting to run
Guards / 依赖许可与来源(cargo-deny) (push) Waiting to run
Release guard / 发布标记语义守门(push tag → 稳定版) (push) Waiting to run
Signing status(播报,不是门禁) / 判据自测(三行/退出码/变异重放/接线) (push) Waiting to run
Signing status(播报,不是门禁) / 签名就绪度(播报) (push) Waiting to run
Release / 选槽位(默认四个:Windows 两档 + Linux 两档) (push) Waiting to run
本批内容见 CHANGELOG.md 的 0.2.4 段。按 D41 用孤儿快照推 main,保留「公开史只有里程碑」的模型。
206 lines
8.4 KiB
Rust
206 lines
8.4 KiB
Rust
//! 出图用的向导状态夹具(D30 视觉验收的输入)。
|
||
//!
|
||
//! ## 为什么要有它
|
||
//!
|
||
//! 向导是**四段有状态**的界面,而截图通道只跑一次 `view()`(第 3 帧落盘),没有真人点按钮。
|
||
//! 组件库的做法是 `--screen=gallery` + `ForcedState`,页面层的做法是 `pages/instances/demo.rs`
|
||
//! 的 `DEMO`——同一类要求,同一类做法:**给渲染器一份确定的状态**,而不是让截图去赌异步时序。
|
||
//!
|
||
//! ## 哪些是真、哪些是夹具(复审请照这条核对)
|
||
//!
|
||
//! | 内容 | 来源 |
|
||
//! |---|---|
|
||
//! | 候选 / 问题 / 失败原因 | **夹具**(合成的 id 与问题文本,只为把版面撑到真实形态) |
|
||
//! | 运行目录 / 隔离档 | 夹具(固定路径,便于两张图逐像素对照) |
|
||
//! | **后果原文** | **真**:`unlucky_core::game::adopt::target_notes()` 现算,一个字不抄 |
|
||
//! | 文案(标题/标签/按钮) | **真**:`unlucky_i18n` 目录 |
|
||
//! | 渲染 | **真**:`--software-renderer`(tiny-skia)1180×760,与产品路径同一棵控件树 |
|
||
//!
|
||
//! 也就是说:这些图证明**版面与文案**,不证明 core 的判据(判据的证据在 `host/adopt/tests.rs`
|
||
//! 与 `core/tests/adopt/`)。
|
||
//!
|
||
//! ## 怎么用
|
||
//!
|
||
//! ```text
|
||
//! ULL_ADOPT_STATE=pick|pick-selected|empty|preview|shared|failed|done \
|
||
//! unlucky-launcher-iced.exe --software-renderer --screenshot <path> [--screenshot-theme dark]
|
||
//! ```
|
||
//!
|
||
//! 只在 `--screenshot` 已经给了的情况下生效(交互模式读这个环境变量没有意义,直接忽略)。
|
||
|
||
use std::path::PathBuf;
|
||
|
||
use unlucky_core::game::adopt::{target_notes, AdoptTarget};
|
||
use unlucky_presenter::dto::{
|
||
AdoptCandidateView, AdoptDoneView, AdoptEvent, AdoptFailureView, AdoptIsolationView,
|
||
AdoptPlanView, AdoptProblemView, AdoptScanView, AdoptState, AdoptTargetView,
|
||
};
|
||
|
||
use super::{Busy, Form};
|
||
|
||
/// 夹具里的版本目录(只是**画**出来的路径,不碰盘)。
|
||
const VERSIONS_DIR: &str = "D:/minecraft/.minecraft/versions";
|
||
|
||
/// 夹具里的数据区运行目录。
|
||
const INSTANCE_RUN_DIR: &str = "D:/ULL/.ULL/data/instances/Better-MC";
|
||
|
||
/// 夹具里的游戏根。
|
||
const GAME_ROOT: &str = "D:/minecraft/.minecraft";
|
||
|
||
/// 环境变量名(见模块文档)。
|
||
pub(crate) const ENV: &str = "ULL_ADOPT_STATE";
|
||
|
||
/// 按名字造一份「打开着的向导」;不认识的名字返回 `None`(不影响正常启动)。
|
||
pub(crate) fn seed(name: &str) -> Option<(Form, AdoptState)> {
|
||
let name = name.trim().to_ascii_lowercase();
|
||
let mut form = Form {
|
||
open: true,
|
||
..Form::default()
|
||
};
|
||
let mut state = AdoptState::default();
|
||
|
||
match name.as_str() {
|
||
// 扫描完成、还没选版本(列表里三态齐全:可导入 / 已登记 / 有问题)
|
||
"pick" => {
|
||
state.apply(AdoptEvent::Scan(Box::new(scan_view())));
|
||
form.busy = Busy::Idle;
|
||
}
|
||
// 扫描完成、**已选中一个可导入的版本**(选中行底色 + 预检按钮启用态)
|
||
"pick-selected" => {
|
||
state.apply(AdoptEvent::Scan(Box::new(scan_view())));
|
||
form.selected = Some("1.20.1".to_string());
|
||
form.busy = Busy::Idle;
|
||
}
|
||
// 游戏根下真没有版本(空态)
|
||
"empty" => {
|
||
state.apply(AdoptEvent::Scan(Box::new(AdoptScanView {
|
||
versions_dir: PathBuf::from(VERSIONS_DIR),
|
||
candidates: Vec::new(),
|
||
})));
|
||
form.busy = Busy::Idle;
|
||
}
|
||
// 选中缺省档(InstanceDir)并点过预检
|
||
"preview" => {
|
||
state.apply(AdoptEvent::Scan(Box::new(scan_view())));
|
||
state.apply(AdoptEvent::Planned(Box::new(plan_view(
|
||
AdoptTargetView::InstanceDir,
|
||
AdoptIsolationView::Custom,
|
||
INSTANCE_RUN_DIR,
|
||
))));
|
||
form.selected = Some("1.20.1".to_string());
|
||
form.checked = true;
|
||
form.busy = Busy::Idle;
|
||
}
|
||
// 选中共享档(Shared):后果原文由 core `target_notes()` 现算
|
||
"shared" => {
|
||
state.apply(AdoptEvent::Scan(Box::new(scan_view())));
|
||
state.apply(AdoptEvent::Planned(Box::new(plan_view(
|
||
AdoptTargetView::Shared,
|
||
AdoptIsolationView::Shared,
|
||
GAME_ROOT,
|
||
))));
|
||
form.selected = Some("1.20.1".to_string());
|
||
form.target = AdoptTargetView::Shared;
|
||
form.checked = true;
|
||
form.busy = Busy::Idle;
|
||
}
|
||
// 预检失败:两条问题逐条列出(不许折叠成一句)
|
||
"failed" => {
|
||
state.apply(AdoptEvent::Scan(Box::new(scan_view())));
|
||
state.apply(AdoptEvent::Failed(Box::new(AdoptFailureView {
|
||
code: "adopt_not_adoptable".to_string(),
|
||
message: "版本 1.19.4 不能登记:清单不是合法 JSON".to_string(),
|
||
problems: vec![
|
||
AdoptProblemView {
|
||
code: "manifest_invalid_json".to_string(),
|
||
message: format!(
|
||
"清单不是合法 JSON {VERSIONS_DIR}/1.19.4/1.19.4.json:expected `,` or `}}` at line 12 column 3"
|
||
),
|
||
},
|
||
AdoptProblemView {
|
||
code: "missing_parent".to_string(),
|
||
message: "版本 1.19.4 继承的父版本 1.19.3 不在盘上".to_string(),
|
||
},
|
||
],
|
||
})));
|
||
form.selected = Some("1.19.4".to_string());
|
||
form.checked = true;
|
||
form.busy = Busy::Idle;
|
||
}
|
||
// 登记完成
|
||
"done" => {
|
||
state.apply(AdoptEvent::Scan(Box::new(scan_view())));
|
||
state.apply(AdoptEvent::Done(Box::new(AdoptDoneView {
|
||
instance_id: "2f1c0a5e-7f4b-4c1d-9a2e-6b8d5c3a1f70".to_string(),
|
||
name: "Better MC".to_string(),
|
||
version_id: "1.20.1-fabric-0.15.0".to_string(),
|
||
isolation: AdoptIsolationView::Custom,
|
||
run_dir: PathBuf::from(INSTANCE_RUN_DIR),
|
||
dir_created: true,
|
||
notes: target_notes(&AdoptTarget::InstanceDir, &PathBuf::from(INSTANCE_RUN_DIR)),
|
||
})));
|
||
form.selected = Some("1.20.1-fabric-0.15.0".to_string());
|
||
form.checked = true;
|
||
form.busy = Busy::Idle;
|
||
}
|
||
_ => return None,
|
||
}
|
||
Some((form, state))
|
||
}
|
||
|
||
/// 一份扫描结果(三态齐全)。
|
||
fn scan_view() -> AdoptScanView {
|
||
AdoptScanView {
|
||
versions_dir: PathBuf::from(VERSIONS_DIR),
|
||
candidates: vec![
|
||
AdoptCandidateView {
|
||
version_id: "1.20.1".to_string(),
|
||
jar_present: true,
|
||
inherits: Vec::new(),
|
||
registered: Vec::new(),
|
||
problems: Vec::new(),
|
||
},
|
||
AdoptCandidateView {
|
||
version_id: "1.20.1-fabric-0.15.0".to_string(),
|
||
jar_present: false,
|
||
inherits: vec!["1.20.1-fabric-0.15.0".to_string(), "1.20.1".to_string()],
|
||
registered: vec!["Better MC".to_string()],
|
||
problems: Vec::new(),
|
||
},
|
||
AdoptCandidateView {
|
||
version_id: "1.19.4".to_string(),
|
||
jar_present: true,
|
||
inherits: Vec::new(),
|
||
registered: Vec::new(),
|
||
problems: vec![AdoptProblemView {
|
||
code: "manifest_invalid_json".to_string(),
|
||
message: format!(
|
||
"清单不是合法 JSON {VERSIONS_DIR}/1.19.4/1.19.4.json:expected `,` or `}}` at line 12 column 3"
|
||
),
|
||
}],
|
||
},
|
||
],
|
||
}
|
||
}
|
||
|
||
/// 一份预检结果。**`notes` 由 core 现算**(共享档/版本目录档的后果原文只有一处)。
|
||
fn plan_view(
|
||
target: AdoptTargetView,
|
||
isolation: AdoptIsolationView,
|
||
run_dir: &str,
|
||
) -> AdoptPlanView {
|
||
let core_target = match target {
|
||
AdoptTargetView::InstanceDir => AdoptTarget::InstanceDir,
|
||
AdoptTargetView::Shared => AdoptTarget::Shared,
|
||
AdoptTargetView::VersionDir => AdoptTarget::VersionDir,
|
||
};
|
||
AdoptPlanView {
|
||
version_id: "1.20.1".to_string(),
|
||
name: "1.20.1".to_string(),
|
||
target,
|
||
isolation,
|
||
run_dir: PathBuf::from(run_dir),
|
||
notes: target_notes(&core_target, &PathBuf::from(run_dir)),
|
||
}
|
||
}
|