web: Use browser focus events for focus management
This patch changes how the player focus is managed. Before this patch, `instance.has_focus` was unrelated to the current focus managed by the browser. When the user clicked anywhere on the window, it was set to `false`, and when the user clicked on the player, it was set to `true`. This had two major issues. 1. When the user clicked on the player, `has_focus` was set to `false`, and then again to `true`. That was problematic when listening to focus change events. 2. Not using browser's focus makes it harder to integrate with Ruffle, i.e. tab out of Ruffle. This patch uses browser's focus management to detect focus and listen to focus events: 1. on `focusin` – FocusGained event is fired, 2. on `focusout` – FocusLost event is fired, and 3. on `pointerdown` – focus is manually set to the canvas. The canvas has `tabindex` set to -1 in order to be focusable.
This commit is contained in:
parent
599352093c
commit
5441749e3e
|
@ -74,7 +74,7 @@ features = [
|
||||||
"EventTarget", "GainNode", "Headers", "HtmlCanvasElement", "HtmlDocument", "HtmlElement", "HtmlFormElement",
|
"EventTarget", "GainNode", "Headers", "HtmlCanvasElement", "HtmlDocument", "HtmlElement", "HtmlFormElement",
|
||||||
"HtmlInputElement", "HtmlTextAreaElement", "KeyboardEvent", "Location", "PointerEvent",
|
"HtmlInputElement", "HtmlTextAreaElement", "KeyboardEvent", "Location", "PointerEvent",
|
||||||
"Request", "RequestInit", "Response", "Storage", "WheelEvent", "Window", "ReadableStream", "RequestCredentials",
|
"Request", "RequestInit", "Response", "Storage", "WheelEvent", "Window", "ReadableStream", "RequestCredentials",
|
||||||
"Url", "Clipboard",
|
"Url", "Clipboard", "FocusEvent"
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.cargo-machete]
|
[package.metadata.cargo-machete]
|
||||||
|
|
|
@ -574,6 +574,8 @@ impl RuffleInstanceBuilder {
|
||||||
|
|
||||||
let (renderer, canvas) = self.create_renderer().await?;
|
let (renderer, canvas) = self.create_renderer().await?;
|
||||||
|
|
||||||
|
canvas.set_tab_index(-1);
|
||||||
|
|
||||||
let mut builder = PlayerBuilder::new()
|
let mut builder = PlayerBuilder::new()
|
||||||
.with_boxed_renderer(renderer)
|
.with_boxed_renderer(renderer)
|
||||||
.with_boxed_audio(self.create_audio_backend(log_subscriber.clone()))
|
.with_boxed_audio(self.create_audio_backend(log_subscriber.clone()))
|
||||||
|
|
|
@ -36,8 +36,8 @@ use url::Url;
|
||||||
use wasm_bindgen::convert::FromWasmAbi;
|
use wasm_bindgen::convert::FromWasmAbi;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use web_sys::{
|
use web_sys::{
|
||||||
AddEventListenerOptions, ClipboardEvent, Element, Event, EventTarget, HtmlCanvasElement,
|
AddEventListenerOptions, ClipboardEvent, Element, Event, EventTarget, FocusEvent,
|
||||||
HtmlElement, KeyboardEvent, PointerEvent, WheelEvent, Window,
|
HtmlCanvasElement, HtmlElement, KeyboardEvent, PointerEvent, WheelEvent, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
static RUFFLE_GLOBAL_PANIC: Once = Once::new();
|
static RUFFLE_GLOBAL_PANIC: Once = Once::new();
|
||||||
|
@ -125,14 +125,15 @@ struct RuffleInstance {
|
||||||
mouse_enter_callback: Option<JsCallback<PointerEvent>>,
|
mouse_enter_callback: Option<JsCallback<PointerEvent>>,
|
||||||
mouse_leave_callback: Option<JsCallback<PointerEvent>>,
|
mouse_leave_callback: Option<JsCallback<PointerEvent>>,
|
||||||
mouse_down_callback: Option<JsCallback<PointerEvent>>,
|
mouse_down_callback: Option<JsCallback<PointerEvent>>,
|
||||||
player_mouse_down_callback: Option<JsCallback<PointerEvent>>,
|
|
||||||
window_mouse_down_callback: Option<JsCallback<PointerEvent>>,
|
|
||||||
mouse_up_callback: Option<JsCallback<PointerEvent>>,
|
mouse_up_callback: Option<JsCallback<PointerEvent>>,
|
||||||
mouse_wheel_callback: Option<JsCallback<WheelEvent>>,
|
mouse_wheel_callback: Option<JsCallback<WheelEvent>>,
|
||||||
key_down_callback: Option<JsCallback<KeyboardEvent>>,
|
key_down_callback: Option<JsCallback<KeyboardEvent>>,
|
||||||
key_up_callback: Option<JsCallback<KeyboardEvent>>,
|
key_up_callback: Option<JsCallback<KeyboardEvent>>,
|
||||||
paste_callback: Option<JsCallback<ClipboardEvent>>,
|
paste_callback: Option<JsCallback<ClipboardEvent>>,
|
||||||
unload_callback: Option<JsCallback<Event>>,
|
unload_callback: Option<JsCallback<Event>>,
|
||||||
|
focusin_callback: Option<JsCallback<FocusEvent>>,
|
||||||
|
focusout_callback: Option<JsCallback<FocusEvent>>,
|
||||||
|
focus_on_press_callback: Option<JsCallback<PointerEvent>>,
|
||||||
has_focus: bool,
|
has_focus: bool,
|
||||||
trace_observer: Rc<RefCell<JsValue>>,
|
trace_observer: Rc<RefCell<JsValue>>,
|
||||||
log_subscriber: Arc<Layered<WASMLayer, Registry>>,
|
log_subscriber: Arc<Layered<WASMLayer, Registry>>,
|
||||||
|
@ -473,14 +474,15 @@ impl RuffleHandle {
|
||||||
mouse_enter_callback: None,
|
mouse_enter_callback: None,
|
||||||
mouse_leave_callback: None,
|
mouse_leave_callback: None,
|
||||||
mouse_down_callback: None,
|
mouse_down_callback: None,
|
||||||
player_mouse_down_callback: None,
|
|
||||||
window_mouse_down_callback: None,
|
|
||||||
mouse_up_callback: None,
|
mouse_up_callback: None,
|
||||||
mouse_wheel_callback: None,
|
mouse_wheel_callback: None,
|
||||||
key_down_callback: None,
|
key_down_callback: None,
|
||||||
key_up_callback: None,
|
key_up_callback: None,
|
||||||
paste_callback: None,
|
paste_callback: None,
|
||||||
unload_callback: None,
|
unload_callback: None,
|
||||||
|
focusin_callback: None,
|
||||||
|
focusout_callback: None,
|
||||||
|
focus_on_press_callback: None,
|
||||||
timestamp: None,
|
timestamp: None,
|
||||||
has_focus: false,
|
has_focus: false,
|
||||||
trace_observer: player.trace_observer,
|
trace_observer: player.trace_observer,
|
||||||
|
@ -587,35 +589,6 @@ impl RuffleHandle {
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
// Create player mouse down handler.
|
|
||||||
instance.player_mouse_down_callback = Some(JsCallback::register(
|
|
||||||
&js_player,
|
|
||||||
"pointerdown",
|
|
||||||
false,
|
|
||||||
move |_js_event| {
|
|
||||||
let _ = ruffle.with_instance_mut(|instance| {
|
|
||||||
instance.has_focus = true;
|
|
||||||
// Ensure the parent window gets focus. This is necessary for events
|
|
||||||
// to be received when the player is inside a frame.
|
|
||||||
instance.window.focus().warn_on_error();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
// Create window mouse down handler.
|
|
||||||
instance.window_mouse_down_callback = Some(JsCallback::register(
|
|
||||||
&window,
|
|
||||||
"pointerdown",
|
|
||||||
true,
|
|
||||||
move |_js_event| {
|
|
||||||
let _ = ruffle.with_instance_mut(|instance| {
|
|
||||||
// If we actually clicked on the player, this will be reset to true
|
|
||||||
// after the event bubbles down to the player.
|
|
||||||
instance.has_focus = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
// Create mouse up handler.
|
// Create mouse up handler.
|
||||||
instance.mouse_up_callback = Some(JsCallback::register(
|
instance.mouse_up_callback = Some(JsCallback::register(
|
||||||
&player.canvas,
|
&player.canvas,
|
||||||
|
@ -767,6 +740,48 @@ impl RuffleHandle {
|
||||||
core.flush_shared_objects();
|
core.flush_shared_objects();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
instance.focusin_callback = Some(JsCallback::register(
|
||||||
|
&player.canvas,
|
||||||
|
"focusin",
|
||||||
|
false,
|
||||||
|
move |_js_event: FocusEvent| {
|
||||||
|
let _ = ruffle.with_instance_mut(|instance| {
|
||||||
|
if !instance.has_focus {
|
||||||
|
instance.has_focus = true;
|
||||||
|
let _ = instance.with_core_mut(|core| {
|
||||||
|
core.handle_event(PlayerEvent::FocusGained);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
instance.focusout_callback = Some(JsCallback::register(
|
||||||
|
&player.canvas,
|
||||||
|
"focusout",
|
||||||
|
false,
|
||||||
|
move |_js_event: FocusEvent| {
|
||||||
|
let _ = ruffle.with_instance_mut(|instance| {
|
||||||
|
if instance.has_focus {
|
||||||
|
let _ = instance.with_core_mut(|core| {
|
||||||
|
core.handle_event(PlayerEvent::FocusLost);
|
||||||
|
});
|
||||||
|
instance.has_focus = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
let canvas = player.canvas.clone();
|
||||||
|
instance.focus_on_press_callback = Some(JsCallback::register(
|
||||||
|
&player.canvas,
|
||||||
|
"pointerdown",
|
||||||
|
false,
|
||||||
|
move |_js_event| {
|
||||||
|
canvas.focus().warn_on_error();
|
||||||
|
},
|
||||||
|
));
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Set initial timestamp and do initial tick to start animation loop.
|
// Set initial timestamp and do initial tick to start animation loop.
|
||||||
|
|
Loading…
Reference in New Issue