mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-02 06:07:06 -06:00
The reference app keeps the inner radius of a star constant when Ctrl or Command is held after the drag starts. VectorCraft's Star tool took the held radius from the first drag event that carried the key, using that event's outer radius. The canvas sends a drag only once the pointer moves, so a key pressed with the pointer still scaled the inner radius with the next move before holding it. Ctrl/Cmd now holds the inner radius the star showed at the last drag position before the key went down, with Alt's straight shoulders when Alt was held. A new press and Escape clear the held radius. Before, after a drag canceled with Escape, the next star took the canceled star's inner radius when Ctrl/Cmd went down before that star's first drag event. Docs: docs/target-app-parity.md.
356 lines
19 KiB
Rust
356 lines
19 KiB
Rust
use super::*;
|
||
use crate::testutil::*;
|
||
use vectorcraft_doc::Selection;
|
||
|
||
#[test]
|
||
fn rect_drag() {
|
||
let (d, _) = doc_with_rect();
|
||
let s = Selection::default();
|
||
let p = paint();
|
||
let cx = cx(&d, &s, &p);
|
||
let mut t = ShapeTool::new("rectangle");
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 10.0, 10.0));
|
||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 50.0, 30.0));
|
||
assert_eq!(a[0], Action::Begin("Rectangle".into()));
|
||
assert_eq!(a[1], Action::Preview("shape.rectangle".into(), json!({"x": 10.0, "y": 10.0, "width": 40.0, "height": 20.0})));
|
||
assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 50.0, 30.0)), vec![Action::Commit]);
|
||
}
|
||
|
||
#[test]
|
||
fn space_moves_the_shape_being_drawn() {
|
||
let (d, _) = doc_with_rect();
|
||
let s = Selection::default();
|
||
let p = paint();
|
||
let cx = cx(&d, &s, &p);
|
||
let space = Mods { space: true, ..Default::default() };
|
||
let mut t = ShapeTool::new("rectangle");
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 50.0, 50.0));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 150.0, 120.0));
|
||
// Space held: the 100 × 70 rectangle follows the pointer.
|
||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 250.0, 170.0).with_mods(space));
|
||
assert_eq!(a.last(), Some(&Action::Preview("shape.rectangle".into(), json!({"x": 150.0, "y": 100.0, "width": 100.0, "height": 70.0}))));
|
||
// Let go of Space: it grows again, from where it was moved to.
|
||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 270.0, 190.0));
|
||
assert_eq!(a.last(), Some(&Action::Preview("shape.rectangle".into(), json!({"x": 150.0, "y": 100.0, "width": 120.0, "height": 90.0}))));
|
||
assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 270.0, 190.0)), vec![Action::Commit]);
|
||
// A star (drawn from its centre) moves its centre.
|
||
let mut t = ShapeTool::new("star");
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 50.0, 50.0));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 80.0, 50.0));
|
||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 90.0, 60.0).with_mods(space));
|
||
assert!(matches!(a.last(), Some(Action::Preview(_, v)) if v["cx"] == 60.0 && v["cy"] == 60.0), "{a:?}");
|
||
}
|
||
|
||
#[test]
|
||
fn star_drag_measure_label_shows_outer_and_inner_radii() {
|
||
let (d, _) = doc_with_rect();
|
||
let s = Selection::default();
|
||
let p = paint();
|
||
let cx = ToolContext { measurement_labels: true, smart_guides: false, ..cx(&d, &s, &p) };
|
||
let mut t = ShapeTool::new("star");
|
||
t.star_ratio = 0.5;
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 10.0, 10.0));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 50.0, 10.0));
|
||
let want = cx.star_radii_label(40.0, 20.0);
|
||
let label = t
|
||
.overlays(&cx)
|
||
.into_iter()
|
||
.find_map(|o| match o {
|
||
Overlay::Measure { text, .. } => Some(text),
|
||
_ => None,
|
||
})
|
||
.unwrap_or_default();
|
||
assert_eq!(label, want, "{label}");
|
||
}
|
||
|
||
/// The star label shows the radii the preview draws, also with Ctrl/Cmd + Alt and when a
|
||
/// Ctrl/Cmd drag shrinks the outer radius below the held inner one (the ratio is clamped).
|
||
#[test]
|
||
fn star_measure_label_matches_the_preview_with_modifiers_and_shrink() {
|
||
let (d, _) = doc_with_rect();
|
||
let s = Selection::default();
|
||
let p = paint();
|
||
let cx = ToolContext { measurement_labels: true, smart_guides: false, ..cx(&d, &s, &p) };
|
||
let label = |t: &ShapeTool| {
|
||
t.overlays(&cx)
|
||
.into_iter()
|
||
.find_map(|o| match o {
|
||
Overlay::Measure { text, .. } => Some(text),
|
||
_ => None,
|
||
})
|
||
.unwrap_or_default()
|
||
};
|
||
let radii = |a: &[Action]| match a.last() {
|
||
Some(Action::Preview(_, v)) => (v["radius1"].as_f64().unwrap(), v["radius2"].as_f64().unwrap()),
|
||
other => panic!("{other:?}"),
|
||
};
|
||
let cmd = Mods { cmd: true, ..Default::default() };
|
||
let cmd_alt = Mods { cmd: true, alt: true, ..Default::default() };
|
||
let mut t = ShapeTool::new("star");
|
||
t.star_ratio = 0.5;
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 10.0, 10.0));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 50.0, 10.0));
|
||
// Ctrl/Cmd holds the 20 inner radius while the outer one grows.
|
||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 70.0, 10.0).with_mods(cmd));
|
||
let (r1, r2) = radii(&a);
|
||
assert_eq!(label(&t), cx.star_radii_label(r1, r2));
|
||
// Ctrl/Cmd + Alt: the preview straightens the shoulders, and so does the label.
|
||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 70.0, 10.0).with_mods(cmd_alt));
|
||
let (r1, r2) = radii(&a);
|
||
assert!((r2 - 20.0).abs() > 1e-6, "Alt should override the held radius: {r2}");
|
||
assert_eq!(label(&t), cx.star_radii_label(r1, r2));
|
||
// Shrinking below the held inner radius clamps the ratio; the label follows the preview.
|
||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 20.0, 10.0).with_mods(cmd));
|
||
let (r1, r2) = radii(&a);
|
||
assert!(r2 <= r1 + 1e-9, "{r1} {r2}");
|
||
assert_eq!(label(&t), cx.star_radii_label(r1, r2));
|
||
}
|
||
|
||
/// During a star drag, Ctrl/Cmd keeps the inner radius the star showed when the key went down
|
||
/// while the outer one follows the pointer (the new ratio stays once the key is let go), and Alt
|
||
/// keeps the shoulders straight (#812). The canvas sends a drag only once the pointer moves, so
|
||
/// the key first arrives with a new position.
|
||
#[test]
|
||
fn a_star_drag_holds_its_inner_radius_with_ctrl_and_straightens_with_alt() {
|
||
let (d, _) = doc_with_rect();
|
||
let s = Selection::default();
|
||
let p = paint();
|
||
let cx = cx(&d, &s, &p);
|
||
let radii = |a: &[Action]| match a.last() {
|
||
Some(Action::Preview(_, v)) => (v["radius1"].as_f64().unwrap(), v["radius2"].as_f64().unwrap()),
|
||
other => panic!("{other:?}"),
|
||
};
|
||
let ctrl = Mods { cmd: true, ..Default::default() };
|
||
let mut t = ShapeTool::new("star");
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 50.0, 50.0));
|
||
let (r1, r2) = radii(&t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 90.0, 50.0)));
|
||
assert_eq!((r1, r2), (40.0, 20.0));
|
||
// Ctrl, pressed with the pointer still: the inner radius stays 20 as the outer one grows to 80.
|
||
let (r1, r2) = radii(&t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 130.0, 50.0).with_mods(ctrl)));
|
||
assert!((r1 - 80.0).abs() < 1e-9 && (r2 - 20.0).abs() < 1e-9, "{r1} {r2}");
|
||
// Let go: the ratio (a quarter) stays.
|
||
let (r1, r2) = radii(&t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 150.0, 50.0)));
|
||
assert!((r2 / r1 - 0.25).abs() < 1e-9, "{r1} {r2}");
|
||
// Alt: straight shoulders, about 0.382 for five points.
|
||
let alt = Mods { alt: true, ..Default::default() };
|
||
let (r1, r2) = radii(&t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 150.0, 50.0).with_mods(alt)));
|
||
assert!((r2 / r1 - 0.381_966).abs() < 1e-5, "{}", r2 / r1);
|
||
assert_eq!(straight_ratio(4), None, "four points can't line up");
|
||
assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 150.0, 50.0)), vec![Action::Commit]);
|
||
}
|
||
|
||
/// Ctrl/Cmd pressed while Alt straightens the shoulders keeps the straightened inner radius, a drag
|
||
/// canceled with Escape leaves no held radius for the next star, and Ctrl/Cmd pressed inside the
|
||
/// drag threshold keeps the inner radius of the first star the drag shows. The canvas lends a
|
||
/// selection tool to a press with Ctrl/Cmd held, so every press here is without it.
|
||
#[test]
|
||
fn a_star_drag_holds_the_inner_radius_it_shows_when_ctrl_goes_down() {
|
||
let (d, _) = doc_with_rect();
|
||
let s = Selection::default();
|
||
let p = paint();
|
||
let cx = cx(&d, &s, &p);
|
||
let radii = |a: &[Action]| match a.last() {
|
||
Some(Action::Preview(_, v)) => (v["radius1"].as_f64().unwrap(), v["radius2"].as_f64().unwrap()),
|
||
other => panic!("{other:?}"),
|
||
};
|
||
let close = |(r1, r2): (f64, f64), (w1, w2): (f64, f64)| assert!((r1 - w1).abs() < 1e-9 && (r2 - w2).abs() < 1e-9, "{r1} {r2}, not {w1} {w2}");
|
||
let ctrl = Mods { cmd: true, ..Default::default() };
|
||
let alt = Mods { alt: true, ..Default::default() };
|
||
let ctrl_alt = Mods { cmd: true, alt: true, ..Default::default() };
|
||
// Alt shows straight shoulders; Ctrl/Cmd pressed then keeps that inner radius.
|
||
let mut t = ShapeTool::new("star");
|
||
let straight = straight_ratio(5).unwrap_or(0.0);
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 50.0, 50.0));
|
||
close(radii(&t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 90.0, 50.0).with_mods(alt))), (40.0, 40.0 * straight));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 130.0, 50.0).with_mods(ctrl_alt));
|
||
close(radii(&t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 170.0, 50.0).with_mods(ctrl))), (120.0, 40.0 * straight));
|
||
|
||
// Escape with the key held, then a new press and the key before the first move: the next star
|
||
// holds what it shows, not the canceled radius.
|
||
let mut t = ShapeTool::new("star");
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 50.0, 50.0));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 90.0, 50.0));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 130.0, 50.0).with_mods(ctrl));
|
||
assert_eq!(t.key(&cx, ToolKey::Escape, ctrl), vec![Action::Cancel]);
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 50.0, 50.0));
|
||
close(radii(&t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 60.0, 50.0).with_mods(ctrl))), (10.0, 2.5));
|
||
close(radii(&t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 70.0, 50.0).with_mods(ctrl))), (20.0, 2.5));
|
||
|
||
// Ctrl/Cmd pressed inside the drag threshold, where no star is shown yet.
|
||
let mut t = ShapeTool::new("star");
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 50.0, 50.0));
|
||
assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 51.0, 50.0)), vec![]);
|
||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 90.0, 50.0).with_mods(ctrl));
|
||
assert_eq!(a[0], Action::Begin("Star".into()));
|
||
close(radii(&a), (40.0, 20.0));
|
||
close(radii(&t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 130.0, 50.0).with_mods(ctrl))), (80.0, 20.0));
|
||
assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 130.0, 50.0).with_mods(ctrl)), vec![Action::Commit]);
|
||
}
|
||
|
||
#[test]
|
||
fn shift_square_alt_center() {
|
||
let t = ShapeTool::new("ellipse");
|
||
let (c, v) = t.command(Point::new(0.0, 0.0), Point::new(10.0, 4.0), Mods { shift: true, alt: true, ..Default::default() });
|
||
assert_eq!(c, "shape.ellipse");
|
||
assert_eq!(v, json!({"x": -10.0, "y": -10.0, "width": 20.0, "height": 20.0}));
|
||
}
|
||
|
||
/// General › Constrain Angle: a Shift-drag squares along the turned axes, and the shape turns
|
||
/// by the angle about its centre.
|
||
#[test]
|
||
fn constrain_angle_squares_along_the_turned_axes() {
|
||
let t = ShapeTool::new("rectangle");
|
||
let shift = Mods { shift: true, ..Default::default() };
|
||
// 45°: the drag straight right is a diagonal of the turned axes, so a square on it.
|
||
let (_, v) = t.command_at(Point::new(0.0, 0.0), Point::new(20.0, 0.0), shift, 45.0);
|
||
let side = 20.0 / 2f64.sqrt();
|
||
let near = |k: &str, want: f64| (v[k].as_f64().unwrap() - want).abs() < 1e-9;
|
||
assert!(near("width", side) && near("height", side) && near("rotation", -45.0), "{v}");
|
||
assert!(near("x", 10.0 - side / 2.0) && near("y", -side / 2.0), "centred on the drag's middle: {v}");
|
||
// Whole turns are the page's axes exactly: the same box as 0°, unturned.
|
||
let (a, b) = (Point::new(3.0, 4.0), Point::new(41.0, 17.0));
|
||
let flat = t.command_at(a, b, Mods::default(), 0.0);
|
||
for turn in [360.0, -360.0, 720.0] {
|
||
assert_eq!(t.command_at(a, b, Mods::default(), turn), flat, "{turn}°");
|
||
}
|
||
// A quarter turn is exact (no floating-point noise in the box).
|
||
let (_, v) = t.command_at(Point::new(0.0, 0.0), Point::new(20.0, 10.0), Mods::default(), 90.0);
|
||
assert_eq!((v["width"].as_f64(), v["height"].as_f64(), v["rotation"].as_f64()), (Some(10.0), Some(20.0), Some(-90.0)), "{v}");
|
||
let (_, w) = t.command_at(Point::new(0.0, 0.0), Point::new(20.0, 10.0), Mods::default(), 450.0);
|
||
assert_eq!(v, w, "450° is 90°");
|
||
}
|
||
|
||
/// The size label gives the box as drawn: measured along the Constrain Angle's axes.
|
||
#[test]
|
||
fn constrain_angle_size_label_measures_the_turned_box() {
|
||
let (d, _) = doc_with_rect();
|
||
let s = Selection::default();
|
||
let p = paint();
|
||
let mut cx = cx(&d, &s, &p);
|
||
cx.constrain_angle = 45.0;
|
||
let mut t = ShapeTool::new("rectangle");
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 300.0, 300.0));
|
||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 340.0, 300.0));
|
||
let Some(Action::Preview(_, v)) = a.last() else { panic!("{a:?}") };
|
||
let (w, h) = (v["width"].as_f64().unwrap(), v["height"].as_f64().unwrap());
|
||
assert!((w - h).abs() < 1e-9 && (w - 40.0 / 2f64.sqrt()).abs() < 1e-9, "{v}");
|
||
let o = t.overlays(&cx);
|
||
assert!(o.iter().any(|o| matches!(o, Overlay::Measure { text, .. } if text == &cx.size_label(w, h))), "{o:?}");
|
||
assert!(!o.iter().any(|o| matches!(o, Overlay::Measure { text, .. } if text == &cx.size_label(40.0, 0.0))), "not the page box");
|
||
}
|
||
|
||
#[test]
|
||
fn click_opens_dialog() {
|
||
let (d, _) = doc_with_rect();
|
||
let s = Selection::default();
|
||
let p = paint();
|
||
let cx = cx(&d, &s, &p);
|
||
let mut t = ShapeTool::new("star");
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 10.0, 10.0));
|
||
assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 10.0, 10.0)), vec![Action::Dialog("star".into(), json!({"x": 10.0, "y": 10.0}))]);
|
||
}
|
||
|
||
#[test]
|
||
fn alt_click_passes_draw_from_centre_to_the_dialog() {
|
||
let (d, _) = doc_with_rect();
|
||
let s = Selection::default();
|
||
let p = paint();
|
||
let cx = cx(&d, &s, &p);
|
||
let alt = Mods { alt: true, ..Default::default() };
|
||
let mut t = ShapeTool::new("rectangle");
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 10.0, 10.0).with_mods(alt));
|
||
assert_eq!(
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 10.0, 10.0).with_mods(alt)),
|
||
vec![Action::Dialog("rectangle".into(), json!({"x": 10.0, "y": 10.0, "alt": true}))]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn arrow_keys_change_sides() {
|
||
let (d, _) = doc_with_rect();
|
||
let s = Selection::default();
|
||
let p = paint();
|
||
let cx = cx(&d, &s, &p);
|
||
let mut t = ShapeTool::new("polygon");
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 10.0, 10.0));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 50.0, 10.0));
|
||
let a = t.key(&cx, ToolKey::Up, Mods::default());
|
||
assert!(matches!(&a[0], Action::Preview(_, v) if v["sides"] == 7));
|
||
}
|
||
|
||
/// The Rounded Rectangle radius stops at General › Corner Radius's range on both of its input
|
||
/// paths: a Tool Options value is clamped to it, and ↑ at the top stays there.
|
||
#[test]
|
||
fn rounded_rectangle_radius_is_clamped_to_its_range() {
|
||
let (d, _) = doc_with_rect();
|
||
let s = Selection::default();
|
||
let p = paint();
|
||
let cx = cx(&d, &s, &p);
|
||
let mut t = ShapeTool::new("roundedRectangle");
|
||
t.set_option("cornerRadius", &json!(5000));
|
||
assert_eq!(t.options()["cornerRadius"], json!(MAX_CORNER_RADIUS));
|
||
t.set_option("cornerRadius", &json!(f64::INFINITY));
|
||
assert_eq!(t.options()["cornerRadius"], json!(12.0), "a non-finite value is the default");
|
||
t.set_option("cornerRadius", &json!(-3));
|
||
assert_eq!(t.options()["cornerRadius"], json!(0.0));
|
||
t.set_option("cornerRadius", &json!(MAX_CORNER_RADIUS));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 10.0, 10.0));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 50.0, 40.0));
|
||
let a = t.key(&cx, ToolKey::Up, Mods::default());
|
||
assert!(matches!(&a[0], Action::Preview(_, v) if v["radius"] == json!(MAX_CORNER_RADIUS)), "{a:?}");
|
||
assert_eq!(t.options()["cornerRadius"], json!(MAX_CORNER_RADIUS));
|
||
}
|
||
|
||
/// The start and the dragged corner land on another object's anchors (#506), hovering shows
|
||
/// it before the press, and the measurement label gives the size drawn.
|
||
#[test]
|
||
fn corners_snap_to_smart_guides() {
|
||
let (d, _) = doc_with_rect();
|
||
let s = Selection::default();
|
||
let p = paint();
|
||
let cx = cx(&d, &s, &p);
|
||
let mut t = ShapeTool::new("rectangle");
|
||
let anchor = |o: &[Overlay], at: Point| o.iter().any(|o| matches!(o, Overlay::Label { text, p, .. } if text == "anchor" && *p == at));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Move, 98.0, 102.0));
|
||
assert!(anchor(&t.overlays(&cx), Point::new(100.0, 100.0)));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 98.0, 102.0));
|
||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 198.0, 202.0));
|
||
assert_eq!(a[1], Action::Preview("shape.rectangle".into(), json!({"x": 100.0, "y": 100.0, "width": 100.0, "height": 100.0})));
|
||
let o = t.overlays(&cx);
|
||
assert!(anchor(&o, Point::new(200.0, 200.0)), "{o:?}");
|
||
assert!(o.iter().any(|o| matches!(o, Overlay::Measure { text, .. } if text == &cx.size_label(100.0, 100.0))));
|
||
// Alt draws from the centre: the label gives the whole size.
|
||
let alt = Mods { alt: true, ..Mods::default() };
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 198.0, 202.0).with_mods(alt));
|
||
assert!(t.overlays(&cx).iter().any(|o| matches!(o, Overlay::Measure { text, .. } if text == &cx.size_label(200.0, 200.0))));
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 198.0, 202.0));
|
||
assert!(t.overlays(&cx).is_empty());
|
||
// A line with Shift keeps to 45° steps, sliding into line with the rect's right side.
|
||
let mut t = ShapeTool::new("lineSegment");
|
||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 300.0, 300.0));
|
||
let shift = Mods { shift: true, ..Mods::default() };
|
||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 203.0, 302.0).with_mods(shift));
|
||
assert!(matches!(&a[..], [_, Action::Preview(_, v)] if v["x2"] == 200.0 && (v["y2"].as_f64().unwrap() - 300.0).abs() < 1e-9), "{a:?}");
|
||
let from = Point::new(300.0, 300.0);
|
||
let to = Point::new(200.0, 300.0);
|
||
assert!(t.overlays(&cx).iter().any(|o| matches!(o, Overlay::Measure { text, .. } if text == &cx.segment_measure_label(from, to))));
|
||
}
|
||
|
||
/// Line Segment Tool Options: Length and Angle are options (for a click's dialog), and Fill
|
||
/// Line makes the drag's line take the current fill.
|
||
#[test]
|
||
fn line_segment_options_and_fill_line() {
|
||
let mut t = ShapeTool::new("lineSegment");
|
||
let (a, b) = (Point::new(0.0, 0.0), Point::new(30.0, 40.0));
|
||
assert!(t.command(a, b, Mods::default()).1.get("fill").is_none());
|
||
for (k, v) in [("length", json!(-80.0)), ("angle", json!(-90.0)), ("fillLine", json!(true))] {
|
||
t.set_option(k, &v);
|
||
}
|
||
t.set_option("length", &json!(f64::NAN));
|
||
assert_eq!(
|
||
(t.options()["length"].clone(), t.options()["angle"].clone(), t.options()["fillLine"].clone()),
|
||
(json!(80.0), json!(270.0), json!(true))
|
||
);
|
||
assert_eq!(t.command(a, b, Mods::default()).1["fill"], json!(true));
|
||
}
|