Timeline: Ctrl/Cmd+wheel and a trackpad pinch zoom time (#428)

egui turns Ctrl/Cmd+wheel and a pinch into a zoom factor (zoom_delta)
rather than a scroll, and the Timeline only zoomed on Alt+wheel, so
both did nothing over the layers. They now zoom time around the
pointer, like Alt+wheel. (B stays Set Work Area Start as in After
Effects; Split Layer is Ctrl/Cmd+Shift+D.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
This commit is contained in:
Claude 2026-10-10 19:16:48 +00:00
parent 69bf806ff5
commit 3bde85dfdb
No known key found for this signature in database
2 changed files with 35 additions and 2 deletions

View File

@ -1614,9 +1614,14 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let max_scroll = (total_h - rows_rect.height() + rh).max(0.0);
if ui.rect_contains_pointer(rows_rect) {
let (dy, dx, zoom) = ui.input(|i| (i.smooth_scroll_delta.y, i.smooth_scroll_delta.x, i.modifiers.alt));
if zoom && dy.abs() > 0.0 {
// Ctrl/Cmd+wheel and a trackpad pinch reach egui as a zoom factor rather than a scroll
// (#428); like Alt+wheel they zoom time.
let pinch = ui.input(|i| i.zoom_delta()) as f64;
let at_x = ui.input(|i| i.pointer.hover_pos()).map(|p| p.x).unwrap_or(graph_x0);
if (pinch - 1.0).abs() > 1e-4 {
set_zoom(&mut app.ui.timeline, &comp, tm.pps * pinch, fit_pps, tm.t(at_x), (at_x - tm.x0) as f64);
} else if zoom && dy.abs() > 0.0 {
// Alt+wheel zooms around the pointer, out until the whole comp shows.
let at_x = ui.input(|i| i.pointer.hover_pos()).map(|p| p.x).unwrap_or(graph_x0);
set_zoom(&mut app.ui.timeline, &comp, tm.pps * (dy as f64 / 200.0).exp(), fit_pps, tm.t(at_x), (at_x - tm.x0) as f64);
} else {
let over_outline = ui.input(|i| i.pointer.hover_pos()).is_some_and(|p| p.x < graph_x0);

View File

@ -59,6 +59,34 @@ fn alt_wheel_zooms_out_until_the_whole_comp_shows() {
assert!(h.state().ui.timeline.pps.is_some());
}
/// #428: Ctrl/Cmd+wheel and a trackpad pinch zoom time over the layers, as Alt+wheel does (egui
/// hands them over as a zoom factor, which the Timeline ignored).
#[test]
fn ctrl_wheel_and_pinch_zoom_time() {
let (mut h, layer) = harness();
h.state_mut().ui.timeline.pps = Some(200.0);
h.run_steps(2);
let pps = |h: &Harness<'_, EffectcraftApp>| h.state().ui.timeline.pps.unwrap_or(0.0);
let start = pps(&h);
assert!(start > 0.0);
let at = over_bar(&h, layer);
h.event(Event::PointerMoved(at));
h.event(Event::Zoom(0.5));
h.step();
let pinched = pps(&h);
assert!(pinched > 0.0 && pinched < start * 0.75, "a pinch in zooms out: {start} -> {pinched}");
let at = over_bar(&h, layer);
h.event(Event::PointerMoved(at));
h.event(Event::ModifiersChanged(Modifiers::COMMAND));
h.event(Event::MouseWheel { unit: MouseWheelUnit::Point, delta: vec2(0.0, 240.0), modifiers: Modifiers::COMMAND, phase: egui::TouchPhase::Move });
h.step();
h.event(Event::ModifiersChanged(Modifiers::NONE));
h.step();
assert!(pps(&h) > pinched, "Ctrl/Cmd+wheel up zooms in: {pinched} -> {}", pps(&h));
// Over the layers, so the rows did not scroll.
assert_eq!(h.state().ui.timeline.scroll_y, 0.0);
}
fn rect(h: &Harness<'_, EffectcraftApp>, id: &str) -> egui::Rect {
let e = h.state().auto.find(id).unwrap_or_else(|| panic!("no {id}")).clone();
egui::Rect::from_min_size(pos2(e.rect[0], e.rect[1]), vec2(e.rect[2], e.rect[3]))