From 424dcab40b8e5119f91ae40f02a0395bb498e3e8 Mon Sep 17 00:00:00 2001 From: Marco Arnulfo Date: Mon, 12 Oct 2026 14:43:18 +0200 Subject: [PATCH] Shortcuts: Numpad * marks the selected layers (#976) Numpad * always added a composition marker. As in After Effects, it now adds a layer marker to the selected layers at the current time, and a composition marker only when no layer is selected. --- crates/ui-egui/src/menus.rs | 5 +++++ crates/ui-egui/tests/ui_timeline_zoom.rs | 27 +++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index b10b0e31..dda1b617 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -1744,6 +1744,11 @@ fn handle_shortcuts_impl(app: &mut EffectcraftApp, ctx: &egui::Context, tab_only // comes as text. if !tab_only && clipboard && app.dialog.is_none() && ctx.input(|i| i.events.iter().any(|e| matches!(e, egui::Event::Text(t) if t == "*"))) { let bound = app.session.shortcuts().bindings().into_iter().find(|(k, _)| *k == "Num*").map(|(_, b)| (b.command.clone(), b.params.clone())); + // As in After Effects, it marks the selected layers; only with none selected does it add + // a composition marker (#976). + let bound = bound.map(|(id, params)| { + if id == "comp.addMarker" && !app.session.state.selected_layers.is_empty() { ("layer.addMarker".to_owned(), params) } else { (id, params) } + }); if let Some((id, params)) = bound && let Err(e) = invoke(app, ctx, &id, if params.is_null() { json!({}) } else { params }) { diff --git a/crates/ui-egui/tests/ui_timeline_zoom.rs b/crates/ui-egui/tests/ui_timeline_zoom.rs index 2c802600..601bcd6f 100644 --- a/crates/ui-egui/tests/ui_timeline_zoom.rs +++ b/crates/ui-egui/tests/ui_timeline_zoom.rs @@ -333,13 +333,38 @@ fn dragging_out_of_the_marker_bin_adds_a_comp_marker() { assert_eq!(markers(&h), vec![13.0]); let history = &h.state().session.history.undo; assert_eq!((history.len(), history.last().map(|u| u.0.as_str())), (undo + 1, Some("Add Marker")), "one undo step"); - // Numpad * (it arrives as text): Add Marker at the current time. + // Numpad * (it arrives as text) with no layer selected: Add Marker at the current time. + h.state_mut().session.execute("edit.deselectAll", json!({})).unwrap(); h.state_mut().session.execute("time.set", json!({"time": 20.0})).unwrap(); h.event(Event::Text("*".into())); h.run_steps(2); assert_eq!(markers(&h), vec![13.0, 20.0]); } +/// #976: as in After Effects, Numpad * marks the selected layers at the current time; only with +/// no layer selected does it add a composition marker. +#[test] +fn numpad_star_marks_the_selected_layer() { + let (mut h, layer) = harness(); + let comp_markers = + |h: &Harness<'_, EffectcraftApp>| -> Vec { h.state().session.active_comp().unwrap().markers.iter().map(|m| m.time.seconds()).collect() }; + let layer_markers = |h: &Harness<'_, EffectcraftApp>| -> Vec { + let comp = h.state().session.active_comp().unwrap(); + comp.layer(effectcraft_engine::project::LayerId(layer)).unwrap().markers.iter().map(|m| m.time.seconds()).collect() + }; + let star = |h: &mut Harness<'_, EffectcraftApp>, time: f64| { + h.state_mut().session.execute("time.set", json!({"time": time})).unwrap(); + h.event(Event::Text("*".into())); + h.run_steps(2); + }; + h.state_mut().session.execute("layer.select", json!({"layers": [layer]})).unwrap(); + star(&mut h, 2.0); + assert_eq!((layer_markers(&h), comp_markers(&h)), (vec![2.0], vec![])); + h.state_mut().session.execute("edit.deselectAll", json!({})).unwrap(); + star(&mut h, 3.0); + assert_eq!((layer_markers(&h), comp_markers(&h)), (vec![2.0], vec![3.0])); +} + /// #468: zoomed in, a CTI scrub held past the right end of the ruler scrolls the timeline (the /// CTI rides the edge), past the left end scrolls it back; during playback the view pages to /// keep the CTI in sight.