复审(代码)r1 的两条 P1 + 两条必修。 P1-1 `SetSetting` 写不回 Float 键(整窗透明度重启回旧值) - `presenter/src/host/settings.rs::parse_value` 改成**按 schema 的 `ValueKind` 取类型**, 不再硬编码 Int/Bool 键名。历史:`appearance.window_opacity`(Float)被当 String 送进 `store.set` → schema 的 `as_f64()` 返回 None → `InvalidValue` → **`save()` 根本没执行**, α 只在内存生效、重启回旧值、每次拖动弹一条 Error toast。 - 测试(presenter 层,`host/settings_tests.rs`): ① `float_key_round_trips_through_set_setting`:set 0.8 → **新开 store(= 重启)** 读回 0.8; ② `every_float_key_in_the_schema_can_be_written_back`:遍历 schema 里**所有** `ValueKind::Float` 的键逐个写回并读回(集合级守门,下次新增 Float 键漏接会立刻红)。 P1-2 `presenter/src/prefs.rs` 404 行 → 拆测试 - `#[cfg(test)] mod tests` 整体挪到 `crates/presenter/src/prefs/tests.rs`,结构体与 re-export 一字未动。`wc -l`:**404 → 315**(tests.rs 90 行)。 P2-2 CPU 档判定只在 `--renderer=cpu` 时成立 - 新增 `app/backend.rs`:`effective_backend()` 把 `probe::apply` 写下的 `ICED_BACKEND` 候选列表翻回档位(`tiny-skia` / `tiny-skia,wgpu` → CPU;`wgpu,tiny-skia` → GPU;未设 → 请求值)。 `App::with_presenter` 用它喂 `appearance::State`,于是**默认 Auto 档在没有独显的机器上** 也会走「面板 α / 玻璃强制 1.0」。 - 测试:`every_iced_backend_list_maps_to_a_tier` + `auto_with_a_software_renderer_forces_the_panel_opaque`。 - 实跑:`ICED_BACKEND=tiny-skia`(= probe 在无独显机器上写下的值)→ `[appearance] boot … panel_alpha=1.000 (requested 0.550) backend=cpu`。 P2-1 平铺退化成「一条竖条」——根因不是几何,是**渲染层没吃 scale**(r3 的编辑没落到 `tiled()`) - `picture::tiled()` 原来仍用 `ContentFit::None` + 原图尺寸,所以 `tiles()` 算出的 `scale` 完全没被消费:小图被按 32dp 画在 129dp 的格子里 ⇒ 到处是缝。 - 现在:每块 `源尺寸 × tiles.scale`、`ContentFit::Fill`、行高显式 `Fixed`、整列 `Fill`。 - 诊断:新增 `background::small_tile_note()`(短边 < 200dp 的图在 tile 档下给一句 「scaled up so the window stays covered」),已接进设置页诊断块(首屏可见)。 - 测试:`tile_grid_always_covers_the_viewport_within_budget`(6 组尺寸,含 32×32 / 16×16 / 1×1, 断言覆盖 ≥ 视口且块数 ≤ 预算)、`small_images_get_a_tile_diagnostic_instead_of_silently_degrading`。 - 出图:`r5-tile-tiny32.png`(32×32 铺满全窗、右缘无裸白、提示可见)、`r5-tile-120.png`、 `r5-tile-big.png`。 顺手(复审 P2-3/P2-4/P2-5) - P2-4:`MAX_SOURCE_EDGE` 16384 → **8192**,并给解码器加 `image::Limits`(宽/高 + `max_alloc` 320MB)作第二道闸;补 `an_over_sized_image_reports_too_large_instead_of_trying_to_decode` (`TooLarge` 这条分支此前无测试)。 - P2-5:`transparency.rs` 补一段「为什么 `LWA_ALPHA` + `WS_EX_LAYERED` 不影响命中测试与 自绘标题栏拖动」(穿透要 `WS_EX_TRANSPARENT` 或 `LWA_COLORKEY`,两者都没用;`window::drag` 走 `WM_NCLBUTTONDOWN(HTCAPTION)`)。 - P2-3:断链 `[background::dimmed]` → `dim_alpha`;`iced/image` → `image-without-codecs`; 测试文档 204 → 205;马赛克残留说明只保留 r1→r2 的历史一句。 - 解码在 UI 线程同步做:**登记欠账**(挪后台线程超出本 mission)。
72 lines
3.1 KiB
Rust
72 lines
3.1 KiB
Rust
//! Elm 三件套:`State` / `Message` / `update` + `view` 接线,以及启动序列。
|
||
//!
|
||
//! 启动序列:①探测渲染后端并写 `ICED_BACKEND`(必须在 `run()` 之前,M60 Q1)→ ②求值字体栈
|
||
//! (D28 前半)→ ③`iced::application(..).run()`(1180×760、无边框自绘标题栏)→ ④首个 `view()`
|
||
//! 打耗时;截图模式第 3 帧落盘后退出(无边框窗首帧可能是旧尺寸,M60 坑 #5)。
|
||
//!
|
||
//! ## 为什么拆成目录(M90)
|
||
//!
|
||
//! 本文件原来是**单个 400 行的 `app.rs`**:正好卡在「单文件 ≤400 行、动它前必须先拆」的硬门槛上
|
||
//! (AGENTS 的文件上限 + 本 mission 的第一条任务)。拆法与职责:
|
||
//!
|
||
//! | 文件 | 职责(**只做这一件事**) |
|
||
//! |---|---|
|
||
//! | [`state`] | [`App`] 字段 + 构造(presenter 三件在此持有)+ 主题切换 |
|
||
//! | [`message`] | [`Message`] 枚举与「导航项 → 路由」的翻译 |
|
||
//! | [`update`] | 全部 `Message` 分支 + 「用户动作 → `AppCommand`」的派发与事件回流 |
|
||
//! | [`subscription`] | 帧事件(截图)与 presenter 的 TICK 节拍 |
|
||
//! | [`view`] | `view` / `theme` / `title` |
|
||
//! | [`boot`] | [`run`]:后端探测 → 字体 → `iced::application(..)` |
|
||
//!
|
||
//! 拆分的每一步都保持**可观察行为不变**(截图四件套 `--screenshot` / `--screenshot-theme` /
|
||
//! `--screen=gallery` / `--diff` 出图逐字节相同),presenter 接线是在拆分**之后**另一步做的。
|
||
//!
|
||
//! ## 组件库为什么还在本模块下声明
|
||
//!
|
||
//! `src/lib.rs` 是五条组件线共用的文件,改它必然冲突;`#[path]` 保证物理路径仍是
|
||
//! `src/components/`(组件目录自己的归属规则不变),逻辑路径仍是 `crate::app::components`
|
||
//! (组件源码里的文档与测试都按这个路径写)。
|
||
|
||
mod appearance;
|
||
mod backend;
|
||
mod boot;
|
||
mod message;
|
||
mod state;
|
||
mod subscription;
|
||
mod update;
|
||
mod view;
|
||
mod window_alpha;
|
||
|
||
#[cfg(test)]
|
||
mod tests;
|
||
|
||
pub(crate) use boot::run;
|
||
pub use message::Message;
|
||
|
||
// 组件库(D39)在本文件声明:`src/lib.rs` 是五条组件线共用的文件,改它必然冲突;
|
||
// `#[path]` 保证物理路径仍是 `src/components/`(组件目录自己的归属规则不变)。
|
||
//
|
||
// M90:可见性从 `mod` 放到 `pub(crate) mod` —— 页面层(`src/pages/**`)要**用**真组件
|
||
// (`components::buttons` / `navigation` / `divider`),而 `app` 不是它的父模块。
|
||
// 只放开 crate 内可见,组件目录的归属规则一字不改。
|
||
#[path = "../components/mod.rs"]
|
||
pub(crate) mod components;
|
||
|
||
/// 验收窗口尺寸(1180×760 @100% DPI —— D30 的硬要求)。
|
||
pub const WINDOW_SIZE: iced::Size = iced::Size {
|
||
width: 1180.0,
|
||
height: 760.0,
|
||
};
|
||
|
||
/// 产品名(自绘标题栏的标题;`'static` 好让 view 借用)。
|
||
pub const APP_TITLE: &str = "Unlucky Launcher";
|
||
|
||
/// 窗口最小尺寸(再小骨架会挤坏:88 导航 + 页面留白 + 卡片)。
|
||
const MIN_SIZE: iced::Size = iced::Size {
|
||
width: 880.0,
|
||
height: 560.0,
|
||
};
|
||
|
||
/// 截图落盘的帧号(见模块文档)。
|
||
const CAPTURE_AT_FRAME: u32 = 3;
|