mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
M3: resize and move the window again right after a resize or move (Wayland)
ViewportCommand::BeginResize and StartDrag hand the pointer to the compositor, which keeps the button's release on Wayland; winit reports only that the pointer left, and egui doesn't count that as a release. egui then still thought the primary button held, so the next press on an edge or the tab strip didn't start a drag until a click inside the window. window_controls::desktop_grab notes each hand-over, and release_after_desktop_grab (called first in raw_input_hook) adds the missed release to the next input, once, unless that input brings one. Ported from WordCraft 1581c288, confirmed by hand there for repeated edge resizes on GNOME/Wayland. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wrwtfrnom6Eg1Jt7xZ7VA8
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0b670caf4a
commit
a9e6069f32
@@ -40,7 +40,7 @@ pub fn tab_strip(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
|
||||
let full = ui.max_rect();
|
||||
let drag = ui.interact(full, ui.id().with("titledrag"), Sense::click_and_drag());
|
||||
if drag.drag_started() {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::StartDrag);
|
||||
window_controls::desktop_grab(ui.ctx(), egui::ViewportCommand::StartDrag);
|
||||
}
|
||||
if drag.double_clicked() {
|
||||
let max = ui.ctx().input(|i| i.viewport().maximized.unwrap_or(false));
|
||||
|
||||
@@ -3125,6 +3125,8 @@ impl eframe::App for PdfCraftApp {
|
||||
/// would keep the place the pointer entered at: tell it where the pointer really is, so the
|
||||
/// page grid can show (and use) the gap under it.
|
||||
fn raw_input_hook(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
// The button's release a window move or resize kept from egui.
|
||||
window_controls::release_after_desktop_grab(ctx, raw);
|
||||
// Before egui reads this frame's wheel input.
|
||||
self.scroll_speed.apply(ctx, raw);
|
||||
if raw.hovered_files.is_empty() && raw.dropped_files.is_empty() {
|
||||
|
||||
@@ -217,7 +217,7 @@ pub fn resize_edges(ctx: &egui::Context) {
|
||||
let response = ui.interact(rect, ui.id().with(("edge", n)), Sense::drag()).on_hover_cursor(cursor(direction));
|
||||
// Sensing only drags, a drag starts on the press itself.
|
||||
if response.drag_started() {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::BeginResize(direction));
|
||||
desktop_grab(ui.ctx(), egui::ViewportCommand::BeginResize(direction));
|
||||
}
|
||||
}
|
||||
// Without the desktop's frame or shadow, a hairline keeps the window apart from
|
||||
@@ -231,6 +231,33 @@ pub fn resize_edges(ctx: &egui::Context) {
|
||||
});
|
||||
}
|
||||
|
||||
fn grab_id() -> egui::Id {
|
||||
egui::Id::new("window_controls_desktop_grab")
|
||||
}
|
||||
|
||||
/// Hand the pointer to the desktop to move or resize the window. On Wayland the compositor keeps
|
||||
/// the button's release and winit reports only that the pointer left, which egui doesn't count as
|
||||
/// a release; egui would think the button still held, and the next press on an edge or the title
|
||||
/// bar wouldn't start a drag until a click inside the window. The release comes back on the next
|
||||
/// input (`release_after_desktop_grab`).
|
||||
pub fn desktop_grab(ctx: &egui::Context, command: egui::ViewportCommand) {
|
||||
ctx.send_viewport_cmd(command);
|
||||
ctx.data_mut(|d| d.insert_temp(grab_id(), ()));
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
/// After `desktop_grab`, add the primary button's release to the next input, unless it brings one.
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Button, Layout, ResizeDirection, zones};
|
||||
@@ -285,4 +312,34 @@ mod tests {
|
||||
assert!(r.width() >= 0.0 && r.height() >= 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_desktop_grab_gives_the_button_release_back() {
|
||||
let ctx = egui::Context::default();
|
||||
let releases = |raw: &egui::RawInput| {
|
||||
raw.events.iter().filter(|e| matches!(e, egui::Event::PointerButton { button: egui::PointerButton::Primary, pressed: false, .. })).count()
|
||||
};
|
||||
let mut raw = egui::RawInput::default();
|
||||
super::release_after_desktop_grab(&ctx, &mut raw);
|
||||
assert_eq!(releases(&raw), 0, "no grab, no release");
|
||||
|
||||
super::desktop_grab(&ctx, egui::ViewportCommand::StartDrag);
|
||||
super::release_after_desktop_grab(&ctx, &mut raw);
|
||||
assert_eq!(releases(&raw), 1, "the release the desktop kept");
|
||||
|
||||
let mut raw = egui::RawInput::default();
|
||||
super::release_after_desktop_grab(&ctx, &mut raw);
|
||||
assert_eq!(releases(&raw), 0, "only once");
|
||||
|
||||
super::desktop_grab(&ctx, egui::ViewportCommand::BeginResize(ResizeDirection::East));
|
||||
let mut raw = egui::RawInput::default();
|
||||
raw.events.push(egui::Event::PointerButton {
|
||||
pos: Pos2::ZERO,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: false,
|
||||
modifiers: egui::Modifiers::NONE,
|
||||
});
|
||||
super::release_after_desktop_grab(&ctx, &mut raw);
|
||||
assert_eq!(releases(&raw), 1, "not doubled when the input brings it");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user