Merge pull request #1037 from vbasky/fix/1001-selection-persist

# Conflicts:
#	crates/engine/src/lib.rs
This commit is contained in:
Brandon Thomas committed 2026-10-13 04:34:34 -04:00
commit 286a521598
5 files changed
+149 -15

No files matched your search

+23 -2
View File
@@ -97,6 +97,27 @@ fn label_index(l: Label) -> usize {
/// A comp's selected properties as `[layer, uid]`: the selected ones, then those with selected
/// keyframes (selecting a keyframe selects its property in After Effects, so scripts find
/// selected keys through `selectedProperties`).
/// Layer ids selected in `comp`, including a comp that is not the active one (#1001).
fn selected_layer_ids(s: &Session, comp: effectcraft_project::ItemId) -> Vec<u64> {
let layers = if s.active_comp_id() == Some(comp) {
&s.state.selected_layers
} else {
match s.state.comp_selections.get(&comp) {
Some(sel) => &sel.layers,
None => return Vec::new(),
}
};
let Some(c) = s.project.comp(comp) else { return Vec::new() };
layers.iter().filter(|l| c.layer(**l).is_some()).map(|l| l.0).collect()
}
fn layer_selected(s: &Session, comp: effectcraft_project::ItemId, layer: effectcraft_project::LayerId) -> bool {
if s.active_comp_id() == Some(comp) {
return s.state.selected_layers.contains(&layer);
}
s.state.comp_selections.get(&comp).is_some_and(|sel| sel.layers.contains(&layer))
}
fn selected_props(s: &Session, c: &effectcraft_project::Comp) -> Vec<J> {
let mut v: Vec<(u64, u64)> = vec![];
let keyed = s.state.selected_keys.iter().map(|k| (k.layer, k.prop));
@@ -135,7 +156,7 @@ fn item(s: &Session, a: &J) -> R {
ItemKind::Comp(c) => {
let cid = id;
o["kind"] = json!("comp");
let sel: Vec<u64> = s.state.selected_layers.iter().filter(|l| c.layer(**l).is_some()).map(|l| l.0).collect();
let sel = selected_layer_ids(s, cid);
let fps = c.frame_rate.as_f64();
if let Some(m) = o.as_object_mut() {
m.extend(
@@ -246,7 +267,7 @@ fn layer(s: &Session, a: &J) -> R {
"isTrackMatte": c.layers.iter().any(|x| x.track_matte.is_some_and(|m| m.layer == l.id)),
"label": label_index(l.label),
"comment": l.comment,
"selected": s.state.selected_layers.contains(&l.id),
"selected": layer_selected(s, cid, l.id),
"autoOrient": format!("{:?}", l.auto_orient),
"width": w, "height": h,
"hasVideo": l.source.is_av(),
+2 -2
View File
@@ -151,7 +151,7 @@ pub(crate) fn exec(s: &mut Session, id: &str, params: J) -> Result<J, String> {
if let Some(c) = target
&& prev != Some(c)
{
s.state.active_comp = Some(c);
s.activate_comp(c);
}
if CREATES_LAYER.contains(&id) {
s.state.selected_layers.clear();
@@ -163,7 +163,7 @@ pub(crate) fn exec(s: &mut Session, id: &str, params: J) -> Result<J, String> {
&& id != "comp.open"
{
match prev {
Some(p) if s.project.comp(p).is_some() => s.state.active_comp = Some(p),
Some(p) if s.project.comp(p).is_some() => s.activate_comp(p),
_ => {
if !s.state.open_comps.contains(&c) {
s.state.open_comps.push(c);
+30
View File
@@ -766,6 +766,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 + " " + a.selectedLayers.length
"#,
);
assert_eq!(out.result, json!("1 true 0"));
// 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 {