2020-08-13 06:51:12 +00:00
|
|
|
#![allow(clippy::same_item_push, clippy::unknown_clippy_lints)]
|
2020-08-13 02:04:14 +00:00
|
|
|
|
2019-05-24 17:25:03 +00:00
|
|
|
//! Ruffle web frontend.
|
2019-05-03 02:11:47 +00:00
|
|
|
mod audio;
|
2019-12-19 01:20:49 +00:00
|
|
|
mod input;
|
2020-09-02 20:05:29 +00:00
|
|
|
mod locale;
|
2020-09-05 19:43:14 +00:00
|
|
|
mod log_adapter;
|
2019-09-01 19:24:04 +00:00
|
|
|
mod navigator;
|
2020-06-15 20:55:29 +00:00
|
|
|
mod storage;
|
2019-05-03 02:11:47 +00:00
|
|
|
|
2020-09-05 19:43:14 +00:00
|
|
|
use crate::log_adapter::WebLogBackend;
|
2020-06-15 20:55:29 +00:00
|
|
|
use crate::storage::LocalStorageBackend;
|
2020-09-02 20:05:29 +00:00
|
|
|
use crate::{
|
|
|
|
audio::WebAudioBackend, input::WebInputBackend, locale::WebLocaleBackend,
|
|
|
|
navigator::WebNavigatorBackend,
|
|
|
|
};
|
2019-05-10 16:06:47 +00:00
|
|
|
use generational_arena::{Arena, Index};
|
2020-09-02 19:02:32 +00:00
|
|
|
use js_sys::{Array, Function, Object, Uint8Array};
|
2020-05-02 09:59:13 +00:00
|
|
|
use ruffle_core::backend::render::RenderBackend;
|
2020-06-15 16:53:19 +00:00
|
|
|
use ruffle_core::backend::storage::MemoryStorageBackend;
|
2020-06-15 23:59:06 +00:00
|
|
|
use ruffle_core::backend::storage::StorageBackend;
|
2020-09-02 19:02:32 +00:00
|
|
|
use ruffle_core::context::UpdateContext;
|
2020-08-22 00:04:02 +00:00
|
|
|
use ruffle_core::events::MouseWheelDelta;
|
2020-09-02 19:02:32 +00:00
|
|
|
use ruffle_core::external::{
|
|
|
|
ExternalInterfaceMethod, ExternalInterfaceProvider, Value as ExternalValue, Value,
|
|
|
|
};
|
2020-05-04 19:37:06 +00:00
|
|
|
use ruffle_core::tag_utils::SwfMovie;
|
2019-11-08 20:09:57 +00:00
|
|
|
use ruffle_core::PlayerEvent;
|
2020-05-14 08:12:24 +00:00
|
|
|
use ruffle_web_common::JsResult;
|
2020-09-02 19:02:32 +00:00
|
|
|
use std::collections::BTreeMap;
|
2020-09-09 22:15:02 +00:00
|
|
|
use std::sync::Once;
|
2019-11-08 20:09:57 +00:00
|
|
|
use std::sync::{Arc, Mutex};
|
2019-05-17 02:14:23 +00:00
|
|
|
use std::{cell::RefCell, error::Error, num::NonZeroI32};
|
2019-08-09 21:50:20 +00:00
|
|
|
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
2020-08-22 00:04:02 +00:00
|
|
|
use web_sys::{
|
|
|
|
AddEventListenerOptions, Element, EventTarget, HtmlCanvasElement, HtmlElement, KeyboardEvent,
|
|
|
|
PointerEvent, WheelEvent,
|
|
|
|
};
|
2019-04-28 01:15:43 +00:00
|
|
|
|
2020-09-09 22:15:02 +00:00
|
|
|
static RUFFLE_GLOBAL_PANIC: Once = Once::new();
|
|
|
|
|
2019-05-03 00:17:02 +00:00
|
|
|
thread_local! {
|
2019-05-24 17:25:03 +00:00
|
|
|
/// We store the actual instances of the ruffle core in a static pool.
|
|
|
|
/// This gives us a clear boundary between the JS side and Rust side, avoiding
|
|
|
|
/// issues with lifetimes and type paramters (which cannot be exported with wasm-bindgen).
|
|
|
|
static INSTANCES: RefCell<Arena<RuffleInstance>> = RefCell::new(Arena::new());
|
2020-09-02 19:51:39 +00:00
|
|
|
|
|
|
|
static CURRENT_CONTEXT: RefCell<Option<*mut UpdateContext<'static, 'static, 'static>>> = RefCell::new(None);
|
2019-05-17 02:14:23 +00:00
|
|
|
}
|
|
|
|
|
2019-08-15 20:48:51 +00:00
|
|
|
type AnimationHandler = Closure<dyn FnMut(f64)>;
|
2019-05-17 02:14:23 +00:00
|
|
|
|
2019-05-24 17:25:03 +00:00
|
|
|
struct RuffleInstance {
|
2019-11-08 20:09:57 +00:00
|
|
|
core: Arc<Mutex<ruffle_core::Player>>,
|
2020-09-02 19:02:32 +00:00
|
|
|
js_player: JavascriptPlayer,
|
2019-08-20 01:40:55 +00:00
|
|
|
canvas: HtmlCanvasElement,
|
|
|
|
canvas_width: i32,
|
|
|
|
canvas_height: i32,
|
2019-08-20 05:23:02 +00:00
|
|
|
device_pixel_ratio: f64,
|
2019-08-22 20:28:06 +00:00
|
|
|
timestamp: Option<f64>,
|
2019-05-17 02:14:23 +00:00
|
|
|
animation_handler: Option<AnimationHandler>, // requestAnimationFrame callback
|
|
|
|
animation_handler_id: Option<NonZeroI32>, // requestAnimationFrame id
|
2019-08-09 21:50:20 +00:00
|
|
|
#[allow(dead_code)]
|
2019-12-23 20:15:06 +00:00
|
|
|
mouse_move_callback: Option<Closure<dyn FnMut(PointerEvent)>>,
|
|
|
|
mouse_down_callback: Option<Closure<dyn FnMut(PointerEvent)>>,
|
|
|
|
mouse_up_callback: Option<Closure<dyn FnMut(PointerEvent)>>,
|
|
|
|
window_mouse_down_callback: Option<Closure<dyn FnMut(PointerEvent)>>,
|
2020-08-22 00:04:02 +00:00
|
|
|
mouse_wheel_callback: Option<Closure<dyn FnMut(WheelEvent)>>,
|
2019-12-22 02:42:55 +00:00
|
|
|
key_down_callback: Option<Closure<dyn FnMut(KeyboardEvent)>>,
|
|
|
|
key_up_callback: Option<Closure<dyn FnMut(KeyboardEvent)>>,
|
|
|
|
has_focus: bool,
|
2020-09-05 19:43:14 +00:00
|
|
|
trace_observer: Arc<RefCell<JsValue>>,
|
2019-05-03 00:17:02 +00:00
|
|
|
}
|
|
|
|
|
2020-09-02 19:02:32 +00:00
|
|
|
#[wasm_bindgen(module = "/packages/core/src/ruffle-player.js")]
|
|
|
|
extern "C" {
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub type JavascriptPlayer;
|
|
|
|
|
|
|
|
#[wasm_bindgen(method)]
|
|
|
|
fn on_callback_available(this: &JavascriptPlayer, name: &str);
|
2020-09-09 22:15:02 +00:00
|
|
|
|
|
|
|
#[wasm_bindgen(method)]
|
|
|
|
fn panic(this: &JavascriptPlayer);
|
2020-09-02 19:02:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct JavascriptInterface {
|
|
|
|
js_player: JavascriptPlayer,
|
|
|
|
}
|
|
|
|
|
2019-05-24 17:25:03 +00:00
|
|
|
/// An opaque handle to a `RuffleInstance` inside the pool.
|
|
|
|
///
|
|
|
|
/// This type is exported to JS, and is used to interact with the library.
|
2019-04-28 01:15:43 +00:00
|
|
|
#[wasm_bindgen]
|
2019-05-17 02:14:23 +00:00
|
|
|
#[derive(Clone)]
|
2019-05-24 17:25:03 +00:00
|
|
|
pub struct Ruffle(Index);
|
2019-04-28 01:15:43 +00:00
|
|
|
|
|
|
|
#[wasm_bindgen]
|
2019-05-24 17:25:03 +00:00
|
|
|
impl Ruffle {
|
2020-09-02 21:34:04 +00:00
|
|
|
pub fn new(
|
|
|
|
parent: HtmlElement,
|
|
|
|
js_player: JavascriptPlayer,
|
|
|
|
allow_script_access: bool,
|
|
|
|
) -> Result<Ruffle, JsValue> {
|
2020-09-09 22:15:02 +00:00
|
|
|
if RUFFLE_GLOBAL_PANIC.is_completed() {
|
|
|
|
// If an actual panic happened, then we can't trust the state it left us in.
|
|
|
|
// Prevent future players from loading so that they can inform the user about the error.
|
|
|
|
return Err("Ruffle is panicking!".into());
|
|
|
|
}
|
|
|
|
set_panic_handler();
|
2020-09-02 21:34:04 +00:00
|
|
|
Ruffle::new_internal(parent, js_player, allow_script_access)
|
|
|
|
.map_err(|_| "Error creating player".into())
|
2020-07-23 03:18:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Stream an arbitrary movie file from (presumably) the Internet.
|
|
|
|
///
|
|
|
|
/// This method should only be called once per player.
|
|
|
|
pub fn stream_from(&mut self, movie_url: &str) {
|
|
|
|
INSTANCES.with(|instances| {
|
2020-09-11 18:42:35 +00:00
|
|
|
let instances = instances.borrow();
|
|
|
|
let instance = instances.get(self.0).unwrap();
|
2020-07-23 03:18:30 +00:00
|
|
|
instance.core.lock().unwrap().fetch_root_movie(movie_url);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Play an arbitrary movie on this instance.
|
|
|
|
///
|
|
|
|
/// This method should only be called once per player.
|
|
|
|
pub fn load_data(&mut self, swf_data: Uint8Array) -> Result<(), JsValue> {
|
|
|
|
let movie = Arc::new({
|
|
|
|
let mut data = vec![0; swf_data.length() as usize];
|
|
|
|
swf_data.copy_to(&mut data[..]);
|
|
|
|
SwfMovie::from_data(&data, None).map_err(|e| format!("Error loading movie: {}", e))?
|
|
|
|
});
|
|
|
|
|
|
|
|
INSTANCES.with(|instances| {
|
2020-09-11 18:42:35 +00:00
|
|
|
let instances = instances.borrow();
|
|
|
|
let instance = instances.get(self.0).unwrap();
|
2020-07-23 03:18:30 +00:00
|
|
|
instance.core.lock().unwrap().set_root_movie(movie);
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(())
|
2019-04-28 01:15:43 +00:00
|
|
|
}
|
|
|
|
|
2020-04-27 10:34:47 +00:00
|
|
|
pub fn play(&mut self) {
|
|
|
|
// Remove instance from the active list.
|
|
|
|
INSTANCES.with(|instances| {
|
2020-09-11 18:42:35 +00:00
|
|
|
let instances = instances.borrow();
|
|
|
|
let instance = instances.get(self.0).unwrap();
|
2020-04-27 10:34:47 +00:00
|
|
|
instance.core.lock().unwrap().set_is_playing(true);
|
|
|
|
log::info!("PLAY!");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-05-17 02:14:23 +00:00
|
|
|
pub fn destroy(&mut self) -> Result<(), JsValue> {
|
|
|
|
// Remove instance from the active list.
|
2020-05-14 08:12:24 +00:00
|
|
|
if let Some(mut instance) = INSTANCES.with(|instances| {
|
2019-05-24 17:25:03 +00:00
|
|
|
let mut instances = instances.borrow_mut();
|
|
|
|
instances.remove(self.0)
|
2019-05-17 02:14:23 +00:00
|
|
|
}) {
|
2020-05-14 08:12:24 +00:00
|
|
|
instance.canvas.remove();
|
|
|
|
|
2020-04-13 11:57:10 +00:00
|
|
|
// Stop all audio playing from the instance
|
|
|
|
let mut player = instance.core.lock().unwrap();
|
|
|
|
let audio = player.audio_mut();
|
|
|
|
audio.stop_all_sounds();
|
|
|
|
|
2020-05-14 08:12:24 +00:00
|
|
|
// Clean up all event listeners.
|
|
|
|
instance.key_down_callback = None;
|
|
|
|
instance.key_up_callback = None;
|
|
|
|
instance.mouse_down_callback = None;
|
|
|
|
instance.mouse_move_callback = None;
|
|
|
|
instance.mouse_up_callback = None;
|
|
|
|
instance.window_mouse_down_callback = None;
|
|
|
|
|
2019-05-17 02:14:23 +00:00
|
|
|
// Cancel the animation handler, if it's still active.
|
2019-05-24 17:25:03 +00:00
|
|
|
if let Some(id) = instance.animation_handler_id {
|
2019-05-17 02:14:23 +00:00
|
|
|
if let Some(window) = web_sys::window() {
|
|
|
|
return window.cancel_animation_frame(id.into());
|
|
|
|
}
|
2020-06-15 23:59:06 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-06 10:51:09 +00:00
|
|
|
|
2019-05-17 02:14:23 +00:00
|
|
|
// Player is dropped at this point.
|
|
|
|
Ok(())
|
2019-04-28 01:15:43 +00:00
|
|
|
}
|
2020-09-02 19:02:32 +00:00
|
|
|
|
|
|
|
#[allow(clippy::boxed_local)] // for js_bind
|
|
|
|
pub fn call_exposed_callback(&self, name: &str, args: Box<[JsValue]>) -> JsValue {
|
|
|
|
let args: Vec<ExternalValue> = args.iter().map(js_to_external_value).collect();
|
2020-09-02 19:51:39 +00:00
|
|
|
|
|
|
|
// Re-entrant callbacks need to return through the hole that was punched through for them
|
|
|
|
// We record the context of external functions, and then if we get an internal callback
|
|
|
|
// during the same call we'll reuse that.
|
|
|
|
// This is unsafe by nature. I don't know any safe way to do this.
|
|
|
|
if let Some(context) = CURRENT_CONTEXT.with(|v| *v.borrow()) {
|
|
|
|
unsafe {
|
|
|
|
if let Some(callback) = (*context).external_interface.get_callback(name) {
|
|
|
|
return external_to_js_value(callback.call(&mut *context, name, args));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-02 19:02:32 +00:00
|
|
|
INSTANCES.with(move |instances| {
|
2020-09-11 18:42:35 +00:00
|
|
|
if let Ok(instances) = instances.try_borrow() {
|
|
|
|
if let Some(instance) = instances.get(self.0) {
|
2020-09-02 19:02:32 +00:00
|
|
|
if let Ok(mut player) = instance.core.try_lock() {
|
|
|
|
return external_to_js_value(player.call_internal_interface(name, args));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
JsValue::NULL
|
|
|
|
})
|
|
|
|
}
|
2020-09-05 19:43:14 +00:00
|
|
|
|
|
|
|
pub fn set_trace_observer(&self, observer: JsValue) {
|
|
|
|
INSTANCES.with(move |instances| {
|
|
|
|
if let Ok(mut instances) = instances.try_borrow_mut() {
|
|
|
|
if let Some(instance) = instances.get_mut(self.0) {
|
|
|
|
*instance.trace_observer.borrow_mut() = observer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2019-04-28 01:15:43 +00:00
|
|
|
}
|
2020-06-15 23:54:00 +00:00
|
|
|
|
2019-05-24 17:25:03 +00:00
|
|
|
impl Ruffle {
|
2020-09-02 19:02:32 +00:00
|
|
|
fn new_internal(
|
|
|
|
parent: HtmlElement,
|
|
|
|
js_player: JavascriptPlayer,
|
2020-09-02 21:34:04 +00:00
|
|
|
allow_script_access: bool,
|
2020-09-02 19:02:32 +00:00
|
|
|
) -> Result<Ruffle, Box<dyn Error>> {
|
2019-05-06 10:51:09 +00:00
|
|
|
let _ = console_log::init_with_level(log::Level::Trace);
|
2019-04-30 08:53:21 +00:00
|
|
|
|
2020-09-07 19:18:25 +00:00
|
|
|
let window = web_sys::window().ok_or("Expected window")?;
|
2020-05-14 08:12:24 +00:00
|
|
|
let document = window.document().ok_or("Expected document")?;
|
|
|
|
|
|
|
|
let (canvas, renderer) = create_renderer(&document)?;
|
|
|
|
parent
|
|
|
|
.append_child(&canvas.clone().into())
|
|
|
|
.into_js_result()?;
|
|
|
|
|
2019-11-08 20:09:57 +00:00
|
|
|
let audio = Box::new(WebAudioBackend::new()?);
|
|
|
|
let navigator = Box::new(WebNavigatorBackend::new());
|
|
|
|
let input = Box::new(WebInputBackend::new(&canvas));
|
2020-09-02 20:05:29 +00:00
|
|
|
let locale = Box::new(WebLocaleBackend::new());
|
2019-11-08 20:09:57 +00:00
|
|
|
|
2020-06-15 23:54:00 +00:00
|
|
|
let current_domain = window.location().href().unwrap();
|
|
|
|
|
2020-06-15 20:55:29 +00:00
|
|
|
let local_storage = window
|
|
|
|
.local_storage()
|
|
|
|
.unwrap()
|
2020-06-15 23:59:06 +00:00
|
|
|
.map(|s| {
|
|
|
|
Box::new(LocalStorageBackend::new(s, current_domain)) as Box<dyn StorageBackend>
|
|
|
|
})
|
2020-06-15 20:55:29 +00:00
|
|
|
.unwrap_or_else(|| Box::new(MemoryStorageBackend::default()));
|
|
|
|
|
2020-09-05 19:43:14 +00:00
|
|
|
let trace_observer = Arc::new(RefCell::new(JsValue::UNDEFINED));
|
|
|
|
let log = Box::new(WebLogBackend::new(trace_observer.clone()));
|
2020-09-05 16:19:03 +00:00
|
|
|
|
|
|
|
let core = ruffle_core::Player::new(
|
|
|
|
renderer,
|
|
|
|
audio,
|
|
|
|
navigator,
|
|
|
|
input,
|
|
|
|
local_storage,
|
|
|
|
locale,
|
|
|
|
log,
|
|
|
|
)?;
|
2019-11-08 20:09:57 +00:00
|
|
|
|
2019-05-17 02:14:23 +00:00
|
|
|
// Create instance.
|
2019-05-24 17:25:03 +00:00
|
|
|
let instance = RuffleInstance {
|
2019-05-17 02:14:23 +00:00
|
|
|
core,
|
2020-09-02 19:02:32 +00:00
|
|
|
js_player,
|
2019-08-20 01:40:55 +00:00
|
|
|
canvas: canvas.clone(),
|
|
|
|
canvas_width: 0, // Intiailize canvas width and height to 0 to force an initial canvas resize.
|
|
|
|
canvas_height: 0,
|
2019-08-20 05:23:02 +00:00
|
|
|
device_pixel_ratio: window.device_pixel_ratio(),
|
2019-05-17 02:14:23 +00:00
|
|
|
animation_handler: None,
|
|
|
|
animation_handler_id: None,
|
2019-08-20 01:17:29 +00:00
|
|
|
mouse_move_callback: None,
|
|
|
|
mouse_down_callback: None,
|
2019-12-22 02:42:55 +00:00
|
|
|
window_mouse_down_callback: None,
|
2019-08-20 01:17:29 +00:00
|
|
|
mouse_up_callback: None,
|
2020-08-22 00:04:02 +00:00
|
|
|
mouse_wheel_callback: None,
|
2019-12-22 02:42:55 +00:00
|
|
|
key_down_callback: None,
|
|
|
|
key_up_callback: None,
|
2019-08-22 20:28:06 +00:00
|
|
|
timestamp: None,
|
2019-12-22 02:42:55 +00:00
|
|
|
has_focus: false,
|
2020-09-05 19:43:14 +00:00
|
|
|
trace_observer,
|
2019-05-17 02:14:23 +00:00
|
|
|
};
|
|
|
|
|
2019-12-23 20:15:06 +00:00
|
|
|
// Prevent touch-scrolling on canvas.
|
|
|
|
canvas.style().set_property("touch-action", "none").unwrap();
|
|
|
|
|
2019-05-17 02:14:23 +00:00
|
|
|
// Register the instance and create the animation frame closure.
|
2019-05-24 17:25:03 +00:00
|
|
|
let mut ruffle = INSTANCES.with(move |instances| {
|
|
|
|
let mut instances = instances.borrow_mut();
|
|
|
|
let index = instances.insert(instance);
|
|
|
|
let ruffle = Ruffle(index);
|
2019-05-17 02:14:23 +00:00
|
|
|
|
2020-09-02 19:02:32 +00:00
|
|
|
// Create the external interface
|
2020-09-02 21:34:04 +00:00
|
|
|
if allow_script_access {
|
2020-09-02 19:02:32 +00:00
|
|
|
let instance = instances.get_mut(index).unwrap();
|
|
|
|
instance
|
|
|
|
.core
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.add_external_interface(Box::new(JavascriptInterface::new(
|
|
|
|
instance.js_player.clone(),
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
2019-05-17 02:14:23 +00:00
|
|
|
// Create the animation frame closure.
|
|
|
|
{
|
2019-05-24 17:25:03 +00:00
|
|
|
let mut ruffle = ruffle.clone();
|
|
|
|
let instance = instances.get_mut(index).unwrap();
|
2019-05-17 02:14:23 +00:00
|
|
|
instance.animation_handler = Some(Closure::wrap(Box::new(move |timestamp: f64| {
|
2019-05-24 17:25:03 +00:00
|
|
|
ruffle.tick(timestamp);
|
2019-05-17 02:14:23 +00:00
|
|
|
})
|
2019-08-15 20:48:51 +00:00
|
|
|
as Box<dyn FnMut(f64)>));
|
2019-05-17 02:14:23 +00:00
|
|
|
}
|
|
|
|
|
2019-08-20 01:17:29 +00:00
|
|
|
// Create mouse move handler.
|
|
|
|
{
|
2019-12-23 20:15:06 +00:00
|
|
|
let mouse_move_callback = Closure::wrap(Box::new(move |js_event: PointerEvent| {
|
2019-08-20 01:17:29 +00:00
|
|
|
INSTANCES.with(move |instances| {
|
2020-09-11 18:42:35 +00:00
|
|
|
let instances = instances.borrow();
|
|
|
|
if let Some(instance) = instances.get(index) {
|
2019-08-20 01:17:29 +00:00
|
|
|
let event = PlayerEvent::MouseMove {
|
2019-08-20 05:23:02 +00:00
|
|
|
x: f64::from(js_event.offset_x()) * instance.device_pixel_ratio,
|
|
|
|
y: f64::from(js_event.offset_y()) * instance.device_pixel_ratio,
|
2019-08-20 01:17:29 +00:00
|
|
|
};
|
2019-11-08 20:09:57 +00:00
|
|
|
instance.core.lock().unwrap().handle_event(event);
|
2019-12-22 02:42:55 +00:00
|
|
|
if instance.has_focus {
|
|
|
|
js_event.prevent_default();
|
|
|
|
}
|
2019-08-20 01:17:29 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
})
|
2019-12-23 20:15:06 +00:00
|
|
|
as Box<dyn FnMut(PointerEvent)>);
|
2019-08-20 01:17:29 +00:00
|
|
|
let canvas_events: &EventTarget = canvas.as_ref();
|
|
|
|
canvas_events
|
|
|
|
.add_event_listener_with_callback(
|
2019-12-23 20:15:06 +00:00
|
|
|
"pointermove",
|
2019-08-20 01:17:29 +00:00
|
|
|
mouse_move_callback.as_ref().unchecked_ref(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let instance = instances.get_mut(index).unwrap();
|
|
|
|
instance.mouse_move_callback = Some(mouse_move_callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create mouse down handler.
|
|
|
|
{
|
2019-12-23 20:15:06 +00:00
|
|
|
let mouse_down_callback = Closure::wrap(Box::new(move |js_event: PointerEvent| {
|
2019-08-20 01:17:29 +00:00
|
|
|
INSTANCES.with(move |instances| {
|
|
|
|
let mut instances = instances.borrow_mut();
|
|
|
|
if let Some(instance) = instances.get_mut(index) {
|
2019-12-22 02:42:55 +00:00
|
|
|
instance.has_focus = true;
|
2019-12-23 20:15:06 +00:00
|
|
|
if let Some(target) = js_event.current_target() {
|
|
|
|
let _ = target
|
|
|
|
.unchecked_ref::<Element>()
|
|
|
|
.set_pointer_capture(js_event.pointer_id());
|
|
|
|
}
|
2019-08-20 01:17:29 +00:00
|
|
|
let event = PlayerEvent::MouseDown {
|
2019-08-20 05:23:02 +00:00
|
|
|
x: f64::from(js_event.offset_x()) * instance.device_pixel_ratio,
|
|
|
|
y: f64::from(js_event.offset_y()) * instance.device_pixel_ratio,
|
2019-08-20 01:17:29 +00:00
|
|
|
};
|
2019-11-08 20:09:57 +00:00
|
|
|
instance.core.lock().unwrap().handle_event(event);
|
2019-12-22 02:42:55 +00:00
|
|
|
js_event.prevent_default();
|
2019-08-20 01:17:29 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
})
|
2019-12-23 20:15:06 +00:00
|
|
|
as Box<dyn FnMut(PointerEvent)>);
|
2019-08-20 01:17:29 +00:00
|
|
|
let canvas_events: &EventTarget = canvas.as_ref();
|
|
|
|
canvas_events
|
|
|
|
.add_event_listener_with_callback(
|
2019-12-23 20:15:06 +00:00
|
|
|
"pointerdown",
|
2019-08-20 01:17:29 +00:00
|
|
|
mouse_down_callback.as_ref().unchecked_ref(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let instance = instances.get_mut(index).unwrap();
|
|
|
|
instance.mouse_down_callback = Some(mouse_down_callback);
|
|
|
|
}
|
|
|
|
|
2019-12-22 02:42:55 +00:00
|
|
|
// Create window mouse down handler.
|
|
|
|
{
|
|
|
|
let window_mouse_down_callback =
|
2019-12-23 20:15:06 +00:00
|
|
|
Closure::wrap(Box::new(move |_js_event: PointerEvent| {
|
2019-12-22 02:42:55 +00:00
|
|
|
INSTANCES.with(|instances| {
|
|
|
|
let mut instances = instances.borrow_mut();
|
|
|
|
if let Some(instance) = instances.get_mut(index) {
|
|
|
|
// If we actually clicked on the canvas, this will be reset to true
|
|
|
|
// after the event bubbles down to the canvas.
|
|
|
|
instance.has_focus = false;
|
|
|
|
}
|
|
|
|
});
|
2019-12-23 20:15:06 +00:00
|
|
|
}) as Box<dyn FnMut(PointerEvent)>);
|
2019-12-22 02:42:55 +00:00
|
|
|
|
|
|
|
window
|
|
|
|
.add_event_listener_with_callback_and_bool(
|
2019-12-23 20:15:06 +00:00
|
|
|
"pointerdown",
|
2019-12-22 02:42:55 +00:00
|
|
|
window_mouse_down_callback.as_ref().unchecked_ref(),
|
|
|
|
true, // Use capture so this first *before* the canvas mouse down handler.
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let instance = instances.get_mut(index).unwrap();
|
|
|
|
instance.window_mouse_down_callback = Some(window_mouse_down_callback);
|
|
|
|
}
|
|
|
|
|
2019-08-20 01:17:29 +00:00
|
|
|
// Create mouse up handler.
|
|
|
|
{
|
2019-12-23 20:15:06 +00:00
|
|
|
let mouse_up_callback = Closure::wrap(Box::new(move |js_event: PointerEvent| {
|
2019-08-20 01:17:29 +00:00
|
|
|
INSTANCES.with(move |instances| {
|
2020-09-11 18:42:35 +00:00
|
|
|
let instances = instances.borrow();
|
|
|
|
if let Some(instance) = instances.get(index) {
|
2019-12-23 20:15:06 +00:00
|
|
|
if let Some(target) = js_event.current_target() {
|
|
|
|
let _ = target
|
|
|
|
.unchecked_ref::<Element>()
|
|
|
|
.release_pointer_capture(js_event.pointer_id());
|
|
|
|
}
|
2019-08-20 01:17:29 +00:00
|
|
|
let event = PlayerEvent::MouseUp {
|
2019-08-20 05:23:02 +00:00
|
|
|
x: f64::from(js_event.offset_x()) * instance.device_pixel_ratio,
|
|
|
|
y: f64::from(js_event.offset_y()) * instance.device_pixel_ratio,
|
2019-08-20 01:17:29 +00:00
|
|
|
};
|
2019-11-08 20:09:57 +00:00
|
|
|
instance.core.lock().unwrap().handle_event(event);
|
2019-12-22 02:42:55 +00:00
|
|
|
if instance.has_focus {
|
|
|
|
js_event.prevent_default();
|
|
|
|
}
|
2019-08-20 01:17:29 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
})
|
2019-12-23 20:15:06 +00:00
|
|
|
as Box<dyn FnMut(PointerEvent)>);
|
2019-08-20 01:17:29 +00:00
|
|
|
let canvas_events: &EventTarget = canvas.as_ref();
|
|
|
|
canvas_events
|
|
|
|
.add_event_listener_with_callback(
|
2019-12-23 20:15:06 +00:00
|
|
|
"pointerup",
|
2019-08-20 01:17:29 +00:00
|
|
|
mouse_up_callback.as_ref().unchecked_ref(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let instance = instances.get_mut(index).unwrap();
|
|
|
|
instance.mouse_up_callback = Some(mouse_up_callback);
|
|
|
|
}
|
|
|
|
|
2020-08-22 00:04:02 +00:00
|
|
|
// Create mouse wheel handler.
|
|
|
|
{
|
|
|
|
let mouse_wheel_callback = Closure::wrap(Box::new(move |js_event: WheelEvent| {
|
|
|
|
INSTANCES.with(move |instances| {
|
2020-09-11 18:42:35 +00:00
|
|
|
let instances = instances.borrow();
|
|
|
|
if let Some(instance) = instances.get(index) {
|
2020-08-22 00:04:02 +00:00
|
|
|
let delta = match js_event.delta_mode() {
|
|
|
|
WheelEvent::DOM_DELTA_LINE => {
|
|
|
|
MouseWheelDelta::Lines(-js_event.delta_y())
|
|
|
|
}
|
|
|
|
WheelEvent::DOM_DELTA_PIXEL => {
|
|
|
|
MouseWheelDelta::Pixels(-js_event.delta_y())
|
|
|
|
}
|
|
|
|
_ => return,
|
|
|
|
};
|
|
|
|
let mut core = instance.core.lock().unwrap();
|
|
|
|
core.handle_event(PlayerEvent::MouseWheel { delta });
|
|
|
|
if core.should_prevent_scrolling() {
|
|
|
|
js_event.prevent_default();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})
|
|
|
|
as Box<dyn FnMut(WheelEvent)>);
|
|
|
|
let canvas_events: &EventTarget = canvas.as_ref();
|
|
|
|
let mut options = AddEventListenerOptions::new();
|
|
|
|
options.passive(false);
|
|
|
|
canvas_events
|
|
|
|
.add_event_listener_with_callback_and_add_event_listener_options(
|
|
|
|
"wheel",
|
|
|
|
mouse_wheel_callback.as_ref().unchecked_ref(),
|
|
|
|
&options,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let instance = instances.get_mut(index).unwrap();
|
|
|
|
instance.mouse_wheel_callback = Some(mouse_wheel_callback);
|
|
|
|
}
|
2019-08-09 21:50:20 +00:00
|
|
|
|
2019-12-19 01:20:49 +00:00
|
|
|
// Create keydown event handler.
|
|
|
|
{
|
2019-12-22 02:42:55 +00:00
|
|
|
let key_down_callback = Closure::wrap(Box::new(move |js_event: KeyboardEvent| {
|
|
|
|
INSTANCES.with(|instances| {
|
2020-09-11 18:42:35 +00:00
|
|
|
if let Some(instance) = instances.borrow().get(index) {
|
2019-12-22 02:42:55 +00:00
|
|
|
if instance.has_focus {
|
2019-12-24 10:41:35 +00:00
|
|
|
let code = js_event.code();
|
2019-11-08 20:09:57 +00:00
|
|
|
instance
|
|
|
|
.core
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.input_mut()
|
|
|
|
.downcast_mut::<WebInputBackend>()
|
|
|
|
.unwrap()
|
|
|
|
.keydown(code.clone());
|
2019-12-24 10:41:35 +00:00
|
|
|
|
|
|
|
if let Some(codepoint) =
|
|
|
|
input::web_key_to_codepoint(&js_event.key())
|
|
|
|
{
|
|
|
|
instance
|
|
|
|
.core
|
2019-11-08 20:09:57 +00:00
|
|
|
.lock()
|
|
|
|
.unwrap()
|
2019-12-24 10:41:35 +00:00
|
|
|
.handle_event(PlayerEvent::TextInput { codepoint });
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(key_code) = input::web_to_ruffle_key_code(&code) {
|
|
|
|
instance
|
|
|
|
.core
|
2019-11-08 20:09:57 +00:00
|
|
|
.lock()
|
|
|
|
.unwrap()
|
2019-12-24 10:41:35 +00:00
|
|
|
.handle_event(PlayerEvent::KeyDown { key_code });
|
|
|
|
}
|
|
|
|
|
2019-12-22 02:42:55 +00:00
|
|
|
js_event.prevent_default();
|
|
|
|
}
|
2019-12-19 01:20:49 +00:00
|
|
|
}
|
2019-12-22 02:42:55 +00:00
|
|
|
});
|
2019-12-19 01:20:49 +00:00
|
|
|
})
|
|
|
|
as Box<dyn FnMut(KeyboardEvent)>);
|
|
|
|
|
|
|
|
window
|
|
|
|
.add_event_listener_with_callback(
|
|
|
|
"keydown",
|
2019-12-22 02:42:55 +00:00
|
|
|
key_down_callback.as_ref().unchecked_ref(),
|
2019-12-19 01:20:49 +00:00
|
|
|
)
|
|
|
|
.unwrap();
|
2019-12-22 02:42:55 +00:00
|
|
|
let instance = instances.get_mut(index).unwrap();
|
|
|
|
instance.key_down_callback = Some(key_down_callback);
|
|
|
|
}
|
2019-12-19 01:20:49 +00:00
|
|
|
|
2019-12-22 02:42:55 +00:00
|
|
|
{
|
|
|
|
let key_up_callback = Closure::wrap(Box::new(move |js_event: KeyboardEvent| {
|
|
|
|
js_event.prevent_default();
|
|
|
|
INSTANCES.with(|instances| {
|
2020-09-11 18:42:35 +00:00
|
|
|
if let Some(instance) = instances.borrow().get(index) {
|
2019-12-22 02:42:55 +00:00
|
|
|
if instance.has_focus {
|
2020-02-26 11:03:36 +00:00
|
|
|
let code = js_event.code();
|
2019-11-08 20:09:57 +00:00
|
|
|
instance
|
|
|
|
.core
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.input_mut()
|
|
|
|
.downcast_mut::<WebInputBackend>()
|
|
|
|
.unwrap()
|
2020-02-26 11:03:36 +00:00
|
|
|
.keyup(code.clone());
|
|
|
|
|
|
|
|
if let Some(key_code) = input::web_to_ruffle_key_code(&code) {
|
|
|
|
instance
|
|
|
|
.core
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.handle_event(PlayerEvent::KeyUp { key_code });
|
|
|
|
}
|
|
|
|
|
2019-12-22 02:42:55 +00:00
|
|
|
js_event.prevent_default();
|
|
|
|
}
|
2019-12-19 01:20:49 +00:00
|
|
|
}
|
2019-12-22 02:42:55 +00:00
|
|
|
});
|
2019-12-19 01:20:49 +00:00
|
|
|
})
|
|
|
|
as Box<dyn FnMut(KeyboardEvent)>);
|
|
|
|
window
|
|
|
|
.add_event_listener_with_callback(
|
|
|
|
"keyup",
|
2019-12-22 02:42:55 +00:00
|
|
|
key_up_callback.as_ref().unchecked_ref(),
|
2019-12-19 01:20:49 +00:00
|
|
|
)
|
|
|
|
.unwrap();
|
2019-12-22 02:42:55 +00:00
|
|
|
let instance = instances.get_mut(index).unwrap();
|
|
|
|
instance.key_up_callback = Some(key_up_callback);
|
2019-12-19 01:20:49 +00:00
|
|
|
}
|
|
|
|
|
2019-05-24 17:25:03 +00:00
|
|
|
ruffle
|
2019-05-03 00:17:02 +00:00
|
|
|
});
|
|
|
|
|
2019-08-22 20:28:06 +00:00
|
|
|
// Set initial timestamp and do initial tick to start animation loop.
|
|
|
|
ruffle.tick(0.0);
|
2019-05-17 02:14:23 +00:00
|
|
|
|
2019-05-24 17:25:03 +00:00
|
|
|
Ok(ruffle)
|
2019-05-17 02:14:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn tick(&mut self, timestamp: f64) {
|
2019-05-24 17:25:03 +00:00
|
|
|
INSTANCES.with(|instances| {
|
|
|
|
let mut instances = instances.borrow_mut();
|
|
|
|
if let Some(instance) = instances.get_mut(self.0) {
|
2020-01-16 05:34:22 +00:00
|
|
|
let window = web_sys::window().unwrap();
|
|
|
|
|
2019-08-22 20:28:06 +00:00
|
|
|
// Calculate the dt from last tick.
|
|
|
|
let dt = if let Some(prev_timestamp) = instance.timestamp {
|
|
|
|
instance.timestamp = Some(timestamp);
|
|
|
|
timestamp - prev_timestamp
|
|
|
|
} else {
|
|
|
|
// Store the timestamp from the initial tick.
|
|
|
|
// (I tried to use Performance.now() to get the initial timestamp,
|
|
|
|
// but this didn't seem to be accurate and caused negative dts on
|
|
|
|
// Chrome.)
|
|
|
|
instance.timestamp = Some(timestamp);
|
|
|
|
0.0
|
|
|
|
};
|
2019-08-09 21:50:20 +00:00
|
|
|
|
2020-05-02 11:25:21 +00:00
|
|
|
let mut core_lock = instance.core.lock().unwrap();
|
|
|
|
core_lock.tick(dt);
|
|
|
|
let mut needs_render = core_lock.needs_render();
|
2019-05-17 02:14:23 +00:00
|
|
|
|
2019-08-20 01:40:55 +00:00
|
|
|
// Check for canvas resize.
|
|
|
|
let canvas_width = instance.canvas.client_width();
|
|
|
|
let canvas_height = instance.canvas.client_height();
|
2020-01-16 05:34:22 +00:00
|
|
|
let device_pixel_ratio = window.device_pixel_ratio(); // Changes via user zooming.
|
|
|
|
if instance.canvas_width != canvas_width
|
|
|
|
|| instance.canvas_height != canvas_height
|
|
|
|
|| (instance.device_pixel_ratio - device_pixel_ratio).abs() >= std::f64::EPSILON
|
2019-08-20 01:40:55 +00:00
|
|
|
{
|
|
|
|
// If a canvas resizes, it's drawing context will get scaled. You must reset
|
|
|
|
// the width and height attributes of the canvas element to recreate the context.
|
|
|
|
// (NOT the CSS width/height!)
|
|
|
|
instance.canvas_width = canvas_width;
|
|
|
|
instance.canvas_height = canvas_height;
|
2020-01-16 05:34:22 +00:00
|
|
|
instance.device_pixel_ratio = device_pixel_ratio;
|
2019-08-20 05:23:02 +00:00
|
|
|
|
2019-08-20 01:40:55 +00:00
|
|
|
// The actual viewport is scaled by DPI, bigger than CSS pixels.
|
2019-08-20 05:23:02 +00:00
|
|
|
let viewport_width =
|
|
|
|
(f64::from(canvas_width) * instance.device_pixel_ratio) as u32;
|
|
|
|
let viewport_height =
|
|
|
|
(f64::from(canvas_height) * instance.device_pixel_ratio) as u32;
|
2019-08-20 01:40:55 +00:00
|
|
|
instance.canvas.set_width(viewport_width);
|
|
|
|
instance.canvas.set_height(viewport_height);
|
2019-11-08 20:09:57 +00:00
|
|
|
|
|
|
|
core_lock.set_viewport_dimensions(viewport_width, viewport_height);
|
|
|
|
core_lock
|
2019-08-20 01:40:55 +00:00
|
|
|
.renderer_mut()
|
|
|
|
.set_viewport_dimensions(viewport_width, viewport_height);
|
|
|
|
|
|
|
|
// Force a re-render if we resize.
|
2020-05-02 11:25:21 +00:00
|
|
|
needs_render = true;
|
|
|
|
}
|
2019-11-08 20:09:57 +00:00
|
|
|
|
2020-05-02 11:25:21 +00:00
|
|
|
if needs_render {
|
|
|
|
core_lock.render();
|
2019-08-20 01:40:55 +00:00
|
|
|
}
|
|
|
|
|
2019-05-17 02:14:23 +00:00
|
|
|
// Request next animation frame.
|
2019-05-24 17:25:03 +00:00
|
|
|
if let Some(handler) = &instance.animation_handler {
|
2019-05-17 02:14:23 +00:00
|
|
|
let window = web_sys::window().unwrap();
|
|
|
|
let id = window
|
|
|
|
.request_animation_frame(handler.as_ref().unchecked_ref())
|
|
|
|
.unwrap();
|
2019-05-24 17:25:03 +00:00
|
|
|
instance.animation_handler_id = NonZeroI32::new(id);
|
2019-05-17 02:14:23 +00:00
|
|
|
} else {
|
2019-05-24 17:25:03 +00:00
|
|
|
instance.animation_handler_id = None;
|
2019-05-17 02:14:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2019-04-28 06:08:59 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-02 09:59:13 +00:00
|
|
|
|
2020-09-02 19:02:32 +00:00
|
|
|
struct JavascriptMethod {
|
|
|
|
this: JsValue,
|
|
|
|
function: JsValue,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ExternalInterfaceMethod for JavascriptMethod {
|
|
|
|
fn call(
|
|
|
|
&self,
|
2020-09-02 19:51:39 +00:00
|
|
|
context: &mut UpdateContext<'_, '_, '_>,
|
2020-09-02 19:02:32 +00:00
|
|
|
args: &[ExternalValue],
|
|
|
|
) -> ExternalValue {
|
2020-09-02 19:51:39 +00:00
|
|
|
let old_context = CURRENT_CONTEXT.with(|v| {
|
|
|
|
v.replace(Some(unsafe {
|
|
|
|
std::mem::transmute::<
|
|
|
|
&mut UpdateContext,
|
|
|
|
&mut UpdateContext<'static, 'static, 'static>,
|
|
|
|
>(context)
|
|
|
|
} as *mut UpdateContext))
|
|
|
|
});
|
|
|
|
let result = if let Some(function) = self.function.dyn_ref::<Function>() {
|
2020-09-02 19:02:32 +00:00
|
|
|
let args_array = Array::new();
|
|
|
|
for arg in args {
|
|
|
|
args_array.push(&external_to_js_value(arg.to_owned()));
|
|
|
|
}
|
|
|
|
if let Ok(result) = function.apply(&self.this, &args_array) {
|
|
|
|
js_to_external_value(&result)
|
|
|
|
} else {
|
|
|
|
ExternalValue::Null
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ExternalValue::Null
|
2020-09-02 19:51:39 +00:00
|
|
|
};
|
|
|
|
CURRENT_CONTEXT.with(|v| v.replace(old_context));
|
|
|
|
result
|
2020-09-02 19:02:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl JavascriptInterface {
|
2020-09-02 21:36:36 +00:00
|
|
|
fn new(js_player: JavascriptPlayer) -> Self {
|
|
|
|
Self { js_player }
|
2020-09-02 19:02:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn find_method(&self, root: JsValue, name: &str) -> Option<JavascriptMethod> {
|
|
|
|
let mut parent = JsValue::UNDEFINED;
|
|
|
|
let mut value = root;
|
|
|
|
for key in name.split('.') {
|
|
|
|
parent = value;
|
|
|
|
value = js_sys::Reflect::get(&parent, &JsValue::from_str(key)).ok()?;
|
|
|
|
}
|
|
|
|
if value.is_function() {
|
|
|
|
Some(JavascriptMethod {
|
|
|
|
this: parent,
|
|
|
|
function: value,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ExternalInterfaceProvider for JavascriptInterface {
|
|
|
|
fn get_method(&self, name: &str) -> Option<Box<dyn ExternalInterfaceMethod>> {
|
2020-09-02 21:36:36 +00:00
|
|
|
if let Some(method) = self.find_method(self.js_player.clone().into(), name) {
|
2020-09-02 19:02:32 +00:00
|
|
|
return Some(Box::new(method));
|
|
|
|
}
|
|
|
|
if let Some(window) = web_sys::window() {
|
|
|
|
if let Some(method) = self.find_method(window.into(), name) {
|
|
|
|
return Some(Box::new(method));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn on_callback_available(&self, name: &str) {
|
|
|
|
self.js_player.on_callback_available(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn js_to_external_value(js: &JsValue) -> ExternalValue {
|
|
|
|
if let Some(value) = js.as_f64() {
|
|
|
|
ExternalValue::Number(value)
|
|
|
|
} else if let Some(value) = js.as_string() {
|
|
|
|
ExternalValue::String(value)
|
|
|
|
} else if let Some(value) = js.as_bool() {
|
|
|
|
ExternalValue::Bool(value)
|
|
|
|
} else if let Some(array) = js.dyn_ref::<Array>() {
|
|
|
|
let mut values = Vec::new();
|
|
|
|
for value in array.values() {
|
|
|
|
if let Ok(value) = value {
|
|
|
|
values.push(js_to_external_value(&value));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ExternalValue::List(values)
|
|
|
|
} else if let Some(object) = js.dyn_ref::<Object>() {
|
|
|
|
let mut values = BTreeMap::new();
|
|
|
|
for entry in Object::entries(&object).values() {
|
|
|
|
if let Ok(entry) = entry.and_then(|v| v.dyn_into::<Array>()) {
|
|
|
|
if let Some(key) = entry.get(0).as_string() {
|
|
|
|
values.insert(key, js_to_external_value(&entry.get(1)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ExternalValue::Object(values)
|
|
|
|
} else {
|
|
|
|
ExternalValue::Null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn external_to_js_value(external: ExternalValue) -> JsValue {
|
|
|
|
match external {
|
|
|
|
Value::Null => JsValue::NULL,
|
|
|
|
Value::Bool(value) => JsValue::from_bool(value),
|
|
|
|
Value::Number(value) => JsValue::from_f64(value),
|
|
|
|
Value::String(value) => JsValue::from_str(&value),
|
|
|
|
Value::Object(object) => {
|
|
|
|
let entries = Array::new();
|
|
|
|
for (key, value) in object {
|
|
|
|
entries.push(&Array::of2(
|
|
|
|
&JsValue::from_str(&key),
|
|
|
|
&external_to_js_value(value),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
if let Ok(result) = Object::from_entries(&entries) {
|
|
|
|
result.into()
|
|
|
|
} else {
|
|
|
|
JsValue::NULL
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Value::List(values) => {
|
|
|
|
let array = Array::new();
|
|
|
|
for value in values {
|
|
|
|
array.push(&external_to_js_value(value));
|
|
|
|
}
|
|
|
|
array.into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-14 08:12:24 +00:00
|
|
|
fn create_renderer(
|
|
|
|
document: &web_sys::Document,
|
|
|
|
) -> Result<(HtmlCanvasElement, Box<dyn RenderBackend>), Box<dyn Error>> {
|
2020-05-02 09:59:13 +00:00
|
|
|
#[cfg(not(any(feature = "canvas", feature = "webgl")))]
|
|
|
|
std::compile_error!("You must enable one of the render backend features (e.g., webgl).");
|
|
|
|
|
2020-05-14 08:12:24 +00:00
|
|
|
// Try to create a backend, falling through to the next backend on failure.
|
|
|
|
// We must recreate the canvas each attempt, as only a single context may be created per canvas
|
|
|
|
// with `getContext`.
|
2020-05-02 09:59:13 +00:00
|
|
|
#[cfg(feature = "webgl")]
|
|
|
|
{
|
2020-05-14 08:12:24 +00:00
|
|
|
log::info!("Creating WebGL renderer...");
|
|
|
|
let canvas: HtmlCanvasElement = document
|
|
|
|
.create_element("canvas")
|
|
|
|
.into_js_result()?
|
|
|
|
.dyn_into()
|
|
|
|
.map_err(|_| "Expected HtmlCanvasElement")?;
|
|
|
|
if let Ok(renderer) = ruffle_render_webgl::WebGlRenderBackend::new(&canvas) {
|
|
|
|
return Ok((canvas, Box::new(renderer)));
|
2020-05-02 09:59:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "canvas")]
|
|
|
|
{
|
2020-05-14 08:12:24 +00:00
|
|
|
log::info!("Falling back to Canvas renderer...");
|
|
|
|
let canvas: HtmlCanvasElement = document
|
|
|
|
.create_element("canvas")
|
|
|
|
.into_js_result()?
|
|
|
|
.dyn_into()
|
|
|
|
.map_err(|_| "Expected HtmlCanvasElement")?;
|
|
|
|
if let Ok(renderer) = ruffle_render_canvas::WebCanvasRenderBackend::new(&canvas) {
|
|
|
|
return Ok((canvas, Box::new(renderer)));
|
2020-05-02 09:59:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Err("Unable to create renderer".into())
|
|
|
|
}
|
2020-09-09 22:15:02 +00:00
|
|
|
|
|
|
|
pub fn set_panic_handler() {
|
|
|
|
static HOOK_HAS_BEEN_SET: Once = Once::new();
|
|
|
|
HOOK_HAS_BEEN_SET.call_once(|| {
|
|
|
|
std::panic::set_hook(Box::new(|info| {
|
|
|
|
RUFFLE_GLOBAL_PANIC.call_once(|| {
|
|
|
|
let _ = INSTANCES.try_with(|instances| {
|
|
|
|
if let Ok(instances) = instances.try_borrow() {
|
|
|
|
for (_, instance) in instances.iter() {
|
|
|
|
instance.js_player.panic();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
console_error_panic_hook::hook(info);
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
}
|