mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2025-01-11 21:31:29 +11:00
Implement stdweb
backend for web
platform
This commit is contained in:
parent
1596cc5d9e
commit
bb285984da
13
Cargo.toml
13
Cargo.toml
|
@ -13,6 +13,10 @@ categories = ["gui"]
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
use_web-sys = ["web-sys", "wasm-bindgen", "instant/wasm-bindgen"]
|
||||||
|
use_stdweb = ["stdweb", "instant/stdweb"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
instant = "0.1"
|
instant = "0.1"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
|
@ -20,9 +24,6 @@ libc = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||||
|
|
||||||
[features]
|
|
||||||
web_sys = ["web-sys", "wasm-bindgen"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
image = "0.21"
|
image = "0.21"
|
||||||
env_logger = "0.5"
|
env_logger = "0.5"
|
||||||
|
@ -102,9 +103,9 @@ features = [
|
||||||
version = "0.2.45"
|
version = "0.2.45"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies.stdweb]
|
||||||
stdweb = { path = "../stdweb", optional = true }
|
version = "0.4.17"
|
||||||
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
optional = true
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
stdweb = { path = "../stdweb" }
|
stdweb = { path = "../stdweb" }
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
#[cfg(feature = "stdweb")]
|
#[cfg(feature = "use_stdweb")]
|
||||||
use stdweb::web::html_element::CanvasElement;
|
use stdweb::web::html_element::CanvasElement;
|
||||||
|
|
||||||
#[cfg(feature = "stdweb")]
|
#[cfg(feature = "use_stdweb")]
|
||||||
pub trait WindowExtStdweb {
|
pub trait WindowExtStdweb {
|
||||||
fn canvas(&self) -> CanvasElement;
|
fn canvas(&self) -> CanvasElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "web-sys")]
|
#[cfg(feature = "use_web-sys")]
|
||||||
use web_sys::HtmlCanvasElement;
|
use web_sys::HtmlCanvasElement;
|
||||||
|
|
||||||
#[cfg(feature = "web-sys")]
|
#[cfg(feature = "use_web-sys")]
|
||||||
pub trait WindowExtWebSys {
|
pub trait WindowExtWebSys {
|
||||||
fn canvas(&self) -> HtmlCanvasElement;
|
fn canvas(&self) -> HtmlCanvasElement;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,496 +0,0 @@
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use dpi::LogicalPosition;
|
|
||||||
use event::{
|
|
||||||
DeviceId as RootDI, ElementState, Event, KeyboardInput, MouseScrollDelta, StartCause,
|
|
||||||
TouchPhase, WindowEvent,
|
|
||||||
};
|
|
||||||
use event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW};
|
|
||||||
use instant::{Duration, Instant};
|
|
||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
clone::Clone,
|
|
||||||
collections::{vec_deque::IntoIter as VecDequeIter, VecDeque},
|
|
||||||
marker::PhantomData,
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
use stdweb::{
|
|
||||||
traits::*,
|
|
||||||
web::{document, event::*, html_element::CanvasElement, window, TimeoutHandle},
|
|
||||||
};
|
|
||||||
use window::WindowId as RootWI;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId(i32);
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub unsafe fn dummy() -> Self {
|
|
||||||
DeviceId(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
elw: RootELW<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopWindowTarget<T: 'static> {
|
|
||||||
pub(crate) runner: EventLoopRunnerShared<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTarget<T> {
|
|
||||||
fn new() -> Self {
|
|
||||||
EventLoopWindowTarget {
|
|
||||||
runner: EventLoopRunnerShared(Rc::new(ELRShared {
|
|
||||||
runner: RefCell::new(None),
|
|
||||||
events: RefCell::new(VecDeque::new()),
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct EventLoopProxy<T: 'static> {
|
|
||||||
runner: EventLoopRunnerShared<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopProxy<T> {
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
|
|
||||||
self.runner.send_event(Event::UserEvent(event));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopRunnerShared<T>(Rc<ELRShared<T>>);
|
|
||||||
|
|
||||||
impl<T> Clone for EventLoopRunnerShared<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
EventLoopRunnerShared(self.0.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ELRShared<T> {
|
|
||||||
runner: RefCell<Option<EventLoopRunner<T>>>,
|
|
||||||
events: RefCell<VecDeque<Event<T>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EventLoopRunner<T> {
|
|
||||||
control: ControlFlowStatus,
|
|
||||||
is_busy: bool,
|
|
||||||
event_handler: Box<dyn FnMut(Event<T>, &mut ControlFlow)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ControlFlowStatus {
|
|
||||||
Init,
|
|
||||||
WaitUntil {
|
|
||||||
timeout: TimeoutHandle,
|
|
||||||
start: Instant,
|
|
||||||
end: Instant,
|
|
||||||
},
|
|
||||||
Wait {
|
|
||||||
start: Instant,
|
|
||||||
},
|
|
||||||
Poll {
|
|
||||||
timeout: TimeoutHandle,
|
|
||||||
},
|
|
||||||
Exit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ControlFlowStatus {
|
|
||||||
fn to_control_flow(&self) -> ControlFlow {
|
|
||||||
match self {
|
|
||||||
ControlFlowStatus::Init => ControlFlow::Poll, // During the Init loop, the user should get Poll, the default control value
|
|
||||||
ControlFlowStatus::WaitUntil { end, .. } => ControlFlow::WaitUntil(*end),
|
|
||||||
ControlFlowStatus::Wait { .. } => ControlFlow::Wait,
|
|
||||||
ControlFlowStatus::Poll { .. } => ControlFlow::Poll,
|
|
||||||
ControlFlowStatus::Exit => ControlFlow::Exit,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_exit(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
ControlFlowStatus::Exit => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoop<T> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
EventLoop {
|
|
||||||
elw: RootELW {
|
|
||||||
p: EventLoopWindowTarget::new(),
|
|
||||||
_marker: PhantomData,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
|
|
||||||
VecDeque::new().into_iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
|
||||||
MonitorHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<F>(self, mut event_handler: F) -> !
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
let runner = self.elw.p.runner;
|
|
||||||
|
|
||||||
let relw = RootELW {
|
|
||||||
p: EventLoopWindowTarget::new(),
|
|
||||||
_marker: PhantomData,
|
|
||||||
};
|
|
||||||
runner.set_listener(Box::new(move |evt, ctrl| event_handler(evt, &relw, ctrl)));
|
|
||||||
|
|
||||||
let document = &document();
|
|
||||||
add_event(&runner, document, |elrs, _: BlurEvent| {
|
|
||||||
elrs.send_event(Event::WindowEvent {
|
|
||||||
window_id: RootWI(WindowId),
|
|
||||||
event: WindowEvent::Focused(false),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
add_event(&runner, document, |elrs, _: FocusEvent| {
|
|
||||||
elrs.send_event(Event::WindowEvent {
|
|
||||||
window_id: RootWI(WindowId),
|
|
||||||
event: WindowEvent::Focused(true),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
add_event(&runner, document, |elrs, event: KeyDownEvent| {
|
|
||||||
let key = event.key();
|
|
||||||
let mut characters = key.chars();
|
|
||||||
let first = characters.next();
|
|
||||||
let second = characters.next();
|
|
||||||
if let (Some(key), None) = (first, second) {
|
|
||||||
elrs.send_event(Event::WindowEvent {
|
|
||||||
window_id: RootWI(WindowId),
|
|
||||||
event: WindowEvent::ReceivedCharacter(key),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
elrs.send_event(Event::WindowEvent {
|
|
||||||
window_id: RootWI(WindowId),
|
|
||||||
event: WindowEvent::KeyboardInput {
|
|
||||||
device_id: RootDI(unsafe { DeviceId::dummy() }),
|
|
||||||
input: KeyboardInput {
|
|
||||||
scancode: scancode(&event),
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
virtual_keycode: button_mapping(&event),
|
|
||||||
modifiers: keyboard_modifiers_state(&event),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
add_event(&runner, document, |elrs, event: KeyUpEvent| {
|
|
||||||
elrs.send_event(Event::WindowEvent {
|
|
||||||
window_id: RootWI(WindowId),
|
|
||||||
event: WindowEvent::KeyboardInput {
|
|
||||||
device_id: RootDI(unsafe { DeviceId::dummy() }),
|
|
||||||
input: KeyboardInput {
|
|
||||||
scancode: scancode(&event),
|
|
||||||
state: ElementState::Released,
|
|
||||||
virtual_keycode: button_mapping(&event),
|
|
||||||
modifiers: keyboard_modifiers_state(&event),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
stdweb::event_loop(); // TODO: this is only necessary for stdweb emscripten, should it be here?
|
|
||||||
|
|
||||||
// Throw an exception to break out of Rust exceution and use unreachable to tell the
|
|
||||||
// compiler this function won't return, giving it a return type of '!'
|
|
||||||
js! {
|
|
||||||
throw "Using exceptions for control flow, don't mind me. This isn't actually an error!";
|
|
||||||
}
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
||||||
EventLoopProxy {
|
|
||||||
runner: self.elw.p.runner.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window_target(&self) -> &RootELW<T> {
|
|
||||||
&self.elw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register<T: 'static>(elrs: &EventLoopRunnerShared<T>, canvas: &CanvasElement) {
|
|
||||||
add_event(elrs, canvas, |elrs, event: PointerOutEvent| {
|
|
||||||
elrs.send_event(Event::WindowEvent {
|
|
||||||
window_id: RootWI(WindowId),
|
|
||||||
event: WindowEvent::CursorLeft {
|
|
||||||
device_id: RootDI(DeviceId(event.pointer_id())),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
add_event(elrs, canvas, |elrs, event: PointerOverEvent| {
|
|
||||||
elrs.send_event(Event::WindowEvent {
|
|
||||||
window_id: RootWI(WindowId),
|
|
||||||
event: WindowEvent::CursorEntered {
|
|
||||||
device_id: RootDI(DeviceId(event.pointer_id())),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
add_event(elrs, canvas, |elrs, event: PointerMoveEvent| {
|
|
||||||
elrs.send_event(Event::WindowEvent {
|
|
||||||
window_id: RootWI(WindowId),
|
|
||||||
event: WindowEvent::CursorMoved {
|
|
||||||
device_id: RootDI(DeviceId(event.pointer_id())),
|
|
||||||
position: LogicalPosition {
|
|
||||||
x: event.offset_x(),
|
|
||||||
y: event.offset_y(),
|
|
||||||
},
|
|
||||||
modifiers: mouse_modifiers_state(&event),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
add_event(elrs, canvas, |elrs, event: PointerUpEvent| {
|
|
||||||
elrs.send_event(Event::WindowEvent {
|
|
||||||
window_id: RootWI(WindowId),
|
|
||||||
event: WindowEvent::MouseInput {
|
|
||||||
device_id: RootDI(DeviceId(event.pointer_id())),
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
button: mouse_button(&event),
|
|
||||||
modifiers: mouse_modifiers_state(&event),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
add_event(elrs, canvas, |elrs, event: PointerDownEvent| {
|
|
||||||
elrs.send_event(Event::WindowEvent {
|
|
||||||
window_id: RootWI(WindowId),
|
|
||||||
event: WindowEvent::MouseInput {
|
|
||||||
device_id: RootDI(DeviceId(event.pointer_id())),
|
|
||||||
state: ElementState::Released,
|
|
||||||
button: mouse_button(&event),
|
|
||||||
modifiers: mouse_modifiers_state(&event),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
add_event(elrs, canvas, |elrs, event: MouseWheelEvent| {
|
|
||||||
let x = event.delta_x();
|
|
||||||
let y = event.delta_y();
|
|
||||||
let delta = match event.delta_mode() {
|
|
||||||
MouseWheelDeltaMode::Line => MouseScrollDelta::LineDelta(x as f32, y as f32),
|
|
||||||
MouseWheelDeltaMode::Pixel => MouseScrollDelta::PixelDelta(LogicalPosition { x, y }),
|
|
||||||
MouseWheelDeltaMode::Page => return,
|
|
||||||
};
|
|
||||||
elrs.send_event(Event::WindowEvent {
|
|
||||||
window_id: RootWI(WindowId),
|
|
||||||
event: WindowEvent::MouseWheel {
|
|
||||||
device_id: RootDI(DeviceId(0)),
|
|
||||||
delta,
|
|
||||||
phase: TouchPhase::Moved,
|
|
||||||
modifiers: mouse_modifiers_state(&event),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_event<T: 'static, E, F>(
|
|
||||||
elrs: &EventLoopRunnerShared<T>,
|
|
||||||
target: &impl IEventTarget,
|
|
||||||
mut handler: F,
|
|
||||||
) where
|
|
||||||
E: ConcreteEvent,
|
|
||||||
F: FnMut(&EventLoopRunnerShared<T>, E) + 'static,
|
|
||||||
{
|
|
||||||
let elrs = elrs.clone();
|
|
||||||
|
|
||||||
target.add_event_listener(move |event: E| {
|
|
||||||
// Don't capture the event if the events loop has been destroyed
|
|
||||||
match &*elrs.0.runner.borrow() {
|
|
||||||
Some(ref runner) if runner.control.is_exit() => return,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
event.prevent_default();
|
|
||||||
event.stop_propagation();
|
|
||||||
event.cancel_bubble();
|
|
||||||
|
|
||||||
handler(&elrs, event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopRunnerShared<T> {
|
|
||||||
// Set the event callback to use for the event loop runner
|
|
||||||
// This the event callback is a fairly thin layer over the user-provided callback that closes
|
|
||||||
// over a RootEventLoopWindowTarget reference
|
|
||||||
fn set_listener(&self, event_handler: Box<dyn FnMut(Event<T>, &mut ControlFlow)>) {
|
|
||||||
*self.0.runner.borrow_mut() = Some(EventLoopRunner {
|
|
||||||
control: ControlFlowStatus::Init,
|
|
||||||
is_busy: false,
|
|
||||||
event_handler,
|
|
||||||
});
|
|
||||||
self.send_event(Event::NewEvents(StartCause::Init));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an event to the event loop runner
|
|
||||||
//
|
|
||||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
|
||||||
pub fn send_event(&self, event: Event<T>) {
|
|
||||||
// If the event loop is closed, it should discard any new events
|
|
||||||
if self.closed() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if event handling is in process, and then release the borrow on the runner
|
|
||||||
let (start_cause, event_is_start) = match *self.0.runner.borrow() {
|
|
||||||
Some(ref runner) if !runner.is_busy => {
|
|
||||||
if let Event::NewEvents(cause) = event {
|
|
||||||
(cause, true)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
match runner.control {
|
|
||||||
ControlFlowStatus::Init => StartCause::Init,
|
|
||||||
ControlFlowStatus::Poll { .. } => StartCause::Poll,
|
|
||||||
ControlFlowStatus::Wait { start } => StartCause::WaitCancelled {
|
|
||||||
start,
|
|
||||||
requested_resume: None,
|
|
||||||
},
|
|
||||||
ControlFlowStatus::WaitUntil { start, end, .. } => {
|
|
||||||
StartCause::WaitCancelled {
|
|
||||||
start,
|
|
||||||
requested_resume: Some(end),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ControlFlowStatus::Exit => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Events are currently being handled, so queue this one and don't try to
|
|
||||||
// double-process the event queue
|
|
||||||
self.0.events.borrow_mut().push_back(event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut control = self.current_control_flow();
|
|
||||||
// Handle starting a new batch of events
|
|
||||||
//
|
|
||||||
// The user is informed via Event::NewEvents that there is a batch of events to process
|
|
||||||
// However, there is only one of these per batch of events
|
|
||||||
self.handle_event(Event::NewEvents(start_cause), &mut control);
|
|
||||||
if !event_is_start {
|
|
||||||
self.handle_event(event, &mut control);
|
|
||||||
}
|
|
||||||
self.handle_event(Event::EventsCleared, &mut control);
|
|
||||||
self.apply_control_flow(control);
|
|
||||||
// If the event loop is closed, it has been closed this iteration and now the closing
|
|
||||||
// event should be emitted
|
|
||||||
if self.closed() {
|
|
||||||
self.handle_event(Event::LoopDestroyed, &mut control);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle_event takes in events and either queues them or applies a callback
|
|
||||||
//
|
|
||||||
// It should only ever be called from send_event
|
|
||||||
fn handle_event(&self, event: Event<T>, control: &mut ControlFlow) {
|
|
||||||
let closed = self.closed();
|
|
||||||
|
|
||||||
match *self.0.runner.borrow_mut() {
|
|
||||||
Some(ref mut runner) => {
|
|
||||||
// An event is being processed, so the runner should be marked busy
|
|
||||||
runner.is_busy = true;
|
|
||||||
|
|
||||||
(runner.event_handler)(event, control);
|
|
||||||
|
|
||||||
// Maintain closed state, even if the callback changes it
|
|
||||||
if closed {
|
|
||||||
*control = ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// An event is no longer being processed
|
|
||||||
runner.is_busy = false;
|
|
||||||
}
|
|
||||||
// If an event is being handled without a runner somehow, add it to the event queue so
|
|
||||||
// it will eventually be processed
|
|
||||||
_ => self.0.events.borrow_mut().push_back(event),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't take events out of the queue if the loop is closed or the runner doesn't exist
|
|
||||||
// If the runner doesn't exist and this method recurses, it will recurse infinitely
|
|
||||||
if !closed && self.0.runner.borrow().is_some() {
|
|
||||||
// Take an event out of the queue and handle it
|
|
||||||
if let Some(event) = self.0.events.borrow_mut().pop_front() {
|
|
||||||
self.handle_event(event, control);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the new ControlFlow that has been selected by the user
|
|
||||||
// Start any necessary timeouts etc
|
|
||||||
fn apply_control_flow(&self, control_flow: ControlFlow) {
|
|
||||||
let mut control_flow_status = match control_flow {
|
|
||||||
ControlFlow::Poll => {
|
|
||||||
let cloned = self.clone();
|
|
||||||
ControlFlowStatus::Poll {
|
|
||||||
timeout: window().set_clearable_timeout(
|
|
||||||
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ControlFlow::Wait => ControlFlowStatus::Wait {
|
|
||||||
start: Instant::now(),
|
|
||||||
},
|
|
||||||
ControlFlow::WaitUntil(end) => {
|
|
||||||
let cloned = self.clone();
|
|
||||||
let start = Instant::now();
|
|
||||||
let delay = if end <= start {
|
|
||||||
Duration::from_millis(0)
|
|
||||||
} else {
|
|
||||||
end - start
|
|
||||||
};
|
|
||||||
ControlFlowStatus::WaitUntil {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
timeout: window().set_clearable_timeout(
|
|
||||||
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
|
|
||||||
delay.as_millis() as u32,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ControlFlow::Exit => ControlFlowStatus::Exit,
|
|
||||||
};
|
|
||||||
|
|
||||||
match *self.0.runner.borrow_mut() {
|
|
||||||
Some(ref mut runner) => {
|
|
||||||
// Put the new control flow status in the runner, and take out the old one
|
|
||||||
// This way we can safely take ownership of the TimeoutHandle and clear it,
|
|
||||||
// so that we don't get 'ghost' invocations of Poll or WaitUntil from earlier
|
|
||||||
// set_timeout invocations
|
|
||||||
std::mem::swap(&mut runner.control, &mut control_flow_status);
|
|
||||||
match control_flow_status {
|
|
||||||
ControlFlowStatus::Poll { timeout }
|
|
||||||
| ControlFlowStatus::WaitUntil { timeout, .. } => timeout.clear(),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the event loop is currntly closed
|
|
||||||
fn closed(&self) -> bool {
|
|
||||||
match *self.0.runner.borrow() {
|
|
||||||
Some(ref runner) => runner.control.is_exit(),
|
|
||||||
None => false, // If the event loop is None, it has not been intialised yet, so it cannot be closed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current control flow state
|
|
||||||
fn current_control_flow(&self) -> ControlFlow {
|
|
||||||
match *self.0.runner.borrow() {
|
|
||||||
Some(ref runner) => runner.control.to_control_flow(),
|
|
||||||
None => ControlFlow::Poll,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
mod event_loop;
|
|
||||||
mod events;
|
|
||||||
mod window;
|
|
||||||
|
|
||||||
pub use self::event_loop::{
|
|
||||||
register, DeviceId, EventLoop, EventLoopProxy, EventLoopRunnerShared, EventLoopWindowTarget,
|
|
||||||
};
|
|
||||||
pub use self::events::{
|
|
||||||
button_mapping, keyboard_modifiers_state, mouse_button, mouse_modifiers_state, scancode,
|
|
||||||
};
|
|
||||||
pub use self::window::{MonitorHandle, PlatformSpecificWindowBuilderAttributes, Window, WindowId};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct OsError(String);
|
|
||||||
|
|
||||||
impl fmt::Display for OsError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: dpi
|
|
||||||
// TODO: close events (stdweb PR required)
|
|
||||||
// TODO: pointer locking (stdweb PR required)
|
|
||||||
// TODO: mouse wheel events (stdweb PR required)
|
|
||||||
// TODO: key event: .which() (stdweb PR)
|
|
||||||
// TODO: should there be a maximization / fullscreen API?
|
|
|
@ -1,329 +0,0 @@
|
||||||
use super::{register, EventLoopWindowTarget, OsError};
|
|
||||||
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
|
|
||||||
use error::{ExternalError, NotSupportedError, OsError as RootOE};
|
|
||||||
use event::{Event, WindowEvent};
|
|
||||||
use icon::Icon;
|
|
||||||
use monitor::MonitorHandle as RootMH;
|
|
||||||
use platform::stdweb::WindowExtStdweb;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::vec_deque::IntoIter as VecDequeIter;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use stdweb::web::{document, html_element::CanvasElement, window};
|
|
||||||
use stdweb::{traits::*, unstable::TryInto};
|
|
||||||
use window::{CursorIcon, Window as RootWindow, WindowAttributes, WindowId as RootWI};
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct MonitorHandle;
|
|
||||||
|
|
||||||
impl MonitorHandle {
|
|
||||||
pub fn hidpi_factor(&self) -> f64 {
|
|
||||||
1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn position(&self) -> PhysicalPosition {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dimensions(&self) -> PhysicalSize {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(&self) -> Option<String> {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct WindowId;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct PlatformSpecificWindowBuilderAttributes;
|
|
||||||
|
|
||||||
impl WindowId {
|
|
||||||
pub unsafe fn dummy() -> WindowId {
|
|
||||||
WindowId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Window {
|
|
||||||
pub(crate) canvas: CanvasElement,
|
|
||||||
pub(crate) redraw: Box<dyn Fn()>,
|
|
||||||
previous_pointer: RefCell<&'static str>,
|
|
||||||
position: RefCell<LogicalPosition>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub fn new<T>(
|
|
||||||
target: &EventLoopWindowTarget<T>,
|
|
||||||
attr: WindowAttributes,
|
|
||||||
_: PlatformSpecificWindowBuilderAttributes,
|
|
||||||
) -> Result<Self, RootOE> {
|
|
||||||
let element = document()
|
|
||||||
.create_element("canvas")
|
|
||||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
|
|
||||||
let canvas: CanvasElement = element
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
|
|
||||||
document()
|
|
||||||
.body()
|
|
||||||
.ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))?
|
|
||||||
.append_child(&canvas);
|
|
||||||
|
|
||||||
register(&target.runner, &canvas);
|
|
||||||
|
|
||||||
let runner = target.runner.clone();
|
|
||||||
let redraw = Box::new(move || {
|
|
||||||
let runner = runner.clone();
|
|
||||||
window().request_animation_frame(move |_| {
|
|
||||||
runner.send_event(Event::WindowEvent {
|
|
||||||
window_id: RootWI(WindowId),
|
|
||||||
event: WindowEvent::RedrawRequested,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
let window = Window {
|
|
||||||
canvas,
|
|
||||||
redraw,
|
|
||||||
previous_pointer: RefCell::new("auto"),
|
|
||||||
position: RefCell::new(LogicalPosition { x: 0.0, y: 0.0 }),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(inner_size) = attr.inner_size {
|
|
||||||
window.set_inner_size(inner_size);
|
|
||||||
} else {
|
|
||||||
window.set_inner_size(LogicalSize {
|
|
||||||
width: 1024.0,
|
|
||||||
height: 768.0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
window.set_min_inner_size(attr.min_inner_size);
|
|
||||||
window.set_max_inner_size(attr.max_inner_size);
|
|
||||||
window.set_resizable(attr.resizable);
|
|
||||||
window.set_title(&attr.title);
|
|
||||||
window.set_maximized(attr.maximized);
|
|
||||||
window.set_visible(attr.visible);
|
|
||||||
//window.set_transparent(attr.transparent);
|
|
||||||
window.set_decorations(attr.decorations);
|
|
||||||
window.set_always_on_top(attr.always_on_top);
|
|
||||||
window.set_window_icon(attr.window_icon);
|
|
||||||
|
|
||||||
Ok(window)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_title(&self, title: &str) {
|
|
||||||
document().set_title(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_visible(&self, _visible: bool) {
|
|
||||||
// Intentionally a no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_redraw(&self) {
|
|
||||||
(self.redraw)();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
|
|
||||||
let bounds = self.canvas.get_bounding_client_rect();
|
|
||||||
Ok(LogicalPosition {
|
|
||||||
x: bounds.get_x(),
|
|
||||||
y: bounds.get_y(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
|
|
||||||
Ok(*self.position.borrow())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_outer_position(&self, position: LogicalPosition) {
|
|
||||||
*self.position.borrow_mut() = position;
|
|
||||||
self.canvas
|
|
||||||
.set_attribute("position", "fixed")
|
|
||||||
.expect("Setting the position for the canvas");
|
|
||||||
self.canvas
|
|
||||||
.set_attribute("left", &position.x.to_string())
|
|
||||||
.expect("Setting the position for the canvas");
|
|
||||||
self.canvas
|
|
||||||
.set_attribute("top", &position.y.to_string())
|
|
||||||
.expect("Setting the position for the canvas");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn inner_size(&self) -> LogicalSize {
|
|
||||||
LogicalSize {
|
|
||||||
width: self.canvas.width() as f64,
|
|
||||||
height: self.canvas.height() as f64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn outer_size(&self) -> LogicalSize {
|
|
||||||
LogicalSize {
|
|
||||||
width: self.canvas.width() as f64,
|
|
||||||
height: self.canvas.height() as f64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_inner_size(&self, size: LogicalSize) {
|
|
||||||
self.canvas.set_width(size.width as u32);
|
|
||||||
self.canvas.set_height(size.height as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
|
|
||||||
// Intentionally a no-op: users can't resize canvas elements
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize>) {
|
|
||||||
// Intentionally a no-op: users can't resize canvas elements
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_resizable(&self, _resizable: bool) {
|
|
||||||
// Intentionally a no-op: users can't resize canvas elements
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn hidpi_factor(&self) -> f64 {
|
|
||||||
1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
|
||||||
let text = match cursor {
|
|
||||||
CursorIcon::Default => "auto",
|
|
||||||
CursorIcon::Crosshair => "crosshair",
|
|
||||||
CursorIcon::Hand => "pointer",
|
|
||||||
CursorIcon::Arrow => "default",
|
|
||||||
CursorIcon::Move => "move",
|
|
||||||
CursorIcon::Text => "text",
|
|
||||||
CursorIcon::Wait => "wait",
|
|
||||||
CursorIcon::Help => "help",
|
|
||||||
CursorIcon::Progress => "progress",
|
|
||||||
|
|
||||||
CursorIcon::NotAllowed => "not-allowed",
|
|
||||||
CursorIcon::ContextMenu => "context-menu",
|
|
||||||
CursorIcon::Cell => "cell",
|
|
||||||
CursorIcon::VerticalText => "vertical-text",
|
|
||||||
CursorIcon::Alias => "alias",
|
|
||||||
CursorIcon::Copy => "copy",
|
|
||||||
CursorIcon::NoDrop => "no-drop",
|
|
||||||
CursorIcon::Grab => "grab",
|
|
||||||
CursorIcon::Grabbing => "grabbing",
|
|
||||||
CursorIcon::AllScroll => "all-scroll",
|
|
||||||
CursorIcon::ZoomIn => "zoom-in",
|
|
||||||
CursorIcon::ZoomOut => "zoom-out",
|
|
||||||
|
|
||||||
CursorIcon::EResize => "e-resize",
|
|
||||||
CursorIcon::NResize => "n-resize",
|
|
||||||
CursorIcon::NeResize => "ne-resize",
|
|
||||||
CursorIcon::NwResize => "nw-resize",
|
|
||||||
CursorIcon::SResize => "s-resize",
|
|
||||||
CursorIcon::SeResize => "se-resize",
|
|
||||||
CursorIcon::SwResize => "sw-resize",
|
|
||||||
CursorIcon::WResize => "w-resize",
|
|
||||||
CursorIcon::EwResize => "ew-resize",
|
|
||||||
CursorIcon::NsResize => "ns-resize",
|
|
||||||
CursorIcon::NeswResize => "nesw-resize",
|
|
||||||
CursorIcon::NwseResize => "nwse-resize",
|
|
||||||
CursorIcon::ColResize => "col-resize",
|
|
||||||
CursorIcon::RowResize => "row-resize",
|
|
||||||
};
|
|
||||||
*self.previous_pointer.borrow_mut() = text;
|
|
||||||
self.canvas
|
|
||||||
.set_attribute("cursor", text)
|
|
||||||
.expect("Setting the cursor on the canvas");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
|
|
||||||
// TODO: pointer capture
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
|
|
||||||
// TODO: pointer capture
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_visible(&self, visible: bool) {
|
|
||||||
if !visible {
|
|
||||||
self.canvas
|
|
||||||
.set_attribute("cursor", "none")
|
|
||||||
.expect("Setting the cursor on the canvas");
|
|
||||||
} else {
|
|
||||||
self.canvas
|
|
||||||
.set_attribute("cursor", *self.previous_pointer.borrow())
|
|
||||||
.expect("Setting the cursor on the canvas");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_maximized(&self, _maximized: bool) {
|
|
||||||
// TODO: should there be a maximization / fullscreen API?
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn fullscreen(&self) -> Option<RootMH> {
|
|
||||||
// TODO: should there be a maximization / fullscreen API?
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_fullscreen(&self, _monitor: Option<RootMH>) {
|
|
||||||
// TODO: should there be a maximization / fullscreen API?
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_decorations(&self, _decorations: bool) {
|
|
||||||
// Intentionally a no-op, no canvas decorations
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
|
||||||
// Intentionally a no-op, no window ordering
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
|
|
||||||
// Currently an intentional no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_ime_position(&self, _position: LogicalPosition) {
|
|
||||||
// TODO: what is this?
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn current_monitor(&self) -> RootMH {
|
|
||||||
RootMH {
|
|
||||||
inner: MonitorHandle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
|
|
||||||
VecDeque::new().into_iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
|
||||||
MonitorHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn id(&self) -> WindowId {
|
|
||||||
// TODO ?
|
|
||||||
unsafe { WindowId::dummy() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowExtStdweb for RootWindow {
|
|
||||||
fn canvas(&self) -> CanvasElement {
|
|
||||||
self.window.canvas.clone()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,10 +11,14 @@ mod event_loop;
|
||||||
mod monitor;
|
mod monitor;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
#[cfg(feature = "web_sys")]
|
#[cfg(feature = "use_web-sys")]
|
||||||
#[path = "web_sys/mod.rs"]
|
#[path = "web_sys/mod.rs"]
|
||||||
mod backend;
|
mod backend;
|
||||||
|
|
||||||
|
#[cfg(feature = "use_stdweb")]
|
||||||
|
#[path = "stdweb/mod.rs"]
|
||||||
|
mod backend;
|
||||||
|
|
||||||
pub use self::device::Id as DeviceId;
|
pub use self::device::Id as DeviceId;
|
||||||
pub use self::error::OsError;
|
pub use self::error::OsError;
|
||||||
pub use self::event_loop::{
|
pub use self::event_loop::{
|
||||||
|
|
|
@ -1,12 +1,51 @@
|
||||||
pub struct Canvas;
|
use super::event;
|
||||||
|
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||||
|
use crate::error::OsError as RootOE;
|
||||||
|
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||||
|
use crate::platform_impl::OsError;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
use stdweb::traits::IPointerEvent;
|
||||||
|
use stdweb::unstable::TryInto;
|
||||||
|
use stdweb::web::event::{
|
||||||
|
BlurEvent, ConcreteEvent, FocusEvent, KeyDownEvent, KeyPressEvent, KeyUpEvent, MouseWheelEvent,
|
||||||
|
PointerDownEvent, PointerMoveEvent, PointerOutEvent, PointerOverEvent, PointerUpEvent,
|
||||||
|
};
|
||||||
|
use stdweb::web::html_element::CanvasElement;
|
||||||
|
use stdweb::web::{
|
||||||
|
document, window, EventListenerHandle, IChildNode, IElement, IEventTarget, IHtmlElement, INode,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Canvas {
|
||||||
|
raw: CanvasElement,
|
||||||
|
on_redraw: Rc<dyn Fn()>,
|
||||||
|
on_focus: Option<EventListenerHandle>,
|
||||||
|
on_blur: Option<EventListenerHandle>,
|
||||||
|
on_keyboard_release: Option<EventListenerHandle>,
|
||||||
|
on_keyboard_press: Option<EventListenerHandle>,
|
||||||
|
on_received_character: Option<EventListenerHandle>,
|
||||||
|
on_cursor_leave: Option<EventListenerHandle>,
|
||||||
|
on_cursor_enter: Option<EventListenerHandle>,
|
||||||
|
on_cursor_move: Option<EventListenerHandle>,
|
||||||
|
on_mouse_press: Option<EventListenerHandle>,
|
||||||
|
on_mouse_release: Option<EventListenerHandle>,
|
||||||
|
on_mouse_wheel: Option<EventListenerHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Canvas {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.raw.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Canvas {
|
impl Canvas {
|
||||||
pub fn new() -> Self {
|
pub fn create<F>(on_redraw: F) -> Result<Self, RootOE>
|
||||||
let element = document()
|
where
|
||||||
|
F: 'static + Fn(),
|
||||||
|
{
|
||||||
|
let canvas: CanvasElement = document()
|
||||||
.create_element("canvas")
|
.create_element("canvas")
|
||||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
|
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
|
||||||
|
|
||||||
let canvas: CanvasElement = element
|
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
|
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
|
||||||
|
|
||||||
|
@ -15,6 +54,198 @@ impl Canvas {
|
||||||
.ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))?
|
.ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))?
|
||||||
.append_child(&canvas);
|
.append_child(&canvas);
|
||||||
|
|
||||||
Canvas(canvas)
|
// TODO: Set up unique ids
|
||||||
|
canvas
|
||||||
|
.set_attribute("tabindex", "0")
|
||||||
|
.expect("Failed to set a tabindex");
|
||||||
|
|
||||||
|
Ok(Canvas {
|
||||||
|
raw: canvas,
|
||||||
|
on_redraw: Rc::new(on_redraw),
|
||||||
|
on_blur: None,
|
||||||
|
on_focus: None,
|
||||||
|
on_keyboard_release: None,
|
||||||
|
on_keyboard_press: None,
|
||||||
|
on_received_character: None,
|
||||||
|
on_cursor_leave: None,
|
||||||
|
on_cursor_enter: None,
|
||||||
|
on_cursor_move: None,
|
||||||
|
on_mouse_release: None,
|
||||||
|
on_mouse_press: None,
|
||||||
|
on_mouse_wheel: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_attribute(&self, attribute: &str, value: &str) {
|
||||||
|
self.raw
|
||||||
|
.set_attribute(attribute, value)
|
||||||
|
.expect(&format!("Set attribute: {}", attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position(&self) -> (f64, f64) {
|
||||||
|
let bounds = self.raw.get_bounding_client_rect();
|
||||||
|
|
||||||
|
(bounds.get_x(), bounds.get_y())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> f64 {
|
||||||
|
self.raw.width() as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> f64 {
|
||||||
|
self.raw.height() as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_size(&self, size: LogicalSize) {
|
||||||
|
self.raw.set_width(size.width as u32);
|
||||||
|
self.raw.set_height(size.height as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw(&self) -> &CanvasElement {
|
||||||
|
&self.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_redraw(&self) {
|
||||||
|
let on_redraw = self.on_redraw.clone();
|
||||||
|
window().request_animation_frame(move |_| on_redraw());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_blur<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(),
|
||||||
|
{
|
||||||
|
self.on_blur = Some(self.add_event(move |_: BlurEvent| {
|
||||||
|
handler();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_focus<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(),
|
||||||
|
{
|
||||||
|
self.on_focus = Some(self.add_event(move |_: FocusEvent| {
|
||||||
|
handler();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_keyboard_release<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_keyboard_release = Some(self.add_event(move |event: KeyUpEvent| {
|
||||||
|
handler(
|
||||||
|
event::scan_code(&event),
|
||||||
|
event::virtual_key_code(&event),
|
||||||
|
event::keyboard_modifiers(&event),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_keyboard_press<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_keyboard_press = Some(self.add_event(move |event: KeyDownEvent| {
|
||||||
|
handler(
|
||||||
|
event::scan_code(&event),
|
||||||
|
event::virtual_key_code(&event),
|
||||||
|
event::keyboard_modifiers(&event),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_received_character<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(char),
|
||||||
|
{
|
||||||
|
// TODO: Use `beforeinput`.
|
||||||
|
//
|
||||||
|
// The `keypress` event is deprecated, but there does not seem to be a
|
||||||
|
// viable/compatible alternative as of now. `beforeinput` is still widely
|
||||||
|
// unsupported.
|
||||||
|
self.on_received_character = Some(self.add_event(move |event: KeyPressEvent| {
|
||||||
|
handler(event::codepoint(&event));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32),
|
||||||
|
{
|
||||||
|
self.on_cursor_leave = Some(self.add_event(move |event: PointerOutEvent| {
|
||||||
|
handler(event.pointer_id());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32),
|
||||||
|
{
|
||||||
|
self.on_cursor_enter = Some(self.add_event(move |event: PointerOverEvent| {
|
||||||
|
handler(event.pointer_id());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_release<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, MouseButton, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_mouse_release = Some(self.add_event(move |event: PointerUpEvent| {
|
||||||
|
handler(
|
||||||
|
event.pointer_id(),
|
||||||
|
event::mouse_button(&event),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_press<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, MouseButton, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_mouse_press = Some(self.add_event(move |event: PointerDownEvent| {
|
||||||
|
handler(
|
||||||
|
event.pointer_id(),
|
||||||
|
event::mouse_button(&event),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_move<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
|
||||||
|
handler(
|
||||||
|
event.pointer_id(),
|
||||||
|
event::mouse_position(&event),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_mouse_wheel = Some(self.add_event(move |event: MouseWheelEvent| {
|
||||||
|
if let Some(delta) = event::mouse_scroll_delta(&event) {
|
||||||
|
handler(0, delta, event::mouse_modifiers(&event));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_event<E, F>(&self, mut handler: F) -> EventListenerHandle
|
||||||
|
where
|
||||||
|
E: ConcreteEvent,
|
||||||
|
F: 'static + FnMut(E),
|
||||||
|
{
|
||||||
|
self.raw.add_event_listener(move |event: E| {
|
||||||
|
event.stop_propagation();
|
||||||
|
event.cancel_bubble();
|
||||||
|
|
||||||
|
handler(event);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,55 @@
|
||||||
use stdweb::{
|
use crate::dpi::LogicalPosition;
|
||||||
JsSerialize,
|
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||||
web::event::{IKeyboardEvent, IMouseEvent},
|
|
||||||
unstable::TryInto
|
|
||||||
};
|
|
||||||
use event::{MouseButton, ModifiersState, ScanCode, VirtualKeyCode};
|
|
||||||
|
|
||||||
pub fn button_mapping(event: &impl IKeyboardEvent) -> Option<VirtualKeyCode> {
|
use stdweb::web::event::{IKeyboardEvent, IMouseEvent, MouseWheelDeltaMode, MouseWheelEvent};
|
||||||
|
use stdweb::{unstable::TryInto, JsSerialize};
|
||||||
|
|
||||||
|
pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton {
|
||||||
|
match event.button() {
|
||||||
|
stdweb::web::event::MouseButton::Left => MouseButton::Left,
|
||||||
|
stdweb::web::event::MouseButton::Right => MouseButton::Right,
|
||||||
|
stdweb::web::event::MouseButton::Wheel => MouseButton::Middle,
|
||||||
|
stdweb::web::event::MouseButton::Button4 => MouseButton::Other(0),
|
||||||
|
stdweb::web::event::MouseButton::Button5 => MouseButton::Other(1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_modifiers(event: &impl IMouseEvent) -> ModifiersState {
|
||||||
|
ModifiersState {
|
||||||
|
shift: event.shift_key(),
|
||||||
|
ctrl: event.ctrl_key(),
|
||||||
|
alt: event.alt_key(),
|
||||||
|
logo: event.meta_key(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition {
|
||||||
|
LogicalPosition {
|
||||||
|
x: event.offset_x() as f64,
|
||||||
|
y: event.offset_y() as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option<MouseScrollDelta> {
|
||||||
|
let x = event.delta_x();
|
||||||
|
let y = event.delta_y();
|
||||||
|
|
||||||
|
match event.delta_mode() {
|
||||||
|
MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
|
||||||
|
MouseWheelDeltaMode::Pixel => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })),
|
||||||
|
MouseWheelDeltaMode::Page => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scan_code<T: JsSerialize>(event: &T) -> ScanCode {
|
||||||
|
let key_code = js! ( return @{event}.key_code; );
|
||||||
|
|
||||||
|
key_code
|
||||||
|
.try_into()
|
||||||
|
.expect("The which value should be a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn virtual_key_code(event: &impl IKeyboardEvent) -> Option<VirtualKeyCode> {
|
||||||
Some(match &event.code()[..] {
|
Some(match &event.code()[..] {
|
||||||
"Digit1" => VirtualKeyCode::Key1,
|
"Digit1" => VirtualKeyCode::Key1,
|
||||||
"Digit2" => VirtualKeyCode::Key2,
|
"Digit2" => VirtualKeyCode::Key2,
|
||||||
|
@ -164,11 +208,11 @@ pub fn button_mapping(event: &impl IKeyboardEvent) -> Option<VirtualKeyCode> {
|
||||||
"WebSearch" => VirtualKeyCode::WebSearch,
|
"WebSearch" => VirtualKeyCode::WebSearch,
|
||||||
"WebStop" => VirtualKeyCode::WebStop,
|
"WebStop" => VirtualKeyCode::WebStop,
|
||||||
"Yen" => VirtualKeyCode::Yen,
|
"Yen" => VirtualKeyCode::Yen,
|
||||||
_ => return None
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mouse_modifiers_state(event: &impl IMouseEvent) -> ModifiersState {
|
pub fn keyboard_modifiers(event: &impl IKeyboardEvent) -> ModifiersState {
|
||||||
ModifiersState {
|
ModifiersState {
|
||||||
shift: event.shift_key(),
|
shift: event.shift_key(),
|
||||||
ctrl: event.ctrl_key(),
|
ctrl: event.ctrl_key(),
|
||||||
|
@ -177,26 +221,9 @@ pub fn mouse_modifiers_state(event: &impl IMouseEvent) -> ModifiersState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton {
|
pub fn codepoint(event: &impl IKeyboardEvent) -> char {
|
||||||
match event.button() {
|
// `event.key()` always returns a non-empty `String`. Therefore, this should
|
||||||
stdweb::web::event::MouseButton::Left => MouseButton::Left,
|
// never panic.
|
||||||
stdweb::web::event::MouseButton::Right => MouseButton::Right,
|
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
||||||
stdweb::web::event::MouseButton::Wheel => MouseButton::Middle,
|
event.key().chars().next().unwrap()
|
||||||
stdweb::web::event::MouseButton::Button4 => MouseButton::Other(0),
|
|
||||||
stdweb::web::event::MouseButton::Button5 => MouseButton::Other(1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn keyboard_modifiers_state(event: &impl IKeyboardEvent) -> ModifiersState {
|
|
||||||
ModifiersState {
|
|
||||||
shift: event.shift_key(),
|
|
||||||
ctrl: event.ctrl_key(),
|
|
||||||
alt: event.alt_key(),
|
|
||||||
logo: event.meta_key(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scancode<T: JsSerialize>(event: &T) -> ScanCode {
|
|
||||||
let which = js! ( return @{event}.which; );
|
|
||||||
which.try_into().expect("The which value should be a number")
|
|
||||||
}
|
}
|
|
@ -1,6 +1,21 @@
|
||||||
#[cfg(feature = "stdweb")]
|
mod canvas;
|
||||||
impl WindowExtStdweb for RootWindow {
|
mod event;
|
||||||
|
mod timeout;
|
||||||
|
|
||||||
|
pub use self::canvas::Canvas;
|
||||||
|
pub use self::timeout::Timeout;
|
||||||
|
|
||||||
|
use crate::platform::web::WindowExtStdweb;
|
||||||
|
use crate::window::Window;
|
||||||
|
|
||||||
|
use stdweb::web::html_element::CanvasElement;
|
||||||
|
|
||||||
|
pub fn throw(msg: &str) {
|
||||||
|
js! { throw @{msg} }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowExtStdweb for Window {
|
||||||
fn canvas(&self) -> CanvasElement {
|
fn canvas(&self) -> CanvasElement {
|
||||||
self.window.canvas.clone()
|
self.window.canvas().raw().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
25
src/platform_impl/web/stdweb/timeout.rs
Normal file
25
src/platform_impl/web/stdweb/timeout.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
use stdweb::web::{window, IWindowOrWorker, TimeoutHandle};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Timeout {
|
||||||
|
handle: TimeoutHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timeout {
|
||||||
|
pub fn new<F>(f: F, duration: Duration) -> Timeout
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(),
|
||||||
|
{
|
||||||
|
Timeout {
|
||||||
|
handle: window().set_clearable_timeout(f, duration.as_millis() as u32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Timeout {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let handle = std::mem::replace(&mut self.handle, unsafe { std::mem::uninitialized() });
|
||||||
|
handle.clear();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue