ruffle/web/src/ui.rs

262 lines
8.2 KiB
Rust
Raw Normal View History

use super::JavascriptPlayer;
use ruffle_core::backend::ui::{Error, MouseCursor, UiBackend};
use ruffle_core::events::KeyCode;
use ruffle_web_common::JsResult;
use std::collections::HashSet;
use web_sys::{HtmlCanvasElement, KeyboardEvent};
#[derive(Debug)]
struct FullScreenError {
jsval: String,
}
impl std::fmt::Display for FullScreenError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.jsval)
}
}
impl std::error::Error for FullScreenError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
/// An implementation of `UiBackend` utilizing `web_sys` bindings to input APIs.
pub struct WebUiBackend {
js_player: JavascriptPlayer,
canvas: HtmlCanvasElement,
keys_down: HashSet<KeyCode>,
cursor_visible: bool,
cursor: MouseCursor,
last_key: KeyCode,
last_char: Option<char>,
}
impl WebUiBackend {
pub fn new(js_player: JavascriptPlayer, canvas: &HtmlCanvasElement) -> Self {
Self {
js_player,
canvas: canvas.clone(),
keys_down: HashSet::new(),
cursor_visible: true,
cursor: MouseCursor::Arrow,
last_key: KeyCode::Unknown,
last_char: None,
}
}
/// Register a key press for a given code string.
pub fn keydown(&mut self, event: &KeyboardEvent) {
let key_code = web_to_ruffle_key_code(&event.code());
self.last_key = key_code;
self.keys_down.insert(key_code);
self.last_char = web_key_to_codepoint(&event.key());
}
/// Register a key release for a given code string.
pub fn keyup(&mut self, event: &KeyboardEvent) {
let key_code = web_to_ruffle_key_code(&event.code());
self.last_key = key_code;
self.keys_down.remove(&key_code);
self.last_char = web_key_to_codepoint(&event.key());
}
fn update_mouse_cursor(&self) {
let cursor = if self.cursor_visible {
match self.cursor {
MouseCursor::Arrow => "auto",
MouseCursor::Hand => "pointer",
MouseCursor::IBeam => "text",
MouseCursor::Grab => "grab",
}
} else {
"none"
};
self.canvas
.style()
.set_property("cursor", cursor)
.warn_on_error();
}
}
impl UiBackend for WebUiBackend {
fn is_key_down(&self, key: KeyCode) -> bool {
self.keys_down.contains(&key)
}
fn last_key_code(&self) -> KeyCode {
self.last_key
}
fn last_key_char(&self) -> Option<char> {
self.last_char
}
fn mouse_visible(&self) -> bool {
self.cursor_visible
}
fn set_mouse_visible(&mut self, visible: bool) {
self.cursor_visible = visible;
self.update_mouse_cursor();
}
fn set_mouse_cursor(&mut self, cursor: MouseCursor) {
self.cursor = cursor;
self.update_mouse_cursor();
}
fn set_clipboard_content(&mut self, _content: String) {
log::warn!("set clipboard not implemented");
}
fn set_fullscreen(&mut self, is_full: bool) -> Result<(), Error> {
match self.js_player.set_fullscreen(is_full) {
Ok(_) => Ok(()),
Err(jsval) => Err(Box::new(FullScreenError {
jsval: jsval
.as_string()
.unwrap_or_else(|| "Failed to change full screen state".to_string()),
})),
}
}
fn display_unsupported_message(&self) {
self.js_player.display_unsupported_message()
}
fn display_root_movie_download_failed_message(&self) {
self.js_player.display_root_movie_download_failed_message()
}
fn message(&self, message: &str) {
self.js_player.display_message(message);
}
}
/// Convert a web `KeyboardEvent.code` value into a Ruffle `KeyCode`.
/// Return `KeyCode::Unknown` if there is no matching Flash key code.
fn web_to_ruffle_key_code(key_code: &str) -> KeyCode {
match key_code {
"Backspace" => KeyCode::Backspace,
"Tab" => KeyCode::Tab,
"Enter" => KeyCode::Return,
"ShiftLeft" | "ShiftRight" => KeyCode::Shift,
"ControlLeft" | "ControlRight" => KeyCode::Control,
"AltLeft" | "AltRight" => KeyCode::Alt,
"CapsLock" => KeyCode::CapsLock,
"Escape" => KeyCode::Escape,
"Space" => KeyCode::Space,
"Digit0" => KeyCode::Key0,
"Digit1" => KeyCode::Key1,
"Digit2" => KeyCode::Key2,
"Digit3" => KeyCode::Key3,
"Digit4" => KeyCode::Key4,
"Digit5" => KeyCode::Key5,
"Digit6" => KeyCode::Key6,
"Digit7" => KeyCode::Key7,
"Digit8" => KeyCode::Key8,
"Digit9" => KeyCode::Key9,
"KeyA" => KeyCode::A,
"KeyB" => KeyCode::B,
"KeyC" => KeyCode::C,
"KeyD" => KeyCode::D,
"KeyE" => KeyCode::E,
"KeyF" => KeyCode::F,
"KeyG" => KeyCode::G,
"KeyH" => KeyCode::H,
"KeyI" => KeyCode::I,
"KeyJ" => KeyCode::J,
"KeyK" => KeyCode::K,
"KeyL" => KeyCode::L,
"KeyM" => KeyCode::M,
"KeyN" => KeyCode::N,
"KeyO" => KeyCode::O,
"KeyP" => KeyCode::P,
"KeyQ" => KeyCode::Q,
"KeyR" => KeyCode::R,
"KeyS" => KeyCode::S,
"KeyT" => KeyCode::T,
"KeyU" => KeyCode::U,
"KeyV" => KeyCode::V,
"KeyW" => KeyCode::W,
"KeyX" => KeyCode::X,
"KeyY" => KeyCode::Y,
"KeyZ" => KeyCode::Z,
"Semicolon" => KeyCode::Semicolon,
"Equal" => KeyCode::Equals,
"Comma" => KeyCode::Comma,
"Minus" => KeyCode::Minus,
"Period" => KeyCode::Period,
"Slash" => KeyCode::Slash,
"Backquote" => KeyCode::Grave,
"BracketLeft" => KeyCode::LBracket,
"Backslash" => KeyCode::Backslash,
"BracketRight" => KeyCode::RBracket,
"Quote" => KeyCode::Apostrophe,
"Numpad0" => KeyCode::Numpad0,
"Numpad1" => KeyCode::Numpad1,
"Numpad2" => KeyCode::Numpad2,
"Numpad3" => KeyCode::Numpad3,
"Numpad4" => KeyCode::Numpad4,
"Numpad5" => KeyCode::Numpad5,
"Numpad6" => KeyCode::Numpad6,
"Numpad7" => KeyCode::Numpad7,
"Numpad8" => KeyCode::Numpad8,
"Numpad9" => KeyCode::Numpad9,
"NumpadMultiply" => KeyCode::Multiply,
"NumpadAdd" => KeyCode::Plus,
"NumpadSubtract" => KeyCode::NumpadMinus,
"NumpadDecimal" => KeyCode::NumpadPeriod,
"NumpadDivide" => KeyCode::NumpadSlash,
"PageUp" => KeyCode::PgUp,
"PageDown" => KeyCode::PgDown,
"End" => KeyCode::End,
"Home" => KeyCode::Home,
"ArrowLeft" => KeyCode::Left,
"ArrowUp" => KeyCode::Up,
"ArrowRight" => KeyCode::Right,
"ArrowDown" => KeyCode::Down,
"Insert" => KeyCode::Insert,
"Delete" => KeyCode::Delete,
"Pause" => KeyCode::Pause,
"ScrollLock" => KeyCode::ScrollLock,
"F1" => KeyCode::F1,
"F2" => KeyCode::F2,
"F3" => KeyCode::F3,
"F4" => KeyCode::F4,
"F5" => KeyCode::F5,
"F6" => KeyCode::F6,
"F7" => KeyCode::F7,
"F8" => KeyCode::F8,
"F9" => KeyCode::F9,
"F10" => KeyCode::F10,
"F11" => KeyCode::F11,
"F12" => KeyCode::F12,
_ => KeyCode::Unknown,
}
}
/// Convert a web `KeyboardEvent.key` value into a character codepoint.
/// Return `None` if they input was not a printable character.
fn web_key_to_codepoint(key: &str) -> Option<char> {
// TODO: This is a very cheesy way to tell if a `KeyboardEvent.key` is a printable character.
// Single character strings will be an actual printable char that we can use as text input.
// All the other special values are multiple characters (e.g. "ArrowLeft").
// It's probably better to explicitly match on all the variants.
let mut chars = key.chars();
let (c1, c2) = (chars.next(), chars.next());
if c2.is_none() {
// Single character.
c1
} else {
// Check for special characters.
match key {
"Backspace" => Some(8 as char),
"Delete" => Some(127 as char),
_ => None,
}
}
}