Fix live hinge animation and Drive Joint slider previews (#835)

Joint previews keep showing finished poses while the slider moves, and the latest value is still computed.
This commit is contained in:
bherbruck authored and GitHub committed 2026-10-13 16:34:09 -04:00
1 parent 152418b624
commit b702f0efde
3 files changed
+194 -7

No files matched your search

@@ -264,6 +264,69 @@ fn drive_value_takes_the_keyboard() {
assert!(app.dialog.is_none());
}
/// A real slider drag updates the requested pose while the pointer is still down.
/// Previewing and cancelling must leave the saved joint and occurrence positions alone.
#[test]
fn drive_slider_previews_intermediate_positions_before_release() {
let mut h = crate::scenario::Harness::new();
h.app = two_boxes();
h.app.viewport.no_pixels = true;
let (a, b) = (occ(&h.app, "A"), occ(&h.app, "B"));
h.app
.run(
"joint.create",
json!({"type": "revolute", "a": {"occurrence": a, "face": [10, 10, 10]},
"b": {"occurrence": b, "face": [40, 10, 0]}, "limits": [[-90, 90]]}),
)
.unwrap();
let before = h.app.session.doc.clone();
h.app.start("joint.drive");
h.frames(4);
let bounds = h
.output
.accesskit_update
.as_ref()
.unwrap()
.nodes
.iter()
.find_map(|(_, n)| (n.role() == egui::accesskit::Role::Slider).then(|| n.bounds()).flatten())
.expect("drive slider");
let at = |fraction: f64| egui::pos2((bounds.x0 + fraction * (bounds.x1 - bounds.x0)) as f32, ((bounds.y0 + bounds.y1) / 2.0) as f32);
let start = at(0.25);
h.app.synthetic.extend([
egui::Event::PointerMoved(start),
egui::Event::PointerButton { pos: start, button: egui::PointerButton::Primary, pressed: true, modifiers: Default::default() },
]);
h.frames(3);
let first = preview_commands(&h.app, h.app.dialog.as_ref().unwrap()).unwrap();
h.app.synthetic.push(egui::Event::PointerMoved(at(0.75)));
h.frames(3);
let second = preview_commands(&h.app, h.app.dialog.as_ref().unwrap()).unwrap();
assert_ne!(first, second, "the held slider requests a new pose before release");
assert!(h.ctx.input(|i| i.pointer.primary_down()));
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
while h.app.preview.busy {
assert!(std::time::Instant::now() < deadline, "held-slider preview did not finish");
std::thread::sleep(std::time::Duration::from_millis(1));
h.frame();
}
assert!(h.app.preview.active, "the viewport shows the pose before release");
assert!(h.app.preview.error.is_none());
assert_eq!(h.app.preview.replaced, ["Body2"]);
assert_eq!(h.app.session.doc, before);
h.app.synthetic.push(egui::Event::PointerButton {
pos: at(0.75),
button: egui::PointerButton::Primary,
pressed: false,
modifiers: Default::default(),
});
h.frames(3);
h.step(&json!({"key": "Escape"})).unwrap();
assert!(h.app.dialog.is_none());
assert!(!h.app.preview.active);
assert_eq!(h.app.session.doc, before, "cancel leaves the joint at its original angle");
}
/// Named joint origins are drawn where their component is: a drive of the occurrence, or a free
/// move not captured yet, carries them along.
#[test]