From 185c2dbc362163c4b7cae97113443879cde5030b Mon Sep 17 00:00:00 2001 From: bmh129 <11478682+bmh129@users.noreply.github.com> Date: Sat, 24 Oct 2026 00:58:06 -0500 Subject: [PATCH] M3: give the kept button release back before the next press (Wayland) The release a desktop grab kept from egui was appended to the next input, so when that input carried the press of a second resize or move the button ended the frame up and the drag stopped. It now goes before the input's first primary press; only a release ahead of any press counts as the kept one, since a quick click's release belongs to its own press. Found by CodeRabbit in review of #3970. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017rPQFoRmQbw8vWhGgGVrRs --- crates/ui-egui/src/window_controls.rs | 57 ++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/crates/ui-egui/src/window_controls.rs b/crates/ui-egui/src/window_controls.rs index 838ce29db..d02ea5dc8 100644 --- a/crates/ui-egui/src/window_controls.rs +++ b/crates/ui-egui/src/window_controls.rs @@ -247,15 +247,23 @@ pub fn desktop_grab(ctx: &egui::Context, command: egui::ViewportCommand) { } /// After `desktop_grab`, add the primary button's release to the next input, unless it brings one. +/// The release goes before the input's first press (the next resize or move, or a click), which +/// egui would otherwise end straight away; a release after that press is the press's own. pub fn release_after_desktop_grab(ctx: &egui::Context, raw: &mut egui::RawInput) { if ctx.data_mut(|d| d.remove_temp::<()>(grab_id())).is_none() { return; } - let released = raw.events.iter().any(|e| matches!(e, egui::Event::PointerButton { button: egui::PointerButton::Primary, pressed: false, .. })); - if !released { - let (pos, modifiers) = ctx.input(|i| (i.pointer.interact_pos().unwrap_or_default(), i.modifiers)); - raw.events.push(egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed: false, modifiers }); - } + let first = raw.events.iter().enumerate().find_map(|(n, e)| match e { + egui::Event::PointerButton { button: egui::PointerButton::Primary, pressed, .. } => Some((n, *pressed)), + _ => None, + }); + let at = match first { + Some((_, false)) => return, + Some((n, true)) => n, + None => raw.events.len(), + }; + let (pos, modifiers) = ctx.input(|i| (i.pointer.interact_pos().unwrap_or_default(), i.modifiers)); + raw.events.insert(at, egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed: false, modifiers }); } #[cfg(test)] @@ -342,4 +350,43 @@ mod tests { super::release_after_desktop_grab(&ctx, &mut raw); assert_eq!(releases(&raw), 1, "not doubled when the input brings it"); } + + #[test] + fn the_release_goes_before_the_press_of_the_next_grab() { + let button = |pressed| egui::Event::PointerButton { + pos: Pos2::new(5.0, 5.0), + button: egui::PointerButton::Primary, + pressed, + modifiers: egui::Modifiers::NONE, + }; + let primary = |raw: &egui::RawInput| { + raw.events + .iter() + .filter_map(|e| if let egui::Event::PointerButton { pressed, .. } = e { Some(*pressed) } else { None }) + .collect::>() + }; + let ctx = egui::Context::default(); + let frame = |events: Vec, grab: Option| { + let mut raw = egui::RawInput { events, ..Default::default() }; + super::release_after_desktop_grab(&ctx, &mut raw); + let order = primary(&raw); + ctx.run_ui(raw, |ui| { + if let Some(command) = grab.clone() { + super::desktop_grab(ui.ctx(), command); + } + }) + .textures_delta + .clear(); + order + }; + // The press of the first resize starts the grab; the desktop keeps its release. + frame(vec![egui::Event::PointerMoved(Pos2::new(5.0, 5.0)), button(true)], Some(egui::ViewportCommand::BeginResize(ResizeDirection::East))); + // The next input brings the press of the second resize. + assert_eq!(frame(vec![button(true)], None), [false, true], "the kept release, then the new press"); + assert!(ctx.input(|i| i.pointer.primary_down()), "the second resize holds the button"); + + // A quick click's release belongs to its own press, not to the grab before it. + frame(vec![], Some(egui::ViewportCommand::StartDrag)); + assert_eq!(frame(vec![button(true), button(false)], None), [false, true, false]); + } }