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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
use webcore::try_from::{
    TryFrom,
    TryInto,
};
use webcore::value::{
    ConversionError,
    Reference,
    Value,
};

/// The set of known gamepad layout mappings.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/mapping)
// https://w3c.github.io/gamepad/#dom-gamepadmappingtype
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum GamepadMappingType {
    /// No mapping is in use for this gamepad
    NoMapping,
    /// This gamepad is mapped to the [Standard Gamepad layout](https://w3c.github.io/gamepad/#remapping)
    Standard,
}

impl TryFrom<Value> for GamepadMappingType {
    type Error = ConversionError;

    fn try_from(v: Value) -> Result<Self, Self::Error> {
        let value: String = v.try_into()?;
        match value.as_ref() {
            "" => Ok(GamepadMappingType::NoMapping),
            "standard" => Ok(GamepadMappingType::Standard),
            s => Err(ConversionError::Custom(format!("invalid gamepad mapping type \"{}\"", s))),
        }
    }
}

/// The state of an individual button on a gamepad device.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/GamepadButton)
// https://w3c.github.io/gamepad/#gamepadbutton-interface
#[derive(Clone, Debug, Eq, PartialEq, ReferenceType)]
#[reference(instance_of = "GamepadButton")]
pub struct GamepadButton( Reference );

impl GamepadButton {

    /// Is the button currently pressed?
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/GamepadButton/pressed)
    // https://w3c.github.io/gamepad/#dom-gamepadbutton-pressed
    #[inline]
    pub fn pressed(&self) -> bool {
        js!(
            return @{self.as_ref()}.pressed;
        ).try_into().unwrap()
    }

    /// Is the button currently touched?
    ///
    /// MDN does not document this. Firefox supports it, but Chrome (as of v65) does not.
    // https://w3c.github.io/gamepad/#dom-gamepadbutton-touched
    #[inline]
    pub fn touched(&self) -> bool {
        js!(
            return @{self.as_ref()}.touched;
        ).try_into().unwrap()
    }

    /// The amount which the button has been pressed, between 0.0 (not pressed), and 1.0 (fully pressed).
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/GamepadButton/value)
    // https://w3c.github.io/gamepad/#dom-gamepadbutton-value
    #[inline]
    pub fn value(&self) -> f64 {
        js!(
            return @{self.as_ref()}.value;
        ).try_into().unwrap()
    }
}

/// An individual gamepad/controller.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad)
// https://w3c.github.io/gamepad/#gamepad-interface
#[derive(Clone, Debug, Eq, PartialEq, ReferenceType)]
#[reference(instance_of = "Gamepad")]
pub struct Gamepad( Reference );

impl Gamepad {

    /// A string containing some information about this gamepad.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id)
    // https://w3c.github.io/gamepad/#dom-gamepad-id
    #[inline]
    pub fn id(&self) -> String {
        js!(
            return @{self.as_ref()}.id;
        ).try_into().unwrap()
    }

    /// An auto-incrementing integer to uniquely identify a connected Gamepad.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index)
    // https://w3c.github.io/gamepad/#dom-gamepad-index
    #[inline]
    pub fn index(&self) -> i32 {
        js!(
            return @{self.as_ref()}.index;
        ).try_into().unwrap()
    }

    /// Is this gamepad connected to the system, powered on, and usable?
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected)
    // https://w3c.github.io/gamepad/#dom-gamepad-connected
    #[inline]
    pub fn connected(&self) -> bool {
        js!(
            return @{self.as_ref()}.connected;
        ).try_into().unwrap()
    }

    /// Monotonically increasing time this gamepad was updated.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/timestamp)
    // https://w3c.github.io/gamepad/#dom-gamepad-timestamp
    #[inline]
    pub fn timestamp(&self) -> f64 {
        js!(
            return @{self.as_ref()}.timestamp;
        ).try_into().unwrap()
    }

    /// The mapping in use for this device.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/mapping)
    // https://w3c.github.io/gamepad/#dom-gamepad-mapping
    #[inline]
    pub fn mapping(&self) -> GamepadMappingType {
        js!(
            return @{self.as_ref()}.mapping;
        ).try_into().unwrap()
    }

    /// Array of values for all axes of the gamepad.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/axes)
    // https://w3c.github.io/gamepad/#dom-gamepad-axes
    #[inline]
    pub fn axes(&self) -> Vec<f64> {
        js!(
            return @{self.as_ref()}.axes;
        ).try_into().unwrap()
    }

    /// Array of button states for all buttons of the gamepad.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/buttons)
    // https://w3c.github.io/gamepad/#dom-gamepad-buttons
    #[inline]
    pub fn buttons(&self) -> Vec<GamepadButton> {
        js!(
            return @{self.as_ref()}.buttons;
        ).try_into().unwrap()
    }

    /// Retrieve all connected gamepads, in an array indexed by each gamepad's `index` member.
    ///
    /// Chrome doesn't update Gamepad state automatically, you must call this function each frame.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getGamepads)
    // https://w3c.github.io/gamepad/#dom-navigator-getgamepads
    pub fn get_all() -> Vec<Option<Gamepad>> {
        js!(
            return Array.from(navigator.getGamepads());
        ).try_into().unwrap()
    }
}

#[cfg(test)]
mod tests {
    use super::GamepadMappingType;

    use webcore::try_from::TryInto;
    use webcore::value::{ConversionError, Value};

    #[test]
    fn test_value_into_gamepad_mapping() {

        let to_mapping = |v: Value| -> Result<GamepadMappingType, ConversionError> {
            v.try_into()
        };

        assert_eq!(to_mapping("standard".into()), Ok(GamepadMappingType::Standard));
        assert_eq!(to_mapping("".into()), Ok(GamepadMappingType::NoMapping));
        assert!(to_mapping("fakemapping".into()).is_err());
        assert!(to_mapping(Value::Null).is_err());
    }

    // most of the Gamepad API is not testable,
    // because Gamepad and GamepadButton are not constructible
}