mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-11 15:22:05 +00:00
Time entry: a negative displayed timecode goes to the frame it shows (#3373)
In a comp whose Start Timecode is negative, the current-time display shows the first frames as `-0:00:00:12`, but typing that back (the Go to Time dialog opens with it since #3419, and the Timeline's time field takes it) read the leading `-` as "move back", so OK on an unedited dialog jumped to another frame. There, a `-` before a full timecode is now the time's sign, as it already is before Feet + Frames; a bare `-n` still steps back, and comps that start at 0 or later are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6aca5e473c
commit
5922dabd3a
@@ -43,7 +43,9 @@ pub fn display_duration(s: &Session, comp: &Comp, len: Tick) -> String {
|
||||
}
|
||||
|
||||
/// Parse a time typed in the project's Time Display Style (relative `+n`/`-n` from frame
|
||||
/// `current`) into a comp frame.
|
||||
/// `current`) into a comp frame. In Timecode style, in a comp whose Start Timecode is negative, a
|
||||
/// `-` before a timecode with separators (`-0:00:00:12`, `-1.00`) is the sign of a displayed time,
|
||||
/// not a step back; a bare `-n` still steps back.
|
||||
pub fn parse_display_time(s: &Session, comp: &Comp, text: &str, current: i64) -> std::result::Result<i64, String> {
|
||||
let fr = comp.frame_rate;
|
||||
let st = &s.project.settings;
|
||||
@@ -53,6 +55,20 @@ pub fn parse_display_time(s: &Session, comp: &Comp, text: &str, current: i64) ->
|
||||
effectcraft_time::parse_feet_frames(text, pf, current + start).map(|f| f - start).map_err(|e| e.0)
|
||||
}
|
||||
None => {
|
||||
// A comp whose Start Timecode is negative shows the times before 0 with a `-`
|
||||
// (`-0:00:00:12`), so there a `-` before a timecode with separators is the sign of a
|
||||
// displayed time and typing what the display shows lands on the frame it shows. A bare
|
||||
// `-n` still moves n frames back.
|
||||
if st.time_display == TimeDisplayStyle::Timecode
|
||||
&& comp.display_start < Tick::ZERO
|
||||
&& let Some(body) = text.trim().strip_prefix('-').map(str::trim_start)
|
||||
&& body.contains([':', ';', '.', ','])
|
||||
&& !body.starts_with(['+', '-'])
|
||||
{
|
||||
let drop_frame = fr.supports_drop_frame() && body.contains(';');
|
||||
let shown = effectcraft_time::parse_timecode(body, fr, drop_frame, current).map_err(|e| e.0)?;
|
||||
return Ok(shown.saturating_neg().saturating_sub(fr.frame_at(comp.display_start)));
|
||||
}
|
||||
// The Timeline shows non-drop timecode (`:`), so typed `:`/`.` text is read as non-drop
|
||||
// too; only explicit `;` fields mean drop-frame (#890).
|
||||
let drop_frame = fr.supports_drop_frame() && text.contains(';');
|
||||
|
||||
@@ -649,6 +649,31 @@ fn time_set_keeps_the_frame_shown_in_the_current_time_field() {
|
||||
assert_eq!(r["frame"], 13);
|
||||
}
|
||||
|
||||
/// A comp that starts before 0 shows its first frames as negative timecode (`-0:00:00:12`).
|
||||
/// Typing back what the current-time display shows (Go to Time opens with it) stays on that frame;
|
||||
/// the leading `-` used to move back from the current frame instead. A bare `-n` still steps back,
|
||||
/// and in a comp that starts at 0 a `-` stays relative.
|
||||
#[test]
|
||||
fn time_set_round_trips_negative_displayed_timecode() {
|
||||
let mut s = Session::default();
|
||||
s.execute("comp.new", json!({"name": "Pre-roll", "width": 64, "height": 64, "frameRate": 24, "duration": 4, "startTime": -1})).unwrap();
|
||||
for frame in [0_i64, 1, 12, 23, 24, 25, 60] {
|
||||
let r = s.execute("time.set", json!({"frame": frame})).unwrap();
|
||||
let display = r["display"].as_str().unwrap().to_string();
|
||||
let r = s.execute("time.set", json!({"timecode": display})).unwrap();
|
||||
assert_eq!(r["frame"], frame, "displayed {display}");
|
||||
}
|
||||
s.execute("time.set", json!({"frame": 12})).unwrap();
|
||||
assert_eq!(s.execute("time.set", json!({"timecode": "-0:00:00:12"})).unwrap()["display"], "-0:00:00:12");
|
||||
assert_eq!(s.execute("time.set", json!({"timecode": "-5"})).unwrap()["frame"], 7, "a bare -n steps back");
|
||||
assert_eq!(s.execute("time.set", json!({"timecode": "-0:00:00:20"})).unwrap()["frame"], 4);
|
||||
assert!(s.execute("time.set", json!({"timecode": "- -1:00"})).is_err(), "a second sign is still an error");
|
||||
|
||||
s.execute("comp.settings", json!({"startTime": 0})).unwrap();
|
||||
s.execute("time.set", json!({"frame": 12})).unwrap();
|
||||
assert_eq!(s.execute("time.set", json!({"timecode": "-0:00:00:02"})).unwrap()["frame"], 10, "relative when the comp starts at 0");
|
||||
}
|
||||
|
||||
/// Frames display style: typed digits are a frame count, so 00100 is frame 100, not 1 s (#530).
|
||||
#[test]
|
||||
fn time_set_reads_frames_style_digits_as_a_frame_count() {
|
||||
|
||||
@@ -1130,6 +1130,22 @@ mod tests {
|
||||
assert_eq!(at, 19, "displayed 00020 with frames starting at 1");
|
||||
}
|
||||
|
||||
/// In a comp that starts 1 s before 0, Go to Time at frame 12 opens on the negative time the
|
||||
/// viewer shows, and OK without editing it stays on frame 12 (it used to jump to frame 0).
|
||||
#[test]
|
||||
fn go_to_time_ok_keeps_a_negative_displayed_time() {
|
||||
let mut app = EffectcraftApp::new(effectcraft_engine::Session::default());
|
||||
app.session.execute("comp.new", json!({"name": "Main", "width": 64, "height": 36, "frameRate": 24, "duration": 4, "startTime": -1})).unwrap();
|
||||
app.session.execute("time.set", json!({"frame": 12})).unwrap();
|
||||
assert!(open_form(&mut app, "time.set", &json!({})));
|
||||
let f = &app.dialog_state.form;
|
||||
assert!(matches!(&f.fields[0].kind, FieldKind::Text(t) if t == "-0:00:00:12"), "{:?}", f.fields[0]);
|
||||
let (command, params) = (f.command.clone(), f.params());
|
||||
app.session.execute(&command, params).unwrap();
|
||||
let at = app.session.active_comp().unwrap().frame_rate.frame_at(app.session.time());
|
||||
assert_eq!(at, 12, "OK on the unedited time stays put");
|
||||
}
|
||||
|
||||
/// File ▸ Export ▸ Adobe Premiere Pro Project… opens a form that says it writes Final Cut Pro
|
||||
/// XML; its fields register automation ids and OK runs `file.exportTimeline`.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user