Merge pull request #1271 from n-delic/fix-1241-select-all-keyframes

#1241: Select All in Effect Controls selects its keyframes
This commit is contained in:
Brandon Thomas 2026-10-13 01:19:18 -04:00
commit ade0f36544
4 changed files with 58 additions and 1 deletions

View File

@ -78,6 +78,10 @@ real_name = "Valentin Klein"
display_name = "Valentin Klein"
sync_github_name = false
[people.n-delic]
display_name = "n-delic"
sync_github_name = false
[people.Silvertree2010]
display_name = "Netter"
sync_github_name = false

View File

@ -176,7 +176,7 @@ pub fn targets_source(app: &FilmcraftApp, params: &Value) -> bool {
/// Execute a UI or engine command by id.
pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, mut params: Value) -> Result<Value, String> {
if id == "edit.selectAll" && crate::panels::graphics::select_all_edited_text(app) {
if id == "edit.selectAll" && (crate::panels::graphics::select_all_edited_text(app) || crate::panels::effect_controls::select_all_keyframes(app)) {
return Ok(Value::Null);
}
if filmcraft_engine::source_monitor::source_command(id) && targets_source(app, &params) && params.get("target").is_none() {

View File

@ -63,6 +63,33 @@ fn keyframe_exists(it: &TrackItem, k: &KeyframeRef) -> bool {
param.is_some_and(|p| p.keyframes.iter().any(|kf| kf.time == k.time))
}
/// Edit ▸ Select All (⌘A) with Effect Controls focused selects every keyframe the panel shows
/// instead of the Timeline's clips (#1241). False when the panel is not focused or shows none.
pub(crate) fn select_all_keyframes(app: &mut FilmcraftApp) -> bool {
if app.ui.focused != crate::dock::PanelKind::EffectControls {
return false;
}
let mut keys = Vec::new();
for (clip, it, _) in selected_clips(app) {
for (effect, e) in it.effects.iter().enumerate() {
let key = |param: &str, mask, time| KeyframeRef { clip: clip.0, effect, param: param.to_string(), mask, time };
for (param, p) in &e.params {
keys.extend(p.keyframes.iter().map(|kf| key(param, None, kf.time)));
}
for (m, mask) in e.masks.iter().enumerate() {
for param in filmcraft_project::mask::MASK_PARAMS {
keys.extend(mask.param(param).into_iter().flat_map(|p| &p.keyframes).map(|kf| key(param, Some(m), kf.time)));
}
}
}
}
if keys.is_empty() {
return false;
}
app.ui.keyframe_selection = keys;
true
}
fn selected_clip(app: &FilmcraftApp) -> Option<(ClipId, TrackItem, TrackKind)> {
selected_clips(app).into_iter().next()
}

View File

@ -311,6 +311,32 @@ fn clicking_a_lane_keyframe_selects_only_that_keyframe() {
assert_eq!(selection(&mut d), json!([]), "a deleted keyframe is not selected");
}
#[test]
fn select_all_in_effect_controls_selects_its_keyframes_not_the_clips() {
// #1241
let (mut d, clip) = Driver::demo();
d.seek(1.0);
for param in ["scale", "position"] {
d.exec("effects.addKeyframe", json!({"clip": clip.id, "effect": "motion", "param": param}));
}
d.seek(2.0);
d.exec("effects.addKeyframe", json!({"clip": clip.id, "effect": "motion", "param": "scale"}));
let clips = d.ok("ui.inspect", json!({}))["selection"].clone();
d.ok("ui.set", json!({"focused": "EffectControls"}));
d.ok("ui.key", json!({"key": "Cmd+A"}));
d.frames(2);
let inspect = d.ok("ui.inspect", json!({}));
let keys = inspect["ui"]["keyframe_selection"].as_array().cloned().unwrap_or_default();
assert_eq!(keys.len(), 3, "{keys:?}");
assert!(keys.iter().all(|k| k["clip"] == clip.id));
assert_eq!(inspect["selection"], clips, "the Timeline selection is unchanged");
// with the Timeline focused, Select All still selects the clips
d.ok("ui.set", json!({"focused": "Timeline"}));
d.ok("ui.key", json!({"key": "Cmd+A"}));
d.frames(2);
assert!(d.ok("ui.inspect", json!({}))["selection"].as_array().map_or(0, Vec::len) > clips.as_array().map_or(0, Vec::len));
}
/// Effect Controls' divider (#643): dragging it widens the effect list or the keyframe area, each
/// keeping a minimum width, and the width stays. The mouse wheel scrolls the rows over the
/// keyframe area too, not only over the effect list.