Rename `should_propagate_event` & add `should_prevent_default` (#5779)

* [x] I have followed the instructions in the PR template

Currently eframe [calls
`prevent_default()`](962c7c7516/crates/eframe/src/web/events.rs (L307-L369))
for all copy / paste events on the
[*document*](962c7c7516/crates/eframe/src/web/events.rs (L88)),
making embedding an egui application in a page (e.g. an react
application) hard (as all copy & paste functionality for other elements
on the page is broken by this).

I'm not sure what the motivation for this is, if any.

This commit / PR adds a callback (`should_prevent_default`), similar to
`should_propgate_event`, that an egui application can use to overwrite
this behavior. It defaults to returning `true` for all events, to keep
the existing behavior.

I call `should_prevent_default` in every place that
`should_propagate_event` is called (which is not all places that
`prevent_default` is called!). I'm not sure for the motivation of not
calling `should_propagate_event` everywhere that `stop_propagation` is
called, but I kept that behavior for the `should_prevent_default`
callback too.

Please let me know if I'm missing some existing functionality that would
allow me to do this, or if there's a reason that we don't want
applications to be able to customize this (i.e. if there's a reason to
always `prevent_default` for all copy / paste events on the whole
document)
This commit is contained in:
Timo von Hartz 2025-03-30 14:00:46 +02:00 committed by GitHub
parent 83254718a3
commit ab0f0b7b64
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 108 additions and 48 deletions

View File

@ -499,10 +499,16 @@ pub struct WebOptions {
/// If the web event corresponding to an egui event should be propagated /// If the web event corresponding to an egui event should be propagated
/// to the rest of the web page. /// to the rest of the web page.
/// ///
/// The default is `false`, meaning /// The default is `true`, meaning
/// [`stopPropagation`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation) /// [`stopPropagation`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation)
/// is called on every event. /// is called on every event, and the event is not propagated to the rest of the web page.
pub should_propagate_event: Box<dyn Fn(&egui::Event) -> bool>, pub should_stop_propagation: Box<dyn Fn(&egui::Event) -> bool>,
/// Whether the web event corresponding to an egui event should have `prevent_default` called
/// on it or not.
///
/// Defaults to true.
pub should_prevent_default: Box<dyn Fn(&egui::Event) -> bool>,
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -519,7 +525,8 @@ impl Default for WebOptions {
dithering: true, dithering: true,
should_propagate_event: Box::new(|_| false), should_stop_propagation: Box::new(|_| true),
should_prevent_default: Box::new(|_| true),
} }
} }
} }

View File

