Merge pull request #3751 from Taha-Mahmoodi/feat/artboard-center-mark

Artboards: Artboard Options shows a center mark and cross hairs on the canvas
This commit is contained in:
BFlat
2026-10-23 09:55:45 +07:00
committed by GitHub
39 changed files with 384 additions and 27 deletions
+5
View File
@@ -447,8 +447,13 @@ pub struct Artboard {
/// selected ([`Document::new_art`]).
#[serde(default, skip_serializing_if = "skip::is_default")]
pub locked: bool,
/// Show Center Mark (Artboard Options): the canvas draws a mark at the artboard's center.
/// Exports and print leave it out. Always written, as before it could be set.
#[serde(default)]
pub show_center_mark: bool,
/// Show Cross Hairs (Artboard Options): the canvas draws a line from the middle of each side
/// through the center. Exports and print leave them out. Always written, as before it could
/// be set.
#[serde(default)]
pub show_cross_hairs: bool,
/// Background color (Properties › Artboards › Fill, Artboard Options). The canvas draws the
+32 -8
View File
@@ -125,7 +125,7 @@ pub fn specs() -> Vec<CommandSpec> {
"New Artboard",
["Window", "Artboards"],
None,
"{x?, y?, width?, height?, name?, color?: color (its background color, as artboard.setProps takes it; default: none)} (default place: right of the last)",
"{x?, y?, width?, height?, name?, color?: color (its background color, as artboard.setProps takes it; default: none), showCenterMark?: bool, showCrossHairs?: bool (as artboard.setProps takes them; default false)} (default place: right of the last)",
has_doc,
artboard_new
),
@@ -143,7 +143,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Artboard Options…",
["Window", "Artboards"],
None,
"{index | artboards: [{index, name?, color?, locked?, x?, y?, width?, height?}…] (several artboards in one undo step, each with its own fields; the art inside each before any of them changes goes with it, and an object inside several goes with the first listed that moves or scales its art), name?, color?: color (\"#rrggbb\", {c,m,y,k}, … as the paint commands take: the background color the canvas draws the artboard's paper in, multiplied with the Simulate Colored Paper color while that is on; exports and print leave it out) | null (no background color), locked?: bool (the Artboards panel's lock column: a locked artboard keeps its rectangle (a new x, y, width or height is an error unless locked is false), can't be moved, deleted or cut, and Rearrange and Fit to Artwork Bounds leave it as it is; the art on it (each object of a layer or sublayer whose bounds with strokes overlap it, a group as one object) acts as locked art until it is unlocked: clicks, marquees, select.set, select.all and the tools pass over it (art drawn or pasted onto it stays selected and editable until a selection leaves it out or it is moved off the locked artboards), it is deselected, and when another artboard moves or scales it stays unless lockedAndHidden), x?, y?, width?, height?, moveArt?: bool (default false; a position-only change moves the contained art with its artboard), scaleArt?: bool (Scale Artwork with Artboard: resized, the artboard takes the art fully inside it (locked and hidden art only with lockedAndHidden?: bool, default prefs moveLockedWithArtboard) and its guides from its old rectangle onto the new one, each side by its own ratio; strokes/corners/patterns as object.scale), strokes?: bool, corners?: bool, patterns?: bool (the three as object.scale, for art that moves or scales with the artboard), lockedAndHidden?: bool (locked and hidden art moves along; default: the preference; recorded in the journal so a replay matches)} → null, or with scaleArt {scaled: the art scaled}",
"{index | artboards: [{index, name?, color?, locked?, showCenterMark?, showCrossHairs?, x?, y?, width?, height?}…] (several artboards in one undo step, each with its own fields; the art inside each before any of them changes goes with it, and an object inside several goes with the first listed that moves or scales its art), name?, color?: color (\"#rrggbb\", {c,m,y,k}, … as the paint commands take: the background color the canvas draws the artboard's paper in, multiplied with the Simulate Colored Paper color while that is on; exports and print leave it out) | null (no background color), locked?: bool (the Artboards panel's lock column: a locked artboard keeps its rectangle (a new x, y, width or height is an error unless locked is false), can't be moved, deleted or cut, and Rearrange and Fit to Artwork Bounds leave it as it is; the art on it (each object of a layer or sublayer whose bounds with strokes overlap it, a group as one object) acts as locked art until it is unlocked: clicks, marquees, select.set, select.all and the tools pass over it (art drawn or pasted onto it stays selected and editable until a selection leaves it out or it is moved off the locked artboards), it is deselected, and when another artboard moves or scales it stays unless lockedAndHidden), showCenterMark?: bool (Artboard Options' Show Center Mark: the canvas draws a mark at the artboard's center), showCrossHairs?: bool (Show Cross Hairs: the canvas draws a line from the middle of each side through the center; both are screen-only, as the artboard's edges are: exports and print leave them out), x?, y?, width?, height?, moveArt?: bool (default false; a position-only change moves the contained art with its artboard), scaleArt?: bool (Scale Artwork with Artboard: resized, the artboard takes the art fully inside it (locked and hidden art only with lockedAndHidden?: bool, default prefs moveLockedWithArtboard) and its guides from its old rectangle onto the new one, each side by its own ratio; strokes/corners/patterns as object.scale), strokes?: bool, corners?: bool, patterns?: bool (the three as object.scale, for art that moves or scales with the artboard), lockedAndHidden?: bool (locked and hidden art moves along; default: the preference; recorded in the journal so a replay matches)} → null, or with scaleArt {scaled: the art scaled}",
has_doc,
artboard_set
),
@@ -819,11 +819,13 @@ fn artboard_new(s: &mut Session, p: &Value) -> Result<Value> {
let def = Rect::new(last.x1 + 20.0, last.y0, last.x1 + 20.0 + last.width(), last.y1);
let r = rect_from(p, def);
let color = artboard_color(p, "artboard.new")?.flatten();
check_switches(p, &["showCenterMark", "showCrossHairs"], "artboard.new")?;
let (show_center_mark, show_cross_hairs) = (bool_or(p, "showCenterMark", false), bool_or(p, "showCrossHairs", false));
let idx = s.edit("New Artboard", |d, _| {
let id = d.next_artboard_id();
let given = str_param(p, "name").map(str::trim).filter(|n| !n.is_empty());
let name = given.map_or_else(|| format!("Artboard {}", d.artboards.len() + 1), str::to_string);
d.artboards.push(Artboard { id, name, rect: r, locked: false, show_center_mark: false, show_cross_hairs: false, color });
d.artboards.push(Artboard { id, name, rect: r, locked: false, show_center_mark, show_cross_hairs, color });
Ok(d.artboards.len() - 1)
})?;
Ok(json!({ "index": idx }))
@@ -846,15 +848,17 @@ fn artboard_delete(s: &mut Session, p: &Value) -> Result<Value> {
ok()
}
/// One artboard's part of `artboard.setProps`: its index, new rectangle, name, color and lock, how
/// far it moved, whether it was resized, the map its art and guides take when they go with it (`fit` when
/// it scales them), and that art.
/// One artboard's part of `artboard.setProps`: its index, new rectangle, name, color, lock and
/// display marks (Show Center Mark, Show Cross Hairs), how far it moved, whether it was resized,
/// the map its art and guides take when they go with it (`fit` when it scales them), and that art.
struct ArtboardEdit<'a> {
index: usize,
now: Rect,
name: Option<&'a str>,
color: Option<Option<vectorcraft_color::Color>>,
lock: Option<bool>,
center_mark: Option<bool>,
cross_hairs: Option<bool>,
delta: vectorcraft_geom::Vec2,
resized: bool,
fit: Option<Affine>,
@@ -872,7 +876,7 @@ fn artboard_set(s: &mut Session, p: &Value) -> Result<Value> {
};
check_switches(p, &["moveArt", "scaleArt", "strokes", "corners", "patterns", "lockedAndHidden"], "artboard.setProps")?;
for q in &entries {
check_switches(q, &["locked"], "artboard.setProps")?;
check_switches(q, &["locked", "showCenterMark", "showCrossHairs"], "artboard.setProps")?;
check_numbers(q, &["x", "y", "width", "height"], "artboard.setProps")?;
}
// Scale Artwork with Artboard: resized, an artboard takes the art fully inside it (as Move
@@ -895,6 +899,7 @@ fn artboard_set(s: &mut Session, p: &Value) -> Result<Value> {
let color = artboard_color(q, "artboard.setProps")?;
let now = rect_from(q, was);
let lock = q.get("locked").and_then(Value::as_bool);
let (center_mark, cross_hairs) = (q.get("showCenterMark").and_then(Value::as_bool), q.get("showCrossHairs").and_then(Value::as_bool));
// A locked artboard keeps its rectangle unless this unlocks it; its name can change.
let reshaped = [now.x0 - was.x0, now.y0 - was.y0, now.x1 - was.x1, now.y1 - was.y1].iter().any(|d| d.abs() > 1e-6);
if reshaped && lock != Some(false) {
@@ -912,7 +917,20 @@ fn artboard_set(s: &mut Session, p: &Value) -> Result<Value> {
let moving = !resized && bool_or(p, "moveArt", false) && delta != vectorcraft_geom::Vec2::ZERO;
let transform = fit.or_else(|| moving.then(|| Affine::translate(delta)));
let art = if transform.is_some() { d.art_moving_with(index, all).into_iter().filter(|id| taken.insert(*id)).collect() } else { vec![] };
edits.push(ArtboardEdit { index, now, name: str_param(q, "name").map(str::trim), color, lock, delta, resized, fit, transform, art });
edits.push(ArtboardEdit {
index,
now,
name: str_param(q, "name").map(str::trim),
color,
lock,
center_mark,
cross_hairs,
delta,
resized,
fit,
transform,
art,
});
}
// Noted in the journal when art goes along, so a replay transforms the same objects whatever
// the preference is then.
@@ -937,6 +955,12 @@ fn artboard_set(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(l) = e.lock {
a.locked = l;
}
if let Some(m) = e.center_mark {
a.show_center_mark = m;
}
if let Some(c) = e.cross_hairs {
a.show_cross_hairs = c;
}
let id = a.id;
if let Some(xf) = e.transform {
let mut sc = if e.fit.is_some_and(|xf| Scaling::factor(xf).is_some()) { scaling } else { Scaling::default() };
+7 -4
View File
@@ -128,15 +128,18 @@ pub fn document(s: &Session) -> Value {
document_opts(s, SummaryOpts::default())
}
/// An artboard's name and rectangle, its background color (`#rrggbb`) when it has one, and
/// `locked: true` when it is locked.
/// An artboard's name and rectangle, its background color (`#rrggbb`) when it has one,
/// `locked: true` when it is locked, and `showCenterMark: true` and `showCrossHairs: true` when
/// it shows them.
fn artboard_summary(a: &vectorcraft_doc::Artboard) -> Value {
let mut v = json!({"name": a.name, "x": a.rect.x0, "y": a.rect.y0, "width": a.rect.width(), "height": a.rect.height()});
if let Some(c) = a.color {
v["color"] = json!(c.to_hex());
}
if a.locked {
v["locked"] = json!(true);
for (key, on) in [("locked", a.locked), ("showCenterMark", a.show_center_mark), ("showCrossHairs", a.show_cross_hairs)] {
if on {
v[key] = json!(true);
}
}
v
}
+103
View File
@@ -828,6 +828,109 @@ fn artboard_lock_round_trips_through_the_native_format() {
assert_eq!(save(&s), before);
}
/// Artboard Options' Show Center Mark and Show Cross Hairs: `artboard.setProps {showCenterMark,
/// showCrossHairs}` sets and clears each on that artboard only, in one undo step, also for a
/// locked artboard and in an `artboards` list. A change without them leaves them, a duplicate
/// keeps them, `artboard.new` takes them, `inspect_document` reports them, and a value that isn't
/// true or false is refused.
#[test]
fn artboard_display_marks_are_set_and_cleared_in_one_undo_step() {
let mut s = session();
s.execute("artboard.new", &json!({})).unwrap();
let marks = |s: &Session, i: usize| {
let a = &s.doc().unwrap().doc.artboards[i];
(a.show_center_mark, a.show_cross_hairs)
};
let undo = |s: &Session| s.doc().unwrap().history.undo.len();
let u0 = undo(&s);
s.execute("artboard.setProps", &json!({"index": 1, "showCenterMark": true})).unwrap();
assert_eq!((marks(&s, 0), marks(&s, 1)), ((false, false), (true, false)), "only that artboard");
s.execute("artboard.setProps", &json!({"index": 1, "showCrossHairs": true, "showCenterMark": false})).unwrap();
assert_eq!(marks(&s, 1), (false, true));
s.execute("artboard.setProps", &json!({"index": 1, "name": "Marked"})).unwrap();
assert_eq!(marks(&s, 1), (false, true), "a change without them leaves them");
assert_eq!(undo(&s), u0 + 3, "one undo step each");
for was in [(false, true), (true, false), (false, false)] {
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(marks(&s, 1), was);
}
s.execute("edit.redo", &json!({})).unwrap();
assert_eq!(marks(&s, 1), (true, false));
// A locked artboard keeps its rectangle, not its display marks.
s.execute("artboard.setProps", &json!({"index": 0, "locked": true})).unwrap();
s.execute("artboard.setProps", &json!({"index": 0, "showCrossHairs": true})).unwrap();
assert_eq!(marks(&s, 0), (false, true));
let u = undo(&s);
s.execute(
"artboard.setProps",
&json!({"artboards": [{"index": 0, "showCenterMark": true}, {"index": 1, "showCenterMark": false, "showCrossHairs": true}]}),
)
.unwrap();
assert_eq!((marks(&s, 0), marks(&s, 1)), ((true, true), (false, true)), "each listed artboard takes its own");
assert_eq!(undo(&s), u + 1, "in one undo step");
let doc = crate::inspect::document(&s);
assert_eq!((doc["artboards"][0]["showCenterMark"].clone(), doc["artboards"][1]["showCrossHairs"].clone()), (json!(true), json!(true)));
assert!(doc["artboards"][1].get("showCenterMark").is_none(), "{doc}");
let copy = s.execute("artboard.duplicate", &json!({"index": 1})).unwrap()["index"].as_u64().unwrap() as usize;
assert_eq!(marks(&s, copy), (false, true), "a duplicate keeps them");
let made = s.execute("artboard.new", &json!({"showCenterMark": true})).unwrap()["index"].as_u64().unwrap() as usize;
assert_eq!(marks(&s, made), (true, false), "a new artboard takes them too");
let plain = s.execute("artboard.new", &json!({})).unwrap()["index"].as_u64().unwrap() as usize;
assert_eq!(marks(&s, plain), (false, false));
let before = s.doc().unwrap().doc.clone();
assert!(s.execute("artboard.setProps", &json!({"index": 1, "showCenterMark": "yes"})).is_err());
assert!(
s.execute("artboard.setProps", &json!({"artboards": [{"index": 0, "showCenterMark": false}, {"index": 1, "showCrossHairs": 1}]})).is_err()
);
assert!(s.execute("artboard.new", &json!({"showCrossHairs": "yes"})).is_err());
assert_eq!(*s.doc().unwrap().doc, *before, "a bad value changes nothing");
}
/// The native format keeps Show Center Mark and Show Cross Hairs. A document that never shows them
/// is written byte for byte as before they could be set, and a file written without the two keys
/// opens with both off.
#[test]
fn artboard_display_marks_round_trip_through_the_native_format() {
let mut s = session();
s.execute("artboard.new", &json!({})).unwrap();
let save = |s: &Session| vectorcraft_format::save(&s.doc().unwrap().doc, false);
let before = save(&s);
s.execute("artboard.setProps", &json!({"artboards": [{"index": 0, "showCenterMark": true}, {"index": 1, "showCrossHairs": true}]})).unwrap();
let bytes = save(&s);
let saved: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
let ab = |i: usize| saved["document"]["artboards"][i].clone();
assert_eq!((ab(0)["show_center_mark"].clone(), ab(0)["show_cross_hairs"].clone()), (json!(true), json!(false)));
assert_eq!((ab(1)["show_center_mark"].clone(), ab(1)["show_cross_hairs"].clone()), (json!(false), json!(true)));
let back = vectorcraft_format::load(&bytes).unwrap();
assert_eq!(back.artboards, s.doc().unwrap().doc.artboards);
s.execute("artboard.setProps", &json!({"artboards": [{"index": 0, "showCenterMark": false}, {"index": 1, "showCrossHairs": false}]})).unwrap();
assert_eq!(save(&s), before, "off again, the file is as it was");
// A file whose artboards have no such keys (older writers) opens with both off.
let mut old: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
for a in old["document"]["artboards"].as_array_mut().unwrap() {
let a = a.as_object_mut().unwrap();
a.remove("show_center_mark");
a.remove("show_cross_hairs");
}
let back = vectorcraft_format::load(&serde_json::to_vec(&old).unwrap()).unwrap();
assert!(back.artboards.iter().all(|a| !a.show_center_mark && !a.show_cross_hairs));
}
/// Show Center Mark and Show Cross Hairs are screen-only: SVG and PDF exports (and print, which
/// prints the PDF) come out the same with them on as with them off.
#[test]
fn artboard_display_marks_stay_out_of_exports() {
let mut s = session();
rect(&mut s);
let doc = |s: &Session| (*s.doc().unwrap().doc).clone();
let off = doc(&s);
s.execute("artboard.setProps", &json!({"index": 0, "showCenterMark": true, "showCrossHairs": true})).unwrap();
let on = doc(&s);
assert_ne!(on.artboards, off.artboards);
assert_eq!(vectorcraft_svg::export(&on, &Default::default()), vectorcraft_svg::export(&off, &Default::default()));
assert_eq!(crate::export_pdf(&on, &Default::default()).unwrap(), crate::export_pdf(&off, &Default::default()).unwrap());
}
#[test]
fn swatch_groups_duplicate_sort() {
let mut s = session();
+2
View File
@@ -459,6 +459,8 @@ impl Tool for ArtboardTool {
Some(a) => {
let mut v = rect_json(self.active, a.rect);
v["name"] = json!(a.name);
v["showCenterMark"] = json!(a.show_center_mark);
v["showCrossHairs"] = json!(a.show_cross_hairs);
vec![Action::Dialog("artboardOptions".into(), v)]
}
None => vec![],
+28
View File
@@ -562,6 +562,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
if doc.setup.has_bleed() {
painter.add(Shape::closed_line(xf.quad(doc.setup.bleed_rect(ab.rect)), Stroke::new(1.0, t.bleed)));
}
// Show Center Mark and Show Cross Hairs (Artboard Options). Only the canvas draws them,
// as it does the edges: exports and print leave them out.
artboard_marks(&painter, &xf, ab, t.artboard_mark);
if editing_name != Some(i) && (artboard_tool || doc.artboards.len() > 1) && tool_labelled != u64::try_from(i).ok() {
// At the artboard's own top-left corner, which a rotated view turns away from its
// bounding box's (#1095).
@@ -2891,6 +2894,31 @@ fn canvas_name_galley(painter: &egui::Painter, corner: Pos2, width: f32, name: &
Some((galley, at))
}
/// The length on screen, in points, of each arm of an artboard's center mark (a cross), at any zoom.
const CENTER_MARK_ARM: f32 = 6.0;
/// Artboard `ab`'s display marks in `color`, each line 1 point wide on screen at any zoom and
/// turned with the view: with Show Cross Hairs, a line from the middle of each side through the
/// center to the middle of the opposite side; with Show Center Mark, a cross at the center whose
/// arms run along the artboard's sides, [`CENTER_MARK_ARM`] long on screen
/// (https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/modify-display-settings-of-artboards.html).
fn artboard_marks(painter: &egui::Painter, xf: &Xf, ab: &vectorcraft_doc::Artboard, color: Color32) {
let (r, stroke) = (ab.rect, Stroke::new(1.0, color));
let c = r.center();
let at = |x: f64, y: f64| xf.to_screen(Point::new(x, y));
if ab.show_cross_hairs {
painter.line_segment([at(r.x0, c.y), at(r.x1, c.y)], stroke);
painter.line_segment([at(c.x, r.y0), at(c.x, r.y1)], stroke);
}
if ab.show_center_mark {
let mid = at(c.x, c.y);
for side in [at(c.x + 1.0, c.y), at(c.x, c.y + 1.0)] {
let arm = (side - mid).normalized() * CENTER_MARK_ARM;
painter.line_segment([mid - arm, mid + arm], stroke);
}
}
}
/// Artboard `name` above its top-left `corner` on screen, no wider than the artboard (`width` on
/// screen): a longer name ends in "…", and an artboard too narrow shows none (#949).
fn artboard_label(painter: &egui::Painter, corner: Pos2, width: f32, name: &str, color: Color32) {
+22 -7
View File
@@ -1,8 +1,11 @@
//! Artboard Options (double-click with the Artboard tool, double-click the tool, or the Artboards
//! panel's options icon or menu): the artboard's fields, sizes parsed with units, its position
//! from a reference point, and its background color (`color`: a color as the paint commands take
//! it, or null for none). Sizing choices also go to the other selected artboards; fitting
//! changes only the active artboard around stationary artwork.
//! from a reference point, its background color (`color`: a color as the paint commands take
//! it, or null for none), and Show Center Mark and Show Cross Hairs (`showCenterMark`,
//! `showCrossHairs`), the display marks the canvas draws on the artboard
//! (https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/modify-display-settings-of-artboards.html).
//! Sizing choices also go to the other selected artboards; fitting changes only the active
//! artboard around stationary artwork.
use serde_json::{Value, json};
use vectorcraft_color::Color;
@@ -32,7 +35,7 @@ fn placed(d: &Dialog) -> Rect {
/// as the Transform panel and the Control bar show an artboard's. A new Width or Height keeps the
/// 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.
/// that picks a color of its own, and the Show Center Mark and Show Cross Hairs 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));
@@ -92,6 +95,9 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
if let Some(c) = set {
d.fields.insert("color".into(), c.as_ref().map_or(Value::Null, crate::panels::color_json));
}
ui.add_space(8.0);
form::check(ui, d, "showCenterMark", tl!("Show Center Mark"));
form::check(ui, d, "showCrossHairs", tl!("Show Cross Hairs"));
false
}
@@ -194,7 +200,10 @@ pub fn open(app: &mut VectorcraftApp) -> Result<Value, String> {
let a = st.doc.artboards.get(index).ok_or("no artboard")?;
let r = a.rect;
let color = a.color.as_ref().map(crate::panels::color_json);
let fields = json!({"index": index, "name": a.name, "x": r.x0, "y": r.y0, "width": r.width(), "height": r.height(), "color": color});
let fields = json!({
"index": index, "name": a.name, "x": r.x0, "y": r.y0, "width": r.width(), "height": r.height(), "color": color,
"showCenterMark": a.show_center_mark, "showCrossHairs": a.show_cross_hairs,
});
app.ui.dialog = Some(Dialog::new(KIND, fields));
Ok(json!({ "dialog": KIND }))
}
@@ -265,8 +274,9 @@ pub fn open_new(app: &mut VectorcraftApp) -> Result<Value, String> {
Ok(json!({ "dialog": KIND }))
}
/// OK for a new artboard: `artboard.new` with the fields (an empty name gives the default one) and
/// the background color picked, and the new artboard becomes the active one.
/// OK for a new artboard: `artboard.new` with the fields (an empty name gives the default one),
/// the background color picked and the display marks ticked, and the new artboard becomes the
/// active one.
fn confirm_new(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
let mut p = json!({});
for k in ["x", "y", "width", "height"] {
@@ -279,6 +289,11 @@ fn confirm_new(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
if let Some(c) = d.fields.get("color") {
p["color"] = c.clone();
}
for k in ["showCenterMark", "showCrossHairs"] {
if d.bool(k) {
p[k] = json!(true);
}
}
let r = run_and_close(app, "artboard.new", p)?;
if let Some(i) = r["index"].as_u64().and_then(|i| usize::try_from(i).ok()) {
crate::panels::artboards::select(app, i);
+50
View File
@@ -1102,6 +1102,56 @@ fn new_artboard_options_give_the_new_artboard_its_background_color() {
}
}
/// Artboard Options shows Show Center Mark and Show Cross Hairs, ticked 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, and a new artboard's Artboard Options gives it the ones ticked.
#[test]
fn artboard_options_show_center_mark_and_cross_hairs() {
use vectorcraft_tools::{PointerEvent, PointerKind};
let mut app = app();
let marks = |app: &VectorcraftApp, i: usize| {
let a = &app.session.doc().unwrap().doc.artboards[i];
(a.show_center_mark, a.show_cross_hairs)
};
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("Show Center Mark") && text.contains("Show Cross Hairs"), "{text}");
let d = app.ui.dialog.as_mut().unwrap();
assert!(!d.bool("showCenterMark") && !d.bool("showCrossHairs"));
d.fields.insert("showCenterMark".into(), json!(true));
let undo = app.session.doc().unwrap().history.undo.len();
super::confirm(&mut app).unwrap();
assert_eq!(marks(&app, 0), (true, false));
assert_eq!(app.session.doc().unwrap().history.undo.len(), undo + 1);
// Opened again, it shows them; unticking one clears it.
crate::dialogs::artboard_options::open(&mut app).unwrap();
let d = app.ui.dialog.as_mut().unwrap();
assert!(d.bool("showCenterMark"));
d.fields.insert("showCenterMark".into(), json!(false));
d.fields.insert("showCrossHairs".into(), json!(true));
super::confirm(&mut app).unwrap();
assert_eq!(marks(&app, 0), (false, true));
// A double-click with the Artboard tool on the artboard's empty space.
app.select_tool("artboard");
let view = app.view_info();
for kind in [PointerKind::Down, PointerKind::Up, PointerKind::Down, PointerKind::Up, PointerKind::DoubleClick] {
crate::canvas::dispatch(&mut app, &PointerEvent::new(kind, 100.0, 100.0), view);
}
let d = app.ui.dialog.clone().expect("Artboard Options opened");
assert_eq!((d.kind.as_str(), d.bool("showCenterMark"), d.bool("showCrossHairs")), ("artboardOptions", false, true));
cancel(&mut app);
// A new artboard takes the ones ticked, and none when none is.
for (ticked, want) in [(Some("showCenterMark"), (true, false)), (None, (false, false))] {
app.run("ui.newArtboard", json!({})).unwrap();
if let Some(k) = ticked {
app.ui.dialog.as_mut().unwrap().fields.insert(k.into(), json!(true));
}
super::confirm(&mut app).unwrap();
let last = app.session.doc().unwrap().doc.artboards.len() - 1;
assert_eq!(marks(&app, last), want);
}
}
/// #1482: with Artboards 1 and 3 selected with the Artboard tool, a new Width in Artboard Options
/// goes to both, each keeping its top-left corner and its height, in one undo step. The new name
/// goes only to the artboard the dialog shows, and Artboard 2 keeps its size. A name alone resizes
+2
View File
@@ -264,6 +264,8 @@
Average… Усредняване…
Axis: Ос:
Background Color: Цвят на фона:
Show Center Mark Показване на централен знак
Show Cross Hairs Показване на кръстосани линии
Background Contents Съдържание на фона
Background Contents: Съдържание на фона:
Background: Фон:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -2436,6 +2436,8 @@ lighting Light Světlo
File Dimensions Rozměry souboru
Transforms Transformace
Background Color: Barva pozadí:
Show Center Mark Zobrazit středovou značku
Show Cross Hairs Zobrazit nitkový kříž
Client-side (.html) Na straně klienta (.html)
Color Reduction: Redukce barev:
Lossless (lossy WebP isn't available yet) Bezeztrátový (ztrátový WebP zatím není k dispozici)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -2235,6 +2235,8 @@ lighting Light Lys
File Dimensions Fildimensioner
Transforms Transformationer
Background Color: Baggrundsfarve:
Show Center Mark Vis midtermærke
Show Cross Hairs Vis trådkors
Client-side (.html) Klientbaseret (.html)
Color Reduction: Farvereduktion:
Lossless (lossy WebP isn't available yet) Tabsfri (WebP med tab er ikke tilgængelig endnu)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -252,6 +252,8 @@
Average… Durchschnitt berechnen…
Axis: Achse:
Background Color: Hintergrundfarbe:
Show Center Mark Mittelpunktmarke einblenden
Show Cross Hairs Fadenkreuz einblenden
Background Contents Hintergrundinhalt
Background Contents: Hintergrundinhalt:
Background: Hintergrund:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -255,6 +255,8 @@
Average… Μέσος όρος…
Axis: Άξονας:
Background Color: Χρώμα φόντου:
Show Center Mark Εμφάνιση σημαδιού κέντρου
Show Cross Hairs Εμφάνιση σταυρονήματος
Background Contents Περιεχόμενα φόντου
Background Contents: Περιεχόμενο φόντου:
Background: Φόντο:
1 # Ελληνικά (el) — VectorCraft
255
256
257
258
259
260
261
262
+2
View File
@@ -277,6 +277,8 @@
Average… Promediar…
Axis: Eje:
Background Color: Color de fondo:
Show Center Mark Mostrar marca central
Show Cross Hairs Mostrar líneas en cruz
Background Contents Contenido de fondo
Background Contents: Contenido de fondo:
Background: Fondo:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -2382,6 +2382,8 @@ lighting Light Valo
File Dimensions Tiedoston mitat
Transforms Muunnokset
Background Color: Taustaväri:
Show Center Mark Näytä keskimerkki
Show Cross Hairs Näytä hiusristikko
Client-side (.html) Asiakaspuoli (.html)
Color Reduction: Värien vähennys:
Lossless (lossy WebP isn't available yet) Häviötön (häviöllinen WebP ei ole vielä käytettävissä)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -278,6 +278,8 @@
Average… Alignement…
Axis: Axe :
Background Color: Couleur d’arrière-plan :
Show Center Mark Afficher le repère central
Show Cross Hairs Afficher le réticule
Background Contents Contenu de l’arrière-plan
Background Contents: Contenu de l’arrière-plan :
Background: Arrière-plan :
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -373,6 +373,8 @@
B4 B4
B5 B5
Background Color: Háttérszín:
Show Center Mark Középjel megjelenítése
Show Cross Hairs Szálkereszt megjelenítése
Background Contents Háttértartalom
Background Contents: Háttértartalom:
Background: Háttér:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -2235,6 +2235,8 @@ lighting Light Cahaya
File Dimensions Dimensi file
Transforms Transformasi
Background Color: Warna latar belakang:
Show Center Mark Tampilkan tanda tengah
Show Cross Hairs Tampilkan garis silang
Client-side (.html) Sisi klien (.html)
Color Reduction: Reduksi warna:
Lossless (lossy WebP isn't available yet) Tanpa kehilangan (WebP lossy belum tersedia)
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
2235
2236
2237
2238
2239
2240
2241
2242
+2
View File
@@ -277,6 +277,8 @@
Average… Media…
Axis: Asse:
Background Color: Colore di sfondo:
Show Center Mark Mostra segno centrale
Show Cross Hairs Mostra mirino
Background Contents Contenuto sfondo
Background Contents: Contenuto sfondo:
Background: Sfondo:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -936,6 +936,8 @@
Average 平均
BLANK DOCUMENT PRESETS 空白のドキュメントプリセット
Background Color: 背景色:
Show Center Mark 中心マークを表示
Show Cross Hairs 十字線を表示
Background Contents 背景のカラー
Background Contents: 背景のカラー:
Background: 背景:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -2130,6 +2130,8 @@ lighting Light სინათლე
File Dimensions ფაილის ზომები
Transforms გარდაქმნები
Background Color: ფონის ფერი:
Show Center Mark ცენტრის ნიშნის ჩვენება
Show Cross Hairs ჯვარედინი ხაზების ჩვენება
Client-side (.html) კლიენტის მხარე (.html)
Color Reduction: ფერების შემცირება:
Lossless (lossy WebP isn't available yet) უდანაკარგო (დანაკარგიანი WebP ჯერ მიუწვდომელია)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -931,6 +931,8 @@
Average 평균
BLANK DOCUMENT PRESETS 빈 문서 프리셋
Background Color: 배경색:
Show Center Mark 중심 표시 보기
Show Cross Hairs 십자선 보기
Background Contents 배경 내용
Background Contents: 배경 내용:
Background: 배경:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -2249,6 +2249,8 @@ lighting Light Licht
File Dimensions Bestandsafmetingen
Transforms Transformaties
Background Color: Achtergrondkleur:
Show Center Mark Middenmarkering tonen
Show Cross Hairs Dradenkruis tonen
Client-side (.html) Client-side (.html)
Color Reduction: Kleurreductie:
Lossless (lossy WebP isn't available yet) Verliesvrij (WebP met verlies is nog niet beschikbaar)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -2349,6 +2349,8 @@ lighting Light Światło
File Dimensions Wymiary pliku
Transforms Przekształcenia
Background Color: Kolor tła:
Show Center Mark Pokaż znacznik środka
Show Cross Hairs Pokaż krzyż nitkowy
Client-side (.html) Po stronie klienta (.html)
Color Reduction: Redukcja kolorów:
Lossless (lossy WebP isn't available yet) Bezstratny (stratny WebP nie jest jeszcze dostępny)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -264,6 +264,8 @@
Average… Média…
BLANK DOCUMENT PRESETS PREDEFINIDOS DE DOCUMENTO EM BRANCO
Background Color: Cor do Plano de Fundo:
Show Center Mark Mostrar Marca Central
Show Cross Hairs Mostrar Linhas Cruzadas
Background Contents Conteúdo do Plano de Fundo
Background Contents: Conteúdo do Plano de Fundo:
Background: Plano de Fundo:
Can't render this file because it contains an unexpected character in line 6 and column 47.
+2
View File
@@ -275,6 +275,8 @@
Average… Усреднить…
Axis: Ось:
Background Color: Цвет фона:
Show Center Mark Показать центральную метку
Show Cross Hairs Показать перекрестие
Background Contents Содержимое фона
Background Contents: Содержимое фона:
Background: Фон:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -266,6 +266,8 @@
Average… Medelvärde…
Axis: Axel:
Background Color: Bakgrundsfärg:
Show Center Mark Visa mittmarkering
Show Cross Hairs Visa hårkors
Background Contents Bakgrundsinnehåll
Background Contents: Bakgrundsinnehåll:
Background: Bakgrund:
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2
View File
@@ -262,6 +262,8 @@
Average… Ortalama…
Axis: Eksen:
Background Color: Arka Plan Rengi:
Show Center Mark Merkez İşaretini Göster
Show Cross Hairs Artı Çizgilerini Göster
Background Contents Arka Plan İçeriği
Background Contents: Arkaplan İçeriği:
Background: Arka plan:
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2
View File
@@ -271,6 +271,8 @@
Average… Усереднити…
Axis: Вісь:
Background Color: Колір тла:
Show Center Mark Показати центральну позначку
Show Cross Hairs Показати перехрестя
Background Contents Вміст фону
Background Contents: Вміст фону:
Background: Фон:
1 # Ukrainian (uk) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation.
271
272
273
274
275
276
277
278
+2
View File
@@ -300,6 +300,8 @@
Axis: Trục:
Back Sau
Background Color: Màu nền:
Show Center Mark Hiện dấu tâm
Show Cross Hairs Hiện đường chữ thập
Background Contents Nội dung nền
Background Contents: Nội dung nền:
Background: Nền:
1 # Vietnamese (vi) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
300
301
302
303
304
305
306
307
+2
View File
@@ -225,6 +225,8 @@
Average… 平均…
Axis: 轴:
Background Color: 背景颜色:
Show Center Mark 显示中心标记
Show Cross Hairs 显示十字线
Background Contents 背景内容
Background Contents: 背景内容:
Background: 背景:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -236,6 +236,8 @@
Average… 平均…
Axis: 軸:
Background Color: 背景顏色:
Show Center Mark 顯示中心標記
Show Cross Hairs 顯示十字線
Background Contents 背景內容
Background Contents: 背景內容:
Background: 背景:
Can't render this file because it contains an unexpected character in line 6 and column 47.
+2 -2
View File
@@ -596,7 +596,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"tool.options",
"Tool Options…",
"",
"{tool: id} what double-clicking a tool button opens: hand → fits the artboard in the window (view.fitArtboard), zoom → 100% (view.actualSize), rotate|scale|reflect|shear → that Object › Transform dialog (dialog `rotate`, `scale`, `reflect` or `shear`; error `nothing selected` without a selection), selection|directSelection|groupSelection → the Move dialog (dialog `move`; the same error), gradient|magicWand → the Gradient or Magic Wand panel (window.panel), artboard → Artboard Options of the active artboard (dialog `artboardOptions`, fields index, name, x, y, width, height; OK runs artboard.setProps, and with other artboards selected with the Artboard tool a new width or height goes to each of them too, in its `artboards` list, each resized about the reference point the form used for that size, or about its top-left corner when the size was set in the fields directly), flare → Flare Tool Options (dialog `flareOptions`, fields diameter and pathLength (pt), opacity, brightness, growth, fuzziness, longest, rayFuzziness, largest (%), rays, rings, direction (°), raysOn, ringsOn; OK runs tool.setOption {tool: \"flare\", values}: the next flare dragged out uses them, and a Flare click's dialog, which also has x and y, draws one there with them), columnGraph|stackedColumnGraph|barGraph|stackedBarGraph|lineGraph|areaGraph|scatterGraph|pieGraph|radarGraph → Graph Type for the selected graph (dialog `command` running graph.setType; an error without one), symbolSprayer|symbolShifter|symbolScruncher|symbolSizer|symbolSpinner|symbolStainer|symbolScreener|symbolStyler → Symbolism Tools Options (dialog `symbolismOptions`, fields tool, diameter (pt), intensity and density (1–10), shared by the eight tools; OK runs tool.setOption {tool, values}), blend → Blend Options, perspectiveGrid → Perspective Grid Options, eyedropper → Eyedropper Options (dialog `eyedropperOptions`, fields sampleSize, pickUp, apply; OK runs eyedropper.setOptions), printTiling → resets the print tiling (print.tiling.set {reset: true}), warp|twirl|pucker|bloat|scallop|crystallize|wrinkle → that tool's Tool Options (dialog `liquifyOptions`, fields tool, width, height, angle, intensity %, usePressure, detail, simplify, simplifyOn, rate, complexity, horizontal %, vertical %, affectAnchors, affectIn, affectOut, showBrush; OK runs tool.setOption {tool, values}), pencil|paintbrush|smooth|blobBrush|eraser → that tool's Tool Options (dialog `freehandOptions`, fields tool and the options the tool keeps: fidelity (pt), fill, keepSelected (on for pencil and paintbrush, off for blobBrush), mergeOnlySelection (off), closeWithin and editWithin (px, 0 is off), size (pt); OK runs tool.setOption {tool, values}), livePaintSelection → Live Paint Selection Tool Options (dialog `livePaintSelectionOptions`, fields selectFills and selectStrokes (both on), color (the highlight color, #rrggbb or a layer color's name; #ff2a2a), width (the highlight's width, 1–20 px; 4); OK runs tool.setOption {tool: \"livePaintSelection\", values}), shapeBuilder → Shape Builder Tool Options (dialog `shapeBuilderOptions`, fields gapDetection (off), gapLength (pt, 0–72; 3), openFilledAsClosed (on), clickStrokeSplitsPath (off), pickColorFrom: artwork|swatches (artwork), cursorSwatchPreview (off), selection: freeform|straightLine (freeform), highlightFill (on), highlightStroke (on), highlightColor (#rrggbb or a layer color's name; #ff4f4f); OK runs tool.setOption {tool: \"shapeBuilder\", values})",
"{tool: id} what double-clicking a tool button opens: hand → fits the artboard in the window (view.fitArtboard), zoom → 100% (view.actualSize), rotate|scale|reflect|shear → that Object › Transform dialog (dialog `rotate`, `scale`, `reflect` or `shear`; error `nothing selected` without a selection), selection|directSelection|groupSelection → the Move dialog (dialog `move`; the same error), gradient|magicWand → the Gradient or Magic Wand panel (window.panel), artboard → Artboard Options of the active artboard (dialog `artboardOptions`, fields index, name, x, y, width, height, showCenterMark, showCrossHairs; OK runs artboard.setProps, and with other artboards selected with the Artboard tool a new width or height goes to each of them too, in its `artboards` list, each resized about the reference point the form used for that size, or about its top-left corner when the size was set in the fields directly), flare → Flare Tool Options (dialog `flareOptions`, fields diameter and pathLength (pt), opacity, brightness, growth, fuzziness, longest, rayFuzziness, largest (%), rays, rings, direction (°), raysOn, ringsOn; OK runs tool.setOption {tool: \"flare\", values}: the next flare dragged out uses them, and a Flare click's dialog, which also has x and y, draws one there with them), columnGraph|stackedColumnGraph|barGraph|stackedBarGraph|lineGraph|areaGraph|scatterGraph|pieGraph|radarGraph → Graph Type for the selected graph (dialog `command` running graph.setType; an error without one), symbolSprayer|symbolShifter|symbolScruncher|symbolSizer|symbolSpinner|symbolStainer|symbolScreener|symbolStyler → Symbolism Tools Options (dialog `symbolismOptions`, fields tool, diameter (pt), intensity and density (1–10), shared by the eight tools; OK runs tool.setOption {tool, values}), blend → Blend Options, perspectiveGrid → Perspective Grid Options, eyedropper → Eyedropper Options (dialog `eyedropperOptions`, fields sampleSize, pickUp, apply; OK runs eyedropper.setOptions), printTiling → resets the print tiling (print.tiling.set {reset: true}), warp|twirl|pucker|bloat|scallop|crystallize|wrinkle → that tool's Tool Options (dialog `liquifyOptions`, fields tool, width, height, angle, intensity %, usePressure, detail, simplify, simplifyOn, rate, complexity, horizontal %, vertical %, affectAnchors, affectIn, affectOut, showBrush; OK runs tool.setOption {tool, values}), pencil|paintbrush|smooth|blobBrush|eraser → that tool's Tool Options (dialog `freehandOptions`, fields tool and the options the tool keeps: fidelity (pt), fill, keepSelected (on for pencil and paintbrush, off for blobBrush), mergeOnlySelection (off), closeWithin and editWithin (px, 0 is off), size (pt); OK runs tool.setOption {tool, values}), livePaintSelection → Live Paint Selection Tool Options (dialog `livePaintSelectionOptions`, fields selectFills and selectStrokes (both on), color (the highlight color, #rrggbb or a layer color's name; #ff2a2a), width (the highlight's width, 1–20 px; 4); OK runs tool.setOption {tool: \"livePaintSelection\", values}), shapeBuilder → Shape Builder Tool Options (dialog `shapeBuilderOptions`, fields gapDetection (off), gapLength (pt, 0–72; 3), openFilledAsClosed (on), clickStrokeSplitsPath (off), pickColorFrom: artwork|swatches (artwork), cursorSwatchPreview (off), selection: freeform|straightLine (freeform), highlightFill (on), highlightStroke (on), highlightColor (#rrggbb or a layer color's name; #ff4f4f); OK runs tool.setOption {tool: \"shapeBuilder\", values})",
),
(
"ui.colorGuideOptions",
@@ -678,7 +678,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"ui.newArtboard",
"New Artboard Options…",
"",
"{} open Artboard Options for a new artboard, filled in as artboard.new {} would make it (Artboard N, the last artboard's size, 20 pt to its right): dialog `artboardOptions` with new: true, fields name, x, y, width, height; OK runs artboard.new with the fields and makes the new artboard the active one; Cancel adds nothing",
"{} open Artboard Options for a new artboard, filled in as artboard.new {} would make it (Artboard N, the last artboard's size, 20 pt to its right): dialog `artboardOptions` with new: true, fields name, x, y, width, height, showCenterMark, showCrossHairs; OK runs artboard.new with the fields and makes the new artboard the active one; Cancel adds nothing",
),
(
"ui.layersPanelOptions",
+64 -1
View File
@@ -1,6 +1,7 @@
//! The canvas draws Document Setup: the red bleed outline and the transparency grid in the
//! document's colours and cell size, also in a rotated view; a white Background Contents hides
//! the grid and Simulate Colored Paper tints the artboard.
//! the grid and Simulate Colored Paper tints the artboard. It also draws each artboard's Show
//! Center Mark and Show Cross Hairs (Artboard Options).
use serde_json::json;
use vectorcraft_engine::Session;
@@ -126,6 +127,68 @@ fn an_artboard_color_draws_its_paper_in_that_color() {
assert!(!fills(&mut app).contains(&inked), "the grid covers the colored artboard too");
}
/// The line segments drawn in `color`, each checked to be 1 point wide.
fn segments(shapes: &[egui::Shape], color: egui::Color32) -> Vec<[egui::Pos2; 2]> {
let mut all = vec![];
flatten(shapes, &mut all);
all.into_iter()
.filter_map(|s| match s {
egui::Shape::LineSegment { points, stroke } if stroke.color == color => {
assert_eq!(stroke.width, 1.0);
Some(points)
}
_ => None,
})
.collect()
}
/// Show Center Mark draws a cross at the artboard's center and Show Cross Hairs a line from the
/// middle of each side through it, in the theme's artboard mark colour, 1 point wide at any zoom.
/// Zoomed in, the cross keeps its size on screen while the cross hairs still span the artboard,
/// and a rotated view turns both with the artboard.
#[test]
fn an_artboard_shows_its_center_mark_and_cross_hairs() {
let mut app = app();
let mark = Tokens::for_brightness(Default::default()).artboard_mark;
assert!(segments(&frame(&mut app).0, mark).is_empty(), "off by default");
let len = |s: &[egui::Pos2; 2]| (s[1] - s[0]).length();
let mid = |a: egui::Pos2, b: egui::Pos2| a + (b - a) / 2.0;
let close = |a: egui::Pos2, b: egui::Pos2| (a - b).length() < 0.5;
app.run("artboard.setProps", json!({"index": 0, "showCenterMark": true})).unwrap();
let (shapes, _, _) = frame(&mut app);
// The artboard's corners on screen: top-left, top-right, bottom-right, bottom-left.
let q = outlines(&shapes, egui::Color32::from_gray(0)).into_iter().next().unwrap();
let center = mid(q[0], q[2]);
let cross = segments(&shapes, mark);
assert_eq!(cross.len(), 2, "{cross:?}");
assert!(cross.iter().all(|s| close(mid(s[0], s[1]), center) && (len(s) - 12.0).abs() < 0.01), "{cross:?} at {center:?}");
assert!(cross.iter().any(|s| (s[0].y - s[1].y).abs() < 0.01) && cross.iter().any(|s| (s[0].x - s[1].x).abs() < 0.01), "{cross:?}");
app.run("artboard.setProps", json!({"index": 0, "showCenterMark": false, "showCrossHairs": true})).unwrap();
let hairs = segments(&frame(&mut app).0, mark);
assert_eq!(hairs.len(), 2, "{hairs:?}");
let (left, right, top, bottom) = (mid(q[0], q[3]), mid(q[1], q[2]), mid(q[0], q[1]), mid(q[3], q[2]));
assert!(hairs.iter().any(|s| close(s[0], left) && close(s[1], right)), "{hairs:?}");
assert!(hairs.iter().any(|s| close(s[0], top) && close(s[1], bottom)), "{hairs:?}");
// Both, zoomed in twice as far.
app.run("artboard.setProps", json!({"index": 0, "showCenterMark": true})).unwrap();
app.view_mut().unwrap().zoom *= 2.0;
let mut both = segments(&frame(&mut app).0, mark);
both.sort_by(|a, b| len(a).total_cmp(&len(b)));
assert_eq!(both.len(), 4, "{both:?}");
assert!(both[..2].iter().all(|s| (len(s) - 12.0).abs() < 0.01), "the cross keeps its size on screen: {both:?}");
let was = [len(&hairs[0]), len(&hairs[1])];
let now = [len(&both[2]), len(&both[3])];
assert!(was.iter().all(|w| now.iter().any(|n| (n - 2.0 * w).abs() < 0.5)), "the cross hairs span the artboard: {was:?} → {now:?}");
// Rotated, the cross turns with the artboard.
app.view_mut().unwrap().rotation = 30.0;
let mut turned = segments(&frame(&mut app).0, mark);
turned.sort_by(|a, b| len(a).total_cmp(&len(b)));
for s in &turned[..2] {
let d = s[1] - s[0];
assert!(d.x.abs() > 1.0 && d.y.abs() > 1.0, "not along the screen's axes: {turned:?}");
}
}
#[test]
fn white_background_hides_the_grid_and_paper_tints_the_artboard() {
let mut app = app();
+4
View File
@@ -156,6 +156,9 @@ pub struct Tokens {
pub highlight: Color32,
/// The bleed outline around artboards (Document Setup → Bleed).
pub bleed: Color32,
/// An artboard's Show Center Mark and Show Cross Hairs (Artboard Options), drawn over its
/// paper in every brightness.
pub artboard_mark: Color32,
/// Error marks (a missing linked file).
pub error: Color32,
/// Warning marks (a modified linked file).
@@ -209,6 +212,7 @@ impl Tokens {
caption_close_text: Color32::WHITE,
highlight: hex(0xff3030),
bleed: hex(0xf03030),
artboard_mark: hex(0x808080),
error: hex(0xe34850),
warning: hex(0xf0a330),
axis_x: hex(0xef6058),
+3 -1
View File
File diff suppressed because one or more lines are too long
+8 -1
View File
@@ -3000,7 +3000,7 @@ value: true}`; the Control bar's and Properties' Scale Artwork with Artboard che
Artboard) makes its handle drags proportional and passes `scaleArt`, as do Properties' and the Transform panel's
artboard size fields while the tool is in use.
`artboard.setProps {artboards: [{index, name?, color?, locked?, x?, y?, width?, height?}…], scaleArt?, moveArt?, …}` sets several
`artboard.setProps {artboards: [{index, name?, color?, locked?, showCenterMark?, showCrossHairs?, x?, y?, width?, height?}…], scaleArt?, moveArt?, …}` sets several
artboards in one undo step, each with its own fields; the other params apply to all of them. The art that moves or
scales with each artboard is the art inside it before any of them changes, and an object inside several of them goes
with the first one listed that moves or scales its art. An empty list and a list that names an artboard twice are
@@ -3052,6 +3052,13 @@ artboard × paper / 255; artboards without a color show the paper color. The tra
it shows. Copies, duplicates and the native format keep the color; exports and print do not draw it. `inspect_document`
lists it as the artboard's `color` (`#rrggbb`), and `ui.colorPicker {artboard: index}` opens the Color Picker for it.
Artboard Options' Show Center Mark and Show Cross Hairs: `artboard.setProps {index, showCenterMark?: bool,
showCrossHairs?: bool}` (or each entry of `artboards`) turns them on or off for that artboard in one undo step, and
`artboard.new` takes them too. The canvas draws the center mark as a small cross at the artboard's center and the cross
hairs as lines from the middle of each side through the center, 1 point wide on screen at any zoom; exports and print
leave them out. Duplicates, copies and the native format keep them, and `inspect_document` adds `showCenterMark: true`
and `showCrossHairs: true` to an artboard that shows them.
```json
{"name":"run_command","arguments":{"command":"artboard.copy","params":{"index":0}}}
{"name":"run_command","arguments":{"command":"edit.paste","params":{}}}
File diff suppressed because one or more lines are too long
+3 -1
View File
File diff suppressed because one or more lines are too long