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,
|
||||
// contrary to key focus change events.
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -195,14 +197,20 @@ impl<'gc> FocusTracker<'gc> {
|
|||
|
||||
// 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() {
|
||||
if !text_field.movie().is_action_script_3() {
|
||||
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());
|
||||
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 text_field.is_editable() {
|
||||
context.ui.open_virtual_keyboard();
|
||||
} else {
|
||||
context.ui.close_virtual_keyboard();
|
||||
|
|
|
@ -755,12 +755,6 @@ export class RufflePlayer extends HTMLElement {
|
|||
}
|
||||
|
||||
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`.
|
||||
if (
|
||||
|
@ -1388,6 +1382,7 @@ export class RufflePlayer extends HTMLElement {
|
|||
console.error("SWF download failed");
|
||||
}
|
||||
}
|
||||
|
||||
private virtualKeyboardInput() {
|
||||
const input = this.virtualKeyboard;
|
||||
const string = input.value;
|
||||
|
@ -1403,17 +1398,42 @@ export class RufflePlayer extends HTMLElement {
|
|||
}
|
||||
input.value = "";
|
||||
}
|
||||
|
||||
protected openVirtualKeyboard(): void {
|
||||
// On Android, the Rust code that opens the virtual keyboard triggers
|
||||
// before the TypeScript code that closes it, so delay opening it
|
||||
if (navigator.userAgent.toLowerCase().includes("android")) {
|
||||
// Virtual keyboard is opened/closed synchronously from core,
|
||||
// and opening/closing it is basically dispatching
|
||||
// focus events (which may also be dispatched to the player).
|
||||
// In order not to deadlock here (or rather throw an error),
|
||||
// these actions should be performed asynchronously.
|
||||
// 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 {
|
||||
setTimeout(() => {
|
||||
this.virtualKeyboard.focus({preventScroll: true});
|
||||
}, 100);
|
||||
} else {
|
||||
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 {
|
||||
return this.shadow.activeElement === this.virtualKeyboard;
|
||||
}
|
||||
|
|
|
@ -175,6 +175,9 @@ extern "C" {
|
|||
#[wasm_bindgen(method, js_name = "openVirtualKeyboard")]
|
||||
fn open_virtual_keyboard(this: &JavascriptPlayer);
|
||||
|
||||
#[wasm_bindgen(method, js_name = "closeVirtualKeyboard")]
|
||||
fn close_virtual_keyboard(this: &JavascriptPlayer);
|
||||
|
||||
#[wasm_bindgen(method, js_name = "isVirtualKeyboardFocused")]
|
||||
fn is_virtual_keyboard_focused(this: &JavascriptPlayer) -> bool;
|
||||
|
||||
|
@ -289,6 +292,11 @@ impl RuffleHandle {
|
|||
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 {
|
||||
self.with_core(|core| core.volume()).unwrap_or_default()
|
||||
}
|
||||
|
|
|
@ -292,7 +292,9 @@ impl UiBackend for WebUiBackend {
|
|||
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 {
|
||||
self.language.clone()
|
||||
|
|
Loading…
Reference in New Issue