diff --git a/crates/ui-egui/src/dialogs/new_document.rs b/crates/ui-egui/src/dialogs/new_document.rs index dde5c83fb..5d53ebe73 100644 --- a/crates/ui-egui/src/dialogs/new_document.rs +++ b/crates/ui-egui/src/dialogs/new_document.rs @@ -330,15 +330,7 @@ fn details(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog, b: &mut ui.add_space(8.0); ui.horizontal(|ui| { form::text(ui, d, "name", DETAILS - 50.0); - if widgets::icon_button(ui, "save", tl!("Save Preset"), d.bool(SAVING), 26.0).clicked() { - let saving = !d.bool(SAVING); - d.fields.insert(SAVING.into(), json!(saving)); - if saving && d.str("presetName").is_empty() { - let template = "Preset {number}"; - let name = crate::i18n::fmt(tl!(template), &[("number", &(app.session.prefs.new_doc_presets.len() + 1).to_string())]); - d.fields.insert("presetName".into(), json!(name)); - } - } + save_button(app, ui, d); }); if d.bool(SAVING) { save_row(app, ui, d); @@ -388,6 +380,19 @@ fn details(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog, b: &mut }); } +/// The Save Preset button beside the name: it shows or hides [`save_row`]. +fn save_button(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) { + if widgets::icon_button(ui, "save", tl!("Save Preset"), d.bool(SAVING), 26.0).clicked() { + let saving = !d.bool(SAVING); + d.fields.insert(SAVING.into(), json!(saving)); + if saving && d.str("presetName").is_empty() { + let template = "Preset {number}"; + let name = crate::i18n::fmt(tl!(template), &[("number", &(app.session.prefs.new_doc_presets.len() + 1).to_string())]); + d.fields.insert("presetName".into(), json!(name)); + } + } +} + /// Save Preset: the preset's name, Save and Cancel. fn save_row(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) { ui.add_space(4.0); @@ -520,7 +525,14 @@ fn show_more(app: &mut VectorcraftApp, ctx: &egui::Context) { ui.add_space(12.0); widgets::label_row(ui, tl!("Name:"), L, |ui| { form::text(ui, &mut d, "name", 300.0); + save_button(app, ui, &mut d); }); + if d.bool(SAVING) { + // Under the name, in the fields' column. + widgets::label_row(ui, "", L, |ui| { + ui.vertical(|ui| save_row(app, ui, &mut d)); + }); + } // Profile: the preset categories (the tabs); Size: the profile's presets. let profiles: Vec<&str> = newdoc::CATEGORIES.iter().map(|c| c.0).collect(); let profile = profiles.iter().find(|p| p.eq_ignore_ascii_case(&d.str("category"))).copied().unwrap_or("[Custom]"); @@ -894,4 +906,49 @@ mod tests { crate::dialogs::confirm(&mut app).unwrap(); assert_eq!(app.session.prefs.recent_new_docs[0].name, "Zine"); } + + /// Save Preset works in More Settings too (#4893), saving its settings: the artboard layout + /// only More Settings shows among them. + #[test] + fn more_settings_saves_a_preset_of_its_settings() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.newDialog", json!({})).unwrap(); + app.ui.dialog.as_mut().unwrap().kind = super::MORE.into(); + set(&mut app, "artboards", json!(4)); + set(&mut app, "artboardLayout", json!({"layout": "column", "spacing": 30})); + set(&mut app, super::SAVING, json!(true)); + set(&mut app, "presetName", json!("Strip")); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let mut time = 0.0; + let mut run = |app: &mut VectorcraftApp, events| { + time += 0.5; + let screen_rect = Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1400.0, 1000.0))); + let raw = egui::RawInput { screen_rect, events, time: Some(time), ..Default::default() }; + let mut out = ctx.run_ui(raw, |ui| crate::dialogs::show(app, ui.ctx())); + out.textures_delta.clear(); + out + }; + run(&mut app, vec![]); + let out = run(&mut app, vec![]); + fn walk(s: &egui::Shape, out: &mut Vec<(String, egui::Pos2)>) { + match s { + egui::Shape::Text(t) => out.push((t.galley.text().to_string(), t.visual_bounding_rect().center())), + egui::Shape::Vec(v) => v.iter().for_each(|s| walk(s, out)), + _ => {} + } + } + let mut texts = vec![]; + out.shapes.iter().for_each(|c| walk(&c.shape, &mut texts)); + assert!(texts.iter().any(|(t, _)| t == "Preset Name"), "the preset's name field shows"); + let at = texts.iter().find(|(t, _)| t == "Save").map(|(_, p)| *p).expect("a Save button"); + let press = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() }; + run(&mut app, vec![egui::Event::PointerMoved(at), press(true)]); + run(&mut app, vec![press(false)]); + let saved = app.session.prefs.new_doc_presets.iter().find(|p| p.name == "Strip").expect("saved"); + assert_eq!(saved.artboards, 4); + assert_eq!((saved.layout, saved.spacing), (super::ArtboardLayout::Column, 30.0)); + assert_eq!((field(&app, "category"), field(&app, "preset"), field(&app, super::SAVING)), (json!("Saved"), json!("Strip"), json!(false))); + assert_eq!(app.ui.dialog.as_ref().unwrap().kind, super::MORE, "More Settings stays open"); + } } diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 3daa404e8..e7e762140 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # Parity with Adobe Illustrator -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (Object › Rasterize with the CMYK colour model makes a CMYK image, #4882; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (New Document: Save Preset in More Settings, #4893; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -19,6 +19,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard | Date | Change | Summary | |---|---|---| +| 2026-10-31 | minor | New Document: More Settings has the Save Preset button beside the name too, saving its settings (artboard layout included) as a preset (#4893); scores unchanged. | | 2026-10-31 | minor | Object › Rasterize in a CMYK document with its CMYK colour model makes a CMYK image of the art's ink amounts (as CMYK exports draw them), so the Properties panel says CMYK and CMYK exports keep its inks; a transparent background becomes the image's opacity mask (#4882); scores unchanged. | | 2026-10-31 | minor | Export for Screens is laid out as Illustrator's: Artboards and Assets tabs with a grid or list of thumbnails, Clear Selection and Prefix under them, Create Sub-folders by Scale or Format (on by default), Export PDFs As Single File or Multiple Files (`pdfs`), iOS and Android rows, the format presets in a ☰ menu, and a Selected / files-to-export count; the naming pattern, Layers and Selection pages and folders list stay in `document.exportForScreens` for scripts and MCP; scores unchanged. | | 2026-10-31 | minor | Export for Screens is laid out as the reference app's: Artboards and Assets tabs with a grid or list of thumbnails, Clear Selection and Prefix under them, Create Sub-folders by Scale or Format (on by default), Export PDFs As Single File or Multiple Files (`pdfs`), iOS and Android rows, the format presets in a ☰ menu, and a Selected / files-to-export count; the naming pattern, Layers and Selection pages and folders list stay in `document.exportForScreens` for scripts and MCP; scores unchanged. | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index df96d0527..3296cb3a5 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI and interaction parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (Export for Screens laid out as the reference app's) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (New Document: Save Preset in More Settings, #4893; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -379,6 +379,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| +| 2026-10-31 | minor | New Document: More Settings has Save Preset beside the name, as Preset Details does, and saves its settings (#4893); scores and estimates unchanged | | 2026-10-31 | minor | Export for Screens is laid out as the reference app's: Artboards and Assets tabs with a grid or list of thumbnails, Clear Selection and Prefix under them, Create Sub-folders by Scale or Format (on by default), Export PDFs As Single File or Multiple Files (`pdfs`), iOS and Android rows, the format presets in a ☰ menu, and a Selected / files-to-export count; the naming pattern, Layers and Selection pages and folders list stay in `document.exportForScreens` for scripts and MCP; scores unchanged | | 2026-10-31 | minor | Direct Selection: with Alt held at the press, a marquee selects every path it touches whole and keeps the Group Selection pointer while dragging (#4890); a click on a selected object's centre point (Show Center) selects the object whole, and a drag from it moves the object (#4891); scores unchanged | | 2026-10-31 | minor | Arrange Tools add-on (a VectorCraft addition): Object › Plug-ins › Arrange Tools; scores and estimates unchanged |