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
/// 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)
/// is called on every event.
pub should_propagate_event: Box<dyn Fn(&egui::Event) -> bool>,
/// is called on every event, and the event is not propagated to the rest of the web page.
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")]
@ -519,7 +525,8 @@ impl Default for WebOptions {
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) {
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.needs_repaint.repaint_asap();
// 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.
if !should_propagate {
if should_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!
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.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.
if !should_propagate {
if should_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);
runner.input.raw.modifiers = modifiers;
let mut propagate_event = false;
let mut should_stop_propagation = true;
if let Some(key) = translate_key(&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,
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);
}
@ -290,7 +295,7 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) {
repeat: false,
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);
}
}
@ -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.
let has_focus = runner.input.raw.focused;
if has_focus && !propagate_event {
if has_focus && should_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") {
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 {
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.needs_repaint.repaint_asap();
}
// 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.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.
if !(runner.web_options.should_propagate_event)(&egui::Event::Cut) {
if (runner.web_options.should_stop_propagation)(&egui::Event::Cut) {
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| {
@ -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.
if !(runner.web_options.should_propagate_event)(&egui::Event::Copy) {
if (runner.web_options.should_stop_propagation)(&egui::Event::Copy) {
event.stop_propagation();
}
event.prevent_default();
if (runner.web_options.should_prevent_default)(&egui::Event::Copy) {
event.prevent_default();
}
})?;
Ok(())
@ -484,7 +502,7 @@ fn install_pointerdown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(
|event: web_sys::PointerEvent, runner: &mut AppRunner| {
let modifiers = modifiers_from_mouse_event(&event);
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) {
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
let modifiers = runner.input.raw.modifiers;
@ -494,7 +512,7 @@ fn install_pointerdown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(
pressed: true,
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);
// 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.
if !should_propagate {
if should_stop_propagation {
event.stop_propagation();
}
// 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,
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);
// 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:
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.
if !should_propagate {
if should_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),
) {
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.needs_repaint.repaint_asap();
// 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.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();
// 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.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,
"touchstart",
|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) {
let egui_event = egui::Event::PointerButton {
pos,
@ -643,7 +674,8 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<()
pressed: true,
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);
}
@ -651,10 +683,13 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<()
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.
if !should_propagate {
if should_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),
) {
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);
push_touches(runner, egui::TouchPhase::Move, &event);
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.
if !should_propagate {
if should_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),
) {
// 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 {
pos,
button: egui::PointerButton::Primary,
pressed: false,
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);
// Then remove hover effect:
should_propagate |=
(runner.web_options.should_propagate_event)(&egui::Event::PointerGone);
should_stop_propagation &=
(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);
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();
// 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.prevent_default();
if should_prevent_default {
event.prevent_default();
}
// Fix virtual keyboard IOS
// Need call focus at the same time of event
@ -769,16 +818,20 @@ fn install_wheel(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsV
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.needs_repaint.repaint_asap();
// 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.prevent_default();
if should_prevent_default {
event.prevent_default();
}
})
}