G1: Mesh point drags show dX/dY in measurement labels

Honor Measurement Labels while moving gradient-mesh and mesh-envelope
points with the Mesh tool or Direct Selection.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
nrq6993-a11y
2026-10-20 02:48:08 +07:00
committed by bflatastic
co-authored by Cursor
parent fbc654734f
commit cfea906681
3 changed files with 51 additions and 8 deletions
+1 -1
View File
@@ -503,7 +503,7 @@ impl Tool for DirectSelectionTool {
self.snapped = false;
if let Some(g) = self.mesh.hit(cx, &cx.selection.objects, p) {
self.state = State::Mesh;
return self.mesh.press(g);
return self.mesh.press(cx, g);
}
self.mesh.unfocus();
if let Some((id, si, ai, out)) = hit_handle(cx, p, point) {
+1 -1
View File
@@ -190,7 +190,7 @@ impl Tool for MeshTool {
if cx.selection.objects != [g.id] && cx.doc.node(g.id).is_some_and(|n| matches!(n.kind, NodeKind::Mesh(_))) {
out.push(Action::Exec("select.set".into(), json!({"ids": [g.id.0]})));
}
out.extend(self.edit.press(g));
out.extend(self.edit.press(cx, g));
return out;
}
self.edit.unfocus();
+49 -6
View File
@@ -8,7 +8,7 @@ use std::borrow::Cow;
use serde_json::{Value, json};
use vectorcraft_doc::live::{GradientMesh, envelope_grid};
use vectorcraft_doc::{EnvelopeKind, Node, NodeId, NodeKind};
use vectorcraft_geom::Point;
use vectorcraft_geom::{Point, Vec2};
use crate::{Action, Overlay, ToolContext};
@@ -35,12 +35,21 @@ pub struct MeshGrab {
pub handle: Option<usize>,
}
/// An in-progress mesh point or handle drag.
struct ActiveDrag {
grab: MeshGrab,
/// Where the grabbed point or handle was when the drag began.
origin: Point,
/// The current drag position (preview target); set on the first `drag_to`.
at: Option<Point>,
}
/// Point and handle drags on meshes.
#[derive(Default)]
pub struct MeshEdit {
/// The point last clicked: its handles show and can be dragged.
focus: Option<(NodeId, usize)>,
drag: Option<MeshGrab>,
drag: Option<ActiveDrag>,
}
impl MeshEdit {
@@ -63,15 +72,23 @@ impl MeshEdit {
}
/// Start dragging `grab` (its point takes the focus).
pub fn press(&mut self, grab: MeshGrab) -> Vec<Action> {
pub fn press(&mut self, cx: &ToolContext, grab: MeshGrab) -> Vec<Action> {
self.focus = Some((grab.id, grab.index));
self.drag = Some(grab);
let mut origin = Point::ZERO;
if let Some(grid) = cx.doc.node(grab.id).and_then(grid_of)
&& let Some(q) = grid.points.get(grab.index)
{
origin = if let Some(h) = grab.handle { q.p + q.handles[h] } else { q.p };
}
self.drag = Some(ActiveDrag { grab, origin, at: None });
vec![Action::Begin("Move Mesh Point".into())]
}
/// The preview of a drag to `p` (`None` when nothing is being dragged).
pub fn drag_to(&self, p: Point) -> Option<Vec<Action>> {
let g = self.drag?;
pub fn drag_to(&mut self, p: Point) -> Option<Vec<Action>> {
let d = self.drag.as_mut()?;
d.at = Some(p);
let g = d.grab;
let mut params = json!({"id": g.id.0, "index": g.index, "x": p.x, "y": p.y});
if let Some(h) = g.handle {
params["handle"] = json!(h);
@@ -109,6 +126,16 @@ impl MeshEdit {
out.push(Overlay::Line { a: q.p, b: q.p + *h, color: FEEDBACK, dashed: false });
out.push(Overlay::Handle { p: q.p + *h, color: FEEDBACK });
}
if cx.measurement_labels
&& let Some(d) = &self.drag
&& let Some(at) = d.at
{
let delta = at - d.origin;
out.push(Overlay::Measure {
p: at + Vec2::new(cx.tol(12.0), cx.tol(12.0)),
text: cx.offset_label(delta.x, delta.y),
});
}
out
}
}
@@ -188,4 +215,20 @@ mod tests {
);
assert_eq!(t.pointer(&c, &PointerEvent::new(PointerKind::Up, 90.0, 210.0)), vec![Action::Commit]);
}
#[test]
fn mesh_drag_honors_measurement_labels() {
let (d, _e) = doc_with_envelope();
let (s, p) = (Selection::default(), paint());
let base = cx(&d, &s, &p);
let on = ToolContext { measurement_labels: true, ..base };
let off = ToolContext { measurement_labels: false, ..base };
let mut t = crate::meshblend::create("mesh").unwrap();
t.pointer(&on, &PointerEvent::new(PointerKind::Down, 200.0, 100.0));
assert!(!t.overlays(&on).iter().any(|o| matches!(o, Overlay::Measure { .. })));
t.pointer(&on, &PointerEvent::new(PointerKind::Drag, 210.0, 90.0));
let want = on.offset_label(10.0, -10.0);
assert!(t.overlays(&on).iter().any(|o| matches!(o, Overlay::Measure { text, .. } if text == &want)));
assert!(!t.overlays(&off).iter().any(|o| matches!(o, Overlay::Measure { .. })));
}
}