From 76761bd2ef4a3c64f6c4c7fe6a770908e6d1971d Mon Sep 17 00:00:00 2001 From: bflatastic Date: Wed, 7 Oct 2026 13:04:12 +0700 Subject: [PATCH] Timeline: enabling time remapping reveals Time Remap (#157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Layer ▸ Time ▸ Enable Time Remapping (and the layer menu's entry) added the property and its two keyframes but left the layer twirled closed, so nothing visibly changed. When the command turns remapping on, the layers that have it now twirl open on Time Remap, through the same per-layer reveal as RR. Turning it off reveals nothing. Test: enabling_time_remapping_reveals_time_remap. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/menus.rs | 22 ++++++++++++++++++++++ crates/ui-egui/src/panels/timeline.rs | 23 +++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 175a1b8f..d94586bf 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -175,6 +175,20 @@ pub fn reveal(app: &mut EffectcraftApp, kind: &str, now: f64, add: bool) { tl.apply_reveal(&targets, kinds); } +/// Show Time Remap on the layers of a `layer.enableTimeRemap` (`layers` by id, else the +/// selected ones) that have it now. +fn reveal_time_remap(app: &mut EffectcraftApp, params: &Value) { + let asked: Vec = match params.get("layers").and_then(Value::as_array) { + Some(a) => a.iter().filter_map(Value::as_u64).collect(), + None => app.session.state.selected_layers.iter().map(|l| l.0).collect(), + }; + let Some(comp) = app.session.active_comp() else { return }; + let layers: Vec = comp.layers.iter().filter(|l| asked.contains(&l.id.0) && l.props.get("timeRemap").is_some()).map(|l| l.id.0).collect(); + if !layers.is_empty() { + app.ui.timeline.apply_reveal(&layers, vec!["timeRemap".into()]); + } +} + /// The layers a reveal shortcut acts on: the selected ones, else every layer of the active comp. fn reveal_targets(app: &EffectcraftApp) -> Vec { if app.session.state.selected_layers.is_empty() { @@ -541,6 +555,14 @@ pub fn invoke(app: &mut EffectcraftApp, ctx: &egui::Context, id: &str, params: V } return r; } + // Enabling time remapping twirls its layers open on Time Remap and its two keys. + if id == "layer.enableTimeRemap" { + let r = run_engine(app, ctx, id, params.clone()); + if r.as_ref().is_ok_and(|on| on.as_bool() == Some(true)) { + reveal_time_remap(app, ¶ms); + } + return r; + } if id == "layer.rename" && params.get("name").is_none() { crate::panels::timeline::begin_rename(app, ctx); return Ok(Value::Null); diff --git a/crates/ui-egui/src/panels/timeline.rs b/crates/ui-egui/src/panels/timeline.rs index 99d336d6..48fc72ec 100644 --- a/crates/ui-egui/src/panels/timeline.rs +++ b/crates/ui-egui/src/panels/timeline.rs @@ -3015,6 +3015,29 @@ mod tests { assert!(rows.iter().any(|r| r.trim() == ">Transform"), "{rows:?}"); } + /// Enabling time remapping twirls the layer open on Time Remap (#157); disabling it reveals + /// nothing. + #[test] + fn enabling_time_remapping_reveals_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(); + s.execute("layer.newSolid", json!({"name": "Other"})).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(); + assert_eq!(app.ui.timeline.layer_reveal.get(&nested), Some(&vec!["timeRemap".to_string()])); + let rows = labels(&app); + let at = rows.iter().position(|r| r == "Inner").unwrap(); + assert_eq!(rows[at + 1].trim(), "Time Remap", "{rows:?}"); + // Through the selection (the Layer menu), turning it off again: nothing new is revealed. + app.session.execute("layer.select", json!({"layers": [nested]})).unwrap(); + app.ui.timeline.layer_reveal.clear(); + crate::menus::invoke(&mut app, &ctx, "layer.enableTimeRemap", json!({})).unwrap(); + assert!(app.ui.timeline.layer_reveal.is_empty()); + } + #[test] fn cti_snaps_to_the_nearest_candidate() { assert_eq!(snap_time(&[0.0, 1.0, 2.0], 1.04, 0.1), 1.0);