mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 10:47:11 -06:00
Step 3 of the Rectangular Grid tool and Arc tool help pages (https://helpx.adobe.com/illustrator/using/tool-techniques/rectangular-grid-tool.html, https://helpx.adobe.com/illustrator/using/tool-techniques/arc-tool.html) and the Tools page (https://helpx.adobe.com/illustrator/using/tools.html) say a double-click on the tool opens its Tool Options. For these four tools the double-click (tool.options, and Enter once #2891 lands) said the tool had no options (#2691). The Tool Options are the tool's click dialog in dialogs/shapes.rs, opened without a place to draw (shapes::open_options; a `__toolOptions` field marks it), as the review of #2979 asked: no copied form code. Its OK runs tool.setOption and draws nothing; Cancel changes nothing. The grids' dialogs are titled Rectangular Grid / Polar Grid Tool Options there. The options are what the next drag draws with and what a click's dialog opens at: - Line Segment: Length and Angle (now kept by the tool and used by the click dialog) and Fill Line (new: shape.line {fill: true} gives the line the current fill; the click dialog has it too); - Arc: lengths, Reference Point, Type, Base Along, Slope, Fill Arc; - the grids: dividers, skews and switches, plus the Default Size and Reference Point, now kept by the tool (width, height, reference) for the click dialog; a drag leaves them out of its command. The grids' Reference Point is drawn only as the 9-point locator (it also showed as a plain number field). New strings (Rectangular Grid Tool Options, Polar Grid Tool Options, Fill Line) have rows in all 24 catalogs. Verification: toolbar::tests::double_clicking_the_line_arc_and_grid_tools_opens_their_tool_options double-clicks each tool's toolbar button: the dialog and its title, Cancel leaves the next drag as it was, OK draws nothing, the next drag uses the option and a click's dialog opens at it (a grid at its Default Size); line_segment_options_and_fill_line, grid_default_size_is_an_option_the_drag_leaves_out, fill_line_gives_a_new_line_the_current_fill. Grid dialog tests now expect the stored Default Size. Live: double-clicking the Rectangular Grid tool in the release build opens Rectangular Grid Tool Options; OK with 2 and 3 dividers, then a drag, draws a 3 × 4 grid.
158 lines
8.0 KiB
Rust
158 lines
8.0 KiB
Rust
//! The new-art template (M3.65): Stroke panel options and graphic styles chosen with nothing
|
|
//! selected, New Art Has Basic Appearance and `paint.default`.
|
|
|
|
use serde_json::{Value, json};
|
|
use vectorcraft_color::{BlendMode, Paint};
|
|
use vectorcraft_doc::{Appearance, Dash, LineCap, Node};
|
|
|
|
use super::*;
|
|
|
|
fn session() -> Session {
|
|
let mut s = Session::new();
|
|
s.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
|
|
s
|
|
}
|
|
|
|
fn run(s: &mut Session, cmd: &str, p: Value) -> Value {
|
|
s.execute(cmd, &p).unwrap_or_else(|e| panic!("{cmd}: {e}"))
|
|
}
|
|
|
|
fn rect(s: &mut Session, x: f64) -> NodeId {
|
|
NodeId(run(s, "shape.rectangle", json!({"x": x, "y": 20, "width": 60, "height": 40}))["id"].as_u64().unwrap())
|
|
}
|
|
|
|
fn node(s: &Session, id: NodeId) -> Node {
|
|
s.doc().unwrap().doc.node(id).unwrap().clone()
|
|
}
|
|
|
|
fn undo_len(s: &Session) -> usize {
|
|
s.doc().unwrap().history.undo.len()
|
|
}
|
|
|
|
#[test]
|
|
fn stroke_options_set_with_nothing_selected_carry_to_the_next_object() {
|
|
let mut s = session();
|
|
let before = rect(&mut s, 20.0);
|
|
run(&mut s, "select.none", json!({}));
|
|
let undo = undo_len(&s);
|
|
run(&mut s, "stroke.set", json!({"cap": "round", "dash": [4, 2], "weight": 3, "endArrow": "Arrow"}));
|
|
assert_eq!(undo_len(&s), undo, "no document change, no undo step");
|
|
// The Stroke panel shows them with nothing selected.
|
|
let shown = s.shown_stroke().unwrap();
|
|
assert_eq!((shown.cap, shown.width, shown.dash.as_ref().map(|d| d.pattern.clone())), (LineCap::Round, 3.0, Some(vec![4.0, 2.0])));
|
|
let id = rect(&mut s, 120.0);
|
|
let st = node(&s, id).appearance.stroke().unwrap().clone();
|
|
assert_eq!((st.cap, st.width, st.end_arrow.is_some()), (LineCap::Round, 3.0, true));
|
|
assert_eq!(st.dash, Some(Dash { pattern: vec![4.0, 2.0], ..Default::default() }));
|
|
assert_eq!(node(&s, id).appearance.fill_paint(), Paint::solid(vectorcraft_color::Color::WHITE), "the fill stays the proxy's");
|
|
assert_eq!(node(&s, before).appearance.stroke().unwrap().cap, LineCap::Butt, "art drawn before keeps its stroke");
|
|
// Editing a selected object's stroke leaves the template alone.
|
|
run(&mut s, "stroke.set", json!({"cap": "square"}));
|
|
run(&mut s, "select.none", json!({}));
|
|
assert_eq!(s.shown_stroke().unwrap().cap, LineCap::Round);
|
|
// Other new art takes it too (a line still without a fill).
|
|
let line = NodeId(run(&mut s, "shape.line", json!({"x1": 10, "y1": 200, "x2": 200, "y2": 200}))["id"].as_u64().unwrap());
|
|
let l = node(&s, line).appearance;
|
|
assert_eq!((l.fill_paint(), l.stroke().unwrap().cap), (Paint::None, LineCap::Round));
|
|
// Default Fill and Stroke resets the template.
|
|
run(&mut s, "paint.default", json!({}));
|
|
let plain = rect(&mut s, 200.0);
|
|
assert_eq!(node(&s, plain).appearance, Appearance::default_art());
|
|
assert!(s.shown_stroke().is_some_and(|st| st.dash.is_none() && st.cap == LineCap::Butt));
|
|
}
|
|
|
|
/// A rectangle with two fills, a drop shadow and 50% opacity, selected.
|
|
fn fancy(s: &mut Session) -> NodeId {
|
|
let id = rect(s, 20.0);
|
|
run(s, "paint.setFill", json!({"color": "#ff0000"}));
|
|
run(s, "appearance.addFill", json!({}));
|
|
run(s, "paint.setFill", json!({"color": "#0000ff"}));
|
|
run(s, "effect.apply", json!({"effect": "stylize.dropShadow"}));
|
|
run(s, "transparency.set", json!({"opacity": 50}));
|
|
id
|
|
}
|
|
|
|
#[test]
|
|
fn with_basic_appearance_off_new_art_takes_the_last_selections_whole_appearance() {
|
|
let mut s = session();
|
|
assert!(s.prefs.new_art_basic, "on by default");
|
|
let src = fancy(&mut s);
|
|
run(&mut s, "select.none", json!({}));
|
|
let basic = rect(&mut s, 120.0);
|
|
let b = node(&s, basic);
|
|
assert_eq!((b.appearance.items.len(), b.appearance.effects.len(), b.opacity), (2, 0, 1.0), "on: one fill and stroke");
|
|
assert_eq!(run(&mut s, "appearance.setNewArtBasic", json!({})), json!({"on": false}), "no param toggles");
|
|
run(&mut s, "select.set", json!({"ids": [src.0]}));
|
|
run(&mut s, "select.none", json!({}));
|
|
let id = rect(&mut s, 220.0);
|
|
let (n, from) = (node(&s, id), node(&s, src));
|
|
assert_eq!(n.appearance.items.iter().filter(|i| i.is_fill()).count(), 2, "both fills");
|
|
assert_eq!(n.appearance.effects.len(), 1, "the drop shadow");
|
|
assert!(n.appearance.approx_eq(&from.appearance));
|
|
assert_eq!((n.opacity, n.blend), (0.5, BlendMode::Normal));
|
|
let q = run(&mut s, "appearance.newArt", json!({}));
|
|
assert_eq!((q["basic"].clone(), q["inherited"].clone(), q["opacity"].clone()), (json!(false), json!(true), json!(50.0)));
|
|
// Back on, new art takes only the basic fill and stroke again.
|
|
run(&mut s, "appearance.setNewArtBasic", json!({"on": true}));
|
|
run(&mut s, "select.none", json!({}));
|
|
let again = rect(&mut s, 300.0);
|
|
let again = node(&s, again);
|
|
assert_eq!((again.appearance.items.len(), again.appearance.effects.len(), again.opacity), (2, 0, 1.0));
|
|
}
|
|
|
|
#[test]
|
|
fn a_graphic_style_clicked_with_nothing_selected_styles_the_next_object() {
|
|
let mut s = session();
|
|
fancy(&mut s);
|
|
// A placed gradient on the top fill: the style keeps it relative to the object's bounds.
|
|
let stops = json!([{"offset": 0, "color": "#ffffff"}, {"offset": 1, "color": "#000000"}]);
|
|
run(&mut s, "paint.setFill", json!({"gradient": {"stops": stops, "start": [20, 40], "end": [50, 40]}}));
|
|
let name = run(&mut s, "graphicStyle.new", json!({"name": "Fancy"}))["name"].as_str().unwrap().to_string();
|
|
run(&mut s, "select.none", json!({}));
|
|
let undo = undo_len(&s);
|
|
assert_eq!(run(&mut s, "graphicStyle.apply", json!({"name": name})), json!({"newArt": true}));
|
|
assert_eq!(undo_len(&s), undo);
|
|
assert_eq!(run(&mut s, "appearance.newArt", json!({}))["graphicStyle"], json!("Fancy"));
|
|
let id = NodeId(run(&mut s, "shape.ellipse", json!({"x": 200, "y": 100, "width": 100, "height": 50}))["id"].as_u64().unwrap());
|
|
let n = node(&s, id);
|
|
assert_eq!((n.appearance.items.len(), n.appearance.effects.len(), n.opacity), (3, 1, 0.5));
|
|
// Linked to the style (its look, gradient placed on the ellipse's own bounds).
|
|
let (g, linked) = s.selection_graphic_style().unwrap();
|
|
assert_eq!((g.name.as_str(), linked), ("Fancy", true));
|
|
// Alt-click (add) with nothing selected stacks a style on the template, unlinked.
|
|
run(&mut s, "select.none", json!({}));
|
|
run(&mut s, "graphicStyle.apply", json!({"name": name, "add": true}));
|
|
let q = run(&mut s, "appearance.newArt", json!({}));
|
|
assert_eq!((q["appearance"]["items"].as_array().unwrap().len(), q["graphicStyle"].clone()), (6, Value::Null));
|
|
assert!(s.execute("graphicStyle.apply", &json!({"name": name, "ids": []})).is_err(), "explicit ids that match nothing");
|
|
}
|
|
|
|
#[test]
|
|
fn the_option_round_trips_with_the_preferences() {
|
|
let mut s = session();
|
|
run(&mut s, "prefs.set", json!({"key": "newArtBasic", "value": false}));
|
|
assert!(!s.prefs.new_art_basic);
|
|
assert_eq!(run(&mut s, "prefs.get", json!({"key": "newArtBasic"})), json!(false));
|
|
let json = s.prefs.to_json();
|
|
assert_eq!(json["newArtBasic"], json!(false));
|
|
let back: Prefs = serde_json::from_value(json).unwrap();
|
|
assert_eq!(back, s.prefs);
|
|
// Older preference files load with it on.
|
|
let old: Prefs = serde_json::from_value(json!({"keyboardIncrement": 2})).unwrap();
|
|
assert!(old.new_art_basic);
|
|
}
|
|
|
|
/// Line Segment Tool Options' Fill Line: `shape.line {fill: true}` gives the line the current
|
|
/// fill; without it a new line has none.
|
|
#[test]
|
|
fn fill_line_gives_a_new_line_the_current_fill() {
|
|
let mut s = session();
|
|
run(&mut s, "paint.setFill", json!({"color": "#ff0000"}));
|
|
let plain = NodeId(run(&mut s, "shape.line", json!({"x1": 10, "y1": 20, "x2": 200, "y2": 80}))["id"].as_u64().unwrap());
|
|
let filled = NodeId(run(&mut s, "shape.line", json!({"x1": 10, "y1": 20, "x2": 200, "y2": 80, "fill": true}))["id"].as_u64().unwrap());
|
|
assert_eq!(node(&s, plain).appearance.fill_paint(), Paint::None);
|
|
assert_eq!(node(&s, filled).appearance.fill_paint(), s.paint.fill);
|
|
assert_ne!(s.paint.fill, Paint::None);
|
|
assert!(s.execute("shape.line", &json!({"x1": 0, "y1": 0, "x2": 1, "y2": 1, "fill": "yes"})).is_err());
|
|
}
|