From 5bf159d5d1ff5cbe77ef8fa9b581e6b8e7c5d702 Mon Sep 17 00:00:00 2001 From: nrq6993-a11y Date: Mon, 19 Oct 2026 00:29:04 +0300 Subject: [PATCH] G1: show distance and angle on Pen, Line and Curvature rubber bands Measurement labels while drawing now match the Measure tool (D, angle, dX/dY) when the preference is on. Co-authored-by: Cursor --- crates/tools/src/draw2/curvature.rs | 21 ++++++++++++++++++++- crates/tools/src/lib.rs | 5 +++++ crates/tools/src/pen.rs | 11 ++++++++++- crates/tools/src/shape.rs | 27 ++++++++++++++++++--------- crates/tools/src/xform.rs | 2 +- 5 files changed, 54 insertions(+), 12 deletions(-) diff --git a/crates/tools/src/draw2/curvature.rs b/crates/tools/src/draw2/curvature.rs index 7394c467d..93d65e074 100644 --- a/crates/tools/src/draw2/curvature.rs +++ b/crates/tools/src/draw2/curvature.rs @@ -598,6 +598,12 @@ impl Tool for CurvatureTool { curvature_extend(&mut tail, false, h, self.how(&e), false); tail.to_bezpath_into(&mut bp); o.push(Overlay::Path { path: bp, color: FEEDBACK, width: 1.0, dashed: false }); + if cx.measurement_labels && e.p.distance(h) > 1e-9 { + o.push(Overlay::Measure { + p: h + Vec2::new(cx.tol(12.0), cx.tol(12.0)), + text: cx.segment_measure_label(e.p, h), + }); + } } return o; } @@ -609,6 +615,15 @@ impl Tool for CurvatureTool { pts.push((h, false)); curvature_spline(&pts, false).to_bezpath_into(&mut bp); o.push(Overlay::Path { path: bp, color: FEEDBACK, width: 1.0, dashed: false }); + if let Some((from, _)) = self.pts.last() + && cx.measurement_labels + && from.distance(h) > 1e-9 + { + o.push(Overlay::Measure { + p: h + Vec2::new(cx.tol(12.0), cx.tol(12.0)), + text: cx.segment_measure_label(*from, h), + }); + } } for (i, (p, _)) in self.pts.iter().enumerate() { o.push(Overlay::CurvePoint { p: *p, color: FEEDBACK, filled: Some(i) == self.current, size: 6.0 }); @@ -1033,7 +1048,11 @@ mod tests { let s = selected(id, Some(5)); let cx = quiet(&d, &s, &p); t.pointer(&cx, &PointerEvent::new(PointerKind::Move, 650.0, 60.0)); - assert!(t.overlays(&cx).iter().any(|o| matches!(o, Overlay::Path { .. })), "the rubber band shows"); + let ovs = t.overlays(&cx); + assert!(ovs.iter().any(|o| matches!(o, Overlay::Path { .. })), "the rubber band shows"); + let from = pt(550.0, 120.0); + let to = pt(650.0, 60.0); + assert!(ovs.iter().any(|o| matches!(o, Overlay::Measure { text, .. } if text == &cx.segment_measure_label(from, to)))); let a = press(&mut t, &cx, 650.0, 60.0, true); assert!(matches!(edit_op(&a[0]), Some(("extend", v)) if v["from"] == "smooth"), "{a:?}"); curvature_extend(&mut sp, false, pt(650.0, 60.0), EndCurve::Smooth, false); diff --git a/crates/tools/src/lib.rs b/crates/tools/src/lib.rs index 76cd40634..85858cb9d 100644 --- a/crates/tools/src/lib.rs +++ b/crates/tools/src/lib.rs @@ -380,6 +380,11 @@ impl ToolContext<'_> { pub fn offset_label(&self, dx: f64, dy: f64) -> String { format!("dX: {}\ndY: {}", self.len(dx), self.len(dy)) } + /// A segment label while drawing: distance, angle and offsets between two points. + pub fn segment_measure_label(&self, a: Point, b: Point) -> String { + let (dist, ang, dx, dy) = xform::measure(a, b); + format!("D: {}\nAngle: {ang:.1}°\n{}", self.len(dist), self.offset_label(dx, dy)) + } /// What Snap to Grid snaps to: the grid's subdivisions (or its lines without any). pub fn grid_step(&self) -> f64 { self.doc.grid.spacing / self.doc.grid.subdivisions.max(1) as f64 diff --git a/crates/tools/src/pen.rs b/crates/tools/src/pen.rs index f578d8e31..1f2483c8a 100644 --- a/crates/tools/src/pen.rs +++ b/crates/tools/src/pen.rs @@ -27,7 +27,7 @@ use serde_json::json; use vectorcraft_doc::{NodeId, NodeKind}; -use vectorcraft_geom::{BezPath, Point}; +use vectorcraft_geom::{BezPath, Point, Vec2}; use crate::direct::hit_handle; use crate::draw2::{AnchorTool, editable_paths}; @@ -396,6 +396,12 @@ impl Tool for PenTool { bp.line_to(h); } o.push(Overlay::Path { path: bp, color: cx.doc.layer_color(id), width: 1.0, dashed: false }); + if cx.measurement_labels && last.distance(h) > 1e-9 { + o.push(Overlay::Measure { + p: h + Vec2::new(cx.tol(12.0), cx.tol(12.0)), + text: cx.segment_measure_label(last, h), + }); + } } o.extend_from_slice(self.snap.guides()); o @@ -1206,6 +1212,9 @@ mod tests { assert!(o.iter().any( |o| matches!(o, Overlay::Path { path, .. } if path.elements().last().and_then(|e| e.end_point()) == Some(Point::new(150.0, 318.0))) )); + let from = Point::new(60.0, 300.0); + let to = Point::new(150.0, 318.0); + assert!(o.iter().any(|o| matches!(o, Overlay::Measure { text, .. } if text == &cx.segment_measure_label(from, to)))); let a = click(&mut t, &cx, 152.0, 318.0, Mods::default()); assert_eq!(a[1], Action::Preview("path.appendAnchor".into(), json!({"id": id.0, "x": 150.0, "y": 318.0}))); let shift = Mods { shift: true, ..Mods::default() }; diff --git a/crates/tools/src/shape.rs b/crates/tools/src/shape.rs index cb81263ec..07593a72b 100644 --- a/crates/tools/src/shape.rs +++ b/crates/tools/src/shape.rs @@ -8,7 +8,7 @@ //! the dragged corner snap to Smart Guides ([`DrawSnap`]), hovering before the press too. use serde_json::{Value, json}; -use vectorcraft_geom::{Point, Rect}; +use vectorcraft_geom::{Point, Rect, Vec2}; use crate::guides::{DrawSnap, Leave, square}; use crate::{Action, Cursor, Mods, Overlay, PointerEvent, PointerKind, Tool, ToolContext, ToolKey}; @@ -251,15 +251,21 @@ impl Tool for ShapeTool { fn overlays(&self, cx: &ToolContext) -> Vec { let mut o = self.snap.guides().to_vec(); if let Some(s) = self.start.filter(|_| self.began && cx.measurement_labels) { - // A box's size as drawn (from its centre with Alt, square with Shift). - let (w, h) = match self.id { - "polygon" | "star" | "lineSegment" => ((self.last - s).x.abs(), (self.last - s).y.abs()), - _ => { - let r = drag_rect(s, self.last, self.mods); - (r.width(), r.height()) - } + let p = self.last + Vec2::new(cx.tol(12.0), cx.tol(12.0)); + let text = if self.id == "lineSegment" { + cx.segment_measure_label(s, self.last) + } else { + // A box's size as drawn (from its centre with Alt, square with Shift). + let (w, h) = match self.id { + "polygon" | "star" => ((self.last - s).x.abs(), (self.last - s).y.abs()), + _ => { + let r = drag_rect(s, self.last, self.mods); + (r.width(), r.height()) + } + }; + cx.size_label(w, h) }; - o.push(Overlay::Measure { p: self.last, text: cx.size_label(w, h) }); + o.push(Overlay::Measure { p, text }); } o } @@ -422,5 +428,8 @@ mod tests { 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)))); } } diff --git a/crates/tools/src/xform.rs b/crates/tools/src/xform.rs index 32203a3b1..bc4066cc4 100644 --- a/crates/tools/src/xform.rs +++ b/crates/tools/src/xform.rs @@ -22,7 +22,7 @@ pub use artboard::ArtboardTool; pub use eyedropper::EyedropperTool; pub use free::{DistortMode, FreeTransformTool}; pub use gradient::GradientTool; -pub use measure::MeasureTool; +pub use measure::{MeasureTool, measure}; pub use transform::{TransformKind, TransformTool}; pub use wand::{LassoTool, MagicWandTool};