Merge pull request #6118 from dotCooCoo/feat/artboard-fade-outside

Artboard Options: Fade Region Outside Artboard darkens the area outside the artboards
This commit is contained in:
BFlat
2026-11-08 14:12:29 +08:00
committed by GitHub
36 changed files with 522 additions and 9 deletions
+3
View File
@@ -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 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),
];
pub fn spec(key: &str) -> Option<&'static PrefSpec> {
+8
View File
@@ -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 everything outside the artboards darker, the pasteboard and the art on it.
pub fade_region_outside_artboard: bool,
/// 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,
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,
+5
View File
@@ -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<Action> {
let busy = matches!(self.drag, Some(Drag::Move { began: true, .. } | Drag::Resize { began: true, .. }));
self.end_drag();
+113
View File
@@ -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);
}
@@ -827,6 +830,116 @@ 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. Presentation Mode, exports and prints never
/// draw the fade.
fn fades_outside_artboards(app: &VectorcraftApp) -> bool {
let prefs = &app.session.prefs;
app.ui.screen_mode != 3
&& app.session.tool_id() == "artboard"
&& prefs.fade_region_outside_artboard
&& (prefs.artboard_update_while_dragging || !app.session.tool_transforming())
}
/// 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<Rect> = doc.artboards.iter().map(|a| a.rect).collect();
let mut mesh = egui::Mesh::default();
for (n, r) in (0u32..).zip(fade_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 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. 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);
ys.dedup();
let mut by_top: Vec<Rect> = 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<Rect> = vec![];
let mut out: Vec<Rect> = vec![];
// The indices in `out` of the band above's rectangles, left to right.
let mut above: Vec<usize> = vec![];
for band in ys.windows(2) {
let &[y0, y1] = band else { continue };
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 b in &active {
let (a, e) = (b.x0.max(bounds.x0), b.x1.min(bounds.x1));
if a >= e {
continue;
}
if a > x {
gaps.push((x, a));
}
x = x.max(e);
}
if x < bounds.x1 {
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;
if out.len() > limit {
return None;
}
}
Some(out)
}
/// Do the artboards show? View › Hide Artboards hides them, except for the Artboard tool, which
/// edits them.
fn shows_artboards(app: &VectorcraftApp) -> bool {
+199
View File
@@ -9112,6 +9112,205 @@ fn hidden_artboards_leave_a_paper_canvas_without_edges() {
assert_eq!(look(&mut app), (false, true));
}
/// 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()
}
/// 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![]);
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!(!fades(&mut app, &ctx), "the Selection tool");
app.select_tool("artboard");
assert!(fades(&mut app, &ctx), "the Artboard tool");
app.select_tool("rectangle");
assert!(!fades(&mut app, &ctx), "the Rectangle tool");
app.run("prefs.set", json!({"key": "fadeRegionOutsideArtboard", "value": false})).unwrap();
app.select_tool("artboard");
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<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, 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.
#[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!(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 = 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() {
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::<f64>(), 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
/// 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 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");
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!(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:?}");
}
}
/// 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<serde_json::Value> {
["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
+69 -9
View File
@@ -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,59 @@ 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 everything outside the artboards darker.
const FADE: &str = "fadeRegionOutsideArtboard";
/// 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.
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)]
}
/// 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<serde_json::Map<String, Value>, String> {
let mut values = serde_json::Map::new();
for (key, now) in global_fields(app) {
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<String, Value>) -> Result<(), String> {
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 +297,7 @@ pub fn open(app: &mut VectorcraftApp) -> Result<Value, String> {
"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 }))
}
@@ -252,10 +306,14 @@ pub fn open(app: &mut VectorcraftApp) -> Result<Value, String> {
/// 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<Value, String> {
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] {
@@ -264,7 +322,7 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
}
}
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);
@@ -295,7 +353,9 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
"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
@@ -309,7 +369,7 @@ pub fn open_new(app: &mut VectorcraftApp) -> Result<Value, String> {
"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 }))
}
+60
View File
@@ -1739,6 +1739,66 @@ 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);
}
/// 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
+2
View File
@@ -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: Съдържание на фона:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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: Περιεχόμενο φόντου:
Can't render this file because it contains an unexpected character in line 163 and column 5.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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 :
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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}
Can't render this file because it is too large.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 2246 and column 5.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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: 背景のカラー:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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: ფერების შემცირება:
Can't render this file because it is too large.
+2
View File
@@ -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: 배경 내용:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 6 and column 47.
+2
View File
@@ -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: Содержимое фона:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2
View File
@@ -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: Вміст фону:
Can't render this file because it contains an unexpected character in line 177 and column 5.
+2
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 199 and column 5.
+2
View File
@@ -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: 背景内容:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -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: 背景內容:
Can't render this file because it contains an unexpected character in line 6 and column 47.
+10
View File
@@ -4202,6 +4202,16 @@ 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 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. 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
{"name":"run_command","arguments":{"command":"artboard.copy","params":{"index":0}}}
{"name":"run_command","arguments":{"command":"edit.paste","params":{}}}
+1
View File
@@ -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 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`.