mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #3549 from Taha-Mahmoodi/fix/2103-artboard-nudge
Fix #2103: arrow keys with the Artboard tool move the selected artboards and their art
This commit is contained in:
@@ -352,6 +352,28 @@ fn duplicate_artboards_copies_every_selected_artboard_in_one_step() {
|
||||
}
|
||||
}
|
||||
|
||||
/// #2103: with the Artboard tool, an arrow key moves the selected artboards and the art on them by
|
||||
/// the keyboard increment (Shift: ten times as far) in one undo step each; the selected art
|
||||
/// doesn't move on its own.
|
||||
#[test]
|
||||
fn artboard_tool_arrow_keys_move_the_selected_artboards_with_their_art() {
|
||||
use vectorcraft_tools::{Mods, ToolKey};
|
||||
let mut s = session();
|
||||
let [a, b, c] = two_selected_artboards(&mut s);
|
||||
s.execute("prefs.set", &json!({"key": "keyboardIncrement", "value": 4})).unwrap();
|
||||
let v = ViewInfo::default();
|
||||
assert!(s.tool_claims_key(ToolKey::Right, v));
|
||||
let undo = s.doc().unwrap().history.undo.len();
|
||||
s.tool_key(ToolKey::Right, Mods::default(), v).unwrap();
|
||||
s.tool_key(ToolKey::Right, Mods { shift: true, ..Mods::default() }, v).unwrap();
|
||||
let xs = |s: &Session| boards(s).into_iter().map(|(x, _)| x).collect::<Vec<_>>();
|
||||
assert_eq!(xs(&s), [44.0, 1044.0, 0.0]);
|
||||
assert_eq!([left(&s, a), left(&s, b), left(&s, c)], [54.0, 1054.0, 10.0], "the art moves with its artboard");
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), undo + 2, "one step per press");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!((xs(&s), left(&s, a)), (vec![4.0, 1004.0, 0.0], 14.0));
|
||||
}
|
||||
|
||||
/// With the Artboard tool, Copy takes every selected artboard and the art on each (the public Cut,
|
||||
/// copy, and paste artboards page). Paste adds a copy of each with its art in one undo step,
|
||||
/// together right of the last artboard; Paste in Place puts them where they were. The tool then
|
||||
|
||||
@@ -12,13 +12,15 @@
|
||||
//! leaves the artboard where it was), drag a handle resizes it
|
||||
//! (Shift proportional, Alt from centre; with the `scaleArt` option on, Scale Artwork with Artboard,
|
||||
//! always proportional, and the art fully inside scales with it), drag on the pasteboard draws a new artboard, Delete removes
|
||||
//! the active one (Copy, Cut and Paste take it with its art: `artboard.copy`) and Escape returns to
|
||||
//! the active one (Copy, Cut and Paste take it with its art: `artboard.copy`), the arrow keys move the
|
||||
//! selected artboards by the keyboard increment (Shift ten times as far; with their artwork when
|
||||
//! `moveArt` is on) and Escape returns to
|
||||
//! the Selection tool (during a drag it only cancels the drag). Moving and resizing snap like drawing
|
||||
//! does: to whole pixels, to the grid, or with Smart Guides to other artboards, their bleed and
|
||||
//! objects (never to the dragged artboard or the art moving or scaling with it); the artboard's own bleed edges
|
||||
//! snap too. A new artboard's corners snap as drawn points do ([`DrawSnap`]). A locked artboard (the
|
||||
//! Artboards panel's lock column) is selected by a click, but a drag doesn't move it or copy it,
|
||||
//! it shows no handles, and Delete leaves it.
|
||||
//! Artboards panel's lock column) is selected by a click, but a drag or an arrow key doesn't move it
|
||||
//! or copy it, it shows no handles, and Delete leaves it.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_geom::{Affine, Point, Rect, Vec2};
|
||||
@@ -469,12 +471,13 @@ impl Tool for ArtboardTool {
|
||||
/// Escape owns the request to return to Selection (or cancel an active drag), so the UI
|
||||
/// applies the tool's request instead of falling through to its general Escape behaviour.
|
||||
/// Delete/Backspace belong to the artboards even when deletion is protected or a drag is
|
||||
/// active: falling through to Clear would delete selected artwork instead.
|
||||
/// active: falling through to Clear would delete selected artwork instead. So do the arrows:
|
||||
/// they nudge the artboards, never the selected art on its own (#2103).
|
||||
fn claims_key(&self, _cx: &ToolContext, key: ToolKey) -> bool {
|
||||
matches!(key, ToolKey::Escape | ToolKey::Delete | ToolKey::Backspace)
|
||||
matches!(key, ToolKey::Escape | ToolKey::Delete | ToolKey::Backspace | ToolKey::Left | ToolKey::Right | ToolKey::Up | ToolKey::Down)
|
||||
}
|
||||
|
||||
fn key(&mut self, cx: &ToolContext, key: ToolKey, _mods: Mods) -> Vec<Action> {
|
||||
fn key(&mut self, cx: &ToolContext, key: ToolKey, mods: Mods) -> Vec<Action> {
|
||||
match key {
|
||||
ToolKey::Delete | ToolKey::Backspace => {
|
||||
// A locked artboard isn't deleted.
|
||||
@@ -508,6 +511,28 @@ impl Tool for ArtboardTool {
|
||||
vec![Action::SwitchTool("selection".into())]
|
||||
}
|
||||
}
|
||||
ToolKey::Left | ToolKey::Right | ToolKey::Up | ToolKey::Down => {
|
||||
// The selected artboards that aren't locked move by the keyboard increment (Shift:
|
||||
// ten times as far, as object.nudge moves art), with their art when Move Artwork
|
||||
// with Artboard is on, as a drag moves them: one step per press.
|
||||
let moving: Vec<usize> = self.selected(cx).into_iter().filter(|i| cx.doc.artboards.get(*i).is_some_and(|a| !a.locked)).collect();
|
||||
if self.drag.is_some() || moving.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
let step = cx.keyboard_increment * if mods.shift { 10.0 } else { 1.0 };
|
||||
let (dx, dy) = match key {
|
||||
ToolKey::Left => (-step, 0.0),
|
||||
ToolKey::Right => (step, 0.0),
|
||||
ToolKey::Up => (0.0, -step),
|
||||
_ => (0.0, step),
|
||||
};
|
||||
let mut v = json!({ "dx": dx, "dy": dy, "moveArt": self.move_art });
|
||||
match moving.as_slice() {
|
||||
[i] => v["index"] = json!(i),
|
||||
_ => v["indices"] = json!(moving),
|
||||
}
|
||||
vec![Action::Exec("artboard.move".into(), v)]
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
@@ -823,6 +848,42 @@ mod tests {
|
||||
assert_eq!(t.options()["selected"], json!([2]));
|
||||
}
|
||||
|
||||
/// The arrow keys move the selected artboards by the keyboard increment, Shift ten times as far,
|
||||
/// with their art when Move Artwork with Artboard is on (#2103): the tool claims them, so they
|
||||
/// no longer fall through to object.nudge and move only the selected art. A locked artboard
|
||||
/// stays, and during a drag the arrows do nothing.
|
||||
#[test]
|
||||
fn arrow_keys_nudge_the_selected_artboards() {
|
||||
let mut d = two_boards();
|
||||
let mut third = d.artboards[0].clone();
|
||||
third.rect = Rect::new(0.0, 600.0, 200.0, 800.0);
|
||||
third.locked = true;
|
||||
d.artboards.push(third);
|
||||
let (s, p) = (Selection::default(), paint());
|
||||
let mut c = cx(&d, &s, &p);
|
||||
c.keyboard_increment = 2.0;
|
||||
let shift = Mods { shift: true, ..Default::default() };
|
||||
let mut t = ArtboardTool { active: 1, ..Default::default() };
|
||||
let moved = |p: Value| vec![Action::Exec("artboard.move".into(), p)];
|
||||
for key in [ToolKey::Left, ToolKey::Right, ToolKey::Up, ToolKey::Down] {
|
||||
assert!(t.claims_key(&c, key), "{key:?}");
|
||||
}
|
||||
assert_eq!(t.key(&c, ToolKey::Right, Mods::default()), moved(json!({"index": 1, "dx": 2.0, "dy": 0.0, "moveArt": true})));
|
||||
assert_eq!(t.key(&c, ToolKey::Up, shift), moved(json!({"index": 1, "dx": 0.0, "dy": -20.0, "moveArt": true})));
|
||||
t.move_art = false;
|
||||
t.set_option("selected", &json!([0, 1, 2]));
|
||||
assert_eq!(
|
||||
t.key(&c, ToolKey::Left, Mods::default()),
|
||||
moved(json!({"indices": [0, 1], "dx": -2.0, "dy": 0.0, "moveArt": false})),
|
||||
"the locked artboard stays"
|
||||
);
|
||||
t.set_option("selected", &json!([2]));
|
||||
assert_eq!(t.key(&c, ToolKey::Down, Mods::default()), vec![], "only a locked artboard selected");
|
||||
t.set_option("selected", &json!([1]));
|
||||
t.pointer(&c, &ev(PointerKind::Down, 700.0, 100.0));
|
||||
assert_eq!(t.key(&c, ToolKey::Down, Mods::default()), vec![], "not during a drag");
|
||||
}
|
||||
|
||||
/// A click selects a locked artboard, but a drag neither moves it nor copies it (Alt), a press
|
||||
/// on its edge doesn't resize it, it shows no handles, the pointer over it is the arrow, and
|
||||
/// Delete leaves it. With an unlocked artboard selected too, a drag and Delete act on that one.
|
||||
|
||||
Reference in New Issue
Block a user