diff --git a/crates/ui-egui/src/panels/timeline.rs b/crates/ui-egui/src/panels/timeline.rs index 281d3e9f..c8315c97 100644 --- a/crates/ui-egui/src/panels/timeline.rs +++ b/crates/ui-egui/src/panels/timeline.rs @@ -1601,7 +1601,20 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } } RowKind::Group { uid, name, open, has_children, fx, eye } => { - lp.rect_filled(left, 0.0, if ri % 2 == 0 { t.row } else { t.row_alt }); + // Selected groups (an effect, a mask, a shape group…) highlight like selected + // property rows. + let sel_group = app.session.state.selected_props.contains(&(layer.id, *uid)); + lp.rect_filled( + left, + 0.0, + if sel_group { + t.row_selected + } else if ri % 2 == 0 { + t.row + } else { + t.row_alt + }, + ); gp.rect_filled(Rect::from_min_max(pos2(graph_x0, r.min.y), r.max), 0.0, t.tl_bg); let indent = cw.name + 6.0 + 14.0 * row.depth as f32; if *has_children { diff --git a/crates/ui-egui/tests/ui_workflow_selection.rs b/crates/ui-egui/tests/ui_workflow_selection.rs index 4e849dca..1880bb86 100644 --- a/crates/ui-egui/tests/ui_workflow_selection.rs +++ b/crates/ui-egui/tests/ui_workflow_selection.rs @@ -372,3 +372,41 @@ fn workflow_snapshot() { h.run_steps(3); h.render().expect("offscreen UI render").save(out).unwrap(); } + +/// Filled rects painted this frame (flattening nested shape lists). +fn filled_rects(h: &Harness<'_, EffectcraftApp>) -> Vec<(Rect, egui::Color32)> { + fn walk(s: &egui::Shape, out: &mut Vec<(Rect, egui::Color32)>) { + match s { + egui::Shape::Rect(r) => out.push((r.rect, r.fill)), + egui::Shape::Vec(v) => v.iter().for_each(|s| walk(s, out)), + _ => {} + } + } + let mut out = vec![]; + for c in &h.output().shapes { + walk(&c.shape, &mut out); + } + out +} + +/// Issues #91 / #97: clicking an effect's name in the Timeline selects it, and its row then +/// highlights like a selected property row (it used to stay unhighlighted). +#[test] +fn timeline_selected_effect_row_highlights() { + let mut h = harness(); + let id = h.state().session.active_comp().unwrap().layers[0].id; + let fx = h.state_mut().session.execute("effect.apply", json!({"layers": [id.0], "effect": "Gaussian Blur"})).unwrap()["effects"][0].as_u64().unwrap(); + let fx_group = h.state().session.active_comp().unwrap().layer(id).unwrap().props.sub("effects").unwrap().uid; + h.state_mut().ui.timeline.open_layers.insert(id.0); + h.state_mut().ui.timeline.open_groups.insert(fx_group); + // `effect.apply` selects the new effect; start from no property selection. + h.state_mut().session.state.selected_props.clear(); + h.run_steps(3); + let row_selected = h.state().tokens.row_selected; + let name = rect(&h, &format!("timeline.group.{fx}.name")); + let highlighted = |h: &Harness<'_, EffectcraftApp>| filled_rects(h).iter().any(|(r, c)| *c == row_selected && r.contains(name.center())); + assert!(!highlighted(&h), "not highlighted before it is selected"); + click(&mut h, name.center(), Modifiers::NONE); + assert!(h.state().session.state.selected_props.contains(&(id, fx)), "the click selects the effect"); + assert!(highlighted(&h), "the selected effect's row is highlighted"); +}