mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 09:59:47 -06:00
Global Edit: a Shift-drag from a highlighted match selects it and moves it
A Shift-press on a highlighted object of the Global Edit waits for the release to leave it out of the edit. A drag from it now selects it when the drag starts and moves it with the selection, as a Shift-drag from an unselected object did. The snap targets are gathered after the object joins the selection.
This commit is contained in:
@@ -434,11 +434,15 @@ fn a_shift_click_on_a_highlighted_object_leaves_it_out() {
|
||||
let mut s = session();
|
||||
let [a, b, c, ..] = squares(&mut s);
|
||||
start(&mut s, a, json!({}));
|
||||
// A Shift-drag that starts on the copy moves the selection and leaves the copy in.
|
||||
// A Shift-drag that starts on the copy selects it and moves it with the selection, and leaves
|
||||
// the copy in.
|
||||
shift_drag(&mut s, (350.0, 50.0), (390.0, 50.0));
|
||||
assert_eq!(members(&s), Some(vec![a, b, c]), "a drag leaves nothing out");
|
||||
assert_eq!(s.doc().unwrap().selection.objects, [a, b]);
|
||||
assert!(bounds(&s, a).x0 > 0.0, "the selection moved: {:?}", bounds(&s, a));
|
||||
assert!(bounds(&s, b).x0 > 300.0, "the copy moved with it: {:?}", bounds(&s, b));
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
sel(&mut s, &[a]);
|
||||
shift_click(&mut s, 350.0, 50.0);
|
||||
assert_eq!(members(&s), Some(vec![a, c]), "the copy is left out");
|
||||
assert_eq!(s.doc().unwrap().selection.objects, [a], "and not selected");
|
||||
|
||||
+23
-12
@@ -74,9 +74,11 @@ enum State {
|
||||
/// guides: released without a drag, it becomes the key object that Align aligns to if
|
||||
/// `can_be_key` allows it (the key again: no key).
|
||||
key: Option<NodeId>,
|
||||
/// Shift-pressed on this highlighted object of the Global Edit: released without a drag,
|
||||
/// it leaves the Global Edit (`select.globalEditExclude`).
|
||||
exclude: Option<NodeId>,
|
||||
/// Shift-pressed on this highlighted object of the Global Edit, and the object the press
|
||||
/// hit: released without a drag, the first leaves the Global Edit
|
||||
/// (`select.globalEditExclude`); a drag selects the second and moves it with the
|
||||
/// selection.
|
||||
exclude: Option<(NodeId, NodeId)>,
|
||||
},
|
||||
Scaling {
|
||||
handle: Handle,
|
||||
@@ -755,8 +757,8 @@ impl Tool for SelectionTool {
|
||||
}
|
||||
}
|
||||
}
|
||||
(PointerKind::Drag, State::Moving { start, press, hold, began, .. }) => {
|
||||
self.drag_moving(cx, p, m, ev.backquote, (start, press, hold, began))
|
||||
(PointerKind::Drag, State::Moving { start, press, hold, began, exclude, .. }) => {
|
||||
self.drag_moving(cx, p, m, ev.backquote, (start, press, hold, began), exclude)
|
||||
}
|
||||
(PointerKind::Drag, State::Scaling { handle, bx, areas }) => {
|
||||
self.last_backquote = ev.backquote;
|
||||
@@ -979,12 +981,12 @@ fn object_behind(cx: &ToolContext, p: Point) -> Option<NodeId> {
|
||||
|
||||
/// A Shift-press on `top` (hit by `h`): a Shift-click takes a selected object out of the selection
|
||||
/// when it is released, and a Shift-drag moves the selection, constrained. On an object of the
|
||||
/// Global Edit that is highlighted, the release leaves that object out of the edit instead.
|
||||
/// Returns the object to deselect and the object to leave out on release; adds to `out` the
|
||||
/// action that selects `top` at once.
|
||||
fn shift_press(cx: &ToolContext, h: &Hit, top: NodeId, out: &mut Vec<Action>) -> (Option<NodeId>, Option<NodeId>) {
|
||||
/// Global Edit that is highlighted, the release leaves that object out of the edit instead, and a
|
||||
/// drag selects `top` when it starts. Returns the object to deselect and the object to leave out
|
||||
/// on release (with `top`); adds to `out` the action that selects `top` at once.
|
||||
fn shift_press(cx: &ToolContext, h: &Hit, top: NodeId, out: &mut Vec<Action>) -> (Option<NodeId>, Option<(NodeId, NodeId)>) {
|
||||
if let Some(member) = global_edit_member(cx, h) {
|
||||
(None, Some(member))
|
||||
(None, Some((member, top)))
|
||||
} else if cx.selection.contains(top) {
|
||||
(Some(top), None)
|
||||
} else {
|
||||
@@ -1088,14 +1090,14 @@ impl SelectionTool {
|
||||
began: bool,
|
||||
deselect: Option<NodeId>,
|
||||
key: Option<NodeId>,
|
||||
exclude: Option<NodeId>,
|
||||
exclude: Option<(NodeId, NodeId)>,
|
||||
) -> Vec<Action> {
|
||||
self.state = State::Idle;
|
||||
self.measure = None;
|
||||
self.guides.clear();
|
||||
self.moving = None;
|
||||
// A Shift-click (no drag) on a highlighted object of the Global Edit leaves it out.
|
||||
if let (false, Some(id)) = (began, exclude) {
|
||||
if let (false, Some((id, _))) = (began, exclude) {
|
||||
self.last_move = None;
|
||||
return vec![Action::Exec("select.globalEditExclude".into(), json!({ "id": id.0 }))];
|
||||
}
|
||||
@@ -1305,13 +1307,22 @@ impl SelectionTool {
|
||||
m: Mods,
|
||||
backquote: bool,
|
||||
(start, press, hold, began): (Point, Point, Point, bool),
|
||||
exclude: Option<(NodeId, NodeId)>,
|
||||
) -> Vec<Action> {
|
||||
let mut out = vec![];
|
||||
if !began {
|
||||
if p.distance(press) < Self::drag_threshold(cx) {
|
||||
return out;
|
||||
}
|
||||
// A Shift-drag from a highlighted object of the Global Edit selects it and moves it
|
||||
// with the selection. `cx` still holds the selection without it, so the snap targets
|
||||
// are gathered at the next drag event.
|
||||
if let Some((_, top)) = exclude {
|
||||
out.push(Action::Exec("select.toggle".into(), json!({ "id": top.0 })));
|
||||
}
|
||||
out.push(Action::Begin(if m.alt { "Copy".into() } else { "Move".into() }));
|
||||
self.moving = exclude.is_none().then(|| MoveSnap::new(cx));
|
||||
} else if self.moving.is_none() {
|
||||
self.moving = Some(MoveSnap::new(cx));
|
||||
}
|
||||
let mut d = move_delta(start, p, m.shift, cx.constrain_angle);
|
||||
|
||||
@@ -1004,6 +1004,34 @@ fn shift_click_toggles_an_object_on_release() {
|
||||
assert!(t.pointer(&cx2, &shift(PointerKind::Up, 150.0, 150.0)).is_empty());
|
||||
}
|
||||
|
||||
/// A Shift-drag that starts on a highlighted object of the Global Edit selects that object and
|
||||
/// moves it; only a Shift-click leaves it out of the edit.
|
||||
#[test]
|
||||
fn shift_drag_on_a_global_edit_match_selects_and_moves_it() {
|
||||
let (d, a, b) = two_rects(Rect::new(300.0, 100.0, 400.0, 200.0));
|
||||
let p = paint();
|
||||
let s = Selection::default();
|
||||
let members = [(a, Affine::IDENTITY), (b, Affine::IDENTITY)];
|
||||
let cx = ToolContext { global_edit: &members, ..cx(&d, &s, &p) };
|
||||
let shift = |kind, x, y| ev(kind, x, y).with_mods(Mods { shift: true, ..Mods::default() });
|
||||
let mut t = SelectionTool::default();
|
||||
assert!(t.pointer(&cx, &shift(PointerKind::Down, 350.0, 150.0)).is_empty());
|
||||
let mut actions = t.pointer(&cx, &shift(PointerKind::Drag, 390.0, 150.0));
|
||||
assert_eq!(actions[..2], [Action::Exec("select.toggle".into(), json!({"id": b.0})), Action::Begin("Move".into())]);
|
||||
actions.extend(t.pointer(&cx, &shift(PointerKind::Drag, 395.0, 150.0)));
|
||||
actions.extend(t.pointer(&cx, &shift(PointerKind::Up, 395.0, 150.0)));
|
||||
assert!(
|
||||
actions.iter().any(|a| matches!(a, Action::Preview(c, v) if c == "object.transform" && v["matrix"][4].as_f64().is_some_and(|x| x > 39.0))),
|
||||
"moved right: {actions:?}"
|
||||
);
|
||||
assert_eq!(actions.last(), Some(&Action::Commit));
|
||||
assert!(!actions.iter().any(|a| matches!(a, Action::Exec(c, _) if c == "select.globalEditExclude")), "{actions:?}");
|
||||
// A Shift-click on it leaves it out instead.
|
||||
let mut t = SelectionTool::default();
|
||||
assert!(t.pointer(&cx, &shift(PointerKind::Down, 350.0, 150.0)).is_empty());
|
||||
assert_eq!(t.pointer(&cx, &shift(PointerKind::Up, 350.0, 150.0)), vec![Action::Exec("select.globalEditExclude".into(), json!({"id": b.0}))]);
|
||||
}
|
||||
|
||||
/// #541: a click on one object of a selection of several makes it the key object; a click on
|
||||
/// the key again lets it go. A drag moves them all, a lone object has no key unless ruler
|
||||
/// guides are selected with it.
|
||||
|
||||
Reference in New Issue
Block a user