use webcore::value::{Reference, Value};
use webcore::try_from::{TryFrom, TryInto};
use webapi::event_target::EventTarget;
use webapi::window::Window;
pub trait IEvent: AsRef< Reference > + TryFrom< Value > {
#[inline]
fn bubbles( &self ) -> bool {
js!(
return @{self.as_ref()}.bubbles;
).try_into().unwrap()
}
#[inline]
fn cancel_bubble( &self ) -> bool {
js!(
return @{self.as_ref()}.cancelBubble;
).try_into().unwrap()
}
#[inline]
fn set_cancel_bubble( &self, value: bool ) {
js! { @(no_return)
@{self.as_ref()}.cancelBubble = @{value};
}
}
#[inline]
fn cancelable( &self ) -> bool {
js!(
return @{self.as_ref()}.cancelable;
).try_into().unwrap()
}
#[inline]
fn current_target( &self ) -> Option< EventTarget > {
js!(
return @{self.as_ref()}.currentTarget;
).try_into().ok()
}
#[inline]
fn default_prevented( &self ) -> bool {
js!(
return @{self.as_ref()}.defaultPrevented;
).try_into().unwrap()
}
fn event_phase( &self ) -> EventPhase {
match js!(
return @{self.as_ref()}.eventPhase;
).try_into().unwrap() {
0 => EventPhase::None,
1 => EventPhase::Capturing,
2 => EventPhase::AtTarget,
3 => EventPhase::Bubbling,
_ => unreachable!("Unexpected EventPhase type"),
}
}
#[inline]
fn stop_immediate_propagation( &self ) {
js! { @(no_return)
@{self.as_ref()}.stopImmediatePropagation();
}
}
#[inline]
fn stop_propagation( &self ) {
js! { @(no_return)
@{self.as_ref()}.stopPropagation();
}
}
#[inline]
fn target( &self ) -> Option< EventTarget > {
js!(
return @{self.as_ref()}.target;
).try_into().ok()
}
#[inline]
fn time_stamp( &self ) -> Option< f64 > {
js!(
return @{self.as_ref()}.timeStamp;
).try_into().ok()
}
#[inline]
fn is_trusted( &self ) -> bool {
js!(
return @{self.as_ref()}.isTrusted;
).try_into().unwrap()
}
#[inline]
fn event_type( &self ) -> String {
js!(
return @{self.as_ref()}.type;
).try_into().unwrap()
}
#[inline]
fn prevent_default( &self ) {
js! { @(no_return)
@{self.as_ref()}.preventDefault();
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EventPhase {
None,
Capturing,
AtTarget,
Bubbling,
}
pub trait ConcreteEvent: IEvent {
const EVENT_TYPE: &'static str;
}
pub struct Event( Reference );
impl IEvent for Event {}
reference_boilerplate! {
Event,
instanceof Event
}
pub struct ChangeEvent( Reference );
impl IEvent for ChangeEvent {}
impl ConcreteEvent for ChangeEvent {
const EVENT_TYPE: &'static str = "change";
}
reference_boilerplate! {
ChangeEvent,
instanceof Event
convertible to Event
}
pub trait IUiEvent: IEvent {
#[inline]
fn detail( &self ) -> i32 {
js!(
return @{self.as_ref()}.detail;
).try_into().unwrap()
}
#[inline]
fn view( &self ) -> Option< Window > {
js!(
return @{self.as_ref()}.view;
).try_into().ok()
}
}
pub struct UiEvent( Reference );
impl IEvent for UiEvent {}
impl IUiEvent for UiEvent {}
reference_boilerplate! {
UiEvent,
instanceof UIEvent
convertible to Event
}
pub struct LoadEvent( Reference );
impl IEvent for LoadEvent {}
impl IUiEvent for LoadEvent {}
impl ConcreteEvent for LoadEvent {
const EVENT_TYPE: &'static str = "load";
}
reference_boilerplate! {
LoadEvent,
instanceof UiEvent
convertible to Event
convertible to UiEvent
}
pub trait IMouseEvent: IUiEvent {
#[inline]
fn alt_key( &self ) -> bool {
js!(
return @{self.as_ref()}.altKey;
).try_into().unwrap()
}
fn button( &self ) -> MouseButton {
match js!(
return @{self.as_ref()}.button;
).try_into().unwrap() {
0 => MouseButton::Left,
1 => MouseButton::Wheel,
2 => MouseButton::Right,
3 => MouseButton::Button4,
4 => MouseButton::Button5,
_ => unreachable!("Unexpected MouseEvent.button value"),
}
}
fn buttons( &self ) -> MouseButtonsState {
MouseButtonsState(
js!(
return @{self.as_ref()}.buttons;
).try_into().unwrap()
)
}
#[inline]
fn client_x( &self ) -> f64 {
js!(
return @{self.as_ref()}.clientX;
).try_into().unwrap()
}
#[inline]
fn client_y( &self ) -> f64 {
js!(
return @{self.as_ref()}.clientY;
).try_into().unwrap()
}
#[inline]
fn ctrl_key( &self ) -> bool {
js!(
return @{self.as_ref()}.ctrlKey;
).try_into().unwrap()
}
#[inline]
fn get_modifier_state( &self, key: ModifierKey ) -> bool {
get_event_modifier_state( self, key )
}
#[inline]
fn meta_key( &self ) -> bool {
js!(
return @{self.as_ref()}.metaKey;
).try_into().unwrap()
}
#[inline]
fn movement_x( &self ) -> f64 {
js!(
return @{self.as_ref()}.movementX;
).try_into().unwrap()
}
#[inline]
fn movement_y( &self ) -> f64 {
js!(
return @{self.as_ref()}.movementY;
).try_into().unwrap()
}
#[inline]
fn region( &self ) -> Option< String > {
js!(
return @{self.as_ref()}.region;
).try_into().ok()
}
#[inline]
fn related_target( &self ) -> Option< EventTarget > {
js!(
return @{self.as_ref()}.relatedTarget;
).try_into().ok()
}
#[inline]
fn screen_x( &self ) -> f64 {
js!(
return @{self.as_ref()}.screenX;
).try_into().unwrap()
}
#[inline]
fn screen_y( &self ) -> f64 {
js!(
return @{self.as_ref()}.screenY;
).try_into().unwrap()
}
#[inline]
fn shift_key( &self ) -> bool {
js!(
return @{self.as_ref()}.shiftKey;
).try_into().unwrap()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MouseButton {
Left,
Wheel,
Right,
Button4,
Button5,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct MouseButtonsState(u8);
impl MouseButtonsState {
pub fn is_down(&self, button: MouseButton) -> bool {
match button {
MouseButton::Left => self.0 & 0b1 != 0,
MouseButton::Right => self.0 & 0b10 != 0,
MouseButton::Wheel => self.0 & 0b100 != 0,
MouseButton::Button4 => self.0 & 0b1000 != 0,
MouseButton::Button5 => self.0 & 0b1_0000 != 0,
}
}
}
pub struct MouseEvent( Reference );
impl IEvent for MouseEvent {}
impl IUiEvent for MouseEvent {}
impl IMouseEvent for MouseEvent {}
reference_boilerplate! {
MouseEvent,
instanceof MouseEvent
convertible to Event
convertible to UiEvent
}
pub struct ClickEvent( Reference );
impl IEvent for ClickEvent {}
impl IUiEvent for ClickEvent {}
impl IMouseEvent for ClickEvent {}
impl ConcreteEvent for ClickEvent {
const EVENT_TYPE: &'static str = "click";
}
reference_boilerplate! {
ClickEvent,
instanceof MouseEvent
convertible to Event
convertible to UiEvent
convertible to MouseEvent
}
pub struct DoubleClickEvent( Reference );
impl IEvent for DoubleClickEvent {}
impl IUiEvent for DoubleClickEvent {}
impl IMouseEvent for DoubleClickEvent {}
impl ConcreteEvent for DoubleClickEvent {
const EVENT_TYPE: &'static str = "dblclick";
}
reference_boilerplate! {
DoubleClickEvent,
instanceof MouseEvent
convertible to Event
convertible to UiEvent
convertible to MouseEvent
}
pub trait IKeyboardEvent: IEvent {
#[inline]
fn alt_key( &self ) -> bool {
js!(
return @{self.as_ref()}.altKey;
).try_into().unwrap()
}
#[inline]
fn code( &self ) -> String {
js!(
return @{self.as_ref()}.code;
).try_into().unwrap()
}
#[inline]
fn ctrl_key( &self ) -> bool {
js!(
return @{self.as_ref()}.ctrlKey;
).try_into().unwrap()
}
#[inline]
fn get_modifier_state( &self, key: ModifierKey ) -> bool {
get_event_modifier_state( self, key )
}
#[inline]
fn is_composing( &self ) -> bool {
js!(
return @{self.as_ref()}.isComposing;
).try_into().unwrap()
}
fn location( &self ) -> KeyboardLocation {
match js!(
return @{self.as_ref()}.location;
).try_into().unwrap() {
0 => KeyboardLocation::Standard,
1 => KeyboardLocation::Left,
2 => KeyboardLocation::Right,
3 => KeyboardLocation::Numpad,
4 => KeyboardLocation::Mobile,
5 => KeyboardLocation::Joystick,
_ => unreachable!("Unexpected KeyboardEvent.location value"),
}
}
#[inline]
fn key( &self ) -> String {
js!(
return @{self.as_ref()}.key;
).into_string().unwrap()
}
#[inline]
fn meta_key( &self ) -> bool {
js!(
return @{self.as_ref()}.metaKey;
).try_into().unwrap()
}
#[inline]
fn repeat( &self ) -> bool {
js!(
return @{self.as_ref()}.repeat;
).try_into().unwrap()
}
#[inline]
fn shift_key( &self ) -> bool {
js!(
return @{self.as_ref()}.shiftKey;
).try_into().unwrap()
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ModifierKey {
Alt,
AltGr,
CapsLock,
Ctrl,
Function,
FunctionLock,
Hyper,
Meta,
NumLock,
OS,
ScrollLock,
Shift,
Super,
Symbol,
SymbolLock,
}
fn get_event_modifier_state< T: IEvent >( event: &T, key: ModifierKey ) -> bool {
js!(
return @{event.as_ref()}.getModifierState( @{
match key {
ModifierKey::Alt => "Alt",
ModifierKey::AltGr => "AltGraph",
ModifierKey::CapsLock => "CapsLock",
ModifierKey::Ctrl => "Control",
ModifierKey::Function => "Fn",
ModifierKey::FunctionLock => "FnLock",
ModifierKey::Hyper => "Hyper",
ModifierKey::Meta => "Meta",
ModifierKey::NumLock => "NumLock",
ModifierKey::OS => "OS",
ModifierKey::ScrollLock => "ScrollLock",
ModifierKey::Shift => "Shift",
ModifierKey::Super => "Super",
ModifierKey::Symbol => "Symbol",
ModifierKey::SymbolLock => "SymbolLock",
}
} );
).try_into().unwrap()
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum KeyboardLocation {
Standard,
Left,
Right,
Numpad,
Mobile,
Joystick,
}
pub struct KeyboardEvent( Reference );
impl IEvent for KeyboardEvent {}
impl IKeyboardEvent for KeyboardEvent {}
reference_boilerplate! {
KeyboardEvent,
instanceof KeyboardEvent
convertible to Event
}
pub struct KeypressEvent( Reference );
impl IEvent for KeypressEvent {}
impl IKeyboardEvent for KeypressEvent {}
impl ConcreteEvent for KeypressEvent {
const EVENT_TYPE: &'static str = "keypress";
}
reference_boilerplate! {
KeypressEvent,
instanceof KeyboardEvent
convertible to Event
convertible to KeyboardEvent
}
pub trait IFocusEvent: IEvent {
#[inline]
fn related_target( &self ) -> Option< EventTarget > {
js!(
return @{self.as_ref()}.relatedTarget;
).try_into().ok()
}
}
pub struct FocusRelatedEvent( Reference );
impl IEvent for FocusRelatedEvent {}
impl IFocusEvent for FocusRelatedEvent {}
reference_boilerplate! {
FocusRelatedEvent,
instanceof FocusEvent
convertible to Event
}
pub struct FocusEvent( Reference );
impl IEvent for FocusEvent {}
impl IFocusEvent for FocusEvent {}
impl ConcreteEvent for FocusEvent {
const EVENT_TYPE: &'static str = "focus";
}
reference_boilerplate! {
FocusEvent,
instanceof FocusEvent
convertible to Event
convertible to FocusRelatedEvent
}
pub struct BlurEvent( Reference );
impl IEvent for BlurEvent {}
impl IFocusEvent for BlurEvent {}
impl ConcreteEvent for BlurEvent {
const EVENT_TYPE: &'static str = "blur";
}
reference_boilerplate! {
BlurEvent,
instanceof FocusEvent
convertible to Event
convertible to FocusRelatedEvent
}
pub struct HashChangeEvent( Reference );
impl IEvent for HashChangeEvent {}
impl ConcreteEvent for HashChangeEvent {
const EVENT_TYPE: &'static str = "hashchange";
}
reference_boilerplate! {
HashChangeEvent,
instanceof HashChangeEvent
convertible to Event
}
impl HashChangeEvent {
#[inline]
pub fn old_url( &self ) -> String {
js!(
return @{self.as_ref()}.oldURL;
).try_into().unwrap()
}
#[inline]
pub fn new_url( &self ) -> String {
js!(
return @{self.as_ref()}.newURL;
).try_into().unwrap()
}
}
pub trait IProgressEvent: IEvent {
#[inline]
fn length_computable( &self ) -> bool {
js!(
return @{self.as_ref()}.lengthComputable;
).try_into().unwrap()
}
#[inline]
fn loaded( &self ) -> u64 {
js!(
return @{self.as_ref()}.loaded;
).try_into().unwrap()
}
#[inline]
fn total( &self ) -> u64 {
js!(
return @{self.as_ref()}.total;
).try_into().unwrap()
}
}
pub struct ProgressRelatedEvent( Reference );
impl IEvent for ProgressRelatedEvent {}
impl IProgressEvent for ProgressRelatedEvent {}
reference_boilerplate! {
ProgressRelatedEvent,
instanceof ProgressEvent
convertible to Event
}
pub struct ProgressEvent( Reference );
impl IEvent for ProgressEvent {}
impl IProgressEvent for ProgressEvent {}
impl ConcreteEvent for ProgressEvent {
const EVENT_TYPE: &'static str = "progress";
}
reference_boilerplate! {
ProgressEvent,
instanceof ProgressEvent
convertible to Event
convertible to ProgressRelatedEvent
}
pub struct LoadStartEvent( Reference );
impl IEvent for LoadStartEvent {}
impl IProgressEvent for LoadStartEvent {}
impl ConcreteEvent for LoadStartEvent {
const EVENT_TYPE: &'static str = "loadstart";
}
reference_boilerplate! {
LoadStartEvent,
instanceof ProgressEvent
convertible to Event
convertible to ProgressRelatedEvent
}
pub struct LoadEndEvent( Reference );
impl IEvent for LoadEndEvent {}
impl IProgressEvent for LoadEndEvent {}
impl ConcreteEvent for LoadEndEvent {
const EVENT_TYPE: &'static str = "loadend";
}
reference_boilerplate! {
LoadEndEvent,
instanceof ProgressEvent
convertible to Event
convertible to ProgressRelatedEvent
}
pub struct AbortEvent( Reference );
impl IEvent for AbortEvent {}
impl ConcreteEvent for AbortEvent {
const EVENT_TYPE: &'static str = "abort";
}
reference_boilerplate! {
AbortEvent,
instanceof Event
convertible to Event
}
pub struct ErrorEvent( Reference );
impl IEvent for ErrorEvent {}
impl ConcreteEvent for ErrorEvent {
const EVENT_TYPE: &'static str = "error";
}
reference_boilerplate! {
ErrorEvent,
instanceof Event
convertible to Event
}
impl ErrorEvent {
#[inline]
pub fn message( &self ) -> String {
return js!(
return @{self.as_ref()}.message;
).try_into().unwrap()
}
#[inline]
pub fn filename( &self ) -> String {
return js!(
return @{self.as_ref()}.filename;
).try_into().unwrap()
}
#[inline]
pub fn lineno( &self ) -> u32 {
return js!(
return @{self.as_ref()}.lineno;
).try_into().unwrap()
}
#[inline]
pub fn colno( &self ) -> u32 {
return js!(
return @{self.as_ref()}.colno;
).try_into().unwrap()
}
}
#[cfg(web_api_tests)]
mod tests {
use super::*;
#[test]
fn test_event() {
let event: Event = js!(
return new Event("dummy")
).try_into().unwrap();
assert_eq!( event.event_type(), "dummy" );
assert_eq!( event.bubbles(), false );
assert!( !event.cancel_bubble() );
assert!( !event.cancelable(), false );
assert!( event.current_target().is_none() );
assert!( !event.default_prevented() );
assert_eq!( event.event_phase(), EventPhase::None );
assert!( event.target().is_none() );
assert!( event.time_stamp().is_some() );
assert!( !event.is_trusted() );
event.stop_immediate_propagation();
event.stop_propagation();
}
#[test]
fn test_change_event() {
let event: ChangeEvent = js!(
return new Event( @{ChangeEvent::EVENT_TYPE} );
).try_into().unwrap();
assert_eq!( event.event_type(), ChangeEvent::EVENT_TYPE );
}
#[test]
fn test_ui_event() {
let event: UiEvent = js!(
return new UIEvent(
@{ClickEvent::EVENT_TYPE},
{
detail: 1,
}
)
).try_into().unwrap();
assert_eq!( event.event_type(), ClickEvent::EVENT_TYPE );
assert_eq!( event.detail(), 1 );
assert!( event.view().is_none() );
}
#[test]
fn test_load_event() {
let event: UiEvent = js!(
return new UIEvent( @{LoadEvent::EVENT_TYPE} );
).try_into().unwrap();
assert_eq!( event.event_type(), LoadEvent::EVENT_TYPE );
}
#[test]
fn test_mouse_event() {
let event: MouseEvent = js!(
return new MouseEvent(
@{ClickEvent::EVENT_TYPE},
{
altKey: false,
button: 2,
buttons: 6,
clientX: 3.0,
clientY: 4.0,
ctrlKey: true,
metaKey: false,
screenX: 1.0,
screenY: 2.0,
shiftKey: true
}
);
).try_into().unwrap();
assert_eq!( event.event_type(), ClickEvent::EVENT_TYPE );
assert_eq!( event.alt_key(), false );
assert_eq!( event.button(), MouseButton::Right );
assert!( !event.buttons().is_down( MouseButton::Left ) );
assert!( event.buttons().is_down( MouseButton::Right ) );
assert!( event.buttons().is_down( MouseButton::Wheel ) );
assert_eq!( event.client_x(), 3.0 );
assert_eq!( event.client_y(), 4.0 );
assert!( event.ctrl_key() );
assert!( !event.get_modifier_state( ModifierKey::Alt ) );
assert!( event.get_modifier_state( ModifierKey::Ctrl ) );
assert!( event.get_modifier_state( ModifierKey::Shift ) );
assert!( !event.meta_key() );
assert_eq!( event.movement_x(), 0.0 );
assert_eq!( event.movement_y(), 0.0 );
assert!( event.region().is_none() );
assert!( event.related_target().is_none() );
assert_eq!( event.screen_x(), 1.0 );
assert_eq!( event.screen_y(), 2.0 );
assert!( event.shift_key() );
}
#[test]
fn test_click_event() {
let event: ClickEvent = js!(
return new MouseEvent( @{ClickEvent::EVENT_TYPE} );
).try_into().unwrap();
assert_eq!( event.event_type(), ClickEvent::EVENT_TYPE );
}
#[test]
fn test_double_click_event() {
let event: DoubleClickEvent = js!(
return new MouseEvent( @{DoubleClickEvent::EVENT_TYPE} );
).try_into().unwrap();
assert_eq!( event.event_type(), DoubleClickEvent::EVENT_TYPE );
}
#[test]
fn test_keyboard_event() {
let event: KeyboardEvent = js!(
return new KeyboardEvent(
@{KeypressEvent::EVENT_TYPE},
{
key: "A",
code: "KeyA",
location: 0,
ctrlKey: true,
shiftKey: false,
altKey: true,
metaKey: false,
repeat: true,
isComposing: false
}
);
).try_into().unwrap();
assert!( event.alt_key() );
assert_eq!( event.code(), "KeyA" );
assert!( event.ctrl_key() );
assert!( event.get_modifier_state( ModifierKey::Alt ) );
assert!( event.get_modifier_state( ModifierKey::Ctrl ) );
assert!( !event.get_modifier_state( ModifierKey::Shift ) );
assert!( !event.is_composing() );
assert_eq!( event.location(), KeyboardLocation::Standard );
assert_eq!( event.key(), "A" );
assert!( !event.meta_key() );
assert!( event.repeat() );
assert!( !event.shift_key() );
}
#[test]
fn test_keypress_event() {
let event: KeypressEvent = js!(
return new KeyboardEvent( @{KeypressEvent::EVENT_TYPE} );
).try_into().unwrap();
assert_eq!( event.event_type(), KeypressEvent::EVENT_TYPE );
}
#[test]
fn test_focus_event() {
let event: FocusEvent = js!(
return new FocusEvent( "focus" );
).try_into().unwrap();
assert_eq!( event.event_type(), "focus" );
assert!( event.related_target().is_none() );
}
#[test]
fn test_blur_event() {
let event: BlurEvent = js!(
return new FocusEvent( @{BlurEvent::EVENT_TYPE} );
).try_into().unwrap();
assert_eq!( event.event_type(), BlurEvent::EVENT_TYPE );
}
#[test]
fn test_hash_change_event() {
let event: HashChangeEvent = js!(
return new HashChangeEvent(
@{HashChangeEvent::EVENT_TYPE},
{
oldURL: "http://test.com#foo",
newURL: "http://test.com#bar"
}
);
).try_into().unwrap();
assert_eq!( event.event_type(), HashChangeEvent::EVENT_TYPE );
assert_eq!( event.old_url(), "http://test.com#foo" );
assert_eq!( event.new_url(), "http://test.com#bar" );
}
#[test]
fn test_progress_event() {
let event: ProgressEvent = js!(
return new ProgressEvent(
@{ProgressEvent::EVENT_TYPE},
{
lengthComputable: true,
loaded: 10,
total: 100,
}
);
).try_into().unwrap();
assert_eq!( event.event_type(), ProgressEvent::EVENT_TYPE );
assert!( event.length_computable() );
assert_eq!( event.loaded(), 10 );
assert_eq!( event.total(), 100 );
}
#[test]
fn test_load_start_event() {
let event: LoadStartEvent = js!(
return new ProgressEvent( @{LoadStartEvent::EVENT_TYPE} );
).try_into().unwrap();
assert_eq!( event.event_type(), LoadStartEvent::EVENT_TYPE );
}
#[test]
fn test_load_end_event() {
let event: LoadEndEvent = js!(
return new ProgressEvent( @{LoadEndEvent::EVENT_TYPE} );
).try_into().unwrap();
assert_eq!( event.event_type(), LoadEndEvent::EVENT_TYPE );
}
#[test]
fn test_abort_event() {
let event: AbortEvent = js!(
return new Event( @{AbortEvent::EVENT_TYPE} );
).try_into().unwrap();
assert_eq!( event.event_type(), AbortEvent::EVENT_TYPE );
}
#[test]
fn test_error_event() {
let event: ErrorEvent = js!(
return new ErrorEvent(
@{ErrorEvent::EVENT_TYPE},
{
message: "Dummy error",
filename: "Dummy.js",
lineno: 5,
colno: 10
}
);
).try_into().unwrap();
assert_eq!( event.event_type(), ErrorEvent::EVENT_TYPE );
assert_eq!( event.message(), "Dummy error".to_string() );
assert_eq!( event.filename(), "Dummy.js".to_string() );
assert_eq!( event.lineno(), 5 );
assert_eq!( event.colno(), 10 );
}
}