Refactor web event handling: break up into smaller functions (#4717)

No change in functionality, just making the code easier to read and
change.

* Part of fixing https://github.com/rerun-io/rerun/issues/6638
This commit is contained in:
Emil Ernerfeldt 2024-06-27 15:26:52 +02:00 committed by GitHub
parent ce0e200503
commit a0f4fafb88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 230 additions and 220 deletions

View File

@ -1,3 +1,5 @@
use web_sys::EventTarget;
use super::*; use super::*;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -47,9 +49,51 @@ fn paint_if_needed(runner: &mut AppRunner) {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsValue> { pub(crate) fn install_event_handlers(runner_ref: &WebRunner) -> Result<(), JsValue> {
let document = web_sys::window().unwrap().document().unwrap(); let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let canvas = runner_ref.try_lock().unwrap().canvas().clone();
install_blur_focus(runner_ref, &document)?;
install_blur_focus(runner_ref, &window)?;
prevent_default(
runner_ref,
&canvas,
&[
// Allow users to use ctrl-p for e.g. a command palette:
"afterprint",
// By default, right-clicks open a browser context menu.
// We don't want to do that (right clicks are handled by egui):
"contextmenu",
],
)?;
install_keydown(runner_ref, &document)?;
install_keyup(runner_ref, &document)?;
install_copy_cut_paste(runner_ref, &document)?;
install_mousedown(runner_ref, &canvas)?;
// Use `document` here to notice if the user releases a drag outside of the canvas:
// See https://github.com/emilk/egui/issues/3157
install_mousemove(runner_ref, &document)?;
install_mouseup(runner_ref, &document)?;
install_mouseleave(runner_ref, &canvas)?;
install_touchstart(runner_ref, &canvas)?;
// Use `document` here to notice if the user drag outside of the canvas:
// See https://github.com/emilk/egui/issues/3157
install_touchmove(runner_ref, &document)?;
install_touchend(runner_ref, &document)?;
install_touchcancel(runner_ref, &canvas)?;
install_wheel(runner_ref, &canvas)?;
install_drag_and_drop(runner_ref, &canvas)?;
install_window_events(runner_ref, &window)?;
Ok(())
}
fn install_blur_focus(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
for event_name in ["blur", "focus"] { for event_name in ["blur", "focus"] {
let closure = move |_event: web_sys::MouseEvent, runner: &mut AppRunner| { let closure = move |_event: web_sys::MouseEvent, runner: &mut AppRunner| {
// log::debug!("{event_name:?}"); // log::debug!("{event_name:?}");
@ -64,11 +108,14 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa
runner.egui_ctx().request_repaint(); runner.egui_ctx().request_repaint();
}; };
runner_ref.add_event_listener(&document, event_name, closure)?; runner_ref.add_event_listener(target, event_name, closure)?;
} }
Ok(())
}
fn install_keydown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
runner_ref.add_event_listener( runner_ref.add_event_listener(
&document, target,
"keydown", "keydown",
|event: web_sys::KeyboardEvent, runner| { |event: web_sys::KeyboardEvent, runner| {
if event.is_composing() || event.key_code() == 229 { if event.is_composing() || event.key_code() == 229 {
@ -141,121 +188,90 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa
// event.stop_propagation(); // event.stop_propagation();
} }
}, },
)?; )
}
runner_ref.add_event_listener( fn install_keyup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
&document, runner_ref.add_event_listener(target, "keyup", |event: web_sys::KeyboardEvent, runner| {
"keyup", let modifiers = modifiers_from_kb_event(&event);
|event: web_sys::KeyboardEvent, runner| { runner.input.raw.modifiers = modifiers;
let modifiers = modifiers_from_kb_event(&event); if let Some(key) = translate_key(&event.key()) {
runner.input.raw.modifiers = modifiers; runner.input.raw.events.push(egui::Event::Key {
if let Some(key) = translate_key(&event.key()) { key,
runner.input.raw.events.push(egui::Event::Key { physical_key: None, // TODO(fornwall)
key, pressed: false,
physical_key: None, // TODO(fornwall) repeat: false,
pressed: false, modifiers,
repeat: false, });
modifiers, }
}); runner.needs_repaint.repaint_asap();
} })
runner.needs_repaint.repaint_asap(); }
},
)?;
fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
#[cfg(web_sys_unstable_apis)] #[cfg(web_sys_unstable_apis)]
runner_ref.add_event_listener( runner_ref.add_event_listener(target, "paste", |event: web_sys::ClipboardEvent, runner| {
&document, if let Some(data) = event.clipboard_data() {
"paste", if let Ok(text) = data.get_data("text") {
|event: web_sys::ClipboardEvent, runner| { let text = text.replace("\r\n", "\n");
if let Some(data) = event.clipboard_data() { if !text.is_empty() {
if let Ok(text) = data.get_data("text") { runner.input.raw.events.push(egui::Event::Paste(text));
let text = text.replace("\r\n", "\n"); runner.needs_repaint.repaint_asap();
if !text.is_empty() {
runner.input.raw.events.push(egui::Event::Paste(text));
runner.needs_repaint.repaint_asap();
}
event.stop_propagation();
event.prevent_default();
} }
event.stop_propagation();
event.prevent_default();
} }
}, }
)?; })?;
#[cfg(web_sys_unstable_apis)] #[cfg(web_sys_unstable_apis)]
runner_ref.add_event_listener( runner_ref.add_event_listener(target, "cut", |event: web_sys::ClipboardEvent, runner| {
&document, runner.input.raw.events.push(egui::Event::Cut);
"cut",
|event: web_sys::ClipboardEvent, runner| {
runner.input.raw.events.push(egui::Event::Cut);
// 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();
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
}, })?;
)?;
#[cfg(web_sys_unstable_apis)] #[cfg(web_sys_unstable_apis)]
runner_ref.add_event_listener( runner_ref.add_event_listener(target, "copy", |event: web_sys::ClipboardEvent, runner| {
&document, runner.input.raw.events.push(egui::Event::Copy);
"copy",
|event: web_sys::ClipboardEvent, runner| {
runner.input.raw.events.push(egui::Event::Copy);
// 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();
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
}, })?;
)?;
Ok(()) Ok(())
} }
pub(crate) fn install_window_events(runner_ref: &WebRunner) -> Result<(), JsValue> { fn install_window_events(runner_ref: &WebRunner, window: &EventTarget) -> Result<(), JsValue> {
let window = web_sys::window().unwrap();
for event_name in ["blur", "focus"] {
let closure = move |_event: web_sys::MouseEvent, runner: &mut AppRunner| {
// log::debug!("{event_name:?}");
let has_focus = event_name == "focus";
if !has_focus {
// We lost focus - good idea to save
runner.save();
}
runner.input.on_web_page_focus_change(has_focus);
runner.egui_ctx().request_repaint();
};
runner_ref.add_event_listener(&window, event_name, closure)?;
}
// Save-on-close // Save-on-close
runner_ref.add_event_listener(&window, "onbeforeunload", |_: web_sys::Event, runner| { runner_ref.add_event_listener(window, "onbeforeunload", |_: web_sys::Event, runner| {
runner.save(); runner.save();
})?; })?;
// NOTE: resize is handled by `ResizeObserver` below // NOTE: resize is handled by `ResizeObserver` below
for event_name in &["load", "pagehide", "pageshow"] { for event_name in &["load", "pagehide", "pageshow"] {
runner_ref.add_event_listener(&window, event_name, move |_: web_sys::Event, runner| { runner_ref.add_event_listener(window, event_name, move |_: web_sys::Event, runner| {
// log::debug!("{event_name:?}"); // log::debug!("{event_name:?}");
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
})?; })?;
} }
runner_ref.add_event_listener(&window, "hashchange", |_: web_sys::Event, runner| { runner_ref.add_event_listener(window, "hashchange", |_: web_sys::Event, runner| {
// `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here // `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here
runner.frame.info.web_info.location.hash = location_hash(); runner.frame.info.web_info.location.hash = location_hash();
runner.needs_repaint.repaint_asap(); // tell the user about the new hash runner.needs_repaint.repaint_asap(); // tell the user about the new hash
@ -283,33 +299,27 @@ pub(crate) fn install_color_scheme_change_event(runner_ref: &WebRunner) -> Resul
Ok(()) Ok(())
} }
pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValue> { fn prevent_default(
let canvas = runner_ref.try_lock().unwrap().canvas().clone(); runner_ref: &WebRunner,
let window = web_sys::window().unwrap(); target: &EventTarget,
let document = window.document().unwrap(); event_names: &[&'static str],
) -> Result<(), JsValue> {
for event_name in event_names {
let closure = move |event: web_sys::MouseEvent, _runner: &mut AppRunner| {
event.prevent_default();
// event.stop_propagation();
// log::debug!("Preventing event {event_name:?}");
};
{ runner_ref.add_event_listener(target, event_name, closure)?;
let prevent_default_events = [
// By default, right-clicks open a context menu.
// We don't want to do that (right clicks is handled by egui):
"contextmenu",
// Allow users to use ctrl-p for e.g. a command palette:
"afterprint",
];
for event_name in prevent_default_events {
let closure = move |event: web_sys::MouseEvent, _runner: &mut AppRunner| {
event.prevent_default();
// event.stop_propagation();
// log::debug!("Preventing event {event_name:?}");
};
runner_ref.add_event_listener(&canvas, event_name, closure)?;
}
} }
Ok(())
}
fn install_mousedown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
runner_ref.add_event_listener( runner_ref.add_event_listener(
&canvas, target,
"mousedown", "mousedown",
|event: web_sys::MouseEvent, runner: &mut AppRunner| { |event: web_sys::MouseEvent, runner: &mut AppRunner| {
let modifiers = modifiers_from_mouse_event(&event); let modifiers = modifiers_from_mouse_event(&event);
@ -334,61 +344,59 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
event.stop_propagation(); event.stop_propagation();
// Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here. // Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here.
}, },
)?; )
}
fn install_mousemove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
// NOTE: we register "mousemove" on `document` instead of just the canvas // NOTE: we register "mousemove" on `document` instead of just the canvas
// in order to track a dragged mouse outside the canvas. // in order to track a dragged mouse outside the canvas.
// See https://github.com/emilk/egui/issues/3157 // See https://github.com/emilk/egui/issues/3157
runner_ref.add_event_listener( runner_ref.add_event_listener(target, "mousemove", |event: web_sys::MouseEvent, runner| {
&document, let modifiers = modifiers_from_mouse_event(&event);
"mousemove", runner.input.raw.modifiers = modifiers;
|event: web_sys::MouseEvent, runner| { let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
let modifiers = modifiers_from_mouse_event(&event); runner.input.raw.events.push(egui::Event::PointerMoved(pos));
runner.input.raw.modifiers = modifiers; runner.needs_repaint.repaint_asap();
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); event.stop_propagation();
runner.input.raw.events.push(egui::Event::PointerMoved(pos)); event.prevent_default();
runner.needs_repaint.repaint_asap(); })
event.stop_propagation(); }
event.prevent_default();
},
)?;
fn install_mouseup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
// Use `document` here to notice if the user releases a drag outside of the canvas. // Use `document` here to notice if the user releases a drag outside of the canvas.
// See https://github.com/emilk/egui/issues/3157 // See https://github.com/emilk/egui/issues/3157
runner_ref.add_event_listener(target, "mouseup", |event: web_sys::MouseEvent, runner| {
let modifiers = modifiers_from_mouse_event(&event);
runner.input.raw.modifiers = modifiers;
if let Some(button) = button_from_mouse_event(&event) {
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
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
// event callback, which is why we run the app logic here and now:
runner.logic();
runner
.text_agent
.set_focus(runner.mutable_text_under_cursor);
// Make sure we paint the output of the above logic call asap:
runner.needs_repaint.repaint_asap();
}
event.stop_propagation();
event.prevent_default();
})
}
fn install_mouseleave(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
runner_ref.add_event_listener( runner_ref.add_event_listener(
&document, target,
"mouseup",
|event: web_sys::MouseEvent, runner| {
let modifiers = modifiers_from_mouse_event(&event);
runner.input.raw.modifiers = modifiers;
if let Some(button) = button_from_mouse_event(&event) {
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
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
// event callback, which is why we run the app logic here and now:
runner.logic();
runner
.text_agent
.set_focus(runner.mutable_text_under_cursor);
// Make sure we paint the output of the above logic call asap:
runner.needs_repaint.repaint_asap();
}
event.stop_propagation();
event.prevent_default();
},
)?;
runner_ref.add_event_listener(
&canvas,
"mouseleave", "mouseleave",
|event: web_sys::MouseEvent, runner| { |event: web_sys::MouseEvent, runner| {
runner.input.raw.events.push(egui::Event::PointerGone); runner.input.raw.events.push(egui::Event::PointerGone);
@ -396,10 +404,12 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
}, },
)?; )
}
fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
runner_ref.add_event_listener( runner_ref.add_event_listener(
&canvas, target,
"touchstart", "touchstart",
|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;
@ -424,65 +434,61 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
}, },
)?; )
}
// Use `document` here to notice if the user drag outside of the canvas. fn install_touchmove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
runner_ref.add_event_listener(target, "touchmove", |event: web_sys::TouchEvent, runner| {
let mut latest_touch_pos_id = runner.input.latest_touch_pos_id;
let pos = pos_from_touch_event(
runner.canvas(),
&event,
&mut latest_touch_pos_id,
runner.egui_ctx(),
);
runner.input.latest_touch_pos_id = latest_touch_pos_id;
runner.input.latest_touch_pos = Some(pos);
runner.input.raw.events.push(egui::Event::PointerMoved(pos));
push_touches(runner, egui::TouchPhase::Move, &event);
runner.needs_repaint.repaint_asap();
event.stop_propagation();
event.prevent_default();
})
}
fn install_touchend(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
// Use `document` here to notice if the user releases a drag outside of the canvas.
// See https://github.com/emilk/egui/issues/3157 // See https://github.com/emilk/egui/issues/3157
runner_ref.add_event_listener( runner_ref.add_event_listener(target, "touchend", |event: web_sys::TouchEvent, runner| {
&document, if let Some(pos) = runner.input.latest_touch_pos {
"touchmove", let modifiers = runner.input.raw.modifiers;
|event: web_sys::TouchEvent, runner| { // First release mouse to click:
let mut latest_touch_pos_id = runner.input.latest_touch_pos_id; runner.input.raw.events.push(egui::Event::PointerButton {
let pos = pos_from_touch_event( pos,
runner.canvas(), button: egui::PointerButton::Primary,
&event, pressed: false,
&mut latest_touch_pos_id, modifiers,
runner.egui_ctx(), });
); // Then remove hover effect:
runner.input.latest_touch_pos_id = latest_touch_pos_id; runner.input.raw.events.push(egui::Event::PointerGone);
runner.input.latest_touch_pos = Some(pos);
runner.input.raw.events.push(egui::Event::PointerMoved(pos)); push_touches(runner, egui::TouchPhase::End, &event);
runner
.text_agent
.set_focus(runner.mutable_text_under_cursor);
push_touches(runner, egui::TouchPhase::Move, &event);
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
}, }
)?; })
}
// Use `document` here to notice if the user releases a drag outside of the canvas. fn install_touchcancel(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
// See https://github.com/emilk/egui/issues/3157
runner_ref.add_event_listener( runner_ref.add_event_listener(
&document, target,
"touchend",
|event: web_sys::TouchEvent, runner| {
if let Some(pos) = runner.input.latest_touch_pos {
let modifiers = runner.input.raw.modifiers;
// First release mouse to click:
runner.input.raw.events.push(egui::Event::PointerButton {
pos,
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);
runner
.text_agent
.set_focus(runner.mutable_text_under_cursor);
runner.needs_repaint.repaint_asap();
event.stop_propagation();
event.prevent_default();
}
},
)?;
runner_ref.add_event_listener(
&canvas,
"touchcancel", "touchcancel",
|event: web_sys::TouchEvent, runner| { |event: web_sys::TouchEvent, runner| {
push_touches(runner, egui::TouchPhase::Cancel, &event); push_touches(runner, egui::TouchPhase::Cancel, &event);
@ -491,7 +497,11 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
}, },
)?; )?;
runner_ref.add_event_listener(&canvas, "wheel", |event: web_sys::WheelEvent, runner| { Ok(())
}
fn install_wheel(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
runner_ref.add_event_listener(target, "wheel", |event: web_sys::WheelEvent, runner| {
let unit = match event.delta_mode() { let unit = match event.delta_mode() {
web_sys::WheelEvent::DOM_DELTA_PIXEL => egui::MouseWheelUnit::Point, web_sys::WheelEvent::DOM_DELTA_PIXEL => egui::MouseWheelUnit::Point,
web_sys::WheelEvent::DOM_DELTA_LINE => egui::MouseWheelUnit::Line, web_sys::WheelEvent::DOM_DELTA_LINE => egui::MouseWheelUnit::Line,
@ -523,9 +533,11 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
})?; })
}
runner_ref.add_event_listener(&canvas, "dragover", |event: web_sys::DragEvent, runner| { fn install_drag_and_drop(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
runner_ref.add_event_listener(target, "dragover", |event: web_sys::DragEvent, runner| {
if let Some(data_transfer) = event.data_transfer() { if let Some(data_transfer) = event.data_transfer() {
runner.input.raw.hovered_files.clear(); runner.input.raw.hovered_files.clear();
for i in 0..data_transfer.items().length() { for i in 0..data_transfer.items().length() {
@ -542,14 +554,14 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
} }
})?; })?;
runner_ref.add_event_listener(&canvas, "dragleave", |event: web_sys::DragEvent, runner| { runner_ref.add_event_listener(target, "dragleave", |event: web_sys::DragEvent, runner| {
runner.input.raw.hovered_files.clear(); runner.input.raw.hovered_files.clear();
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
})?; })?;
runner_ref.add_event_listener(&canvas, "drop", { runner_ref.add_event_listener(target, "drop", {
let runner_ref = runner_ref.clone(); let runner_ref = runner_ref.clone();
move |event: web_sys::DragEvent, runner| { move |event: web_sys::DragEvent, runner| {

View File

@ -71,9 +71,7 @@ impl WebRunner {
self.runner.replace(Some(runner)); self.runner.replace(Some(runner));
{ {
events::install_canvas_events(self)?; events::install_event_handlers(self)?;
events::install_document_events(self)?;
events::install_window_events(self)?;
if follow_system_theme { if follow_system_theme {
events::install_color_scheme_change_event(self)?; events::install_color_scheme_change_event(self)?;