feat(app-iced): 向导的出图种子 + 卡壳几何修正(M223 视觉自检)
D30 的截图通道在本机可用(`--software-renderer` 走 tiny-skia,1180×760 落盘),所以不再把视觉 证据留空:新增 `ULL_ADOPT_STATE`(与 `ULL_JAVA_STATE` / `ULL_AI_STATE` 同一族,**只在 `--screenshot` 下生效**),可把向导摆到七种状态之一(pick / empty / preview / shared / failed / done + 深色),于是出图不靠「赌异步时序」。 顺带修掉三处只有出图才看得见的问题: 1. **卡壳把页脚裁掉了**:内容高于 `max_height` 时 iced 直接裁,第一版 PICK 段的「实例名」与 按钮整排不可见。现在列表段(Pick/Failed)定高 + 中间区可滚、页脚固定;短内容段 (Preview/Done)按内容收高,不留一大片空白; 2. **两枚一模一样的「关闭」**:Failed/Done 的主按钮就是关闭,文字关闭不该再出现; 3. Preview 段少了实例名一行,且标题下的摘要重复了「共用目录 · 共用游戏目录」。 夹具只造候选/问题/运行目录这些**版面数据**,后果原文仍由 core 的 `target_notes()` 现算, 图证明版面与文案、不证明 core 判据(见 `DevDocs/artifacts/m223/README.md` 的真/假表)。 为守 400 行(`components::gate` 实测 412 行红),卡壳/标题/页脚纯搬移到 `app/adopt/shell.rs`。
This commit is contained in:
1 parent
a36e17a5d3
commit
6ffbd51f60
10 files changed
+435
-171
No files matched your search
@@ -9,7 +9,13 @@
|
||||
//! | Failed | 稳定码 + 人读原因 + **`problems[]` 逐条** | `AdoptEvent::Failed` | [`panes`] |
|
||||
//! | Done | 实例名 / 运行目录 / 提示原文 | `AdoptEvent::Done` | [`panes`] |
|
||||
//!
|
||||
//! 「现在画哪一段」只有一处判据(presenter 的 `AdoptState::stage`),本文件只做投影。
|
||||
//! 「现在画哪一段」的判据只有一处:presenter 的 `AdoptState::stage`,外面套一条收窄
|
||||
//! [`Form::stage`]——`Preview` 只在用户**点过预检**(`Form::checked`)之后才算数。收窄的理由:
|
||||
//! 共享档 / 版本目录档一被点选就要显示后果原文(§5.2),那要用一次**零写盘**的预检换;
|
||||
//! 但那次结果不该把用户直接甩到确认页(实例名还没填完)。
|
||||
//!
|
||||
//! 卡壳与页脚在 [`shell`](定高可滚 / 收高两种几何),三段视图在 [`pick`] 与 [`panes`],
|
||||
//! 等宽与正文小工具在 `text`,D30 出图种子在 `shot`(`ULL_ADOPT_STATE`,只在 `--screenshot` 下生效)。
|
||||
//!
|
||||
//! ## 三条纪律(契约 §5 的 1/2/3)
|
||||
//!
|
||||
@@ -34,40 +40,33 @@
|
||||
|
||||
mod panes;
|
||||
mod pick;
|
||||
mod shell;
|
||||
/// D30 出图用的状态夹具(只在 `--screenshot` 下按环境变量生效)。
|
||||
pub(crate) mod shot;
|
||||
mod text;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use iced::widget::{column, container, mouse_area, opaque, row, Space};
|
||||
use iced::widget::{container, mouse_area, opaque, Space};
|
||||
use iced::{Alignment, Background, Element, Length};
|
||||
|
||||
use unlucky_design::color::Role;
|
||||
use unlucky_design::elevation::MaterialLayer;
|
||||
use unlucky_design::shape::ShapeScale;
|
||||
use unlucky_design::spacing::Space as Sp;
|
||||
use unlucky_design::typography::TypeRole;
|
||||
use unlucky_i18n::{Catalog, Key, Lang};
|
||||
use unlucky_i18n::{Catalog, Lang};
|
||||
use unlucky_presenter::dto::{
|
||||
AdoptCandidateView, AdoptPlanView, AdoptStage, AdoptState, AdoptTargetView,
|
||||
};
|
||||
|
||||
use crate::app::components::buttons::{self, Button};
|
||||
use crate::app::components::feedback::dialog::scrim;
|
||||
use crate::pages::frame;
|
||||
use crate::theme::Ui;
|
||||
|
||||
use self::text::{isolation_label, target_label, wrapped};
|
||||
use super::message::Message;
|
||||
|
||||
/// 向导卡宽度:比模态卡(560)宽一档——候选行要放「版本 id + 状态」,目标档是三枚 chip。
|
||||
const CARD_WIDTH: f32 = 640.0;
|
||||
|
||||
/// 候选列表 / 问题清单的滚动高(再多就滚,页脚按钮永远留在卡里)。
|
||||
const CARD_LIST_HEIGHT: f32 = 260.0;
|
||||
|
||||
/// 卡片的最大高(窗口更矮时整卡按窗口走:内容在中间区滚)。
|
||||
const CARD_MAX_HEIGHT: f32 = 560.0;
|
||||
const CARD_MAX_HEIGHT: f32 = 620.0;
|
||||
|
||||
/// 正在等哪一条命令的回程。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
@@ -222,7 +221,7 @@ pub(super) fn layer<'a>(
|
||||
.on_press(adopt(AdoptMessage::Close));
|
||||
|
||||
// 透明的一层只负责居中;吃点击的 `opaque` 只包卡片(点卡内不关,点遮罩才关)。
|
||||
let centered = container(opaque(card(ui, form, adopt_state, catalog)))
|
||||
let centered = container(opaque(shell::card(ui, form, adopt_state, catalog)))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.align_x(Alignment::Center)
|
||||
@@ -232,84 +231,8 @@ pub(super) fn layer<'a>(
|
||||
iced::widget::stack![base, backdrop, centered].into()
|
||||
}
|
||||
|
||||
/// 卡片本体:标题 + 一句说明 + 中间(按段) + 页脚按钮。
|
||||
fn card<'a>(
|
||||
ui: &'a Ui,
|
||||
form: &'a Form,
|
||||
adopt_state: &'a AdoptState,
|
||||
catalog: Catalog,
|
||||
) -> Element<'a, Message> {
|
||||
let stage = form.stage(adopt_state);
|
||||
let body = column![
|
||||
frame::text_role(
|
||||
ui,
|
||||
TypeRole::HeadlineSmall,
|
||||
true,
|
||||
title_text(stage, catalog),
|
||||
ui.color(Role::OnSurface),
|
||||
)
|
||||
.map(Message::Page),
|
||||
wrapped(
|
||||
ui,
|
||||
subtitle(stage, adopt_state, catalog),
|
||||
ui.color(Role::OnSurfaceVariant)
|
||||
),
|
||||
frame::divider(ui).map(Message::Page),
|
||||
middle(ui, form, adopt_state, stage, catalog),
|
||||
frame::divider(ui).map(Message::Page),
|
||||
footer(ui, form, adopt_state, stage, catalog),
|
||||
]
|
||||
.spacing(ui.space(Sp::X3))
|
||||
.width(Length::Fill);
|
||||
|
||||
container(body)
|
||||
.width(Length::Fixed(CARD_WIDTH))
|
||||
.max_height(CARD_MAX_HEIGHT)
|
||||
.padding(ui.space(Sp::X6))
|
||||
.style(move |_theme| container::Style {
|
||||
background: Some(Background::Color(ui.layer(MaterialLayer::Overlay))),
|
||||
border: ui.outlined(ShapeScale::ExtraLarge),
|
||||
..container::Style::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// 段标题(四段各一句)。
|
||||
fn title_text(stage: AdoptStage, catalog: Catalog) -> &'static str {
|
||||
match stage {
|
||||
AdoptStage::Pick => catalog.text(Key::AdoptTitle),
|
||||
AdoptStage::Preview => catalog.text(Key::AdoptPlanTitle),
|
||||
AdoptStage::Failed => catalog.text(Key::AdoptFailureTitle),
|
||||
AdoptStage::Done => catalog.text(Key::AdoptDoneTitle),
|
||||
}
|
||||
}
|
||||
|
||||
/// 标题下那句说明(Pick 段说清「什么都不复制」;Done 段说清「去实例列表启动」)。
|
||||
fn subtitle(stage: AdoptStage, adopt_state: &AdoptState, catalog: Catalog) -> String {
|
||||
match stage {
|
||||
AdoptStage::Pick => catalog.text(Key::AdoptSubtitle).to_string(),
|
||||
AdoptStage::Preview => adopt_state
|
||||
.plan
|
||||
.as_ref()
|
||||
.map(|plan| {
|
||||
format!(
|
||||
"{} · {}",
|
||||
isolation_label(plan.isolation, catalog),
|
||||
target_label(plan.target, catalog)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
AdoptStage::Failed => adopt_state
|
||||
.failure
|
||||
.as_ref()
|
||||
.map(|failure| failure.message.clone())
|
||||
.unwrap_or_default(),
|
||||
AdoptStage::Done => catalog.text(Key::AdoptDoneHint).to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 中间区(按段交给 `pick` / `panes`)。
|
||||
fn middle<'a>(
|
||||
pub(super) fn middle<'a>(
|
||||
ui: &'a Ui,
|
||||
form: &'a Form,
|
||||
adopt_state: &'a AdoptState,
|
||||
@@ -329,62 +252,3 @@ fn middle<'a>(
|
||||
AdoptStage::Done => panes::done(ui, adopt_state.done.as_ref(), catalog),
|
||||
}
|
||||
}
|
||||
|
||||
/// 页脚按钮(按段给不同组合;在等回程时主按钮禁用)。
|
||||
fn footer<'a>(
|
||||
ui: &'a Ui,
|
||||
form: &'a Form,
|
||||
adopt_state: &'a AdoptState,
|
||||
stage: AdoptStage,
|
||||
catalog: Catalog,
|
||||
) -> Element<'a, Message> {
|
||||
let busy = form.busy.waiting();
|
||||
|
||||
let primary: Element<'a, Message> = match stage {
|
||||
AdoptStage::Pick => {
|
||||
let ready = form.candidate(adopt_state).is_some_and(|c| c.adoptable()) && !busy;
|
||||
if ready {
|
||||
buttons::filled(
|
||||
ui,
|
||||
Button::new(catalog.text(Key::AdoptPreview), adopt(AdoptMessage::Check)),
|
||||
)
|
||||
} else {
|
||||
buttons::filled(ui, Button::disabled(catalog.text(Key::AdoptPreview)))
|
||||
}
|
||||
}
|
||||
AdoptStage::Preview if !busy => buttons::filled(
|
||||
ui,
|
||||
Button::new(
|
||||
catalog.text(Key::AdoptConfirm),
|
||||
adopt(AdoptMessage::Confirm),
|
||||
),
|
||||
),
|
||||
AdoptStage::Preview => {
|
||||
buttons::filled(ui, Button::disabled(catalog.text(Key::AdoptConfirm)))
|
||||
}
|
||||
// 失败 / 完成:主按钮就是关掉(Done 不用「去列表看」——实例已经在列表里了)
|
||||
AdoptStage::Failed | AdoptStage::Done => buttons::filled(
|
||||
ui,
|
||||
Button::new(catalog.text(Key::AdoptClose), adopt(AdoptMessage::Close)),
|
||||
),
|
||||
};
|
||||
|
||||
let mut actions = row![].spacing(ui.space(Sp::X2)).width(Length::Fill);
|
||||
// Preview / Failed 给一条「退回候选列表」(= 重新扫一次,代价只有一次读目录)
|
||||
if matches!(stage, AdoptStage::Preview | AdoptStage::Failed) {
|
||||
actions = actions.push(buttons::text(
|
||||
ui,
|
||||
Button::new(catalog.text(Key::AdoptBack), adopt(AdoptMessage::Back)),
|
||||
));
|
||||
}
|
||||
actions = actions.push(container(Space::new()).width(Length::Fill));
|
||||
if !matches!(stage, AdoptStage::Done) {
|
||||
actions = actions.push(buttons::text(
|
||||
ui,
|
||||
Button::new(catalog.text(Key::AdoptClose), adopt(AdoptMessage::Close)),
|
||||
));
|
||||
}
|
||||
actions = actions.push(primary);
|
||||
|
||||
column![actions].width(Length::Fill).into()
|
||||
}
|
||||
Reference in new issue
Block a user