Skip to content

Commit

Permalink
Add pointer events and focus handling for apps run in a Shadow DOM
Browse files Browse the repository at this point in the history
  • Loading branch information
xxvvii committed Jan 22, 2025
1 parent d864655 commit a8c1790
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 16 deletions.
1 change: 1 addition & 0 deletions crates/eframe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ web-sys = { workspace = true, features = [
"ResizeObserverEntry",
"ResizeObserverOptions",
"ResizeObserverSize",
"ShadowRoot",
"Storage",
"Touch",
"TouchEvent",
Expand Down
17 changes: 12 additions & 5 deletions crates/eframe/src/web/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::{
push_touches, text_from_keyboard_event, theme_from_dark_mode, translate_key, AppRunner,
Closure, JsCast, JsValue, WebRunner,
};
use web_sys::EventTarget;
use web_sys::{Document, EventTarget, ShadowRoot};

// TODO(emilk): there are more calls to `prevent_default` and `stop_propagation`
// than what is probably needed.
Expand Down Expand Up @@ -510,10 +510,17 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
/// 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 {
let document = web_sys::window().unwrap().document().unwrap();
let is_hovering_canvas = document
.element_from_point(pos.x, pos.y)
.is_some_and(|element| element.eq(runner.canvas()));
let root_node = runner.canvas().get_root_node();

let element_at_point = if let Some(document) = root_node.dyn_ref::<Document>() {
document.element_from_point(pos.x, pos.y)
} else if let Some(shadow) = root_node.dyn_ref::<ShadowRoot>() {
shadow.element_from_point(pos.x, pos.y)
} else {
None
};

let is_hovering_canvas = element_at_point.is_some_and(|element| element.eq(runner.canvas()));
let is_pointer_down = runner
.egui_ctx()
.input(|i| i.pointer.any_down() || i.any_touches());
Expand Down
20 changes: 12 additions & 8 deletions crates/eframe/src/web/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu;
pub use backend::*;

use wasm_bindgen::prelude::*;
use web_sys::MediaQueryList;
use web_sys::{Document, MediaQueryList, Node};

use input::{
button_from_mouse_event, modifiers_from_kb_event, modifiers_from_mouse_event,
Expand All @@ -61,18 +61,22 @@ pub(crate) fn string_from_js_value(value: &JsValue) -> String {
/// - `<a>`/`<area>` with an `href` attribute
/// - `<input>`/`<select>`/`<textarea>`/`<button>` which aren't `disabled`
/// - any other element with a `tabindex` attribute
pub(crate) fn focused_element() -> Option<web_sys::Element> {
web_sys::window()?
.document()?
.active_element()?
.dyn_into()
.ok()
pub(crate) fn focused_element(root: Node) -> Option<web_sys::Element> {
if let Some(document) = root.dyn_ref::<Document>() {
document.active_element()
} else if let Some(shadow) = root.dyn_ref::<web_sys::ShadowRoot>() {
shadow.active_element()
} else {
None
}
}

pub(crate) fn has_focus<T: JsCast>(element: &T) -> bool {
fn try_has_focus<T: JsCast>(element: &T) -> Option<bool> {
let element = element.dyn_ref::<web_sys::Element>()?;
let focused_element = focused_element()?;
let root = element.get_root_node();

let focused_element = focused_element(root)?;
Some(element == &focused_element)
}
try_has_focus(element).unwrap_or(false)
Expand Down
15 changes: 13 additions & 2 deletions crates/eframe/src/web/text_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use std::cell::Cell;

use wasm_bindgen::prelude::*;
use web_sys::{Document, Node};

use super::{AppRunner, WebRunner};

Expand All @@ -14,7 +15,7 @@ pub struct TextAgent {

impl TextAgent {
/// Attach the agent to the document.
pub fn attach(runner_ref: &WebRunner) -> Result<Self, JsValue> {
pub fn attach(runner_ref: &WebRunner, root: Node) -> Result<Self, JsValue> {
let document = web_sys::window().unwrap().document().unwrap();

// create an `<input>` element
Expand All @@ -36,7 +37,17 @@ impl TextAgent {
style.set_property("position", "absolute")?;
style.set_property("top", "0")?;
style.set_property("left", "0")?;
document.body().unwrap().append_child(&input)?;

if root.has_type::<Document>() {
// root object is a document, append to its body
root.dyn_into::<Document>()?
.body()
.unwrap()
.append_child(&input)?;
} else {
// append input into root directly
root.append_child(&input)?;
}

// attach event listeners

Expand Down
2 changes: 1 addition & 1 deletion crates/eframe/src/web/web_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl WebRunner {
) -> Result<(), JsValue> {
self.destroy();

let text_agent = TextAgent::attach(self)?;
let text_agent = TextAgent::attach(self, canvas.get_root_node())?;

let runner = AppRunner::new(canvas, web_options, app_creator, text_agent).await?;

Expand Down

0 comments on commit a8c1790

Please sign in to comment.