Artboard Options: the fade covers the box around crossing artboards when they would leave too many rectangles

This commit is contained in:
dotCooCoo
2026-11-08 12:47:55 +07:00
committed by bflatastic
parent cdf42d5ab5
commit 12da9ef677
4 changed files with 51 additions and 11 deletions
+21 -4
View File
@@ -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<Rect> = 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<Rect> {
outside_rects(bounds, boards, FADE_MAX_RECTS).unwrap_or_else(|| {
let all: Vec<Rect> = 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<Rect> {
/// 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<Vec<Rect>> {
let mut ys: Vec<f64> = 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<Rect> {
}
}
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
+26 -4
View File
@@ -9182,13 +9182,35 @@ fn outside_rects_of_a_row_of_boards_stay_few() {
let boards: Vec<Rect> =
(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::<f64>(), 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<Rect> = (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::<f64>(), 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() {
+3 -2
View File
@@ -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
+1 -1
View File
@@ -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`.