118 lines
4.6 KiB
Rust
118 lines
4.6 KiB
Rust
//! Use Preview Bounds: the Transform panel values, the bounding box, Align and Distribute measure
|
|
//! visual bounds (strokes included) when it is on.
|
|
|
|
use serde_json::{Value, json};
|
|
use vectorcraft_doc::NodeId;
|
|
use vectorcraft_geom::Rect;
|
|
use vectorcraft_tools::{PointerEvent, PointerKind};
|
|
|
|
use super::*;
|
|
|
|
fn session() -> Session {
|
|
let mut s = Session::new();
|
|
s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap();
|
|
s
|
|
}
|
|
|
|
fn run(s: &mut Session, id: &str, p: Value) -> Value {
|
|
s.execute(id, &p).unwrap_or_else(|e| panic!("{id}: {e}"))
|
|
}
|
|
|
|
/// A rectangle with a `weight` pt stroke (0: no stroke).
|
|
fn rect(s: &mut Session, x: f64, w: f64, weight: f64) -> NodeId {
|
|
let id = NodeId(run(s, "shape.rectangle", json!({"x": x, "y": 100, "width": w, "height": 50}))["id"].as_u64().unwrap());
|
|
if weight > 0.0 {
|
|
run(s, "stroke.set", json!({"weight": weight}));
|
|
} else {
|
|
run(s, "paint.setStroke", json!({"none": true}));
|
|
}
|
|
id
|
|
}
|
|
|
|
fn geometric(s: &Session, id: NodeId) -> Rect {
|
|
s.doc().unwrap().doc.node(id).unwrap().geometric_bounds().unwrap()
|
|
}
|
|
|
|
fn preview(s: &mut Session, on: bool) {
|
|
run(s, "prefs.set", json!({"key": "usePreviewBounds", "value": on}));
|
|
}
|
|
|
|
/// Two rectangles: one with a 10 pt stroke at x = 100, an unstroked one at x = 20; align left.
|
|
fn aligned_left(on: bool, p: Value) -> f64 {
|
|
let mut s = session();
|
|
let a = rect(&mut s, 100.0, 50.0, 10.0);
|
|
rect(&mut s, 20.0, 50.0, 0.0);
|
|
preview(&mut s, on);
|
|
run(&mut s, "select.all", json!({}));
|
|
let mut p = p;
|
|
p["horizontal"] = json!("left");
|
|
run(&mut s, "object.align", p);
|
|
geometric(&s, a).x0
|
|
}
|
|
|
|
#[test]
|
|
fn align_left_differs_by_half_the_stroke() {
|
|
assert_eq!(aligned_left(false, json!({})), 20.0);
|
|
// The 10 pt stroke's outer edge lines up with the other object: the path sits 5 pt in.
|
|
assert_eq!(aligned_left(true, json!({})), 25.0);
|
|
// `bounds` overrides the preference either way.
|
|
assert_eq!(aligned_left(true, json!({"bounds": "geometric"})), 20.0);
|
|
assert_eq!(aligned_left(false, json!({"bounds": "preview"})), 25.0);
|
|
let mut s = session();
|
|
rect(&mut s, 0.0, 10.0, 0.0);
|
|
assert!(s.execute("object.align", &json!({"horizontal": "left", "bounds": "visual"})).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn distribute_spacing_measures_preview_bounds() {
|
|
let mut s = session();
|
|
let a = rect(&mut s, 0.0, 50.0, 10.0);
|
|
let b = rect(&mut s, 200.0, 50.0, 10.0);
|
|
preview(&mut s, true);
|
|
run(&mut s, "select.set", json!({"ids": [a.0, b.0]}));
|
|
run(&mut s, "object.distributeSpacing", json!({"axis": "horizontal", "spacing": 0}));
|
|
// The strokes touch: the paths are a stroke weight apart.
|
|
assert_eq!(geometric(&s, b).x0 - geometric(&s, a).x1, 10.0);
|
|
}
|
|
|
|
#[test]
|
|
fn the_w_field_sets_the_visual_width() {
|
|
let mut s = session();
|
|
let id = rect(&mut s, 100.0, 100.0, 10.0);
|
|
preview(&mut s, true);
|
|
assert_eq!(s.transform_bounds(&[id]).unwrap().width(), 110.0);
|
|
// Strokes scale: the whole visual box doubles, from its left edge.
|
|
run(&mut s, "object.setBounds", json!({"width": 220, "reference": 0, "proportional": true, "strokes": true}));
|
|
let v = s.transform_bounds(&[id]).unwrap();
|
|
assert!((v.width() - 220.0).abs() < 1e-9 && (v.x0 - 95.0).abs() < 1e-9, "{v:?}");
|
|
assert!((geometric(&s, id).width() - 200.0).abs() < 1e-9);
|
|
run(&mut s, "edit.undo", json!({}));
|
|
// Strokes kept: the path takes the difference.
|
|
run(&mut s, "object.setBounds", json!({"width": 220, "reference": 0, "strokes": false}));
|
|
let v = s.transform_bounds(&[id]).unwrap();
|
|
assert!((v.width() - 220.0).abs() < 1e-9 && (v.x0 - 95.0).abs() < 1e-9, "{v:?}");
|
|
assert!((geometric(&s, id).width() - 210.0).abs() < 1e-9);
|
|
// X places the visual box's reference point.
|
|
run(&mut s, "object.setBounds", json!({"x": 0, "reference": 0}));
|
|
assert!((s.transform_bounds(&[id]).unwrap().x0).abs() < 1e-9);
|
|
assert!((geometric(&s, id).x0 - 5.0).abs() < 1e-9);
|
|
}
|
|
|
|
#[test]
|
|
fn the_bounding_box_handles_sit_on_the_preview_bounds() {
|
|
// Drag from the visual right edge (10 pt outside the path) 100 pt to the right.
|
|
let drag = |on: bool| {
|
|
let mut s = session();
|
|
let id = rect(&mut s, 100.0, 100.0, 20.0);
|
|
preview(&mut s, on);
|
|
let v = ViewInfo::default();
|
|
s.select_tool("selection", v).unwrap();
|
|
for (k, x) in [(PointerKind::Down, 210.0), (PointerKind::Drag, 310.0), (PointerKind::Up, 310.0)] {
|
|
s.pointer(&PointerEvent::new(k, x, 125.0), v).unwrap();
|
|
}
|
|
geometric(&s, id)
|
|
};
|
|
assert!(drag(true).width() > 150.0, "a scale from the visual edge");
|
|
assert_eq!(drag(false).width(), 100.0, "off, the point is no handle");
|
|
}
|