diff --git a/crates/ui-egui/src/panels/camera_tracker_ui.rs b/crates/ui-egui/src/panels/camera_tracker_ui.rs index 18e24813..71cea155 100644 --- a/crates/ui-egui/src/panels/camera_tracker_ui.rs +++ b/crates/ui-egui/src/panels/camera_tracker_ui.rs @@ -215,8 +215,22 @@ pub fn viewer_hook( return; } // Interaction over the image. - let resp = ui.interact(map.area, egui::Id::new("viewer-camera-tracker"), Sense::click_and_drag()); + // While Space (Hand tool) is held the overlay only hovers, so the viewer underneath gets the pan drag. + let sense = if super::space_hand(&ctx) { Sense::hover() } else { Sense::click_and_drag() }; + let resp = ui.interact(map.area, egui::Id::new("viewer-camera-tracker"), sense); app.auto.add("viewer.cameraTracker", map.area, "3D Camera Tracker points"); + // The overlay is on top of the viewer, so a middle-button drag lands here: pan the viewer like the Hand tool. + if resp.dragged_by(egui::PointerButton::Middle) { + let pid = egui::Id::new("viewer-camera-tracker-pan"); + if resp.drag_started() { + let start_pan = app.ui.viewer.pan; + ui.data_mut(|d| d.insert_temp(pid, start_pan)); + } + if let (Some(start_pan), Some(d)) = (ui.data(|m| m.get_temp::<[f32; 2]>(pid)), resp.total_drag_delta()) { + app.ui.viewer.pan = [start_pan[0] + d.x, start_pan[1] + d.y]; + } + return; + } for (id, sp, r) in hits.iter().take(400) { app.auto.add(&format!("viewer.cameraTracker.point.{id}"), Rect::from_center_size(*sp, vec2(r * 2.0 + 4.0, r * 2.0 + 4.0)), "track point"); } diff --git a/crates/ui-egui/src/panels/viewer.rs b/crates/ui-egui/src/panels/viewer.rs index dbf45034..1a0a8087 100644 --- a/crates/ui-egui/src/panels/viewer.rs +++ b/crates/ui-egui/src/panels/viewer.rs @@ -1046,7 +1046,9 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } } let gid = egui::Id::new("viewer-gesture"); - if let Some(hp) = resp.hover_pos() { + // An overlay on top of the viewer (e.g. the camera tracker points) takes the hover, but the pointer is still over the viewer: keep wheel zoom working. + let hover_pos = resp.hover_pos().or_else(|| if resp.contains_pointer() { ui.input(|i| i.pointer.hover_pos()) } else { None }); + if let Some(hp) = hover_pos { app.pointer_comp = Some({ let c = map.to_comp(hp); [c[0] as f32, c[1] as f32] diff --git a/crates/ui-egui/tests/ui_camera_tracker.rs b/crates/ui-egui/tests/ui_camera_tracker.rs index 9e0bede6..a90c3fa4 100644 --- a/crates/ui-egui/tests/ui_camera_tracker.rs +++ b/crates/ui-egui/tests/ui_camera_tracker.rs @@ -145,6 +145,60 @@ fn viewer_points_selection_and_effect_controls() { assert!(h.state().session.is_enabled("track.camera")); } +/// Drag with `button` from `from` to `to` in steps, then release. +fn drag_with(h: &mut Harness<'_, EffectcraftApp>, button: egui::PointerButton, from: egui::Pos2, to: egui::Pos2) { + h.input_mut().events.push(egui::Event::PointerMoved(from)); + h.input_mut().events.push(egui::Event::PointerButton { pos: from, button, pressed: true, modifiers: Default::default() }); + h.step(); + for i in 1..=8 { + h.input_mut().events.push(egui::Event::PointerMoved(from + (to - from) * (i as f32 / 8.0))); + h.step(); + } + h.input_mut().events.push(egui::Event::PointerButton { pos: to, button, pressed: false, modifiers: Default::default() }); + h.run_steps(3); +} + +/// #505: with the track points shown, the wheel still zooms the viewer, and a middle-button drag +/// and a Spacebar (Hand tool) drag over the points still pan it. +#[test] +fn viewer_zooms_and_pans_over_the_track_points() { + let (a, ..) = app(); + let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| a); + h.state_mut().show_panel(PanelKind::Composition); + h.run_steps(4); + // Wheel over a point: zooms. + let at = rect(&h, "viewer.cameraTracker.point.7").center(); + let zoom = h.state().ui.viewer.zoom; + h.input_mut().events.push(egui::Event::PointerMoved(at)); + h.input_mut().events.push(egui::Event::MouseWheel { + unit: egui::MouseWheelUnit::Point, + delta: egui::vec2(0.0, 120.0), + modifiers: Default::default(), + phase: egui::TouchPhase::Move, + }); + h.run_steps(20); + assert_ne!(h.state().ui.viewer.zoom, zoom, "the wheel zooms over the track points"); + // Middle-button drag from a point: pans, and selects nothing. + let at = rect(&h, "viewer.cameraTracker.point.7").center(); + let before = h.state().ui.viewer.pan; + drag_with(&mut h, egui::PointerButton::Middle, at, at + egui::vec2(120.0, 60.0)); + let after = h.state().ui.viewer.pan; + assert!((after[0] - before[0] - 120.0).abs() < 1.0 && (after[1] - before[1] - 60.0).abs() < 1.0, "{before:?} → {after:?}"); + assert!(h.state().session.state.camera_points.is_empty(), "a pan selects no points"); + // Spacebar held: a primary drag from a point pans instead of drawing a marquee. + let at = rect(&h, "viewer.cameraTracker.point.7").center(); + let space = |h: &mut Harness<'_, EffectcraftApp>, pressed: bool| { + h.input_mut().events.push(egui::Event::Key { key: egui::Key::Space, physical_key: None, pressed, repeat: false, modifiers: Default::default() }); + h.step(); + }; + space(&mut h, true); + drag_with(&mut h, egui::PointerButton::Primary, at, at + egui::vec2(80.0, 40.0)); + space(&mut h, false); + let end = h.state().ui.viewer.pan; + assert!((end[0] - after[0] - 80.0).abs() < 1.0 && (end[1] - after[1] - 40.0).abs() < 1.0, "{after:?} → {end:?}"); + assert!(h.state().session.state.camera_points.is_empty(), "a Spacebar pan selects no points"); +} + /// Step the UI until background frame renders have landed. fn settle(h: &mut Harness<'_, EffectcraftApp>) { for _ in 0..600 {