Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditionally propagate web events using a filter in WebOptions #5056

Merged
merged 2 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@ 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.
liamrosenfeld marked this conversation as resolved.
Show resolved Hide resolved
pub should_propagate_event: Box<dyn Fn(&egui::Event) -> bool>,
}

#[cfg(target_arch = "wasm32")]
Expand All @@ -471,6 +475,8 @@ impl Default for WebOptions {
wgpu_options: egui_wgpu::WgpuConfiguration::default(),

dithering: true,

should_propagate_event: Box::new(|_| false),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/eframe/src/web/app_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
133 changes: 96 additions & 37 deletions crates/eframe/src/web/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
liamrosenfeld marked this conversation as resolved.
Show resolved Hide resolved
event.stop_propagation();
if !should_propagate {
event.stop_propagation();
}
}
}

Expand Down Expand Up @@ -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);
Expand All @@ -195,7 +201,9 @@ pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner)
}

// Assume egui uses all key events, and don't let them propagate to parent elements.
event.stop_propagation();
if !should_propagate {
event.stop_propagation();
}
}
}

Expand Down Expand Up @@ -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" {
Expand All @@ -264,20 +276,22 @@ 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();

let has_focus = runner.input.raw.focused;
if has_focus {
if has_focus && !propagate_event {
// Assume egui uses all key events, and don't let them propagate to parent elements.
event.stop_propagation();
}
Expand All @@ -288,11 +302,17 @@ 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();
if !should_propagate {
event.stop_propagation();
}
event.prevent_default();
}
}
Expand All @@ -310,7 +330,9 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul
runner.needs_repaint.repaint_asap();
}

event.stop_propagation();
if !(runner.web_options.should_propagate_event)(&egui::Event::Cut) {
event.stop_propagation();
}
event.prevent_default();
})?;

Expand All @@ -326,7 +348,9 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul
runner.needs_repaint.repaint_asap();
}

event.stop_propagation();
if !(runner.web_options.should_propagate_event)(&egui::Event::Copy) {
event.stop_propagation();
}
event.prevent_default();
})?;

Expand Down Expand Up @@ -400,15 +424,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:
Expand All @@ -417,7 +444,9 @@ 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();
if !should_propagate {
event.stop_propagation();
}
// Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here.
},
)
Expand All @@ -439,12 +468,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
Expand All @@ -463,7 +494,9 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
runner.needs_repaint.repaint_asap();

event.prevent_default();
event.stop_propagation();
if !should_propagate {
event.stop_propagation();
}
}
}
},
Expand Down Expand Up @@ -495,9 +528,13 @@ 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();
if !should_propagate {
event.stop_propagation();
}
event.prevent_default();
}
})
Expand All @@ -510,7 +547,9 @@ 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();
if !(runner.web_options.should_propagate_event)(&egui::Event::PointerGone) {
event.stop_propagation();
}
event.prevent_default();
},
)
Expand All @@ -521,18 +560,23 @@ 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();
if !should_propagate {
event.stop_propagation();
}
event.prevent_default();
},
)
Expand All @@ -545,11 +589,15 @@ 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();
if !should_propagate {
event.stop_propagation();
}
event.prevent_default();
}
}
Expand All @@ -564,19 +612,26 @@ 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();
if !should_propagate {
event.stop_propagation();
}
event.prevent_default();

// Fix virtual keyboard IOS
Expand Down Expand Up @@ -617,25 +672,29 @@ 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.
// However, we explicitly handle it here in order to better match the pinch-to-zoom
// 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();
if !should_propagate {
event.stop_propagation();
}
event.prevent_default();
})
}
Expand Down
Loading