diff --git a/crates/ui-egui/src/panels/mod.rs b/crates/ui-egui/src/panels/mod.rs index 047966af..ab5a1c8f 100644 --- a/crates/ui-egui/src/panels/mod.rs +++ b/crates/ui-egui/src/panels/mod.rs @@ -84,6 +84,18 @@ pub fn timecode(session: &Session, comp: &Comp, t: Tick) -> String { effectcraft_engine::commands::time::display_time(session, comp, t) } +/// Ctrl/Cmd-click on a current-time display toggles the project's Time Display Style between +/// Timecode and Frames, as in After Effects (through Project Settings: one undo step). +pub(crate) fn toggle_time_display(app: &mut EffectcraftApp, resp: &egui::Response) { + if !(resp.clicked() && resp.ctx.input(|i| i.modifiers.command)) { + return; + } + let timecode = app.session.project.settings.time_display == effectcraft_engine::project::TimeDisplayStyle::Timecode; + if let Err(e) = app.session.execute("file.projectSettings", serde_json::json!({"timeDisplay": if timecode { "frames" } else { "timecode" }})) { + app.ui.status = e.to_string(); + } +} + /// Spacebar held outside a text field: the Hand tool (drags pan the viewer and scroll the /// Timeline). pub(crate) fn space_hand(ctx: &egui::Context) -> bool { diff --git a/crates/ui-egui/src/panels/timeline.rs b/crates/ui-egui/src/panels/timeline.rs index 69139d3b..042718fb 100644 --- a/crates/ui-egui/src/panels/timeline.rs +++ b/crates/ui-egui/src/panels/timeline.rs @@ -1156,9 +1156,16 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let tc_rect = Rect::from_min_size(pos2(rect.min.x + 12.0, top + 6.0), vec2(150.0, 24.0)); let tc_resp = ui.interact(tc_rect, egui::Id::new("tl-timecode"), Sense::click_and_drag()); p.text(tc_rect.left_center(), Align2::LEFT_CENTER, &tc, Tokens::semibold(16.0), t.timecode); - let sub = format!("{:05} ({:.2} fps)", fr.frame_at(time) + app.session.project.settings.frame_start, fr.as_f64()); - p.text(pos2(tc_rect.min.x, tc_rect.max.y + 8.0), Align2::LEFT_CENTER, sub, Tokens::ui(10.5), t.text_faint); + // Under it the other style (frames under timecode, timecode under frames) and the rate. + let settings = &app.session.project.settings; + let other = if settings.time_display == effectcraft_engine::project::TimeDisplayStyle::Timecode { + format!("{:05}", fr.frame_at(time) + settings.frame_start) + } else { + effectcraft_engine::time::format_timecode_ae(fr.frame_at(time + comp.display_start), fr, false) + }; + p.text(pos2(tc_rect.min.x, tc_rect.max.y + 8.0), Align2::LEFT_CENTER, format!("{other} ({:.2} fps)", fr.as_f64()), Tokens::ui(10.5), t.text_faint); app.auto.add("timeline.timecode", tc_rect, &tc); + super::toggle_time_display(app, &tc_resp); if tc_resp.dragged() { let d = tc_resp.drag_delta().x as f64; let f = fr.frame_at(time) + (d * 0.5).round() as i64; diff --git a/crates/ui-egui/src/panels/viewer_tools.rs b/crates/ui-egui/src/panels/viewer_tools.rs index 06b529b3..36b64b48 100644 --- a/crates/ui-egui/src/panels/viewer_tools.rs +++ b/crates/ui-egui/src/panels/viewer_tools.rs @@ -733,7 +733,9 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, let g = p.layout_no_wrap(tc, Tokens::mono(12.0), t.timecode); let tr = Rect::from_min_size(pos2(x + 4.0, cy - 9.0), g.size() + vec2(8.0, 4.0)); p.galley(pos2(tr.min.x + 4.0, tr.min.y + 2.0), g, t.timecode); + let resp = ui.interact(tr, egui::Id::new("vw-timecode"), Sense::click()); app.auto.add("viewer.timecode", tr, "Current time"); + crate::panels::toggle_time_display(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 { diff --git a/crates/ui-egui/tests/ui_inline_edit.rs b/crates/ui-egui/tests/ui_inline_edit.rs index 47b77471..a8702ad1 100644 --- a/crates/ui-egui/tests/ui_inline_edit.rs +++ b/crates/ui-egui/tests/ui_inline_edit.rs @@ -127,3 +127,49 @@ fn the_expression_pick_whip_inserts_at_the_cursor_while_editing() { whip(&mut h); assert_eq!(expr(&h), "transform.rotation"); } + +/// A click at `p` with `modifiers` held. +fn click_with(h: &mut Harness<'_, EffectcraftApp>, p: Pos2, modifiers: Modifiers) { + h.input_mut().events.push(Event::PointerMoved(p)); + h.input_mut().events.push(Event::ModifiersChanged(modifiers)); + h.step(); + h.input_mut().events.push(Event::PointerButton { pos: p, button: PointerButton::Primary, pressed: true, modifiers }); + h.input_mut().events.push(Event::PointerButton { pos: p, button: PointerButton::Primary, pressed: false, modifiers }); + h.step(); + h.input_mut().events.push(Event::ModifiersChanged(Modifiers::NONE)); + h.run_steps(3); +} + +/// #370: Ctrl/Cmd-click on the current-time display (the Timeline's or the Composition +/// panel's) toggles the Time Display Style between Timecode and Frames, one undo step each, as +/// in After Effects; under the Timeline's display the other style shows. +#[test] +fn ctrl_click_on_the_time_display_toggles_timecode_and_frames() { + use effectcraft_engine::project::TimeDisplayStyle; + let (mut h, _) = harness(); + h.state_mut().session.execute("time.set", json!({"frame": 30})).unwrap(); + h.run_steps(2); + let shown = |h: &Harness<'_, EffectcraftApp>, id: &str| h.state().auto.find(id).unwrap().label.clone(); + let style = |h: &Harness<'_, EffectcraftApp>| h.state().session.project.settings.time_display; + let undo = h.state().session.history.undo.len(); + let at = center(&h, "timeline.timecode"); + click_with(&mut h, at, Modifiers::COMMAND); + assert_eq!(style(&h), TimeDisplayStyle::Frames); + assert_eq!(shown(&h, "timeline.timecode"), "00030"); + assert_eq!(h.state().session.history.undo.len(), undo + 1, "one undo step"); + assert!(!h.ctx.egui_wants_keyboard_input(), "no time entry opened"); + let at = center(&h, "viewer.timecode"); + 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. + click_with(&mut h, at, Modifiers::NONE); + let at = center(&h, "timeline.timecode"); + click_with(&mut h, at, Modifiers::NONE); + assert_eq!(style(&h), TimeDisplayStyle::Timecode); + // Feet + Frames goes to Timecode. + h.state_mut().session.execute("file.projectSettings", json!({"timeDisplay": "feet35"})).unwrap(); + h.run_steps(2); + click_with(&mut h, at, Modifiers::COMMAND); + assert_eq!(style(&h), TimeDisplayStyle::Timecode); +} diff --git a/docs/agents.md b/docs/agents.md index 207406f5..e12c1509 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -482,6 +482,8 @@ Extension points and the bundled extensions: [plugins.md](plugins.md). step (separated X/Y/Z Position; Orientation and X/Y/Z Rotation on 3D layers). * Rename a layer with Enter (or double-click a name): the whole name is selected, Enter or a click elsewhere commits, Escape cancels; `layer.rename {layer, name}` does it directly. +* Ctrl/Cmd-click on the current-time display (Timeline or Composition panel) toggles the Time + Display Style between Timecode and Frames: `file.projectSettings {"timeDisplay": "frames"}`. ### History, puppet recording, plug-ins diff --git a/docs/parity.md b/docs/parity.md index 3f05de6f..4ebdb4b4 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -171,7 +171,7 @@ the disk cache in the formats wave; the 3D Camera Tracker in M12.6.) | Web | 98% | 0.1 | No shared-memory threads inside one engine instance (the decided design is one engine instance per worker; a threaded build needs nightly `build-std`) (M13.30: Render Queue, analyses and Content-Aware Fill render on each job worker's own WebGPU device in passes, particles and Advanced 3D read back under keys, layer buffers in the browser's disk cache; browser storage, Web Audio, Web Worker renders/analyses, WebGPU viewer and offline install landed in M15.2; viewer frames in frame workers fed by project diffs, Roto Brush propagation in a worker, non-blocking `wait: true` jobs and a browser Media Browser (File System Access folders, browser storage) in M13.10; WEB-1 in M13.24: GPU effects and the GPU compositor in the frame workers on their own WebGPU devices with deferred readbacks (frames render in passes; Backend Auto per comp), the disk cache in the Origin Private File System (written by the workers with sync access handles, LRU under the settings' limit, served after a reload), the storage manager (Settings ▸ Disk ▸ Browser Storage, `storage.info` / `storage.persist` / `storage.clear`), and a WGSL constant Chrome rejected (it disabled every GPU kernel in Chrome) fixed) | | 3D | 88% | 5.5 | reflections of other layers (Advanced 3D surfaces reflect only the Environment light; Material Options ▸ Appears in Reflections is stored but has no visible effect yet, #261), multi-view layouts, the Extended Viewer for Advanced 3D comps (Classic 3D Extended Viewer landed in M13.5 UI completion); collapsed precomps of another size seen through the parent's camera render (fixed in M13.2); stereo rigs, orbit nulls, lights controlled by the camera, cameras/lights from glTF models, environment backgrounds, Advanced 3D motion blur, blend modes and track mattes landed in M7.7; Classic 3D iris-shaped bokeh with highlights, progressive depth of field on tilted layers and the focus-link commands landed in M13.5; Advanced 3D depth of field with the iris and highlight options, collapsed precomps as real Advanced 3D geometry and extruded text/shape strokes landed in M13.8; Advanced 3D (glTF/OBJ models, primitives, extruded text and shapes, PBR, image-based light, shadow maps, GPU rasteriser) in M7.4–M7.6; Advanced 3D end to end on the GPU (motion blur, iris depth of field, compositing) in M13.11 | | Effects | ≈ 85% | 3.0 | GPU versions of the remaining effects (280 run on the GPU since M13.29, EFF-5: M13.28 added the remaining pixel effects and the geometry and audio generators (Lightning, Advanced Lightning, Beam, Lens Flare, Radio Waves, Vegas, Stroke, Scribble, Write-on, Paint Bucket, Eyedropper Fill, CC Glue Gun, CC Threads, Audio Spectrum / Waveform, Basic / Path Text); M13.29 added the particle effects (CC Particle World / Systems II, Particle Playground, CC Ball Action, CC Pixel Polly, CC Scatterize, Curl Noise) and removed the fallbacks of Shatter's wireframe views, Foam's extras, Timewarp's Matte Layer, strong Warp Fisheye / Twist bends and custom `.ocio` configs; M13.23 added the 3D Channel and Immersive Video families, Apply Color LUT, the OCIO effects, Color Profile Converter and the simulations' render passes (Card Wipe included); M13.22 added the CC light family, the CC transitions, Block Dissolve, Radial Shadow, CC Bender / Blobbylize / Cylinder / Sphere / Spotlight / Environment, 3D Glasses, Numbers, Timecode and the time effects joined the 166 of M13.13; Fractal and the analysis / tool effects still render on the CPU) and the missing controls listed as partial in [effects.md](effects.md) (every After Effects effect exists since M9.11, M12.5 and M12.6; parameter names, order, twirl-downs, popups, units and defaults were aligned in M9.12) | -| Interface | 75% | 5.0 | more Learn tutorials and pixel-level fidelity of dialogs (G2 on 9 October: the viewer's resize cursors point the way each handle scales and turn with the layer, #393; the in-window menu bar works from the keyboard (Alt, arrows, Enter, Escape) and the app's shortcuts wait while a menu is open, #279; the Timeline's composition marker bin and numpad * add comp markers, #275; Settings ▸ Appearance ▸ UI Scale (75–200 %, beyond After Effects, which follows the system's scaling), #284; G2 on 7 October: Project items, files and effects dropped on the Composition viewer, Project items dropped in the Timeline between layers and at a time, #85, #88, #89; popup menus moved to fit the window take clicks, so Render Queue templates apply, #117; View ▸ New Viewer landed in M13.32; an automation id on every control of Composition and Solid Settings, Group / Ungroup Shapes in the Layer menu, Window ▸ Learn, the flowchart shortcuts and Reveal in Explorer landed in M3.10 and M3.13; the Home ▸ Templates gallery (eight original built-in templates, user templates from File ▸ Save as Template…) and View ▸ Simulate Output ▸ My Custom RGB… landed in M13.25; Timeline layer reordering by drag, a non-snapping viewer pan, a working rename field and twirl arrows, and the full set of property reveal shortcuts — double presses, Alt+Shift keyframes, Ctrl+` — landed in M13.17–M13.20; the Home ▸ Learn tab with interactive tutorials and a UI fidelity pass landed in M13.5 UI completion; visual editors for Lumetri RGB / hue-saturation curves, Colorama's output cycle wheel, Glow's colour map and Reshape's correspondence points (viewer handles), a scrolling Preview panel and AE-style Composition / Timeline tabs (close, label swatch, viewer lock) landed in M13.10; Timeline outline and Project panel columns scroll horizontally, the Layer Style dialog, ROI resize handles, Pan Behind snapping and 3D Reference Axes landed in M13.5; native macOS menu bar, Timeline columns/search/reveal-add, Home screen with recent projects and all AE workspaces landed; viewer rulers/snapping/channels/snapshots landed in M0.13) | +| Interface | 75% | 5.0 | more Learn tutorials and pixel-level fidelity of dialogs (G2 on 9 October: the viewer's resize cursors point the way each handle scales and turn with the layer, #393; Ctrl/Cmd-click on the Timeline's or the Composition panel's current-time display toggles Timecode / Frames, #370; the in-window menu bar works from the keyboard (Alt, arrows, Enter, Escape) and the app's shortcuts wait while a menu is open, #279; the Timeline's composition marker bin and numpad * add comp markers, #275; Settings ▸ Appearance ▸ UI Scale (75–200 %, beyond After Effects, which follows the system's scaling), #284; G2 on 7 October: Project items, files and effects dropped on the Composition viewer, Project items dropped in the Timeline between layers and at a time, #85, #88, #89; popup menus moved to fit the window take clicks, so Render Queue templates apply, #117; View ▸ New Viewer landed in M13.32; an automation id on every control of Composition and Solid Settings, Group / Ungroup Shapes in the Layer menu, Window ▸ Learn, the flowchart shortcuts and Reveal in Explorer landed in M3.10 and M3.13; the Home ▸ Templates gallery (eight original built-in templates, user templates from File ▸ Save as Template…) and View ▸ Simulate Output ▸ My Custom RGB… landed in M13.25; Timeline layer reordering by drag, a non-snapping viewer pan, a working rename field and twirl arrows, and the full set of property reveal shortcuts — double presses, Alt+Shift keyframes, Ctrl+` — landed in M13.17–M13.20; the Home ▸ Learn tab with interactive tutorials and a UI fidelity pass landed in M13.5 UI completion; visual editors for Lumetri RGB / hue-saturation curves, Colorama's output cycle wheel, Glow's colour map and Reshape's correspondence points (viewer handles), a scrolling Preview panel and AE-style Composition / Timeline tabs (close, label swatch, viewer lock) landed in M13.10; Timeline outline and Project panel columns scroll horizontally, the Layer Style dialog, ROI resize handles, Pan Behind snapping and 3D Reference Axes landed in M13.5; native macOS menu bar, Timeline columns/search/reveal-add, Home screen with recent projects and all AE workspaces landed; viewer rulers/snapping/channels/snapshots landed in M0.13) | | Project | ≈ 68% | 5.5 | OCIO displays beyond the built-in tone map; relative footage paths and relinking when a project moves (unsaved-changes prompts, a modified mark that follows undo and project files that name their version and always reopen landed in M3.9 and M3.14; auto-save and crash recovery in M3.7; Color Engine with OCIO/ACES working spaces, HDR compand/tone mapping, Rec. 2100 PQ/HLG output, Feet + Frames, display colour management, Simulate Output and the locked viewer landed in M7.7; proxies and Interpret Footage fields / pixel aspect / alpha guess landed: PRJ-8, PRJ-3; image sequence import options (Sequence, Force Alphabetical Order, frame rate, picked ranges, folders, the browser), missing-frame placeholders and Interpret Footage Start Timecode landed: #297) | | Masks & roto | 74% | 5.0 | (audit estimates; Roto Brush 2.0 / 3.0's trained model, MobileSAM in the swappable `segment` module, has since landed in M13.35; variable-width mask feather points with the Mask Feather tool landed in M13.5; mask tracking and Mask Interpolation landed in M6.6; Roto Brush & Refine Edge with graph-cut segmentation, flow propagation, edge matting, decontamination and Freeze in M6.7) | | Preview | 77% | 2.8 | GPU effects for the audio-driven effects and the stepped particle systems; content-keyed RAM preview frames (an edit drops every comp's), audio scrubbing, more snapshot slots (G2 on 7 October: Audio, Lock and Shy switches keep the cached frames and playback with audio waits for frames instead of skipping them, #103; running out of video memory renders the frame on the CPU and keeps fewer GPU frames instead of crashing, #106; RAM preview keys with the render options, least-recently-shown eviction, Auto resolution while playing, Cache Before Playback that fits, an achieved-fps readout, Cache Frames When Idle and purges that clear what they name landed in M4.5–M4.8; 3D channel, VR, OCIO, Card Wipe and the simulations' render passes run on the GPU since M13.23; Advanced 3D layers with blend modes / track mattes / Preserve Transparency and environment backgrounds composite on the GPU since M13.22; pooled GPU textures and fused quantisation since M13.13: Lower Third GPU warm 21 → 4 ms/frame, and Auto now picks the CPU or the GPU per comp from measured frame times; Advanced 3D runs — raster, motion blur, iris depth of field, compositing — and wireframes run on the GPU since M13.11: CPU warm 3394 → GPU warm 290 ms/frame at 1920×1080 (11.7×; Half 968 → 85 ms), GPU ≠ CPU on 0.005 % of pixels, on an M4 Pro under load; Classic 3D bokeh depth of field — iris shapes, highlights, fringe, progressive blur on tilted planes — runs in WGSL since M13.6: 3D Showcase GPU warm 205 → 54 ms/frame at full size on an M4 Pro; the Preview panel's five shortcuts with their own Include / Loop / Cache Before Playback / Range / Play From / Frame Rate / Skip / Resolution / Full Screen / stop options and `playback.settings.get/set` landed in M13.8; Classic 3D runs and adjustment layers composite on the GPU since M12.7; persistent disk cache with the blue cache bar landed; region of interest, snapshots, exposure and Fast Previews landed in M0.13) |