Timeline: a selected key selects its property and layer (#252)

Clicking (or marquee-selecting) keyframes in the Timeline selected only
the keys. Their property and layer stayed unselected, so the Graph
Editor, which shows the selected properties or else the animated
properties of the selected layers, came up empty until the property
was selected separately. As in After Effects, the Timeline's key
selection now selects the keys' properties and layers too (Shift adds
to them). keys.select takes this as `selectProperties`; the Graph
Editor's own key clicks leave what it shows alone.

Test: a_key_clicked_in_the_timeline_shows_in_the_graph_editor
(ui_keyframes).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
bflatasticandClaude Opus 5.5 committed 2026-10-08 16:19:53 +07:00
1 parent 72a2d47131
commit 8d5e4ae0ef
3 files changed
+38 -3

No files matched your search

+19 -1
View File
@@ -387,6 +387,24 @@ fn select_keys(s: &mut Session, p: &Value) -> Result<Value> {
} else {
s.state.selected_keys = sel;
}
// From the Timeline, selected keys select their properties and layers too, as in After
// Effects, so the Graph Editor shows them (#252).
if b_p(p, "selectProperties").unwrap_or(false) && !s.state.selected_keys.is_empty() {
let extend = b_p(p, "add").unwrap_or(false) || b_p(p, "toggle").unwrap_or(false);
let st = &mut s.state;
if !extend {
st.selected_props.clear();
st.selected_layers.clear();
}
for k in &st.selected_keys {
if !st.selected_props.contains(&(k.layer, k.prop)) {
st.selected_props.push((k.layer, k.prop));
}
if !st.selected_layers.contains(&k.layer) {
st.selected_layers.push(k.layer);
}
}
}
Ok(json!(s.state.selected_keys.len()))
}
@@ -780,7 +798,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Select Keyframes",
[],
None,
"{keys: [{layer, prop: uid | path (or `path`), time (layer s)}], add?, toggle?: bool (Shift+click: in or out of the selection)}",
"{keys: [{layer, prop: uid | path (or `path`), time (layer s)}], add?, toggle?: bool (Shift+click: in or out of the selection), selectProperties?: bool (their properties and layers too, as a Timeline click does)}",
has_comp,
select_keys
),
+2 -2
View File
@@ -2094,7 +2094,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let kresp = ui.interact(kr, egui::Id::new(("key", uid, k.time.0)), Sense::click_and_drag());
app.auto.add(&format!("timeline.key.{uid}.{}", fr.frame_at(ct)), kr, &format!("{} key", prop.name));
let mods = ui.input(|i| i.modifiers);
let this_key = json!({"keys": [{"layer": layer.id.0, "prop": uid, "time": k.time.seconds()}]});
let this_key = json!({"keys": [{"layer": layer.id.0, "prop": uid, "time": k.time.seconds()}], "selectProperties": true});
if kresp.clicked() && mods.command {
// Ctrl+click: Linear ↔ Auto Bezier; Ctrl+Alt+click: Hold on / off.
actions.push(("keys.select".into(), this_key.clone()));
@@ -2375,7 +2375,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
}
}
actions.push(("keys.select".into(), json!({"keys": keys, "add": ui.input(|i| i.modifiers.shift)})));
actions.push(("keys.select".into(), json!({"keys": keys, "add": ui.input(|i| i.modifiers.shift), "selectProperties": true})));
}
// CTI.
+17
View File
@@ -148,6 +148,23 @@ fn shift_click_toggles_and_ctrl_click_switches_interpolation() {
assert_eq!(key(&h).out_interp, effectcraft_engine::keyframe::Interp::Hold);
}
/// Clicking a key in the Timeline selects its property and layer too, as in After Effects, so
/// the Graph Editor shows the key's curve (#252).
#[test]
fn a_key_clicked_in_the_timeline_shows_in_the_graph_editor() {
let (mut h, id, uid) = harness();
h.state_mut().session.execute("edit.deselectAll", json!({})).unwrap();
h.run_steps(2);
let ks = keys(&h, uid);
click_with(&mut h, ks[1], Default::default());
let st = &h.state().session.state;
assert_eq!(st.selected_keys.len(), 1);
assert_eq!((st.selected_props.clone(), st.selected_layers.clone()), (vec![(id, uid)], vec![id]), "the key's property and layer");
h.state_mut().ui.timeline.graph_editor = true;
h.run_steps(4);
assert!(h.state().auto.find(&format!("timeline.graph.key.{uid}.0.1")).is_some(), "the Graph Editor shows the key");
}
#[test]
fn graph_editor_drags_move_every_selected_key_and_shift_keeps_an_axis() {
let (mut h, id, uid) = harness();