Merge pull request #1516 from AganzeFelicite/fix/vector-marquee-anywhere-1508

M4: a drag anywhere in vector edit mode draws a marquee (#1508)
This commit is contained in:
BFlat
2026-10-30 01:12:34 +07:00
committed by GitHub
5 changed files with 43 additions and 15 deletions
+1 -1
View File
@@ -236,7 +236,7 @@ pub struct PointerEvent {
}
/// The pointer is still within [`DRAG_SLOP_PX`] of where it was pressed.
fn within_slop(ev: &PointerEvent, start: Point) -> bool {
pub(crate) fn within_slop(ev: &PointerEvent, start: Point) -> bool {
(ev.p() - start).hypot() * ev.zoom.max(1e-6) < DRAG_SLOP_PX
}
+17 -12
View File
@@ -13,7 +13,7 @@ use super::regions::region_hit;
use super::transform::{self, BoxHit, PointBox};
use super::{EditPart, VHit, VTool, VectorEdit, edit, editing, hit, hit_any, local_offset, network, ops, points_of, selected_points, set_state};
use crate::snap::Targets;
use crate::tools::{Cursor, Gesture, Handle, PenCursor, PointerEvent, PointerKind};
use crate::tools::{Cursor, Gesture, Handle, PenCursor, PointerEvent, PointerKind, within_slop};
use crate::{Result, Session};
/// An in-progress edit-mode gesture (cheap to clone: snap targets are shared).
@@ -67,10 +67,13 @@ pub(crate) enum VGesture {
moved: bool,
smooth: bool,
},
/// Marquee and lasso: `base` is each edited layer's selection to add to.
/// Marquee and lasso: `base` is each edited layer's selection to add to. `leave` is a press
/// on another layer: released without a drag, it leaves edit mode and goes to the
/// normal tool.
Marquee {
start: Point,
base: Vec<EditPart>,
leave: Option<PointerEvent>,
},
Lasso {
points: Vec<Point>,
@@ -700,21 +703,18 @@ impl Session {
}
}
None => {
// A press on another layer (and not shift) leaves edit mode, so the normal tool
// takes it; empty canvas only clears the points and starts a marquee (a
// double-click or Escape leaves edit mode).
// Every drag here marquees (#1508). A click on another layer (and not shift)
// leaves edit mode, so the normal tool takes it; a click on empty canvas only
// clears the points (a double-click or Escape leaves edit mode).
let inside =
ve.parts().iter().any(|p| doc.world_render_bounds(p.id).is_some_and(|b| b.inflate(tol * 4.0, tol * 4.0).contains(ev.p())));
let page = self.ds()?.page;
let other = !inside && self.hit(&doc, page, ev.p(), tol * 2.0).is_some();
if other && !ev.shift {
super::end(self)?;
return Ok(false);
}
if !ev.shift {
set_state(self, VectorEdit::clear_selection)?;
}
self.vgesture(VGesture::Marquee { start: ev.p(), base: Self::select_base(ve, ev.shift) });
let leave = (other && !ev.shift).then(|| ev.clone());
self.vgesture(VGesture::Marquee { start: ev.p(), base: Self::select_base(ve, ev.shift), leave });
}
},
VTool::Bend => match h {
@@ -738,7 +738,7 @@ impl Session {
self.begin_interaction("Bend")?;
self.vgesture(VGesture::BendSegment { seg, t });
}
None => self.vgesture(VGesture::Marquee { start: ev.p(), base: Self::select_base(ve, ev.shift) }),
None => self.vgesture(VGesture::Marquee { start: ev.p(), base: Self::select_base(ve, ev.shift), leave: None }),
},
VTool::Pen => {
let at = if h.is_none() && !ev.shift { self.snap_pen(ev, Some(id), &[])?.0 } else { ev.p() };
@@ -951,7 +951,7 @@ impl Session {
self.preview(|doc, _| edit(doc, id, |net| ops::make_smooth(net, v, d)))?;
self.vgesture(VGesture::BendVertex { v, origin, moved: true, smooth });
}
VGesture::Marquee { start, base } => {
VGesture::Marquee { start, base, .. } => {
let r = Rect::from_points(start, ev.p());
self.tool.overlay.marquee = Some([r.x0, r.y0, r.x1, r.y1]);
self.select_parts(&base, |doc, id| super::vertices_in(doc, id, r), false)?;
@@ -1068,6 +1068,11 @@ impl Session {
VGesture::Handle { .. } | VGesture::BendSegment { .. } | VGesture::Pen { .. } | VGesture::BoxRotate { .. } => {
self.commit_interaction()?
}
VGesture::Marquee { start, leave: Some(press), .. } if within_slop(ev, start) => {
super::end(self)?;
self.pointer(&press)?;
return self.pointer(ev);
}
// The active layer moves to one with points selected when it has none.
VGesture::Marquee { .. } | VGesture::Lasso { .. } => set_state(self, |e| {
if e.vertices.is_empty()
@@ -542,6 +542,27 @@ fn selecting_points_and_segments() {
assert_eq!(page_pos(&s, id, tl), Point::new(1.0, 0.0), "the drag is one undo step");
}
#[test]
fn a_marquee_starts_anywhere_on_the_canvas() {
// #1508: a drag far from the edited shape, over another layer, marquees its points; a click
// there still leaves edit mode and selects that layer.
let mut s = Session::new();
let bg = id_of(&ex(&mut s, "shape.rectangle", json!({"x": -500, "y": -500, "width": 1000, "height": 1000}))["id"]).unwrap();
let id = square(&mut s, true);
let (tl, tr) = (point_at(&s, id, (0.0, 0.0)), point_at(&s, id, (100.0, 0.0)));
let bg_x = |s: &Session| s.doc().unwrap().world_transform(bg).translation().x;
drag(&mut s, (-200.0, -200.0), (110.0, 10.0), NONE);
let mut got = editing(&s).expect("a drag away from the shape stays in edit mode").vertices;
got.sort_unstable();
let mut want = vec![tl, tr];
want.sort_unstable();
assert_eq!(got, want, "the marquee selects the points inside it");
assert_eq!(bg_x(&s), -500.0, "the layer under the drag stays put");
click(&mut s, (-200.0, -200.0), NONE);
assert!(editing(&s).is_none(), "a click away from the shape leaves edit mode");
assert_eq!(s.selection(), vec![bg], "the click selects the layer under it");
}
#[test]
fn deleting_points_vs_delete_and_heal() {
// (key, Shift, points, segments, closed afterwards, derivation)