用户原话「默认左侧固定,允许折叠」:M297 盘查确认 iced 从来没接过折叠按钮, presenter 的 toggle_nav/nav_collapsed 是 Slint 遗留、唯一调用方在已退役的 crates/app。 ## 令牌(先定数值再动手) - nav_collapsed_width = 56 + 2 × Space::X1 = **64**:胶囊(M283 定稿 56×32,本轮不动) 两侧各留 4px 才不被裁切;旧值 48 的死因写进注释(48 − 2×4 = 40 < 56)。 - 断言:折叠宽 ≥ 胶囊宽 + 2×X1、≥ touch_target 48、在 4px 栅格上。 ## 折叠按钮与折叠态 - 按钮放**轨底、头像位之上**:轨顶一个像素都不动(M283/M299 的展开态对照图仍可比), 轨底本来就是「固定贴底」的槽,两态下位置不变。几何与导航项同一套(56×32 药丸 + 52 命中高)。 - 折叠态:隐藏文字标签与账户名,每项/头像/按钮都补 M3 气泡 tooltip(折叠态不许是一列无名符号); 纵节奏不变(项高仍 52),所以切换时图标不上下跳。五项图标两两不同由用例钉住。 ## 动效(复用底盘、零新常数) - 帧泵加**第二条并列宽度轨**(nav ⇄ rail),时长/曲线复用同一张 rail_spec(); 借用右栗那条会让左轨折叠改掉右栗的目标(暗耦合),tower 已否掉那个方案。 - 起跑在 update 侧;启动按落盘档对表(幂等,仅在没有轨道在跑时);折叠**不置 view_dirty** —— 宽度在 view 里逐帧采样,帧由轨道自己续,跑完 needs_frames() 回 false。 ## 落盘(任务要求「重启读回仍折叠」) - core schema 登记 ui.nav.collapsed(纯增量 bool,不碰 schemaVersion);presenter 桥加 load/persist 一对(走唯一写入口 write_with,现读→改→落盘);启动 construct 读回。 - 截图通道不读用户配置(D30),折叠态出图走 ULL_NAV_COLLAPSED=1。 ## 判据 - app-iced: 1053 passed(新增 3 条接线用例 + 7 条 shell/几何/树结构用例 + 6 条帧泵用例); presenter: 408 passed(新增落盘往返 1 条);fmt / clippy --workspace --all-targets 均 0。 - 变异实验:删 observe_nav ⇒ 宽度停在 88 红;删启动对表 ⇒ 首次展开硬跳红。 - 像素(DevDocs/artifacts/m300/):折叠态内容左缘 112→88(24px 真回收)、药丸两态都 56 宽 且折叠档右缘 59 < 64(不裁切)、图标行是展开态子集(纵节奏不变)。 ## 已知偏离(已登记,tower 批了紧随 mission) crates/app-iced/src/pages/layout.rs 的 rail_width() 仍是 const fn(恒 88),所以折叠态内容块 宽度不变、整块左移 24px ⇒ 右侧多一条 24px 空白带(像素图里保留、未裁)。
61 lines
2.9 KiB
Rust
61 lines
2.9 KiB
Rust
//! 导航轨底部**账户头像位**的状态换算(M114)+ 轨自身的折叠档(M300)。
|
||
//!
|
||
//! 为什么单独一个文件:`app/view.rs` 贴着 400 行硬线,而这块逻辑与页面树无关——
|
||
//! 它只回答「轨底那个头像位现在该显示成未登录,还是某个账户」,以及「这一帧轨多宽」。
|
||
|
||
use crate::app::components::motion;
|
||
use crate::shell;
|
||
|
||
use super::super::state::App;
|
||
|
||
/// 导航轨收成「图标条」的窗口高度(M114)。
|
||
///
|
||
/// 判据不是「项数」而是**真实可用高度**:四项 + 底部头像位在 48 标题栏之上还要放得下
|
||
/// 4 × 52 + 4 × 4 + 52 ≈ 300;留出一屏正文的最小高度(424 行宽 + 页头 + 边距),
|
||
/// 低于 720 就把标签收掉,只留图标与头像位(头像位**永远可见**,任务要求)。
|
||
const RAIL_COMPACT_HEIGHT: f32 = 720.0;
|
||
|
||
impl App {
|
||
/// 导航轨底部头像位的状态(M114):未登录 / 已登录两态要一眼能分。
|
||
///
|
||
/// M300:这里同时是轨的折叠档与过渡进度的**唯一构造点**——`shell::NavRail`(不在 scope 内)
|
||
/// 只负责把它转交给 `nav::rail`,几何全部来自 [`shell::RailAccount::nav_width`]。
|
||
pub(super) fn rail_account(&self) -> shell::RailAccount {
|
||
let compact = self.height < RAIL_COMPACT_HEIGHT;
|
||
let (collapsed, progress) = self.nav_state();
|
||
match self.state.active_account() {
|
||
Some(account) => {
|
||
shell::RailAccount::signed_in(account.monogram(), account.name.clone())
|
||
.compact(compact)
|
||
.nav(collapsed, progress)
|
||
}
|
||
// M106:未登录标签跟着**生效语言**走(早期钉死 `Lang::En` 的写法已删)。
|
||
None => shell::RailAccount::signed_out(
|
||
self.lang()
|
||
.catalog()
|
||
.text(unlucky_i18n::Key::AccountRailSignedOut),
|
||
)
|
||
.compact(compact)
|
||
.nav(collapsed, progress),
|
||
}
|
||
}
|
||
|
||
/// 轨的折叠档 + **这一帧**的过渡进度(0 = 折叠 64,1 = 展开 88)。
|
||
///
|
||
/// 进度是**采样**来的,不是自己算的:`motion::nav(open)` 在「帧泵的目标与 `open` 不符」时
|
||
/// 直接给终值,于是截图 harness / 单测直接摆状态也不会把界面卡在中间那一帧。
|
||
fn nav_state(&self) -> (bool, f32) {
|
||
let collapsed = self.state.prefs.nav_collapsed;
|
||
(collapsed, motion::nav(!collapsed))
|
||
}
|
||
|
||
/// 这一帧导航轨的宽度(展开 88 ⇄ 折叠 64,中间帧按动效进度插值)。
|
||
///
|
||
/// **给用例用**:它读的就是 `view()` 递给 `nav::rail` 的那个值(同一个 `RailAccount`),
|
||
/// 所以「点击后宽度真的变了」这条断言不会与视图各算一套而假绿。
|
||
#[cfg(test)]
|
||
pub(in crate::app) fn nav_rail_width(&self) -> f32 {
|
||
self.rail_account().nav_width(self.ui().metrics())
|
||
}
|
||
}
|