Drawing tools: the measurement readout shows with the rubber band off

Enable Rubber Band now hides only the preview segment in the Pen, the
Curvature tool and the Smart Pen. With it off and Measurement Labels on,
the three tools still show the D / Angle / dX / dY readout at the pointer
while a path is drawn, and the Curvature tool also shows it when
going on from the current end of a selected path. With Measurement Labels
off, no readout shows either way. The click places the same anchor as
before.

Docs: docs/mcp.md (penRubberBand, curvatureRubberBand), docs/ui-parity.md,
docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-11-12 20:27:27 -08:00
parent 465091d7d2
commit fad257ce82
9 changed files with 164 additions and 32 deletions
+7 -4
View File
@@ -573,12 +573,15 @@ impl Tool for SmartPenTool {
fn overlays(&self, cx: &ToolContext) -> Vec<Overlay> {
let mut o = vec![];
if cx.pen_rubber_band
&& self.press.is_none()
// The rubber band preference hides only the preview segment; Measurement Labels decides the readout.
if self.press.is_none()
&& let (Some((id, _, sp)), Some(h)) = (self.active(cx), self.hover)
&& let Some(path) = self.rubber_band(sp, h)
{
o.push(Overlay::Path { path, color: cx.doc.layer_color(id), width: 1.0, dashed: false });
if cx.pen_rubber_band
&& let Some(path) = self.rubber_band(sp, h)
{
o.push(Overlay::Path { path, color: cx.doc.layer_color(id), width: 1.0, dashed: false });
}
if let Some(last) = sp.anchors.last().map(|a| a.p)
&& cx.measurement_labels
&& last.distance(h) > 1e-9
@@ -315,6 +315,35 @@ fn the_rubber_band_shows_the_next_segment_and_ending_the_path() {
assert!(t.overlays(&cx).is_empty());
}
/// With Enable Rubber Band for the Pen off, the preview segment is hidden but Measurement Labels
/// still shows the readout from the last anchor to the pointer.
#[test]
fn the_measurement_label_shows_with_the_rubber_band_off() {
let (d, _, s) = doc(line());
let p = paint();
let on = plain(&d, &s, &p);
let off = ToolContext { pen_rubber_band: false, ..plain(&d, &s, &p) };
let (from, to) = (Point::new(200.0, 100.0), Point::new(300.0, 160.0));
let measure =
|o: &[Overlay]| o.iter().filter_map(|o| if let Overlay::Measure { text, .. } = o { Some(text.clone()) } else { None }).collect::<Vec<_>>();
let band = |o: &[Overlay]| o.iter().any(|o| matches!(o, Overlay::Path { .. }));
let mut t = drawing(&off, from);
t.pointer(&off, &ev(PointerKind::Move, to.x, to.y, NO));
let o = t.overlays(&off);
assert_eq!(measure(&o), vec![off.segment_measure_label(from, to)], "{o:?}");
assert!(!band(&o), "{o:?}");
let o = t.overlays(&on);
assert_eq!(measure(&o), vec![on.segment_measure_label(from, to)], "{o:?}");
assert!(band(&o), "{o:?}");
for rubber in [true, false] {
let quiet = ToolContext { pen_rubber_band: rubber, measurement_labels: false, ..plain(&d, &s, &p) };
assert!(measure(&t.overlays(&quiet)).is_empty());
}
// Hovering exactly on the last anchor measures no segment.
t.pointer(&off, &ev(PointerKind::Move, from.x, from.y, NO));
assert!(measure(&t.overlays(&off)).is_empty());
}
#[test]
fn an_alt_drag_away_from_paths_selects_the_points_inside_a_marquee() {
let (d, id, s) = doc(SubPath::polyline(&[Point::new(100.0, 100.0), Point::new(200.0, 100.0), Point::new(300.0, 100.0)], false));
+23 -17
View File
@@ -718,38 +718,44 @@ impl Tool for CurvatureTool {
}
fn overlays(&self, cx: &ToolContext) -> Vec<Overlay> {
let mut o = self.snap.guides().to_vec();
let band = cx.curvature_rubber_band && self.drag.is_none();
// The rubber band preference hides only the preview curve; Measurement Labels decides the readout.
let idle = self.drag.is_none();
let band = cx.curvature_rubber_band;
let mut bp = BezPath::new();
if self.active(cx).is_none() {
// The curve on from the current end of a selected path: its last segment and the next.
if band
if idle
&& let (Some(e), Some(h)) = (current_end(cx), self.hover)
&& let Some(sp) = subpath(cx, e.id, e.si)
{
let n = sp.anchors.len();
let near = if e.start {
sp.anchors.iter().take(2).rev().map(Anchor::reversed).collect()
} else {
sp.anchors.iter().skip(n.saturating_sub(2)).copied().collect()
};
let mut tail = SubPath::new(near, false);
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 band {
let n = sp.anchors.len();
let near = if e.start {
sp.anchors.iter().take(2).rev().map(Anchor::reversed).collect()
} else {
sp.anchors.iter().skip(n.saturating_sub(2)).copied().collect()
};
let mut tail = SubPath::new(near, false);
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;
}
if band
if idle
&& !self.closed
&& let Some(h) = self.hover
{
let mut pts = self.pts.clone();
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 band {
let mut pts = self.pts.clone();
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
+48
View File
@@ -539,3 +539,51 @@ fn clicks_go_on_from_a_current_end_and_the_other_end_closes() {
let a = press(&mut t, &quiet(&d, &s, &p), -50.0, 150.0, false);
assert!(matches!(edit_op(&a[0]), Some(("extend", v)) if v["end"] == "start" && v["from"] == "keep"), "{a:?}");
}
/// With Enable Rubber Band for the Curvature tool off, the preview curve is hidden but
/// Measurement Labels still shows the readout, both while drawing a path and going on from the
/// current end of a selected one.
#[test]
fn the_measurement_label_shows_with_the_rubber_band_off() {
let p = paint();
let measure =
|o: &[Overlay]| o.iter().filter_map(|o| if let Overlay::Measure { text, .. } = o { Some(text.clone()) } else { None }).collect::<Vec<_>>();
let band = |o: &[Overlay]| o.iter().any(|o| matches!(o, Overlay::Path { .. }));
let to = pt(650.0, 60.0);
// Going on from the current end of a selected path.
let (d, id) = doc_with(wave());
let s = selected(id, Some(4));
let on = quiet(&d, &s, &p);
let off = ToolContext { curvature_rubber_band: false, ..quiet(&d, &s, &p) };
let mut t = CurvatureTool::default();
t.pointer(&off, &PointerEvent::new(PointerKind::Move, to.x, to.y));
let from = pt(450.0, 60.0);
let o = t.overlays(&off);
assert_eq!(measure(&o), vec![off.segment_measure_label(from, to)], "{o:?}");
assert!(!band(&o), "{o:?}");
let o = t.overlays(&on);
assert_eq!(measure(&o), vec![on.segment_measure_label(from, to)], "{o:?}");
assert!(band(&o), "{o:?}");
// A path being drawn.
let (d, id) = doc_with(SubPath::polyline(&[pt(100.0, 100.0), pt(200.0, 150.0)], false));
let s = selected(id, None);
let on = quiet(&d, &s, &p);
let off = ToolContext { curvature_rubber_band: false, ..quiet(&d, &s, &p) };
let mut t = CurvatureTool { id: Some(id), pts: vec![(pt(100.0, 100.0), false), (pt(200.0, 150.0), false)], ..CurvatureTool::default() };
assert!(t.active(&off).is_some());
t.pointer(&off, &PointerEvent::new(PointerKind::Move, to.x, to.y));
let from = pt(200.0, 150.0);
let o = t.overlays(&off);
assert_eq!(measure(&o), vec![off.segment_measure_label(from, to)], "{o:?}");
assert!(!band(&o), "{o:?}");
let o = t.overlays(&on);
assert_eq!(measure(&o), vec![on.segment_measure_label(from, to)], "{o:?}");
assert!(band(&o), "{o:?}");
for rubber in [true, false] {
let unlabeled = ToolContext { curvature_rubber_band: rubber, measurement_labels: false, ..quiet(&d, &s, &p) };
assert!(measure(&t.overlays(&unlabeled)).is_empty());
}
// Hovering exactly on the last point measures no segment.
t.pointer(&off, &PointerEvent::new(PointerKind::Move, from.x, from.y));
assert!(measure(&t.overlays(&off)).is_empty());
}
+11 -9
View File
@@ -495,19 +495,21 @@ impl Tool for PenTool {
return t.overlays(cx);
}
let mut o = vec![];
if cx.pen_rubber_band
&& self.drag.is_none()
// The rubber band preference hides only the preview segment; Measurement Labels decides the readout.
if self.drag.is_none()
&& self.handle.is_none()
&& let (Some((id, _, _, last, out)), Some(h)) = (self.active(cx), self.hover)
{
let mut bp = BezPath::new();
bp.move_to(last);
if out.distance(last) > 1e-9 {
bp.quad_to(out, h);
} else {
bp.line_to(h);
if cx.pen_rubber_band {
let mut bp = BezPath::new();
bp.move_to(last);
if out.distance(last) > 1e-9 {
bp.quad_to(out, h);
} else {
bp.line_to(h);
}
o.push(Overlay::Path { path: bp, color: cx.doc.layer_color(id), width: 1.0, dashed: false });
}
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) });
}
+42
View File
@@ -773,6 +773,48 @@ fn anchors_snap_to_smart_guides() {
assert_eq!(a[1], Action::Preview("path.create".into(), json!({"anchors": [{"x": 100.0, "y": 100.0}]})));
}
/// With Enable Rubber Band off, the preview segment is hidden but Measurement Labels still shows
/// the D / Angle / dX / dY readout at the snapped point before the next click.
#[test]
fn measurement_label_shows_with_the_rubber_band_off() {
let (d, id, s) = drawing(vectorcraft_geom::SubPath::polyline(&[Point::new(10.0, 300.0), Point::new(60.0, 300.0)], false));
let p = paint();
let on = cx(&d, &s, &p);
let off = ToolContext { pen_rubber_band: false, ..cx(&d, &s, &p) };
let from = Point::new(60.0, 300.0);
let to = Point::new(150.0, 318.0);
let measure =
|o: &[Overlay]| o.iter().filter_map(|o| if let Overlay::Measure { text, .. } = o { Some(text.clone()) } else { None }).collect::<Vec<_>>();
let preview = |o: &[Overlay]| {
o.iter().any(|o| matches!(o, Overlay::Path { path, .. } if path.elements().first().and_then(|e| e.end_point()) == Some(from)))
};
let mut t = PenTool { drawing: true, ..PenTool::default() };
t.pointer(&off, &PointerEvent::new(PointerKind::Move, 152.0, 318.0));
let o = t.overlays(&off);
assert_eq!(measure(&o), vec![off.segment_measure_label(from, to)], "{o:?}");
assert!(!preview(&o), "{o:?}");
// The rubber band on still shows both.
let o = t.overlays(&on);
assert_eq!(measure(&o), vec![on.segment_measure_label(from, to)], "{o:?}");
assert!(preview(&o), "{o:?}");
// Measurement Labels off shows no readout either way.
for band in [true, false] {
let quiet = ToolContext { pen_rubber_band: band, measurement_labels: false, ..cx(&d, &s, &p) };
assert!(measure(&t.overlays(&quiet)).is_empty());
}
// The click places the same anchor as with the rubber band on.
let a = click(&mut t, &off, 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, "subpath": 0})));
// Hovering exactly on the last anchor measures no segment.
let mut t = PenTool { drawing: true, ..PenTool::default() };
t.pointer(&off, &PointerEvent::new(PointerKind::Move, 60.0, 300.0));
assert!(measure(&t.overlays(&off)).is_empty());
// With no path being drawn there is nothing to measure from.
let mut t = PenTool::default();
t.pointer(&off, &PointerEvent::new(PointerKind::Move, 152.0, 318.0));
assert!(measure(&t.overlays(&off)).is_empty());
}
#[test]
fn alt_over_a_shown_handle_or_an_anchor_converts() {
// The middle anchor is smooth: handles at (150, 100) and (250, 100) round (200, 100).
+2 -1
View File
@@ -5389,7 +5389,8 @@ The Selection & Anchor Display and General preferences apply to `pointer_gesture
scales it too; off (the default) it stays where it is. `artboard.duplicate`, `artboard.copy` and `artboard.cut` copy or
cut locked and hidden art only with it on. A `lockedAndHidden` param on any of these commands takes its place.
- `penRubberBand`, `curvatureRubberBand` (on by default): off, the Pen and Curvature tools draw no preview segment to
the pointer.
the pointer. With either off, the Pen, the Smart Pen (`penRubberBand`) and the Curvature tool (`curvatureRubberBand`)
still show the D / Angle / dX / dY readout at the pointer while `measurementLabels` is on.
- `showHandlesMultipleAnchors` (on by default): off, Direct Selection (and the Anchor Point tool, and the Pen with Alt)
shows and drags direction handles only while a single anchor is selected. On, the handles of the direct-selected
anchors, or of every anchor of a path selected as a whole, show and drag. `handleStyle` (`solid`, `hollow`, `large`) draws their ends (desktop app).
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -233,7 +233,7 @@ tools, never checked side by side) isn't captured row by row.
| "intersect" label and snapping to path intersections | partial | #1658 (part of #1602): a point drawn with the Pen, the shape tools or the other drawing tools and an anchor dragged with Direct Selection snap to where paths and ruler guides cross; a moved selection's own anchors and the Width tool don't |
| Construction guides at preference angles while drawing | done | #506 |
| Line extension: the dragged end of a straight segment snaps to the segment's own direction at any angle, past its other end or short of it, with the guide along it | done | #2120; Direct Selection and Curvature, one anchor dragged (with several, the move has no single direction); nearer the horizontal or vertical through the other end, the point lines up with it ("align"), so a nearly level segment can still be straightened |
| Measurement labels while drawing, moving and resizing | done | sizes and offsets; the segment being drawn is labeled with its distance, angle and offsets by the Line Segment tool, and by the Pen, Curvature and Smart Pen tools with Enable Rubber Band on (#2983) |
| Measurement labels while drawing, moving and resizing | done | sizes and offsets; the segment being drawn is labeled with its distance, angle and offsets by the Line Segment tool, and by the Pen, Curvature and Smart Pen tools with Enable Rubber Band on or off (#2983) |
| Spacing guides (equal distances) | partial | for moves and bounding-box resizes (#394); not while drawing |
| Distance Guides: Alt/Option over another object, or an empty part of an artboard, shows the selection's distances to it (Smart Guides › Invoke Distance Guides) | done | Selection tool hover with Smart Guides on: the gap between facing edges, else the offsets of matching edges, to another object; the left, right, top and bottom distances to the artboard; labelled in the General unit. The selection is measured as a whole. Nothing while moving or nudging, which the public pages don't describe. [Use Distance Guides](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/use-distance-guides-for-accurate-placement.html), [Smart Guides options](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/smart-guides-options.html) |
| Snapping tolerance preference | done | |