Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c0bc79d

Browse files
committedJul 18, 2024·
Fix canvas/text input focus on iOS web browsers
1 parent b2d74ea commit c0bc79d

File tree

4 files changed

+99
-16
lines changed

4 files changed

+99
-16
lines changed
 

‎crates/eframe/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ web-sys = { workspace = true, features = [
248248
"Storage",
249249
"Touch",
250250
"TouchEvent",
251+
"PointerEvent",
251252
"TouchList",
252253
"WebGl2RenderingContext",
253254
"WebglDebugRendererInfo",

‎crates/eframe/src/web/events.rs

+84-2
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,16 @@ pub(crate) fn install_event_handlers(runner_ref: &WebRunner) -> Result<(), JsVal
7777
// so we check if we have focus inside of the handler.
7878
install_copy_cut_paste(runner_ref, &document)?;
7979

80-
install_mousedown(runner_ref, &canvas)?;
8180
// Use `document` here to notice if the user releases a drag outside of the canvas:
8281
// See https://github.com/emilk/egui/issues/3157
8382
install_mousemove(runner_ref, &document)?;
84-
install_mouseup(runner_ref, &document)?;
83+
if is_mobile() {
84+
install_pointerup(runner_ref, &canvas)?;
85+
install_pointerdown(runner_ref, &canvas)?;
86+
} else {
87+
install_mouseup(runner_ref, &document)?;
88+
install_mousedown(runner_ref, &canvas)?;
89+
}
8590
install_mouseleave(runner_ref, &canvas)?;
8691

8792
install_touchstart(runner_ref, &canvas)?;
@@ -420,6 +425,83 @@ fn install_mousedown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
420425
)
421426
}
422427

428+
fn install_pointerdown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
429+
runner_ref.add_event_listener(
430+
target,
431+
"pointerdown",
432+
|event: web_sys::PointerEvent, runner: &mut AppRunner| {
433+
let modifiers = modifiers_from_mouse_event(&event);
434+
runner.input.raw.modifiers = modifiers;
435+
if let Some(button) = button_from_mouse_event(&event) {
436+
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
437+
let modifiers = runner.input.raw.modifiers;
438+
runner.input.raw.events.push(egui::Event::PointerButton {
439+
pos,
440+
button,
441+
pressed: true,
442+
modifiers,
443+
});
444+
445+
// In Safari we are only allowed to write to the clipboard during the
446+
// event callback, which is why we run the app logic here and now:
447+
runner.logic();
448+
449+
// Make sure we paint the output of the above logic call asap:
450+
runner.needs_repaint.repaint_asap();
451+
}
452+
event.stop_propagation();
453+
// Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here.
454+
},
455+
)
456+
}
457+
458+
fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
459+
runner_ref.add_event_listener(
460+
target,
461+
"pointerup",
462+
|event: web_sys::PointerEvent, runner| {
463+
let modifiers = modifiers_from_mouse_event(&event);
464+
runner.input.raw.modifiers = modifiers;
465+
466+
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
467+
468+
if is_interested_in_pointer_event(
469+
runner,
470+
egui::pos2(event.client_x() as f32, event.client_y() as f32),
471+
) {
472+
if let Some(button) = button_from_mouse_event(&event) {
473+
let modifiers = runner.input.raw.modifiers;
474+
runner.input.raw.events.push(egui::Event::PointerButton {
475+
pos,
476+
button,
477+
pressed: false,
478+
modifiers,
479+
});
480+
481+
// Previously on iOS, the canvas would not receive focus on
482+
// any touch event, which resulted in the on-screen keyboard
483+
// not working when focusing on a text field in an egui app.
484+
// This attempts to fix that by forcing the focus on any
485+
// click on the canvas.
486+
runner.canvas().focus().ok();
487+
488+
// In Safari we are only allowed to do certain things
489+
// (like playing audio, start a download, etc)
490+
// on user action, such as a click.
491+
// So we need to run the app logic here and now:
492+
runner.logic();
493+
494+
// Make sure we paint the output of the above logic call asap:
495+
runner.needs_repaint.repaint_asap();
496+
497+
event.prevent_default();
498+
event.stop_propagation();
499+
}
500+
}
501+
},
502+
)
503+
}
504+
423505
/// Returns true if the cursor is above the canvas, or if we're dragging something.
424506
/// Pass in the position in browser viewport coordinates (usually event.clientX/Y).
425507
fn is_interested_in_pointer_event(runner: &AppRunner, pos: egui::Pos2) -> bool {

‎crates/eframe/src/web/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,16 @@ pub fn percent_decode(s: &str) -> String {
264264
.decode_utf8_lossy()
265265
.to_string()
266266
}
267+
268+
/// Returns `true` if the app is likely running on a mobile device.
269+
pub(crate) fn is_mobile() -> bool {
270+
fn try_is_mobile() -> Option<bool> {
271+
const MOBILE_DEVICE: [&str; 6] =
272+
["Android", "iPhone", "iPad", "iPod", "webOS", "BlackBerry"];
273+
274+
let user_agent = web_sys::window()?.navigator().user_agent().ok()?;
275+
let is_mobile = MOBILE_DEVICE.iter().any(|&name| user_agent.contains(name));
276+
Some(is_mobile)
277+
}
278+
try_is_mobile().unwrap_or(false)
279+
}

‎crates/eframe/src/web/text_agent.rs

+1-14
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::cell::Cell;
55

66
use wasm_bindgen::prelude::*;
77

8-
use super::{AppRunner, WebRunner};
8+
use super::{is_mobile, AppRunner, WebRunner};
99

1010
pub struct TextAgent {
1111
input: web_sys::HtmlInputElement,
@@ -173,16 +173,3 @@ impl Drop for TextAgent {
173173
self.input.remove();
174174
}
175175
}
176-
177-
/// Returns `true` if the app is likely running on a mobile device.
178-
fn is_mobile() -> bool {
179-
fn try_is_mobile() -> Option<bool> {
180-
const MOBILE_DEVICE: [&str; 6] =
181-
["Android", "iPhone", "iPad", "iPod", "webOS", "BlackBerry"];
182-
183-
let user_agent = web_sys::window()?.navigator().user_agent().ok()?;
184-
let is_mobile = MOBILE_DEVICE.iter().any(|&name| user_agent.contains(name));
185-
Some(is_mobile)
186-
}
187-
try_is_mobile().unwrap_or(false)
188-
}

0 commit comments

Comments
 (0)
Please sign in to comment.