Force canvas/text input focus on touch for iOS web browsers (#4848)
This commit is contained in:
parent
56df31ab48
commit
34db001db1
|
|
@ -248,6 +248,7 @@ web-sys = { workspace = true, features = [
|
||||||
"Storage",
|
"Storage",
|
||||||
"Touch",
|
"Touch",
|
||||||
"TouchEvent",
|
"TouchEvent",
|
||||||
|
"PointerEvent",
|
||||||
"TouchList",
|
"TouchList",
|
||||||
"WebGl2RenderingContext",
|
"WebGl2RenderingContext",
|
||||||
"WebglDebugRendererInfo",
|
"WebglDebugRendererInfo",
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// so we check if we have focus inside of the handler.
|
||||||
install_copy_cut_paste(runner_ref, &document)?;
|
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:
|
// Use `document` here to notice if the user releases a drag outside of the canvas:
|
||||||
// See https://github.com/emilk/egui/issues/3157
|
// See https://github.com/emilk/egui/issues/3157
|
||||||
install_mousemove(runner_ref, &document)?;
|
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_mouseleave(runner_ref, &canvas)?;
|
||||||
|
|
||||||
install_touchstart(runner_ref, &canvas)?;
|
install_touchstart(runner_ref, &canvas)?;
|
||||||
|
|
@ -390,11 +390,11 @@ fn prevent_default_and_stop_propagation(
|
||||||
Ok(())
|
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(
|
runner_ref.add_event_listener(
|
||||||
target,
|
target,
|
||||||
"mousedown",
|
"pointerdown",
|
||||||
|event: web_sys::MouseEvent, runner: &mut AppRunner| {
|
|event: web_sys::PointerEvent, runner: &mut AppRunner| {
|
||||||
let modifiers = modifiers_from_mouse_event(&event);
|
let modifiers = modifiers_from_mouse_event(&event);
|
||||||
runner.input.raw.modifiers = modifiers;
|
runner.input.raw.modifiers = modifiers;
|
||||||
if let Some(button) = button_from_mouse_event(&event) {
|
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.
|
/// 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).
|
/// Pass in the position in browser viewport coordinates (usually event.clientX/Y).
|
||||||
fn is_interested_in_pointer_event(runner: &AppRunner, pos: egui::Pos2) -> bool {
|
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> {
|
fn install_mouseleave(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
|
||||||
runner_ref.add_event_listener(
|
runner_ref.add_event_listener(
|
||||||
target,
|
target,
|
||||||
|
|
|
||||||
|
|
@ -264,3 +264,16 @@ pub fn percent_decode(s: &str) -> String {
|
||||||
.decode_utf8_lossy()
|
.decode_utf8_lossy()
|
||||||
.to_string()
|
.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<bool> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::cell::Cell;
|
||||||
|
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use super::{AppRunner, WebRunner};
|
use super::{is_mobile, AppRunner, WebRunner};
|
||||||
|
|
||||||
pub struct TextAgent {
|
pub struct TextAgent {
|
||||||
input: web_sys::HtmlInputElement,
|
input: web_sys::HtmlInputElement,
|
||||||
|
|
@ -173,16 +173,3 @@ impl Drop for TextAgent {
|
||||||
self.input.remove();
|
self.input.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the app is likely running on a mobile device.
|
|
||||||
fn is_mobile() -> bool {
|
|
||||||
fn try_is_mobile() -> Option<bool> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue