Tracker: a track point can be grabbed when zoomed out (fixes #1067)

The attach point's 12 px grab area sat on top of the feature region and is tested first. Zoomed out, a default feature region is smaller than that on screen, so every press inside it moved the attach point instead of the point. Inside the feature region the attach point now grabs only the region's inner half.
This commit is contained in:
Marco Arnulfo 2026-10-12 21:21:57 +02:00
parent 470ea7a3af
commit 8e21e5dbc1
2 changed files with 55 additions and 2 deletions

View File

@ -749,7 +749,11 @@ pub fn draw_overlay(app: &mut EffectcraftApp, painter: &egui::Painter, map: &Vie
let fbig = fq[0].distance(fq[2]) > 28.0;
let sbig = sq[0].distance(sq[2]) > 28.0;
let n = i + 1;
let hr = Rect::from_center_size(a, vec2(12.0, 12.0));
// Zoomed out the feature region can be smaller than the attach point's grab area: keep the
// grab to the region's inner half so the rest of it still grabs the point.
let fr = Rect::from_points(&fq);
let grab = if quad_contains(&fq, a) { 12.0f32.min(fr.width().min(fr.height()) * 0.5) } else { 12.0 };
let hr = Rect::from_center_size(a, vec2(grab, grab));
hits.push(Hit { point: n, part: Part::Attach, rect: hr, quad: None });
app.auto.add(&format!("viewer.track.{n}.attach"), hr, "Attach Point");
for (k, q) in fq.iter().enumerate() {
@ -768,7 +772,6 @@ pub fn draw_overlay(app: &mut EffectcraftApp, painter: &egui::Painter, map: &Vie
hits.push(Hit { point: n, part: Part::SearchCorner(k), rect: r.expand(2.0), quad: None });
app.auto.add(&format!("viewer.track.{n}.search.{k}"), r, "Search region corner");
}
let fr = Rect::from_points(&fq);
let sr = Rect::from_points(&sq);
app.auto.add(&format!("viewer.track.{n}.feature"), fr, "Feature region");
app.auto.add(&format!("viewer.track.{n}.search"), sr, "Search region");

View File

@ -115,3 +115,53 @@ fn tracker_panel_and_track_point_widgets() {
effectcraft_ui_egui::menus::invoke(h.state_mut(), &ctx, "track.editTargetDialog", json!({})).unwrap();
assert_eq!(h.state().dialog, Some(effectcraft_ui_egui::Dialog::TrackTarget));
}
fn drag(h: &mut Harness<'_, EffectcraftApp>, 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: egui::PointerButton::Primary, 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: egui::PointerButton::Primary, pressed: false, modifiers: Default::default() });
h.run_steps(2);
}
/// #1067: zoomed out, a default track point's feature region is smaller on screen than the attach
/// point's grab area, which used to swallow every press inside it: the point itself could not be
/// grabbed. Inside the region off its centre moves the point at any zoom; the centre still grabs the
/// attach point.
#[test]
fn zoomed_out_track_point_can_be_grabbed() {
for mag in [0.25f32, 0.5, 1.0] {
let mut s = Session::default();
s.execute("comp.new", json!({"name": "Track", "width": 1920, "height": 1080, "duration": 2})).unwrap();
s.execute("layer.newSolid", json!({"name": "Plate", "color": "#406080"})).unwrap();
s.execute("layer.select", json!({"layers": ["Plate"]})).unwrap();
s.execute("track.motion", json!({})).unwrap();
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(move |_| EffectcraftApp::new(s));
h.state_mut().show_panel(PanelKind::Composition);
h.run_steps(3);
h.state_mut().ui.viewer.zoom = Some(mag);
h.run_steps(3);
let zoom = rect(&h, "viewer.comp").width() / 1920.0;
let point = |h: &mut Harness<'_, EffectcraftApp>| h.state_mut().session.execute("track.status", json!({})).unwrap()["tracker"]["points"][0].clone();
let p0 = point(&mut h);
let fr = rect(&h, "viewer.track.1.feature");
let from = fr.center() + egui::vec2(fr.width() * 0.35, 0.0);
drag(&mut h, from, from + egui::vec2(40.0, 20.0));
let p1 = point(&mut h);
let dx = p1["center"][0].as_f64().unwrap() - p0["center"][0].as_f64().unwrap();
let dy = p1["center"][1].as_f64().unwrap() - p0["center"][1].as_f64().unwrap();
assert!((dx - 40.0 / zoom as f64).abs() < 1.0 && (dy - 20.0 / zoom as f64).abs() < 1.0, "{mag}×: the point moved by ({dx}, {dy}), {p1}");
assert_eq!(p1["attachOffset"], p0["attachOffset"], "{mag}×: the attach point moves with the point");
let fr = rect(&h, "viewer.track.1.feature");
drag(&mut h, fr.center(), fr.center() + egui::vec2(40.0, 20.0));
let p2 = point(&mut h);
assert_eq!(p2["center"], p1["center"], "{mag}×: the centre grabs the attach point, not the point");
assert_ne!(p2["attachOffset"], p1["attachOffset"], "{mag}×: the centre grabs the attach point");
// Dragged out of the region, the attach point keeps its full grab area.
assert_eq!(rect(&h, "viewer.track.1.attach").width(), 12.0, "{mag}×");
}
}