Merge pull request #857 from pravdomil/claude/u-uu-shortcuts-time-remap-5dd91b

Timeline: U and UU reveal Time Remap
This commit is contained in:
Brandon Thomas 2026-10-12 03:23:30 -04:00
commit f41fdf0680
3 changed files with 53 additions and 0 deletions

View File

@ -312,6 +312,18 @@ fn reveal(s: &mut Session, p: &Value) -> Result<Value> {
let (w, h) = effectcraft_render::source_size(&s.project, l);
let anchor = [w as f64 / 2.0, h as f64 / 2.0];
let center = [comp.width as f64 / 2.0, comp.height as f64 / 2.0];
// Properties at the top of the layer's tree (Time Remap): enabled time remapping is a
// modification in itself.
for pr in l.props.props() {
let hit = match kind.as_str() {
"keyframes" => pr.is_animated(),
"animation" => pr.is_animated() || pr.has_expression(),
_ => pr.match_id == "timeRemap" || modified(pr, "", anchor, center),
};
if hit {
out.push(json!({"layer": lid.0, "prop": pr.uid}));
}
}
for top in l.props.groups() {
let hit = |pr: &Property| match kind.as_str() {
"keyframes" => pr.is_animated(),

View File

@ -712,6 +712,25 @@ fn deleting_time_remap_turns_time_remapping_off() {
assert_eq!(prop(&s, l, "timeRemap").keys.len(), 1);
}
/// U (keyframes), the animation reveal and UU (modified) take in Time Remap, which sits at the
/// top of the layer's tree rather than in a group.
#[test]
fn reveal_shortcuts_take_in_time_remap() {
let (mut s, l) = precomp_setup();
let revealed = |s: &mut Session, kind: &str| -> Vec<u64> {
let r = s.execute("anim.reveal", json!({"kind": kind})).unwrap();
r["props"].as_array().unwrap().iter().filter(|p| p["layer"] == l).filter_map(|p| p["prop"].as_u64()).collect()
};
for kind in ["keyframes", "animation", "modified"] {
assert!(revealed(&mut s, kind).is_empty(), "{kind}: nothing to reveal before remapping");
}
s.execute("layer.enableTimeRemap", json!({})).unwrap();
let uid = prop(&s, l, "timeRemap").uid;
for kind in ["keyframes", "animation", "modified"] {
assert_eq!(revealed(&mut s, kind), vec![uid], "{kind}");
}
}
/// Enable Time Remapping on a layer extended past its source's end (its last frame frozen) puts
/// the end key at the source's end, not at the extended out point (#290).
#[test]

View File

@ -3907,6 +3907,28 @@ mod tests {
assert!(rows.iter().any(|r| r.trim() == ">Transform"), "{rows:?}");
}
/// U and UU show Time Remap on a layer with time remapping enabled.
#[test]
fn u_and_uu_reveal_time_remap() {
let mut s = effectcraft_engine::Session::default();
let inner = s.execute("comp.new", json!({"name": "Inner", "width": 32, "height": 32, "duration": 2})).unwrap()["comp"].as_u64().unwrap();
s.execute("comp.new", json!({"name": "Main", "width": 64, "height": 64, "duration": 4})).unwrap();
let nested = s.execute("layer.addItem", json!({"item": inner})).unwrap()["layer"].as_u64().unwrap();
let mut app = EffectcraftApp::new(s);
let ctx = egui::Context::default();
crate::menus::invoke(&mut app, &ctx, "layer.enableTimeRemap", json!({"layers": [nested]})).unwrap();
app.session.execute("layer.select", json!({"layers": [nested]})).unwrap();
for uu in [false, true] {
app.ui.timeline.layer_reveal.clear();
app.last_reveal = uu.then(|| ("keyframes".into(), f64::MAX));
crate::menus::invoke(&mut app, &ctx, "anim.reveal", json!({"kind": "keyframes"})).unwrap();
assert_eq!(app.ui.timeline.layer_reveal.get(&nested), Some(&vec!["props".to_string()]), "UU: {uu}");
let rows = labels(&app);
let at = rows.iter().position(|r| r == "Inner").unwrap();
assert_eq!(rows[at + 1].trim(), "Time Remap", "UU: {uu}: {rows:?}");
}
}
/// Enabling time remapping twirls the layer open on Time Remap (#157); disabling it reveals
/// nothing.
#[test]