Graph Editor: protect selection tools and restore queue-close focus

This commit is contained in:
Kristian Myram
2026-10-29 22:38:53 +01:00
parent b3887c226f
commit f93d336f9d
11 changed files with 194 additions and 10 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ fn compatible(a: &Property, b: &effectcraft_keyframe::Value) -> bool {
a.value.kind_name() == b.kind_name() && a.value.components().len() == b.components().len()
}
fn locked_selection(comp: &effectcraft_project::Comp, selected: &[KeyRef]) -> Vec<KeyRef> {
pub(crate) fn locked_selection(comp: &effectcraft_project::Comp, selected: &[KeyRef]) -> Vec<KeyRef> {
selected.iter().filter(|k| comp.layer(k.layer).is_some_and(|l| l.switches.locked)).copied().collect()
}
+3 -2
View File
@@ -213,7 +213,8 @@ fn sample_curve(
fn grouped_selection(s: &Session) -> std::collections::BTreeMap<(LayerId, Uid), Vec<Tick>> {
let mut g: std::collections::BTreeMap<(LayerId, Uid), Vec<Tick>> = Default::default();
for k in &s.state.selected_keys {
let Some(comp) = s.active_comp() else { return g };
for k in super::prop::editable_keys(comp, &s.state.selected_keys) {
g.entry((k.layer, k.prop)).or_default().push(k.time);
}
for v in g.values_mut() {
@@ -273,7 +274,7 @@ fn run_on_selection(
s.edit(label, None, |proj, st| {
let comp = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
let fr = comp.frame_rate;
let mut sel = vec![];
let mut sel = super::anim::locked_selection(comp, &st.selected_keys);
for ((lid, uid), times) in groups {
let Some(l) = comp.layer_mut(lid) else { continue };
let lc = l.clone();
+2 -1
View File
@@ -209,7 +209,8 @@ fn add_keyframe(s: &mut Session, _: &Value) -> Result<Value> {
/// Selected keys grouped by (layer, property).
fn keys_by_prop(s: &Session) -> BTreeMap<(LayerId, Uid), Vec<Tick>> {
let mut m: BTreeMap<(LayerId, Uid), Vec<Tick>> = BTreeMap::new();
for k in &s.state.selected_keys {
let Some(comp) = s.active_comp() else { return m };
for k in super::prop::editable_keys(comp, &s.state.selected_keys) {
m.entry((k.layer, k.prop)).or_default().push(k.time);
}
m
+2
View File
@@ -37,6 +37,8 @@ pub(crate) fn label_keys(s: &mut Session, p: &Value) -> Result<Value> {
return Err(bad("keys.label", "select keyframes first"));
}
let cid = comp_id(s, p)?;
let comp = s.project.comp(cid).ok_or(EngineError::NoComp)?;
let keys = super::prop::editable_keys(comp, &keys);
let n = s.edit("Keyframe Label", None, |proj, _| {
let mut n = 0;
for k in &keys {
+7 -5
View File
@@ -437,20 +437,22 @@ fn interpolate(s: &mut Session, p: &Value) -> Result<Value> {
// The layer: given, else the layer of selected Mask Path keys, else the selection.
let cid = super::comp_id(s, p)?;
let comp = s.project.comp(cid).ok_or(EngineError::NoComp)?;
let selected = super::prop::editable_keys(comp, &s.state.selected_keys);
let lid = match p.get("layer") {
Some(v) => super::resolve_layer(comp, v).ok_or_else(|| bad(CMD, format!("no layer {v}")))?,
None => match s.state.selected_keys.iter().find(|k| comp.layer(k.layer).is_some_and(|l| l.masks().is_some_and(|m| m.find(k.prop).is_some()))) {
None => match selected.iter().find(|k| comp.layer(k.layer).is_some_and(|l| l.masks().is_some_and(|m| m.find(k.prop).is_some()))) {
Some(k) => k.layer,
None => layer_p(s, p, CMD)?.1,
},
};
let layer = comp.layer(lid).ok_or(EngineError::NoComp)?;
if !super::prop::key_layer_editable(comp, lid) {
return Err(bad(CMD, "the layer is locked"));
}
let masks = layer.masks().ok_or_else(|| bad(CMD, "the layer has no masks"))?;
let uid = match p.get("mask") {
Some(_) => mask_uid(s, layer, p),
None => s
.state
.selected_keys
None => selected
.iter()
.filter(|k| k.layer == lid)
.find_map(|k| masks.groups().find(|g| g.get("path").is_some_and(|pr| pr.uid == k.prop)).map(|g| g.uid))
@@ -465,7 +467,7 @@ fn interpolate(s: &mut Session, p: &Value) -> Result<Value> {
let ts: Vec<Tick> = a.iter().filter_map(Value::as_f64).map(|t| layer.layer_time(comp.frame_rate.snap_nearest(Tick::from_seconds_f64(t)))).collect();
ts.into_iter().filter_map(|t| path_prop.keys.iter().find(|k| (k.time.0 - t.0).abs() <= comp.frame_duration().0 / 2).map(|k| k.time)).collect()
}
None => s.state.selected_keys.iter().filter(|k| k.layer == lid && k.prop == path_prop.uid).map(|k| k.time).collect(),
None => selected.iter().filter(|k| k.layer == lid && k.prop == path_prop.uid).map(|k| k.time).collect(),
};
times.sort();
times.dedup();
+9
View File
@@ -883,6 +883,15 @@ fn select_keys(s: &mut Session, p: &Value) -> Result<Value> {
Ok(out)
}
/// References that can be edited, including explicit key lists supplied by commands.
pub(crate) fn editable_keys(comp: &effectcraft_project::Comp, selected: &[KeyRef]) -> Vec<KeyRef> {
selected.iter().filter(|key| key_layer_editable(comp, key.layer)).copied().collect()
}
pub(crate) fn key_layer_editable(comp: &effectcraft_project::Comp, layer: LayerId) -> bool {
comp.layer(layer).is_some_and(|layer| !layer.switches.locked)
}
/// Apply `f` to every selected key; keeps the selection pointing at moved keys.
pub(crate) fn edit_keys(s: &mut Session, label: &str, merge: Option<&str>, f: impl Fn(&mut Vec<Keyframe>, usize, &mut Tick) -> bool) -> Result<Value> {
let cid = s.active_comp_id().ok_or(EngineError::NoComp)?;