Edit: Delete removes selected animators, masks and shape groups (#151)
Selecting a property group also selects its layer, and edit.clear only looked for selected keys, mask vertices, puppet pins and effects before deleting the selected layers, so Delete with a text animator, mask or shape group selected deleted the whole layer. edit.clear now removes the selected removable groups (text animators, masks, shape items, trackers) of the active comp in one undo step before falling back to layers. It shares the removal with prop.removeGroup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2448deeb40
commit
31fc517baa
|
|
@ -147,6 +147,12 @@ fn delete(s: &mut Session, p: &Value) -> Result<Value> {
|
|||
if p.get("layers").is_none() && !super::effect::selected_effects(s).is_empty() {
|
||||
return s.execute("effect.remove", json!({}));
|
||||
}
|
||||
// Masks, shape items, text animators or trackers selected → remove them.
|
||||
if p.get("layers").is_none()
|
||||
&& let Some(n) = super::prop_groups::remove_selected(s)?
|
||||
{
|
||||
return Ok(n);
|
||||
}
|
||||
let (cid, ids) = layers_p(s, p)?;
|
||||
let ids = super::unlocked(s, cid, ids, "edit.clear")?;
|
||||
// Children of deleted layers are unparented where they are (like After Effects).
|
||||
|
|
|
|||
|
|
@ -51,17 +51,38 @@ fn set_enabled(s: &mut Session, p: &Value) -> Result<Value> {
|
|||
}
|
||||
|
||||
fn remove(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let c = "prop.removeGroup";
|
||||
let (cid, lid, uid) = group_ref(s, p, c, true)?;
|
||||
let (cid, lid, uid) = group_ref(s, p, "prop.removeGroup", true)?;
|
||||
remove_groups(s, cid, &[(lid, uid)])?;
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
/// Remove groups of comp `cid` in one undo step.
|
||||
fn remove_groups(s: &mut Session, cid: ItemId, groups: &[(LayerId, Uid)]) -> Result<()> {
|
||||
s.edit("Delete", None, |proj, st| {
|
||||
let l = layer_mut(proj, cid, lid)?;
|
||||
let parent = l.props.parent_of_mut(uid).ok_or_else(|| bad(c, "group vanished"))?;
|
||||
parent.children.retain(|n| n.uid() != uid);
|
||||
st.selected_props.retain(|(_, u)| *u != uid);
|
||||
Ok(Value::Null)
|
||||
for (lid, uid) in groups {
|
||||
// A group inside one removed before it went with it.
|
||||
if let Some(parent) = layer_mut(proj, cid, *lid)?.props.parent_of_mut(*uid) {
|
||||
parent.children.retain(|n| n.uid() != *uid);
|
||||
}
|
||||
}
|
||||
st.selected_props.retain(|(_, u)| !groups.iter().any(|(_, g)| g == u));
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Edit ▸ Clear with masks, shape items, text animators or trackers selected in the active comp:
|
||||
/// removes those groups (not their layers) in one undo step. None when none are selected.
|
||||
pub(crate) fn remove_selected(s: &mut Session) -> Result<Option<Value>> {
|
||||
let (Some(cid), Some(c)) = (s.active_comp_id(), s.active_comp()) else { return Ok(None) };
|
||||
let groups: Vec<(LayerId, Uid)> =
|
||||
s.state.selected_props.iter().filter(|(lid, uid)| c.layer(*lid).and_then(|l| l.props.find_group(*uid)).is_some_and(is_instance)).copied().collect();
|
||||
if groups.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
remove_groups(s, cid, &groups)?;
|
||||
Ok(Some(json!(groups.len())))
|
||||
}
|
||||
|
||||
fn duplicate(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let c = "prop.duplicateGroup";
|
||||
let (cid, lid, uid) = group_ref(s, p, c, true)?;
|
||||
|
|
|
|||
|
|
@ -124,3 +124,46 @@ fn property_group_edits() {
|
|||
let tr = s.active_comp().unwrap().layers[0].props.sub("transform").unwrap().uid;
|
||||
assert!(s.execute("prop.removeGroup", json!({"layer": l, "prop": tr})).is_err());
|
||||
}
|
||||
|
||||
/// Delete with a text animator, mask or shape group selected removes just those groups, in one
|
||||
/// undo step, and keeps their layers (#151). A fixed group selected still deletes the layer.
|
||||
#[test]
|
||||
fn delete_removes_selected_groups_not_their_layer() {
|
||||
let mut s = Session::default();
|
||||
s.execute("comp.new", json!({"name": "C", "width": 64, "height": 64, "duration": 1})).unwrap();
|
||||
let t = s.execute("layer.newText", json!({"text": "ABCD"})).unwrap()["layer"].as_u64().unwrap();
|
||||
s.execute("layer.addTextAnimator", json!({"layer": t, "properties": ["tracking"]})).unwrap();
|
||||
let sh = s.execute("layer.newShape", json!({"kind": "star"})).unwrap()["layer"].as_u64().unwrap();
|
||||
s.execute("layer.addMask", json!({"layer": sh, "shape": "ellipse"})).unwrap();
|
||||
let groups = |s: &Session, l: u64, path: &str| -> Vec<u64> {
|
||||
s.active_comp().unwrap().layer(LayerId(l)).map_or(vec![], |l| l.props.group(path).unwrap().groups().map(|g| g.uid).collect())
|
||||
};
|
||||
let anim = groups(&s, t, "text/animators")[0];
|
||||
let (mask, shape) = (groups(&s, sh, "masks")[0], groups(&s, sh, "contents")[0]);
|
||||
let delete = |s: &mut Session| {
|
||||
let undo = s.history.undo.len();
|
||||
s.execute("edit.clear", json!({})).unwrap();
|
||||
assert_eq!(s.history.undo.len(), undo + 1, "one undo step");
|
||||
};
|
||||
|
||||
s.execute("prop.select", json!({"layer": t, "prop": anim, "selectKeys": false})).unwrap();
|
||||
let before = s.project.clone();
|
||||
delete(&mut s);
|
||||
assert!(groups(&s, t, "text/animators").is_empty(), "the animator is gone");
|
||||
assert_eq!(s.active_comp().unwrap().layers.len(), 2, "its layer stays");
|
||||
assert!(s.state.selected_props.is_empty());
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(s.project, before);
|
||||
|
||||
s.execute("prop.select", json!({"layer": sh, "prop": mask, "selectKeys": false})).unwrap();
|
||||
s.execute("prop.select", json!({"layer": sh, "prop": shape, "selectKeys": false, "add": true})).unwrap();
|
||||
delete(&mut s);
|
||||
assert!(groups(&s, sh, "masks").is_empty());
|
||||
assert!(!groups(&s, sh, "contents").contains(&shape));
|
||||
assert_eq!(s.active_comp().unwrap().layers.len(), 2);
|
||||
|
||||
let tr = s.active_comp().unwrap().layer(LayerId(t)).unwrap().props.sub("transform").unwrap().uid;
|
||||
s.execute("prop.select", json!({"layer": t, "prop": tr, "selectKeys": false})).unwrap();
|
||||
delete(&mut s);
|
||||
assert!(s.active_comp().unwrap().layer(LayerId(t)).is_none(), "a fixed group selected: the layer goes");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue