mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-22 03:32:39 -05:00
Merge pull request #1711
This commit is contained in:
commit
415e353dc0
3 files changed
+50
-4
No files matched your search
@@ -115,6 +115,11 @@ fn duplicate(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if !items.is_empty() {
|
||||
return super::prop_groups::duplicate_contents(s, &items);
|
||||
}
|
||||
// Text animators or their selectors selected → duplicate them inside their text layers.
|
||||
let items = super::prop_groups::selected_animator_items(s);
|
||||
if !items.is_empty() {
|
||||
return super::prop_groups::duplicate_contents(s, &items);
|
||||
}
|
||||
}
|
||||
let (cid, ids) = layers_p(s, p)?;
|
||||
let new = s.edit("Duplicate", None, |proj, st| {
|
||||
|
||||
@@ -100,6 +100,22 @@ pub(crate) fn selected_contents(s: &Session) -> Vec<(LayerId, Uid)> {
|
||||
out
|
||||
}
|
||||
|
||||
/// Text animators and their selectors selected on the active comp's text layers, top to bottom.
|
||||
/// A selector inside a selected animator isn't listed, since it is copied with it.
|
||||
pub(crate) fn selected_animator_items(s: &Session) -> Vec<(LayerId, Uid)> {
|
||||
let mut out = vec![];
|
||||
let Some(c) = s.active_comp().filter(|_| !s.state.selected_props.is_empty()) else { return out };
|
||||
for l in c.layers.iter().filter(|l| matches!(l.source, LayerSource::Text)) {
|
||||
if let Some(animators) = l.props.group("text/animators") {
|
||||
let selected = |u: Uid| s.state.selected_props.contains(&(l.id, u));
|
||||
let mut uids = vec![];
|
||||
collect_selected(animators, &selected, &mut uids, 0);
|
||||
out.extend(uids.into_iter().map(|u| (l.id, u)));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn collect_selected(g: &PropGroup, selected: &dyn Fn(Uid) -> bool, out: &mut Vec<Uid>, depth: usize) {
|
||||
if depth > super::shape_stroke::MAX_DEPTH {
|
||||
return;
|
||||
@@ -174,8 +190,9 @@ pub(crate) fn paste_contents(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!({"contents": pasted.iter().map(|(_, u)| *u).collect::<Vec<_>>()}))
|
||||
}
|
||||
|
||||
/// Edit ▸ Duplicate with shape items selected: each is copied in place, above the original and
|
||||
/// named as After Effects does ("Rectangle 1" → "Rectangle 2"), and the copies are selected.
|
||||
/// 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.
|
||||
pub(crate) fn duplicate_contents(s: &mut Session, items: &[(LayerId, Uid)]) -> Result<Value> {
|
||||
let c = "edit.duplicate";
|
||||
let cid = s.active_comp_id().ok_or(EngineError::NoComp)?;
|
||||
@@ -183,7 +200,7 @@ pub(crate) fn duplicate_contents(s: &mut Session, items: &[(LayerId, Uid)]) -> R
|
||||
let mut next = proj.next_id;
|
||||
let mut out = vec![];
|
||||
for (lid, uid) in items {
|
||||
let parent = layer_mut(proj, cid, *lid)?.props.parent_of_mut(*uid).ok_or_else(|| bad(c, "the shape item is gone"))?;
|
||||
let parent = layer_mut(proj, cid, *lid)?.props.parent_of_mut(*uid).ok_or_else(|| bad(c, "the group is gone"))?;
|
||||
let Some((i, Node::Group(g))) = parent.children.iter().enumerate().find(|(_, n)| n.uid() == *uid) else { continue };
|
||||
let mut g = g.clone();
|
||||
g.reassign_uids(&mut next);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! undo/redo and serde round-trips).
|
||||
|
||||
use effectcraft_keyframe::Value as KV;
|
||||
use effectcraft_project::{Node, Project, PropGroup};
|
||||
use effectcraft_project::{LayerId, Node, Project, PropGroup};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::Session;
|
||||
@@ -244,3 +244,27 @@ fn menus_have_text_animation_entries() {
|
||||
assert!(s.is_enabled("layer.enablePerChar3D"));
|
||||
assert!(!s.is_enabled("layer.addTextSelector"), "needs an animator");
|
||||
}
|
||||
|
||||
/// Duplicate with a Range Selector selected under a text animator copies the selector, not the
|
||||
/// whole text layer (#1665); a selected animator is copied the same way.
|
||||
#[test]
|
||||
fn duplicate_copies_selected_text_selector_not_the_layer() {
|
||||
let (mut s, t) = session();
|
||||
s.execute("layer.addTextAnimator", json!({"layer": t, "properties": ["tracking"]})).unwrap();
|
||||
let anims = |s: &Session| s.active_comp().unwrap().layer(LayerId(t)).unwrap().props.group("text/animators").unwrap().clone();
|
||||
let selectors = |s: &Session| -> Vec<u64> { anims(s).groups().next().unwrap().sub("selectors").unwrap().groups().map(|g| g.uid).collect() };
|
||||
let sel = selectors(&s)[0];
|
||||
|
||||
s.execute("prop.select", json!({"layer": t, "prop": sel, "selectKeys": false})).unwrap();
|
||||
s.execute("edit.duplicate", json!({})).unwrap();
|
||||
assert_eq!(s.active_comp().unwrap().layers.len(), 1, "no second text layer");
|
||||
assert_eq!(selectors(&s).len(), 2, "the selector is copied");
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(selectors(&s), vec![sel]);
|
||||
|
||||
let anim = anims(&s).groups().next().unwrap().uid;
|
||||
s.execute("prop.select", json!({"layer": t, "prop": anim, "selectKeys": false})).unwrap();
|
||||
s.execute("edit.duplicate", json!({})).unwrap();
|
||||
assert_eq!(s.active_comp().unwrap().layers.len(), 1);
|
||||
assert_eq!(anims(&s).groups().count(), 2, "the animator is copied");
|
||||
}
|
||||
Reference in new issue
Block a user