mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 05:37:23 -06:00
Perspective Selection: a move of loose art keeps its plane when 4 clears the active plane
The move drag remembers the plane it places loose art on. Pressing 4 during the drag, or dragging on after it, previews and commits the move on that plane instead of dropping it. A 1-3 key pressed before the drag threshold also sets the plane the move uses.
This commit is contained in:
@@ -281,6 +281,39 @@ fn shift_drag_keeps_its_displacement_after_a_plane_key() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loose_art_move_survives_clearing_the_active_plane() {
|
||||
for (drag_after_key, switch_before_drag) in [(false, false), (true, false), (false, true)] {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 800, "height": 600, "created": null})).unwrap();
|
||||
s.execute("perspective.grid.preset", &json!({"kind": 2})).unwrap();
|
||||
s.execute("perspective.grid.snap", &json!({"on": false})).unwrap();
|
||||
s.execute("perspective.plane.set", &json!({"plane": "right"})).unwrap();
|
||||
let result = s.execute("shape.rectangle", &json!({"x": 450, "y": 380, "width": 60, "height": 60})).unwrap();
|
||||
let id = NodeId(result["id"].as_u64().unwrap());
|
||||
assert_eq!(attached(&s, id), None);
|
||||
let before = s.doc().unwrap().doc.node(id).unwrap().geometric_bounds().unwrap();
|
||||
let (from, to) = (before.center(), before.center() + Vec2::new(30.0, 15.0));
|
||||
let v = ViewInfo::default();
|
||||
s.select_tool("perspectiveSelection", v).unwrap();
|
||||
let undo_before = undo_len(&s);
|
||||
s.pointer(&PointerEvent::new(PointerKind::Down, from.x, from.y), v).unwrap();
|
||||
if switch_before_drag {
|
||||
s.tool_key(ToolKey::Digit(2), Mods::default(), v).unwrap();
|
||||
}
|
||||
s.pointer(&PointerEvent::new(PointerKind::Drag, to.x, to.y), v).unwrap();
|
||||
s.tool_key(ToolKey::Digit(4), Mods::default(), v).unwrap();
|
||||
assert_eq!(grid(&s).plane, Plane::None);
|
||||
if drag_after_key {
|
||||
s.pointer(&PointerEvent::new(PointerKind::Drag, to.x + 10.0, to.y), v).unwrap();
|
||||
}
|
||||
s.pointer(&PointerEvent::new(PointerKind::Up, to.x, to.y), v).unwrap();
|
||||
assert_eq!(attached(&s, id), Some((if switch_before_drag { Plane::Ground } else { Plane::Right }, 0.0)));
|
||||
assert_ne!(s.doc().unwrap().doc.node(id).unwrap().geometric_bounds().unwrap(), before);
|
||||
assert_eq!(undo_len(&s), undo_before + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cmd_alt_shift_drag_copies_on_a_plane_axis() {
|
||||
let (mut s, id) = persp_session();
|
||||
|
||||
@@ -1286,6 +1286,31 @@ mod tests {
|
||||
assert_eq!(params["constrain"], json!(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loose_move_preview_keeps_its_plane_when_the_active_plane_is_cleared() {
|
||||
let (mut d, id) = doc_with_rect();
|
||||
let mut g = grid(2);
|
||||
g.plane = Plane::Right;
|
||||
g.store(&mut d);
|
||||
let mut selection = Selection::default();
|
||||
selection.add(id);
|
||||
let p = paint();
|
||||
let from = d.node(id).unwrap().geometric_bounds().unwrap().center();
|
||||
let to = from + Vec2::new(30.0, 15.0);
|
||||
let mut tool = PerspectiveSelectionTool::default();
|
||||
let c = cx(&d, &selection, &p);
|
||||
tool.pointer(&c, &PointerEvent::new(PointerKind::Down, from.x, from.y));
|
||||
let actions = tool.pointer(&c, &PointerEvent::new(PointerKind::Drag, to.x, to.y));
|
||||
assert!(matches!(actions.last(), Some(Action::Preview(_, params)) if params["plane"] == "right"));
|
||||
g.plane = Plane::None;
|
||||
g.store(&mut d);
|
||||
let c = cx(&d, &selection, &p);
|
||||
let actions = tool.key(&c, ToolKey::Digit(4), Mods::default());
|
||||
assert!(matches!(actions.last(), Some(Action::Preview(_, params)) if params["plane"] == "right"), "{actions:?}");
|
||||
let actions = tool.pointer(&c, &PointerEvent::new(PointerKind::Drag, to.x + 10.0, to.y));
|
||||
assert!(matches!(actions.last(), Some(Action::Preview(_, params)) if params["plane"] == "right"), "{actions:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selection_move_recomputes_shift_copy_and_perpendicular() {
|
||||
let (mut d, id) = doc_with_rect();
|
||||
|
||||
@@ -120,7 +120,7 @@ enum SelDrag {
|
||||
Idle,
|
||||
/// Pressed on an object: moves `ids` once the pointer leaves the drag threshold. `perp`: along
|
||||
/// the plane's normal (5 pressed while dragging); `copy` and `shift`: modifiers at the last event.
|
||||
Move { ids: Vec<NodeId>, from: Point, last: Point, began: bool, perp: bool, copy: bool, shift: bool },
|
||||
Move { ids: Vec<NodeId>, from: Point, last: Point, plane: Plane, began: bool, perp: bool, copy: bool, shift: bool },
|
||||
/// Dragging `handle` of the perspective bounding box `rect` (plane space, on `plane` at `depth`).
|
||||
Scale { ids: Vec<NodeId>, plane: Plane, depth: f64, rect: Rect, handle: BoxHandle },
|
||||
/// Dragging a plane widget.
|
||||
@@ -164,9 +164,11 @@ pub(super) fn resize_cursor(d: Vec2) -> Cursor {
|
||||
impl PerspectiveSelectionTool {
|
||||
/// The `perspective.move` preview for the move drag (and its measurement label).
|
||||
fn move_preview(&mut self, cx: &ToolContext) -> Option<Action> {
|
||||
let SelDrag::Move { ids, from, last, perp, copy, shift, .. } = &self.drag else { return None };
|
||||
let SelDrag::Move { ids, from, last, plane, perp, copy, shift, .. } = &self.drag else { return None };
|
||||
let g = PerspectiveGrid::current(cx.doc);
|
||||
let at = ids.first().and_then(|id| g.attachment_of(cx.doc, *id)).or((g.plane != Plane::None).then_some((g.plane, g.offset(g.plane))));
|
||||
let move_plane = if g.plane == Plane::None { *plane } else { g.plane };
|
||||
let at =
|
||||
ids.first().and_then(|id| g.attachment_of(cx.doc, *id)).or((move_plane != Plane::None).then_some((move_plane, g.offset(move_plane))));
|
||||
let constrained = if *shift && !*perp {
|
||||
at.and_then(|(plane, depth)| {
|
||||
let delta = g.plane_delta(plane, depth, *from, *last)?;
|
||||
@@ -179,8 +181,8 @@ impl PerspectiveSelectionTool {
|
||||
if *shift && !*perp {
|
||||
v["constrain"] = json!(true);
|
||||
}
|
||||
if g.plane != Plane::None {
|
||||
v["plane"] = json!(g.plane.id());
|
||||
if move_plane != Plane::None {
|
||||
v["plane"] = json!(move_plane.id());
|
||||
}
|
||||
self.measure = at.and_then(|(pl, depth)| {
|
||||
let text = if *perp {
|
||||
@@ -230,24 +232,26 @@ impl Tool for PerspectiveSelectionTool {
|
||||
vec![top]
|
||||
};
|
||||
// Selecting an object in perspective makes its plane the active one.
|
||||
let mut plane = g.plane;
|
||||
if let Some((pl, _)) = g.attachment_of(cx.doc, top)
|
||||
&& pl != g.plane
|
||||
{
|
||||
acts.push(Action::Exec("perspective.plane.set".into(), json!({"plane": pl.id()})));
|
||||
plane = pl;
|
||||
}
|
||||
self.drag = SelDrag::Move { ids, from: p, last: p, began: false, perp: false, copy: m.alt, shift: m.shift };
|
||||
self.drag = SelDrag::Move { ids, from: p, last: p, plane, began: false, perp: false, copy: m.alt, shift: m.shift };
|
||||
acts
|
||||
}
|
||||
(PointerKind::Drag, SelDrag::Move { ids, from, began, perp, .. }) => {
|
||||
(PointerKind::Drag, SelDrag::Move { ids, from, plane, began, perp, .. }) => {
|
||||
if !began && p.distance(from) < cx.tol(3.0) {
|
||||
self.drag = SelDrag::Move { ids, from, last: p, began, perp, copy: m.alt, shift: m.shift };
|
||||
self.drag = SelDrag::Move { ids, from, last: p, plane, began, perp, copy: m.alt, shift: m.shift };
|
||||
return vec![];
|
||||
}
|
||||
let mut out = vec![];
|
||||
if !began {
|
||||
out.push(Action::Begin(if m.alt { "Copy in Perspective" } else { "Move in Perspective" }.into()));
|
||||
}
|
||||
self.drag = SelDrag::Move { ids, from, last: p, began: true, perp, copy: m.alt, shift: m.shift };
|
||||
self.drag = SelDrag::Move { ids, from, last: p, plane, began: true, perp, copy: m.alt, shift: m.shift };
|
||||
out.extend(self.move_preview(cx));
|
||||
out
|
||||
}
|
||||
@@ -307,10 +311,14 @@ impl Tool for PerspectiveSelectionTool {
|
||||
return if *began { self.move_preview(cx).into_iter().collect() } else { vec![] };
|
||||
}
|
||||
if matches!(key, ToolKey::Digit(1..=4))
|
||||
&& let SelDrag::Move { began: true, shift, .. } = &mut self.drag
|
||||
&& let SelDrag::Move { began, plane, shift, .. } = &mut self.drag
|
||||
{
|
||||
*shift = mods.shift;
|
||||
return self.move_preview(cx).into_iter().collect();
|
||||
let active = PerspectiveGrid::current(cx.doc).plane;
|
||||
if active != Plane::None {
|
||||
*plane = active;
|
||||
}
|
||||
return if *began { self.move_preview(cx).into_iter().collect() } else { vec![] };
|
||||
}
|
||||
let (dx, dy) = match key {
|
||||
ToolKey::Left => (-1, 0),
|
||||
|
||||
Reference in New Issue
Block a user