From ab0f0b7b64fa85e8186d6596df6df3253a210b46 Mon Sep 17 00:00:00 2001 From: Timo von Hartz Date: Sun, 30 Mar 2025 14:00:46 +0200 Subject: [PATCH] Rename `should_propagate_event` & add `should_prevent_default` (#5779) * [x] I have followed the instructions in the PR template Currently eframe [calls `prevent_default()`](https://github.com/emilk/egui/blob/962c7c75166dff3369d20675bcfd527d3287149f/crates/eframe/src/web/events.rs#L307-L369) for all copy / paste events on the [*document*](https://github.com/emilk/egui/blob/962c7c75166dff3369d20675bcfd527d3287149f/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) --- crates/eframe/src/epi.rs | 15 +++- crates/eframe/src/web/events.rs | 141 ++++++++++++++++++++++---------- 2 files changed, 108 insertions(+), 48 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index fb589fe2..562311dc 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -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 bool>, + /// is called on every event, and the event is not propagated to the rest of the web page. + pub should_stop_propagation: Box 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 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), } } } diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 6a1b7b6d..32fba9ef 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -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(); + } }) }