Merge PR #1024: New Workspace: Capture Panel Locations and Menu Customization, as InDesign's

Conflicts:
- crates/ui-egui/src/dialogs.rs: kept the branch's dialog code and added #1024's New Workspace width, Capture checkboxes and confirm params at the branch's indentation.
- crates/ui-egui/src/i18n.rs, i18n/{ar,de,it,ja,ka,pt_br,uk}.rs: kept both sides.
This commit is contained in:
aiongg
2026-10-16 04:03:12 +00:00
12 changed files with 85 additions and 8 deletions
+1
View File
@@ -458,6 +458,7 @@ Newest first.
- 2026-10-15: A page scrolled or jumped to shows its paper while its render is on its way, as in InDesign, instead of the black of its shadow.
- 2026-10-15: Tab, Shift+Tab and Presentation hide the icon column's panel flyout and the classic floating panels too, as InDesign hides floating panels.
- 2026-10-15: Layers panel: an object's row is renamed with a slow double-click (a second click on the selected row), as in InDesign; a quick double-click only selects it.
- **2026-10-15** · New Workspace is InDesign's: Name, and Capture › Panel Locations and Menu Customization (the items hidden with Edit › Menus), both on by default; a workspace saved without panel locations leaves the panels where they are. No readiness percentage change.
- **2026-10-14** · The Type tool draws a new text frame over a locked empty frame, as InDesign does, instead of turning the locked frame into a text frame. No readiness percentage change.
- **2026-10-14** · 100% black shows as pure black on screen by default, as InDesign's Appearance of Black does out of the box (Display All Blacks as Rich Black); Preferences › Appearance of Black › Display All Blacks Accurately still shows it as the dark grey it prints, and a saved choice is kept. No readiness percentage change.
- **2026-10-14** · Pathfinder keeps the frontmost object, as InDesign does: a photo placed over shapes and intersected with them shows through the shapes (it was dropped, and the shapes kept their black fill); Add, Exclude Overlap and Minus Back take the front object's fill too, Subtract still keeps the back one. No readiness percentage change.
+9 -6
View File
@@ -2036,7 +2036,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) {
let available = (ctx.content_rect().size() - frame_margin - egui::vec2(16.0, 16.0)).max(egui::Vec2::splat(1.0));
let preferred_width: f32 = match d.id.as_str() {
"alert" => 440.0,
"closeDocument" | "updateLink" | "deletePages" => 380.0,
"closeDocument" | "updateLink" | "deletePages" | "newWorkspace" => 380.0,
"newDocument" => 960.0,
"numberingSection" => 560.0,
crate::effects_dialog::ID => 720.0,
@@ -2534,10 +2534,10 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) {
crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Name:"));
text_field(ui, &mut d, "name", 220.0);
});
crate::rtl::label(
ui,
egui::RichText::new(crate::i18n::tr(&app.ui.language, "Saves which bars show and where the panels are.")).size(10.5),
);
ui.add_space(6.0);
crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Capture")).font(semibold(12.0)));
check(ui, &mut d, "panelLocations", crate::i18n::tr(&app.ui.language, "Panel Locations"));
check(ui, &mut d, "menuCustomization", crate::i18n::tr(&app.ui.language, "Menu Customization"));
}
"fittingOptions" => {
check(ui, &mut d, "autoFit", crate::i18n::tr(&app.ui.language, "Auto-Fit"));
@@ -3327,7 +3327,10 @@ pub fn confirm(app: &mut DesignApp) -> Result<Value, String> {
doc["glyphFallback"] = json!(d.b("glyphFallback"));
app.run("document.preferences", doc)
}
"newWorkspace" => app.run("window.newWorkspace", json!({"name": d.s("name")})),
"newWorkspace" => app.run(
"window.newWorkspace",
json!({"name": d.s("name"), "panelLocations": d.b("panelLocations"), "menuCustomization": d.b("menuCustomization")}),
),
"deletePages" => {
if d.b("dontShowAgain") {
app.ui.skip_delete_pages_warning = true;
+2
View File
@@ -119,6 +119,8 @@ const TABLE: &[(&str, [&str; 5])] = &[
"受影响的页面包含对象。仍要删除这些页面吗?",
],
),
("Capture", ["Erfassen", "Capturer", "Capturar", "取り込み", "捕捉"]),
("Panel Locations", ["Bedienfeldpositionen", "Emplacements des panneaux", "Ubicaciones de paneles", "パネルの位置", "面板位置"]),
(
"Bulleted & Numbered Lists",
[
+2
View File
@@ -52,6 +52,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Don't show again", "عدم الإظهار مرة أخرى"),
("The affected page contains objects. Delete the page anyway?", "تحتوي الصفحة المعنية على كائنات. هل تريد حذف الصفحة على أي حال؟"),
("The affected pages contain objects. Delete the pages anyway?", "تحتوي الصفحات المعنية على كائنات. هل تريد حذف الصفحات على أي حال؟"),
("Capture", "التقاط"),
("Panel Locations", "مواضع اللوحات"),
("Bulleted & Numbered Lists", "القوائم ذات التعداد النقطي والرقمي"),
("Current Page Number", "رقم الصفحة الحالية"),
("Apply Color (,)", "تطبيق اللون (,)"),
+2
View File
@@ -37,6 +37,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Don't show again", "Nicht mehr anzeigen"),
("The affected page contains objects. Delete the page anyway?", "Die betroffene Seite enthält Objekte. Seite trotzdem löschen?"),
("The affected pages contain objects. Delete the pages anyway?", "Die betroffenen Seiten enthalten Objekte. Seiten trotzdem löschen?"),
("Capture", "Erfassen"),
("Panel Locations", "Bedienfeldpositionen"),
("Clear", "Löschen"),
("Duplicate", "Duplizieren"),
("Select All", "Alles auswählen"),
+2
View File
@@ -51,6 +51,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Don't show again", "Non mostrare più"),
("The affected page contains objects. Delete the page anyway?", "La pagina interessata contiene oggetti. Eliminarla comunque?"),
("The affected pages contain objects. Delete the pages anyway?", "Le pagine interessate contengono oggetti. Eliminarle comunque?"),
("Capture", "Acquisisci"),
("Panel Locations", "Posizioni pannelli"),
("Bulleted & Numbered Lists", "Elenchi puntati e numerati"),
("Current Page Number", "Numero di pagina corrente"),
("Apply Color (,)", "Applica colore (,)"),
+2
View File
@@ -54,6 +54,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Don't show again", "今後表示しない"),
("The affected page contains objects. Delete the page anyway?", "対象のページにはオブジェクトが含まれています。ページを削除しますか?"),
("The affected pages contain objects. Delete the pages anyway?", "対象のページにはオブジェクトが含まれています。ページを削除しますか?"),
("Capture", "取り込み"),
("Panel Locations", "パネルの位置"),
("Bulleted & Numbered Lists", "箇条書き"),
("Apply Color (,)", "カラーを適用 (,)"),
("Apply Gradient (.)", "グラデーションを適用 (.)"),
+2
View File
@@ -37,6 +37,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Don't show again", "აღარ აჩვენო"),
("The affected page contains objects. Delete the page anyway?", "გვერდი შეიცავს ობიექტებს. მაინც წავშალო გვერდი?"),
("The affected pages contain objects. Delete the pages anyway?", "გვერდები შეიცავს ობიექტებს. მაინც წავშალო გვერდები?"),
("Capture", "შენახვა"),
("Panel Locations", "პანელების მდებარეობა"),
("Reverse Path", "ტრაექტორიის შებრუნება"),
("Set Anchor", "საყრდენი წერტილის დაყენება"),
("Clear", "გასუფთავება"),
+2
View File
@@ -54,6 +54,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Don't show again", "Não mostrar novamente"),
("The affected page contains objects. Delete the page anyway?", "A página afetada contém objetos. Excluir a página mesmo assim?"),
("The affected pages contain objects. Delete the pages anyway?", "As páginas afetadas contêm objetos. Excluir as páginas mesmo assim?"),
("Capture", "Capturar"),
("Panel Locations", "Locais dos painéis"),
("Bulleted & Numbered Lists", "Listas com marcadores e numeradas"),
("Show Hidden Characters", "Mostrar caracteres ocultos"),
("Current Page Number", "Número da página atual"),
+2
View File
@@ -52,6 +52,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Don't show again", "Більше не показувати"),
("The affected page contains objects. Delete the page anyway?", "Сторінка містить об’єкти. Усе одно видалити сторінку?"),
("The affected pages contain objects. Delete the pages anyway?", "Сторінки містять об’єкти. Усе одно видалити сторінки?"),
("Capture", "Зберегти"),
("Panel Locations", "Розташування панелей"),
("Bulleted & Numbered Lists", "Марковані й нумеровані списки"),
("Current Page Number", "Номер поточної сторінки"),
("Apply Color (,)", "Застосувати колір (,)"),
+7
View File
@@ -119,6 +119,13 @@ pub struct SavedWorkspace {
/// The workspace's own icon column (see [`UiState::icon_groups`]).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub icon_groups: Option<Vec<Vec<String>>>,
/// Saved with New Workspace › Capture › Panel Locations off: choosing the workspace leaves the
/// bars and panels where they are.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub keep_panels: bool,
/// New Workspace › Capture › Menu Customization: the menu items hidden with Edit › Menus.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hidden_menu_items: Option<Vec<String>>,
}
/// A missing on-by-default bool stays on. `bool::default` is false.
+52 -2
View File
@@ -289,7 +289,12 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
None,
"{name: Advanced|Book|Digital Publishing|Essentials|Essentials Classic|Interactive for PDF|Printing and Proofing|Typography or a saved workspace}",
),
("window.newWorkspace", "New Workspace…", None, "{name} — saves the current bars and panel arrangement"),
(
"window.newWorkspace",
"New Workspace…",
None,
"{name, panelLocations?: bool (default true: the bars and panels), menuCustomization?: bool (default true: the menu items hidden with Edit › Menus)} — saves the workspace",
),
("window.deleteWorkspace", "Delete Workspace…", None, "{name}"),
("window.resetWorkspace", "Reset Workspace", None, "{} — back to the current workspace as saved (or its defaults)"),
("window.brightness", "Interface Color Theme", None, "{brightness: dark|mediumDark|mediumLight|light|highContrast}"),
@@ -1775,7 +1780,8 @@ fn run_ui_inner(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value
"window.toolsDoubleColumn" => flag(&mut app.ui.tools_double_column),
"window.newWorkspace" => {
let Some(name) = p.get("name").and_then(Value::as_str).map(str::trim).filter(|n| !n.is_empty()) else {
app.ui.dialog = Some(crate::dialogs::Dialog::new("newWorkspace", json!({"name": ""})));
app.ui.dialog =
Some(crate::dialogs::Dialog::new("newWorkspace", json!({"name": "", "panelLocations": true, "menuCustomization": true})));
return Some(Ok(Value::Null));
};
let u = &app.ui;
@@ -1792,6 +1798,8 @@ fn run_ui_inner(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value
docking: u.docking.clone(),
docking_hidden: u.docking_hidden.clone(),
icon_groups: u.icon_groups.clone(),
keep_panels: !p.get("panelLocations").and_then(Value::as_bool).unwrap_or(true),
hidden_menu_items: p.get("menuCustomization").and_then(Value::as_bool).unwrap_or(true).then(|| u.hidden_menu_items.clone()),
};
app.ui.custom_workspaces.retain(|x| x.name != name);
app.ui.custom_workspaces.push(w);
@@ -1820,6 +1828,13 @@ fn run_ui_inner(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value
app.ui.docking_opened = None;
let name = p.get("name").and_then(Value::as_str).unwrap_or("Essentials");
if let Some(w) = app.ui.custom_workspaces.iter().find(|w| w.name == name).cloned() {
if let Some(hidden) = w.hidden_menu_items {
app.ui.hidden_menu_items = hidden;
}
if w.keep_panels {
app.ui.workspace = w.name;
return Some(Ok(Value::Null));
}
app.ui.control_bar = w.control_bar;
app.ui.task_bar = w.task_bar;
app.ui.task_bar_pin = w.task_bar_pin;
@@ -4785,6 +4800,41 @@ mod tests {
assert_eq!(app.ui.workspace, "Essentials");
}
/// New Workspace › Capture (Learn Skills Daily's InDesign course, 11:40): Panel Locations and
/// Menu Customization, both on by default.
#[test]
fn new_workspace_captures_panel_locations_and_menu_customization() {
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
// The dialog offers both, checked.
run_ui(&mut app, "window.newWorkspace", &json!({})).unwrap().unwrap();
let d = app.ui.dialog.take().unwrap();
assert_eq!(
(d.id.as_str(), d.fields["panelLocations"].clone(), d.fields["menuCustomization"].clone()),
("newWorkspace", json!(true), json!(true))
);
// Both captured.
app.ui.dock_tab = "swatches".into();
app.ui.hidden_menu_items = vec!["Type/Glyphs".into()];
run_ui(&mut app, "window.newWorkspace", &json!({"name": "All"})).unwrap().unwrap();
// Menus only: choosing it leaves the panels where they are.
app.ui.hidden_menu_items = vec!["Layout/Pages".into()];
run_ui(&mut app, "window.newWorkspace", &json!({"name": "Menus", "panelLocations": false})).unwrap().unwrap();
// Panels only: the menus stay as they are.
app.ui.dock_tab = "layers".into();
run_ui(&mut app, "window.newWorkspace", &json!({"name": "Panels", "menuCustomization": false})).unwrap().unwrap();
app.ui.dock_tab = "properties".into();
app.ui.hidden_menu_items.clear();
run_ui(&mut app, "window.workspace", &json!({"name": "Menus"})).unwrap().unwrap();
assert_eq!((app.ui.dock_tab.as_str(), app.ui.hidden_menu_items.clone()), ("properties", vec!["Layout/Pages".to_string()]));
run_ui(&mut app, "window.workspace", &json!({"name": "Panels"})).unwrap().unwrap();
assert_eq!((app.ui.dock_tab.as_str(), app.ui.hidden_menu_items.clone()), ("layers", vec!["Layout/Pages".to_string()]));
run_ui(&mut app, "window.workspace", &json!({"name": "All"})).unwrap().unwrap();
assert_eq!((app.ui.dock_tab.as_str(), app.ui.hidden_menu_items.clone()), ("swatches", vec!["Type/Glyphs".to_string()]));
// A workspace saved before the options existed restores its panels.
let old: crate::SavedWorkspace = serde_json::from_value(json!({"name": "Old", "dockTab": "pages"})).unwrap();
assert!(!old.keep_panels && old.hidden_menu_items.is_none());
}
#[test]
fn essentials_classic_is_the_control_panel_and_iconic_groups() {
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());