mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-27 19:47:12 -06:00
Merge pull request #921 from pravdomil/claude/alt-click-layer-toggles-b7a7a3
Timeline: Alt-click an A/V feature switches the other layers
This commit is contained in:
@@ -382,6 +382,9 @@ fn set_switch(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let name = str_p(p, "switch").ok_or_else(|| bad("layer.setSwitch", "missing `switch`"))?.to_string();
|
||||
let v = b_p(p, "value");
|
||||
let label = format!("Layer Switch ({name})");
|
||||
if b_p(p, "others") == Some(true) {
|
||||
return set_switch_others(s, cid, &ids, &name, v, &label);
|
||||
}
|
||||
// `merge`: a switch dragged over several layers in the Timeline is one undo step.
|
||||
let r = s.edit(&label, merge_p(p), |proj, _| {
|
||||
let comp = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
@@ -399,6 +402,38 @@ fn set_switch(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!(r))
|
||||
}
|
||||
|
||||
/// `layer.setSwitch {others: true}` (Alt-click on an A/V feature in the Timeline): the named
|
||||
/// layers stand alone, shown / heard / soloed / unlocked while every other layer the switch
|
||||
/// applies to is the opposite; when they already do, the other layers come back. `value` sets
|
||||
/// the other layers directly.
|
||||
fn set_switch_others(s: &mut Session, cid: ItemId, ids: &[LayerId], name: &str, value: Option<bool>, label: &str) -> Result<Value> {
|
||||
let r = s.edit(label, None, |proj, _| {
|
||||
let comp = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
// The state that sets a layer apart: unlocked for Lock, on for the rest.
|
||||
let active = !matches!(name, "lock" | "locked");
|
||||
// Track mattes keep their video off by design.
|
||||
let mattes: Vec<LayerId> =
|
||||
if matches!(name, "video" | "eye") { comp.layers.iter().filter_map(|l| l.track_matte.map(|m| m.layer)).collect() } else { Vec::new() };
|
||||
let applies = |l: &Layer| match name {
|
||||
"audio" => l.props.sub("audio").is_some(),
|
||||
"lock" | "locked" => true,
|
||||
_ => l.source.is_av() && !mattes.contains(&l.id),
|
||||
};
|
||||
let mut alone = true;
|
||||
for l in comp.layers.iter().filter(|l| applies(l)) {
|
||||
let Some(on) = switch_value(l, name) else { return Err(bad("layer.setSwitch", format!("unknown switch `{name}`"))) };
|
||||
alone &= on == (ids.contains(&l.id) == active);
|
||||
}
|
||||
let others = value.unwrap_or(alone == active);
|
||||
for l in comp.layers.iter_mut().filter(|l| applies(l)) {
|
||||
let v = if ids.contains(&l.id) { active } else { others };
|
||||
set_switch_value(l, name, v);
|
||||
}
|
||||
Ok(others)
|
||||
})?;
|
||||
Ok(json!(r))
|
||||
}
|
||||
|
||||
fn switch_value(l: &Layer, name: &str) -> Option<bool> {
|
||||
let sw = &l.switches;
|
||||
Some(match name {
|
||||
@@ -1538,7 +1573,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Layer Switch",
|
||||
[],
|
||||
None,
|
||||
"{layers?, switch: video|audio|solo|lock|shy|collapse|quality|fx|frameBlend|motionBlur|adjustment|threeD|guide|preserveTransparency, value?, merge?}",
|
||||
"{layers?, switch: video|audio|solo|lock|shy|collapse|quality|fx|frameBlend|motionBlur|adjustment|threeD|guide|preserveTransparency, value?, merge?, others? (the named layers alone: the other layers get the opposite state, or come back)}",
|
||||
has_layers,
|
||||
set_switch
|
||||
),
|
||||
|
||||
@@ -52,6 +52,41 @@ fn layer_quality_sampling_frame_blending_and_undo() {
|
||||
assert!(s.execute("layer.quality", json!({"quality": "fancy"})).is_err());
|
||||
}
|
||||
|
||||
/// `layer.setSwitch {others: true}` (Alt-click on an A/V feature): the layer stands alone, the
|
||||
/// other layers come back the next time.
|
||||
#[test]
|
||||
fn set_switch_others() {
|
||||
let mut s = comp();
|
||||
let a = solid(&mut s, "#ff0000");
|
||||
let b = solid(&mut s, "#00ff00");
|
||||
let c = solid(&mut s, "#0000ff");
|
||||
let get = |s: &Session, f: fn(&effectcraft_project::Layer) -> bool| [a, b, c].map(|l| f(&layer(s, l)));
|
||||
s.execute("layer.setSwitch", json!({"layers": [b], "switch": "video", "value": false})).unwrap();
|
||||
// B is hidden: it is shown, alone; then the others come back; one undo step each.
|
||||
s.execute("layer.setSwitch", json!({"layers": [b], "switch": "video", "others": true})).unwrap();
|
||||
assert_eq!(get(&s, |l| l.switches.video), [false, true, false]);
|
||||
s.execute("layer.setSwitch", json!({"layers": [b], "switch": "video", "others": true})).unwrap();
|
||||
assert_eq!(get(&s, |l| l.switches.video), [true; 3]);
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(get(&s, |l| l.switches.video), [false, true, false]);
|
||||
// Solo.
|
||||
s.execute("layer.setSwitch", json!({"layers": [a], "switch": "solo", "value": true})).unwrap();
|
||||
s.execute("layer.setSwitch", json!({"layers": [c], "switch": "solo", "others": true})).unwrap();
|
||||
assert_eq!(get(&s, |l| l.switches.solo), [false, false, true]);
|
||||
// Lock: the layer stays unlocked and the others lock, then unlock.
|
||||
s.execute("layer.setSwitch", json!({"layers": [a], "switch": "lock", "others": true})).unwrap();
|
||||
assert_eq!(get(&s, |l| l.switches.locked), [false, true, true]);
|
||||
s.execute("layer.setSwitch", json!({"layers": [a], "switch": "lock", "others": true})).unwrap();
|
||||
assert_eq!(get(&s, |l| l.switches.locked), [false; 3]);
|
||||
// A track matte keeps its video off.
|
||||
s.execute("layer.setTrackMatte", json!({"layer": b, "matte": a})).unwrap();
|
||||
s.execute("layer.setSwitch", json!({"layers": [a], "switch": "video", "value": false})).unwrap();
|
||||
s.execute("layer.setSwitch", json!({"layers": [c], "switch": "video", "others": true})).unwrap();
|
||||
s.execute("layer.setSwitch", json!({"layers": [c], "switch": "video", "others": true})).unwrap();
|
||||
assert_eq!(get(&s, |l| l.switches.video), [false, true, true]);
|
||||
assert!(s.execute("layer.setSwitch", json!({"layers": [c], "switch": "nope", "others": true})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_switches_menu() {
|
||||
let mut s = comp();
|
||||
|
||||
Reference in New Issue
Block a user