mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 09:31:03 +00:00
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.
This commit is contained in:
@@ -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),
|
||||
];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<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 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<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 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
|
||||
|
||||
@@ -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::<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
|
||||
@@ -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:?}");
|
||||
|
||||
@@ -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<serde_json::Map<String, Value>, 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<String, Value>) -> Result<(), String> {
|
||||
if values.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -299,11 +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> {
|
||||
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<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
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-3
@@ -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).
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
Reference in New Issue
Block a user