feat(app-iced): M90 —— app.rs 拆成 src/app/ + presenter 真接线 + 实例页端到端
拆分(行为不变,截图四件套逐字节相同):
- 400 行的 app.rs 拆成 src/app/{mod,state,message,update,subscription,view,boot,tests}.rs,
每个文件 ≤ 300 行;components 的 #[path] 跟着搬到 src/app/mod.rs(逻辑路径仍是 crate::app::components),
可见性放到 pub(crate) 让页面层能用真组件。
- 拆分前后各出 shell/gallery 浅深四张图,cmp 全部 IDENTICAL。
presenter 接线(crate 文档 §4):
- App 持有 Box<dyn Backend> + SettingsBridge + AppState;启动 build_backend + SettingsBridge::open + AppState::new。
- 截图通道强制 mock(D30 可复现);交互模式走真 host,--data-dir 从 M90 起真的被消费。
- Boot 抽干首屏快照(第 3 帧落盘,等节拍会把空页面拍进图);节拍 = 帧事件 + 33ms 时间闸门
(iced::time::every 要 tokio/smol,见 finding),tick → drain_events → AppState::apply。
- 用户动作不乐观回显:Launch/Stop 走 Backend::dispatch,状态等事件回来才变。
- 路由:pages::Message::Navigate(Route) 直接进 AppState::navigate,选中项 = 路由栈顶(不再有本地副本);
首屏自动选中第一行(list-detail 桌面惯例),只做一次。
实例页打通(其余三页保留占位视觉 + Message/update + 路由):
- 列表行换成 components::navigation::ListItem(真五态/选中),页头与详情动作换成 components::buttons
(Play 是唯一的实心 accent),分隔线换成 components::divider。
- 数据全部来自 presenter 快照:VERSION/LOADER/ISOLATION/VERSION ID/FILES/LAST PLAYED + 运行状态行。
- 导航轨换成真 NavigationRail(88),5 项各自压/替换路由。
版面约束未破(实测):列表列 336、正文列 424(元信息卡 206+12+206,右缘 896)、rail 88(药丸 16..71)、
断点 600/840 走 frame_metrics;新增 pages/tests.rs 的 9 条数值断言钉住它们。
This commit is contained in:
1 parent
270026338f
commit
b484998fc9
17 files changed
+1990
-676
No files matched your search
@@ -0,0 +1,38 @@
|
||||
//! 订阅:帧事件(截图闸门 + presenter 的 TICK 节拍)与窗口尺寸事件。
|
||||
//!
|
||||
//! ## 为什么节拍挂在帧事件上,而不是 `iced::time::every`
|
||||
//!
|
||||
//! `iced::time::every` 只有 `tokio` / `smol` 两个异步后端提供;本 crate 关掉了 iced 的默认 features,
|
||||
//! 执行器是 `thread-pool`(M60 踩坑 Q2),拿不到那个函数。想用它就得改 `Cargo.toml` 开一个异步
|
||||
//! 运行时(不在本 mission 的 scope 内,已报 finding)。
|
||||
//!
|
||||
//! 所以节拍走**帧事件 + 时间闸门**:`window::frames()` 每帧给一条 `Frame`,app 侧按真实流逝时间
|
||||
//! 判断「够不够 33 ms」,够了才推进一拍 presenter(`backend.tick(elapsed)` / `drain_events`)。
|
||||
//! 好处是**按真实时间推进**(不是「数了多少帧 × 33 ms」,掉帧也不会把游戏启动进度拖慢),
|
||||
//! 代价是交互模式下窗口会持续重绘(30 帧的活干在 vsync 的节拍上)。
|
||||
//!
|
||||
//! 截图模式下这条订阅原本就在(第 3 帧落盘),合并之后两条路径共用同一个计时器。
|
||||
|
||||
use iced::{window, Subscription};
|
||||
|
||||
use super::message::Message;
|
||||
use super::state::App;
|
||||
|
||||
impl App {
|
||||
/// 本次进程的订阅表。
|
||||
pub(super) fn subscription(&self) -> Subscription<Message> {
|
||||
let mut subscriptions = vec![window::frames().map(|_| Message::Frame)];
|
||||
|
||||
// 断点 600 / 840 要跟着真窗口宽度走;截图模式的窗口尺寸由参数钉死,不吃 resize。
|
||||
if !self.has_screenshot() {
|
||||
subscriptions.push(window::events().map(|(_, event)| Message::Window(event)));
|
||||
}
|
||||
|
||||
Subscription::batch(subscriptions)
|
||||
}
|
||||
|
||||
/// 是否订阅帧事件(= 截图模式要帧数闸门)。
|
||||
pub(super) fn wants_frames(&self) -> bool {
|
||||
self.has_screenshot()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user