1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//! Service to register key press event listeners on elements.

use crate::callback::Callback;
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use std::fmt;
cfg_if! {
    if #[cfg(feature = "std_web")] {
        use stdweb::web::event::{ConcreteEvent, KeyDownEvent, KeyPressEvent, KeyUpEvent};
        use stdweb::web::{EventListenerHandle, IEventTarget};
    } else if #[cfg(feature = "web_sys")] {
        use gloo::events::{EventListener, EventListenerOptions};
        use wasm_bindgen::JsCast;
        use web_sys::{Event, EventTarget, KeyboardEvent};
    }
}

/// Service for registering callbacks on elements to get keystrokes from the user.
///
/// # Note
/// Elements which natively support keyboard input (such as `<input/>` or `<textarea/>`) can use the
/// `onkeypress` or `oninput` attributes from within the html macro. You **should use those events
/// instead** of locating the element and registering an event listener using this service.
///
/// This service is for adding key event listeners to elements which don't support these attributes,
/// (for example the `document` and `<canvas>` elements).
#[derive(Debug)]
pub struct KeyboardService {}

/// Handle for the key event listener.
///
/// When the handle goes out of scope, the listener will be removed from the element.
pub struct KeyListenerHandle(
    #[cfg(feature = "std_web")] Option<EventListenerHandle>,
    #[cfg(feature = "web_sys")] EventListener,
);

impl fmt::Debug for KeyListenerHandle {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("KeyListenerHandle")
    }
}

impl KeyboardService {
    /// Registers a callback which listens to KeyPressEvents on a provided element.
    ///
    /// # Documentation
    /// [keypress event](https://developer.mozilla.org/en-US/docs/Web/API/Document/keypress_event)
    ///
    /// # Warning
    /// This API has been deprecated in the HTML standard and it is not recommended for use in new projects.
    /// Consult the browser compatibility chart in the linked MDN documentation.
    pub fn register_key_press<
        #[cfg(feature = "std_web")] T: IEventTarget,
        #[cfg(feature = "web_sys")] T: AsRef<EventTarget>,
    >(
        element: &T,
        #[cfg(feature = "std_web")] callback: Callback<KeyPressEvent>,
        #[cfg(feature = "web_sys")] callback: Callback<KeyboardEvent>,
    ) -> KeyListenerHandle {
        cfg_match! {
            feature = "std_web" => register_key_impl(element, callback),
            feature = "web_sys" => register_key_impl(element, callback, "keypress"),
        }
    }

    /// Registers a callback which listens to KeyDownEvents on a provided element.
    ///
    /// # Documentation
    /// [keydown event](https://developer.mozilla.org/en-US/docs/Web/API/Document/keydown_event)
    ///
    /// # Note
    /// This browser feature is relatively new and is set to replace the `keypress` event.
    /// It may not be fully supported in all browsers.
    /// Consult the browser compatibility chart in the linked MDN documentation.
    pub fn register_key_down<
        #[cfg(feature = "std_web")] T: IEventTarget,
        #[cfg(feature = "web_sys")] T: AsRef<EventTarget>,
    >(
        element: &T,
        #[cfg(feature = "std_web")] callback: Callback<KeyDownEvent>,
        #[cfg(feature = "web_sys")] callback: Callback<KeyboardEvent>,
    ) -> KeyListenerHandle {
        cfg_match! {
            feature = "std_web" => register_key_impl(element, callback),
            feature = "web_sys" => register_key_impl(element, callback, "keydown"),
        }
    }

    /// Registers a callback that listens to KeyUpEvents on a provided element.
    ///
    /// # Documentation
    /// [keyup event](https://developer.mozilla.org/en-US/docs/Web/API/Document/keyup_event)
    ///
    /// # Note
    /// This browser feature is relatively new and is set to replace keypress events.
    /// It may not be fully supported in all browsers.
    /// Consult the browser compatibility chart in the linked MDN documentation.
    pub fn register_key_up<
        #[cfg(feature = "std_web")] T: IEventTarget,
        #[cfg(feature = "web_sys")] T: AsRef<EventTarget>,
    >(
        element: &T,
        #[cfg(feature = "std_web")] callback: Callback<KeyUpEvent>,
        #[cfg(feature = "web_sys")] callback: Callback<KeyboardEvent>,
    ) -> KeyListenerHandle {
        cfg_match! {
            feature = "std_web" => register_key_impl(element, callback),
            feature = "web_sys" => register_key_impl(element, callback, "keyup"),
        }
    }
}

#[cfg(feature = "std_web")]
fn register_key_impl<T: IEventTarget, E: 'static + ConcreteEvent>(
    element: &T,
    callback: Callback<E>,
) -> KeyListenerHandle {
    let handle = element.add_event_listener(move |event: E| {
        callback.emit(event);
    });
    cfg_match! {
        feature = "std_web" => KeyListenerHandle(Some(handle)),
        feature = "web_sys" => KeyListenerHandle(handle),
    }
}

#[cfg(feature = "web_sys")]
fn register_key_impl<T: AsRef<EventTarget>>(
    element: &T,
    callback: Callback<KeyboardEvent>,
    event: &'static str,
) -> KeyListenerHandle {
    let listener = move |event: &Event| {
        let event = event
            .dyn_ref::<KeyboardEvent>()
            .expect("wrong event type")
            .clone();
        callback.emit(event);
    };
    let options = EventListenerOptions::enable_prevent_default();
    KeyListenerHandle(EventListener::new_with_options(
        element.as_ref(),
        event,
        options,
        listener,
    ))
}

#[cfg(feature = "std_web")]
impl Drop for KeyListenerHandle {
    fn drop(&mut self) {
        if let Some(handle) = self.0.take() {
            handle.remove()
        } else {
            panic!("Tried to drop KeyListenerHandle twice")
        }
    }
}