From 1063f5090a795d93c3e81ba1d676dad843bad4ea Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 04:20:03 -0800 Subject: [PATCH 1/5] Artboard Options: Fade Region Outside Artboard darkens the pasteboard while the Artboard tool is active Artboard Options gets a Global group with Fade Region Outside Artboard and Update While Dragging. Both are application preferences (fadeRegionOutsideArtboard, artboardUpdateWhileDragging), on by default and also listed under Preferences > General > Artboard Options. While the Artboard tool is active, the canvas draws the pasteboard with each color channel at 60% of its value. With Update While Dragging off, the pasteboard draws as usual while the tool moves or resizes an artboard and darker again when the drag ends. Exports and print do not change. Docs: docs/mcp.md, docs/target-app-parity.md. --- crates/engine/src/cmd/prefscmds.rs | 3 + crates/engine/src/prefs.rs | 8 ++ crates/tools/src/xform/artboard.rs | 5 + crates/ui-egui/src/canvas.rs | 18 +++- crates/ui-egui/src/canvas/tests.rs | 92 +++++++++++++++++++ .../ui-egui/src/dialogs/artboard_options.rs | 60 ++++++++++-- crates/ui-egui/src/dialogs/tests.rs | 38 ++++++++ crates/ui-egui/src/i18n/bg.tsv | 2 + crates/ui-egui/src/i18n/bs.tsv | 2 + crates/ui-egui/src/i18n/ca.tsv | 2 + crates/ui-egui/src/i18n/cs.tsv | 2 + crates/ui-egui/src/i18n/da.tsv | 2 + crates/ui-egui/src/i18n/de.tsv | 2 + crates/ui-egui/src/i18n/el.tsv | 2 + crates/ui-egui/src/i18n/es.tsv | 2 + crates/ui-egui/src/i18n/fi.tsv | 2 + crates/ui-egui/src/i18n/fr.tsv | 2 + crates/ui-egui/src/i18n/hi.tsv | 2 + crates/ui-egui/src/i18n/hu.tsv | 2 + crates/ui-egui/src/i18n/id.tsv | 2 + crates/ui-egui/src/i18n/it.tsv | 2 + crates/ui-egui/src/i18n/ja.tsv | 2 + crates/ui-egui/src/i18n/ka.tsv | 2 + crates/ui-egui/src/i18n/ko.tsv | 2 + crates/ui-egui/src/i18n/nl.tsv | 2 + crates/ui-egui/src/i18n/pl.tsv | 2 + crates/ui-egui/src/i18n/pt-br.tsv | 2 + crates/ui-egui/src/i18n/ru.tsv | 2 + crates/ui-egui/src/i18n/sv.tsv | 2 + crates/ui-egui/src/i18n/tr.tsv | 2 + crates/ui-egui/src/i18n/uk.tsv | 2 + crates/ui-egui/src/i18n/vi.tsv | 2 + crates/ui-egui/src/i18n/zh-hans.tsv | 2 + crates/ui-egui/src/i18n/zh-hant.tsv | 2 + docs/mcp.md | 9 ++ docs/target-app-parity.md | 1 + 36 files changed, 281 insertions(+), 7 deletions(-) diff --git a/crates/engine/src/cmd/prefscmds.rs b/crates/engine/src/cmd/prefscmds.rs index 6bd9c8dc5..a1a51ec8e 100644 --- a/crates/engine/src/cmd/prefscmds.rs +++ b/crates/engine/src/cmd/prefscmds.rs @@ -423,6 +423,9 @@ pub const PREF_SPECS: &[PrefSpec] = &[ // The Align panel menu's Align to Glyph Bounds: Align and Distribute measure type by its glyphs' ink. p!("alignGlyphBoundsPoint", "Type", "Align to Glyph Bounds", "Point Text", bool), p!("alignGlyphBoundsArea", "Type", "Align to Glyph Bounds", "Area Text", bool), + // Artboard Options › Global: the canvas darkens the pasteboard while the Artboard tool is active. + p!("fadeRegionOutsideArtboard", "General", "Artboard Options", "Fade Region Outside Artboard", bool), + p!("artboardUpdateWhileDragging", "General", "Artboard Options", "Update While Dragging", bool), ]; pub fn spec(key: &str) -> Option<&'static PrefSpec> { diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index b49c40786..7fe288754 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -41,6 +41,12 @@ pub struct Prefs { /// Align panel menu › Align to Glyph Bounds › Area Text: the same for area type, instead of /// its frame. pub align_glyph_bounds_area: bool, + /// Artboard Options › Fade Region Outside Artboard: while the Artboard tool is active, the + /// canvas draws the pasteboard outside the artboards darker. + pub fade_region_outside_artboard: bool, + /// Artboard Options › Update While Dragging: the pasteboard stays darker while the Artboard + /// tool moves or resizes an artboard; off, it draws as usual until the drag ends. + pub artboard_update_while_dragging: bool, pub display_print_size: bool, pub double_click_to_isolate: bool, pub transform_pattern_tiles: bool, @@ -350,6 +356,8 @@ impl Default for Prefs { use_preview_bounds: false, align_glyph_bounds_point: false, align_glyph_bounds_area: false, + fade_region_outside_artboard: true, + artboard_update_while_dragging: true, display_print_size: false, double_click_to_isolate: true, transform_pattern_tiles: false, diff --git a/crates/tools/src/xform/artboard.rs b/crates/tools/src/xform/artboard.rs index 09933d2a4..c3710c1bd 100644 --- a/crates/tools/src/xform/artboard.rs +++ b/crates/tools/src/xform/artboard.rs @@ -648,6 +648,11 @@ impl Tool for ArtboardTool { } } + /// A drag is moving or resizing artboards (Artboard Options › Update While Dragging reads it). + fn transforming(&self) -> bool { + matches!(self.drag, Some(Drag::Move { began: true, .. } | Drag::Resize { began: true, .. })) + } + fn deactivate(&mut self, _cx: &ToolContext) -> Vec { let busy = matches!(self.drag, Some(Drag::Move { began: true, .. } | Drag::Resize { began: true, .. })); self.end_drag(); diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 9f5438d50..084654066 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -759,7 +759,7 @@ fn paint_paper( // An artboard with a background color draws its paper in that color. With Simulate Colored Paper // on, the canvas multiplies it with the paper color: each sRGB channel is artboard × paper / 255, // rounded. The transparency grid, while it shows, covers it as it covers the others. - painter.rect_filled(rect, 0.0, t.pasteboard); + painter.rect_filled(rect, 0.0, if fades_outside_artboards(app) { faded(t.pasteboard) } else { t.pasteboard }); if !shows_artboards(app) { // View › Hide Artboards: no edges and no pasteboard, the whole canvas paper (#4038). let setup = &doc.setup; @@ -827,6 +827,22 @@ fn reproject_frame(app: &VectorcraftApp, painter: &egui::Painter, xf: &Xf, rect: } } +/// Does the pasteboard draw darker? Artboard Options › Fade Region Outside Artboard darkens it while +/// the Artboard tool is active, and with Update While Dragging off it draws as usual while the tool +/// moves or resizes an artboard. Exports and prints never draw the pasteboard. +fn fades_outside_artboards(app: &VectorcraftApp) -> bool { + let prefs = &app.session.prefs; + app.session.tool_id() == "artboard" + && prefs.fade_region_outside_artboard + && (prefs.artboard_update_while_dragging || !app.session.tool_transforming()) +} + +/// The faded pasteboard: each channel of `c` at 60% of its value. +fn faded(c: Color32) -> Color32 { + let dim = |v: u8| (f32::from(v) * 0.6).round() as u8; + Color32::from_rgba_premultiplied(dim(c.r()), dim(c.g()), dim(c.b()), c.a()) +} + /// Do the artboards show? View › Hide Artboards hides them, except for the Artboard tool, which /// edits them. fn shows_artboards(app: &VectorcraftApp) -> bool { diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index d289a0403..8e7560b11 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -9112,6 +9112,98 @@ fn hidden_artboards_leave_a_paper_canvas_without_edges() { assert_eq!(look(&mut app), (false, true)); } +/// One headless canvas frame → the fill of the pasteboard: the rect covering the canvas that is +/// either the theme's pasteboard color or that color faded. +fn pasteboard_fill(app: &mut VectorcraftApp, ctx: &egui::Context) -> Color32 { + let shapes = shapes(app, ctx); + let usual = Tokens::get(ctx).pasteboard; + let canvas = app.canvas_rect.unwrap(); + shapes + .iter() + .find_map(|s| match s { + Shape::Rect(r) if r.rect.contains_rect(canvas) && (r.fill == usual || r.fill == faded(usual)) => Some(r.fill), + _ => None, + }) + .expect("the pasteboard is drawn") +} + +/// Artboard Options › Fade Region Outside Artboard: while the Artboard tool is active the pasteboard +/// draws darker; with another tool, or with the preference off, it draws as usual. +#[test] +fn the_artboard_tool_fades_the_pasteboard() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 400, "height": 300})).unwrap(); + let ctx = egui::Context::default(); + frame(&mut app, &ctx, vec![]); + let usual = Tokens::get(&ctx).pasteboard; + let dark = faded(usual); + assert!(dark.r() < usual.r() && dark.g() < usual.g() && dark.b() < usual.b(), "{dark:?} is darker than {usual:?}"); + assert!(app.session.prefs.fade_region_outside_artboard, "on by default"); + assert_eq!(pasteboard_fill(&mut app, &ctx), usual, "the Selection tool"); + app.select_tool("artboard"); + assert_eq!(pasteboard_fill(&mut app, &ctx), dark, "the Artboard tool"); + app.select_tool("rectangle"); + assert_eq!(pasteboard_fill(&mut app, &ctx), usual, "the Rectangle tool"); + app.run("prefs.set", json!({"key": "fadeRegionOutsideArtboard", "value": false})).unwrap(); + app.select_tool("artboard"); + assert_eq!(pasteboard_fill(&mut app, &ctx), usual, "the preference off"); +} + +/// Artboard Options › Update While Dragging: on, the pasteboard stays darker while the Artboard tool +/// moves or resizes an artboard; off, it draws as usual during the drag and darker again after it. +#[test] +fn update_while_dragging_keeps_or_drops_the_fade_during_an_artboard_drag() { + for update in [true, false] { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 400, "height": 300})).unwrap(); + app.run("prefs.set", json!({"key": "artboardUpdateWhileDragging", "value": update})).unwrap(); + app.select_tool("artboard"); + let ctx = egui::Context::default(); + frame(&mut app, &ctx, vec![]); + let usual = Tokens::get(&ctx).pasteboard; + let r = app.session.active().unwrap().doc.artboards[0].rect; + let view = app.view_info(); + // A move from the artboard's middle, then a resize from its right edge. + let (cx, cy) = (r.center().x, r.center().y); + for (from, to) in + [(Point::new(cx, cy), Point::new(cx + 40.0, cy + 30.0)), (Point::new(r.x1 + 40.0, cy + 30.0), Point::new(r.x1 + 90.0, cy + 30.0))] + { + dispatch(&mut app, &PointerEvent::new(PointerKind::Down, from.x, from.y), view); + for p in [from + (to - from) * 0.5, to] { + dispatch(&mut app, &PointerEvent::new(PointerKind::Drag, p.x, p.y), view); + } + assert!(app.session.tool_transforming(), "the artboard is being dragged"); + let during = pasteboard_fill(&mut app, &ctx); + assert_eq!(during, if update { faded(usual) } else { usual }, "Update While Dragging {update}"); + dispatch(&mut app, &PointerEvent::new(PointerKind::Up, to.x, to.y), view); + assert!(!app.session.tool_transforming()); + assert_eq!(pasteboard_fill(&mut app, &ctx), faded(usual), "darker again after the drag"); + } + let moved = app.session.active().unwrap().doc.artboards[0].rect; + assert!(moved.x0 > r.x0 && moved.width() > r.width(), "moved, then resized: {moved:?} from {r:?}"); + } +} + +/// The fade is drawn only on the canvas: exports with the Artboard tool active and the fade on are +/// the same as with the Selection tool and the fade off. +#[test] +fn the_pasteboard_fade_leaves_exports_unchanged() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 200, "height": 100})).unwrap(); + app.run("shape.rectangle", json!({"x": -40, "y": 20, "width": 120, "height": 40})).unwrap(); + let exports = |app: &mut VectorcraftApp| -> Vec { + ["svg", "png"].iter().map(|f| app.run("document.export", json!({"format": f})).unwrap()).collect() + }; + app.run("prefs.set", json!({"key": "fadeRegionOutsideArtboard", "value": false})).unwrap(); + let plain = exports(&mut app); + app.run("prefs.set", json!({"key": "fadeRegionOutsideArtboard", "value": true})).unwrap(); + app.select_tool("artboard"); + let ctx = egui::Context::default(); + frame(&mut app, &ctx, vec![]); + assert!(fades_outside_artboards(&app)); + assert_eq!(exports(&mut app), plain); +} + /// Finished worker renders are uploaded whenever they are not older than what is currently /// displayed on screen. During continuous edit streams (drag previews, held arrow nudges, or /// command bursts) where the document revision advances every frame, intermediate worker frames diff --git a/crates/ui-egui/src/dialogs/artboard_options.rs b/crates/ui-egui/src/dialogs/artboard_options.rs index 94ec54317..3eb7cd257 100644 --- a/crates/ui-egui/src/dialogs/artboard_options.rs +++ b/crates/ui-egui/src/dialogs/artboard_options.rs @@ -7,6 +7,9 @@ //! and its video display options, Show Video Safe Areas and Video Ruler Pixel Aspect Ratio //! (`showVideoSafeAreas`, `videoRulerPixelAspectRatio`) //! (https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/set-video-display-options-for-artboards.html). +//! Under Global, Fade Region Outside Artboard and Update While Dragging set the application +//! preferences `fadeRegionOutsideArtboard` and `artboardUpdateWhileDragging` for every document +//! (https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf, Artboard options). //! Sizing choices also go to the other selected artboards; fitting changes only the active //! artboard around stationary artwork. @@ -28,8 +31,8 @@ pub(super) const SPEC: DialogSpec = DialogSpec { body, confirm, // A new artboard's dialog opens without these; its swatch, checkboxes and Video Ruler Pixel - // Aspect Ratio field add them. - inputs: |_| &["color", "showCenterMark", "showCrossHairs", "showVideoSafeAreas", PIXEL_ASPECT], + // Aspect Ratio field add them. The Global checkboxes' fields come from the preferences. + inputs: |_| &["color", "showCenterMark", "showCrossHairs", "showVideoSafeAreas", PIXEL_ASPECT, FADE, UPDATE_WHILE_DRAGGING], ..DialogSpec::FORM }; @@ -47,7 +50,8 @@ fn placed(d: &Dialog) -> Rect { /// reference point where it was, as their sizes do. /// Then Background Color: the Fill list (None, White, Black; Custom for another color) and a swatch /// that picks a color of its own, the Show Center Mark, Show Cross Hairs and Show Video Safe Areas -/// checkboxes, and the Video Ruler Pixel Aspect Ratio field. +/// checkboxes, and the Video Ruler Pixel Aspect Ratio field. Last, under Global, the Fade Region +/// Outside Artboard and Update While Dragging checkboxes. fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { let (unit, origin, refi) = (app.session.general_unit(), app.ruler_origin(), reference_index(ui)); let (was, w0, h0) = (placed(d), d.f64("width", 0.0), d.f64("height", 0.0)); @@ -126,9 +130,52 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { } }); }); + ui.add_space(8.0); + ui.label(egui::RichText::new(tl!("Global")).strong()); + form::check(ui, d, FADE, tl!("Fade Region Outside Artboard")); + form::check(ui, d, UPDATE_WHILE_DRAGGING, tl!("Update While Dragging")); false } +/// The preference and field of Fade Region Outside Artboard: while the Artboard tool is active, +/// the canvas draws the pasteboard darker. +const FADE: &str = "fadeRegionOutsideArtboard"; +/// The preference and field of Update While Dragging: the pasteboard stays darker while the +/// Artboard tool moves or resizes an artboard. +const UPDATE_WHILE_DRAGGING: &str = "artboardUpdateWhileDragging"; + +/// `fields` with the Global checkboxes ([`global_fields`]) added. +fn with_globals(app: &VectorcraftApp, mut fields: Value) -> Value { + for (key, on) in global_fields(app) { + fields[key] = json!(on); + } + fields +} + +/// The Global checkboxes as the preferences have them, for the dialog's fields. +fn global_fields(app: &VectorcraftApp) -> [(&'static str, bool); 2] { + let prefs = &app.session.prefs; + [(FADE, prefs.fade_region_outside_artboard), (UPDATE_WHILE_DRAGGING, prefs.artboard_update_while_dragging)] +} + +/// OK sets the Global checkboxes' preferences that the form changed, in one `prefs.set`, and leaves +/// the preferences alone when none changed. `prefs.set` refuses a value that is not true or false, +/// and an error keeps the dialog open. +fn set_globals(app: &mut VectorcraftApp, d: &Dialog) -> Result<(), String> { + let mut values = serde_json::Map::new(); + for (key, now) in global_fields(app) { + if let Some(v) = d.fields.get(key) + && *v != json!(now) + { + values.insert(key.into(), v.clone()); + } + } + if values.is_empty() { + return Ok(()); + } + app.run("prefs.set", json!({ "values": values })).map(drop) +} + /// The field of the Video Ruler Pixel Aspect Ratio, as `artboard.setProps` takes it. pub(super) const PIXEL_ASPECT: &str = "videoRulerPixelAspectRatio"; @@ -243,7 +290,7 @@ pub fn open(app: &mut VectorcraftApp) -> Result { "showCenterMark": a.show_center_mark, "showCrossHairs": a.show_cross_hairs, "showVideoSafeAreas": a.show_video_safe_areas, PIXEL_ASPECT: a.video_ruler_pixel_aspect_ratio, }); - app.ui.dialog = Some(Dialog::new(KIND, fields)); + app.ui.dialog = Some(Dialog::new(KIND, with_globals(app, fields))); Ok(json!({ "dialog": KIND })) } @@ -254,6 +301,7 @@ pub fn open(app: &mut VectorcraftApp) -> Result { /// row; the top-left corner when the form set none), all in one undo step. Fitting applies only /// to this artboard and never moves its artwork. fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { + set_globals(app, d)?; if d.bool("new") { return confirm_new(app, d); } @@ -264,7 +312,7 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { } } if let Some(o) = p.as_object_mut() { - o.retain(|k, _| !k.starts_with("__")); + o.retain(|k, _| !k.starts_with("__") && k != FADE && k != UPDATE_WHILE_DRAGGING); } let index = p["index"].as_u64().and_then(|i| usize::try_from(i).ok()).unwrap_or(0); let was = app.session.active().and_then(|st| st.doc.artboards.get(index)).map(|a| a.rect); @@ -309,7 +357,7 @@ pub fn open_new(app: &mut VectorcraftApp) -> Result { "new": true, "name": format!("Artboard {}", abs.len() + 1), "x": last.x1 + 20.0, "y": last.y0, "width": last.width(), "height": last.height(), }); - app.ui.dialog = Some(Dialog::new(KIND, fields)); + app.ui.dialog = Some(Dialog::new(KIND, with_globals(app, fields))); Ok(json!({ "dialog": KIND })) } diff --git a/crates/ui-egui/src/dialogs/tests.rs b/crates/ui-egui/src/dialogs/tests.rs index 555b21d1f..0414e1e0e 100644 --- a/crates/ui-egui/src/dialogs/tests.rs +++ b/crates/ui-egui/src/dialogs/tests.rs @@ -1739,6 +1739,44 @@ fn artboard_options_show_center_mark_and_cross_hairs() { } } +/// Artboard Options shows Fade Region Outside Artboard and Update While Dragging under Global, +/// ticked as the preferences have them (on by default). OK sets the preferences, for the dialog of +/// an existing artboard and of a new one alike, and the artboard keeps its other settings. +#[test] +fn artboard_options_global_checkboxes_set_the_preferences() { + let mut app = app(); + let prefs = |app: &VectorcraftApp| (app.session.prefs.fade_region_outside_artboard, app.session.prefs.artboard_update_while_dragging); + assert_eq!(prefs(&app), (true, true)); + crate::dialogs::artboard_options::open(&mut app).unwrap(); + let text = crate::tests_labels::painted_text(&mut app, |app, ui| show(app, ui.ctx())); + assert!(text.contains("Global") && text.contains("Fade Region Outside Artboard") && text.contains("Update While Dragging"), "{text}"); + let d = app.ui.dialog.as_mut().unwrap(); + assert!(d.bool("fadeRegionOutsideArtboard") && d.bool("artboardUpdateWhileDragging")); + d.fields.insert("fadeRegionOutsideArtboard".into(), json!(false)); + let rect = app.session.doc().unwrap().doc.artboards[0].rect; + super::confirm(&mut app).unwrap(); + assert!(app.ui.dialog.is_none()); + assert_eq!(prefs(&app), (false, true)); + assert_eq!(app.session.doc().unwrap().doc.artboards[0].rect, rect); + // Opened again, it shows them as set; a value that is not true or false is refused and changes + // nothing. A new artboard's dialog sets them too. + crate::dialogs::artboard_options::open(&mut app).unwrap(); + let d = app.ui.dialog.as_mut().unwrap(); + assert!(!d.bool("fadeRegionOutsideArtboard")); + d.fields.insert("artboardUpdateWhileDragging".into(), json!("sometimes")); + assert!(super::confirm(&mut app).is_err()); + assert_eq!(prefs(&app), (false, true), "nothing changed"); + cancel(&mut app); + app.run("ui.newArtboard", json!({})).unwrap(); + let d = app.ui.dialog.as_mut().unwrap(); + assert!(!d.bool("fadeRegionOutsideArtboard") && d.bool("artboardUpdateWhileDragging")); + d.fields.insert("fadeRegionOutsideArtboard".into(), json!(true)); + d.fields.insert("artboardUpdateWhileDragging".into(), json!(false)); + super::confirm(&mut app).unwrap(); + assert_eq!(prefs(&app), (true, false)); + assert_eq!(app.session.doc().unwrap().doc.artboards.len(), 2); +} + /// Artboard Options shows Show Video Safe Areas and Video Ruler Pixel Aspect Ratio (one row of /// it), set as the artboard has them whether it opens from the Artboards panel or from a /// double-click with the Artboard tool. OK sets them in one undo step (a typed ratio is read as a diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index df6fe9658..c42766e7c 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -271,6 +271,8 @@ font-technology Variable Променливи Show Cross Hairs Показване на кръстосани линии Show Video Safe Areas Показване на безопасните видеозони Video Ruler Pixel Aspect Ratio: Пропорция на пиксела на видеолинийките: + Fade Region Outside Artboard Затъмняване на областта извън работната област + Update While Dragging Обновяване при плъзгане Hide Video Rulers Скриване на видеолинийките Background Contents Съдържание на фона Background Contents: Съдържание на фона: diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index b60b18d94..07e429218 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -2562,6 +2562,8 @@ digits Malayalam Malajalam Show Cross Hairs Prikaži končanicu Show Video Safe Areas Prikaži sigurne oblasti za video Video Ruler Pixel Aspect Ratio: Omjer stranica piksela video ravnala: + Fade Region Outside Artboard Zatamni područje izvan radne ploče + Update While Dragging Ažuriraj tokom povlačenja Hide Video Rulers Sakrij video ravnala Client-side (.html) Na strani klijenta (.html) Color Reduction: Smanjenje boja: diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 7a179ba8d..354541faf 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -4681,6 +4681,8 @@ digits Malayalam Malaiàlam Video 3840×2160 Vídeo 3840×2160 Video Banner 2560×1440 Bàner de vídeo 2560×1440 Video Ruler Pixel Aspect Ratio: Relació d’aspecte de píxel de les regles de vídeo: + Fade Region Outside Artboard Enfosqueix la regió fora del tauler + Update While Dragging Actualitza mentre s’arrossega Video Thumbnail 1280×720 Miniatura de vídeo 1280×720 View Visualitza View Artboard Only Visualitza només el tauler diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index d6ff710d5..8ebf53aac 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2592,6 +2592,8 @@ digits Malayalam Malajálamské Show Cross Hairs Zobrazit nitkový kříž Show Video Safe Areas Zobrazit bezpečné oblasti videa Video Ruler Pixel Aspect Ratio: Poměr stran pixelu video pravítek: + Fade Region Outside Artboard Ztmavit oblast mimo pracovní plochu + Update While Dragging Aktualizovat při tažení Hide Video Rulers Skrýt video pravítka Client-side (.html) Na straně klienta (.html) Color Reduction: Redukce barev: diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 76f7d6f40..423388739 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -2380,6 +2380,8 @@ digits Malayalam Malayalam Show Cross Hairs Vis trådkors Show Video Safe Areas Vis sikre videoområder Video Ruler Pixel Aspect Ratio: Pixelformat for videolinealer: + Fade Region Outside Artboard Ton området uden for kunstpladen ned + Update While Dragging Opdater under trækning Hide Video Rulers Skjul videolinealer Client-side (.html) Klientbaseret (.html) Color Reduction: Farvereduktion: diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index e644f7e19..99d4def70 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -259,6 +259,8 @@ font-technology Variable Variabel Show Cross Hairs Fadenkreuz einblenden Show Video Safe Areas Videosichere Bereiche einblenden Video Ruler Pixel Aspect Ratio: Pixel-Seitenverhältnis der Videolineale: + Fade Region Outside Artboard Bereich außerhalb der Zeichenfläche abblenden + Update While Dragging Beim Ziehen aktualisieren Hide Video Rulers Videolineale ausblenden Background Contents Hintergrundinhalt Background Contents: Hintergrundinhalt: diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index c2f0bd8e1..21deaf13f 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -262,6 +262,8 @@ font-technology Variable Μεταβλητές Show Cross Hairs Εμφάνιση σταυρονήματος Show Video Safe Areas Εμφάνιση ασφαλών περιοχών βίντεο Video Ruler Pixel Aspect Ratio: Αναλογία διαστάσεων pixel χαράκων βίντεο: + Fade Region Outside Artboard Σκίαση περιοχής εκτός πίνακα σχεδίασης + Update While Dragging Ενημέρωση κατά τη μεταφορά Hide Video Rulers Απόκρυψη χαράκων βίντεο Background Contents Περιεχόμενα φόντου Background Contents: Περιεχόμενο φόντου: diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index f4d1c7f9f..1a6d4677a 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -284,6 +284,8 @@ font-technology Variable Variables Show Cross Hairs Mostrar líneas en cruz Show Video Safe Areas Mostrar áreas seguras de video Video Ruler Pixel Aspect Ratio: Proporción de píxel de las reglas de video: + Fade Region Outside Artboard Atenuar región fuera de la mesa de trabajo + Update While Dragging Actualizar al arrastrar Hide Video Rulers Ocultar reglas de video Background Contents Contenido de fondo Background Contents: Contenido de fondo: diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 89e1e3fc7..0954e96eb 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -2536,6 +2536,8 @@ digits Malayalam Malajalam Show Cross Hairs Näytä hiusristikko Show Video Safe Areas Näytä videon turva-alueet Video Ruler Pixel Aspect Ratio: Videoviivainten kuvapisteen kuvasuhde: + Fade Region Outside Artboard Himmennä piirtoalustan ulkopuolinen alue + Update While Dragging Päivitä vedettäessä Hide Video Rulers Piilota videoviivaimet Client-side (.html) Asiakaspuoli (.html) Color Reduction: Värien vähennys: diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index e3008549b..49353e65d 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -285,6 +285,8 @@ font-technology Variable Variables Show Cross Hairs Afficher le réticule Show Video Safe Areas Afficher les zones de sécurité vidéo Video Ruler Pixel Aspect Ratio: Format des pixels des règles vidéo : + Fade Region Outside Artboard Assombrir la zone hors du plan de travail + Update While Dragging Mettre à jour pendant le déplacement Hide Video Rulers Masquer les règles vidéo Background Contents Contenu de l’arrière-plan Background Contents: Contenu de l’arrière-plan : diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 85013a818..5b388b2b1 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -5096,6 +5096,8 @@ Video 3840×2160 वीडियो 3840 × 2160 Video Banner 2560×1440 वीडियो बैनर 2560 × 1440 Video Ruler Pixel Aspect Ratio: वीडियो शासक पिक्सेल दृष्टि अनुपात: + Fade Region Outside Artboard आर्टबोर्ड के बाहर का क्षेत्र धुंधला करें + Update While Dragging खींचते समय अपडेट करें Video Thumbnail 1280×720 वीडियो थंबनेल 1280 × 720 View व्यू View {n} {n} diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index d4e6c9389..c1db67e2a 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -380,6 +380,8 @@ font-technology Variable Változó Show Cross Hairs Szálkereszt megjelenítése Show Video Safe Areas Biztonságos videoterületek megjelenítése Video Ruler Pixel Aspect Ratio: Videovonalzók képpont-méretaránya: + Fade Region Outside Artboard A rajztáblán kívüli terület elhalványítása + Update While Dragging Frissítés húzás közben Hide Video Rulers Videovonalzók elrejtése Background Contents Háttértartalom Background Contents: Háttértartalom: diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 7bc7f54e3..9465638a6 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -2380,6 +2380,8 @@ digits Malayalam Malayalam Show Cross Hairs Tampilkan garis silang Show Video Safe Areas Tampilkan area aman video Video Ruler Pixel Aspect Ratio: Rasio aspek piksel penggaris video: + Fade Region Outside Artboard Redupkan wilayah di luar artboard + Update While Dragging Perbarui saat menyeret Hide Video Rulers Sembunyikan penggaris video Client-side (.html) Sisi klien (.html) Color Reduction: Reduksi warna: diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 7436de5da..112e2d340 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -284,6 +284,8 @@ font-technology Variable Variabili Show Cross Hairs Mostra mirino Show Video Safe Areas Mostra aree sicure video Video Ruler Pixel Aspect Ratio: Proporzioni pixel dei righelli video: + Fade Region Outside Artboard Attenua area esterna alla tavola da disegno + Update While Dragging Aggiorna durante il trascinamento Hide Video Rulers Nascondi righelli video Background Contents Contenuto sfondo Background Contents: Contenuto sfondo: diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index f594cf38a..9acface09 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1029,6 +1029,8 @@ digits Malayalam マラヤーラム数字 Show Cross Hairs 十字線を表示 Show Video Safe Areas ビデオセーフエリアを表示 Video Ruler Pixel Aspect Ratio: ビデオ定規のピクセル縦横比: + Fade Region Outside Artboard アートボードの外側を暗く表示 + Update While Dragging ドラッグ中に更新 Hide Video Rulers ビデオ定規を隠す Background Contents 背景のカラー Background Contents: 背景のカラー: diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 2238cb65f..36a6ea1ba 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -2282,6 +2282,8 @@ digits Malayalam მალაიალამური Show Cross Hairs ჯვარედინი ხაზების ჩვენება Show Video Safe Areas ვიდეოს უსაფრთხო არეების ჩვენება Video Ruler Pixel Aspect Ratio: ვიდეოს სახაზავების პიქსელის თანაფარდობა: + Fade Region Outside Artboard სამუშაო დაფის გარეთ არის დაბნელება + Update While Dragging განახლება გადათრევისას Hide Video Rulers ვიდეოს სახაზავების დამალვა Client-side (.html) კლიენტის მხარე (.html) Color Reduction: ფერების შემცირება: diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 11e5df3e3..bd3859a2e 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -1019,6 +1019,8 @@ digits Malayalam 말라얄람어 Show Cross Hairs 십자선 보기 Show Video Safe Areas 비디오 안전 영역 보기 Video Ruler Pixel Aspect Ratio: 비디오 눈금자 픽셀 종횡비: + Fade Region Outside Artboard 아트보드 외부 영역 어둡게 표시 + Update While Dragging 드래그하는 동안 업데이트 Hide Video Rulers 비디오 눈금자 숨기기 Background Contents 배경 내용 Background Contents: 배경 내용: diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 1af60b3c0..09368f0f5 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -2394,6 +2394,8 @@ digits Malayalam Malayalam Show Cross Hairs Dradenkruis tonen Show Video Safe Areas Veilige videogebieden tonen Video Ruler Pixel Aspect Ratio: Pixelverhouding van videolinialen: + Fade Region Outside Artboard Gebied buiten tekenbord dimmen + Update While Dragging Bijwerken tijdens slepen Hide Video Rulers Videolinialen verbergen Client-side (.html) Client-side (.html) Color Reduction: Kleurreductie: diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 9a5f75eea..31ec50b67 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2506,6 +2506,8 @@ digits Malayalam Malajalam Show Cross Hairs Pokaż krzyż nitkowy Show Video Safe Areas Pokaż bezpieczne obszary wideo Video Ruler Pixel Aspect Ratio: Proporcje piksela linijek wideo: + Fade Region Outside Artboard Przyciemnij obszar poza obszarem kompozycji + Update While Dragging Aktualizuj podczas przeciągania Hide Video Rulers Ukryj linijki wideo Client-side (.html) Po stronie klienta (.html) Color Reduction: Redukcja kolorów: diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index f929a8a6d..bae1d67dc 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -270,6 +270,8 @@ Show Cross Hairs Mostrar Linhas Cruzadas Show Video Safe Areas Mostrar Áreas Seguras de Vídeo Video Ruler Pixel Aspect Ratio: Proporção de Pixel das Réguas de Vídeo: + Fade Region Outside Artboard Escurecer Região Fora da Prancheta + Update While Dragging Atualizar ao Arrastar Hide Video Rulers Ocultar Réguas de Vídeo Background Contents Conteúdo do Plano de Fundo Background Contents: Conteúdo do Plano de Fundo: diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 1153cc107..1f5c3249a 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -282,6 +282,8 @@ font-technology Variable Вариативные Show Cross Hairs Показать перекрестие Show Video Safe Areas Показать безопасные зоны видео Video Ruler Pixel Aspect Ratio: Соотношение сторон пикселя видео-линеек: + Fade Region Outside Artboard Затемнять область вне монтажной области + Update While Dragging Обновлять при перетаскивании Hide Video Rulers Скрыть видео-линейки Background Contents Содержимое фона Background Contents: Содержимое фона: diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 39abeb7be..717b5a947 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -273,6 +273,8 @@ font-technology Variable Variabla Show Cross Hairs Visa hårkors Show Video Safe Areas Visa videosäkra områden Video Ruler Pixel Aspect Ratio: Pixelproportion för videolinjaler: + Fade Region Outside Artboard Tona ned området utanför ritytan + Update While Dragging Uppdatera vid dragning Hide Video Rulers Dölj videolinjaler Background Contents Bakgrundsinnehåll Background Contents: Bakgrundsinnehåll: diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index dd0dfcad7..6bb74f042 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -269,6 +269,8 @@ font-technology Variable Değişken Show Cross Hairs Artı Çizgilerini Göster Show Video Safe Areas Video Güvenli Alanlarını Göster Video Ruler Pixel Aspect Ratio: Video Cetveli Piksel En Boy Oranı: + Fade Region Outside Artboard Çalışma Yüzeyi Dışındaki Bölgeyi Karart + Update While Dragging Sürüklerken Güncelle Hide Video Rulers Video Cetvellerini Gizle Background Contents Arka Plan İçeriği Background Contents: Arkaplan İçeriği: diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 1c974aa23..337e92826 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -278,6 +278,8 @@ font-technology Variable Варіативні Show Cross Hairs Показати перехрестя Show Video Safe Areas Показати безпечні зони відео Video Ruler Pixel Aspect Ratio: Співвідношення сторін пікселя відео-лінійок: + Fade Region Outside Artboard Затемнювати ділянку поза монтажною областю + Update While Dragging Оновлювати під час перетягування Hide Video Rulers Приховати відео-лінійки Background Contents Вміст фону Background Contents: Вміст фону: diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index fa34763b4..771e1a930 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -307,6 +307,8 @@ font-technology Variable Biến thiên Show Cross Hairs Hiện đường chữ thập Show Video Safe Areas Hiện vùng an toàn video Video Ruler Pixel Aspect Ratio: Tỷ lệ khung điểm ảnh của thước video: + Fade Region Outside Artboard Làm tối vùng bên ngoài bảng vẽ + Update While Dragging Cập nhật khi kéo Hide Video Rulers Ẩn thước video Background Contents Nội dung nền Background Contents: Nội dung nền: diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 55474a0ac..65dae513b 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -230,6 +230,8 @@ Show Cross Hairs 显示十字线 Show Video Safe Areas 显示视频安全区域 Video Ruler Pixel Aspect Ratio: 视频标尺像素长宽比: + Fade Region Outside Artboard 调暗画板外的区域 + Update While Dragging 拖动时更新 Hide Video Rulers 隐藏视频标尺 Background Contents 背景内容 Background Contents: 背景内容: diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 3351956b9..852e1e4ce 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -243,6 +243,8 @@ font-technology Variable 可變 Show Cross Hairs 顯示十字線 Show Video Safe Areas 顯示視訊安全區域 Video Ruler Pixel Aspect Ratio: 視訊尺標像素長寬比: + Fade Region Outside Artboard 調暗工作區域外的區域 + Update While Dragging 拖曳時更新 Hide Video Rulers 隱藏視訊尺標 Background Contents 背景內容 Background Contents: 背景內容: diff --git a/docs/mcp.md b/docs/mcp.md index f16fb79e0..c5aeaa907 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4202,6 +4202,15 @@ is the width of one video pixel over its height, which `view.videoRulers` counts the native format keep both (written only when on or not 1), and `inspect_document` adds `showVideoSafeAreas: true` and `videoRulerPixelAspectRatio` to an artboard that has them. +Artboard Options' Global checkboxes are application preferences, not artboard properties: `fadeRegionOutsideArtboard` +(Fade Region Outside Artboard) and `artboardUpdateWhileDragging` (Update While Dragging), both on by default +(`prefs.set {values: {fadeRegionOutsideArtboard: false}}`; also Edit › Preferences › General › Artboard Options). With +the first on, the canvas draws the pasteboard around the artboards darker while the Artboard tool is active: each color +channel at 60% of its value, VectorCraft's own amount. With the second off, the pasteboard draws as usual while the +Artboard tool moves or resizes an artboard and darker again when the drag ends. Exports and print do not change. The +user guide describes both checkboxes under Artboard options +(https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf). + ```json {"name":"run_command","arguments":{"command":"artboard.copy","params":{"index":0}}} {"name":"run_command","arguments":{"command":"edit.paste","params":{}}} diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 17c4e5dfe..507577f2d 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -593,6 +593,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Rulers (#1212):** drag the intersection to preview and set a document zero point, or double-click it to reset to the active artboard's top-left. Transform, Properties and Control X/Y fields measure from it and convert entered positions back to absolute command coordinates. The origin survives every native format version and compression; geometry, guides and patterns stay in place. Public reference: [Use rulers](https://helpx.adobe.com/ca/illustrator/desktop/measure-and-align/grids-and-guides/use-rulers.html). - **Global and artboard rulers:** View › Rulers › Change to Global / Artboard Rulers (Cmd+Alt+R, `view.globalRulers`). A new document has artboard rulers: the rulers and the Transform, Properties, Control and Info X/Y count from the active artboard's top-left corner, or the zero point dragged out for that artboard, which moves with it; with global rulers one document zero point serves every artboard. Guide readouts and Shift ruler-tick snapping follow the same zero point. The mode is saved with the document; files from before it open with global rulers, as they measured. Regression tests: `engine/tests/it/ruler_origin.rs`, `ui-egui/src/tests/ruler_origin.rs`, `tools/src/rulerguide.rs`. Public reference: [About rulers](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/about-rulers.html). - **Video display options (#2849):** Artboard Options' Show Video Safe Areas and Video Ruler Pixel Aspect Ratio, and View › Rulers › Show Video Rulers ([Set video display options for artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/set-video-display-options-for-artboards.html)). Each artboard has its own (`artboard.setProps`/`artboard.new` `showVideoSafeAreas`, `videoRulerPixelAspectRatio` 0.1–10, default 1), set in one undo step and written to the native format only when on or not 1, so other documents save byte for byte as before. The help page names the safe areas without numbers; our choice is [EBU R 95](https://tech.ebu.ch/docs/r/r095.pdf)'s (note 5): the canvas outlines the action-safe area 3.5% and the title-safe (graphics-safe) area 5% in from each edge, 1 point wide and turned with the view; exports and print leave them out. Show Video Rulers (`view.videoRulers`) shows the rulers counting the active artboard in pixels from its top-left corner, whatever the ruler origin: a pixel is as many points wide as the ratio and one point tall, so a 720 × 480 pt artboard at 0.9 reads 800 × 480. Hide Video Rulers returns them to the General unit; Shift ruler-tick snapping of new guides keeps the General unit. Regression tests: `tests/panelcmds.rs` (`artboard_video_display_options_*`, `artboard_display_marks_stay_out_of_exports`), `tests/docsetup.rs` (`an_artboard_shows_its_video_safe_areas`), `canvas.rs` (`video_rulers_count_the_artboard_in_pixels_of_its_aspect_ratio`), `tests/ruler_origin.rs` (`show_video_rulers_counts_the_active_artboard_in_pixels`), `dialogs/tests.rs` (`artboard_options_show_video_display_options`). +- **Fade Region Outside Artboard:** Artboard Options' Global checkboxes Fade Region Outside Artboard and Update While Dragging ([user guide, Artboard options](https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf)) are application preferences (`fadeRegionOutsideArtboard`, `artboardUpdateWhileDragging`), both on by default. While the Artboard tool is active the canvas draws the pasteboard with each color channel at 60% of its value (VectorCraft's own amount); with Update While Dragging off it draws as usual during an artboard move or resize. Exports and print do not change. Regression tests: `canvas::tests` (`the_artboard_tool_fades_the_pasteboard`, `update_while_dragging_keeps_or_drops_the_fade_during_an_artboard_drag`, `the_pasteboard_fade_leaves_exports_unchanged`), `dialogs/tests.rs` (`artboard_options_global_checkboxes_set_the_preferences`). - **Saved views (G7):** View › New View… and Edit Views… ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html): up to 25 views per document, saved with it, renamed or deleted in Edit Views, chosen from the bottom of the View menu). Each view keeps the zoom, centre and rotation; the View menu lists all 25 (`view.goto1`–`view.goto25`), and Edit Views… lists them to rename or delete, applied together as one undo step. A file's views are checked when it opens (at most 25, unique names up to 255 characters, a finite centre and a positive zoom). Tests: `saved_views_round_trip_and_from_a_file_are_tidied` (`crates/format/tests/prop_format.rs`), `crates/engine/src/cmd/views.rs`, `crates/ui-egui/src/dialogs/edit_views.rs` and `every_saved_view_is_listed_at_the_bottom_of_the_view_menu` (`crates/ui-egui/src/menus.rs`). - **Window › Arrange › Tile (G7):** Tile (`window.arrange.tile`) shows every open document at once, edge to edge in the document area ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)), one pane each in a grid of as many columns as the square root of the number of documents, rounded up, each fitted to its pane. The current document's pane is the canvas; the other panes draw their documents with their artboards, guides and selected objects, and a press in one makes its document the current one. Consolidate All Windows (`window.arrange.consolidateAllWindows`) shows one document under its tab again. The grid and when the two items are enabled (Tile with two documents or more, Consolidate All Windows while they are tiled) are VectorCraft's own. Tests: `crates/ui-egui/src/canvas/panes/tests.rs`. - **Window › Arrange › Tile (G7):** Tile (`window.arrange.tile`) shows every open document at once, edge to edge in the document area ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)), one pane each in a grid of as many columns as the square root of the number of documents, rounded up, each fitted to its pane. The current document's pane is the canvas; the other panes draw their documents with their artboards, guides and selected objects, and a press in one makes its document the current one. Consolidate All Windows (`window.arrange.consolidateAllWindows`) shows one document under its tab again. The grid and when the two items are enabled (Tile with two documents or more, Consolidate All Windows while they are tiled) are VectorCraft's own. Tests: `crates/ui-egui/src/canvas/panes/tests.rs`. From e620da7ed6e2761a1fe448308b99a407d7b13622 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 04:56:44 -0800 Subject: [PATCH 2/5] Artboard Options: the fade covers art outside the artboards and OK sets the Global checkboxes after the artboard With Fade Region Outside Artboard on, the canvas now draws the fade over the art as well as the pasteboard: one mesh of black at 40% opacity covers the canvas outside the artboards, so the pasteboard and any art on it show each channel at 60% of its value, and art inside an artboard is unchanged. It replaces the darker pasteboard fill. Artboard Options sets the Global checkboxes' preferences only after artboard.setProps or artboard.new succeeds. When the artboard command refuses a value, the preferences stay as they were and the dialog stays open. A Global value that is not true or false is refused before anything changes. Docs: docs/mcp.md, docs/target-app-parity.md. --- crates/engine/src/cmd/prefscmds.rs | 2 +- crates/engine/src/prefs.rs | 6 +- crates/ui-egui/src/canvas.rs | 61 ++++++++-- crates/ui-egui/src/canvas/tests.rs | 104 +++++++++++++----- .../ui-egui/src/dialogs/artboard_options.rs | 42 ++++--- crates/ui-egui/src/dialogs/tests.rs | 22 ++++ docs/mcp.md | 6 +- docs/target-app-parity.md | 2 +- 8 files changed, 188 insertions(+), 57 deletions(-) diff --git a/crates/engine/src/cmd/prefscmds.rs b/crates/engine/src/cmd/prefscmds.rs index a1a51ec8e..f0829932b 100644 --- a/crates/engine/src/cmd/prefscmds.rs +++ b/crates/engine/src/cmd/prefscmds.rs @@ -423,7 +423,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[ // The Align panel menu's Align to Glyph Bounds: Align and Distribute measure type by its glyphs' ink. p!("alignGlyphBoundsPoint", "Type", "Align to Glyph Bounds", "Point Text", bool), p!("alignGlyphBoundsArea", "Type", "Align to Glyph Bounds", "Area Text", bool), - // Artboard Options › Global: the canvas darkens the pasteboard while the Artboard tool is active. + // Artboard Options › Global: the canvas darkens the area outside the artboards while the Artboard tool is active. p!("fadeRegionOutsideArtboard", "General", "Artboard Options", "Fade Region Outside Artboard", bool), p!("artboardUpdateWhileDragging", "General", "Artboard Options", "Update While Dragging", bool), ]; diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index 7fe288754..7730075ea 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -42,10 +42,10 @@ pub struct Prefs { /// its frame. pub align_glyph_bounds_area: bool, /// Artboard Options › Fade Region Outside Artboard: while the Artboard tool is active, the - /// canvas draws the pasteboard outside the artboards darker. + /// canvas draws everything outside the artboards darker, the pasteboard and the art on it. pub fade_region_outside_artboard: bool, - /// Artboard Options › Update While Dragging: the pasteboard stays darker while the Artboard - /// tool moves or resizes an artboard; off, it draws as usual until the drag ends. + /// Artboard Options › Update While Dragging: the area outside the artboards stays darker while + /// the Artboard tool moves or resizes an artboard; off, it draws as usual until the drag ends. pub artboard_update_while_dragging: bool, pub display_print_size: bool, pub double_click_to_isolate: bool, diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 084654066..44d8674d7 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -326,6 +326,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { } fill_uncovered(app, ui.ctx(), &painter, &key, &doc, rect, &xf); reproject_frame(app, &painter, &xf, rect, (key.doc, v.rotation)); + if fades_outside_artboards(app) { + fade_outside_artboards(&painter, &xf, &doc); + } if let Some(look) = grid_look.filter(|_| !grids_in_back) { grid(&painter, &xf, doc.grid.spacing, doc.grid.subdivisions, look); } @@ -759,7 +762,7 @@ fn paint_paper( // An artboard with a background color draws its paper in that color. With Simulate Colored Paper // on, the canvas multiplies it with the paper color: each sRGB channel is artboard × paper / 255, // rounded. The transparency grid, while it shows, covers it as it covers the others. - painter.rect_filled(rect, 0.0, if fades_outside_artboards(app) { faded(t.pasteboard) } else { t.pasteboard }); + painter.rect_filled(rect, 0.0, t.pasteboard); if !shows_artboards(app) { // View › Hide Artboards: no edges and no pasteboard, the whole canvas paper (#4038). let setup = &doc.setup; @@ -827,9 +830,9 @@ fn reproject_frame(app: &VectorcraftApp, painter: &egui::Painter, xf: &Xf, rect: } } -/// Does the pasteboard draw darker? Artboard Options › Fade Region Outside Artboard darkens it while -/// the Artboard tool is active, and with Update While Dragging off it draws as usual while the tool -/// moves or resizes an artboard. Exports and prints never draw the pasteboard. +/// Does the area outside the artboards draw darker? Artboard Options › Fade Region Outside Artboard +/// darkens it while the Artboard tool is active, and with Update While Dragging off it draws as +/// usual while the tool moves or resizes an artboard. Exports and prints never draw the fade. fn fades_outside_artboards(app: &VectorcraftApp) -> bool { let prefs = &app.session.prefs; app.session.tool_id() == "artboard" @@ -837,10 +840,52 @@ fn fades_outside_artboards(app: &VectorcraftApp) -> bool { && (prefs.artboard_update_while_dragging || !app.session.tool_transforming()) } -/// The faded pasteboard: each channel of `c` at 60% of its value. -fn faded(c: Color32) -> Color32 { - let dim = |v: u8| (f32::from(v) * 0.6).round() as u8; - Color32::from_rgba_premultiplied(dim(c.r()), dim(c.g()), dim(c.b()), c.a()) +/// The fade's color: black at 40% opacity, which shows each channel under it at 60% of its value. +const FADE_OUTSIDE: Color32 = Color32::from_black_alpha(102); + +/// Fade Region Outside Artboard: over the pasteboard and the art on it, one mesh in [`FADE_OUTSIDE`] +/// that covers the canvas outside the artboards of `doc`, drawn through the view. The mesh is not +/// anti-aliased, so its rectangles meet without seams. +fn fade_outside_artboards(painter: &egui::Painter, xf: &Xf, doc: &vectorcraft_doc::Document) { + let boards: Vec = doc.artboards.iter().map(|a| a.rect).collect(); + let mut mesh = egui::Mesh::default(); + for (n, r) in (0u32..).zip(outside_rects(view_bounds(xf), &boards)) { + for p in xf.quad(r) { + mesh.colored_vertex(p, FADE_OUTSIDE); + } + let a = n * 4; + mesh.add_triangle(a, a + 1, a + 2); + mesh.add_triangle(a, a + 2, a + 3); + } + painter.add(Shape::mesh(mesh)); +} + +/// The parts of `bounds` that no rectangle of `boards` covers, as rectangles that do not overlap: +/// for each band between consecutive top and bottom edges, the spans between the boards that +/// cross it. +fn outside_rects(bounds: Rect, boards: &[Rect]) -> Vec { + let mut ys: Vec = boards.iter().flat_map(|b| [b.y0, b.y1]).filter(|y| *y > bounds.y0 && *y < bounds.y1).collect(); + ys.extend([bounds.y0, bounds.y1]); + ys.sort_by(f64::total_cmp); + ys.dedup(); + let mut out = vec![]; + for band in ys.windows(2) { + let &[y0, y1] = band else { continue }; + let mut spans: Vec<(f64, f64)> = + boards.iter().filter(|b| b.y0 <= y0 && b.y1 >= y1).map(|b| (b.x0.max(bounds.x0), b.x1.min(bounds.x1))).filter(|(a, b)| a < b).collect(); + spans.sort_by(|a, b| a.0.total_cmp(&b.0)); + let mut x = bounds.x0; + for (a, b) in spans { + if a > x { + out.push(Rect::new(x, y0, a, y1)); + } + x = x.max(b); + } + if x < bounds.x1 { + out.push(Rect::new(x, y0, bounds.x1, y1)); + } + } + out } /// Do the artboards show? View › Hide Artboards hides them, except for the Artboard tool, which diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index 8e7560b11..161a247c6 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -9112,41 +9112,95 @@ fn hidden_artboards_leave_a_paper_canvas_without_edges() { assert_eq!(look(&mut app), (false, true)); } -/// One headless canvas frame → the fill of the pasteboard: the rect covering the canvas that is -/// either the theme's pasteboard color or that color faded. -fn pasteboard_fill(app: &mut VectorcraftApp, ctx: &egui::Context) -> Color32 { - let shapes = shapes(app, ctx); - let usual = Tokens::get(ctx).pasteboard; - let canvas = app.canvas_rect.unwrap(); - shapes - .iter() - .find_map(|s| match s { - Shape::Rect(r) if r.rect.contains_rect(canvas) && (r.fill == usual || r.fill == faded(usual)) => Some(r.fill), - _ => None, - }) - .expect("the pasteboard is drawn") +/// One headless canvas frame → whether the fade outside the artboards is drawn. +fn fades(app: &mut VectorcraftApp, ctx: &egui::Context) -> bool { + fade_mesh(&shapes(app, ctx)).is_some() } -/// Artboard Options › Fade Region Outside Artboard: while the Artboard tool is active the pasteboard -/// draws darker; with another tool, or with the preference off, it draws as usual. +/// The fade outside the artboards among `shapes`, with its position: the mesh in [`FADE_OUTSIDE`]. +fn fade_mesh(shapes: &[Shape]) -> Option<(usize, &egui::Mesh)> { + shapes.iter().enumerate().find_map(|(i, s)| match s { + Shape::Mesh(m) if !m.vertices.is_empty() && m.vertices.iter().all(|v| v.color == FADE_OUTSIDE) => Some((i, &**m)), + _ => None, + }) +} + +/// Does `mesh` cover the screen point `p`? +fn mesh_covers(mesh: &egui::Mesh, p: Pos2) -> bool { + let cross = |a: Pos2, b: Pos2| (b - a).x * (p - a).y - (b - a).y * (p - a).x; + mesh.indices.chunks(3).any(|t| { + let [a, b, c] = [t[0], t[1], t[2]].map(|i| mesh.vertices[i as usize].pos); + let (d0, d1, d2) = (cross(a, b), cross(b, c), cross(c, a)); + (d0 >= 0.0 && d1 >= 0.0 && d2 >= 0.0) || (d0 <= 0.0 && d1 <= 0.0 && d2 <= 0.0) + }) +} + +/// Artboard Options › Fade Region Outside Artboard: while the Artboard tool is active the area +/// outside the artboards draws darker; with another tool, or with the preference off, it draws as +/// usual. #[test] fn the_artboard_tool_fades_the_pasteboard() { let mut app = VectorcraftApp::new(Session::new(), Default::default()); app.run("file.new", json!({"width": 400, "height": 300})).unwrap(); let ctx = egui::Context::default(); frame(&mut app, &ctx, vec![]); - let usual = Tokens::get(&ctx).pasteboard; - let dark = faded(usual); - assert!(dark.r() < usual.r() && dark.g() < usual.g() && dark.b() < usual.b(), "{dark:?} is darker than {usual:?}"); + assert_eq!(FADE_OUTSIDE.a(), 102, "black at 40%: each channel under it shows at 60%"); assert!(app.session.prefs.fade_region_outside_artboard, "on by default"); - assert_eq!(pasteboard_fill(&mut app, &ctx), usual, "the Selection tool"); + assert!(!fades(&mut app, &ctx), "the Selection tool"); app.select_tool("artboard"); - assert_eq!(pasteboard_fill(&mut app, &ctx), dark, "the Artboard tool"); + assert!(fades(&mut app, &ctx), "the Artboard tool"); app.select_tool("rectangle"); - assert_eq!(pasteboard_fill(&mut app, &ctx), usual, "the Rectangle tool"); + assert!(!fades(&mut app, &ctx), "the Rectangle tool"); app.run("prefs.set", json!({"key": "fadeRegionOutsideArtboard", "value": false})).unwrap(); app.select_tool("artboard"); - assert_eq!(pasteboard_fill(&mut app, &ctx), usual, "the preference off"); + assert!(!fades(&mut app, &ctx), "the preference off"); +} + +/// The fade covers the art that lies on the pasteboard as well as the pasteboard: it is drawn over +/// the art, over a point of an object outside the artboard and not over a point of an object on it, +/// in an upright view and in a rotated one. +#[test] +fn the_fade_darkens_art_on_the_pasteboard_and_not_on_the_artboard() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + app.session.execute("shape.rectangle", &json!({"x": -150, "y": 100, "width": 100, "height": 100})).unwrap(); + app.session.execute("shape.rectangle", &json!({"x": 150, "y": 100, "width": 100, "height": 100})).unwrap(); + app.canvas.worker_started = true; // render on this thread + let ctx = egui::Context::default(); + shapes(&mut app, &ctx); // the first frame fits the view to the artboard + app.view_mut().unwrap().zoom = 0.5; + app.select_tool("artboard"); + for rotation in [0.0, 30.0] { + app.view_mut().unwrap().rotation = rotation; + let s = shapes(&mut app, &ctx); + let art = app.canvas.texture.as_ref().unwrap().id(); + let image = s.iter().position(|s| matches!(s, Shape::Mesh(m) if m.texture_id == art)).expect("the art"); + let (at, mesh) = fade_mesh(&s).expect("the fade"); + assert!(at > image, "over the art, rotation {rotation}"); + let xf = Xf::new(app.canvas_rect.unwrap(), app.view().unwrap()); + assert!(mesh_covers(mesh, xf.to_screen(Point::new(-100.0, 150.0))), "the object outside, rotation {rotation}"); + assert!(!mesh_covers(mesh, xf.to_screen(Point::new(200.0, 150.0))), "the object on the artboard, rotation {rotation}"); + assert!(mesh_covers(mesh, xf.to_screen(Point::new(200.0, -40.0))), "the pasteboard, rotation {rotation}"); + } +} + +/// The parts outside the boards: none where boards cover the bounds, the bounds without boards, and +/// otherwise rectangles that do not overlap a board or each other and add up to the uncovered area. +#[test] +fn outside_rects_cover_the_bounds_minus_the_boards() { + let bounds = Rect::new(0.0, 0.0, 100.0, 100.0); + assert!(outside_rects(bounds, &[Rect::new(-10.0, -10.0, 110.0, 110.0)]).is_empty()); + assert_eq!(outside_rects(bounds, &[]), vec![bounds]); + let boards = [Rect::new(10.0, 10.0, 40.0, 40.0), Rect::new(30.0, 30.0, 60.0, 80.0), Rect::new(90.0, -20.0, 130.0, 50.0)]; + let parts = outside_rects(bounds, &boards); + let area = |r: &Rect| r.width() * r.height(); + let overlap = |a: &Rect, b: &Rect| (a.x1.min(b.x1) - a.x0.max(b.x0)).max(0.0) * (a.y1.min(b.y1) - a.y0.max(b.y0)).max(0.0); + for (i, p) in parts.iter().enumerate() { + assert!(boards.iter().all(|b| overlap(p, b) == 0.0), "{p:?} overlaps a board"); + assert!(parts[i + 1..].iter().all(|q| overlap(p, q) == 0.0), "{p:?} overlaps another part"); + } + // Inside the bounds the boards cover 900 + 1,500 less their 100 of overlap, and 500 of the third. + assert_eq!(parts.iter().map(area).sum::(), 10_000.0 - (900.0 + 1_500.0 - 100.0 + 500.0)); } /// Artboard Options › Update While Dragging: on, the pasteboard stays darker while the Artboard tool @@ -9160,7 +9214,6 @@ fn update_while_dragging_keeps_or_drops_the_fade_during_an_artboard_drag() { app.select_tool("artboard"); let ctx = egui::Context::default(); frame(&mut app, &ctx, vec![]); - let usual = Tokens::get(&ctx).pasteboard; let r = app.session.active().unwrap().doc.artboards[0].rect; let view = app.view_info(); // A move from the artboard's middle, then a resize from its right edge. @@ -9173,11 +9226,10 @@ fn update_while_dragging_keeps_or_drops_the_fade_during_an_artboard_drag() { dispatch(&mut app, &PointerEvent::new(PointerKind::Drag, p.x, p.y), view); } assert!(app.session.tool_transforming(), "the artboard is being dragged"); - let during = pasteboard_fill(&mut app, &ctx); - assert_eq!(during, if update { faded(usual) } else { usual }, "Update While Dragging {update}"); + assert_eq!(fades(&mut app, &ctx), update, "Update While Dragging {update}"); dispatch(&mut app, &PointerEvent::new(PointerKind::Up, to.x, to.y), view); assert!(!app.session.tool_transforming()); - assert_eq!(pasteboard_fill(&mut app, &ctx), faded(usual), "darker again after the drag"); + assert!(fades(&mut app, &ctx), "darker again after the drag"); } let moved = app.session.active().unwrap().doc.artboards[0].rect; assert!(moved.x0 > r.x0 && moved.width() > r.width(), "moved, then resized: {moved:?} from {r:?}"); diff --git a/crates/ui-egui/src/dialogs/artboard_options.rs b/crates/ui-egui/src/dialogs/artboard_options.rs index 3eb7cd257..0ce0de935 100644 --- a/crates/ui-egui/src/dialogs/artboard_options.rs +++ b/crates/ui-egui/src/dialogs/artboard_options.rs @@ -138,10 +138,10 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { } /// The preference and field of Fade Region Outside Artboard: while the Artboard tool is active, -/// the canvas draws the pasteboard darker. +/// the canvas draws everything outside the artboards darker. const FADE: &str = "fadeRegionOutsideArtboard"; -/// The preference and field of Update While Dragging: the pasteboard stays darker while the -/// Artboard tool moves or resizes an artboard. +/// The preference and field of Update While Dragging: the area outside the artboards stays darker +/// while the Artboard tool moves or resizes an artboard. const UPDATE_WHILE_DRAGGING: &str = "artboardUpdateWhileDragging"; /// `fields` with the Global checkboxes ([`global_fields`]) added. @@ -158,18 +158,25 @@ fn global_fields(app: &VectorcraftApp) -> [(&'static str, bool); 2] { [(FADE, prefs.fade_region_outside_artboard), (UPDATE_WHILE_DRAGGING, prefs.artboard_update_while_dragging)] } -/// OK sets the Global checkboxes' preferences that the form changed, in one `prefs.set`, and leaves -/// the preferences alone when none changed. `prefs.set` refuses a value that is not true or false, -/// and an error keeps the dialog open. -fn set_globals(app: &mut VectorcraftApp, d: &Dialog) -> Result<(), String> { +/// The Global checkboxes' preferences that the form changed. A value that is not true or false is +/// an error, which keeps the dialog open before anything changes. +fn global_changes(app: &VectorcraftApp, d: &Dialog) -> Result, String> { let mut values = serde_json::Map::new(); for (key, now) in global_fields(app) { - if let Some(v) = d.fields.get(key) - && *v != json!(now) - { - values.insert(key.into(), v.clone()); + match d.fields.get(key) { + Some(Value::Bool(on)) if *on != now => { + values.insert(key.into(), json!(on)); + } + Some(Value::Bool(_)) | None => {} + Some(_) => return Err(format!("`{key}` must be true or false")), } } + Ok(values) +} + +/// OK sets the Global checkboxes' preferences that the form changed ([`global_changes`]), in one +/// `prefs.set`, and leaves the preferences alone when none changed. +fn set_globals(app: &mut VectorcraftApp, values: serde_json::Map) -> Result<(), String> { if values.is_empty() { return Ok(()); } @@ -299,11 +306,14 @@ pub fn open(app: &mut VectorcraftApp) -> Result { /// one is among them), each resized about the reference /// point the form's last Width or Height change used (fields `__refx` and `__refy`, its column and /// row; the top-left corner when the form set none), all in one undo step. Fitting applies only -/// to this artboard and never moves its artwork. +/// to this artboard and never moves its artwork. The Global checkboxes' preferences are set only +/// after the artboard command succeeds, so an error leaves them as they were. fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { - set_globals(app, d)?; + let globals = global_changes(app, d)?; if d.bool("new") { - return confirm_new(app, d); + let r = confirm_new(app, d)?; + set_globals(app, globals)?; + return Ok(r); } let mut p = Value::Object(d.fields.clone()); for k in ["x", "y", "width", "height", PIXEL_ASPECT] { @@ -343,7 +353,9 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { "moveArt": !d.bool("__fit") && crate::panels::artboards::move_art(app), }); let p = crate::panels::artboards::set_props_params(common, entries).ok_or("no artboard")?; - run_and_close(app, "artboard.setProps", p) + let r = run_and_close(app, "artboard.setProps", p)?; + set_globals(app, globals)?; + Ok(r) } /// `ui.newArtboard` (Alt-click on the Artboards panel's New Artboard): Artboard Options for a new diff --git a/crates/ui-egui/src/dialogs/tests.rs b/crates/ui-egui/src/dialogs/tests.rs index 0414e1e0e..35bd659fc 100644 --- a/crates/ui-egui/src/dialogs/tests.rs +++ b/crates/ui-egui/src/dialogs/tests.rs @@ -1777,6 +1777,28 @@ fn artboard_options_global_checkboxes_set_the_preferences() { assert_eq!(app.session.doc().unwrap().doc.artboards.len(), 2); } +/// When the artboard command refuses a value, OK leaves the Global checkboxes' preferences as they +/// were, for the dialog of an existing artboard and of a new one alike. +#[test] +fn artboard_options_error_leaves_the_global_preferences_unchanged() { + let mut app = app(); + let prefs = |app: &VectorcraftApp| (app.session.prefs.fade_region_outside_artboard, app.session.prefs.artboard_update_while_dragging); + for new in [false, true] { + if new { + app.run("ui.newArtboard", json!({})).unwrap(); + } else { + crate::dialogs::artboard_options::open(&mut app).unwrap(); + } + let d = app.ui.dialog.as_mut().unwrap(); + d.fields.insert("fadeRegionOutsideArtboard".into(), json!(false)); + d.fields.insert("videoRulerPixelAspectRatio".into(), json!(20)); + assert!(super::confirm(&mut app).is_err(), "new: {new}"); + assert_eq!(prefs(&app), (true, true), "new: {new}"); + assert_eq!(app.session.doc().unwrap().doc.artboards.len(), 1, "new: {new}"); + cancel(&mut app); + } +} + /// Artboard Options shows Show Video Safe Areas and Video Ruler Pixel Aspect Ratio (one row of /// it), set as the artboard has them whether it opens from the Artboards panel or from a /// double-click with the Artboard tool. OK sets them in one undo step (a typed ratio is read as a diff --git a/docs/mcp.md b/docs/mcp.md index c5aeaa907..5b707f59d 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4205,9 +4205,9 @@ the native format keep both (written only when on or not 1), and `inspect_docume Artboard Options' Global checkboxes are application preferences, not artboard properties: `fadeRegionOutsideArtboard` (Fade Region Outside Artboard) and `artboardUpdateWhileDragging` (Update While Dragging), both on by default (`prefs.set {values: {fadeRegionOutsideArtboard: false}}`; also Edit › Preferences › General › Artboard Options). With -the first on, the canvas draws the pasteboard around the artboards darker while the Artboard tool is active: each color -channel at 60% of its value, VectorCraft's own amount. With the second off, the pasteboard draws as usual while the -Artboard tool moves or resizes an artboard and darker again when the drag ends. Exports and print do not change. The +the first on, the canvas draws everything outside the artboards darker while the Artboard tool is active, the pasteboard +and the art on it: each color channel at 60% of its value, VectorCraft's own amount. With the second off, that area +draws as usual while the Artboard tool moves or resizes an artboard and darker again when the drag ends. Exports and print do not change. The user guide describes both checkboxes under Artboard options (https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf). diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 507577f2d..7c4e487cf 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -593,7 +593,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Rulers (#1212):** drag the intersection to preview and set a document zero point, or double-click it to reset to the active artboard's top-left. Transform, Properties and Control X/Y fields measure from it and convert entered positions back to absolute command coordinates. The origin survives every native format version and compression; geometry, guides and patterns stay in place. Public reference: [Use rulers](https://helpx.adobe.com/ca/illustrator/desktop/measure-and-align/grids-and-guides/use-rulers.html). - **Global and artboard rulers:** View › Rulers › Change to Global / Artboard Rulers (Cmd+Alt+R, `view.globalRulers`). A new document has artboard rulers: the rulers and the Transform, Properties, Control and Info X/Y count from the active artboard's top-left corner, or the zero point dragged out for that artboard, which moves with it; with global rulers one document zero point serves every artboard. Guide readouts and Shift ruler-tick snapping follow the same zero point. The mode is saved with the document; files from before it open with global rulers, as they measured. Regression tests: `engine/tests/it/ruler_origin.rs`, `ui-egui/src/tests/ruler_origin.rs`, `tools/src/rulerguide.rs`. Public reference: [About rulers](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/about-rulers.html). - **Video display options (#2849):** Artboard Options' Show Video Safe Areas and Video Ruler Pixel Aspect Ratio, and View › Rulers › Show Video Rulers ([Set video display options for artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/set-video-display-options-for-artboards.html)). Each artboard has its own (`artboard.setProps`/`artboard.new` `showVideoSafeAreas`, `videoRulerPixelAspectRatio` 0.1–10, default 1), set in one undo step and written to the native format only when on or not 1, so other documents save byte for byte as before. The help page names the safe areas without numbers; our choice is [EBU R 95](https://tech.ebu.ch/docs/r/r095.pdf)'s (note 5): the canvas outlines the action-safe area 3.5% and the title-safe (graphics-safe) area 5% in from each edge, 1 point wide and turned with the view; exports and print leave them out. Show Video Rulers (`view.videoRulers`) shows the rulers counting the active artboard in pixels from its top-left corner, whatever the ruler origin: a pixel is as many points wide as the ratio and one point tall, so a 720 × 480 pt artboard at 0.9 reads 800 × 480. Hide Video Rulers returns them to the General unit; Shift ruler-tick snapping of new guides keeps the General unit. Regression tests: `tests/panelcmds.rs` (`artboard_video_display_options_*`, `artboard_display_marks_stay_out_of_exports`), `tests/docsetup.rs` (`an_artboard_shows_its_video_safe_areas`), `canvas.rs` (`video_rulers_count_the_artboard_in_pixels_of_its_aspect_ratio`), `tests/ruler_origin.rs` (`show_video_rulers_counts_the_active_artboard_in_pixels`), `dialogs/tests.rs` (`artboard_options_show_video_display_options`). -- **Fade Region Outside Artboard:** Artboard Options' Global checkboxes Fade Region Outside Artboard and Update While Dragging ([user guide, Artboard options](https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf)) are application preferences (`fadeRegionOutsideArtboard`, `artboardUpdateWhileDragging`), both on by default. While the Artboard tool is active the canvas draws the pasteboard with each color channel at 60% of its value (VectorCraft's own amount); with Update While Dragging off it draws as usual during an artboard move or resize. Exports and print do not change. Regression tests: `canvas::tests` (`the_artboard_tool_fades_the_pasteboard`, `update_while_dragging_keeps_or_drops_the_fade_during_an_artboard_drag`, `the_pasteboard_fade_leaves_exports_unchanged`), `dialogs/tests.rs` (`artboard_options_global_checkboxes_set_the_preferences`). +- **Fade Region Outside Artboard:** Artboard Options' Global checkboxes Fade Region Outside Artboard and Update While Dragging ([user guide, Artboard options](https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf)) are application preferences (`fadeRegionOutsideArtboard`, `artboardUpdateWhileDragging`), both on by default. While the Artboard tool is active the canvas draws everything outside the artboards, the pasteboard and the art on it, with each color channel at 60% of its value (VectorCraft's own amount); with Update While Dragging off it draws as usual during an artboard move or resize. Exports and print do not change. Regression tests: `canvas::tests` (`the_artboard_tool_fades_the_pasteboard`, `the_fade_darkens_art_on_the_pasteboard_and_not_on_the_artboard`, `outside_rects_cover_the_bounds_minus_the_boards`, `update_while_dragging_keeps_or_drops_the_fade_during_an_artboard_drag`, `the_pasteboard_fade_leaves_exports_unchanged`), `dialogs/tests.rs` (`artboard_options_global_checkboxes_set_the_preferences`, `artboard_options_error_leaves_the_global_preferences_unchanged`). - **Saved views (G7):** View › New View… and Edit Views… ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html): up to 25 views per document, saved with it, renamed or deleted in Edit Views, chosen from the bottom of the View menu). Each view keeps the zoom, centre and rotation; the View menu lists all 25 (`view.goto1`–`view.goto25`), and Edit Views… lists them to rename or delete, applied together as one undo step. A file's views are checked when it opens (at most 25, unique names up to 255 characters, a finite centre and a positive zoom). Tests: `saved_views_round_trip_and_from_a_file_are_tidied` (`crates/format/tests/prop_format.rs`), `crates/engine/src/cmd/views.rs`, `crates/ui-egui/src/dialogs/edit_views.rs` and `every_saved_view_is_listed_at_the_bottom_of_the_view_menu` (`crates/ui-egui/src/menus.rs`). - **Window › Arrange › Tile (G7):** Tile (`window.arrange.tile`) shows every open document at once, edge to edge in the document area ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)), one pane each in a grid of as many columns as the square root of the number of documents, rounded up, each fitted to its pane. The current document's pane is the canvas; the other panes draw their documents with their artboards, guides and selected objects, and a press in one makes its document the current one. Consolidate All Windows (`window.arrange.consolidateAllWindows`) shows one document under its tab again. The grid and when the two items are enabled (Tile with two documents or more, Consolidate All Windows while they are tiled) are VectorCraft's own. Tests: `crates/ui-egui/src/canvas/panes/tests.rs`. - **Window › Arrange › Tile (G7):** Tile (`window.arrange.tile`) shows every open document at once, edge to edge in the document area ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)), one pane each in a grid of as many columns as the square root of the number of documents, rounded up, each fitted to its pane. The current document's pane is the canvas; the other panes draw their documents with their artboards, guides and selected objects, and a press in one makes its document the current one. Consolidate All Windows (`window.arrange.consolidateAllWindows`) shows one document under its tab again. The grid and when the two items are enabled (Tile with two documents or more, Consolidate All Windows while they are tiled) are VectorCraft's own. Tests: `crates/ui-egui/src/canvas/panes/tests.rs`. From 92d350552913fb62eb49359b6905c93cec52adb5 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 05:27:04 -0800 Subject: [PATCH 3/5] Artboard Options: Presentation Mode draws no fade, and the fade's rectangles stay few for many artboards The fade outside the artboards is no longer drawn in Presentation Mode, which shows only the art, even when the Artboard tool is active. The rectangles that make up the fade are now merged down from band to band when a gap keeps the same left and right edges, and the bands are swept with the boards sorted by their top edge. A row of 200 top-aligned artboards of different heights now gives at most 600 rectangles instead of about 20,000. --- crates/ui-egui/src/canvas.rs | 61 +++++++++++++++++++++++------- crates/ui-egui/src/canvas/tests.rs | 33 ++++++++++++++++ 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 44d8674d7..cec0c7ca9 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -832,10 +832,12 @@ fn reproject_frame(app: &VectorcraftApp, painter: &egui::Painter, xf: &Xf, rect: /// Does the area outside the artboards draw darker? Artboard Options › Fade Region Outside Artboard /// darkens it while the Artboard tool is active, and with Update While Dragging off it draws as -/// usual while the tool moves or resizes an artboard. Exports and prints never draw the fade. +/// usual while the tool moves or resizes an artboard. Presentation Mode, exports and prints never +/// draw the fade. fn fades_outside_artboards(app: &VectorcraftApp) -> bool { let prefs = &app.session.prefs; - app.session.tool_id() == "artboard" + app.ui.screen_mode != 3 + && app.session.tool_id() == "artboard" && prefs.fade_region_outside_artboard && (prefs.artboard_update_while_dragging || !app.session.tool_transforming()) } @@ -861,29 +863,62 @@ fn fade_outside_artboards(painter: &egui::Painter, xf: &Xf, doc: &vectorcraft_do } /// The parts of `bounds` that no rectangle of `boards` covers, as rectangles that do not overlap: -/// for each band between consecutive top and bottom edges, the spans between the boards that -/// cross it. +/// for each band between consecutive top and bottom edges, the gaps between the boards that cross +/// it. A gap with the same left and right edges as a gap of the band above extends that gap's +/// rectangle down, so each board edge adds at most a few rectangles. fn outside_rects(bounds: Rect, boards: &[Rect]) -> Vec { let mut ys: Vec = boards.iter().flat_map(|b| [b.y0, b.y1]).filter(|y| *y > bounds.y0 && *y < bounds.y1).collect(); ys.extend([bounds.y0, bounds.y1]); ys.sort_by(f64::total_cmp); ys.dedup(); - let mut out = vec![]; + let mut by_top: Vec = boards.iter().copied().filter(|b| b.y0 < bounds.y1 && b.y1 > bounds.y0).collect(); + by_top.sort_by(|a, b| a.y0.total_cmp(&b.y0)); + let mut next = 0; + // The boards that cross the current band, left to right. + let mut active: Vec = vec![]; + let mut out: Vec = vec![]; + // The indices in `out` of the band above's rectangles, left to right. + let mut above: Vec = vec![]; for band in ys.windows(2) { let &[y0, y1] = band else { continue }; - let mut spans: Vec<(f64, f64)> = - boards.iter().filter(|b| b.y0 <= y0 && b.y1 >= y1).map(|b| (b.x0.max(bounds.x0), b.x1.min(bounds.x1))).filter(|(a, b)| a < b).collect(); - spans.sort_by(|a, b| a.0.total_cmp(&b.0)); + while let Some(b) = by_top.get(next).copied().filter(|b| b.y0 <= y0) { + let at = active.partition_point(|a| a.x0 <= b.x0); + active.insert(at, b); + next += 1; + } + active.retain(|b| b.y1 >= y1); + let mut gaps = vec![]; let mut x = bounds.x0; - for (a, b) in spans { - if a > x { - out.push(Rect::new(x, y0, a, y1)); + for b in &active { + let (a, e) = (b.x0.max(bounds.x0), b.x1.min(bounds.x1)); + if a >= e { + continue; } - x = x.max(b); + if a > x { + gaps.push((x, a)); + } + x = x.max(e); } if x < bounds.x1 { - out.push(Rect::new(x, y0, bounds.x1, y1)); + gaps.push((x, bounds.x1)); } + let mut prev = above.iter().copied().peekable(); + let mut row = Vec::with_capacity(gaps.len()); + for (a, e) in gaps { + while prev.next_if(|&i| out[i].x0 < a).is_some() {} + match prev.peek().copied() { + Some(i) if out[i].x0 == a && out[i].x1 == e => { + out[i].y1 = y1; + row.push(i); + prev.next(); + } + _ => { + row.push(out.len()); + out.push(Rect::new(a, y0, e, y1)); + } + } + } + above = row; } out } diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index 161a247c6..9b0d9989b 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -9156,6 +9156,39 @@ fn the_artboard_tool_fades_the_pasteboard() { assert!(!fades(&mut app, &ctx), "the preference off"); } +/// Presentation Mode shows only the art: with the Artboard tool active and the fade on, it draws no +/// fade, and the fade comes back when Presentation Mode ends. +#[test] +fn presentation_mode_draws_no_fade() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 400, "height": 300})).unwrap(); + let ctx = egui::Context::default(); + frame(&mut app, &ctx, vec![]); + app.select_tool("artboard"); + assert!(fades(&mut app, &ctx), "the Artboard tool"); + app.run("view.presentation", json!({})).unwrap(); + assert_eq!(app.ui.screen_mode, 3); + assert_eq!(app.session.tool_id(), "artboard"); + assert!(!fades(&mut app, &ctx), "Presentation Mode"); + app.run("view.presentation", json!({})).unwrap(); + assert!(fades(&mut app, &ctx), "after Presentation Mode"); +} + +/// A row of boards aligned at the top, each a different height, gives a number of parts that grows +/// with the number of boards, not with its square, and the parts still add up to the uncovered area. +#[test] +fn outside_rects_of_a_row_of_boards_stay_few() { + let n = 200; + let boards: Vec = + (0..n).map(|i| Rect::new(20.0 * f64::from(i) + 10.0, 10.0, 20.0 * f64::from(i) + 20.0, 20.0 + 10.0 * f64::from(i))).collect(); + let bounds = Rect::new(0.0, 0.0, 20.0 * f64::from(n) + 10.0, 10.0 * f64::from(n) + 40.0); + let parts = outside_rects(bounds, &boards); + assert!(parts.len() <= 3 * n as usize, "{} parts for {n} boards", parts.len()); + let area = |r: &Rect| r.width() * r.height(); + let covered: f64 = boards.iter().map(area).sum(); + assert_eq!(parts.iter().map(area).sum::(), area(&bounds) - covered); +} + /// The fade covers the art that lies on the pasteboard as well as the pasteboard: it is drawn over /// the art, over a point of an object outside the artboard and not over a point of an object on it, /// in an upright view and in a rotated one. From cdf42d5ab5535089651a780174388482eb49b669 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 06:09:12 -0800 Subject: [PATCH 4/5] Docs: Fade Region Outside Artboard lines name Presentation Mode and the new tests --- docs/mcp.md | 4 ++-- docs/target-app-parity.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/mcp.md b/docs/mcp.md index 5b707f59d..9bae3f4ba 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4207,8 +4207,8 @@ Artboard Options' Global checkboxes are application preferences, not artboard pr (`prefs.set {values: {fadeRegionOutsideArtboard: false}}`; also Edit › Preferences › General › Artboard Options). With the first on, the canvas draws everything outside the artboards darker while the Artboard tool is active, the pasteboard and the art on it: each color channel at 60% of its value, VectorCraft's own amount. With the second off, that area -draws as usual while the Artboard tool moves or resizes an artboard and darker again when the drag ends. Exports and print do not change. The -user guide describes both checkboxes under Artboard options +draws as usual while the Artboard tool moves or resizes an artboard and darker again when the drag ends. Presentation +Mode, exports and print do not change. The user guide describes both checkboxes under Artboard options (https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf). ```json diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 7c4e487cf..a9027e716 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -593,7 +593,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Rulers (#1212):** drag the intersection to preview and set a document zero point, or double-click it to reset to the active artboard's top-left. Transform, Properties and Control X/Y fields measure from it and convert entered positions back to absolute command coordinates. The origin survives every native format version and compression; geometry, guides and patterns stay in place. Public reference: [Use rulers](https://helpx.adobe.com/ca/illustrator/desktop/measure-and-align/grids-and-guides/use-rulers.html). - **Global and artboard rulers:** View › Rulers › Change to Global / Artboard Rulers (Cmd+Alt+R, `view.globalRulers`). A new document has artboard rulers: the rulers and the Transform, Properties, Control and Info X/Y count from the active artboard's top-left corner, or the zero point dragged out for that artboard, which moves with it; with global rulers one document zero point serves every artboard. Guide readouts and Shift ruler-tick snapping follow the same zero point. The mode is saved with the document; files from before it open with global rulers, as they measured. Regression tests: `engine/tests/it/ruler_origin.rs`, `ui-egui/src/tests/ruler_origin.rs`, `tools/src/rulerguide.rs`. Public reference: [About rulers](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/about-rulers.html). - **Video display options (#2849):** Artboard Options' Show Video Safe Areas and Video Ruler Pixel Aspect Ratio, and View › Rulers › Show Video Rulers ([Set video display options for artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/set-video-display-options-for-artboards.html)). Each artboard has its own (`artboard.setProps`/`artboard.new` `showVideoSafeAreas`, `videoRulerPixelAspectRatio` 0.1–10, default 1), set in one undo step and written to the native format only when on or not 1, so other documents save byte for byte as before. The help page names the safe areas without numbers; our choice is [EBU R 95](https://tech.ebu.ch/docs/r/r095.pdf)'s (note 5): the canvas outlines the action-safe area 3.5% and the title-safe (graphics-safe) area 5% in from each edge, 1 point wide and turned with the view; exports and print leave them out. Show Video Rulers (`view.videoRulers`) shows the rulers counting the active artboard in pixels from its top-left corner, whatever the ruler origin: a pixel is as many points wide as the ratio and one point tall, so a 720 × 480 pt artboard at 0.9 reads 800 × 480. Hide Video Rulers returns them to the General unit; Shift ruler-tick snapping of new guides keeps the General unit. Regression tests: `tests/panelcmds.rs` (`artboard_video_display_options_*`, `artboard_display_marks_stay_out_of_exports`), `tests/docsetup.rs` (`an_artboard_shows_its_video_safe_areas`), `canvas.rs` (`video_rulers_count_the_artboard_in_pixels_of_its_aspect_ratio`), `tests/ruler_origin.rs` (`show_video_rulers_counts_the_active_artboard_in_pixels`), `dialogs/tests.rs` (`artboard_options_show_video_display_options`). -- **Fade Region Outside Artboard:** Artboard Options' Global checkboxes Fade Region Outside Artboard and Update While Dragging ([user guide, Artboard options](https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf)) are application preferences (`fadeRegionOutsideArtboard`, `artboardUpdateWhileDragging`), both on by default. While the Artboard tool is active the canvas draws everything outside the artboards, the pasteboard and the art on it, with each color channel at 60% of its value (VectorCraft's own amount); with Update While Dragging off it draws as usual during an artboard move or resize. Exports and print do not change. Regression tests: `canvas::tests` (`the_artboard_tool_fades_the_pasteboard`, `the_fade_darkens_art_on_the_pasteboard_and_not_on_the_artboard`, `outside_rects_cover_the_bounds_minus_the_boards`, `update_while_dragging_keeps_or_drops_the_fade_during_an_artboard_drag`, `the_pasteboard_fade_leaves_exports_unchanged`), `dialogs/tests.rs` (`artboard_options_global_checkboxes_set_the_preferences`, `artboard_options_error_leaves_the_global_preferences_unchanged`). +- **Fade Region Outside Artboard:** Artboard Options' Global checkboxes Fade Region Outside Artboard and Update While Dragging ([user guide, Artboard options](https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf)) are application preferences (`fadeRegionOutsideArtboard`, `artboardUpdateWhileDragging`), both on by default. While the Artboard tool is active the canvas draws everything outside the artboards, the pasteboard and the art on it, with each color channel at 60% of its value (VectorCraft's own amount); with Update While Dragging off it draws as usual during an artboard move or resize. Presentation Mode, exports and print do not change. Regression tests: `canvas::tests` (`the_artboard_tool_fades_the_pasteboard`, `presentation_mode_draws_no_fade`, `the_fade_darkens_art_on_the_pasteboard_and_not_on_the_artboard`, `outside_rects_cover_the_bounds_minus_the_boards`, `outside_rects_of_a_row_of_boards_stay_few`, `update_while_dragging_keeps_or_drops_the_fade_during_an_artboard_drag`, `the_pasteboard_fade_leaves_exports_unchanged`), `dialogs/tests.rs` (`artboard_options_global_checkboxes_set_the_preferences`, `artboard_options_error_leaves_the_global_preferences_unchanged`). - **Saved views (G7):** View › New View… and Edit Views… ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html): up to 25 views per document, saved with it, renamed or deleted in Edit Views, chosen from the bottom of the View menu). Each view keeps the zoom, centre and rotation; the View menu lists all 25 (`view.goto1`–`view.goto25`), and Edit Views… lists them to rename or delete, applied together as one undo step. A file's views are checked when it opens (at most 25, unique names up to 255 characters, a finite centre and a positive zoom). Tests: `saved_views_round_trip_and_from_a_file_are_tidied` (`crates/format/tests/prop_format.rs`), `crates/engine/src/cmd/views.rs`, `crates/ui-egui/src/dialogs/edit_views.rs` and `every_saved_view_is_listed_at_the_bottom_of_the_view_menu` (`crates/ui-egui/src/menus.rs`). - **Window › Arrange › Tile (G7):** Tile (`window.arrange.tile`) shows every open document at once, edge to edge in the document area ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)), one pane each in a grid of as many columns as the square root of the number of documents, rounded up, each fitted to its pane. The current document's pane is the canvas; the other panes draw their documents with their artboards, guides and selected objects, and a press in one makes its document the current one. Consolidate All Windows (`window.arrange.consolidateAllWindows`) shows one document under its tab again. The grid and when the two items are enabled (Tile with two documents or more, Consolidate All Windows while they are tiled) are VectorCraft's own. Tests: `crates/ui-egui/src/canvas/panes/tests.rs`. - **Window › Arrange › Tile (G7):** Tile (`window.arrange.tile`) shows every open document at once, edge to edge in the document area ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)), one pane each in a grid of as many columns as the square root of the number of documents, rounded up, each fitted to its pane. The current document's pane is the canvas; the other panes draw their documents with their artboards, guides and selected objects, and a press in one makes its document the current one. Consolidate All Windows (`window.arrange.consolidateAllWindows`) shows one document under its tab again. The grid and when the two items are enabled (Tile with two documents or more, Consolidate All Windows while they are tiled) are VectorCraft's own. Tests: `crates/ui-egui/src/canvas/panes/tests.rs`. From 12da9ef677b06fd92cd8aa0dc79e1be4c3d0f756 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 06:31:11 -0800 Subject: [PATCH 5/5] Artboard Options: the fade covers the box around crossing artboards when they would leave too many rectangles --- crates/ui-egui/src/canvas.rs | 25 +++++++++++++++++++++---- crates/ui-egui/src/canvas/tests.rs | 30 ++++++++++++++++++++++++++---- docs/mcp.md | 5 +++-- docs/target-app-parity.md | 2 +- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index cec0c7ca9..0793ac9e0 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -851,7 +851,7 @@ const FADE_OUTSIDE: Color32 = Color32::from_black_alpha(102); fn fade_outside_artboards(painter: &egui::Painter, xf: &Xf, doc: &vectorcraft_doc::Document) { let boards: Vec = doc.artboards.iter().map(|a| a.rect).collect(); let mut mesh = egui::Mesh::default(); - for (n, r) in (0u32..).zip(outside_rects(view_bounds(xf), &boards)) { + for (n, r) in (0u32..).zip(fade_rects(view_bounds(xf), &boards)) { for p in xf.quad(r) { mesh.colored_vertex(p, FADE_OUTSIDE); } @@ -862,11 +862,25 @@ fn fade_outside_artboards(painter: &egui::Painter, xf: &Xf, doc: &vectorcraft_do painter.add(Shape::mesh(mesh)); } +/// The most rectangles the fade draws exactly around the artboards. +const FADE_MAX_RECTS: usize = 4096; + +/// The rectangles the fade covers: the parts of `bounds` outside the artboards ([`outside_rects`]), +/// or, when those would be more than [`FADE_MAX_RECTS`], the parts outside the box around all the +/// artboards, at most four. +fn fade_rects(bounds: Rect, boards: &[Rect]) -> Vec { + outside_rects(bounds, boards, FADE_MAX_RECTS).unwrap_or_else(|| { + let all: Vec = boards.iter().copied().reduce(|a, b| a.union(b)).into_iter().collect(); + outside_rects(bounds, &all, FADE_MAX_RECTS).unwrap_or_default() + }) +} + /// The parts of `bounds` that no rectangle of `boards` covers, as rectangles that do not overlap: /// for each band between consecutive top and bottom edges, the gaps between the boards that cross /// it. A gap with the same left and right edges as a gap of the band above extends that gap's -/// rectangle down, so each board edge adds at most a few rectangles. -fn outside_rects(bounds: Rect, boards: &[Rect]) -> Vec { +/// rectangle down, so each board edge adds at most a few rectangles. Boards that cross each other +/// can leave many more gaps; past `limit` rectangles it stops and returns `None`. +fn outside_rects(bounds: Rect, boards: &[Rect], limit: usize) -> Option> { let mut ys: Vec = boards.iter().flat_map(|b| [b.y0, b.y1]).filter(|y| *y > bounds.y0 && *y < bounds.y1).collect(); ys.extend([bounds.y0, bounds.y1]); ys.sort_by(f64::total_cmp); @@ -919,8 +933,11 @@ fn outside_rects(bounds: Rect, boards: &[Rect]) -> Vec { } } above = row; + if out.len() > limit { + return None; + } } - out + Some(out) } /// Do the artboards show? View › Hide Artboards hides them, except for the Artboard tool, which diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index 9b0d9989b..d9fa7f09c 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -9182,13 +9182,35 @@ fn outside_rects_of_a_row_of_boards_stay_few() { let boards: Vec = (0..n).map(|i| Rect::new(20.0 * f64::from(i) + 10.0, 10.0, 20.0 * f64::from(i) + 20.0, 20.0 + 10.0 * f64::from(i))).collect(); let bounds = Rect::new(0.0, 0.0, 20.0 * f64::from(n) + 10.0, 10.0 * f64::from(n) + 40.0); - let parts = outside_rects(bounds, &boards); + let parts = outside_rects(bounds, &boards, usize::MAX).unwrap(); assert!(parts.len() <= 3 * n as usize, "{} parts for {n} boards", parts.len()); let area = |r: &Rect| r.width() * r.height(); let covered: f64 = boards.iter().map(area).sum(); assert_eq!(parts.iter().map(area).sum::(), area(&bounds) - covered); } +/// 500 narrow tall boards crossed by 500 narrow wide ones leave about 250,000 gaps between them. +/// The fade then covers only the parts outside the box around all the boards, so it stays a few +/// rectangles. +#[test] +fn the_fade_of_crossing_boards_stays_bounded() { + let n = 500; + let side = 4.0 * f64::from(n); + let boards: Vec = (0..n) + .flat_map(|i| { + let at = 4.0 * f64::from(i); + [Rect::new(at, 0.0, at + 1.0, side), Rect::new(0.0, at, side, at + 1.0)] + }) + .collect(); + let bounds = Rect::new(-100.0, -100.0, side + 100.0, side + 100.0); + assert!(outside_rects(bounds, &boards, FADE_MAX_RECTS).is_none(), "more than {FADE_MAX_RECTS} gaps"); + let parts = fade_rects(bounds, &boards); + assert!(parts.len() <= 4, "{} parts", parts.len()); + let area = |r: &Rect| r.width() * r.height(); + let all = Rect::new(0.0, 0.0, side, side); + assert_eq!(parts.iter().map(area).sum::(), area(&bounds) - area(&all)); +} + /// The fade covers the art that lies on the pasteboard as well as the pasteboard: it is drawn over /// the art, over a point of an object outside the artboard and not over a point of an object on it, /// in an upright view and in a rotated one. @@ -9222,10 +9244,10 @@ fn the_fade_darkens_art_on_the_pasteboard_and_not_on_the_artboard() { #[test] fn outside_rects_cover_the_bounds_minus_the_boards() { let bounds = Rect::new(0.0, 0.0, 100.0, 100.0); - assert!(outside_rects(bounds, &[Rect::new(-10.0, -10.0, 110.0, 110.0)]).is_empty()); - assert_eq!(outside_rects(bounds, &[]), vec![bounds]); + assert!(fade_rects(bounds, &[Rect::new(-10.0, -10.0, 110.0, 110.0)]).is_empty()); + assert_eq!(fade_rects(bounds, &[]), vec![bounds]); let boards = [Rect::new(10.0, 10.0, 40.0, 40.0), Rect::new(30.0, 30.0, 60.0, 80.0), Rect::new(90.0, -20.0, 130.0, 50.0)]; - let parts = outside_rects(bounds, &boards); + let parts = fade_rects(bounds, &boards); let area = |r: &Rect| r.width() * r.height(); let overlap = |a: &Rect, b: &Rect| (a.x1.min(b.x1) - a.x0.max(b.x0)).max(0.0) * (a.y1.min(b.y1) - a.y0.max(b.y0)).max(0.0); for (i, p) in parts.iter().enumerate() { diff --git a/docs/mcp.md b/docs/mcp.md index 9bae3f4ba..a51a5f6c8 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4207,8 +4207,9 @@ Artboard Options' Global checkboxes are application preferences, not artboard pr (`prefs.set {values: {fadeRegionOutsideArtboard: false}}`; also Edit › Preferences › General › Artboard Options). With the first on, the canvas draws everything outside the artboards darker while the Artboard tool is active, the pasteboard and the art on it: each color channel at 60% of its value, VectorCraft's own amount. With the second off, that area -draws as usual while the Artboard tool moves or resizes an artboard and darker again when the drag ends. Presentation -Mode, exports and print do not change. The user guide describes both checkboxes under Artboard options +draws as usual while the Artboard tool moves or resizes an artboard and darker again when the drag ends. When crossing +artboards would leave more than 4,096 separate rectangles in view, only the area outside the box around all the +artboards draws darker. Presentation Mode, exports and print do not change. The user guide describes both checkboxes under Artboard options (https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf). ```json diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index a9027e716..a5ecfce14 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -593,7 +593,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Rulers (#1212):** drag the intersection to preview and set a document zero point, or double-click it to reset to the active artboard's top-left. Transform, Properties and Control X/Y fields measure from it and convert entered positions back to absolute command coordinates. The origin survives every native format version and compression; geometry, guides and patterns stay in place. Public reference: [Use rulers](https://helpx.adobe.com/ca/illustrator/desktop/measure-and-align/grids-and-guides/use-rulers.html). - **Global and artboard rulers:** View › Rulers › Change to Global / Artboard Rulers (Cmd+Alt+R, `view.globalRulers`). A new document has artboard rulers: the rulers and the Transform, Properties, Control and Info X/Y count from the active artboard's top-left corner, or the zero point dragged out for that artboard, which moves with it; with global rulers one document zero point serves every artboard. Guide readouts and Shift ruler-tick snapping follow the same zero point. The mode is saved with the document; files from before it open with global rulers, as they measured. Regression tests: `engine/tests/it/ruler_origin.rs`, `ui-egui/src/tests/ruler_origin.rs`, `tools/src/rulerguide.rs`. Public reference: [About rulers](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/about-rulers.html). - **Video display options (#2849):** Artboard Options' Show Video Safe Areas and Video Ruler Pixel Aspect Ratio, and View › Rulers › Show Video Rulers ([Set video display options for artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/set-video-display-options-for-artboards.html)). Each artboard has its own (`artboard.setProps`/`artboard.new` `showVideoSafeAreas`, `videoRulerPixelAspectRatio` 0.1–10, default 1), set in one undo step and written to the native format only when on or not 1, so other documents save byte for byte as before. The help page names the safe areas without numbers; our choice is [EBU R 95](https://tech.ebu.ch/docs/r/r095.pdf)'s (note 5): the canvas outlines the action-safe area 3.5% and the title-safe (graphics-safe) area 5% in from each edge, 1 point wide and turned with the view; exports and print leave them out. Show Video Rulers (`view.videoRulers`) shows the rulers counting the active artboard in pixels from its top-left corner, whatever the ruler origin: a pixel is as many points wide as the ratio and one point tall, so a 720 × 480 pt artboard at 0.9 reads 800 × 480. Hide Video Rulers returns them to the General unit; Shift ruler-tick snapping of new guides keeps the General unit. Regression tests: `tests/panelcmds.rs` (`artboard_video_display_options_*`, `artboard_display_marks_stay_out_of_exports`), `tests/docsetup.rs` (`an_artboard_shows_its_video_safe_areas`), `canvas.rs` (`video_rulers_count_the_artboard_in_pixels_of_its_aspect_ratio`), `tests/ruler_origin.rs` (`show_video_rulers_counts_the_active_artboard_in_pixels`), `dialogs/tests.rs` (`artboard_options_show_video_display_options`). -- **Fade Region Outside Artboard:** Artboard Options' Global checkboxes Fade Region Outside Artboard and Update While Dragging ([user guide, Artboard options](https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf)) are application preferences (`fadeRegionOutsideArtboard`, `artboardUpdateWhileDragging`), both on by default. While the Artboard tool is active the canvas draws everything outside the artboards, the pasteboard and the art on it, with each color channel at 60% of its value (VectorCraft's own amount); with Update While Dragging off it draws as usual during an artboard move or resize. Presentation Mode, exports and print do not change. Regression tests: `canvas::tests` (`the_artboard_tool_fades_the_pasteboard`, `presentation_mode_draws_no_fade`, `the_fade_darkens_art_on_the_pasteboard_and_not_on_the_artboard`, `outside_rects_cover_the_bounds_minus_the_boards`, `outside_rects_of_a_row_of_boards_stay_few`, `update_while_dragging_keeps_or_drops_the_fade_during_an_artboard_drag`, `the_pasteboard_fade_leaves_exports_unchanged`), `dialogs/tests.rs` (`artboard_options_global_checkboxes_set_the_preferences`, `artboard_options_error_leaves_the_global_preferences_unchanged`). +- **Fade Region Outside Artboard:** Artboard Options' Global checkboxes Fade Region Outside Artboard and Update While Dragging ([user guide, Artboard options](https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf)) are application preferences (`fadeRegionOutsideArtboard`, `artboardUpdateWhileDragging`), both on by default. While the Artboard tool is active the canvas draws everything outside the artboards, the pasteboard and the art on it, with each color channel at 60% of its value (VectorCraft's own amount), or only outside the box around all the artboards when crossing artboards would leave more than 4,096 rectangles in view; with Update While Dragging off it draws as usual during an artboard move or resize. Presentation Mode, exports and print do not change. Regression tests: `canvas::tests` (`the_artboard_tool_fades_the_pasteboard`, `presentation_mode_draws_no_fade`, `the_fade_darkens_art_on_the_pasteboard_and_not_on_the_artboard`, `outside_rects_cover_the_bounds_minus_the_boards`, `outside_rects_of_a_row_of_boards_stay_few`, `the_fade_of_crossing_boards_stays_bounded`, `update_while_dragging_keeps_or_drops_the_fade_during_an_artboard_drag`, `the_pasteboard_fade_leaves_exports_unchanged`), `dialogs/tests.rs` (`artboard_options_global_checkboxes_set_the_preferences`, `artboard_options_error_leaves_the_global_preferences_unchanged`). - **Saved views (G7):** View › New View… and Edit Views… ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html): up to 25 views per document, saved with it, renamed or deleted in Edit Views, chosen from the bottom of the View menu). Each view keeps the zoom, centre and rotation; the View menu lists all 25 (`view.goto1`–`view.goto25`), and Edit Views… lists them to rename or delete, applied together as one undo step. A file's views are checked when it opens (at most 25, unique names up to 255 characters, a finite centre and a positive zoom). Tests: `saved_views_round_trip_and_from_a_file_are_tidied` (`crates/format/tests/prop_format.rs`), `crates/engine/src/cmd/views.rs`, `crates/ui-egui/src/dialogs/edit_views.rs` and `every_saved_view_is_listed_at_the_bottom_of_the_view_menu` (`crates/ui-egui/src/menus.rs`). - **Window › Arrange › Tile (G7):** Tile (`window.arrange.tile`) shows every open document at once, edge to edge in the document area ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)), one pane each in a grid of as many columns as the square root of the number of documents, rounded up, each fitted to its pane. The current document's pane is the canvas; the other panes draw their documents with their artboards, guides and selected objects, and a press in one makes its document the current one. Consolidate All Windows (`window.arrange.consolidateAllWindows`) shows one document under its tab again. The grid and when the two items are enabled (Tile with two documents or more, Consolidate All Windows while they are tiled) are VectorCraft's own. Tests: `crates/ui-egui/src/canvas/panes/tests.rs`. - **Window › Arrange › Tile (G7):** Tile (`window.arrange.tile`) shows every open document at once, edge to edge in the document area ([public source](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)), one pane each in a grid of as many columns as the square root of the number of documents, rounded up, each fitted to its pane. The current document's pane is the canvas; the other panes draw their documents with their artboards, guides and selected objects, and a press in one makes its document the current one. Consolidate All Windows (`window.arrange.consolidateAllWindows`) shows one document under its tab again. The grid and when the two items are enabled (Tile with two documents or more, Consolidate All Windows while they are tiled) are VectorCraft's own. Tests: `crates/ui-egui/src/canvas/panes/tests.rs`.