diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index d9bbe6b9715..22907c0979a 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -248,6 +248,7 @@ web-sys = { workspace = true, features = [ "Storage", "Touch", "TouchEvent", + "PointerEvent", "TouchList", "WebGl2RenderingContext", "WebglDebugRendererInfo", diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 1b30a74ef15..e3696057d04 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -77,11 +77,11 @@ pub(crate) fn install_event_handlers(runner_ref: &WebRunner) -> Result<(), JsVal // so we check if we have focus inside of the handler. install_copy_cut_paste(runner_ref, &document)?; - install_mousedown(runner_ref, &canvas)?; // Use `document` here to notice if the user releases a drag outside of the canvas: // See https://github.com/emilk/egui/issues/3157 install_mousemove(runner_ref, &document)?; - install_mouseup(runner_ref, &document)?; + install_pointerup(runner_ref, &document)?; + install_pointerdown(runner_ref, &canvas)?; install_mouseleave(runner_ref, &canvas)?; install_touchstart(runner_ref, &canvas)?; @@ -390,11 +390,11 @@ fn prevent_default_and_stop_propagation( Ok(()) } -fn install_mousedown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> { +fn install_pointerdown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> { runner_ref.add_event_listener( target, - "mousedown", - |event: web_sys::MouseEvent, runner: &mut AppRunner| { + "pointerdown", + |event: web_sys::PointerEvent, runner: &mut AppRunner| { let modifiers = modifiers_from_mouse_event(&event); runner.input.raw.modifiers = modifiers; if let Some(button) = button_from_mouse_event(&event) { @@ -420,6 +420,53 @@ fn install_mousedown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), ) } +fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> { + runner_ref.add_event_listener( + target, + "pointerup", + |event: web_sys::PointerEvent, runner| { + let modifiers = modifiers_from_mouse_event(&event); + runner.input.raw.modifiers = modifiers; + + let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); + + if is_interested_in_pointer_event( + runner, + egui::pos2(event.client_x() as f32, event.client_y() as f32), + ) { + if let Some(button) = button_from_mouse_event(&event) { + let modifiers = runner.input.raw.modifiers; + runner.input.raw.events.push(egui::Event::PointerButton { + pos, + button, + pressed: false, + modifiers, + }); + + // Previously on iOS, the canvas would not receive focus on + // any touch event, which resulted in the on-screen keyboard + // not working when focusing on a text field in an egui app. + // This attempts to fix that by forcing the focus on any + // click on the canvas. + runner.canvas().focus().ok(); + + // In Safari we are only allowed to do certain things + // (like playing audio, start a download, etc) + // on user action, such as a click. + // So we need to run the app logic here and now: + runner.logic(); + + // Make sure we paint the output of the above logic call asap: + runner.needs_repaint.repaint_asap(); + + event.prevent_default(); + event.stop_propagation(); + } + } + }, + ) +} + /// Returns true if the cursor is above the canvas, or if we're dragging something. /// Pass in the position in browser viewport coordinates (usually event.clientX/Y). fn is_interested_in_pointer_event(runner: &AppRunner, pos: egui::Pos2) -> bool { @@ -453,42 +500,6 @@ fn install_mousemove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), }) } -fn install_mouseup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> { - runner_ref.add_event_listener(target, "mouseup", |event: web_sys::MouseEvent, runner| { - let modifiers = modifiers_from_mouse_event(&event); - runner.input.raw.modifiers = modifiers; - - let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); - - if is_interested_in_pointer_event( - runner, - egui::pos2(event.client_x() as f32, event.client_y() as f32), - ) { - if let Some(button) = button_from_mouse_event(&event) { - let modifiers = runner.input.raw.modifiers; - runner.input.raw.events.push(egui::Event::PointerButton { - pos, - button, - pressed: false, - modifiers, - }); - - // In Safari we are only allowed to do certain things - // (like playing audio, start a download, etc) - // on user action, such as a click. - // So we need to run the app logic here and now: - runner.logic(); - - // Make sure we paint the output of the above logic call asap: - runner.needs_repaint.repaint_asap(); - - event.prevent_default(); - event.stop_propagation(); - } - } - }) -} - fn install_mouseleave(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> { runner_ref.add_event_listener( target, diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index 71aeb7915ab..519c4052876 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -264,3 +264,16 @@ pub fn percent_decode(s: &str) -> String { .decode_utf8_lossy() .to_string() } + +/// Returns `true` if the app is likely running on a mobile device. +pub(crate) fn is_mobile() -> bool { + fn try_is_mobile() -> Option { + const MOBILE_DEVICE: [&str; 6] = + ["Android", "iPhone", "iPad", "iPod", "webOS", "BlackBerry"]; + + let user_agent = web_sys::window()?.navigator().user_agent().ok()?; + let is_mobile = MOBILE_DEVICE.iter().any(|&name| user_agent.contains(name)); + Some(is_mobile) + } + try_is_mobile().unwrap_or(false) +} diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index 234dbb8ff3f..bc59d90563d 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -5,7 +5,7 @@ use std::cell::Cell; use wasm_bindgen::prelude::*; -use super::{AppRunner, WebRunner}; +use super::{is_mobile, AppRunner, WebRunner}; pub struct TextAgent { input: web_sys::HtmlInputElement, @@ -173,16 +173,3 @@ impl Drop for TextAgent { self.input.remove(); } } - -/// Returns `true` if the app is likely running on a mobile device. -fn is_mobile() -> bool { - fn try_is_mobile() -> Option { - const MOBILE_DEVICE: [&str; 6] = - ["Android", "iPhone", "iPad", "iPod", "webOS", "BlackBerry"]; - - let user_agent = web_sys::window()?.navigator().user_agent().ok()?; - let is_mobile = MOBILE_DEVICE.iter().any(|&name| user_agent.contains(name)); - Some(is_mobile) - } - try_is_mobile().unwrap_or(false) -}