mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 06:10:06 -04:00
eframe web: Better panic handling (#2942)
* Refactor: remove extra store of events * Remove unnecessary extra function * Refactor: simplify event registering * Store panic summary * egui_demo_app: move web-part to own module * index.html: await * Properly unsubscribe from events on panic * Better error handling * Demo app html: hide the wasm canvas and show an error message on panic * egui_demo_app: add panic button to test panic response on web * fix typo * Use a constructor to create WebHandle * Refactor: less use of locks in the interfaces * More consistent naming
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
//! The text agent is an `<input>` element used to trigger
|
||||
//! mobile keyboard and IME input.
|
||||
//!
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
use super::{canvas_element, AppRunner, AppRunnerContainer};
|
||||
use egui::mutex::MutexGuard;
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use super::{canvas_element, AppRunner, AppRunnerRef};
|
||||
|
||||
static AGENT_ID: &str = "egui_text_agent";
|
||||
|
||||
pub fn text_agent() -> web_sys::HtmlInputElement {
|
||||
@@ -21,7 +21,7 @@ pub fn text_agent() -> web_sys::HtmlInputElement {
|
||||
}
|
||||
|
||||
/// Text event handler,
|
||||
pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
|
||||
pub fn install_text_agent(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let body = document.body().expect("document should have a body");
|
||||
@@ -44,60 +44,56 @@ pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(
|
||||
input.set_hidden(true);
|
||||
|
||||
// When IME is off
|
||||
runner_container.add_event_listener(&input, "input", {
|
||||
runner_ref.add_event_listener(&input, "input", {
|
||||
let input_clone = input.clone();
|
||||
let is_composing = is_composing.clone();
|
||||
|
||||
move |_event: web_sys::InputEvent, mut runner_lock| {
|
||||
move |_event: web_sys::InputEvent, runner| {
|
||||
let text = input_clone.value();
|
||||
if !text.is_empty() && !is_composing.get() {
|
||||
input_clone.set_value("");
|
||||
runner_lock.input.raw.events.push(egui::Event::Text(text));
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
runner.input.raw.events.push(egui::Event::Text(text));
|
||||
runner.needs_repaint.repaint_asap();
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
{
|
||||
// When IME is on, handle composition event
|
||||
runner_container.add_event_listener(&input, "compositionstart", {
|
||||
runner_ref.add_event_listener(&input, "compositionstart", {
|
||||
let input_clone = input.clone();
|
||||
let is_composing = is_composing.clone();
|
||||
|
||||
move |_event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| {
|
||||
move |_event: web_sys::CompositionEvent, runner: &mut AppRunner| {
|
||||
is_composing.set(true);
|
||||
input_clone.set_value("");
|
||||
|
||||
runner_lock
|
||||
.input
|
||||
.raw
|
||||
.events
|
||||
.push(egui::Event::CompositionStart);
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
runner.input.raw.events.push(egui::Event::CompositionStart);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
}
|
||||
})?;
|
||||
|
||||
runner_container.add_event_listener(
|
||||
runner_ref.add_event_listener(
|
||||
&input,
|
||||
"compositionupdate",
|
||||
move |event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| {
|
||||
move |event: web_sys::CompositionEvent, runner: &mut AppRunner| {
|
||||
if let Some(event) = event.data().map(egui::Event::CompositionUpdate) {
|
||||
runner_lock.input.raw.events.push(event);
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
runner.input.raw.events.push(event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
runner_container.add_event_listener(&input, "compositionend", {
|
||||
runner_ref.add_event_listener(&input, "compositionend", {
|
||||
let input_clone = input.clone();
|
||||
|
||||
move |event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| {
|
||||
move |event: web_sys::CompositionEvent, runner: &mut AppRunner| {
|
||||
is_composing.set(false);
|
||||
input_clone.set_value("");
|
||||
|
||||
if let Some(event) = event.data().map(egui::Event::CompositionEnd) {
|
||||
runner_lock.input.raw.events.push(event);
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
runner.input.raw.events.push(event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
}
|
||||
}
|
||||
})?;
|
||||
@@ -105,20 +101,16 @@ pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(
|
||||
|
||||
// When input lost focus, focus on it again.
|
||||
// It is useful when user click somewhere outside canvas.
|
||||
runner_container.add_event_listener(
|
||||
&input,
|
||||
"focusout",
|
||||
move |_event: web_sys::MouseEvent, _| {
|
||||
// Delay 10 ms, and focus again.
|
||||
let func = js_sys::Function::new_no_args(&format!(
|
||||
"document.getElementById('{}').focus()",
|
||||
AGENT_ID
|
||||
));
|
||||
window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(&func, 10)
|
||||
.unwrap();
|
||||
},
|
||||
)?;
|
||||
runner_ref.add_event_listener(&input, "focusout", move |_event: web_sys::MouseEvent, _| {
|
||||
// Delay 10 ms, and focus again.
|
||||
let func = js_sys::Function::new_no_args(&format!(
|
||||
"document.getElementById('{}').focus()",
|
||||
AGENT_ID
|
||||
));
|
||||
window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(&func, 10)
|
||||
.unwrap();
|
||||
})?;
|
||||
|
||||
body.append_child(&input)?;
|
||||
|
||||
@@ -126,7 +118,7 @@ pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(
|
||||
}
|
||||
|
||||
/// Focus or blur text agent to toggle mobile keyboard.
|
||||
pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> {
|
||||
pub fn update_text_agent(runner: &mut AppRunner) -> Option<()> {
|
||||
use web_sys::HtmlInputElement;
|
||||
let window = web_sys::window()?;
|
||||
let document = window.document()?;
|
||||
@@ -166,9 +158,6 @@ pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Drop runner lock
|
||||
drop(runner);
|
||||
|
||||
// Holding the runner lock while calling input.blur() causes a panic.
|
||||
// This is most probably caused by the browser running the event handler
|
||||
// for the triggered blur event synchronously, meaning that the mutex
|
||||
@@ -178,15 +167,33 @@ pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> {
|
||||
// and this apparently is the fix for it
|
||||
//
|
||||
// ¯\_(ツ)_/¯ - @DusterTheFirst
|
||||
input.blur().ok()?;
|
||||
|
||||
input.set_hidden(true);
|
||||
canvas_style.set_property("position", "absolute").ok()?;
|
||||
canvas_style.set_property("top", "0%").ok()?; // move back to normal position
|
||||
// So since we are inside a runner lock here, we just postpone the blur/hide:
|
||||
|
||||
call_after_delay(std::time::Duration::from_millis(0), move || {
|
||||
input.blur().ok();
|
||||
input.set_hidden(true);
|
||||
canvas_style.set_property("position", "absolute").ok();
|
||||
canvas_style.set_property("top", "0%").ok(); // move back to normal position
|
||||
});
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn call_after_delay(delay: std::time::Duration, f: impl FnOnce() + 'static) {
|
||||
use wasm_bindgen::prelude::*;
|
||||
let window = web_sys::window().unwrap();
|
||||
let closure = Closure::once(f);
|
||||
let delay_ms = delay.as_millis() as _;
|
||||
window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
closure.as_ref().unchecked_ref(),
|
||||
delay_ms,
|
||||
)
|
||||
.unwrap();
|
||||
closure.forget(); // We must forget it, or else the callback is canceled on drop
|
||||
}
|
||||
|
||||
/// If context is running under mobile device?
|
||||
fn is_mobile() -> Option<bool> {
|
||||
const MOBILE_DEVICE: [&str; 6] = ["Android", "iPhone", "iPad", "iPod", "webOS", "BlackBerry"];
|
||||
|
||||
Reference in New Issue
Block a user