use super::event_util::*;
use super::{FlufflError, WindowManager};
use crate::audio::{init_audio_threads, FlufflAudioContext};
pub use js_sys;
pub use wasm_bindgen;
pub use wasm_bindgen::prelude::*;
pub use wasm_bindgen::JsCast;
pub use web_sys::*;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::Arc;
use glow;
use glow::*;
use wasm_bindgen_futures::*;
use web_sys;
use super::{FlufflWindowPtr,FlufflWindowConfigs};
static mut GLOBAL_EVENT_QUEUE: Option<FlufflEvent> = None;
pub fn init_global_event_queue() {
unsafe { GLOBAL_EVENT_QUEUE = Some(FlufflEvent::new()) };
}
#[allow(dead_code)]
pub struct FlufflWindow {
glue_event: Option<FlufflEvent>,
gl: Arc<Box<Context>>,
render_loop: Option<glow::RenderLoop>,
window_width: u32,
window_height: u32,
audio_ctx: Arc<RefCell<FlufflAudioContext>>,
canvas: HtmlCanvasElement,
}
impl FlufflWindow {
fn get_render_loop(&mut self) -> Option<impl glow::HasRenderLoop> {
self.render_loop.take()
}
pub fn main_loop<F, T>(
mut fluffr_window: Self,
aux: T,
core_loop: impl Fn(FlufflWindowPtr, Rc<Cell<bool>>, Rc<RefCell<T>>) -> F + 'static + Copy,
) where
T: 'static,
F: std::future::Future<Output = ()> + 'static,
{
let render_loop = fluffr_window.get_render_loop().unwrap();
let window_ptr = FlufflWindowPtr {
ptr: Arc::new(RefCell::new(fluffr_window)),
};
let aux_ptr = Rc::new(RefCell::new(aux));
render_loop.run(move |running| {
if window_ptr.window_mut_cb(|win| transfer_events(win)) {
let win_ptr_clone = window_ptr.clone();
let local_running = Rc::new(Cell::new(*running));
let local_running_clone = local_running.clone();
let aux_ptr_clone = aux_ptr.clone();
spawn_local(core_loop(win_ptr_clone, local_running_clone, aux_ptr_clone));
*running = local_running.get();
}
});
}
}
impl WindowManager for FlufflWindow {
#[allow(unused_variables)]
fn init(config_xml: &str) -> Result<Self, FlufflError> {
#[allow(unused_unsafe)]
let config_js = unsafe { get_xml_config() };
let xml_text = if config_js.is_empty() == false {
config_js.as_str()
} else {
config_xml
};
let settings = FlufflWindowConfigs::new().parser_config_file(config_xml);
let web_window = web_sys::window().unwrap();
let canvas = web_window
.document()
.unwrap()
.get_element_by_id(settings.canvas_id.as_str())
.unwrap()
.dyn_into::<HtmlCanvasElement>()
.unwrap();
let webgl2_context = canvas
.get_context(settings.webgl_version.as_str())
.unwrap()
.unwrap()
.dyn_into::<WebGl2RenderingContext>()
.unwrap();
let gl = glow::Context::from_webgl2_context(webgl2_context);
let render_loop = glow::RenderLoop::from_request_animation_frame();
match attach_event_handlers(&web_window, &canvas) {
Err(_) => {
return Err(FlufflError::WindowInitError(String::from(
"javascript event listeners failed",
)));
}
_ => (),
}
let window = Self {
window_width: settings.width,
window_height: settings.height,
glue_event: Some(FlufflEvent::new()),
render_loop: Some(render_loop),
gl: Arc::new(Box::new(gl)),
audio_ctx: Arc::new(RefCell::new(FlufflAudioContext::new())),
canvas,
};
init_audio_threads();
init_global_event_queue();
Ok(window)
}
fn get_events(&mut self) -> &mut FlufflEvent {
self.glue_event.as_mut().unwrap()
}
fn width(&self) -> u32 {
self.canvas.width()
}
fn height(&self) -> u32 {
self.canvas.height()
}
fn gl(&self) -> Arc<Box<Context>> {
self.gl.clone()
}
fn audio_context(&self) -> Arc<RefCell<FlufflAudioContext>> {
self.audio_ctx.clone()
}
}
fn attach_event_handlers(window: &Window, canvas: &HtmlCanvasElement) -> Result<(), JsValue> {
canvas.style().set_property("border", "solid")?;
{
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
let (x, y, dx, dy) = (event.x(), event.y(), event.movement_x(), event.movement_y());
let event_queue = unsafe { GLOBAL_EVENT_QUEUE.as_mut().unwrap() };
event_queue.push_event(EventKind::MouseMove { x, y, dx, dy });
}) as Box<dyn FnMut(_)>);
canvas.add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref())?;
closure.forget();
}
{
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
let (x, y) = (event.x(), event.y());
let button = event.button();
let event_queue = unsafe { GLOBAL_EVENT_QUEUE.as_mut().unwrap() };
event_queue.push_event(EventKind::MouseDown {
button_code: get_button_code(button),
x,
y,
});
}) as Box<dyn FnMut(_)>);
canvas.add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref())?;
closure.forget();
}
{
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
let (x, y) = (event.x(), event.y());
let button = event.button();
get_global_event_queue_mut().push_event(EventKind::MouseUp {
button_code: get_button_code(button),
x,
y,
});
}) as Box<dyn FnMut(_)>);
canvas.add_event_listener_with_callback("mouseup", closure.as_ref().unchecked_ref())?;
closure.forget();
}
{
let closure = Closure::wrap(Box::new(move |event: web_sys::Event| {
event.prevent_default();
}) as Box<dyn FnMut(_)>);
canvas.add_event_listener_with_callback("contextmenu", closure.as_ref().unchecked_ref())?;
closure.forget();
}
{
let closure = Closure::wrap(Box::new(move |event: web_sys::WheelEvent| {
let delta_y = (-event.delta_y().signum()) as i32;
get_global_event_queue_mut().push_event(EventKind::MouseWheel {
button_code: MouseCode::WHEEL {
direction: delta_y as i32,
},
});
}) as Box<dyn FnMut(_)>);
canvas.add_event_listener_with_callback("wheel", closure.as_ref().unchecked_ref())?;
closure.forget();
}
{
let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| {
let eq = get_global_event_queue_mut();
match event.code().as_str() {
"KeyA" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_A,
}),
"KeyB" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_B,
}),
"KeyC" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_C,
}),
"KeyD" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_D,
}),
"KeyE" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_E,
}),
"KeyF" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_F,
}),
"KeyG" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_G,
}),
"KeyH" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_H,
}),
"KeyI" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_I,
}),
"KeyJ" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_J,
}),
"KeyK" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_K,
}),
"KeyL" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_L,
}),
"KeyM" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_M,
}),
"KeyN" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_N,
}),
"KeyO" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_O,
}),
"KeyP" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_P,
}),
"KeyQ" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_Q,
}),
"KeyR" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_R,
}),
"KeyS" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_S,
}),
"KeyT" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_T,
}),
"KeyU" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_U,
}),
"KeyV" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_V,
}),
"KeyW" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_W,
}),
"KeyX" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_X,
}),
"KeyY" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_Y,
}),
"KeyZ" => eq.push_event(EventKind::KeyDown {
code: KeyCode::KEY_Z,
}),
"Backquote" => eq.push_event(EventKind::KeyDown {
code: KeyCode::BACK_QUOTE,
}),
"Digit0" => eq.push_event(EventKind::KeyDown {
code: KeyCode::NUM_0,
}),
"Digit1" => eq.push_event(EventKind::KeyDown {
code: KeyCode::NUM_1,
}),
"Digit2" => eq.push_event(EventKind::KeyDown {
code: KeyCode::NUM_2,
}),
"Digit3" => eq.push_event(EventKind::KeyDown {
code: KeyCode::NUM_3,
}),
"Digit4" => eq.push_event(EventKind::KeyDown {
code: KeyCode::NUM_4,
}),
"Digit5" => eq.push_event(EventKind::KeyDown {
code: KeyCode::NUM_5,
}),
"Digit6" => eq.push_event(EventKind::KeyDown {
code: KeyCode::NUM_6,
}),
"Digit7" => eq.push_event(EventKind::KeyDown {
code: KeyCode::NUM_7,
}),
"Digit8" => eq.push_event(EventKind::KeyDown {
code: KeyCode::NUM_8,
}),
"Digit9" => eq.push_event(EventKind::KeyDown {
code: KeyCode::NUM_9,
}),
"Minus" => eq.push_event(EventKind::KeyDown {
code: KeyCode::MINUS,
}),
"Equal" => eq.push_event(EventKind::KeyDown {
code: KeyCode::EQUALS,
}),
"Comma" => eq.push_event(EventKind::KeyDown {
code: KeyCode::COMMA,
}),
"Semicolon" => eq.push_event(EventKind::KeyDown {
code: KeyCode::COLON,
}),
"Quote" => eq.push_event(EventKind::KeyDown {
code: KeyCode::QUOTE,
}),
"Slash" => eq.push_event(EventKind::KeyDown {
code: KeyCode::FORWARD_SLASH,
}),
"Backslash" => eq.push_event(EventKind::KeyDown {
code: KeyCode::BACKSLASH,
}),
"Insert" => eq.push_event(EventKind::KeyDown {
code: KeyCode::INSERT,
}),
"Home" => eq.push_event(EventKind::KeyDown {
code: KeyCode::HOME,
}),
"PageUp" => eq.push_event(EventKind::KeyDown {
code: KeyCode::PAGE_UP,
}),
"PageDown" => eq.push_event(EventKind::KeyDown {
code: KeyCode::PAGE_DOWN,
}),
"End" => eq.push_event(EventKind::KeyDown { code: KeyCode::END }),
"Delete" => eq.push_event(EventKind::KeyDown {
code: KeyCode::DELETE,
}),
"ShiftLeft" => eq.push_event(EventKind::KeyDown {
code: KeyCode::SHIFT_L,
}),
"ShiftRight" => eq.push_event(EventKind::KeyDown {
code: KeyCode::SHIFT_R,
}),
"ArrowUp" => eq.push_event(EventKind::KeyDown {
code: KeyCode::ARROW_UP,
}),
"ArrowLeft" => eq.push_event(EventKind::KeyDown {
code: KeyCode::ARROW_LEFT,
}),
"ArrowRight" => eq.push_event(EventKind::KeyDown {
code: KeyCode::ARROW_RIGHT,
}),
"ArrowDown" => eq.push_event(EventKind::KeyDown {
code: KeyCode::ARROW_DOWN,
}),
"Space" => eq.push_event(EventKind::KeyDown {
code: KeyCode::SPACE,
}),
"Period" => eq.push_event(EventKind::KeyDown {
code: KeyCode::PERIOD,
}),
_ => (),
}
}) as Box<dyn FnMut(_)>);
window.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())?;
closure.forget();
}
{
let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| {
let eq = get_global_event_queue_mut();
match event.code().as_str() {
"KeyA" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_A,
}),
"KeyB" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_B,
}),
"KeyC" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_C,
}),
"KeyD" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_D,
}),
"KeyE" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_E,
}),
"KeyF" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_F,
}),
"KeyG" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_G,
}),
"KeyH" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_H,
}),
"KeyI" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_I,
}),
"KeyJ" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_J,
}),
"KeyK" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_K,
}),
"KeyL" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_L,
}),
"KeyM" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_M,
}),
"KeyN" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_N,
}),
"KeyO" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_O,
}),
"KeyP" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_P,
}),
"KeyQ" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_Q,
}),
"KeyR" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_R,
}),
"KeyS" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_S,
}),
"KeyT" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_T,
}),
"KeyU" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_U,
}),
"KeyV" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_V,
}),
"KeyW" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_W,
}),
"KeyX" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_X,
}),
"KeyY" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_Y,
}),
"KeyZ" => eq.push_event(EventKind::KeyUp {
code: KeyCode::KEY_Z,
}),
"Backquote" => eq.push_event(EventKind::KeyUp {
code: KeyCode::BACK_QUOTE,
}),
"Digit0" => eq.push_event(EventKind::KeyUp {
code: KeyCode::NUM_0,
}),
"Digit1" => eq.push_event(EventKind::KeyUp {
code: KeyCode::NUM_1,
}),
"Digit2" => eq.push_event(EventKind::KeyUp {
code: KeyCode::NUM_2,
}),
"Digit3" => eq.push_event(EventKind::KeyUp {
code: KeyCode::NUM_3,
}),
"Digit4" => eq.push_event(EventKind::KeyUp {
code: KeyCode::NUM_4,
}),
"Digit5" => eq.push_event(EventKind::KeyUp {
code: KeyCode::NUM_5,
}),
"Digit6" => eq.push_event(EventKind::KeyUp {
code: KeyCode::NUM_6,
}),
"Digit7" => eq.push_event(EventKind::KeyUp {
code: KeyCode::NUM_7,
}),
"Digit8" => eq.push_event(EventKind::KeyUp {
code: KeyCode::NUM_8,
}),
"Digit9" => eq.push_event(EventKind::KeyUp {
code: KeyCode::NUM_9,
}),
"Minus" => eq.push_event(EventKind::KeyUp {
code: KeyCode::MINUS,
}),
"Equal" => eq.push_event(EventKind::KeyUp {
code: KeyCode::EQUALS,
}),
"Comma" => eq.push_event(EventKind::KeyUp {
code: KeyCode::COMMA,
}),
"Semicolon" => eq.push_event(EventKind::KeyUp {
code: KeyCode::COLON,
}),
"Quote" => eq.push_event(EventKind::KeyUp {
code: KeyCode::QUOTE,
}),
"Slash" => eq.push_event(EventKind::KeyUp {
code: KeyCode::FORWARD_SLASH,
}),
"Backslash" => eq.push_event(EventKind::KeyUp {
code: KeyCode::BACKSLASH,
}),
"Insert" => eq.push_event(EventKind::KeyUp {
code: KeyCode::INSERT,
}),
"Home" => eq.push_event(EventKind::KeyUp {
code: KeyCode::HOME,
}),
"PageUp" => eq.push_event(EventKind::KeyUp {
code: KeyCode::PAGE_UP,
}),
"PageDown" => eq.push_event(EventKind::KeyUp {
code: KeyCode::PAGE_DOWN,
}),
"End" => eq.push_event(EventKind::KeyUp { code: KeyCode::END }),
"Delete" => eq.push_event(EventKind::KeyUp {
code: KeyCode::DELETE,
}),
"ShiftLeft" => eq.push_event(EventKind::KeyUp {
code: KeyCode::SHIFT_L,
}),
"ShiftRight" => eq.push_event(EventKind::KeyUp {
code: KeyCode::SHIFT_R,
}),
"ArrowUp" => eq.push_event(EventKind::KeyUp {
code: KeyCode::ARROW_UP,
}),
"ArrowLeft" => eq.push_event(EventKind::KeyUp {
code: KeyCode::ARROW_LEFT,
}),
"ArrowRight" => eq.push_event(EventKind::KeyUp {
code: KeyCode::ARROW_RIGHT,
}),
"ArrowDown" => eq.push_event(EventKind::KeyUp {
code: KeyCode::ARROW_DOWN,
}),
"Space" => eq.push_event(EventKind::KeyUp {
code: KeyCode::SPACE,
}),
"Period" => eq.push_event(EventKind::KeyUp {
code: KeyCode::PERIOD,
}),
_ => (),
}
}) as Box<dyn FnMut(_)>);
window.add_event_listener_with_callback("keyup", closure.as_ref().unchecked_ref())?;
closure.forget();
}
Ok(())
}
fn transfer_events(window: &mut FlufflWindow) {
unsafe { GLOBAL_EVENT_QUEUE.as_mut() }
.unwrap()
.iter_mut()
.for_each(|e| {
window.get_events().push_event(e);
});
}
fn get_button_code(button: i16) -> MouseCode {
match button {
0 => MouseCode::LEFT_BUTTON,
1 => MouseCode::WHEEL { direction: 0 },
2 => MouseCode::RIGHT_BUTTON,
_ => MouseCode::WHEEL { direction: 0 },
}
}
fn get_global_event_queue_mut<'a>() -> &'a mut FlufflEvent {
unsafe { GLOBAL_EVENT_QUEUE.as_mut().unwrap() }
}
#[wasm_bindgen(module = "/js/aux_util.js")]
extern "C" {
fn get_xml_config() -> String;
pub fn set_xml_config(xml_text: String);
}