mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-26 02:57:34 -06:00
Merge pull request #1730
# Conflicts: # crates/engine/src/commands/prop_groups.rs
This commit is contained in:
@@ -143,8 +143,9 @@ pub(crate) fn cut_contents(s: &mut Session, items: &[(LayerId, Uid)]) -> Result<
|
||||
}
|
||||
|
||||
/// Edit ▸ Paste with shape items on the clipboard: copies go into each selected shape layer's
|
||||
/// Contents, above its topmost selected item (in that item's group), else on top. Names stay
|
||||
/// unique in their group ("Rectangle 2"…), and the copies are selected.
|
||||
/// Contents, above its topmost selected item (in that item's group), else on top (paints and path
|
||||
/// operations alone: at the bottom). Names stay unique in their group ("Rectangle 2"…), and the
|
||||
/// copies are selected.
|
||||
pub(crate) fn paste_contents(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let c = "edit.paste";
|
||||
let clip = s.state.contents_clipboard.clone();
|
||||
@@ -156,6 +157,9 @@ pub(crate) fn paste_contents(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
return Err(bad(c, "select a shape layer to paste shape items into"));
|
||||
}
|
||||
let sel = selected_contents(s);
|
||||
let acts_on_above = clip.iter().all(|g| !matches!(g.match_id.as_str(), "group" | "rect" | "ellipse" | "star" | "path"));
|
||||
// Merge Paths clears the draws above it, so at the bottom it would erase the paints (#1681).
|
||||
let has_merge = clip.iter().any(|g| g.match_id == "merge");
|
||||
let label = match clip.as_slice() {
|
||||
[g] => format!("Paste {}", g.name),
|
||||
_ => "Paste".to_string(),
|
||||
@@ -170,11 +174,19 @@ pub(crate) fn paste_contents(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let parent = l.props.parent_of(*u)?;
|
||||
Some((parent.uid, parent.children.iter().position(|n| n.uid() == *u)?))
|
||||
});
|
||||
let unselected = spot.is_none();
|
||||
let (parent, at) = match spot {
|
||||
Some((pu, i)) => (l.props.find_group_mut(pu), i),
|
||||
None => (l.props.sub_mut("contents"), 0),
|
||||
};
|
||||
let parent = parent.ok_or_else(|| bad(c, "the layer has no contents"))?;
|
||||
// Trim Paths, Wiggle Paths, fills… act on the items above them (as Add places them),
|
||||
// so with nothing selected they go to the bottom: on top they'd affect nothing (#1681).
|
||||
let at = match unselected {
|
||||
true if has_merge => merge_spot(parent),
|
||||
true if acts_on_above => parent.children.len(),
|
||||
_ => at,
|
||||
};
|
||||
for (k, g) in clip.iter().enumerate() {
|
||||
let mut g = g.clone();
|
||||
g.reassign_uids(&mut next);
|
||||
@@ -190,6 +202,18 @@ pub(crate) fn paste_contents(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!({"contents": pasted.iter().map(|(_, u)| *u).collect::<Vec<_>>()}))
|
||||
}
|
||||
|
||||
/// Where Merge Paths goes in `contents` when nothing is selected: before the first paint, so the
|
||||
/// paints still draw the merged path (the renderer drops the draws above a Merge Paths), else after
|
||||
/// the last path item.
|
||||
fn merge_spot(contents: &PropGroup) -> usize {
|
||||
let is = |n: &Node, ids: &[&str]| matches!(n, Node::Group(g) if ids.contains(&g.match_id.as_str()));
|
||||
let paints = ["fill", "stroke", "gfill", "gstroke"];
|
||||
match contents.children.iter().position(|n| is(n, &paints)) {
|
||||
Some(i) => i,
|
||||
None => contents.children.iter().rposition(|n| is(n, &["rect", "ellipse", "star", "path"])).map_or(0, |i| i + 1),
|
||||
}
|
||||
}
|
||||
|
||||
/// Edit ▸ Duplicate with shape items, text animators or selectors selected: each is copied in
|
||||
/// place, above the original and named as After Effects does ("Rectangle 1" → "Rectangle 2"),
|
||||
/// and the copies are selected.
|
||||
|
||||
@@ -1595,6 +1595,40 @@ fn shape_contents_copy_cut_and_paste_between_shape_layers() {
|
||||
assert!(s.execute("edit.paste", json!({})).is_err());
|
||||
}
|
||||
|
||||
/// Pasting Trim Paths into another shape layer with nothing selected puts it below the shapes,
|
||||
/// where it acts on them (it acts on the items above it); on top it would trim nothing (#1681, #1629).
|
||||
#[test]
|
||||
fn pasted_path_operator_goes_below_the_shapes_it_acts_on() {
|
||||
let mut s = comp();
|
||||
let a = s.execute("layer.newShape", json!({"kind": "rect"})).unwrap()["layer"].as_u64().unwrap();
|
||||
let trim = s.execute("layer.addShapeItem", json!({"layer": a, "kind": "trim"})).unwrap()["uid"].as_u64().unwrap();
|
||||
let b = s.execute("layer.newShape", json!({"kind": "star", "name": "B"})).unwrap()["layer"].as_u64().unwrap();
|
||||
s.execute("prop.select", json!({"layer": a, "prop": trim})).unwrap();
|
||||
s.execute("edit.copy", json!({})).unwrap();
|
||||
s.execute("layer.select", json!({"layers": [b]})).unwrap();
|
||||
s.execute("edit.paste", json!({})).unwrap();
|
||||
assert_eq!(contents_names(&s, b), ["Polystar 1", "Trim Paths 1"]);
|
||||
}
|
||||
|
||||
/// Pasting Merge Paths onto a painted layer with nothing selected keeps the paint: the renderer
|
||||
/// drops the draws above a Merge Paths, so it goes between the path and the paints (#1681).
|
||||
#[test]
|
||||
fn pasted_merge_paths_keeps_the_targets_paint() {
|
||||
let mut s = comp();
|
||||
let a = s.execute("layer.newShape", json!({"kind": "none", "name": "A"})).unwrap()["layer"].as_u64().unwrap();
|
||||
let merge = s.execute("layer.addShapeItem", json!({"layer": a, "kind": "merge"})).unwrap()["uid"].as_u64().unwrap();
|
||||
let b = s.execute("layer.newShape", json!({"kind": "none", "name": "B"})).unwrap()["layer"].as_u64().unwrap();
|
||||
s.execute("layer.addShapeItem", json!({"layer": b, "kind": "rect"})).unwrap();
|
||||
s.execute("layer.addShapeItem", json!({"layer": b, "kind": "fill"})).unwrap();
|
||||
let before = opaque_centroid(&s).2;
|
||||
s.execute("prop.select", json!({"layer": a, "prop": merge})).unwrap();
|
||||
s.execute("edit.copy", json!({})).unwrap();
|
||||
s.execute("layer.select", json!({"layers": [b]})).unwrap();
|
||||
s.execute("edit.paste", json!({})).unwrap();
|
||||
assert_eq!(contents_names(&s, b), ["Rectangle Path 1", "Merge Paths 1", "Fill 1"]);
|
||||
assert_eq!(opaque_centroid(&s).2, before);
|
||||
}
|
||||
|
||||
/// Edit ▸ Duplicate with shape items selected duplicates them in their layer, above the
|
||||
/// original and named "Rectangle 2", not the layer (#227).
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user