Fix: native gesture fallback dedupe (#5080)

* fix(input): avoid double-applying native pinch during legacy fallback

* test(input): cover native trackpad rotation and preference gating
This commit is contained in:
Nigel Ihza Firdausy 2026-10-13 23:03:11 +07:00 committed by GitHub
parent 77d3478df1
commit 35710a94ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 53 additions and 0 deletions

View File

@ -317,6 +317,25 @@ mod tests {
assert!(huge.is_finite() && huge.abs() <= 180.0);
}
#[test]
fn native_rotate_event_reaches_canvas_when_trackpad_rotation_is_enabled() {
let ctx = egui::Context::default();
let mut enabled = None;
let mut disabled = None;
let raw = egui::RawInput {
events: vec![Event::Rotate(std::f32::consts::FRAC_PI_6)],
..Default::default()
};
let mut output = ctx.run_ui(raw, |ui| {
enabled = trackpad_delta(ui.ctx(), true);
disabled = trackpad_delta(ui.ctx(), false);
});
output.textures_delta.clear();
assert!(enabled.is_some_and(|degrees| (degrees - 30.0).abs() < 1e-3), "{enabled:?}");
assert_eq!(disabled, None);
}
#[test]
fn trackpad_pref_gates_rotation() {
assert_eq!(apply_trackpad(10.0, false, 0.5), None);

View File

@ -126,6 +126,14 @@ fn pinch_id() -> Id {
/// [`legacy_pinch_to_zoom`] for one frame's raw input (`raw_input_hook`, Windows and Linux).
pub fn fold_legacy_pinch(ctx: &Context, raw: &mut RawInput) {
// Native gesture events already carry their own magnification. Some backends
// also emit a Ctrl-wheel event in the same frame: folding that wheel event
// would apply the same pinch twice. End any legacy continuation as well,
// so a later real Ctrl+wheel notch isn't treated as part of that pinch.
if raw.events.iter().any(|event| matches!(event, Event::Zoom(_))) {
ctx.data_mut(|d| d.remove::<f64>(pinch_id()));
return;
}
let (notch, speed) = ctx.options(|o| (o.input_options.line_scroll_speed, o.input_options.scroll_zoom_speed));
let now = raw.time.unwrap_or_else(|| ctx.input(|i| i.time));
let last = ctx.data(|d| d.get_temp::<f64>(pinch_id()));
@ -359,6 +367,32 @@ mod tests {
assert!(matches!(wheel_only[0], Event::MouseWheel { .. }), "whole mouse notch is not a pinch");
}
#[test]
fn native_pinch_is_not_zoomed_twice_when_ctrl_wheel_arrives_in_the_same_frame() {
let (_, zoom) = gesture(vec![vec![Event::Zoom(1.2), wheel(Vec2::new(0.0, 0.25), CTRL)]], false, true);
assert!((zoom - 1.2).abs() < 1e-4, "native zoom must win over fallback: {zoom}");
}
#[test]
fn native_pinch_resets_legacy_gesture_continuation() {
let ctx = Context::default();
let mut legacy = RawInput { time: Some(1.0), events: vec![wheel(Vec2::new(0.0, 0.25), CTRL)], ..Default::default() };
fold_legacy_pinch(&ctx, &mut legacy);
assert!(matches!(legacy.events[0], Event::Zoom(_)));
// Native magnification takes over before the synthetic gesture times out.
let mut native =
RawInput { time: Some(1.1), events: vec![Event::Zoom(1.1), wheel(Vec2::new(0.0, 0.25), CTRL)], ..Default::default() };
fold_legacy_pinch(&ctx, &mut native);
assert!(matches!(native.events[0], Event::Zoom(f) if (f - 1.1).abs() < 1e-5));
assert!(matches!(native.events[1], Event::MouseWheel { .. }));
// A notched mouse scroll must no longer inherit the previous pinch.
let mut notch = RawInput { time: Some(1.12), events: vec![wheel(Vec2::new(0.0, 1.0), CTRL)], ..Default::default() };
fold_legacy_pinch(&ctx, &mut notch);
assert!(matches!(notch.events[0], Event::MouseWheel { .. }));
}
#[test]
fn a_whole_notch_inside_a_pinch_stays_a_pinch() {
let mut ev = [wheel(Vec2::new(0.0, 0.5), CTRL)];