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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rPQFoRmQbw8vWhGgGVrRs
This commit is contained in:
bmh129
2026-10-24 00:58:06 -05:00
co-authored by Claude Opus 5.5
parent 0b63a73c04
commit 185c2dbc36
+52 -5
View File
@@ -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::<Vec<_>>()
};
let ctx = egui::Context::default();
let frame = |events: Vec<egui::Event>, grab: Option<egui::ViewportCommand>| {
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]);
}
}