mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Fix #2103: arrow keys with the Artboard tool move the selected artboards and their art
With the Artboard tool the arrow keys did nothing to the artboards: the
tool claimed only Escape, Delete and Backspace (tools/src/xform/artboard.rs
claims_key), so the arrows fell through to object.nudge in
ui-egui/src/shortcuts.rs, which moved the selected art on its own (or
nothing, with no art selected) and left the artboard where it was.
- artboard.rs: the tool claims the arrow keys and turns each press into
one artboard.move of the selected artboards that aren't locked, by the
keyboard increment (Preferences > General > Keyboard Increment, read
from the tool context as Touch Type does), Shift ten times as far as
object.nudge does, with moveArt from the tool's Move/Copy Artwork with
Artboard option, as a drag sends it. One undo step per press; nothing
during a drag or with only locked artboards selected.
- tests: arrow_keys_nudge_the_selected_artboards (tools: claims, step,
Shift, moveArt, several boards, locked, mid-drag) and
artboard_tool_arrow_keys_move_the_selected_artboards_with_their_art
(engine: the artboards and the art on them move, one step per press).
Verified live over --control: a 400x300 artboard with a selected 40 pt
square, Artboard tool, ArrowRight: before, the artboard stayed at x 0 and
the square moved to x 51 ("Move"); after, the artboard goes to x 1 and the
square with it ("Move Artboard"); Shift+Down moves both 10 pt; with
Move Artwork off only the artboard moves; increment 5 moves 5 pt.
The drag case in the report: a drag already moves the art, but only the
art that lies wholly inside the artboard and is unlocked and visible
(doc/src/lib.rs art_on_artboard; locked and hidden art only with
Move Locked and Hidden Artwork with Artboard). Art overlapping the
artboard's edge stays behind by design, which may be what the reporter
saw when dragging.
This commit is contained in:
committed by
bflatastic
parent
c49a1faa4b
commit
b0d5ab3f68
@@ -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