diff --git a/ROADMAP.md b/ROADMAP.md index 6cf80d362..16bd8b1da 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -131,6 +131,7 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap ## Progress log +- 2026-11-04: New Document: the units chosen in Preset Details now belong to the chosen document, as the pane's options are listed for the selected preset; the chosen card shows them, every other Recent and Saved card keeps the units its document or preset was made in, and the built-in tabs still follow the chosen units, so switching between inches and millimetres no longer restyles every recent template. Scores and estimates unchanged. - 2026-11-02: Mouse wheel over a closed font-family field in Character, Properties or Control changes selected text through the family list using saved font filters (excluding search text), one undo step per notch, stopping at the ends (#5253). Scores and estimates unchanged. - 2026-10-31: Pattern libraries: Window › Swatch Libraries › Patterns and the Swatches panel's library menu open Geometric Patterns, a built-in library of eleven pattern swatches (dots, stripes, checks, a grid and shapes); clicking one adds its pattern and swatch to the document, once, and applies it to the active fill or stroke (`swatch.library.add`). - 2026-11-01: Find Nearest Swatch joins the Color Effects add-on (Object › Plug-ins › Color Effects): for each colour of the selection, the closest swatches of the document, a built-in library or a library or colour book the user loaded, with their CIEDE2000 difference; Apply gives each colour its nearest swatch as one undo step, linking global and spot swatches and adding a library's to the document first, so a process colour becomes the closest spot colour (`addon.color.nearestSwatch`, `addon.color.applyNearestSwatch`, #4923). diff --git a/crates/ui-egui/src/dialogs/new_document.rs b/crates/ui-egui/src/dialogs/new_document.rs index 5d53ebe73..1a4c39389 100644 --- a/crates/ui-egui/src/dialogs/new_document.rs +++ b/crates/ui-egui/src/dialogs/new_document.rs @@ -41,7 +41,8 @@ const PARAMS: [&str; 12] = [ "rasterEffectsPpi", "previewMode", ]; -/// The preset the fields were last filled from (its fields as `apply` set them). +/// The preset the fields were last filled from, as `apply` set them; a Recent or Saved card keeps +/// its own units here, which the units chosen in Preset Details don't change. const APPLIED: &str = "__applied"; /// Save Preset's name field is showing. const SAVING: &str = "__saving"; @@ -257,21 +258,50 @@ fn presets(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { ui.horizontal_wrapped(|ui| { ui.spacing_mut().item_spacing = egui::vec2(12.0, 12.0); let (w, h, preset, units) = (d.f64("width", 0.0), d.f64("height", 0.0), d.str("preset"), unit(d)); + // The fields were last filled from this preset's card; Recent keeps one entry per size + // and units, so the units tell two entries of one size apart. + let chosen = d + .fields + .get(APPLIED) + .filter(|a| a.get("preset").and_then(Value::as_str) == Some(preset.as_str())) + .and_then(Value::as_object) + .cloned(); for s in &list { - // The built-in presets' cards show the chosen orientation (square sizes have none) - // and the units chosen in Preset Details; choosing one keeps both. Recent and Saved - // show each entry as it was made, so a portrait and a landscape A3 are two cards - // (#2860). - let mut s = DocSettings { units, ..s.clone() }; - if cat > 1 && ((w > h && s.height > s.width) || (h > w && s.width > s.height)) { - std::mem::swap(&mut s.width, &mut s.height); + // The built-in presets' cards show the chosen orientation (square sizes have none); + // Recent and Saved show each entry as it was made, so a portrait and a landscape A3 + // are two cards (#2860). + let mut card = s.clone(); + if cat > 1 && ((w > h && card.height > card.width) || (h > w && card.width > card.height)) { + std::mem::swap(&mut card.width, &mut card.height); } - let selected = d.fields.contains_key(APPLIED) && s.name == preset && s.width == w && s.height == h; - let card = preset_card(ui, &s, selected); - if card.clicked() || card.double_clicked() { - apply(d, &s); + // The built-in presets all share the units chosen in Preset Details. + if cat > 1 { + card.units = units; } - create |= card.double_clicked(); + let selected = chosen.as_ref().is_some_and(|a| { + a.get("preset").and_then(Value::as_str) == Some(card.name.as_str()) + && a.get("width").and_then(Value::as_f64) == Some(card.width) + && a.get("height").and_then(Value::as_f64) == Some(card.height) + && (cat > 1 || a.get("units").and_then(Value::as_str) == Some(s.units.label())) + }); + // The chosen card shows the units chosen in Preset Details, which the new document + // will have (#2078); the others keep their own, so choosing one document's units + // doesn't restyle the whole tab. + if selected { + card.units = units; + } + let resp = preset_card(ui, &card, selected); + if resp.clicked() || resp.double_clicked() { + apply(d, &card); + // The card is still the entry it was, whatever units it shows: its own units say + // which entry it is, so a chosen card stays chosen after a units change. + if cat < 2 + && let Some(Value::Object(a)) = d.fields.get_mut(APPLIED) + { + a.insert("units".into(), json!(s.units.label())); + } + } + create |= resp.double_clicked(); } }); }); diff --git a/crates/ui-egui/src/panels/tests_units.rs b/crates/ui-egui/src/panels/tests_units.rs index da5be17f4..687174e83 100644 --- a/crates/ui-egui/src/panels/tests_units.rs +++ b/crates/ui-egui/src/panels/tests_units.rs @@ -5,7 +5,7 @@ use serde_json::json; use vectorcraft_doc::Unit; -use super::tests_appearance::{app_with_rect, frame_events, run}; +use super::tests_appearance::{app_with_rect, click, frame_events, run, text_rect}; use super::*; /// Every text drawn in the second headless frame of `f` (windows size themselves in the first). @@ -171,3 +171,87 @@ fn new_document_cards_follow_the_chosen_units() { assert!(shows(&t, "288 × 432 px") && !shows(&t, "101.6 × 152.4 mm"), "Postcard in px: {t:?}"); assert_eq!(app.ui.dialog.as_ref().unwrap().fields["preset"], json!("Postcard")); } + +/// Choosing units in Preset Details changes the chosen document, not every recent one: each card +/// keeps the units its document was made in, so switching between inches and millimetres leaves the +/// other templates as they were; choosing another card brings its units back. +#[test] +fn recent_cards_keep_their_own_units() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + // 8.5 × 11 in, then 210 × 297 mm, which Recent lists first and the dialog opens on. + run(&mut app, "file.new", json!({"width": "8.5 in", "height": "11 in", "units": "Inches"})); + run(&mut app, "file.new", json!({"width": "210 mm", "height": "297 mm", "units": "Millimeters"})); + app.run("file.newDialog", json!({})).unwrap(); + assert_eq!(app.ui.dialog.as_ref().unwrap().fields["units"], json!("Millimeters")); + app.ui.dialog.as_mut().unwrap().fields.insert("units".into(), json!("Points")); + let t = dialog_texts(&mut app); + assert!(shows(&t, "595.276 × 841.89 pt"), "the chosen entry in points: {t:?}"); + assert!(shows(&t, "8.5 × 11 in"), "the other entry keeps its inches: {t:?}"); + assert!(!shows(&t, "612 × 792 pt"), "the inches entry isn't turned into points: {t:?}"); + // Clicking the inches card makes it the chosen one, in its own units. + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let mut draw = |app: &mut VectorcraftApp, ui: &mut Ui| crate::dialogs::show(app, ui.ctx()); + frame_events(&ctx, &mut app, vec![], &mut draw); // the window sizes itself here + let inches = text_rect(&frame_events(&ctx, &mut app, vec![], &mut draw), "8.5 × 11 in").center(); + click(&ctx, &mut app, inches, &mut draw); + let d = app.ui.dialog.as_ref().unwrap(); + assert_eq!(d.fields["units"], json!("Inches")); + assert_eq!((d.fields["width"].clone(), d.fields["height"].clone()), (json!(612.0), json!(792.0))); +} + +/// A saved preset keeps the units it was saved in too, whichever units the dialog is showing. +#[test] +fn saved_cards_keep_their_own_units() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + run(&mut app, "file.newPresets.save", json!({"name": "A4 mm", "width": "210 mm", "height": "297 mm", "units": "Millimeters"})); + app.run("file.newDialog", json!({})).unwrap(); + let d = app.ui.dialog.as_mut().unwrap(); + d.fields.insert("category".into(), json!("Saved")); + d.fields.insert("units".into(), json!("Points")); + let t = dialog_texts(&mut app); + assert!(shows(&t, "210 × 297 mm") && !shows(&t, "595.276 × 841.89 pt"), "the saved preset keeps millimetres: {t:?}"); +} + +/// Clicking the card that is already chosen leaves it chosen, in the units the dialog is showing: +/// the entry's own units still say which card it is. +#[test] +fn clicking_the_chosen_recent_card_keeps_it_chosen() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + run(&mut app, "file.new", json!({"width": "8.5 in", "height": "11 in", "units": "Inches"})); + app.run("file.newDialog", json!({})).unwrap(); + app.ui.dialog.as_mut().unwrap().fields.insert("units".into(), json!("Points")); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let mut draw = |app: &mut VectorcraftApp, ui: &mut Ui| crate::dialogs::show(app, ui.ctx()); + frame_events(&ctx, &mut app, vec![], &mut draw); // the window sizes itself here + let card = text_rect(&frame_events(&ctx, &mut app, vec![], &mut draw), "612 × 792 pt").center(); + click(&ctx, &mut app, card, &mut draw); + let t = dialog_texts(&mut app); + assert!(shows(&t, "612 × 792 pt") && !shows(&t, "8.5 × 11 in"), "the card stays chosen in points: {t:?}"); +} + +/// Recent keeps one entry per size and units, so two entries can be one size in two units: the +/// chosen card is the entry the fields were filled from, not every card of that size. +#[test] +fn recent_entries_of_one_size_in_two_units_stay_apart() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + // Both are 612 × 792 pt; Recent lists the points one first, which the dialog opens on. + run(&mut app, "file.new", json!({"width": "8.5 in", "height": "11 in", "units": "Inches"})); + run(&mut app, "file.new", json!({"width": "612 pt", "height": "792 pt", "units": "Points"})); + app.run("file.newDialog", json!({})).unwrap(); + app.ui.dialog.as_mut().unwrap().fields.insert("units".into(), json!("Inches")); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let mut draw = |app: &mut VectorcraftApp, ui: &mut Ui| crate::dialogs::show(app, ui.ctx()); + frame_events(&ctx, &mut app, vec![], &mut draw); // the window sizes itself here + let texts = frame_events(&ctx, &mut app, vec![], &mut draw); + let mut cards: Vec = texts.iter().filter(|(t, _)| t == "8.5 × 11 in").map(|(_, r)| *r).collect(); + cards.sort_by(|a, b| a.center().x.total_cmp(&b.center().x)); + assert_eq!(cards.len(), 2, "both cards read in inches: {texts:?}"); + // The right-hand card is the inches entry: choosing it leaves the points entry in points. + click(&ctx, &mut app, cards[1].center(), &mut draw); + assert_eq!(app.ui.dialog.as_ref().unwrap().fields["units"], json!("Inches")); + let t = dialog_texts(&mut app); + assert!(shows(&t, "612 × 792 pt"), "the points entry keeps its points: {t:?}"); +} diff --git a/docs/roadmap.md b/docs/roadmap.md index c75e857b0..cf8cf6a5d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap: milestones and what's next -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-03 · **Change:** minor (OpenType panel offers font-defined Stylistic Sets; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (New Document's units belong to the chosen preset; Recent and Saved cards keep their own units; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -28,6 +28,7 @@ M7 type: Touch Type moves a right-to-left character along its line and up from i | Date | Change | Summary | |---|---|---| +| 2026-11-04 | minor | New Document: the units chosen in Preset Details belong to the chosen preset; Recent and Saved cards keep the units their document or preset was made in, and the built-in tabs still follow the chosen units (#2078). Scores and estimates unchanged. | | 2026-11-03 | minor | M1: Enclosing Lasso provides whole-object enclosure selection as an add-on with no default shortcut or Basic toolbar slot; estimates unchanged. | | 2026-11-03 | minor | New gradients use CMYK default stops in CMYK documents, including Gradient tool creation. Scores and estimates unchanged. | | 2026-10-30 | minor | M4: G9: `.ai`/EPS editing data: a blend takes the transparency (opacity, blend mode, knockout) in its state block after its record, so its steps knock each other out as the page draws them; a user's file comes in 0.6% from its page instead of 1.3%. Scores and estimates unchanged. | @@ -158,6 +159,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| +| 2026-11-04 | minor | New Document: the units chosen in Preset Details belong to the chosen preset; Recent and Saved cards keep their own units; scores and estimates unchanged. | | 2026-11-03 | minor | M7: the OpenType panel offers the selected font's available Stylistic Sets with custom or numbered names; estimates unchanged. | | 2026-11-03 | minor | M1: Enclosing Lasso adds freehand whole-object selection under Object › Plug-ins; estimates unchanged. | | 2026-11-02 | minor | Font-family fields in Character, Properties and Control step selected text with the wheel (#5253), with one undo step per notch; scores and estimates unchanged. | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 849d7fa00..02329b46d 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-11-04 · **Change:** minor (Document Tools add-on: Batch Trace Folder, #4921; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (New Document's units belong to the chosen preset; Recent and Saved cards keep their own units; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -23,6 +23,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard | Date | Change | Summary | |---|---|---| +| 2026-11-04 | minor | New Document: the units chosen in Preset Details belong to the chosen preset, as the public page lists the pane's options for the selected preset; the chosen card shows them, every other Recent and Saved card keeps the units its document or preset was made in, and the built-in tabs still follow the chosen units (#2078). Scores and estimates unchanged. | | 2026-11-04 | minor | Document Tools add-on (a VectorCraft addition): Batch Trace Folder under Object › Plug-ins (`addon.document.batchTraceFolder`, #4921); evidence added; scores unchanged. | | 2026-11-04 | minor | Object › Expand turns selected symbol instances into their art as Break Link places it, one level deep, with the Expand dialog's Fill and Stroke reaching that art. Scores and estimates unchanged. | | 2026-11-03 | minor | The OpenType panel offers the selected font's stylistic sets by custom or numbered name, and allows several to be active. Scores and estimates unchanged. | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 62d62e5d9..26f56b16f 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-11-04 · **Change:** minor (Document Tools add-on: Batch Trace Folder under Object › Plug-ins, #4921; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (New Document's units belong to the chosen preset; Recent and Saved cards keep their own units; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -423,6 +423,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| +| 2026-11-04 | minor | New Document: the units chosen in Preset Details belong to the chosen document, as the pane's options are listed "for the selected preset" ([public source](https://helpx.adobe.com/illustrator/desktop/add-and-import-files/start-a-new-file/create-and-save-custom-document-presets.html)); the chosen card shows them, every other Recent and Saved card keeps the units its document or preset was made in, and the built-in tabs still follow the chosen units (#2078); scores and estimates unchanged | | 2026-11-01 | minor | Object › Image Trace › Batch Trace Folder asks for a source and a destination folder and traces each image into it (#4921); scores and estimates unchanged | | 2026-11-01 | minor | Edit › Copy and Cut take the selected ruler guides, with the objects or alone; pasting puts them on the layer new art goes to, moved as the objects are (alone: where they were), Paste in Place onto the active artboard and Paste on All Artboards onto each (#3226); scores and estimates unchanged | | 2026-11-04 | minor | Area type: Align Top / Center / Bottom / Justify Vertically buttons in the Properties panel (Paragraph section) and the Control panel set its vertical alignment in the frame, as Area Type Options does; turned off for frames with rows or columns and threaded frames (#3759); scores and estimates unchanged |