web: Improve virtual keyboard support
This patch integrates the virtual keyboard with the newly added focus management and removes Android-specific code, instead using generic logic which takes advantage of improved focus support in SWFs.
This commit is contained in:
parent
53d2d16162
commit
96173b0501
|
@ -107,6 +107,8 @@ impl<'gc> FocusTracker<'gc> {
|
||||||
// Mouse focus change events are not dispatched when the object is the same,
|
// Mouse focus change events are not dispatched when the object is the same,
|
||||||
// contrary to key focus change events.
|
// contrary to key focus change events.
|
||||||
if InteractiveObject::option_ptr_eq(old, new) {
|
if InteractiveObject::option_ptr_eq(old, new) {
|
||||||
|
// Re-open the keyboard when the user clicked an already focused text field.
|
||||||
|
self.update_virtual_keyboard(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,15 +196,21 @@ impl<'gc> FocusTracker<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This applies even if the focused element hasn't changed.
|
// This applies even if the focused element hasn't changed.
|
||||||
|
if let Some(text_field) = self.get_as_edit_text() {
|
||||||
|
if text_field.is_editable() && !text_field.movie().is_action_script_3() {
|
||||||
|
// TODO This logic is inaccurate and addresses
|
||||||
|
// only setting the focus programmatically.
|
||||||
|
let length = text_field.text_length();
|
||||||
|
text_field.set_selection(Some(TextSelection::for_range(0, length)), context.gc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_virtual_keyboard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_virtual_keyboard(&self, context: &mut UpdateContext<'_, 'gc>) {
|
||||||
if let Some(text_field) = self.get_as_edit_text() {
|
if let Some(text_field) = self.get_as_edit_text() {
|
||||||
if text_field.is_editable() {
|
if text_field.is_editable() {
|
||||||
if !text_field.movie().is_action_script_3() {
|
|
||||||
// TODO This logic is inaccurate and addresses
|
|
||||||
// only setting the focus programmatically.
|
|
||||||
let length = text_field.text_length();
|
|
||||||
text_field
|
|
||||||
.set_selection(Some(TextSelection::for_range(0, length)), context.gc());
|
|
||||||
}
|
|
||||||
context.ui.open_virtual_keyboard();
|
context.ui.open_virtual_keyboard();
|
||||||
} else {
|
} else {
|
||||||
context.ui.close_virtual_keyboard();
|
context.ui.close_virtual_keyboard();
|
||||||
|
|
|
@ -755,12 +755,6 @@ export class RufflePlayer extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.unmuteAudioContext();
|
this.unmuteAudioContext();
|
||||||
// On Android, the virtual keyboard needs to be dismissed as otherwise it re-focuses when clicking elsewhere
|
|
||||||
if (navigator.userAgent.toLowerCase().includes("android")) {
|
|
||||||
this.container.addEventListener("click", () =>
|
|
||||||
this.virtualKeyboard.blur(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Treat invalid values as `AutoPlay.Auto`.
|
// Treat invalid values as `AutoPlay.Auto`.
|
||||||
if (
|
if (
|
||||||
|
@ -1388,6 +1382,7 @@ export class RufflePlayer extends HTMLElement {
|
||||||
console.error("SWF download failed");
|
console.error("SWF download failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private virtualKeyboardInput() {
|
private virtualKeyboardInput() {
|
||||||
const input = this.virtualKeyboard;
|
const input = this.virtualKeyboard;
|
||||||
const string = input.value;
|
const string = input.value;
|
||||||
|
@ -1403,17 +1398,42 @@ export class RufflePlayer extends HTMLElement {
|
||||||
}
|
}
|
||||||
input.value = "";
|
input.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected openVirtualKeyboard(): void {
|
protected openVirtualKeyboard(): void {
|
||||||
// On Android, the Rust code that opens the virtual keyboard triggers
|
// Virtual keyboard is opened/closed synchronously from core,
|
||||||
// before the TypeScript code that closes it, so delay opening it
|
// and opening/closing it is basically dispatching
|
||||||
if (navigator.userAgent.toLowerCase().includes("android")) {
|
// focus events (which may also be dispatched to the player).
|
||||||
setTimeout(() => {
|
// In order not to deadlock here (or rather throw an error),
|
||||||
this.virtualKeyboard.focus({ preventScroll: true });
|
// these actions should be performed asynchronously.
|
||||||
}, 100);
|
// However, some browsers (i.e. Safari) require user interaction
|
||||||
|
// in order to open the virtual keyboard.
|
||||||
|
// That is why we are checking whether Ruffle already has focus:
|
||||||
|
// 1. if it does, no focus events will be dispatched to
|
||||||
|
// the player when we focus the virtual keyboard, and
|
||||||
|
// 2. if it doesn't, the action shouldn't be a result of user
|
||||||
|
// interaction and focusing synchronously wouldn't work anyway.
|
||||||
|
if (this.instance?.has_focus()) {
|
||||||
|
this.virtualKeyboard.focus({preventScroll: true});
|
||||||
} else {
|
} else {
|
||||||
this.virtualKeyboard.focus({ preventScroll: true });
|
setTimeout(() => {
|
||||||
|
this.virtualKeyboard.focus({preventScroll: true});
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected closeVirtualKeyboard(): void {
|
||||||
|
// Note that closing the keyboard is a little tricky, as we cannot
|
||||||
|
// just remove the focus here, as the player should still be focused.
|
||||||
|
// We want to switch the focus to the container instead, but the user may also
|
||||||
|
// click away from the player, and in that case we do not want to re-focus it.
|
||||||
|
// We also have to take into account that the keyboard may be
|
||||||
|
// closed even if the player doesn't have focus at all.
|
||||||
|
// That's why we have to "transfer" the focus from the keyboard to the container.
|
||||||
|
if (this.isVirtualKeyboardFocused()) {
|
||||||
|
this.container.focus({ preventScroll: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected isVirtualKeyboardFocused(): boolean {
|
protected isVirtualKeyboardFocused(): boolean {
|
||||||
return this.shadow.activeElement === this.virtualKeyboard;
|
return this.shadow.activeElement === this.virtualKeyboard;
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,6 +175,9 @@ extern "C" {
|
||||||
#[wasm_bindgen(method, js_name = "openVirtualKeyboard")]
|
#[wasm_bindgen(method, js_name = "openVirtualKeyboard")]
|
||||||
fn open_virtual_keyboard(this: &JavascriptPlayer);
|
fn open_virtual_keyboard(this: &JavascriptPlayer);
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, js_name = "closeVirtualKeyboard")]
|
||||||
|
fn close_virtual_keyboard(this: &JavascriptPlayer);
|
||||||
|
|
||||||
#[wasm_bindgen(method, js_name = "isVirtualKeyboardFocused")]
|
#[wasm_bindgen(method, js_name = "isVirtualKeyboardFocused")]
|
||||||
fn is_virtual_keyboard_focused(this: &JavascriptPlayer) -> bool;
|
fn is_virtual_keyboard_focused(this: &JavascriptPlayer) -> bool;
|
||||||
|
|
||||||
|
@ -289,6 +292,11 @@ impl RuffleHandle {
|
||||||
self.with_core(|core| core.is_playing()).unwrap_or_default()
|
self.with_core(|core| core.is_playing()).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_focus(&self) -> bool {
|
||||||
|
self.with_instance(|instance| instance.has_focus)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn volume(&self) -> f32 {
|
pub fn volume(&self) -> f32 {
|
||||||
self.with_core(|core| core.volume()).unwrap_or_default()
|
self.with_core(|core| core.volume()).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,7 +292,9 @@ impl UiBackend for WebUiBackend {
|
||||||
self.js_player.open_virtual_keyboard()
|
self.js_player.open_virtual_keyboard()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_virtual_keyboard(&self) {}
|
fn close_virtual_keyboard(&self) {
|
||||||
|
self.js_player.close_virtual_keyboard()
|
||||||
|
}
|
||||||
|
|
||||||
fn language(&self) -> LanguageIdentifier {
|
fn language(&self) -> LanguageIdentifier {
|
||||||
self.language.clone()
|
self.language.clone()
|
||||||
|
|
Loading…
Reference in New Issue