From 22b04320a486f6d03746cc60ae9e14bb8dd9060b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 14:48:14 +0000 Subject: [PATCH 1/2] Viewer: clicking the current-time display opens Go to Time (#3373) The time display in the Composition panel's bottom bar only reacted to Ctrl/Cmd+click, which switches timecode and frames. A plain click now opens the Go to Time dialog (View > Go to Time), as in After Effects. The Ctrl/Cmd+click test follows: its plain click now opens the dialog, which it closes. --- crates/ui-egui/src/panels/viewer_tools.rs | 9 +++++++++ crates/ui-egui/tests/it/ui_inline_edit.rs | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/panels/viewer_tools.rs b/crates/ui-egui/src/panels/viewer_tools.rs index 563657e91..162b40037 100644 --- a/crates/ui-egui/src/panels/viewer_tools.rs +++ b/crates/ui-egui/src/panels/viewer_tools.rs @@ -981,6 +981,7 @@ fn bottom_bar_contents(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, z } app.auto.add_response("viewer.timecode", &resp, "Current time"); crate::panels::toggle_time_display(app, &resp); + go_to_time_on_click(app, &resp); // Right: render time. let ms = app.frames.last_ms.lock().map(|v| *v).unwrap_or(0.0); if tr.max.x + 50.0 < bar.max.x { @@ -989,6 +990,14 @@ fn bottom_bar_contents(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, z (tr.right() + 8.0 - bar.left()).max(bar.width()) } +/// A plain click on the time display opens Go to Time, as it does in After Effects; Ctrl/Cmd+click +/// is [`crate::panels::toggle_time_display`] (#3373). +fn go_to_time_on_click(app: &mut EffectcraftApp, resp: &egui::Response) { + if resp.clicked() && !resp.ctx.input(|i| i.modifiers.command) { + crate::panels::forms::open_form(app, "time.set", &json!({})); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ui-egui/tests/it/ui_inline_edit.rs b/crates/ui-egui/tests/it/ui_inline_edit.rs index 6c29045bb..ee0b3d35a 100644 --- a/crates/ui-egui/tests/it/ui_inline_edit.rs +++ b/crates/ui-egui/tests/it/ui_inline_edit.rs @@ -438,8 +438,11 @@ fn ctrl_click_on_the_time_display_toggles_timecode_and_frames() { click_with(&mut h, at, Modifiers::COMMAND); assert_eq!(style(&h), TimeDisplayStyle::Timecode); assert_eq!(shown(&h, "timeline.timecode"), "0:00:01:06"); - // A plain click changes nothing. + // A plain click leaves the display alone: it opens Go to Time (#3373), closed here. click_with(&mut h, at, Modifiers::NONE); + assert_eq!(style(&h), TimeDisplayStyle::Timecode); + key(&mut h, Key::Escape); + assert!(h.query_by_label("Go to Time").is_none(), "Go to Time closed"); let at = center(&h, "timeline.timecode"); click_with(&mut h, at, Modifiers::NONE); assert_eq!(style(&h), TimeDisplayStyle::Timecode); @@ -746,3 +749,17 @@ fn typing_one_component_with_several_layers_selected_keeps_their_other_component assert_eq!(pos(&h, a), [40.0, 50.0, -300.0]); assert_eq!(pos(&h, b), [200.0, 120.0, -300.0], "B keeps its own X and Y"); } + +/// #3373: a click on the Composition panel's time display opens Go to Time, as in After Effects; +/// Ctrl/Cmd+click still switches between timecode and frames. +#[test] +fn clicking_the_viewer_time_display_opens_go_to_time() { + let (mut h, _) = harness(); + h.state_mut().session.execute("time.set", json!({"frame": 30})).unwrap(); + h.run_steps(2); + assert!(h.query_by_label("Go to Time").is_none(), "set up: no dialog yet"); + let at = center(&h, "viewer.timecode"); + click(&mut h, at, 1); + h.run_steps(2); + assert!(h.query_by_label("Go to Time").is_some(), "the dialog is open"); +} From ddb8c958a0d0eeee846f3069bcb2f8f26e868e81 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 15:49:40 +0000 Subject: [PATCH 2/2] Go to Time: show and take the time as the project displays it (#3373) The dialog's Frame field held the engine's zero-based frame, so with frames shown from 1 the viewer's 00013 opened as 12, and typing 13 went to the frame displayed 00014. It now holds the time in the project's display style (timecode, frames from Start Numbering Frames At, or feet + frames), which time.set already reads that way. The now-unused 'Frame' translation rows go. --- crates/ui-egui/src/i18n/ui.rs | 3 --- crates/ui-egui/src/panels/forms.rs | 26 ++++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/crates/ui-egui/src/i18n/ui.rs b/crates/ui-egui/src/i18n/ui.rs index a16b879d7..1a162dab3 100644 --- a/crates/ui-egui/src/i18n/ui.rs +++ b/crates/ui-egui/src/i18n/ui.rs @@ -3651,7 +3651,6 @@ const SWEDISH: &[(&str, &str)] = &[ ("Smoothing", "Utjämning"), ("Top", "Överkant"), ("Mode", "Läge"), - ("Frame", "Bildruta"), ("Orientation", "Orientering"), ("Create", "Skapa"), ("Overlap", "Överlappning"), @@ -8866,7 +8865,6 @@ const GEORGIAN_PANELS: &[(&str, &str)] = &[ ("Height", "სიმაღლე"), ("Shape", "ფიგურა"), ("Mode", "რეჟიმი"), - ("Frame", "კადრი"), ("Orientation", "ორიენტაცია"), ("Position (pixels)", "პოზიცია (პიქსელი)"), ("Position (percent of the composition)", "პოზიცია (კომპოზიციის პროცენტი)"), @@ -9849,7 +9847,6 @@ const FRENCH: &[(&str, &str)] = &[ ("Height", "Hauteur"), ("Shape", "Forme"), ("Mode", "Mode"), - ("Frame", "Image"), ("Orientation", "Orientation"), ("Position (pixels)", "Position (pixels)"), ("Position (percent of the composition)", "Position (pourcentage de la composition)"), diff --git a/crates/ui-egui/src/panels/forms.rs b/crates/ui-egui/src/panels/forms.rs index e83530fa4..9b94360a5 100644 --- a/crates/ui-egui/src/panels/forms.rs +++ b/crates/ui-egui/src/panels/forms.rs @@ -630,9 +630,11 @@ pub fn open_form(app: &mut EffectcraftApp, id: &str, p: &Value) -> bool { )], ) } + // The time as the project displays it (timecode, or frames from Start Numbering Frames At, + // or feet + frames), which `time.set` reads back the same way. "time.set" if !has(p, &["time", "frame", "timecode"]) => { - let frame = comp.map(|c| c.frame_rate.frame_at(t)).unwrap_or(0); - ("Go to Time".into(), vec![Field::num("frame", "Frame", frame as f64)]) + let shown = comp.map(|c| crate::panels::timecode(s, c, t)).unwrap_or_default(); + ("Go to Time".into(), vec![Field::text("timecode", "Time", &shown)]) } "view.addGuide" if !has(p, &["position"]) => { let (w, _) = comp.map(|c| (c.width, c.height)).unwrap_or((1920, 1080)); @@ -1108,6 +1110,26 @@ mod tests { assert!(app.dialog.is_none(), "one Enter confirms"); } + /// Go to Time shows the time as the project displays it, so with Frames numbered from 1 the + /// viewer's frame 00013 opens as 00013 and 00020 goes to the frame displayed 00020 (the + /// engine's frame 19). The dialog used to show and take the engine's own frame number (#3373). + #[test] + fn go_to_time_uses_the_projects_frame_numbering() { + 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})).unwrap(); + app.session.execute("file.projectSettings", json!({"timeDisplay": "frames", "frameStart": 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_eq!(f.command, "time.set"); + assert!(matches!(&f.fields[0].kind, FieldKind::Text(t) if t == "00013"), "{:?}", f.fields[0]); + app.dialog_state.form.fields[0].kind = FieldKind::Text("00020".into()); + let (command, params) = (app.dialog_state.form.command.clone(), app.dialog_state.form.params()); + app.session.execute(&command, params).unwrap(); + let at = app.session.active_comp().unwrap().frame_rate.frame_at(app.session.time()); + assert_eq!(at, 19, "displayed 00020 with frames starting at 1"); + } + /// 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]