mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-27 11:37:02 -06:00
M13.18: drag layer rows in the Timeline to reorder them — a line shows where they land, the selected layers move together (an unselected row moves alone), one undo step, nothing for a drop in place; layer.arrange {above: layer} for agents
This commit is contained in:
@@ -689,6 +689,18 @@ fn arrange(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let (cid, ids) = layers_p(s, p)?;
|
||||
let how = str_p(p, "to").unwrap_or("front").to_string();
|
||||
let index = p.get("index").and_then(Value::as_u64).map(|i| i as usize);
|
||||
// `above`: put the layers right above that one (the timeline's drag to reorder).
|
||||
let above = match p.get("above") {
|
||||
Some(v) => {
|
||||
let comp = s.project.comp(cid).ok_or(EngineError::NoComp)?;
|
||||
let a = resolve_layer(comp, v).ok_or_else(|| bad("layer.arrange", "no such layer for `above`"))?;
|
||||
if ids.contains(&a) {
|
||||
return Err(bad("layer.arrange", "`above` must be a layer that isn't being moved"));
|
||||
}
|
||||
Some(a)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
s.edit("Arrange Layers", None, |proj, _| {
|
||||
let comp = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
let n = comp.layers.len();
|
||||
@@ -704,6 +716,7 @@ fn arrange(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
});
|
||||
let at = match (how.as_str(), index) {
|
||||
_ if above.is_some() => comp.layers.iter().position(|l| Some(l.id) == above).unwrap_or(comp.layers.len()),
|
||||
(_, Some(i)) => i.saturating_sub(1).min(comp.layers.len()),
|
||||
("front", _) => 0,
|
||||
("back", _) => comp.layers.len(),
|
||||
@@ -1144,7 +1157,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
p["frames"] = json!(1);
|
||||
slip(s, &p)
|
||||
}),
|
||||
cmd!("layer.arrange", "Arrange", ["Layer", "Arrange"], None, "{layers?, to: front|forward|backward|back, index?}", has_layers, arrange),
|
||||
cmd!("layer.arrange", "Arrange", ["Layer", "Arrange"], None, "{layers?, to: front|forward|backward|back, index?, above?: layer}", has_layers, arrange),
|
||||
cmd!(
|
||||
"layer.precompose",
|
||||
"Pre-compose...",
|
||||
|
||||
@@ -571,3 +571,26 @@ fn time_stretch_holds_in_current_or_out() {
|
||||
assert!((ly.out_point.seconds() - 4.0).abs() < 1e-6 && (ly.in_point.seconds() - 2.0).abs() < 1e-6);
|
||||
assert!(s.execute("layer.timeStretch", json!({"layers": [l], "percent": 0})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrange_above_moves_layers_to_a_place_in_the_stack() {
|
||||
let mut s = Session::default();
|
||||
s.execute("comp.new", json!({"name": "Stack", "width": 100, "height": 100, "duration": 2})).unwrap();
|
||||
for n in ["D", "C", "B", "A"] {
|
||||
s.execute("layer.newSolid", json!({"name": n, "width": 10, "height": 10})).unwrap();
|
||||
}
|
||||
let names = |s: &Session| s.active_comp().unwrap().layers.iter().map(|l| l.name.clone()).collect::<Vec<_>>();
|
||||
assert_eq!(names(&s), ["A", "B", "C", "D"]);
|
||||
// A below C (one undo step), then B and D together above A, then C to the bottom.
|
||||
s.execute("layer.arrange", json!({"layers": ["A"], "above": "D"})).unwrap();
|
||||
assert_eq!(names(&s), ["B", "C", "A", "D"]);
|
||||
s.execute("layer.arrange", json!({"layers": ["B", "D"], "above": "A"})).unwrap();
|
||||
assert_eq!(names(&s), ["C", "B", "D", "A"]);
|
||||
s.execute("layer.arrange", json!({"layers": ["C"], "to": "back"})).unwrap();
|
||||
assert_eq!(names(&s), ["B", "D", "A", "C"]);
|
||||
s.undo();
|
||||
assert_eq!(names(&s), ["C", "B", "D", "A"]);
|
||||
// A layer can't go above itself; unknown targets fail.
|
||||
assert!(s.execute("layer.arrange", json!({"layers": ["B"], "above": "B"})).is_err());
|
||||
assert!(s.execute("layer.arrange", json!({"layers": ["B"], "above": "Nope"})).is_err());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user