diff --git a/crates/engine/src/commands/time.rs b/crates/engine/src/commands/time.rs index 9ed41f47..81197602 100644 --- a/crates/engine/src/commands/time.rs +++ b/crates/engine/src/commands/time.rs @@ -38,7 +38,19 @@ pub fn parse_display_time(s: &Session, comp: &Comp, text: &str, current: i64) -> let start = fr.frame_at(comp.display_start) + st.frame_start; effectcraft_time::parse_feet_frames(text, pf, current + start).map(|f| f - start).map_err(|e| e.0) } - None => effectcraft_time::parse_timecode(text, fr, fr.supports_drop_frame(), current).map_err(|e| e.0), + None => { + let parsed = effectcraft_time::parse_timecode(text, fr, fr.supports_drop_frame(), current).map_err(|e| e.0)?; + let relative = text.trim().starts_with('+') || text.trim().starts_with('-'); + if relative { + Ok(parsed) + } else if st.time_display == TimeDisplayStyle::Frames { + // Displayed digits are composition frame plus Start Numbering Frames At. + Ok(parsed - st.frame_start) + } else { + // Displayed timecode includes the composition Start Timecode offset. + Ok(parsed - fr.frame_at(comp.display_start)) + } + } } } diff --git a/crates/engine/src/tests_timeline.rs b/crates/engine/src/tests_timeline.rs index cdfbf8dd..eacf3da3 100644 --- a/crates/engine/src/tests_timeline.rs +++ b/crates/engine/src/tests_timeline.rs @@ -375,6 +375,29 @@ fn time_set_snaps_to_the_nearest_frame_and_ae_timecode() { assert_eq!(effectcraft_time::format_timecode_ae(75, fr, true), "0;00;02;15"); } +#[test] +fn time_set_keeps_the_frame_shown_in_the_current_time_field() { + let mut s = Session::default(); + s.execute( + "comp.new", + json!({"name": "Example", "width": 64, "height": 64, "frameRate": 30, "duration": 0.6, "startTimecode": "0:00:00:05"}), + ) + .unwrap(); + let r = s.execute("time.set", json!({"frame": 0})).unwrap(); + assert_eq!(r["display"], "0:00:00:05"); + let r = s.execute("time.set", json!({"timecode": "0:00:00:05"})).unwrap(); + assert_eq!((r["frame"].as_i64(), r["time"].as_f64()), (Some(0), Some(0.0))); + + s.execute("file.projectSettings", json!({"timeDisplay": "frames"})).unwrap(); + std::sync::Arc::make_mut(&mut s.project).settings.frame_start = 1; + let r = s.execute("time.set", json!({"frame": 12})).unwrap(); + assert_eq!(r["display"], "00013"); + let r = s.execute("time.set", json!({"timecode": "00013"})).unwrap(); + assert_eq!((r["frame"].as_i64(), r["time"].as_f64()), (Some(12), Some(0.4))); + let r = s.execute("time.set", json!({"timecode": "+1"})).unwrap(); + assert_eq!(r["frame"], 13); +} + #[test] fn separate_dimensions_split_and_rejoin() { let (mut s, l) = setup();