Fix: still track mouse when dragging outside web canvas (#4522)

* Closes https://github.com/emilk/egui/issues/3157

If the mouse leaves the canvas when dragging a slider, the slider will
still move.

---

To support this, I had to revert https://github.com/emilk/egui/pull/4419
Despite that, I fail to reproduce the two issues it claimed to solve:

* https://github.com/emilk/egui/issues/4406 may have been solved in
another way by this PR
* https://github.com/emilk/egui/issues/4418 I cannot reproduce on Mac.
If it is still a problem, I think it should be solved by triggering a
`PointerEvent::Released` when focus is lost (i.e. on alt-tab), and not
on `PointerGone`
This commit is contained in:
Emil Ernerfeldt 2024-05-22 11:48:34 +02:00 committed by GitHub
parent 7035aa4e53
commit c8578c9a6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 73 additions and 56 deletions

View File

@ -284,6 +284,8 @@ pub(crate) fn install_color_scheme_change_event(runner_ref: &WebRunner) -> Resul
pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValue> { pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValue> {
let canvas = runner_ref.try_lock().unwrap().canvas().clone(); let canvas = runner_ref.try_lock().unwrap().canvas().clone();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
{ {
let prevent_default_events = [ let prevent_default_events = [
@ -333,8 +335,11 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
}, },
)?; )?;
// NOTE: we register "mousemove" on `document` instead of just the canvas
// in order to track a dragged mouse outside the canvas.
// See https://github.com/emilk/egui/issues/3157
runner_ref.add_event_listener( runner_ref.add_event_listener(
&canvas, &document,
"mousemove", "mousemove",
|event: web_sys::MouseEvent, runner| { |event: web_sys::MouseEvent, runner| {
let modifiers = modifiers_from_mouse_event(&event); let modifiers = modifiers_from_mouse_event(&event);
@ -347,31 +352,37 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
}, },
)?; )?;
runner_ref.add_event_listener(&canvas, "mouseup", |event: web_sys::MouseEvent, runner| { // Use `document` here to notice if the user releases a drag outside of the canvas.
let modifiers = modifiers_from_mouse_event(&event); // See https://github.com/emilk/egui/issues/3157
runner.input.raw.modifiers = modifiers; runner_ref.add_event_listener(
if let Some(button) = button_from_mouse_event(&event) { &document,
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); "mouseup",
let modifiers = runner.input.raw.modifiers; |event: web_sys::MouseEvent, runner| {
runner.input.raw.events.push(egui::Event::PointerButton { let modifiers = modifiers_from_mouse_event(&event);
pos, runner.input.raw.modifiers = modifiers;
button, if let Some(button) = button_from_mouse_event(&event) {
pressed: false, let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
modifiers, let modifiers = runner.input.raw.modifiers;
}); runner.input.raw.events.push(egui::Event::PointerButton {
pos,
button,
pressed: false,
modifiers,
});
// In Safari we are only allowed to write to the clipboard during the // In Safari we are only allowed to write to the clipboard during the
// event callback, which is why we run the app logic here and now: // event callback, which is why we run the app logic here and now:
runner.logic(); runner.logic();
// Make sure we paint the output of the above logic call asap: // Make sure we paint the output of the above logic call asap:
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
text_agent::update_text_agent(runner); text_agent::update_text_agent(runner);
} }
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
})?; },
)?;
runner_ref.add_event_listener( runner_ref.add_event_listener(
&canvas, &canvas,
@ -412,8 +423,10 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
}, },
)?; )?;
// Use `document` here to notice if the user drag outside of the canvas.
// See https://github.com/emilk/egui/issues/3157
runner_ref.add_event_listener( runner_ref.add_event_listener(
&canvas, &document,
"touchmove", "touchmove",
|event: web_sys::TouchEvent, runner| { |event: web_sys::TouchEvent, runner| {
let mut latest_touch_pos_id = runner.input.latest_touch_pos_id; let mut latest_touch_pos_id = runner.input.latest_touch_pos_id;
@ -434,28 +447,34 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
}, },
)?; )?;
runner_ref.add_event_listener(&canvas, "touchend", |event: web_sys::TouchEvent, runner| { // Use `document` here to notice if the user releases a drag outside of the canvas.
if let Some(pos) = runner.input.latest_touch_pos { // See https://github.com/emilk/egui/issues/3157
let modifiers = runner.input.raw.modifiers; runner_ref.add_event_listener(
// First release mouse to click: &document,
runner.input.raw.events.push(egui::Event::PointerButton { "touchend",
pos, |event: web_sys::TouchEvent, runner| {
button: egui::PointerButton::Primary, if let Some(pos) = runner.input.latest_touch_pos {
pressed: false, let modifiers = runner.input.raw.modifiers;
modifiers, // First release mouse to click:
}); runner.input.raw.events.push(egui::Event::PointerButton {
// Then remove hover effect: pos,
runner.input.raw.events.push(egui::Event::PointerGone); button: egui::PointerButton::Primary,
pressed: false,
modifiers,
});
// Then remove hover effect:
runner.input.raw.events.push(egui::Event::PointerGone);
push_touches(runner, egui::TouchPhase::End, &event); push_touches(runner, egui::TouchPhase::End, &event);
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
} }
// Finally, focus or blur text agent to toggle mobile keyboard: // Finally, focus or blur text agent to toggle mobile keyboard:
text_agent::update_text_agent(runner); text_agent::update_text_agent(runner);
})?; },
)?;
runner_ref.add_event_listener( runner_ref.add_event_listener(
&canvas, &canvas,

View File

@ -1875,8 +1875,8 @@ impl Context {
drag_started: _, drag_started: _,
dragged, dragged,
drag_stopped: _, drag_stopped: _,
hovered,
contains_pointer, contains_pointer,
hovered,
} = interact_widgets; } = interact_widgets;
if true { if true {

View File

@ -800,10 +800,8 @@ impl PointerState {
} }
Event::PointerGone => { Event::PointerGone => {
self.latest_pos = None; self.latest_pos = None;
self.pointer_events.push(PointerEvent::Released { // When dragging a slider and the mouse leaves the viewport, we still want the drag to work,
click: None, // so we don't treat this as a `PointerEvent::Released`.
button: PointerButton::Primary,
});
// NOTE: we do NOT clear `self.interact_pos` here. It will be cleared next frame. // NOTE: we do NOT clear `self.interact_pos` here. It will be cleared next frame.
} }
Event::MouseMoved(delta) => *self.motion.get_or_insert(Vec2::ZERO) += *delta, Event::MouseMoved(delta) => *self.motion.get_or_insert(Vec2::ZERO) += *delta,

View File

@ -283,7 +283,7 @@ pub(crate) fn interact(
drag_started, drag_started,
dragged, dragged,
drag_stopped, drag_stopped,
hovered,
contains_pointer, contains_pointer,
hovered,
} }
} }

