用户原话「默认左侧固定,允许折叠」: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 空白带(像素图里保留、未裁)。
195 lines
16 KiB
Rust
195 lines
16 KiB
Rust
//! 金表的**表数据**(`Key::ALL` 顺序 × (en, zh-CN) 逐字快照,第一列是 `Key::name()`)。「为什么
|
||
//! 有这张表、它抓得住什么」全在 `catalog.rs` 的 `mod golden;` 里;本文件只放数据。
|
||
pub(super) const GOLDEN: &[(&str, &str, &str)] = &[
|
||
("NavInstances", "Instances", "实例"),
|
||
("NavDownload", "Versions", "版本管理"),
|
||
("NavDownloadExpanded", "Version management", "版本管理"),
|
||
("NavLogs", "Logs", "日志"),
|
||
("NavSettings", "Settings", "设置"),
|
||
("NavCollapse", "Collapse the navigation rail", "折叠导航轨"),
|
||
("NavExpand", "Expand the navigation rail", "展开导航轨"),
|
||
("InstancesNew", "New instance", "新建实例"),
|
||
("InstancesImport", "Import", "导入"),
|
||
("InstancesBack", "Back to instances", "返回实例列表"),
|
||
("InstancesPlay", "Play", "启动"),
|
||
("InstancesEmptyTitle", "No instance selected", "未选择实例"),
|
||
("InstancesEmptyBody", "Pick an instance on the left to see its version, loader and launch options.", "在左侧选择一个实例,查看它的版本、加载器与启动选项。"),
|
||
("InstancesTitle", "Instances", "实例"),
|
||
("InstancesSubtitle", "Local versions and their mod loaders", "本地版本与它们的模组加载器"),
|
||
("InstancesAllTitle", "ALL INSTANCES", "全部实例"),
|
||
("InstancesStop", "Stop", "停止"),
|
||
("InstancesDuplicate", "Duplicate", "复制"),
|
||
("InstancesOpenFolder", "Open folder", "打开文件夹"),
|
||
("InstancesDelete", "Delete", "删除"),
|
||
("InstancesNarrowNote", "Below 840 px the list and the detail share one column, so opening an instance replaces this screen (full-page switch). Widen the window to see both panes again.", "窗口窄于 840 像素时,列表与详情共用一列:打开一个实例会整页替换这一屏(整页切换)。把窗口拉宽就能重新同时看见两栏。"),
|
||
("InstancesMetaVersion", "VERSION", "版本"),
|
||
("InstancesMetaLoader", "LOADER", "加载器"),
|
||
("InstancesMetaMemory", "MEMORY", "内存"),
|
||
("InstancesMetaJava", "JAVA", "Java"),
|
||
("InstancesMetaMods", "MODS", "模组"),
|
||
("InstancesMetaIsolation", "ISOLATION", "隔离方式"),
|
||
("InstancesMetaVersionId", "VERSION ID", "版本标识"),
|
||
("InstancesMetaFiles", "FILES", "文件"),
|
||
("InstancesMetaLastPlayed", "LAST PLAYED", "上次游玩"),
|
||
("InstancesPresentOnDisk", "Present on disk", "已在磁盘上"),
|
||
("InstancesMissing", "Missing", "缺失"),
|
||
("InstancesNeverPlayed", "Never", "从未游玩"),
|
||
("InstancesIsolationIsolated", "Isolated", "独立目录"),
|
||
("InstancesIsolationShared", "Shared", "共用目录"),
|
||
("InstancesRunning", "Running · pid {pid}", "运行中 · pid {pid}"),
|
||
("InstancesFailed", "Launch failed · {reason}", "启动失败 · {reason}"),
|
||
("InstancesCrashed", "the game crashed (exit {code})", "游戏崩溃(退出码 {code})"),
|
||
("InstancesSilentFailed", "exit 0, but never started", "退出码 0,没起来"),
|
||
("InstancesStageDownload", "Downloading", "下载中"),
|
||
("InstancesStageVerify", "Verifying", "校验中"),
|
||
("InstancesStageLaunch", "Starting", "启动中"),
|
||
("InstancesStageWindow", "Waiting for window", "等待游戏窗口"),
|
||
("InstancesLaunchTitle", "LAUNCH OPTIONS", "启动参数"),
|
||
("InstancesLaunchJvm", "JVM arguments", "JVM 参数"),
|
||
("InstancesLaunchJvmHint", "Space separated, quotes allowed. Empty = follow the global extra JVM arguments (both are sent; on duplicates the instance wins).", "空格分隔、支持引号。留空 = 跟随全局附加 JVM 参数(两者叠加下发,同名选项实例赢)。"),
|
||
("InstancesLaunchGame", "Game arguments", "游戏参数"),
|
||
("InstancesLaunchGameHint", "Appended last, so they override earlier game arguments. Empty = nothing appended.", "追加在命令行最后,可覆盖前面的游戏参数。留空 = 不追加。"),
|
||
("InstancesLaunchMemory", "Max memory (MiB)", "最大内存(MiB)"),
|
||
("InstancesLaunchMemoryHint", "Empty = follow the global default ({default} MiB).", "留空 = 跟随全局默认({default} MiB)。"),
|
||
("InstancesLaunchJava", "Java executable", "Java 可执行文件"),
|
||
("InstancesLaunchJavaHint", "Empty = follow the global setting, then auto-pick (managed > JAVA_HOME > PATH).", "留空 = 跟随全局设置,再退到自动挑选(托管 > 环境变量 > 系统路径)。"),
|
||
("InstancesLaunchBackend", "Graphics backend", "图形后端"),
|
||
("InstancesLaunchBackendGated", "Requires MC {min}+ (this instance: {version}). The flag is not sent on this version.", "需游戏版本 {min} 及以上(本实例 {version}),这个版本不会下发该参数。"),
|
||
("InstancesLaunchEffective", "Effective: {value}", "生效:{value}"),
|
||
("InstancesBackendDefault", "Default", "默认"),
|
||
("InstancesBackendOpengl", "OpenGL", "OpenGL 后端"),
|
||
("InstancesBackendVulkan", "Vulkan", "Vulkan 后端"),
|
||
("InstancesLaunchSave", "Save", "保存"),
|
||
("InstancesLaunchReset", "Reset to global", "恢复跟随全局"),
|
||
("AdoptTitle", "Import an existing version", "导入已有版本"),
|
||
("AdoptSubtitle", "Register versions another launcher already installed. Nothing is copied: versions, libraries and assets stay where they are.", "把别的启动器已经装好的版本登记成实例。不复制任何东西:versions / libraries / assets 原地不动。"),
|
||
("AdoptScanning", "Scanning versions/…", "正在扫描 versions/…"),
|
||
("AdoptEmpty", "No version found under {dir}. Install one here, or point the game folder at the .minecraft another launcher uses.", "在 {dir} 下一个版本都没找到。可以用本启动器装一个,或者把游戏目录指向别的启动器用的那个 .minecraft。"),
|
||
("AdoptSelectLabel", "Pick a version", "选择版本"),
|
||
("AdoptReady", "Ready", "可导入"),
|
||
("AdoptBlocked", "{count} problem(s)", "{count} 个问题"),
|
||
("AdoptRegistered", "Already used by {name}", "已被 {name} 使用"),
|
||
("AdoptTargetLabel", "Where should the game run?", "游戏在哪个目录里跑"),
|
||
("AdoptTargetInstanceDir", "New folder in ULL data (default)", "ULL 数据区新建目录(缺省)"),
|
||
("AdoptTargetInstanceDirHint", "mods, saves and config stay out of the other launcher's .minecraft", "mods / saves / config 不会落进别人的 .minecraft"),
|
||
("AdoptTargetShared", "Share the game folder", "共用游戏目录"),
|
||
("AdoptTargetSharedHint", "mods, saves and config are written into the other launcher's .minecraft", "mods / saves / config 会写进别人的 .minecraft"),
|
||
("AdoptTargetVersionDir", "Use the version folder", "用版本目录"),
|
||
("AdoptTargetVersionDirHint", "player data grows inside versions/<id>", "玩家数据会长在 versions/<id> 里"),
|
||
("AdoptNameLabel", "Instance name (empty = version id)", "实例名(留空 = 版本 id)"),
|
||
("AdoptPreview", "Check", "预检"),
|
||
("AdoptConfirm", "Import", "导入"),
|
||
("AdoptBack", "Back", "返回"),
|
||
("AdoptClose", "Close", "关闭"),
|
||
("AdoptPlanTitle", "Ready to import", "可以导入"),
|
||
("AdoptPlanRunDir", "Game folder", "运行目录"),
|
||
("AdoptPlanIsolation", "Isolation", "隔离方式"),
|
||
("AdoptFailureTitle", "Import failed", "导入失败"),
|
||
("AdoptProblemCount", "Every reason is listed below:", "全部原因如下:"),
|
||
("AdoptInstanceName", "Instance name", "实例名"),
|
||
("AdoptDoneTitle", "Imported", "已导入"),
|
||
("AdoptDoneHint", "It is in the instance list now. Player data will live in the game folder below.", "它已经出现在实例列表里;玩家数据会落在下面这个运行目录。"),
|
||
("InstancesModsTitle", "MODS", "模组"),
|
||
("InstancesModsCount", "{enabled} enabled · {disabled} disabled", "{enabled} 启用 · {disabled} 禁用"),
|
||
("InstancesModsLoading", "Reading mods/…", "正在读取 mods/…"),
|
||
("InstancesModsEmpty", "No mods here yet — put jars into mods/ and they show up.", "这里还没有模组——把模组文件放进 mods/ 就会出现在这里。"),
|
||
("InstancesModsMissingDir", "This instance has no mods/ directory yet.", "这个实例还没有 mods/ 目录。"),
|
||
("InstancesModsRunning", "The game is running — toggles are read-only until it exits.", "游戏正在运行——退出前开关只读。"),
|
||
("InstancesModsOldBadge", "legacy file", "旧版本残留"),
|
||
("InstancesModsSkipped", "{count} entries skipped", "跳过 {count} 项"),
|
||
("InstancesModsFailed", "Could not read mods/: {reason}", "读取 mods/ 失败:{reason}"),
|
||
("InstancesModsToggleFailed", "Could not toggle {file}: {reason}", "启停 {file} 失败:{reason}"),
|
||
("InstancesModsReasonRunning", "the game is running", "游戏正在运行"),
|
||
("InstancesModsReasonInstance", "the instance is gone", "实例已不存在"),
|
||
("InstancesModsReasonName", "the file name is not usable", "文件名不可用"),
|
||
("InstancesModsReasonDirectory", "there is no usable mods/ directory", "没有可用的 mods/ 目录"),
|
||
("InstancesModsReasonMissing", "the file is gone", "文件已不在"),
|
||
("InstancesModsReasonNotFile", "that is not a regular file", "那不是普通文件"),
|
||
("InstancesModsReasonCollision", "a file with that name already exists", "同名文件已存在"),
|
||
("InstancesModsReasonRename", "the rename failed", "改名失败"),
|
||
("InstancesSavesTitle", "SAVES", "存档"),
|
||
("InstancesSavesCount", "{worlds} worlds · {shots} screenshots", "{worlds} 个世界 · {shots} 张截图"),
|
||
("InstancesSavesLoading", "Reading saves/…", "正在读取 saves/…"),
|
||
("InstancesSavesEmpty", "No worlds or screenshots yet — play once and they show up here.", "还没有世界或截图——玩一次就会出现在这里。"),
|
||
("InstancesSavesMissingDir", "This instance has no saves/ or screenshots/ directory yet.", "这个实例还没有 saves/ 或 screenshots/ 目录。"),
|
||
("InstancesSavesWorlds", "WORLDS", "世界"),
|
||
("InstancesSavesScreenshots", "SCREENSHOTS", "截图"),
|
||
("InstancesSavesNoWorlds", "No worlds yet.", "还没有世界。"),
|
||
("InstancesSavesNoScreenshots", "No screenshots yet.", "还没有截图。"),
|
||
("InstancesSavesWorldIcon", "has icon", "有图标"),
|
||
("InstancesSavesTimeUnknown", "time unknown", "时间未知"),
|
||
("InstancesSavesSkipped", "{count} entries skipped", "跳过 {count} 项"),
|
||
("InstancesSavesOpenFolder", "Open saves folder", "打开存档文件夹"),
|
||
("InstancesSavesRetry", "Retry", "重试"),
|
||
("InstancesSavesFailed", "Could not read saves/: {reason}", "读取 saves/ 失败:{reason}"),
|
||
("InstancesSavesOpenFailed", "Could not open the folder: {reason}", "打开文件夹失败:{reason}"),
|
||
("InstancesSavesReasonDir", "the directory is not usable", "目录不可用"),
|
||
("InstancesSavesReasonInstance", "the instance is gone", "实例已不存在"),
|
||
("InstancesSavesReasonOpen", "the file manager did not start", "文件管理器没起来"),
|
||
("InstancesSavesRename", "Rename", "重命名"),
|
||
("InstancesSavesBackup", "Back up", "备份"),
|
||
("InstancesSavesDelete", "Delete", "删除"),
|
||
("InstancesSavesRenamePlaceholder", "New name for this world", "这个世界的新名字"),
|
||
("InstancesSavesRenameConfirm", "Rename", "改名"),
|
||
("InstancesSavesCancel", "Cancel", "取消"),
|
||
("InstancesSavesBusy", "Working…", "正在处理…"),
|
||
("InstancesSavesDeleteTitle", "Delete this world?", "删除这个世界?"),
|
||
("InstancesSavesDeleteBody", "{dir} and everything inside it will be deleted. This cannot be undone.", "{dir} 和它里面的全部内容都会被删除,且不能撤销。"),
|
||
("InstancesSavesDeleteConfirm", "Delete world", "删除世界"),
|
||
("InstancesSavesRenamed", "Renamed {from} → {to}.", "已把 {from} 改名为 {to}。"),
|
||
("InstancesSavesDeleted", "Deleted {dir}.", "已删除 {dir}。"),
|
||
("InstancesSavesBackedUp", "Backed up {dir} to {path} ({size}).", "已把 {dir} 备份到 {path}({size})。"),
|
||
("InstancesSavesBackupSkipped", "{count} symlinks skipped", "跳过 {count} 个符号链接"),
|
||
("InstancesSavesRenameFailed", "Could not rename {file}: {reason}", "重命名 {file} 失败:{reason}"),
|
||
("InstancesSavesDeleteFailed", "Could not delete {file}: {reason}", "删除 {file} 失败:{reason}"),
|
||
("InstancesSavesBackupFailed", "Could not back up {file}: {reason}", "备份 {file} 失败:{reason}"),
|
||
("InstancesSavesReasonUnsafeName", "the name is not usable", "名字不可用"),
|
||
("InstancesSavesReasonSymlink", "that path is a symbolic link (not followed)", "那个路径是符号链接(不跟随)"),
|
||
("InstancesSavesReasonMissing", "the world is gone", "那个世界已不在"),
|
||
("InstancesSavesReasonNotDir", "that is not a directory", "那不是目录"),
|
||
("InstancesSavesReasonConflict", "a world with that name already exists", "同名世界已存在"),
|
||
("InstancesSavesReasonBoundary", "that path is outside saves/", "那个路径落在 saves/ 之外"),
|
||
("InstancesSavesReasonTooLarge", "the world is too large for one zip", "这个世界太大,一个 zip 装不下"),
|
||
("InstancesSavesReasonWrite", "the write failed", "写盘失败"),
|
||
("InstancesSavesReasonRunning", "the game is running (exit it first)", "游戏正在运行(先退出再操作)"),
|
||
("InstancesSavesPreviewNone", "no preview", "未生成预览"),
|
||
("InstancesSavesPreviewTooLarge", "too large for a preview (over 8 MiB)", "文件过大,未生成预览(超过 8 MiB)"),
|
||
("InstancesSavesPreviewUnreadable", "the file could not be read", "读不出这张图"),
|
||
("InstancesSavesPreviewDecode", "not a decodable image", "解不出这张图(可能已损坏或格式不支持)"),
|
||
("InstancesEmptyNoneTitle", "No instances yet", "还没有实例"),
|
||
("InstancesEmptyNoneBody", "Nothing has been registered yet. Import a version that is already on disk to get started.", "这里还没有实例。导入一个已经装在盘上的版本就能开始。"),
|
||
("InstancesAdoptCta", "Import existing version", "导入已有版本"),
|
||
("InstancesDiscoveredNote", "Discovered in versions/: where it runs is decided by the files that are already there.", "在版本目录里自动发现:它从哪里启动,由盘上已有的文件决定。"),
|
||
("InstancesDiscoveredRunShared", "Detected instance: it has no mods/ or saves/ of its own, so this launch uses the .minecraft folder (shared).", "自动发现的实例:它的版本目录里没有 mods/ 或 saves/,这次启动按共用目录运行(.minecraft 根目录)。"),
|
||
("InstancesDiscoveredRunIsolated", "Detected instance: it already has its own mods/ or saves/, so this launch uses its version folder (isolated).", "自动发现的实例:它的版本目录里已经有 mods/ 或 saves/,这次启动按独立目录运行(它自己的版本文件夹)。"),
|
||
("InstancesSavesSource", "Saves folder: {dir}", "存档目录:{dir}"),
|
||
];
|
||
|
||
/// 金表的**全量**行(本文件 → [`tail`] → [`tail_ai`] → [`tail2`],顺序即 `Key::ALL`)。
|
||
///
|
||
/// 拆成四段只为守 400 行硬线(M157/M159/M197 往金表追加文案后越线);对调用方是**一条**序列,
|
||
/// `catalog/tests.rs` 的逐字节断言不用关心分段。
|
||
///
|
||
/// ⚠️ 加段时**必须**在这里也 chain 上:条数与内容都只能从这一条序列导出(见 [`len`])。
|
||
pub(super) fn rows() -> impl Iterator<Item = &'static (&'static str, &'static str, &'static str)> {
|
||
GOLDEN
|
||
.iter()
|
||
.chain(tail::ROWS.iter())
|
||
.chain(tail_ai::ROWS.iter())
|
||
.chain(tail2::ROWS.iter())
|
||
}
|
||
|
||
/// 金表总行数(`Key::ALL` 的条数)。
|
||
///
|
||
/// **定义成 `rows().count()`,不是各段 `len()` 相加**:r3 复审实测过一条假绿——把
|
||
/// [`rows`] 里的 `chain(tail2::ROWS)` 删掉,`catalog/tests.rs` 仍全绿,因为条数来自一处手工
|
||
/// 聚合(仍有 tail2)而内容只遍历 `rows()`(少了 tail2),于是 213/459 行无人看守。
|
||
/// 现在条数与内容同源:漏挂一段 ⇒ 条数当场对不上 ⇒ 用例必红。
|
||
pub(super) fn len() -> usize {
|
||
rows().count()
|
||
}
|
||
|
||
mod tail;
|
||
mod tail2;
|
||
mod tail_ai;
|