mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
Record a duplicate's inPlace only when a copy is an artboard
Alt-drag, Cmd+Opt-drag and Free Transform on a copy duplicate with `inPlace: true` for every layer, but the flag only changes anything for an artboard. With in-place duplicates now refused on strict .atn export, a recorded copy of a plain layer could no longer be exported, although a plain Duplicate replays it exactly. Recording now drops `inPlace` when no copy is an artboard, as `layer.select` already rewrites its recorded params. Refs #7200 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2401039887
commit
aa54979583
@@ -1025,6 +1025,43 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// An Alt-drag copy records `inPlace` (#7200), which only matters for an artboard: a plain
|
||||
/// layer's recorded copy must still export to .atn, while an artboard's must be refused rather
|
||||
/// than exported as a copy beside the original.
|
||||
#[test]
|
||||
fn recorded_in_place_duplicate_keeps_in_place_only_for_artboards() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", json!({"width": 8, "height": 8})).unwrap();
|
||||
s.execute("actions.record", json!({"name": "Layer copy"})).unwrap();
|
||||
s.execute("layer.duplicate", json!({"inPlace": true})).unwrap();
|
||||
s.execute("actions.stop", json!({})).unwrap();
|
||||
s.execute("layer.new.artboard", json!({"name": "Board", "rect": [0, 0, 8, 8]})).unwrap();
|
||||
s.execute("actions.record", json!({"name": "Board copy"})).unwrap();
|
||||
s.execute("layer.duplicate", json!({"inPlace": true})).unwrap();
|
||||
s.execute("actions.stop", json!({})).unwrap();
|
||||
assert_eq!(s.actions.list[0].steps, vec![("layer.duplicate".to_string(), json!({}))]);
|
||||
assert_eq!(s.actions.list[1].steps, vec![("layer.duplicate".to_string(), json!({"inPlace": true}))]);
|
||||
assert!(crate::atn_cmds::export("Audit", &[&s.actions.list[0]], &[], false).is_ok());
|
||||
assert!(crate::atn_cmds::export("Audit", &[&s.actions.list[1]], &[], false).is_err());
|
||||
// Several selected layers are copied together: one artboard among them keeps `inPlace`.
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", json!({"width": 8, "height": 8})).unwrap();
|
||||
let photo = s.active().unwrap().active_layer.unwrap();
|
||||
s.execute("layer.new.layer", json!({"name": "Logo"})).unwrap();
|
||||
s.execute("layer.select", json!({"layer": photo.0, "mode": "add"})).unwrap();
|
||||
s.execute("actions.record", json!({"name": "Layers copy"})).unwrap();
|
||||
s.execute("layer.duplicate", json!({"inPlace": true})).unwrap();
|
||||
s.execute("actions.stop", json!({})).unwrap();
|
||||
assert_eq!(s.active().unwrap().selected_layers().len(), 2, "both layers were copied");
|
||||
s.execute("layer.new.artboard", json!({"name": "Board", "rect": [0, 0, 8, 8]})).unwrap();
|
||||
s.execute("layer.select", json!({"layer": photo.0, "mode": "add"})).unwrap();
|
||||
s.execute("actions.record", json!({"name": "Mixed copy"})).unwrap();
|
||||
s.execute("layer.duplicate", json!({"inPlace": true})).unwrap();
|
||||
s.execute("actions.stop", json!({})).unwrap();
|
||||
assert_eq!(s.actions.list[0].steps, vec![("layer.duplicate".to_string(), json!({}))]);
|
||||
assert_eq!(s.actions.list[1].steps, vec![("layer.duplicate".to_string(), json!({"inPlace": true}))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named_selection_preserves_modes_and_duplicate_occurrences() {
|
||||
let mut s = Session::new();
|
||||
|
||||
@@ -183,6 +183,20 @@ pub(crate) fn place_copy(doc: &mut Document, id: LayerId) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Record a Duplicate's `inPlace` only when a copy (the selection after it) is an artboard: for
|
||||
/// any other layer it changes nothing, and Photoshop's Duplicate can't express it, so a recorded
|
||||
/// Alt-drag copy of a plain layer must stay exportable to .atn (#7200).
|
||||
pub(crate) fn recorded_duplicate(s: &Session, mut p: Value) -> Value {
|
||||
let Some(st) = s.active() else { return p };
|
||||
if st.selected_layers().into_iter().any(|id| st.doc.layer(id).is_some_and(|l| l.artboard().is_some())) {
|
||||
return p;
|
||||
}
|
||||
if let Some(fields) = p.as_object_mut() {
|
||||
fields.remove("inPlace");
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
/// Grow the canvas so every artboard fits. Photoshop's artboard documents grow in every
|
||||
/// direction: a board left of or above the origin shifts the whole document (boards, their
|
||||
/// contents, guides, paths…) right/down by its overhang, then the canvas grows to show it. An
|
||||
|
||||
@@ -799,8 +799,11 @@ impl Session {
|
||||
self.journal.truncate(from);
|
||||
}
|
||||
if !crate::brush_cmds::coalesce_journal(self, id, ¶ms) {
|
||||
let params =
|
||||
if id == "layer.select" && self.actions.recording.is_some() { crate::layer_multi_cmds::recorded_selection(self, params) } else { params };
|
||||
let params = match id {
|
||||
"layer.select" if self.actions.recording.is_some() => crate::layer_multi_cmds::recorded_selection(self, params),
|
||||
"layer.duplicate" if self.actions.recording.is_some() => crate::artboard_cmds::recorded_duplicate(self, params),
|
||||
_ => params,
|
||||
};
|
||||
self.journal.push((id.to_string(), params));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user