@ -139,15 +139,20 @@ fn install_keydown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), J
{ {
if let Some(text) = text_from_keyboard_event(&event) { if let Some(text) = text_from_keyboard_event(&event) {
let egui_event = egui::Event::Text(text); let egui_event = egui::Event::Text(text);
let should_propagate = (runner.web_options.should_propagate_event)(&egui_event); let should_stop_propagation =
(runner.web_options.should_stop_propagation)(&egui_event);
let should_prevent_default =
(runner.web_options.should_prevent_default)(&egui_event);
runner.input.raw.events.push(egui_event); runner.input.raw.events.push(egui_event);
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
// If this is indeed text, then prevent any other action. // If this is indeed text, then prevent any other action.
event.prevent_default(); if should_prevent_default {
event.prevent_default();
}
// Use web options to tell if the event should be propagated to parent elements. // Use web options to tell if the event should be propagated to parent elements.
if !should_propagate { if should_stop_propagation {
event.stop_propagation(); event.stop_propagation();
} }
} }
@ -184,7 +189,7 @@ pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner)
repeat: false, // egui will fill this in for us! repeat: false, // egui will fill this in for us!
modifiers, modifiers,
}; };
let should_propagate = (runner.web_options.should_propagate_event)(&egui_event); let should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event);
runner.input.raw.events.push(egui_event); runner.input.raw.events.push(egui_event);
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
@ -201,7 +206,7 @@ pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner)
} }
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
if !should_propagate { if should_stop_propagation {
event.stop_propagation(); event.stop_propagation();
} }
} }
@ -261,7 +266,7 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) {
let modifiers = modifiers_from_kb_event(&event); let modifiers = modifiers_from_kb_event(&event);
runner.input.raw.modifiers = modifiers; runner.input.raw.modifiers = modifiers;
let mut propagate_event = false; let mut should_stop_propagation = true;
if let Some(key) = translate_key(&event.key()) { if let Some(key) = translate_key(&event.key()) {
let egui_event = egui::Event::Key { let egui_event = egui::Event::Key {
@ -271,7 +276,7 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) {
repeat: false, repeat: false,
modifiers, modifiers,
}; };
propagate_event |= (runner.web_options.should_propagate_event)(&egui_event); should_stop_propagation &= (runner.web_options.should_stop_propagation)(&egui_event);
runner.input.raw.events.push(egui_event); runner.input.raw.events.push(egui_event);
} }
@ -290,7 +295,7 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) {
repeat: false, repeat: false,
modifiers, modifiers,
}; };
propagate_event |= (runner.web_options.should_propagate_event)(&egui_event); should_stop_propagation &= (runner.web_options.should_stop_propagation)(&egui_event);
runner.input.raw.events.push(egui_event); runner.input.raw.events.push(egui_event);
} }
} }
@ -299,7 +304,7 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) {
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
let has_focus = runner.input.raw.focused; let has_focus = runner.input.raw.focused;
if has_focus && !propagate_event { if has_focus && should_stop_propagation {
event.stop_propagation(); event.stop_propagation();
} }
} }
@ -310,19 +315,26 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul
if let Ok(text) = data.get_data("text") { if let Ok(text) = data.get_data("text") {
let text = text.replace("\r\n", "\n"); let text = text.replace("\r\n", "\n");
let mut should_propagate = false; let mut should_stop_propagation = true;
let mut should_prevent_default = true;
if !text.is_empty() && runner.input.raw.focused { if !text.is_empty() && runner.input.raw.focused {
let egui_event = egui::Event::Paste(text); let egui_event = egui::Event::Paste(text);
should_propagate = (runner.web_options.should_propagate_event)(&egui_event); should_stop_propagation =
(runner.web_options.should_stop_propagation)(&egui_event);
should_prevent_default =
(runner.web_options.should_prevent_default)(&egui_event);
runner.input.raw.events.push(egui_event); runner.input.raw.events.push(egui_event);
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
} }
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
if !should_propagate { if should_stop_propagation {
event.stop_propagation(); event.stop_propagation();
} }
event.prevent_default();
if should_prevent_default {
event.prevent_default();
}
} }
} }
})?; })?;
@ -340,10 +352,13 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul
} }
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
if !(runner.web_options.should_propagate_event)(&egui::Event::Cut) { if (runner.web_options.should_stop_propagation)(&egui::Event::Cut) {
event.stop_propagation(); event.stop_propagation();
} }
event.prevent_default();
if (runner.web_options.should_prevent_default)(&egui::Event::Cut) {
event.prevent_default();
}
})?; })?;
runner_ref.add_event_listener(target, "copy", |event: web_sys::ClipboardEvent, runner| { runner_ref.add_event_listener(target, "copy", |event: web_sys::ClipboardEvent, runner| {
@ -359,10 +374,13 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul
} }
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
if !(runner.web_options.should_propagate_event)(&egui::Event::Copy) { if (runner.web_options.should_stop_propagation)(&egui::Event::Copy) {
event.stop_propagation(); event.stop_propagation();
} }
event.prevent_default();
if (runner.web_options.should_prevent_default)(&egui::Event::Copy) {
event.prevent_default();
}
})?; })?;
Ok(()) Ok(())
@ -484,7 +502,7 @@ fn install_pointerdown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(
|event: web_sys::PointerEvent, runner: &mut AppRunner| { |event: web_sys::PointerEvent, runner: &mut AppRunner| {
let modifiers = modifiers_from_mouse_event(&event); let modifiers = modifiers_from_mouse_event(&event);
runner.input.raw.modifiers = modifiers; runner.input.raw.modifiers = modifiers;
let mut should_propagate = false; let mut should_stop_propagation = true;
if let Some(button) = button_from_mouse_event(&event) { if let Some(button) = button_from_mouse_event(&event) {
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
let modifiers = runner.input.raw.modifiers; let modifiers = runner.input.raw.modifiers;
@ -494,7 +512,7 @@ fn install_pointerdown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(
pressed: true, pressed: true,
modifiers, modifiers,
}; };
should_propagate = (runner.web_options.should_propagate_event)(&egui_event); should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event);
runner.input.raw.events.push(egui_event); runner.input.raw.events.push(egui_event);
// 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
@ -506,7 +524,7 @@ fn install_pointerdown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(
} }
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
if !should_propagate { if should_stop_propagation {
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.
@ -536,7 +554,10 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
pressed: false, pressed: false,
modifiers, modifiers,
}; };
let should_propagate = (runner.web_options.should_propagate_event)(&egui_event); let should_stop_propagation =
(runner.web_options.should_stop_propagation)(&egui_event);
let should_prevent_default =
(runner.web_options.should_prevent_default)(&egui_event);
runner.input.raw.events.push(egui_event); runner.input.raw.events.push(egui_event);
// Previously on iOS, the canvas would not receive focus on // Previously on iOS, the canvas would not receive focus on
@ -555,10 +576,12 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
// 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.prevent_default(); if should_prevent_default {
event.prevent_default();
}
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
if !should_propagate { if should_stop_propagation {
event.stop_propagation(); event.stop_propagation();
} }
} }
@ -600,15 +623,19 @@ fn install_mousemove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
egui::pos2(event.client_x() as f32, event.client_y() as f32), egui::pos2(event.client_x() as f32, event.client_y() as f32),
) { ) {
let egui_event = egui::Event::PointerMoved(pos); let egui_event = egui::Event::PointerMoved(pos);
let should_propagate = (runner.web_options.should_propagate_event)(&egui_event); let should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event);
let should_prevent_default = (runner.web_options.should_prevent_default)(&egui_event);
runner.input.raw.events.push(egui_event); runner.input.raw.events.push(egui_event);
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
if !should_propagate { if should_stop_propagation {
event.stop_propagation(); event.stop_propagation();
} }
event.prevent_default();
if should_prevent_default {
event.prevent_default();
}
} }
}) })
} }
@ -622,10 +649,13 @@ fn install_mouseleave(runner_ref: &WebRunner, target: &EventTarget) -> Result<()
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
if !(runner.web_options.should_propagate_event)(&egui::Event::PointerGone) { if (runner.web_options.should_stop_propagation)(&egui::Event::PointerGone) {
event.stop_propagation(); event.stop_propagation();
} }
event.prevent_default();
if (runner.web_options.should_prevent_default)(&egui::Event::PointerGone) {
event.prevent_default();
}
}, },
) )
} }
@ -635,7 +665,8 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<()
target, target,
"touchstart", "touchstart",
|event: web_sys::TouchEvent, runner| { |event: web_sys::TouchEvent, runner| {
let mut should_propagate = false; let mut should_stop_propagation = true;
let mut should_prevent_default = true;
if let Some((pos, _)) = primary_touch_pos(runner, &event) { if let Some((pos, _)) = primary_touch_pos(runner, &event) {
let egui_event = egui::Event::PointerButton { let egui_event = egui::Event::PointerButton {
pos, pos,
@ -643,7 +674,8 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<()
pressed: true, pressed: true,
modifiers: runner.input.raw.modifiers, modifiers: runner.input.raw.modifiers,
}; };
should_propagate = (runner.web_options.should_propagate_event)(&egui_event); should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event);
should_prevent_default = (runner.web_options.should_prevent_default)(&egui_event);
runner.input.raw.events.push(egui_event); runner.input.raw.events.push(egui_event);
} }
@ -651,10 +683,13 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<()
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
if !should_propagate { if should_stop_propagation {
event.stop_propagation(); event.stop_propagation();
} }
event.prevent_default();
if should_prevent_default {
event.prevent_default();
}
}, },
) )
} }
@ -667,17 +702,23 @@ fn install_touchmove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
egui::pos2(touch.client_x() as f32, touch.client_y() as f32), egui::pos2(touch.client_x() as f32, touch.client_y() as f32),
) { ) {
let egui_event = egui::Event::PointerMoved(pos); let egui_event = egui::Event::PointerMoved(pos);
let should_propagate = (runner.web_options.should_propagate_event)(&egui_event); let should_stop_propagation =
(runner.web_options.should_stop_propagation)(&egui_event);
let should_prevent_default =
(runner.web_options.should_prevent_default)(&egui_event);
runner.input.raw.events.push(egui_event); runner.input.raw.events.push(egui_event);
push_touches(runner, egui::TouchPhase::Move, &event); push_touches(runner, egui::TouchPhase::Move, &event);
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
if !should_propagate { if should_stop_propagation {
event.stop_propagation(); event.stop_propagation();
} }
event.prevent_default();
if should_prevent_default {
event.prevent_default();
}
} }
} }
}) })
@ -691,18 +732,23 @@ fn install_touchend(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
egui::pos2(touch.client_x() as f32, touch.client_y() as f32), egui::pos2(touch.client_x() as f32, touch.client_y() as f32),
) { ) {
// First release mouse to click: // First release mouse to click:
let mut should_propagate = false; let mut should_stop_propagation = true;
let mut should_prevent_default = true;
let egui_event = egui::Event::PointerButton { let egui_event = egui::Event::PointerButton {
pos, pos,
button: egui::PointerButton::Primary, button: egui::PointerButton::Primary,
pressed: false, pressed: false,
modifiers: runner.input.raw.modifiers, modifiers: runner.input.raw.modifiers,
}; };
should_propagate |= (runner.web_options.should_propagate_event)(&egui_event); should_stop_propagation &=
(runner.web_options.should_stop_propagation)(&egui_event);
should_prevent_default &= (runner.web_options.should_prevent_default)(&egui_event);
runner.input.raw.events.push(egui_event); runner.input.raw.events.push(egui_event);
// Then remove hover effect: // Then remove hover effect:
should_propagate |= should_stop_propagation &=
(runner.web_options.should_propagate_event)(&egui::Event::PointerGone); (runner.web_options.should_stop_propagation)(&egui::Event::PointerGone);
should_prevent_default &=
(runner.web_options.should_prevent_default)(&egui::Event::PointerGone);
runner.input.raw.events.push(egui::Event::PointerGone); runner.input.raw.events.push(egui::Event::PointerGone);
push_touches(runner, egui::TouchPhase::End, &event); push_touches(runner, egui::TouchPhase::End, &event);
@ -710,10 +756,13 @@ fn install_touchend(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
if !should_propagate { if should_stop_propagation {
event.stop_propagation(); event.stop_propagation();
} }
event.prevent_default();
if should_prevent_default {
event.prevent_default();
}
// Fix virtual keyboard IOS // Fix virtual keyboard IOS
// Need call focus at the same time of event // Need call focus at the same time of event
@ -769,16 +818,20 @@ fn install_wheel(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsV
modifiers, modifiers,
} }
}; };
let should_propagate = (runner.web_options.should_propagate_event)(&egui_event); let should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event);
let should_prevent_default = (runner.web_options.should_prevent_default)(&egui_event);
runner.input.raw.events.push(egui_event); runner.input.raw.events.push(egui_event);
runner.needs_repaint.repaint_asap(); runner.needs_repaint.repaint_asap();
// Use web options to tell if the web event should be propagated to parent elements based on the egui event. // Use web options to tell if the web event should be propagated to parent elements based on the egui event.
if !should_propagate { if should_stop_propagation {
event.stop_propagation(); event.stop_propagation();
} }
event.prevent_default();
if should_prevent_default {
event.prevent_default();
}
}) })
} }