diff --git a/crates/ui-egui/src/panels/viewer_tools.rs b/crates/ui-egui/src/panels/viewer_tools.rs index 3429f5404..5e5a644e8 100644 --- a/crates/ui-egui/src/panels/viewer_tools.rs +++ b/crates/ui-egui/src/panels/viewer_tools.rs @@ -669,8 +669,8 @@ pub(crate) fn resolution_label(res: Resolution, scale: f64) -> String { } /// The Composition panel's bottom bar, in After Effects 2026 order: magnification, resolution, -/// transparency grid, mask and shape path visibility, region of interest, grid and guide -/// options, show channel, reset exposure, exposure, take / show snapshot, Fast Previews, the 3D +/// Fast Previews, transparency grid, mask and shape path visibility, region of interest, grid and +/// guide options, show channel, reset exposure, exposure, take / show snapshot, the 3D /// renderer and view, and the current time. pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, zoom: f32, time: Tick, comp: &Comp) { let t = app.tokens; @@ -721,10 +721,11 @@ fn exposure_field(app: &mut EffectcraftApp, ui: &mut egui::Ui, er: Rect, stops: } } -/// Lay out the viewer's control row from `bar.min.x`: magnification, resolution, transparency, mask, ROI, grid/guide, -/// channel/colour management, exposure, snapshot, fast previews, 3D renderer/view and Extended Viewer, timecode and render-time -/// indicators, running commands or toggling viewer UI state. Returns the width the row -/// occupies, at least `bar.width()`, for the horizontal scroll area. +/// Lay out the viewer's control row from `bar.min.x`: magnification, resolution, Fast Previews, +/// transparency, mask, ROI, grid/guide, channel/colour management, exposure, snapshot, 3D +/// renderer/view and Extended Viewer, timecode and render-time indicators, running commands or +/// toggling viewer UI state. Returns the width the row occupies, at least `bar.width()`, for the +/// horizontal scroll area. fn bottom_bar_contents(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, zoom: f32, time: Tick, comp: &Comp) -> f32 { let t = app.tokens; let p = ui.painter().clone(); @@ -779,6 +780,16 @@ fn bottom_bar_contents(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, z *x += 24.0; (resp.clicked(), r) }; + // Fast Previews. + let fp = app.session.state.viewer.fast_previews; + let (clicked, fr) = tog(ui, &mut app.auto, &mut x, Icon::Sparkle, fp != FastPreviews::Off || app.ui.viewer.fast_preview, "fastPreviews", "Fast Previews"); + if clicked { + toggle_popup(ui, "vw-fast-pop"); + } + let items: Vec<(String, bool)> = FastPreviews::ALL.iter().map(|m| (m.label().to_string(), fp == *m)).collect(); + if let Some(i) = popup(app, ui, "vw-fast-pop", fr, &items, "fastPreviewsItem") { + let _ = app.session.execute("view.fastPreviewMode", json!({"mode": FastPreviews::ALL[i].id()})); + } if tog(ui, &mut app.auto, &mut x, Icon::Checker, app.ui.viewer.transparency_grid, "transparency", "Toggle Transparency Grid").0 { app.ui.viewer.transparency_grid = !app.ui.viewer.transparency_grid; } @@ -861,16 +872,6 @@ fn bottom_bar_contents(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, z if app.session.snapshot.is_some() && held != app.session.state.viewer.show_snapshot { let _ = app.session.execute("view.showSnapshot", json!({"value": held})); } - // Fast Previews. - let fp = vo.fast_previews; - let (clicked, fr) = tog(ui, &mut app.auto, &mut x, Icon::Sparkle, fp != FastPreviews::Off || app.ui.viewer.fast_preview, "fastPreviews", "Fast Previews"); - if clicked { - toggle_popup(ui, "vw-fast-pop"); - } - let items: Vec<(String, bool)> = FastPreviews::ALL.iter().map(|m| (m.label().to_string(), fp == *m)).collect(); - if let Some(i) = popup(app, ui, "vw-fast-pop", fr, &items, "fastPreviewsItem") { - let _ = app.session.execute("view.fastPreviewMode", json!({"mode": FastPreviews::ALL[i].id()})); - } x += 4.0; // 3D renderer + view. if comp.has_3d() { diff --git a/crates/ui-egui/tests/it/ui_viewer.rs b/crates/ui-egui/tests/it/ui_viewer.rs index 0ab31237b..f0086d28a 100644 --- a/crates/ui-egui/tests/it/ui_viewer.rs +++ b/crates/ui-egui/tests/it/ui_viewer.rs @@ -281,6 +281,7 @@ fn bottom_bar_in_after_effects_order() { let order = [ "viewer.magnification", "viewer.resolution", + "viewer.fastPreviews", "viewer.transparency", "viewer.masks", "viewer.roi", @@ -290,7 +291,6 @@ fn bottom_bar_in_after_effects_order() { "viewer.exposure", "viewer.snapshot", "viewer.showSnapshot", - "viewer.fastPreviews", "viewer.timecode", ]; let xs: Vec = order.iter().map(|id| rect(&h, id).min.x).collect(); diff --git a/crates/ui-egui/tests/it/viewer_geometry.rs b/crates/ui-egui/tests/it/viewer_geometry.rs index d57a8aa9e..8a212cc35 100644 --- a/crates/ui-egui/tests/it/viewer_geometry.rs +++ b/crates/ui-egui/tests/it/viewer_geometry.rs @@ -143,6 +143,16 @@ fn compact_control_row_scrolls_and_preserves_interactions() { h.run_steps(3); assert!(!h.state().session.state.viewer.show_snapshot, "releasing the snapshot control must return to the current frame"); + // Fast Previews sits beside the resolution control, so the row that scrolled to the timecode has + // to scroll back before its menu is reachable. + h.event(Event::MouseWheel { + unit: egui::MouseWheelUnit::Point, + delta: vec2(1500.0, 0.0), + modifiers: Default::default(), + phase: egui::TouchPhase::Move, + source: egui::MouseWheelSource::Wheel, + }); + h.run_steps(30); click_control(&mut h, "viewer.fastPreviews"); click_control(&mut h, "viewer.fastPreviewsItem.1"); assert_eq!(h.state().session.state.viewer.fast_previews.id(), "adaptive", "a scrolled dropdown must retain its menu command");