Files
effectcraft/crates/ui-egui/tests/it/ui_tracker.rs
T
aiongg 0173abde8c CI: dev-profile gate, one test binary per crate, nextest, line tables only (#2235)
* Dev profile: line tables only for workspace crates, no debug info for dependencies

* xtask ci: dev-profile gate, nextest, tests without examples, wasm --keep-going

* One integration-test binary per crate

* xtask ci: run effectcraft-gpu's shared-device tests in one cargo test process

* xtask ci: run every stage and report all failures

* xtask ci: name the stage type (clippy type_complexity)

* xtask ci: run every test pass and report all failures

* cargo fmt
2026-10-23 12:59:51 -04:00

168 lines
8.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Headless checks for the Tracker panel and the viewer's track point widgets (egui_kittest, UI
//! logic only: no GPU needed).
use effectcraft_engine::Session;
use effectcraft_ui_egui::EffectcraftApp;
use effectcraft_ui_egui::dock::PanelKind;
use egui_kittest::Harness;
use serde_json::json;
fn app() -> EffectcraftApp {
let mut s = Session::default();
s.execute("comp.new", json!({"name": "Track", "width": 640, "height": 360, "duration": 2})).unwrap();
s.execute("layer.newSolid", json!({"name": "Plate", "color": "#406080"})).unwrap();
s.execute("layer.newSolid", json!({"name": "Target", "width": 80, "height": 80})).unwrap();
s.execute("layer.select", json!({"layers": ["Plate"]})).unwrap();
EffectcraftApp::new(s)
}
fn ids(h: &Harness<'_, EffectcraftApp>) -> Vec<String> {
h.state().auto.previous.iter().chain(h.state().auto.elements.iter()).map(|e| e.id.clone()).collect()
}
fn rect(h: &Harness<'_, EffectcraftApp>, id: &str) -> egui::Rect {
let e = h.state().auto.find(id).unwrap_or_else(|| panic!("no {id}"));
egui::Rect::from_min_size(egui::pos2(e.rect[0], e.rect[1]), egui::vec2(e.rect[2], e.rect[3]))
}
#[test]
fn tracker_panel_and_track_point_widgets() {
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| app());
h.state_mut().show_panel(PanelKind::Tracker);
h.run_steps(3);
let all = ids(&h);
for want in [
"tracker.trackCamera",
"tracker.warpStabilizer",
"tracker.trackMotion",
"tracker.stabilizeMotion",
"tracker.motionSource",
"tracker.currentTrack",
"tracker.trackType",
"tracker.position",
"tracker.rotation",
"tracker.scale",
"tracker.motionTarget",
"tracker.editTarget",
"tracker.options",
"tracker.analyze.frameBackward",
"tracker.analyze.backward",
"tracker.analyze.forward",
"tracker.analyze.frameForward",
"tracker.reset",
"tracker.apply",
] {
assert!(all.iter().any(|i| i == want), "missing {want}");
}
// Track Motion from the panel.
let r = rect(&h, "tracker.trackMotion");
h.input_mut().events.push(egui::Event::PointerMoved(r.center()));
h.input_mut().events.push(egui::Event::PointerButton {
pos: r.center(),
button: egui::PointerButton::Primary,
pressed: true,
modifiers: Default::default(),
});
h.input_mut().events.push(egui::Event::PointerButton {
pos: r.center(),
button: egui::PointerButton::Primary,
pressed: false,
modifiers: Default::default(),
});
h.run_steps(3);
let st = h.state_mut().session.execute("track.status", json!({})).unwrap();
assert_eq!(st["tracker"]["name"], "Tracker 1", "{st}");
h.state_mut().session.execute("track.setPoint", json!({"point": 1, "featureSize": [60, 60], "searchSize": [120, 120]})).unwrap();
h.state_mut().show_panel(PanelKind::Composition);
h.run_steps(3);
let all = ids(&h);
for want in ["viewer.track.1.feature", "viewer.track.1.search", "viewer.track.1.attach", "viewer.track.1.feature.0", "viewer.track.1.search.2"] {
assert!(all.iter().any(|i| i == want), "missing {want}");
}
// Drag the feature region: the point moves by the drag in layer pixels (one undo step).
let c0 = st["tracker"]["points"][0]["center"].clone();
let fr = rect(&h, "viewer.track.1.feature");
let zoom = {
let a = rect(&h, "viewer.comp");
a.width() / 640.0
};
// Inside the feature region, away from the attach point crosshair (which sits on the centre).
let from = fr.center() + egui::vec2(15.0, 0.0);
let to = from + egui::vec2(40.0, 20.0);
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);
let st = h.state_mut().session.execute("track.status", json!({})).unwrap();
let c1 = &st["tracker"]["points"][0]["center"];
let dx = c1[0].as_f64().unwrap() - c0[0].as_f64().unwrap();
let dy = c1[1].as_f64().unwrap() - c0[1].as_f64().unwrap();
assert!((dx - 40.0 / zoom as f64).abs() < 1.0 && (dy - 20.0 / zoom as f64).abs() < 1.0, "moved by ({dx}, {dy}) at zoom {zoom}");
let undo = h.state().session.history.undo.iter().filter(|(l, _)| l == "Move Track Point").count();
assert_eq!(undo, 2, "one undo step per drag");
// Options and Edit Target dialogs.
let ctx = h.ctx.clone();
effectcraft_ui_egui::menus::invoke(h.state_mut(), &ctx, "track.optionsDialog", json!({})).unwrap();
assert_eq!(h.state().dialog, Some(effectcraft_ui_egui::Dialog::TrackOptions));
h.step();
assert!(ids(&h).iter().any(|i| i == "dialog.trackOptions.ok"));
h.state_mut().dialog = None;
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}×");
}
}