Fix #693: a click activates its artboard; in-place pastes go onto it

- A press with a selection tool inside an artboard makes it the active one, as
  the Artboard tool's does; before, only the navigator and the Artboards panel
  changed it.
- Paste in Place, in Front and in Back take `artboard` (the UI passes the active
  one): the objects go where they were on the artboard they were copied from,
  on that artboard. Without it, they paste where they were, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-09 13:32:44 +07:00
co-authored by Claude Opus 5.5
parent 843507549f
commit e9bbf43d15
5 changed files with 82 additions and 5 deletions
+16 -4
View File
@@ -55,7 +55,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Paste in Front",
["Edit"],
Some("Cmd+F"),
"{swatchConflict?} paste in place just above the top selected object, or on top of the current layer when nothing is selected (resources and layers as edit.paste) → {ids, added, merged, renamed}",
"{swatchConflict?, artboard?: index (the active artboard: the objects go where they were on the artboard they were copied from, on this one)} paste in place just above the top selected object, or on top of the current layer when nothing is selected (resources and layers as edit.paste) → {ids, added, merged, renamed}",
has_clipboard,
|s, p| paste(s, p, PasteMode::Front)
),
@@ -64,7 +64,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Paste in Back",
["Edit"],
Some("Cmd+B"),
"{swatchConflict?} paste in place just below the bottom selected object, or at the bottom of the current layer when nothing is selected (resources and layers as edit.paste) → {ids, added, merged, renamed}",
"{swatchConflict?, artboard?: index (as edit.pasteInFront)} paste in place just below the bottom selected object, or at the bottom of the current layer when nothing is selected (resources and layers as edit.paste) → {ids, added, merged, renamed}",
has_clipboard,
|s, p| paste(s, p, PasteMode::Back)
),
@@ -73,7 +73,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Paste in Place",
["Edit"],
Some("Cmd+Shift+V"),
"{swatchConflict?} paste where the objects were copied (resources and layers as edit.paste) → {ids, added, merged, renamed}",
"{swatchConflict?, artboard?: index (as edit.pasteInFront)} paste where the objects were copied (resources and layers as edit.paste) → {ids, added, merged, renamed}",
has_clipboard,
|s, p| paste(s, p, PasteMode::InPlace)
),
@@ -310,6 +310,17 @@ fn paste(s: &mut Session, p: &Value, mode: PasteMode) -> Result<Value> {
r
}
/// Paste in Place, in Front or in Back onto artboard `artboard` (the active one, #693): where the
/// objects were on the artboard they were copied from, on that one; without it (or a copy from no
/// artboard), where they were.
fn onto_artboard(doc: &Document, p: &Value, clip: &Clipboard) -> Affine {
let target = p.get("artboard").and_then(Value::as_u64).and_then(|i| usize::try_from(i).ok()).and_then(|i| doc.artboards.get(i));
match (target, clip.source_artboard) {
(Some(t), Some(src)) => Affine::translate(t.rect.origin() - src.origin()),
_ => Affine::IDENTITY,
}
}
fn paste_clip(s: &mut Session, p: &Value, mode: PasteMode, clip: &Clipboard, choices: &SwatchChoices) -> Result<Value> {
let off = s.prefs.paste_offset;
let st = s.doc()?;
@@ -345,7 +356,8 @@ fn paste_clip(s: &mut Session, p: &Value, mode: PasteMode, clip: &Clipboard, cho
let src = clip.source_artboard.or_else(|| st.doc.artboards.first().map(|a| a.rect)).map(|r| r.origin()).unwrap_or_default();
st.doc.artboards.iter().map(|a| Affine::translate(a.rect.origin() - src)).collect()
}
_ => vec![Affine::IDENTITY],
// In place, in front, in back: onto the active artboard when the UI names it.
_ => vec![onto_artboard(&st.doc, p, clip)],
};
let (ids, imported, artboard) = s.edit(mode.label(), |d, sel| {
let imported = clip.import_into(d, choices, same_doc);
+20
View File
@@ -551,6 +551,26 @@ fn paste_on_all_artboards_keeps_the_offset_to_the_source_artboard() {
}
}
/// Paste in Place, in Front and in Back onto the active artboard (#693): where the objects were on
/// the artboard they were copied from, on the one named; without it, where they were.
#[test]
fn in_place_pastes_go_onto_the_artboard_named() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 200, "height": 100, "artboards": 3})).unwrap();
let boards: Vec<Rect> = doc(&s).artboards.iter().map(|a| a.rect).collect();
let r = rect(&mut s, boards[0].x0 + 10.0, boards[0].y0 + 15.0, 20.0, 20.0);
copy(&mut s, &[r]);
for cmd in ["edit.pasteInPlace", "edit.pasteInFront", "edit.pasteInBack"] {
let ids = ids_of(&s.execute(cmd, &json!({"artboard": 2})).unwrap());
assert_eq!(doc(&s).bounds_of(&ids, false).unwrap().origin(), Point::new(boards[2].x0 + 10.0, boards[2].y0 + 15.0), "{cmd}");
let ids = ids_of(&s.execute(cmd, &json!({})).unwrap());
assert_eq!(doc(&s).bounds_of(&ids, false).unwrap().origin(), Point::new(boards[0].x0 + 10.0, boards[0].y0 + 15.0), "{cmd} without one");
// An artboard that isn't there: where they were.
let ids = ids_of(&s.execute(cmd, &json!({"artboard": 9})).unwrap());
assert_eq!(doc(&s).bounds_of(&ids, false).unwrap().origin(), Point::new(boards[0].x0 + 10.0, boards[0].y0 + 15.0), "{cmd} on no artboard");
}
}
#[test]
fn paste_remembers_layers_restores_the_source_layers() {
let mut s = session();