feat(nav): M300 导航轨折叠按钮 + 折叠宽度令牌 + 宽度过渡与落盘
用户原话「默认左侧固定,允许折叠」: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 空白带(像素图里保留、未裁)。
This commit is contained in:
1 parent
1ce494631c
commit
fa53d72f73
26 files changed
+1640
-320
No files matched your search
@@ -101,11 +101,40 @@ pub struct LayoutMetrics {
|
||||
/// 官方 `NavigationDrawerTokens.ContainerWidth = 360`、`MinimumDrawerWidth = 240`;
|
||||
/// 本项目取官方下限 240——360 + AI 面板 320 = 680 = 1180 窗宽的 58%,内容区只剩 500 放不下两列卡片。
|
||||
pub nav_drawer_width: f32,
|
||||
/// 折叠态导航(rail 形态)宽度。
|
||||
/// 折叠态导航(rail 形态)宽度 —— **轨的展开档(默认)**。
|
||||
///
|
||||
/// 88 = 12b §5.1 对官方 rail 96 的登记偏离(−8):指示器宽 88 − 2×16 = 56 仍成立,
|
||||
/// 再窄就装不下 56 宽的选中指示器。
|
||||
///
|
||||
/// 「折叠态」这个词在本仓指 **rail 形态本身**(与 240 的 drawer 相对)。M300 起 rail 自己
|
||||
/// 也允许再收一档(只留图标),那一档是 [`LayoutMetrics::nav_collapsed_width`]。
|
||||
pub nav_rail_width: f32,
|
||||
/// 导航轨**再收一档**(只留图标、隐藏文字标签)时的宽度(M300)。
|
||||
///
|
||||
/// ## 算式:64 = 56 + 2 × 4
|
||||
///
|
||||
/// 判据是「胶囊不能被裁切」而不是「比 88 窄就行」:选中指示器 / 状态层那块矩形(M283 定稿
|
||||
/// **56×32**,本 mission 不动)在折叠态**仍然存在**(选中项照样要有底色),所以轨宽必须容得下
|
||||
/// 它加两侧留白:
|
||||
///
|
||||
/// ```text
|
||||
/// 胶囊宽 = nav_rail_width − 2 × Space::X4 = 88 − 32 = 56 (见 shell/nav.rs 的 indicator_size)
|
||||
/// nav_collapsed_width = 胶囊宽 + 2 × Space::X1 = 56 + 8 = 64
|
||||
/// ```
|
||||
///
|
||||
/// **为什么不是原来的 48**:旧令牌 `nav_collapsed_width = 48` 在 M69 被删,正是因为它连胶囊
|
||||
/// 都放不下 —— `48 − 2×X1 = 40 < 56`(`48 − 2×X4 = 16` 更不用说)。所以 48 不是「再收一档」,
|
||||
/// 是「收过头」。折叠档的下界由 `spacing.rs` 自己的断言
|
||||
/// `nav_collapsed_width >= 胶囊宽 + 2 × Space::X1` 钉住:任何人想把它改小,先得改胶囊尺寸,
|
||||
/// 而那是 M283 的定稿值(改它要在 mission 里声明)。
|
||||
///
|
||||
/// **为什么是 X1(4)而不是 X2(8)**:8 会让折叠档变成 72、与 88 只差 16(腾出的空间不足以
|
||||
/// 支撑「折叠」这个动作的意义),而 4 已经是栅格上的最小值、且 64 ≥ `touch_target`(48) ——
|
||||
/// 折叠态每一项的命中区仍是 64 宽整条,不比展开态差。
|
||||
///
|
||||
/// **纵节奏不变**:折叠只改宽度与标签可见性,项高仍是 `nav_item_height`(52 = 32 + 4 + 16),
|
||||
/// 于是切换时图标不上下跳、命中高 52 ≥ 48。
|
||||
pub nav_collapsed_width: f32,
|
||||
/// 搜索条高度(G11)。
|
||||
///
|
||||
/// 官方 `SearchBarTokens.ContainerHeight = 56`。**不走 [`LayoutMetrics::control_height`](40)**:
|
||||
@@ -157,6 +186,8 @@ impl LayoutMetrics {
|
||||
// `nav_collapsed_width=48` 的名字与值相反,已删)。
|
||||
nav_drawer_width: 240.0,
|
||||
nav_rail_width: 88.0,
|
||||
// M300:轨自己再收一档(只留图标)= 胶囊 56 + 2 × Space::X1(4)。算式见该字段文档。
|
||||
nav_collapsed_width: 64.0,
|
||||
// G11:官方 SearchBarTokens.ContainerHeight = 56。
|
||||
search_bar_height: 56.0,
|
||||
// G11/G12:官方 DividerTokens.Thickness = 1.0dp → 一像素线的唯一真源。
|
||||
@@ -208,6 +239,7 @@ mod tests {
|
||||
("titlebar_height", m.titlebar_height),
|
||||
("nav_drawer_width", m.nav_drawer_width),
|
||||
("nav_rail_width", m.nav_rail_width),
|
||||
("nav_collapsed_width", m.nav_collapsed_width),
|
||||
("search_bar_height", m.search_bar_height),
|
||||
("nav_item_height", m.nav_item_height),
|
||||
("nav_item_gap", m.nav_item_gap),
|
||||
@@ -244,6 +276,36 @@ mod tests {
|
||||
assert!(1180.0 - m.nav_drawer_width - m.ai_panel_width >= 600.0);
|
||||
}
|
||||
|
||||
/// **M300:折叠档必须容得下 56 宽的胶囊 + 两侧留白。**
|
||||
///
|
||||
/// 抓得住:①有人把 `nav_collapsed_width` 改回 48(`48 − 2×X1 = 40 < 56` ⇒ 第三条红,且第一条
|
||||
/// 直接对不上算式);②改胶囊尺寸却忘了改折叠档(第一条红:`nav_collapsed_width` 不再等于
|
||||
/// 「胶囊 + 2×X1」);③折叠档比命中下限还窄(第四条红)。
|
||||
/// 抓不住:真画出来的裁切(那要出图,见 `DevDocs/artifacts/m300/nav-collapsed-*.png`)。
|
||||
#[test]
|
||||
fn the_collapsed_rail_fits_the_pill_plus_its_side_padding() {
|
||||
let m = LayoutMetrics::DESKTOP;
|
||||
// 胶囊宽 = 轨宽 − 2×16(M283 定稿 56×32,本 mission 不动它)
|
||||
let pill = m.nav_rail_width - Space::X4.dp() * 2.0;
|
||||
assert_eq!(pill, 56.0, "M283 的胶囊宽");
|
||||
assert_eq!(
|
||||
m.nav_collapsed_width,
|
||||
pill + 2.0 * Space::X1.dp(),
|
||||
"折叠档 = 胶囊 + 两侧各一档 X1"
|
||||
);
|
||||
assert_eq!(m.nav_collapsed_width, 64.0);
|
||||
// 旧的 48 为什么死:它连一档 X1 的留白都放不下胶囊。
|
||||
assert!(
|
||||
48.0 - 2.0 * Space::X1.dp() < pill,
|
||||
"48 放不下 56 的胶囊 —— 这正是旧令牌被删的原因"
|
||||
);
|
||||
assert!(
|
||||
m.nav_collapsed_width >= m.touch_target,
|
||||
"折叠档(每一项的整条命中区)不许低于 48"
|
||||
);
|
||||
assert!(m.nav_collapsed_width < m.nav_rail_width);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_bar_height_is_a_page_level_entry() {
|
||||
let m = LayoutMetrics::DESKTOP;
|
||||
|
||||
Reference in new issue
Block a user