diff --git a/ROADMAP.md b/ROADMAP.md index 944f14c95..280a096b7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -129,6 +129,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m Newest first. +- **2026-10-11** · The View menu follows InDesign's: proofing first, then zoom, Rotate Spread, Screen Mode, Display Performance, rulers, Extras, Grids & Guides and Structure; the view items that sat at the end of the Type menu moved there. No readiness percentage change. - **2026-10-11** · Properties › Appearance's Fill and Stroke menus gain InDesign's Tint field and, for a text frame, Apply To: Frame / Text. No readiness percentage change. - **2026-10-11** · `story.replaceRange`, `story.setText`, notes, footnotes, endnotes, type on a path and table cells read `\r` and `\r\n` as paragraph returns, as `frame.create` and `text.insert` already did. No readiness percentage change. - **2026-10-11** · Corner Options keep curved sides curved and round every corner point, including where an arc meets a line (a Pathfinder quarter circle with rounded corners stayed a triangle). No readiness percentage change. diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index f2bcd2ed4..2d943ef58 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -3169,6 +3169,9 @@ const TABLE: &[(&str, [&str; 5])] = &[ ("Tint:", ["Farbton:", "Teinte:", "Matiz:", "濃淡:", "色调:"]), ("Apply To:", ["Anwenden auf:", "Appliquer à :", "Aplicar a:", "適用先:", "应用于:"]), ("Frame", ["Rahmen", "Bloc", "Marco", "フレーム", "框架"]), + ("Slug", ["Infobereich", "Ligne-bloc", "Indicaciones", "印刷可能領域", "辅助信息区"]), + ("Presentation", ["Präsentation", "Présentation", "Presentación", "プレゼンテーション", "演示文稿"]), + ("Extras", ["Extras", "Options supplémentaires", "Extras", "エクストラ", "附加"]), ("Column", ["Spalte", "Colonne", "Columna", "段", "栏"]), // File › New › Document ("Recent", ["Zuletzt verwendet", "Récents", "Recientes", "最近使用", "最近"]), diff --git a/crates/ui-egui/src/i18n/it.rs b/crates/ui-egui/src/i18n/it.rs index d631bdb1b..bc53315d1 100644 --- a/crates/ui-egui/src/i18n/it.rs +++ b/crates/ui-egui/src/i18n/it.rs @@ -1681,4 +1681,5 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Slug:", "Indicazioni:"), ("Apply To:", "Applica a:"), ("Frame", "Cornice"), + ("Presentation", "Presentazione"), ]; diff --git a/crates/ui-egui/src/i18n/ja.rs b/crates/ui-egui/src/i18n/ja.rs index aa59ccd42..8b5fc6ba0 100644 --- a/crates/ui-egui/src/i18n/ja.rs +++ b/crates/ui-egui/src/i18n/ja.rs @@ -1682,15 +1682,18 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Type/Insert Special Character/Quotation Marks", "書式/特殊文字を挿入/引用符"), ("Type/Insert Special Character/Symbols", "書式/特殊文字を挿入/記号"), ("Type/Insert White Space", "書式/空白文字を挿入"), - ("Type/Proof Setup", "書式/カラー校正設定"), - ("Type/Rotate Spread", "書式/スプレッドを回転"), - ("Type/Separations Preview", "書式/分版プレビュー"), ("Type/Story Direction", "書式/組み方向"), ("Type/Text Variables", "書式/テキスト変数"), ("Type/Track Changes", "書式/変更を記録"), ("Type/Type on a Path", "書式/パス上のテキスト"), ("View/Display Performance", "表示/表示品質"), + ("View/Extras", "表示/表示補助"), ("View/Grids & Guides", "表示/グリッドとガイド"), + ("View/Proof Setup", "表示/カラー校正設定"), + ("View/Rotate Spread", "表示/スプレッドを回転"), + ("View/Screen Mode", "表示/スクリーンモード"), + ("View/Separations Preview", "表示/分版プレビュー"), + ("View/Structure", "表示/構造"), ("Window/Arrange", "ウィンドウ/配置"), ("Window/Interactive", "ウィンドウ/インタラクティブ"), ("Window/Interface Color Theme", "ウィンドウ/インターフェイスのカラーテーマ"), diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index cf5d84810..78bfb6238 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -447,34 +447,6 @@ pub const MENUS: &[(&str, &[&str])] = &[ "cmd:type.sizeDown", "-", "ui:view.hiddenCharacters", - "ui:view.taggedFrames", - ">Rotate Spread", - "ui:view.rotateSpread|90° CW|{\"angle\": 90}", - "ui:view.rotateSpread|90° CCW|{\"angle\": -90}", - "ui:view.rotateSpread|180°|{\"angle\": 180}", - "ui:view.rotateSpread|Clear Rotation|{\"angle\": 0}", - "<", - ">Proof Setup", - "ui:view.proofSetup|Working CMYK|{\"target\": \"workingCmyk\"}", - "ui:view.proofSetup|Internet Standard RGB (sRGB)|{\"target\": \"srgb\"}", - "ui:view.proofSetup|Legacy Macintosh RGB|{\"target\": \"legacyMacRgb\"}", - "ui:view.proofSetup|Monitor RGB|{\"target\": \"monitorRgb\"}", - "ui:view.proofSetup|Color Blindness – Protanopia|{\"target\": \"protanopia\"}", - "ui:view.proofSetup|Color Blindness – Deuteranopia|{\"target\": \"deuteranopia\"}", - "-", - "ui:view.proofSetup|Simulate Paper Color|{\"simulatePaper\": \"toggle\"}", - "<", - "ui:view.proofColors", - "ui:view.flattenerPreview", - "ui:view.tagMarkers", - ">Separations Preview", - "ui:view.separations|Off|{\"plate\": null, \"inkLimit\": null}", - "ui:view.separations|Cyan|{\"plate\": \"cyan\"}", - "ui:view.separations|Magenta|{\"plate\": \"magenta\"}", - "ui:view.separations|Yellow|{\"plate\": \"yellow\"}", - "ui:view.separations|Black|{\"plate\": \"black\"}", - "ui:view.separations|Ink Limit 300%|{\"inkLimit\": 300}", - "<", "-", ">Hyperlinks & Cross-References", "cmd:hyperlink.create", @@ -667,6 +639,28 @@ pub const MENUS: &[(&str, &[&str])] = &[ ( "View", &[ + "ui:view.overprintPreview", + ">Proof Setup", + "ui:view.proofSetup|Working CMYK|{\"target\": \"workingCmyk\"}", + "ui:view.proofSetup|Internet Standard RGB (sRGB)|{\"target\": \"srgb\"}", + "ui:view.proofSetup|Legacy Macintosh RGB|{\"target\": \"legacyMacRgb\"}", + "ui:view.proofSetup|Monitor RGB|{\"target\": \"monitorRgb\"}", + "ui:view.proofSetup|Color Blindness – Protanopia|{\"target\": \"protanopia\"}", + "ui:view.proofSetup|Color Blindness – Deuteranopia|{\"target\": \"deuteranopia\"}", + "-", + "ui:view.proofSetup|Simulate Paper Color|{\"simulatePaper\": \"toggle\"}", + "<", + "ui:view.proofColors", + ">Separations Preview", + "ui:view.separations|Off|{\"plate\": null, \"inkLimit\": null}", + "ui:view.separations|Cyan|{\"plate\": \"cyan\"}", + "ui:view.separations|Magenta|{\"plate\": \"magenta\"}", + "ui:view.separations|Yellow|{\"plate\": \"yellow\"}", + "ui:view.separations|Black|{\"plate\": \"black\"}", + "ui:view.separations|Ink Limit 300%|{\"inkLimit\": 300}", + "<", + "ui:view.flattenerPreview", + "-", "ui:view.zoomIn", "ui:view.zoomOut", "ui:view.fitPage", @@ -675,9 +669,20 @@ pub const MENUS: &[(&str, &[&str])] = &[ "ui:view.actualSize", "ui:view.entirePasteboard", "-", - "ui:view.overprintPreview", - "ui:view.togglePreview", - "ui:view.togglePresentation", + ">Rotate Spread", + "ui:view.rotateSpread|90° CW|{\"angle\": 90}", + "ui:view.rotateSpread|90° CCW|{\"angle\": -90}", + "ui:view.rotateSpread|180°|{\"angle\": 180}", + "ui:view.rotateSpread|Clear Rotation|{\"angle\": 0}", + "<", + "-", + ">Screen Mode", + "ui:view.screenMode|Normal|{\"mode\": \"normal\"}", + "ui:view.screenMode|Preview|{\"mode\": \"preview\"}", + "ui:view.screenMode|Bleed|{\"mode\": \"bleed\"}", + "ui:view.screenMode|Slug|{\"mode\": \"slug\"}", + "ui:view.screenMode|Presentation|{\"mode\": \"presentation\"}", + "<", ">Display Performance", "ui:view.fastDisplay", "ui:view.typicalDisplay", @@ -685,10 +690,11 @@ pub const MENUS: &[(&str, &[&str])] = &[ "<", "-", "ui:view.rulers", + "-", + ">Extras", "ui:view.frameEdges", "ui:view.textThreads", - "ui:view.hiddenCharacters", - "-", + "<", ">Grids & Guides", "ui:view.guides", "ui:view.snapToGuides", @@ -699,6 +705,11 @@ pub const MENUS: &[(&str, &[&str])] = &[ "-", "ui:app.deleteAllGuides", "<", + "-", + ">Structure", + "ui:view.taggedFrames", + "ui:view.tagMarkers", + "<", ], ), ( @@ -2006,6 +2017,17 @@ pub fn checked(app: &DesignApp, id: &str, params: &Value) -> Option { "view.proofColors" => app.ui.proof_colors, "view.flattenerPreview" => app.ui.flattener_preview, "view.tagMarkers" => app.ui.tag_markers, + "view.screenMode" => { + let mode = match params.get("mode").and_then(Value::as_str) { + Some("normal") => crate::ScreenMode::Normal, + Some("preview") => crate::ScreenMode::Preview, + Some("bleed") => crate::ScreenMode::Bleed, + Some("slug") => crate::ScreenMode::Slug, + Some("presentation") => crate::ScreenMode::Presentation, + _ => return None, + }; + app.ui.screen_mode == mode + } "view.rotateSpread" => match params.get("angle").and_then(Value::as_i64) { Some(0) => app.session.active().is_some_and(|st| st.doc.spreads.iter().all(|sp| sp.pages.first().is_none_or(|p| p.view_rotation == 0))), _ => return None, @@ -2728,6 +2750,64 @@ pub fn command_search(app: &DesignApp, query: &str) -> Vec { mod tests { use super::*; + /// The entries (command ids) of a menu bar menu, submenus included. + fn menu_ids(title: &str) -> Vec<&'static str> { + let items = MENUS.iter().find(|(t, _)| *t == title).map(|(_, items)| *items).unwrap_or_default(); + items.iter().filter_map(|e| e.split_once(':').map(|(_, rest)| rest.split('|').next().unwrap_or(rest))).collect() + } + + #[test] + fn view_items_are_in_the_view_menu_as_in_indesign() { + let (view, typ) = (menu_ids("View"), menu_ids("Type")); + for id in [ + "view.rotateSpread", + "view.proofSetup", + "view.proofColors", + "view.flattenerPreview", + "view.separations", + "view.taggedFrames", + "view.tagMarkers", + ] { + assert!(view.contains(&id), "{id} is in View"); + assert!(!typ.contains(&id), "{id} isn't in Type"); + } + // Screen Mode and Extras are View submenus; Show Hidden Characters stays in Type. + assert!(view.contains(&"view.screenMode") && view.contains(&"view.frameEdges")); + assert!(typ.contains(&"view.hiddenCharacters") && !view.contains(&"view.hiddenCharacters")); + // W still toggles Normal / Preview without a menu entry. + assert_eq!(ui_label("view.togglePreview").and_then(|c| c.1), Some("W")); + } + + /// The submenu paths of a menu bar menu ("View/Screen Mode"), as Edit › Menus keys them. + fn submenu_paths(title: &str) -> Vec { + let items = MENUS.iter().find(|(t, _)| *t == title).map(|(_, items)| *items).unwrap_or_default(); + let mut stack = vec![title.to_string()]; + let mut out = Vec::new(); + for e in items { + if let Some(name) = e.strip_prefix('>') { + let path = format!("{}/{name}", stack.last().map_or("", String::as_str)); + out.push(path.clone()); + stack.push(path); + } else if *e == "<" { + stack.pop(); + } + } + out + } + + #[test] + fn screen_mode_shows_the_current_mode_and_view_submenus_are_translated() { + let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); + let mode = |app: &DesignApp, m: &str| checked(app, "view.screenMode", &json!({ "mode": m })); + assert_eq!((mode(&app, "normal"), mode(&app, "preview")), (Some(true), Some(false))); + app.ui.screen_mode = crate::ScreenMode::Preview; + assert_eq!((mode(&app, "normal"), mode(&app, "preview")), (Some(false), Some(true))); + // Every View submenu has its Japanese path (Edit › Menus shows them). + for path in submenu_paths("View") { + assert_ne!(crate::i18n::tr("ja", &path), path, "{path} has no Japanese row"); + } + } + fn walk(items: &[Item], out: &mut Vec<(String, String, Value)>) { for it in items { match it {