Conditionally propagate web events using a filter in WebOptions (#5056)
Currently egui will prevent all web events from propagating. This causes issues in contexts where you are using egui in a larger web context that wants to receive events that egui does not directly respond to. For example, currently using egui in a VSCode extension will block all app hotkeys, such as saving and opening the panel. This adds a closure to `WebOptions` that takes in a reference to the egui event that is generated from a web event and returns if the corresponding web event should be propagated or not. The default for it is to always return false. Alternatives I considered were: 1. Having the propagation filter be a property of the focus in memory. That way it could be configured by the view currently selected. I opted away from that because I wanted to avoid lowering eframe implementation specific stuff into egui. 2. Having events contain a `web_propagate` flag that could be set when handling them. However, that would not be compatible with the current system of egui events being handled outside of the web event handler. I just recently started using egui so I am not sure how idiomatic my approach here is. I would be happy to switch this over to a different architecture if there are suggestions. <!-- Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md) before opening a Pull Request! * Keep your PR:s small and focused. * The PR title is what ends up in the changelog, so make it descriptive! * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to test and add commits to your PR. * Remember to run `cargo fmt` and `cargo clippy`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. Please be patient! I will review your PR, but my time is limited! --> * [x] I have followed the instructions in the PR template
This commit is contained in:
parent
2be93aca5d
commit
df9cd21248
|
|
@ -457,6 +457,14 @@ pub struct WebOptions {
|
|||
///
|
||||
/// Defaults to true.
|
||||
pub dithering: bool,
|
||||
|
||||
/// If the web event corresponding to an egui event should be propagated
|
||||
/// to the rest of the web page.
|
||||
///
|
||||
/// The default is `false`, 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>,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
|
@ -472,6 +480,8 @@ impl Default for WebOptions {
|
|||
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
|
||||
|
||||
dithering: true,
|
||||
|
||||
should_propagate_event: Box::new(|_| false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use super::{now_sec, text_agent::TextAgent, web_painter::WebPainter, NeedRepaint
|
|||
|
||||
pub struct AppRunner {
|
||||
#[allow(dead_code)]
|
||||
web_options: crate::WebOptions,
|
||||
pub(crate) web_options: crate::WebOptions,
|
||||
pub(crate) frame: epi::Frame,
|
||||
egui_ctx: egui::Context,
|
||||
painter: super::ActiveWebPainter,
|
||||
|
|
|
|||
|
|
@ -138,14 +138,18 @@ fn install_keydown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), J
|
|||
&& !runner.text_agent.has_focus()
|
||||
{
|
||||
if let Some(text) = text_from_keyboard_event(&event) {
|
||||
runner.input.raw.events.push(egui::Event::Text(text));
|
||||
let egui_event = egui::Event::Text(text);
|
||||
let should_propagate = (runner.web_options.should_propagate_event)(&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();
|
||||
|
||||
// Assume egui uses all key events, and don't let them propagate to parent elements.
|
||||
event.stop_propagation();
|
||||
// Use web options to tell if the event should be propagated to parent elements.
|
||||
if !should_propagate {
|
||||
event.stop_propagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -173,13 +177,15 @@ pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner)
|
|||
let egui_key = translate_key(&key);
|
||||
|
||||
if let Some(egui_key) = egui_key {
|
||||
runner.input.raw.events.push(egui::Event::Key {
|
||||
let egui_event = egui::Event::Key {
|
||||
key: egui_key,
|
||||
physical_key: None, // TODO(fornwall)
|
||||
pressed: true,
|
||||
repeat: false, // egui will fill this in for us!
|
||||
modifiers,
|
||||
});
|
||||
};
|
||||
let should_propagate = (runner.web_options.should_propagate_event)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
let prevent_default = should_prevent_default_for_key(runner, &modifiers, egui_key);
|
||||
|
|
@ -194,8 +200,10 @@ pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner)
|
|||
event.prevent_default();
|
||||
}
|
||||
|
||||
// Assume egui uses all key events, and don't let them propagate to parent elements.
|
||||
event.stop_propagation();
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if !should_propagate {
|
||||
event.stop_propagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -246,14 +254,18 @@ 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;
|
||||
|
||||
if let Some(key) = translate_key(&event.key()) {
|
||||
runner.input.raw.events.push(egui::Event::Key {
|
||||
let egui_event = egui::Event::Key {
|
||||
key,
|
||||
physical_key: None, // TODO(fornwall)
|
||||
pressed: false,
|
||||
repeat: false,
|
||||
modifiers,
|
||||
});
|
||||
};
|
||||
propagate_event |= (runner.web_options.should_propagate_event)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
}
|
||||
|
||||
if event.key() == "Meta" || event.key() == "Control" {
|
||||
|
|
@ -264,21 +276,23 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) {
|
|||
|
||||
let keys_down = runner.egui_ctx().input(|i| i.keys_down.clone());
|
||||
for key in keys_down {
|
||||
runner.input.raw.events.push(egui::Event::Key {
|
||||
let egui_event = egui::Event::Key {
|
||||
key,
|
||||
physical_key: None,
|
||||
pressed: false,
|
||||
repeat: false,
|
||||
modifiers,
|
||||
});
|
||||
};
|
||||
propagate_event |= (runner.web_options.should_propagate_event)(&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.
|
||||
let has_focus = runner.input.raw.focused;
|
||||
if has_focus {
|
||||
// Assume egui uses all key events, and don't let them propagate to parent elements.
|
||||
if has_focus && !propagate_event {
|
||||
event.stop_propagation();
|
||||
}
|
||||
}
|
||||
|
|
@ -288,11 +302,19 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul
|
|||
if let Some(data) = event.clipboard_data() {
|
||||
if let Ok(text) = data.get_data("text") {
|
||||
let text = text.replace("\r\n", "\n");
|
||||
|
||||
let mut should_propagate = false;
|
||||
if !text.is_empty() && runner.input.raw.focused {
|
||||
runner.input.raw.events.push(egui::Event::Paste(text));
|
||||
let egui_event = egui::Event::Paste(text);
|
||||
should_propagate = (runner.web_options.should_propagate_event)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
}
|
||||
event.stop_propagation();
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if !should_propagate {
|
||||
event.stop_propagation();
|
||||
}
|
||||
event.prevent_default();
|
||||
}
|
||||
}
|
||||
|
|
@ -310,7 +332,10 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul
|
|||
runner.needs_repaint.repaint_asap();
|
||||
}
|
||||
|
||||
event.stop_propagation();
|
||||
// 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) {
|
||||
event.stop_propagation();
|
||||
}
|
||||
event.prevent_default();
|
||||
})?;
|
||||
|
||||
|
|
@ -326,7 +351,10 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul
|
|||
runner.needs_repaint.repaint_asap();
|
||||
}
|
||||
|
||||
event.stop_propagation();
|
||||
// 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) {
|
||||
event.stop_propagation();
|
||||
}
|
||||
event.prevent_default();
|
||||
})?;
|
||||
|
||||
|
|
@ -400,15 +428,18 @@ 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;
|
||||
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 {
|
||||
let egui_event = egui::Event::PointerButton {
|
||||
pos,
|
||||
button,
|
||||
pressed: true,
|
||||
modifiers,
|
||||
});
|
||||
};
|
||||
should_propagate = (runner.web_options.should_propagate_event)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
|
||||
// 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:
|
||||
|
|
@ -417,7 +448,11 @@ fn install_pointerdown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(
|
|||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
}
|
||||
event.stop_propagation();
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if !should_propagate {
|
||||
event.stop_propagation();
|
||||
}
|
||||
// Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here.
|
||||
},
|
||||
)
|
||||
|
|
@ -439,12 +474,14 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
|
|||
) {
|
||||
if let Some(button) = button_from_mouse_event(&event) {
|
||||
let modifiers = runner.input.raw.modifiers;
|
||||
runner.input.raw.events.push(egui::Event::PointerButton {
|
||||
let egui_event = egui::Event::PointerButton {
|
||||
pos,
|
||||
button,
|
||||
pressed: false,
|
||||
modifiers,
|
||||
});
|
||||
};
|
||||
let should_propagate = (runner.web_options.should_propagate_event)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
|
||||
// Previously on iOS, the canvas would not receive focus on
|
||||
// any touch event, which resulted in the on-screen keyboard
|
||||
|
|
@ -463,7 +500,11 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
|
|||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
event.prevent_default();
|
||||
event.stop_propagation();
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if !should_propagate {
|
||||
event.stop_propagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -495,9 +536,15 @@ fn install_mousemove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
|
|||
runner,
|
||||
egui::pos2(event.client_x() as f32, event.client_y() as f32),
|
||||
) {
|
||||
runner.input.raw.events.push(egui::Event::PointerMoved(pos));
|
||||
let egui_event = egui::Event::PointerMoved(pos);
|
||||
let should_propagate = (runner.web_options.should_propagate_event)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if !should_propagate {
|
||||
event.stop_propagation();
|
||||
}
|
||||
event.prevent_default();
|
||||
}
|
||||
})
|
||||
|
|
@ -510,7 +557,11 @@ fn install_mouseleave(runner_ref: &WebRunner, target: &EventTarget) -> Result<()
|
|||
|event: web_sys::MouseEvent, runner| {
|
||||
runner.input.raw.events.push(egui::Event::PointerGone);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
|
||||
// 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) {
|
||||
event.stop_propagation();
|
||||
}
|
||||
event.prevent_default();
|
||||
},
|
||||
)
|
||||
|
|
@ -521,18 +572,25 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<()
|
|||
target,
|
||||
"touchstart",
|
||||
|event: web_sys::TouchEvent, runner| {
|
||||
let mut should_propagate = false;
|
||||
if let Some((pos, _)) = primary_touch_pos(runner, &event) {
|
||||
runner.input.raw.events.push(egui::Event::PointerButton {
|
||||
let egui_event = egui::Event::PointerButton {
|
||||
pos,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: true,
|
||||
modifiers: runner.input.raw.modifiers,
|
||||
});
|
||||
};
|
||||
should_propagate = (runner.web_options.should_propagate_event)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
}
|
||||
|
||||
push_touches(runner, egui::TouchPhase::Start, &event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if !should_propagate {
|
||||
event.stop_propagation();
|
||||
}
|
||||
event.prevent_default();
|
||||
},
|
||||
)
|
||||
|
|
@ -545,11 +603,17 @@ fn install_touchmove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
|
|||
runner,
|
||||
egui::pos2(touch.client_x() as f32, touch.client_y() as f32),
|
||||
) {
|
||||
runner.input.raw.events.push(egui::Event::PointerMoved(pos));
|
||||
let egui_event = egui::Event::PointerMoved(pos);
|
||||
let should_propagate = (runner.web_options.should_propagate_event)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
|
||||
push_touches(runner, egui::TouchPhase::Move, &event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if !should_propagate {
|
||||
event.stop_propagation();
|
||||
}
|
||||
event.prevent_default();
|
||||
}
|
||||
}
|
||||
|
|
@ -564,19 +628,28 @@ 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:
|
||||
runner.input.raw.events.push(egui::Event::PointerButton {
|
||||
let mut should_propagate = false;
|
||||
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);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
// Then remove hover effect:
|
||||
should_propagate |=
|
||||
(runner.web_options.should_propagate_event)(&egui::Event::PointerGone);
|
||||
runner.input.raw.events.push(egui::Event::PointerGone);
|
||||
|
||||
push_touches(runner, egui::TouchPhase::End, &event);
|
||||
|
||||
runner.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if !should_propagate {
|
||||
event.stop_propagation();
|
||||
}
|
||||
event.prevent_default();
|
||||
|
||||
// Fix virtual keyboard IOS
|
||||
|
|
@ -617,7 +690,7 @@ fn install_wheel(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsV
|
|||
|
||||
let modifiers = modifiers_from_wheel_event(&event);
|
||||
|
||||
if modifiers.ctrl && !runner.input.raw.modifiers.ctrl {
|
||||
let egui_event = if modifiers.ctrl && !runner.input.raw.modifiers.ctrl {
|
||||
// The browser is saying the ctrl key is down, but it isn't _really_.
|
||||
// This happens on pinch-to-zoom on a Mac trackpad.
|
||||
// egui will treat ctrl+scroll as zoom, so it all works.
|
||||
|
|
@ -625,17 +698,23 @@ fn install_wheel(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsV
|
|||
// speed of a native app, without being sensitive to egui's `scroll_zoom_speed` setting.
|
||||
let pinch_to_zoom_sensitivity = 0.01; // Feels good on a Mac trackpad in 2024
|
||||
let zoom_factor = (pinch_to_zoom_sensitivity * delta.y).exp();
|
||||
runner.input.raw.events.push(egui::Event::Zoom(zoom_factor));
|
||||
egui::Event::Zoom(zoom_factor)
|
||||
} else {
|
||||
runner.input.raw.events.push(egui::Event::MouseWheel {
|
||||
egui::Event::MouseWheel {
|
||||
unit,
|
||||
delta,
|
||||
modifiers,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
let should_propagate = (runner.web_options.should_propagate_event)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
|
||||
runner.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if !should_propagate {
|
||||
event.stop_propagation();
|
||||
}
|
||||
event.prevent_default();
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue