diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index a6243e44f..5f3593ac4 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -757,15 +757,17 @@ impl Session { true } - /// Drop selections that no longer exist. + /// Drop selections that no longer exist. Every comp keeps its own layer selection (After + /// Effects), so selections in comps that are not open survive; only ids that are gone from + /// every comp are dropped (#1001). pub fn sanitize_state(&mut self) { let p = self.project.clone(); self.state.open_comps.retain(|c| p.comp(*c).is_some()); if self.state.active_comp.is_some_and(|c| p.comp(c).is_none()) { self.state.active_comp = self.state.open_comps.first().copied(); } - if let Some(c) = self.active_comp() { - let ids: Vec = c.layers.iter().map(|l| l.id).collect(); + { + let ids: Vec = p.comps().flat_map(|(_, c)| c.layers.iter().map(|l| l.id)).collect(); self.state.selected_layers.retain(|l| ids.contains(l)); self.state.selected_props.retain(|(l, _)| ids.contains(l)); self.state.selected_keys.retain(|k| ids.contains(&k.layer)); diff --git a/crates/script/src/tests.rs b/crates/script/src/tests.rs index 303bd820a..c18faee71 100644 --- a/crates/script/src/tests.rs +++ b/crates/script/src/tests.rs @@ -698,6 +698,36 @@ fn group_selected_false_deselects_masks_and_effects() { assert_eq!(out.result, json!([[true, true], [false, true], [false, false]])); } +/// A layer selected by script in a comp that is not open stays selected for the next script: +/// every comp keeps its own layer selection (#1001). +#[test] +fn layer_selection_in_a_closed_comp_survives_to_the_next_script() { + let mut s = session(); + let out = ok( + &mut s, + r#" + var a = app.project.items.addComp("Active", 400, 300, 1, 2, 25); + a.openInViewer(); + var c = app.project.items.addComp("Other", 400, 300, 1, 2, 25); + c.layers.addText("A"); + c.layers.addText("B"); + c.layer("B").selected = true; + c.selectedLayers.length + " " + c.layer("B").selected + "#, + ); + assert_eq!(out.result, json!("1 true")); + // A second script call, the way a panel talks to the app over the control channel. + let out = ok( + &mut s, + r#" + var c = null; + for (var k = 1; k <= app.project.numItems; k++) { if (app.project.item(k).name === "Other") c = app.project.item(k); } + c.selectedLayers.length + " " + c.layer("B").selected + "#, + ); + assert_eq!(out.result, json!("1 true")); +} + /// Probes every file as a 64x32 still (the media layer's single-file probe). struct Stills; impl effectcraft_engine::Importer for Stills {