mirror of
https://github.com/italicsjenga/rust_minifb.git
synced 2024-12-23 11:21:30 +11:00
Wasm websys wip (#239)
* WIP on wasm support Fix canvas not showing by actually placing it in the document Also set the initial title Use the js! macro to draw pixels Draw to the canvas using ImageData It doesn't work because an animation frame needs to be requested somehow * Complete WASM work into a usable state. This works on the previous commits and makes it usable. There is a multi platform example about how to use it here: https://github.com/dc740/minifb-async-examples Co-authored-by: Thomas Versteeg <thomasversteeg@gmx.com>
This commit is contained in:
parent
2bf4c248bf
commit
f42f516339
35
Cargo.toml
35
Cargo.toml
|
@ -41,6 +41,12 @@ wayland = [
|
|||
"xkb",
|
||||
"xkbcommon-sys",
|
||||
]
|
||||
web = [
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"instant/wasm-bindgen",
|
||||
"instant/inaccurate"
|
||||
]
|
||||
|
||||
[target.'cfg(not(any(target_os = "macos", target_os = "redox", windows)))'.dependencies]
|
||||
wayland-client = { version = "0.29", optional = true }
|
||||
|
@ -55,5 +61,34 @@ xkbcommon-sys = { version = "0.7.5", optional = true }
|
|||
x11-dl = { version = "2.19.1", optional = true }
|
||||
libc = { version = "0.2.107", optional = true }
|
||||
|
||||
|
||||
|
||||
[target.x86_64-unknown-redox.dependencies]
|
||||
orbclient = "0.3.20"
|
||||
|
||||
|
||||
# The `wasm-bindgen` crate provides the bare minimum functionality needed
|
||||
# to interact with JavaScript.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
|
||||
version = "0.2.79"
|
||||
optional = true
|
||||
features = ["serde-serialize"]
|
||||
|
||||
# The `web-sys` crate allows you to interact with the various browser APIs,
|
||||
# like the DOM.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
|
||||
version = "0.3.56"
|
||||
optional = true
|
||||
features = [
|
||||
"console", "Window", "Document", "Navigator", "Element", "Node", "ImageData", "HtmlCanvasElement", "HtmlImageElement", "CanvasRenderingContext2d",
|
||||
"Headers", "Request", "RequestInit", "RequestMode", "Response", "Blob", "Url", "Gamepad", "GamepadButton", "GamepadEvent", 'MouseEvent', 'KeyboardEvent',
|
||||
]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
instant = { version = "0.1.12", features = [ "wasm-bindgen", "inaccurate" ] }
|
||||
js-sys = "0.3.56"
|
||||
wasm-bindgen-futures = "0.4.29"
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
serde_derive = "1.0.123"
|
||||
futures = "0.3.12"
|
||||
|
||||
|
|
8
Web.toml
Normal file
8
Web.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
# The default value of `--target` used when building this crate
|
||||
# in cases where it's not specified on the command line.
|
||||
default-target = "wasm32-unknown-unknown"
|
||||
|
||||
[cargo-web]
|
||||
# Asserts the minimum required version of `cargo-web` necessary
|
||||
# to compile this crate; supported since 0.6.0.
|
||||
minimum-version = "0.6.0"
|
33
build.rs
33
build.rs
|
@ -1,12 +1,33 @@
|
|||
use std::env;
|
||||
extern crate cc;
|
||||
|
||||
//cargo build --target=wasm32-unknown-unknown --verbose --no-default-features --features web
|
||||
|
||||
fn main() {
|
||||
if cfg!(not(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
target_os = "redox"
|
||||
))) && cfg!(not(any(feature = "wayland", feature = "x11")))
|
||||
/*
|
||||
println!("Environment configuration:");
|
||||
for (key, value) in env::vars() {
|
||||
if key.starts_with("CARGO_CFG_") {
|
||||
println!("{}: {:?}", key, value);
|
||||
}
|
||||
}
|
||||
println!("OS: {:?}", env::var("OS").unwrap_or("".to_string()));
|
||||
println!("FAMILY: {:?}", env::var("FAMILY").unwrap_or("".to_string()));
|
||||
println!("ARCH: {:?}", env::var("ARCH").unwrap_or("".to_string()));
|
||||
println!("TARGET: {:?}", env::var("TARGET").unwrap_or("".to_string()));
|
||||
*/
|
||||
// target_arch is not working? OS FAMILY and ARCH variables were empty too
|
||||
// I think the cross-compilation is broken. We could take these from the environment,
|
||||
// since the build script seems to have a different target_arch than the destination.
|
||||
let target = env::var("TARGET").unwrap_or("".to_string());
|
||||
if target != "wasm32-unknown-unknown"
|
||||
&& cfg!(not(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
target_os = "redox",
|
||||
target_arch = "wasm32", // this is ignored. Why?
|
||||
)))
|
||||
&& cfg!(not(any(feature = "wayland", feature = "x11")))
|
||||
{
|
||||
panic!("At least one of the x11 or wayland features must be enabled");
|
||||
}
|
||||
|
@ -21,7 +42,7 @@ fn main() {
|
|||
.compile("libminifb_native.a");
|
||||
println!("cargo:rustc-link-lib=framework=Metal");
|
||||
println!("cargo:rustc-link-lib=framework=MetalKit");
|
||||
} else if !env.contains("windows") {
|
||||
} else if !env.contains("windows") && !env.contains("wasm32") {
|
||||
// build scalar on non-windows and non-mac
|
||||
cc::Build::new()
|
||||
.file("src/native/posix/scalar.cpp")
|
||||
|
|
11
src/error.rs
11
src/error.rs
|
@ -37,3 +37,14 @@ impl fmt::Debug for Error {
|
|||
}
|
||||
|
||||
impl StdError for Error {}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl From<wasm_bindgen::JsValue> for Error {
|
||||
fn from(js_value: wasm_bindgen::JsValue) -> Self {
|
||||
Error::UpdateFailed(
|
||||
js_value
|
||||
.as_string()
|
||||
.unwrap_or("Non string error.".to_string()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
#[cfg(feature = "web")]
|
||||
extern crate instant;
|
||||
|
||||
use crate::{InputCallback, Key, KeyRepeat};
|
||||
#[cfg(feature = "web")]
|
||||
use instant::{Duration, Instant};
|
||||
use std::mem;
|
||||
#[cfg(not(feature = "web"))]
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct KeyHandler {
|
||||
|
|
|
@ -124,6 +124,8 @@ use self::os::macos as imp;
|
|||
use self::os::posix as imp;
|
||||
#[cfg(target_os = "redox")]
|
||||
use self::os::redox as imp;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use self::os::wasm as imp;
|
||||
#[cfg(target_os = "windows")]
|
||||
use self::os::windows as imp;
|
||||
///
|
||||
|
@ -804,7 +806,7 @@ impl Window {
|
|||
/// Get POSIX menus. Will only return menus on POSIX-like OSes like Linux or BSD
|
||||
/// otherwise ```None```
|
||||
///
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_arch = "wasm32"))]
|
||||
pub fn get_posix_menus(&self) -> Option<&Vec<UnixMenu>> {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -10,5 +10,7 @@ pub mod macos;
|
|||
pub mod posix;
|
||||
#[cfg(target_os = "redox")]
|
||||
pub mod redox;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
|
|
230
src/os/wasm/keycodes.rs
Normal file
230
src/os/wasm/keycodes.rs
Normal file
|
@ -0,0 +1,230 @@
|
|||
use crate::key::Key;
|
||||
pub const BACKSPACE: &'static str = "Backspace";
|
||||
pub const TAB: &'static str = "Tab";
|
||||
pub const ENTER: &'static str = "Enter";
|
||||
pub const SHIFT_LEFT: &'static str = "ShiftLeft";
|
||||
pub const SHIFT_RIGHT: &'static str = "ShiftRight";
|
||||
pub const CONTROL_LEFT: &'static str = "ControlLeft";
|
||||
pub const CONTROL_RIGHT: &'static str = "ControlRight";
|
||||
pub const ALT_LEFT: &'static str = "AltLeft";
|
||||
pub const ALT_RIGHT: &'static str = "AltRight";
|
||||
pub const PAUSE: &'static str = "Pause";
|
||||
pub const CAPS_LOCK: &'static str = "CapsLock";
|
||||
pub const ESCAPE: &'static str = "Escape";
|
||||
pub const SPACE: &'static str = "Space";
|
||||
pub const PAGE_UP: &'static str = "PageUp";
|
||||
pub const PAGE_DOWN: &'static str = "PageDown";
|
||||
pub const END: &'static str = "End";
|
||||
pub const HOME: &'static str = "Home";
|
||||
pub const ARROW_LEFT: &'static str = "ArrowLeft";
|
||||
pub const ARROW_UP: &'static str = "ArrowUp";
|
||||
pub const ARROW_RIGHT: &'static str = "ArrowRight";
|
||||
pub const ARROW_DOWN: &'static str = "ArrowDown";
|
||||
//pub const PRINT_SCREEN: &'static str = "PrintScreen";
|
||||
pub const INSERT: &'static str = "Insert";
|
||||
pub const DELETE: &'static str = "Delete";
|
||||
pub const DIGIT_0: &'static str = "Digit0";
|
||||
pub const DIGIT_1: &'static str = "Digit1";
|
||||
pub const DIGIT_2: &'static str = "Digit2";
|
||||
pub const DIGIT_3: &'static str = "Digit3";
|
||||
pub const DIGIT_4: &'static str = "Digit4";
|
||||
pub const DIGIT_5: &'static str = "Digit5";
|
||||
pub const DIGIT_6: &'static str = "Digit6";
|
||||
pub const DIGIT_7: &'static str = "Digit7";
|
||||
pub const DIGIT_8: &'static str = "Digit8";
|
||||
pub const DIGIT_9: &'static str = "Digit9";
|
||||
pub const KEY_A: &'static str = "KeyA";
|
||||
pub const KEY_B: &'static str = "KeyB";
|
||||
pub const KEY_C: &'static str = "KeyC";
|
||||
pub const KEY_D: &'static str = "KeyD";
|
||||
pub const KEY_E: &'static str = "KeyE";
|
||||
pub const KEY_F: &'static str = "KeyF";
|
||||
pub const KEY_G: &'static str = "KeyG";
|
||||
pub const KEY_H: &'static str = "KeyH";
|
||||
pub const KEY_I: &'static str = "KeyI";
|
||||
pub const KEY_J: &'static str = "KeyJ";
|
||||
pub const KEY_K: &'static str = "KeyK";
|
||||
pub const KEY_L: &'static str = "KeyL";
|
||||
pub const KEY_M: &'static str = "KeyM";
|
||||
pub const KEY_N: &'static str = "KeyN";
|
||||
pub const KEY_O: &'static str = "KeyO";
|
||||
pub const KEY_P: &'static str = "KeyP";
|
||||
pub const KEY_Q: &'static str = "KeyQ";
|
||||
pub const KEY_R: &'static str = "KeyR";
|
||||
pub const KEY_S: &'static str = "KeyS";
|
||||
pub const KEY_T: &'static str = "KeyT";
|
||||
pub const KEY_U: &'static str = "KeyU";
|
||||
pub const KEY_V: &'static str = "KeyV";
|
||||
pub const KEY_W: &'static str = "KeyW";
|
||||
pub const KEY_X: &'static str = "KeyX";
|
||||
pub const KEY_Y: &'static str = "KeyY";
|
||||
pub const KEY_Z: &'static str = "KeyZ";
|
||||
//pub const META_LEFT: &'static str = "MetaLeft";
|
||||
//pub const META_RIGHT: &'static str = "MetaRight";
|
||||
//pub const CONTEXT_MENU: &'static str = "ContextMenu";
|
||||
pub const NUMPAD_0: &'static str = "Numpad0";
|
||||
pub const NUMPAD_1: &'static str = "Numpad1";
|
||||
pub const NUMPAD_2: &'static str = "Numpad2";
|
||||
pub const NUMPAD_3: &'static str = "Numpad3";
|
||||
pub const NUMPAD_4: &'static str = "Numpad4";
|
||||
pub const NUMPAD_5: &'static str = "Numpad5";
|
||||
pub const NUMPAD_6: &'static str = "Numpad6";
|
||||
pub const NUMPAD_7: &'static str = "Numpad7";
|
||||
pub const NUMPAD_8: &'static str = "Numpad8";
|
||||
pub const NUMPAD_9: &'static str = "Numpad9";
|
||||
pub const NUMPAD_MULTIPLY: &'static str = "NumpadMultiply";
|
||||
pub const NUMPAD_ADD: &'static str = "NumpadAdd";
|
||||
pub const NUMPAD_SUBTRACT: &'static str = "NumpadSubtract";
|
||||
pub const NUMPAD_DECIMAL: &'static str = "NumpadDecimal";
|
||||
pub const NUMPAD_DIVIDE: &'static str = "NumpadDivide";
|
||||
pub const F1: &'static str = "F1";
|
||||
pub const F2: &'static str = "F2";
|
||||
pub const F3: &'static str = "F3";
|
||||
pub const F4: &'static str = "F4";
|
||||
pub const F5: &'static str = "F5";
|
||||
pub const F6: &'static str = "F6";
|
||||
pub const F7: &'static str = "F7";
|
||||
pub const F8: &'static str = "F8";
|
||||
pub const F9: &'static str = "F9";
|
||||
pub const F10: &'static str = "F10";
|
||||
pub const F11: &'static str = "F11";
|
||||
pub const F12: &'static str = "F12";
|
||||
pub const NUM_LOCK: &'static str = "NumLock";
|
||||
pub const SCROLL_LOCK: &'static str = "ScrollLock";
|
||||
//pub const VOLUME_MUTE: &'static str = "VolumeMute";
|
||||
//pub const VOLUME_DOWN: &'static str = "VolumeDown";
|
||||
//pub const VOLUME_UP: &'static str = "VolumeUp";
|
||||
//pub const MEDIA_SELECT: &'static str = "MediaSelect";
|
||||
//pub const LAUNCH_APP1: &'static str = "LaunchApp1";
|
||||
//pub const LAUNCH_APP2: &'static str = "LaunchApp2";
|
||||
pub const SEMICOLON: &'static str = "Semicolon";
|
||||
pub const EQUAL: &'static str = "Equal";
|
||||
pub const COMMA: &'static str = "Comma";
|
||||
pub const MINUS: &'static str = "Minus";
|
||||
pub const PERIOD: &'static str = "Period";
|
||||
pub const SLASH: &'static str = "Slash";
|
||||
pub const BACKQUOTE: &'static str = "Backquote";
|
||||
pub const BRACKET_LEFT: &'static str = "BracketLeft";
|
||||
pub const BACKSLASH: &'static str = "Backslash";
|
||||
pub const BRACKET_RIGHT: &'static str = "BracketRight";
|
||||
pub const QUOTE: &'static str = "Quote";
|
||||
|
||||
pub fn event_to_key(event: &web_sys::KeyboardEvent) -> Key {
|
||||
match event.code().as_str() {
|
||||
BACKSPACE => Key::Backspace,
|
||||
TAB => Key::Tab,
|
||||
ENTER => Key::Enter,
|
||||
SHIFT_LEFT => Key::LeftShift,
|
||||
SHIFT_RIGHT => Key::RightShift,
|
||||
CONTROL_LEFT => Key::LeftCtrl,
|
||||
CONTROL_RIGHT => Key::RightCtrl,
|
||||
ALT_LEFT => Key::LeftAlt,
|
||||
ALT_RIGHT => Key::RightAlt,
|
||||
PAUSE => Key::Pause,
|
||||
CAPS_LOCK => Key::CapsLock,
|
||||
ESCAPE => Key::Escape,
|
||||
SPACE => Key::Space,
|
||||
PAGE_UP => Key::PageUp,
|
||||
PAGE_DOWN => Key::PageDown,
|
||||
END => Key::End,
|
||||
HOME => Key::Home,
|
||||
ARROW_LEFT => Key::Left,
|
||||
ARROW_UP => Key::Up,
|
||||
ARROW_RIGHT => Key::Right,
|
||||
ARROW_DOWN => Key::Down,
|
||||
INSERT => Key::Insert,
|
||||
DELETE => Key::Delete,
|
||||
DIGIT_0 => Key::Key0,
|
||||
DIGIT_1 => Key::Key1,
|
||||
DIGIT_2 => Key::Key2,
|
||||
DIGIT_3 => Key::Key3,
|
||||
DIGIT_4 => Key::Key4,
|
||||
DIGIT_5 => Key::Key5,
|
||||
DIGIT_6 => Key::Key6,
|
||||
DIGIT_7 => Key::Key7,
|
||||
DIGIT_8 => Key::Key8,
|
||||
DIGIT_9 => Key::Key9,
|
||||
KEY_A => Key::A,
|
||||
KEY_B => Key::B,
|
||||
KEY_C => Key::C,
|
||||
KEY_D => Key::D,
|
||||
KEY_E => Key::E,
|
||||
KEY_F => Key::F,
|
||||
KEY_G => Key::G,
|
||||
KEY_H => Key::H,
|
||||
KEY_I => Key::I,
|
||||
KEY_J => Key::J,
|
||||
KEY_K => Key::K,
|
||||
KEY_L => Key::L,
|
||||
KEY_M => Key::M,
|
||||
KEY_N => Key::N,
|
||||
KEY_O => Key::O,
|
||||
KEY_P => Key::P,
|
||||
KEY_Q => Key::Q,
|
||||
KEY_R => Key::R,
|
||||
KEY_S => Key::S,
|
||||
KEY_T => Key::T,
|
||||
KEY_U => Key::U,
|
||||
KEY_V => Key::V,
|
||||
KEY_W => Key::W,
|
||||
KEY_X => Key::X,
|
||||
KEY_Y => Key::Y,
|
||||
KEY_Z => Key::Z,
|
||||
NUMPAD_0 => Key::NumPad0,
|
||||
NUMPAD_1 => Key::NumPad1,
|
||||
NUMPAD_2 => Key::NumPad2,
|
||||
NUMPAD_3 => Key::NumPad3,
|
||||
NUMPAD_4 => Key::NumPad4,
|
||||
NUMPAD_5 => Key::NumPad5,
|
||||
NUMPAD_6 => Key::NumPad6,
|
||||
NUMPAD_7 => Key::NumPad7,
|
||||
NUMPAD_8 => Key::NumPad8,
|
||||
NUMPAD_9 => Key::NumPad9,
|
||||
NUMPAD_MULTIPLY => Key::NumPadAsterisk,
|
||||
NUMPAD_ADD => Key::NumPadPlus,
|
||||
NUMPAD_SUBTRACT => Key::NumPadMinus,
|
||||
NUMPAD_DECIMAL => Key::NumPadDot,
|
||||
NUMPAD_DIVIDE => Key::NumPadSlash,
|
||||
F1 => Key::F1,
|
||||
F2 => Key::F2,
|
||||
F3 => Key::F3,
|
||||
F4 => Key::F4,
|
||||
F5 => Key::F5,
|
||||
F6 => Key::F6,
|
||||
F7 => Key::F7,
|
||||
F8 => Key::F8,
|
||||
F9 => Key::F9,
|
||||
F10 => Key::F10,
|
||||
F11 => Key::F11,
|
||||
F12 => Key::F12,
|
||||
NUM_LOCK => Key::NumLock,
|
||||
SCROLL_LOCK => Key::ScrollLock,
|
||||
SEMICOLON => Key::Semicolon,
|
||||
EQUAL => Key::Equal,
|
||||
COMMA => Key::Comma,
|
||||
MINUS => Key::Minus,
|
||||
PERIOD => Key::Period,
|
||||
SLASH => Key::Slash,
|
||||
BACKQUOTE => Key::Backquote,
|
||||
BRACKET_LEFT => Key::LeftBracket,
|
||||
BACKSLASH => Key::Backslash,
|
||||
BRACKET_RIGHT => Key::RightBracket,
|
||||
QUOTE => Key::Apostrophe,
|
||||
_ => {
|
||||
/*
|
||||
PRINT_SCREEN=> Key::PrintScreen,
|
||||
META_LEFT=> Key::MetaLeft,
|
||||
META_RIGHT=> Key::MetaRight,
|
||||
CONTEXT_MENU=> Key::ContextMenu,
|
||||
VOLUME_MUTE=> Key::VolumeMute,
|
||||
VOLUME_DOWN=> Key::VolumeDown,
|
||||
VOLUME_UP=> Key::VolumeUp,
|
||||
MEDIA_SELECT=> Key::MediaSelect,
|
||||
LAUNCH_APP1=> Key::LaunchApp1,
|
||||
LAUNCH_APP2=> Key::LaunchApp2,
|
||||
*/
|
||||
// ignore other keys
|
||||
Key::Unknown
|
||||
}
|
||||
}
|
||||
}
|
434
src/os/wasm/mod.rs
Normal file
434
src/os/wasm/mod.rs
Normal file
|
@ -0,0 +1,434 @@
|
|||
mod keycodes;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::Clamped;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::ImageData;
|
||||
use web_sys::{window, CanvasRenderingContext2d, HtmlCanvasElement};
|
||||
|
||||
use crate::buffer_helper;
|
||||
use crate::key_handler::KeyHandler;
|
||||
use crate::mouse_handler;
|
||||
use crate::Icon;
|
||||
use crate::InputCallback;
|
||||
use crate::Result;
|
||||
use crate::{CursorStyle, MouseButton, MouseMode};
|
||||
use crate::{Key, KeyRepeat};
|
||||
use crate::{MenuHandle, MenuItem, MenuItemHandle, UnixMenu, UnixMenuItem};
|
||||
use crate::{Scale, WindowOptions};
|
||||
use core;
|
||||
use keycodes::event_to_key;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::os::raw;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(dead_code)] // Only used on 32-bit builds currently
|
||||
pub fn u32_as_u8<'a>(src: &'a [u32]) -> &'a [u8] {
|
||||
unsafe { core::slice::from_raw_parts(src.as_ptr() as *mut u8, src.len() * 4) }
|
||||
}
|
||||
|
||||
struct MouseState {
|
||||
pos: Cell<Option<(i32, i32)>>,
|
||||
//scroll: Cell<Option<(i32, i32)>>,
|
||||
left_button: Cell<bool>,
|
||||
right_button: Cell<bool>,
|
||||
middle_button: Cell<bool>,
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
width: u32,
|
||||
height: u32,
|
||||
bg_color: u32,
|
||||
window_scale: usize,
|
||||
img_data: ImageData,
|
||||
canvas: HtmlCanvasElement,
|
||||
context: Rc<CanvasRenderingContext2d>,
|
||||
mouse_state: Rc<MouseState>,
|
||||
key_handler: Rc<RefCell<KeyHandler>>,
|
||||
menu_counter: MenuHandle,
|
||||
menus: Vec<UnixMenu>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result<Window> {
|
||||
let window_scale = match opts.scale {
|
||||
Scale::X1 => 1,
|
||||
Scale::X2 => 2,
|
||||
Scale::X4 => 4,
|
||||
Scale::X8 => 8,
|
||||
Scale::X16 => 16,
|
||||
Scale::X32 => 32,
|
||||
Scale::FitScreen => 1, //TODO: Resize the canvas and implement this
|
||||
};
|
||||
let document = window().unwrap().document().unwrap();
|
||||
document.set_title(name);
|
||||
|
||||
// Create a canvas element and place it in the window
|
||||
let canvas = document
|
||||
.create_element("canvas")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlCanvasElement>()
|
||||
.unwrap();
|
||||
|
||||
let body = document.body().unwrap();
|
||||
body.append_child(&canvas).unwrap();
|
||||
|
||||
canvas.set_width(width as u32);
|
||||
canvas.set_height(height as u32);
|
||||
// set this to get the keyboard events
|
||||
canvas.set_tab_index(0);
|
||||
|
||||
// Create an image buffer
|
||||
let context: CanvasRenderingContext2d = canvas
|
||||
.get_context("2d")
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.dyn_into::<CanvasRenderingContext2d>()
|
||||
.unwrap();
|
||||
context.set_image_smoothing_enabled(false);
|
||||
let img_data = ImageData::new_with_sw(width as u32, height as u32).unwrap();
|
||||
let context = Rc::new(context);
|
||||
let key_handler = Rc::new(RefCell::new(KeyHandler::new()));
|
||||
let mouse_struct = MouseState {
|
||||
pos: Cell::new(None),
|
||||
//scroll: Cell::new(None),
|
||||
left_button: Cell::new(false),
|
||||
right_button: Cell::new(false),
|
||||
middle_button: Cell::new(false),
|
||||
};
|
||||
let mouse_state = Rc::new(mouse_struct);
|
||||
{
|
||||
let key_handler = key_handler.clone();
|
||||
let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| {
|
||||
event.prevent_default();
|
||||
let key = event_to_key(&event);
|
||||
key_handler.borrow_mut().set_key_state(key, true);
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
canvas.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())?;
|
||||
closure.forget(); // FYI, the closure now lives forevah... evah... evah...
|
||||
}
|
||||
{
|
||||
let key_handler = key_handler.clone();
|
||||
let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| {
|
||||
event.prevent_default();
|
||||
let key = event_to_key(&event);
|
||||
key_handler.borrow_mut().set_key_state(key, false);
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
canvas.add_event_listener_with_callback("keyup", closure.as_ref().unchecked_ref())?;
|
||||
closure.forget(); // FYI, the closure now lives forevah... evah... evah...
|
||||
}
|
||||
{
|
||||
let mouse_state = mouse_state.clone();
|
||||
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
||||
mouse_state
|
||||
.pos
|
||||
.set(Some((event.offset_x() as i32, event.offset_y() as i32)));
|
||||
match event.button() {
|
||||
0 => mouse_state.left_button.set(true),
|
||||
1 => mouse_state.middle_button.set(true),
|
||||
2 => mouse_state.right_button.set(true),
|
||||
_ => (),
|
||||
}
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
canvas
|
||||
.add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref())?;
|
||||
closure.forget();
|
||||
}
|
||||
{
|
||||
let mouse_state = mouse_state.clone();
|
||||
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
||||
mouse_state
|
||||
.pos
|
||||
.set(Some((event.offset_x() as i32, event.offset_y() as i32)));
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
canvas
|
||||
.add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref())?;
|
||||
closure.forget();
|
||||
}
|
||||
{
|
||||
let mouse_state = mouse_state.clone();
|
||||
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
||||
mouse_state
|
||||
.pos
|
||||
.set(Some((event.offset_x() as i32, event.offset_y() as i32)));
|
||||
match event.button() {
|
||||
0 => mouse_state.left_button.set(false),
|
||||
1 => mouse_state.middle_button.set(false),
|
||||
2 => mouse_state.right_button.set(false),
|
||||
_ => (),
|
||||
}
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
canvas.add_event_listener_with_callback("mouseup", closure.as_ref().unchecked_ref())?;
|
||||
closure.forget();
|
||||
}
|
||||
|
||||
let mut window = Window {
|
||||
width: width as u32,
|
||||
height: height as u32,
|
||||
bg_color: 0,
|
||||
window_scale,
|
||||
img_data,
|
||||
canvas,
|
||||
context: context.clone(),
|
||||
key_handler,
|
||||
mouse_state,
|
||||
menu_counter: MenuHandle(0),
|
||||
menus: Vec::new(),
|
||||
};
|
||||
|
||||
window.set_title(name);
|
||||
|
||||
Ok(window)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&mut self, title: &str) {
|
||||
let document = window().unwrap().document().unwrap();
|
||||
document.set_title(title);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_rate(&mut self, rate: Option<std::time::Duration>) {}
|
||||
|
||||
#[inline]
|
||||
pub fn update_rate(&mut self) {}
|
||||
|
||||
#[inline]
|
||||
pub fn get_window_handle(&self) -> *mut raw::c_void {
|
||||
0 as *mut raw::c_void
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn topmost(&self, topmost: bool) {
|
||||
// TODO?
|
||||
}
|
||||
|
||||
pub fn set_background_color(&mut self, bg_color: u32) {
|
||||
self.bg_color = bg_color;
|
||||
}
|
||||
|
||||
pub fn set_cursor_visibility(&mut self, visibility: bool) {
|
||||
//TODO?
|
||||
}
|
||||
|
||||
pub fn update_with_buffer_stride(
|
||||
&mut self,
|
||||
buffer: &[u32],
|
||||
buf_width: usize,
|
||||
buf_height: usize,
|
||||
buf_stride: usize,
|
||||
) -> Result<()> {
|
||||
buffer_helper::check_buffer_size(buf_width, buf_height, buf_width, buffer)?;
|
||||
// scaling not implemented. It's faster to just update the buffer
|
||||
//unsafe { self.scale_buffer(buffer, buf_width, buf_height, buf_stride) };
|
||||
self.update_with_buffer(&buffer).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_with_buffer(&mut self, buffer: &[u32]) -> Result<()> {
|
||||
buffer_helper::check_buffer_size(
|
||||
self.width as usize,
|
||||
self.height as usize,
|
||||
self.window_scale,
|
||||
buffer,
|
||||
)?;
|
||||
let mut data = u32_as_u8(buffer);
|
||||
|
||||
self.img_data = ImageData::new_with_u8_clamped_array_and_sh(
|
||||
Clamped(&mut data),
|
||||
self.width,
|
||||
self.height,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.update();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.key_handler.borrow_mut().update();
|
||||
self.context
|
||||
.put_image_data(&self.img_data, 0.0, 0.0)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_icon(&mut self, icon: Icon) {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&mut self, x: isize, y: isize) {}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (isize, isize) {
|
||||
let (x, y) = (0, 0);
|
||||
(x as isize, y as isize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_size(&self) -> (usize, usize) {
|
||||
(self.width as usize, self.height as usize)
|
||||
}
|
||||
|
||||
pub fn get_mouse_pos(&self, mode: MouseMode) -> Option<(f32, f32)> {
|
||||
if let Some((mouse_x, mouse_y)) = self.mouse_state.pos.get() {
|
||||
mouse_handler::get_pos(
|
||||
mode,
|
||||
mouse_x as f32,
|
||||
mouse_y as f32,
|
||||
self.window_scale as f32,
|
||||
self.width as f32 * self.window_scale as f32,
|
||||
self.height as f32 * self.window_scale as f32,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_unscaled_mouse_pos(&self, mode: MouseMode) -> Option<(f32, f32)> {
|
||||
if let Some((mouse_x, mouse_y)) = self.mouse_state.pos.get() {
|
||||
mouse_handler::get_pos(
|
||||
mode,
|
||||
mouse_x as f32,
|
||||
mouse_y as f32,
|
||||
1.0 as f32,
|
||||
self.width as f32 * self.window_scale as f32,
|
||||
self.height as f32 * self.window_scale as f32,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mouse_down(&self, button: MouseButton) -> bool {
|
||||
match button {
|
||||
MouseButton::Left => self.mouse_state.left_button.get(),
|
||||
MouseButton::Middle => self.mouse_state.middle_button.get(),
|
||||
MouseButton::Right => self.mouse_state.right_button.get(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_scroll_wheel(&self) -> Option<(f32, f32)> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_style(&mut self, cursor: CursorStyle) {}
|
||||
|
||||
pub fn get_keys(&self) -> Vec<Key> {
|
||||
self.key_handler.borrow().get_keys()
|
||||
}
|
||||
|
||||
pub fn get_keys_pressed(&self, repeat: KeyRepeat) -> Vec<Key> {
|
||||
self.key_handler.borrow().get_keys_pressed(repeat)
|
||||
}
|
||||
|
||||
pub fn is_key_down(&self, key: Key) -> bool {
|
||||
self.key_handler.borrow().is_key_down(key)
|
||||
}
|
||||
|
||||
pub fn set_key_repeat_delay(&mut self, delay: f32) {
|
||||
self.key_handler.borrow_mut().set_key_repeat_delay(delay)
|
||||
}
|
||||
|
||||
pub fn set_key_repeat_rate(&mut self, rate: f32) {
|
||||
self.key_handler.borrow_mut().set_key_repeat_rate(rate)
|
||||
}
|
||||
|
||||
pub fn is_key_pressed(&self, key: Key, repeat: KeyRepeat) -> bool {
|
||||
self.key_handler.borrow().is_key_pressed(key, repeat)
|
||||
}
|
||||
|
||||
pub fn is_key_released(&self, key: Key) -> bool {
|
||||
self.key_handler.borrow().is_key_released(key)
|
||||
}
|
||||
|
||||
pub fn set_input_callback(&mut self, callback: Box<dyn InputCallback>) {
|
||||
self.key_handler.borrow_mut().set_input_callback(callback)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_open(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_keys_released(&self) -> Vec<Key> {
|
||||
self.key_handler.borrow().get_keys_released()
|
||||
}
|
||||
pub fn is_active(&mut self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn next_menu_handle(&mut self) -> MenuHandle {
|
||||
let handle = self.menu_counter;
|
||||
self.menu_counter.0 += 1;
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn add_menu(&mut self, menu: &Menu) -> MenuHandle {
|
||||
let handle = self.next_menu_handle();
|
||||
let mut menu = menu.internal.clone();
|
||||
menu.handle = handle;
|
||||
self.menus.push(menu);
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn remove_menu(&mut self, handle: MenuHandle) {
|
||||
self.menus.retain(|ref menu| menu.handle != handle);
|
||||
}
|
||||
|
||||
pub fn is_menu_pressed(&mut self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Menu {
|
||||
pub internal: UnixMenu,
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
pub fn new(name: &str) -> Result<Menu> {
|
||||
Ok(Menu {
|
||||
internal: UnixMenu {
|
||||
handle: MenuHandle(0),
|
||||
item_counter: MenuItemHandle(0),
|
||||
name: name.to_owned(),
|
||||
items: Vec::new(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_sub_menu(&mut self, name: &str, sub_menu: &Menu) {}
|
||||
|
||||
fn next_item_handle(&mut self) -> MenuItemHandle {
|
||||
let handle = self.internal.item_counter;
|
||||
self.internal.item_counter.0 += 1;
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn add_menu_item(&mut self, item: &MenuItem) -> MenuItemHandle {
|
||||
let item_handle = self.next_item_handle();
|
||||
self.internal.items.push(UnixMenuItem {
|
||||
sub_menu: None,
|
||||
handle: self.internal.item_counter,
|
||||
id: item.id,
|
||||
label: item.label.clone(),
|
||||
enabled: item.enabled,
|
||||
key: item.key,
|
||||
modifier: item.modifier,
|
||||
});
|
||||
item_handle
|
||||
}
|
||||
|
||||
pub fn remove_item(&mut self, handle: &MenuItemHandle) {}
|
||||
}
|
||||
|
||||
unsafe impl raw_window_handle::HasRawWindowHandle for Window {
|
||||
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
|
||||
//TODO: assign a different ID to each window
|
||||
let handle = raw_window_handle::WebHandle::empty();
|
||||
raw_window_handle::RawWindowHandle::Web(handle)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,8 @@
|
|||
#[cfg(feature = "web")]
|
||||
extern crate instant;
|
||||
#[cfg(feature = "web")]
|
||||
use instant::{Duration, Instant};
|
||||
#[cfg(not(feature = "web"))]
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct UpdateRate {
|
||||
|
|
Loading…
Reference in a new issue