View File

@ -466,23 +466,23 @@ fn response_summary(response: &egui::Response, show_hovers: bool) -> String {
// These are in inverse logical/chonological order, because we show them in the ui that way: // These are in inverse logical/chonological order, because we show them in the ui that way:
if response.triple_clicked_by(button) { if response.triple_clicked_by(button) {
writeln!(new_info, "Triple_clicked_by{button_suffix}").ok(); writeln!(new_info, "Triple-clicked{button_suffix}").ok();
} }
if response.double_clicked_by(button) { if response.double_clicked_by(button) {
writeln!(new_info, "Double_clicked_by{button_suffix}").ok(); writeln!(new_info, "Double-clicked{button_suffix}").ok();
} }
if response.clicked_by(button) { if response.clicked_by(button) {
writeln!(new_info, "Clicked_by{button_suffix}").ok(); writeln!(new_info, "Clicked{button_suffix}").ok();
} }
if response.drag_stopped_by(button) { if response.drag_stopped_by(button) {
writeln!(new_info, "Drag_stopped_by{button_suffix}").ok(); writeln!(new_info, "Drag stopped{button_suffix}").ok();
} }
if response.dragged_by(button) { if response.dragged_by(button) {
writeln!(new_info, "Dragged_by{button_suffix}").ok(); writeln!(new_info, "Dragged{button_suffix}").ok();
} }
if response.drag_started_by(button) { if response.drag_started_by(button) {
writeln!(new_info, "Drag_started_by{button_suffix}").ok(); writeln!(new_info, "Drag started{button_suffix}").ok();
} }
} }