fix(app-iced): M90 r2 —— 找回窗口装饰 + 按需节拍 + 数据根一致 + 假绿守卫
复审 P1-1(产品页丢窗口装饰):shell 加内容槽位 view_with_content(ui, nav, title, content, forced),
app/view.rs 把 pages::page(...) 塞进真外壳;shell::view 保留给 Route::Agent 回落(删了会让私有
view/nav::rail 变死代码,-D warnings 红)。content() 拆到 shell/fallback.rs 让 mod.rs 回到 365 行。
1180 浅/深新图右上角三个 caption 键实测:ink 中心 y=23.5(栏高 48 的一半)、x 中心 1064.5/1110.5/
1156.5 = 46px 分档的 1065/1111/1157(±0.5px),close 命中区右缘 = 1180 窗角。
偏离 #7 改写成「交互面缺失」并注明本轮已修。
P1-2(harness.rs 402 行):tests 外置到 pages/harness/tests.rs(343 + 64 行);
components/gate.rs 的行数守门从走查 components/ 扩到 src/**(新测试当场抓出 app/tests.rs 416 行,
已按同样办法拆成 app/tests.rs + app/tests/presenter.rs)。
P2-1(常驻重绘 + 每拍重建模型):subscription 只在「截图 / 有 Busy-Running / 有 toast / 3 s 派发
观察窗」时订阅 window::frames();refresh_view 加 view_dirty 门。实测交互模式 9 s:只有 2 行 tick
trace(n=30/60,观察窗内),之后 6.5 s 一条都没有;refreshes 全程停在 7。
P2-2(两个数据根):--data-dir 时 SettingsBridge::with_path(config_path(root)),与后端同根。
P3-2(假绿):新增 RecorderBackend(dispatch 只记账、drain_events 恒空)+ launching_only_dispatches
_and_never_echoes_locally;植入本地回显后该测试实测转红(status 变 Downloading · 0%、can_stop 翻
true),撤回后转绿。
P3-3:尺寸字面量守门目录加 app;P3-1:--force-state 经 pages::Model::forced 作用到产品页控件
(hover 图里页头/列表/动作行/rail/caption 全部可见状态层);视觉 P2-2:加载器名统一走 badge()。
gallery 回归改口径:r2 与「主线 + r1」的 gallery 逐字节相同(cmp IDENTICAL);与 r1 记的
c825e1a 基线不同,差异来自主线 M86/M88 的令牌收口,不是本 mission。
This commit is contained in:
1 parent
b484998fc9
commit
022778a5a7
17 files changed
+980
-496
No files matched your search
@@ -19,7 +19,7 @@ use std::time::Instant;
|
||||
use iced::window;
|
||||
|
||||
use unlucky_design::color::SchemeFlavor;
|
||||
use unlucky_presenter::backend::{build_backend, AppOptions};
|
||||
use unlucky_presenter::backend::{build_backend, config_path, AppOptions};
|
||||
use unlucky_presenter::dto::Backend;
|
||||
use unlucky_presenter::prefs::ThemeMode;
|
||||
use unlucky_presenter::settings_bridge::SettingsBridge;
|
||||
@@ -37,6 +37,11 @@ use super::WINDOW_SIZE;
|
||||
/// `theme::config_poll_due` 的计数都按这个节拍走,两个 UI 版的时间语义因此一致。
|
||||
pub(super) const TICK: std::time::Duration = std::time::Duration::from_millis(33);
|
||||
|
||||
/// 派发指令后保持帧订阅的观察窗(见 [`App::watch_until`])。
|
||||
///
|
||||
/// 3 秒够真 host 把「命令已受理」的第一条事件交回来;之后由「有 Busy/Running 实例」这个条件续订。
|
||||
pub(super) const WATCH_WINDOW: std::time::Duration = std::time::Duration::from_secs(3);
|
||||
|
||||
/// 进程内「run() 开始」的时刻(首帧耗时基线)。
|
||||
pub(super) static STARTED: OnceLock<Instant> = OnceLock::new();
|
||||
/// 首帧日志只打一次。
|
||||
@@ -64,10 +69,20 @@ pub struct App {
|
||||
pub(super) width: f32,
|
||||
/// 展示转换缓存(presenter 快照 → 页面视图模型)。
|
||||
pub(super) view: pages::Model,
|
||||
/// 视图模型是否过期(P2-1):只有 `apply` / `navigate` / resize / 主题这类**真的改了状态**
|
||||
/// 的事情才置位,`advance()` 每拍只在置位时重建模型(否则 30 Hz × N 实例的白克隆)。
|
||||
pub(super) view_dirty: bool,
|
||||
/// 「刚派发过指令,等后端回事件」的观察窗(P2-1)。
|
||||
///
|
||||
/// 空闲态不订阅帧事件(不点亮窗口重绘),但派发之后必须有一段短窗口保持节拍,
|
||||
/// 否则真 host 的异步事件永远抽不出来(`LaunchInstance` 之后状态还是 Idle ⇒ 帧订阅永不开启)。
|
||||
pub(super) watch_until: Option<Instant>,
|
||||
/// 窗口句柄(`window::latest()` 之后才有)。
|
||||
pub(super) window: Option<window::Id>,
|
||||
/// 已画的帧数(截图闸门用)。
|
||||
pub(super) frames: u32,
|
||||
/// 视图模型重建次数(P2-1 的可观测计数:空闲态 100 拍里必须是 0 增长)。
|
||||
pub(super) refreshes: u64,
|
||||
/// 首屏快照到位后是否已经自动选中过第一行(list-detail 的桌面惯例,见 `update::auto_select`)。
|
||||
pub(super) auto_selected: bool,
|
||||
}
|
||||
@@ -80,7 +95,14 @@ impl App {
|
||||
/// [`build_backend`] 自己退回 mock)。
|
||||
pub(super) fn new(options: Options, families: Families, flavor: SchemeFlavor) -> Self {
|
||||
let backend = build_backend(&app_options(&options));
|
||||
let bridge = SettingsBridge::open();
|
||||
// P2-2:设置桥必须与后端指向**同一个数据根**。早先这里无条件 `SettingsBridge::open()`
|
||||
// (按便携配置解析),于是 `--data-dir X` 会出现「实例来自 X、主题/设置读写默认根」的一进程
|
||||
// 两个数据根。Slint 版是同一条口径:`app/src/ui_bind.rs` 的
|
||||
// `Some(root) => SettingsBridge::with_path(config_path(root))`。
|
||||
let bridge = match &options.data_dir {
|
||||
Some(root) => SettingsBridge::with_path(config_path(root)),
|
||||
None => SettingsBridge::open(),
|
||||
};
|
||||
Self::with_presenter(options, families, flavor, backend, bridge)
|
||||
}
|
||||
|
||||
@@ -110,8 +132,11 @@ impl App {
|
||||
last_tick: Instant::now(),
|
||||
width,
|
||||
view,
|
||||
view_dirty: false,
|
||||
watch_until: None,
|
||||
window: None,
|
||||
frames: 0,
|
||||
refreshes: 0,
|
||||
auto_selected: false,
|
||||
}
|
||||
}
|
||||
@@ -137,8 +162,12 @@ impl App {
|
||||
}
|
||||
|
||||
/// 把 presenter 的快照转换成页面视图模型(**展示转换在 UI 侧做**)。
|
||||
///
|
||||
/// 代价是「克隆 N 个实例的名字/版本串」,所以只在 [`App::view_dirty`] 置位时调用(P2-1)。
|
||||
pub(super) fn refresh_view(&mut self) {
|
||||
self.view = super::view::model(&self.state, self.width);
|
||||
self.view = super::view::model(&self.state, self.width, self.options.force_state);
|
||||
self.view_dirty = false;
|
||||
self.refreshes += 1;
|
||||
}
|
||||
|
||||
/// 按设置文件里的主题模式切一次外观;`--theme` 显式给了就不动。
|
||||
|
||||
Reference in new issue
Block a user