mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 09:29:44 -06:00
Merge pull request #5277 from dotCooCoo/fix/measure-shift-snap
Measure: a Shift-drag snaps its end point on its 45° step
This commit is contained in:
@@ -95,12 +95,7 @@ impl Tool for MeasureInspectTool {
|
||||
}
|
||||
PointerKind::Drag | PointerKind::Up if self.dragging => {
|
||||
if let Some((a, _)) = self.line {
|
||||
let b = if ev.mods.shift {
|
||||
a + vectorcraft_geom::constrain_angle_from(ev.pos - a, 45.0, cx.constrain_angle)
|
||||
} else {
|
||||
crate::guides::snap_draw(cx, ev.pos, &[]).0
|
||||
};
|
||||
self.line = Some((a, b));
|
||||
self.line = Some((a, crate::xform::measure_end_point(cx, a, ev.pos, ev.mods.shift)));
|
||||
}
|
||||
self.dragging = ev.kind == PointerKind::Drag;
|
||||
}
|
||||
@@ -213,4 +208,23 @@ mod tests {
|
||||
assert_eq!(t.options(), Value::Null);
|
||||
assert_eq!(area_label(Unit::Points, 2.0), "2.00 pt²");
|
||||
}
|
||||
|
||||
/// A Shift-drag snaps its end point to the grid, then projects it onto its 45° step.
|
||||
#[test]
|
||||
fn shift_drag_snaps_its_end_point() {
|
||||
let (d, _) = doc_with_rect();
|
||||
let (s, p) = (Selection::default(), paint());
|
||||
let mut cx = cx(&d, &s, &p);
|
||||
cx.smart_guides = false;
|
||||
cx.snap_to_grid = true;
|
||||
let shift = Mods { shift: true, ..Mods::default() };
|
||||
let mut t = MeasureInspectTool::default();
|
||||
// (37.3, 12.1) snaps to (36, 9) on the 9 pt grid, and Shift keeps it at 0°: (36, 0).
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 37.3, 12.1).with_mods(shift));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 37.3, 12.1).with_mods(shift));
|
||||
let o = t.options();
|
||||
assert!((o["distance"].as_f64().unwrap() - 36.0).abs() < 1e-9, "on the 9 pt grid: {o}");
|
||||
assert!(o["dy"].as_f64().unwrap().abs() < 1e-9, "kept horizontal: {o}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ pub use artboard::ArtboardTool;
|
||||
pub use eyedropper::EyedropperTool;
|
||||
pub use free::{DistortMode, FreeTransformTool};
|
||||
pub use gradient::GradientTool;
|
||||
pub(crate) use measure::end_point as measure_end_point;
|
||||
pub use measure::{MeasureTool, measure};
|
||||
pub use transform::{TransformKind, TransformTool};
|
||||
pub use wand::{LassoTool, MagicWandTool};
|
||||
|
||||
@@ -23,6 +23,28 @@ pub fn measure(a: Point, b: Point) -> (f64, f64, f64, f64) {
|
||||
(d.hypot(), (-d.y).atan2(d.x).to_degrees() + 0.0, d.x, d.y)
|
||||
}
|
||||
|
||||
/// The end of a measurement from `a` with the pointer at `p`: `p` snapped as a drawing tool snaps
|
||||
/// it. With Shift held, the end stays on the 45° step from the Constrain Angle nearest the
|
||||
/// pointer's direction: `p` is projected onto the step, then snapped along it as the Line Segment
|
||||
/// tool snaps a Shift-drawn end. With Snap to Point alone, the point `p` snaps to (an anchor, or
|
||||
/// in line with a ruler guide) is projected instead.
|
||||
pub(crate) fn end_point(cx: &ToolContext, a: Point, p: Point, shift: bool) -> Point {
|
||||
if !shift {
|
||||
return crate::guides::snap_draw(cx, p, &[]).0;
|
||||
}
|
||||
let d = vectorcraft_geom::constrain_angle_from(p - a, 45.0, cx.constrain_angle);
|
||||
let len = d.hypot();
|
||||
if len < 1e-12 {
|
||||
return a;
|
||||
}
|
||||
let u = d / len;
|
||||
let to_point = cx.snap_to_point && !(cx.smart_guides || cx.snap_to_grid || cx.snap_to_pixel);
|
||||
let s = if to_point { crate::guides::snap_draw(cx, p, &[]).0 } else { p };
|
||||
let q = a + u * (s - a).dot(u);
|
||||
let leave = crate::guides::Leave::segment(cx, a, true);
|
||||
crate::guides::PointSnap::new(cx, || crate::guides::Targets::collect(cx.doc, &[], None)).snap_from(cx, q, Some(&leave)).0
|
||||
}
|
||||
|
||||
impl Tool for MeasureTool {
|
||||
fn id(&self) -> &'static str {
|
||||
"measure"
|
||||
@@ -42,12 +64,7 @@ impl Tool for MeasureTool {
|
||||
}
|
||||
PointerKind::Drag | PointerKind::Up if self.dragging => {
|
||||
if let Some((a, _)) = self.line {
|
||||
let b = if ev.mods.shift {
|
||||
a + vectorcraft_geom::constrain_angle_from(ev.pos - a, 45.0, cx.constrain_angle)
|
||||
} else {
|
||||
crate::guides::snap_draw(cx, ev.pos, &[]).0
|
||||
};
|
||||
self.line = Some((a, b));
|
||||
self.line = Some((a, end_point(cx, a, ev.pos, ev.mods.shift)));
|
||||
}
|
||||
self.dragging = ev.kind == PointerKind::Drag;
|
||||
}
|
||||
@@ -141,6 +158,89 @@ mod tests {
|
||||
assert!(ov.iter().any(|o| matches!(o, Overlay::Line { .. })), "the measured line stays");
|
||||
}
|
||||
|
||||
/// A Shift-drag snaps its end point as a drag without Shift does, then projects it onto its
|
||||
/// 45° step: onto the grid with Snap to Grid, in line with an anchor with Smart Guides. With
|
||||
/// no snapping, the end is the pointer projected onto the step.
|
||||
#[test]
|
||||
fn shift_drag_snaps_its_end_point() {
|
||||
let (d, _) = doc_with_rect();
|
||||
let s = Selection::default();
|
||||
let p = paint();
|
||||
let mut cx = cx(&d, &s, &p);
|
||||
cx.smart_guides = false;
|
||||
cx.snap_to_grid = true;
|
||||
let shift = Mods { shift: true, ..Mods::default() };
|
||||
let mut t = MeasureTool::default();
|
||||
// (37.3, 12.1) snaps to (36, 9) on the 9 pt grid, and Shift keeps it at 0°: (36, 0).
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 37.3, 12.1).with_mods(shift));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 37.3, 12.1).with_mods(shift));
|
||||
let o = t.options();
|
||||
assert!((o["distance"].as_f64().unwrap() - 36.0).abs() < 1e-9, "on the 9 pt grid: {o}");
|
||||
assert!(o["dy"].as_f64().unwrap().abs() < 1e-9, "kept horizontal: {o}");
|
||||
// Near 45°: (31.4, 27.7) snaps to (27, 27).
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 31.4, 27.7).with_mods(shift));
|
||||
let o = t.options();
|
||||
assert!((o["dx"].as_f64().unwrap() - 27.0).abs() < 1e-9, "{o}");
|
||||
assert!((o["dy"].as_f64().unwrap() - 27.0).abs() < 1e-9, "{o}");
|
||||
// No snapping: the pointer is projected onto 0°, so the measurement is 37.3 pt.
|
||||
cx.snap_to_grid = false;
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 37.3, 12.1).with_mods(shift));
|
||||
let o = t.options();
|
||||
assert!((o["distance"].as_f64().unwrap() - 37.3).abs() < 1e-9, "{o}");
|
||||
assert!(o["dy"].as_f64().unwrap().abs() < 1e-9, "{o}");
|
||||
// Snap to Point alone: the end snaps to the anchor at (200, 200) and is projected onto
|
||||
// 0°, so it ends in line with it: (200, 203).
|
||||
assert!(cx.snap_to_point);
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 300.0, 203.0));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 201.0, 201.0).with_mods(shift));
|
||||
let o = t.options();
|
||||
assert!((o["dx"].as_f64().unwrap() + 100.0).abs() < 1e-9, "{o}");
|
||||
assert!(o["dy"].as_f64().unwrap().abs() < 1e-9, "{o}");
|
||||
// An anchor on the 45° step: the end lands on it.
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 101.0, 100.0).with_mods(shift));
|
||||
let o = t.options();
|
||||
assert!((o["dx"].as_f64().unwrap() - 100.0).abs() < 1e-9, "{o}");
|
||||
assert!((o["dy"].as_f64().unwrap() - 100.0).abs() < 1e-9, "{o}");
|
||||
// Smart Guides: from (300, 300), Shift keeps the end at 0° and it lands in line with x = 200.
|
||||
cx.snap_to_grid = false;
|
||||
cx.smart_guides = true;
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 300.0, 300.0));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 203.0, 303.0).with_mods(shift));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 203.0, 303.0).with_mods(shift));
|
||||
let o = t.options();
|
||||
assert!((o["distance"].as_f64().unwrap() - 100.0).abs() < 1e-9, "{o}");
|
||||
assert!(o["dy"].as_f64().unwrap().abs() < 1e-9, "{o}");
|
||||
// At 45°, the end slides along the step to where it meets x = 200: (200, 220).
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 300.0, 320.0));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 203.0, 222.5).with_mods(shift));
|
||||
let o = t.options();
|
||||
assert!((o["dx"].as_f64().unwrap() + 100.0).abs() < 1e-9, "{o}");
|
||||
assert!((o["dy"].as_f64().unwrap() + 100.0).abs() < 1e-9, "{o}");
|
||||
}
|
||||
|
||||
/// With Snap to Point alone, a Shift-drag's end slides along its 45° step to a ruler guide.
|
||||
#[test]
|
||||
fn shift_drag_meets_a_ruler_guide() {
|
||||
let (mut d, _) = doc_with_rect();
|
||||
d.guides.push(vectorcraft_doc::Guide::new(true, 50.0));
|
||||
let s = Selection::default();
|
||||
let p = paint();
|
||||
let mut cx = cx(&d, &s, &p);
|
||||
cx.smart_guides = false;
|
||||
assert!(cx.snap_to_point);
|
||||
let shift = Mods { shift: true, ..Mods::default() };
|
||||
let mut t = MeasureTool::default();
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 51.0, 49.0).with_mods(shift));
|
||||
let o = t.options();
|
||||
assert!((o["dx"].as_f64().unwrap() - 50.0).abs() < 1e-9, "{o}");
|
||||
assert!((o["dy"].as_f64().unwrap() - 50.0).abs() < 1e-9, "{o}");
|
||||
}
|
||||
|
||||
/// Escape during a drag ends it and brings back the result of the drag before (none at
|
||||
/// first); the release after it measures nothing.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user