mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
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:
@@ -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
|
||||
|
||||
@@ -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() };
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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![],
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -255,6 +255,8 @@
|
||||
Average… Μέσος όρος…
|
||||
Axis: Άξονας:
|
||||
Background Color: Χρώμα φόντου:
|
||||
Show Center Mark Εμφάνιση σημαδιού κέντρου
|
||||
Show Cross Hairs Εμφάνιση σταυρονήματος
|
||||
Background Contents Περιεχόμενα φόντου
|
||||
Background Contents: Περιεχόμενο φόντου:
|
||||
Background: Φόντο:
|
||||
|
||||
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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)
|
||||
|
||||
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -271,6 +271,8 @@
|
||||
Average… Усереднити…
|
||||
Axis: Вісь:
|
||||
Background Color: Колір тла:
|
||||
Show Center Mark Показати центральну позначку
|
||||
Show Cross Hairs Показати перехрестя
|
||||
Background Contents Вміст фону
|
||||
Background Contents: Вміст фону:
|
||||
Background: Фон:
|
||||
|
||||
|
@@ -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:
|
||||
|
||||
|
@@ -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.
|
@@ -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.
|
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
File diff suppressed because one or more lines are too long
+8
-1
@@ -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
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user