Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pointer events and focus handling for apps run in a Shadow DOM #5627

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
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
Loading