chargrid_input/
lib.rs

1pub use coord_2d::Coord;
2#[cfg(feature = "serialize")]
3use serde::{Deserialize, Serialize};
4
5#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
6#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
7pub enum ScrollDirection {
8    Up,
9    Down,
10    Left,
11    Right,
12}
13
14#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
15#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
16pub enum MouseButton {
17    Left,
18    Right,
19    Middle,
20}
21
22#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
23#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
24pub struct NotSupported;
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
27pub enum KeyboardInput {
28    Char(char),
29    Function(u8),
30    Up,
31    Down,
32    Left,
33    Right,
34    Home,
35    End,
36    PageUp,
37    PageDown,
38    Delete,
39}
40
41#[cfg(feature = "serialize")]
42mod key_names {
43    pub const UP: &str = "up";
44    pub const DOWN: &str = "down";
45    pub const LEFT: &str = "left";
46    pub const RIGHT: &str = "right";
47    pub const HOME: &str = "home";
48    pub const END: &str = "end";
49    pub const PAGE_UP: &str = "page-up";
50    pub const PAGE_DOWN: &str = "page-down";
51    pub const DELETE: &str = "delete";
52}
53
54#[cfg(feature = "serialize")]
55impl KeyboardInput {
56    fn try_from_str(s: &str) -> Option<Self> {
57        if s.chars().count() == 1 {
58            let c = s.chars().next().unwrap();
59            return Some(KeyboardInput::Char(c));
60        }
61        if s.starts_with('f') || s.starts_with('F') {
62            let (_, maybe_number_str) = s.split_at(1);
63            if let Ok(number) = maybe_number_str.parse::<u8>() {
64                return Some(KeyboardInput::Function(number));
65            }
66        }
67        use key_names::*;
68        match s {
69            UP => Some(KeyboardInput::Up),
70            DOWN => Some(KeyboardInput::Down),
71            LEFT => Some(KeyboardInput::Left),
72            RIGHT => Some(KeyboardInput::Right),
73            HOME => Some(KeyboardInput::Home),
74            END => Some(KeyboardInput::End),
75            PAGE_UP => Some(KeyboardInput::PageUp),
76            PAGE_DOWN => Some(KeyboardInput::PageDown),
77            DELETE => Some(KeyboardInput::Delete),
78            _ => None,
79        }
80    }
81}
82
83#[cfg(feature = "serialize")]
84impl serde::Serialize for KeyboardInput {
85    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86    where
87        S: serde::Serializer,
88    {
89        use key_names::*;
90        use KeyboardInput::*;
91        match self {
92            Char(c) => serializer.serialize_char(*c),
93            Function(n) => serializer.serialize_str(&format!("f{}", n)),
94            Up => serializer.serialize_str(UP),
95            Down => serializer.serialize_str(DOWN),
96            Left => serializer.serialize_str(LEFT),
97            Right => serializer.serialize_str(RIGHT),
98            Home => serializer.serialize_str(HOME),
99            End => serializer.serialize_str(END),
100            PageUp => serializer.serialize_str(PAGE_UP),
101            PageDown => serializer.serialize_str(PAGE_DOWN),
102            Delete => serializer.serialize_str(DELETE),
103        }
104    }
105}
106
107#[cfg(feature = "serialize")]
108impl<'de> serde::Deserialize<'de> for KeyboardInput {
109    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
110    where
111        D: serde::Deserializer<'de>,
112    {
113        struct Visitor;
114        impl<'de> serde::de::Visitor<'de> for Visitor {
115            type Value = KeyboardInput;
116
117            fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
118                formatter.write_str("a keyboard input description")
119            }
120
121            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
122            where
123                E: serde::de::Error,
124            {
125                KeyboardInput::try_from_str(s)
126                    .ok_or_else(|| E::custom(format!("couldn't parse {}", s)))
127            }
128        }
129        deserializer.deserialize_str(Visitor)
130    }
131}
132
133#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
134#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
135pub enum MouseInput {
136    MouseMove {
137        button: Option<MouseButton>,
138        coord: Coord,
139    },
140    MousePress {
141        button: MouseButton,
142        coord: Coord,
143    },
144    MouseRelease {
145        // some platforms (e.g. ansi terminal) don't report the button that was released
146        button: Result<MouseButton, NotSupported>,
147        coord: Coord,
148    },
149    MouseScroll {
150        direction: ScrollDirection,
151        coord: Coord,
152    },
153}
154
155impl MouseInput {
156    pub fn coord(&self) -> Coord {
157        match self {
158            Self::MouseMove { coord, .. }
159            | Self::MousePress { coord, .. }
160            | Self::MouseRelease { coord, .. }
161            | Self::MouseScroll { coord, .. } => *coord,
162        }
163    }
164
165    fn coord_mut(&mut self) -> &mut Coord {
166        match self {
167            Self::MouseMove { coord, .. }
168            | Self::MousePress { coord, .. }
169            | Self::MouseRelease { coord, .. }
170            | Self::MouseScroll { coord, .. } => coord,
171        }
172    }
173
174    pub fn relative_to_coord(&self, coord: Coord) -> Self {
175        let mut ret = *self;
176        *ret.coord_mut() -= coord;
177        ret
178    }
179}
180
181#[cfg(feature = "gamepad")]
182mod gamepad {
183    #[cfg(feature = "serialize")]
184    use serde::{Deserialize, Serialize};
185
186    #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
187    #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
188    pub enum GamepadButton {
189        DPadUp,
190        DPadRight,
191        DPadDown,
192        DPadLeft,
193        North,
194        East,
195        South,
196        West,
197        Start,
198        Select,
199        LeftBumper,
200        RightBumper,
201    }
202
203    #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
204    #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
205    pub struct GamepadInput {
206        pub button: GamepadButton,
207        pub id: u64,
208    }
209}
210
211#[cfg(feature = "gamepad")]
212pub use gamepad::{GamepadButton, GamepadInput};
213
214/// Opinionated policy for interpreting input for convenience
215#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
216pub enum InputPolicy {
217    Up,
218    Down,
219    Left,
220    Right,
221    Select,
222}
223
224/// An input event
225#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
226#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
227pub enum Input {
228    Keyboard(KeyboardInput),
229    Mouse(MouseInput),
230    #[cfg(feature = "gamepad")]
231    Gamepad(GamepadInput),
232}
233
234impl Input {
235    pub fn is_keyboard(&self) -> bool {
236        match self {
237            Input::Keyboard(_) => true,
238            Input::Mouse(_) => false,
239            #[cfg(feature = "gamepad")]
240            Input::Gamepad(_) => false,
241        }
242    }
243
244    pub fn is_mouse(&self) -> bool {
245        match self {
246            Input::Keyboard(_) => false,
247            Input::Mouse(_) => true,
248            #[cfg(feature = "gamepad")]
249            Input::Gamepad(_) => false,
250        }
251    }
252
253    #[cfg(feature = "gamepad")]
254    pub fn is_gamepad(&self) -> bool {
255        match self {
256            Input::Keyboard(_) => false,
257            Input::Mouse(_) => false,
258            Input::Gamepad(_) => true,
259        }
260    }
261
262    pub fn keyboard(self) -> Option<KeyboardInput> {
263        match self {
264            Input::Keyboard(keyboard_input) => Some(keyboard_input),
265            Input::Mouse(_) => None,
266            #[cfg(feature = "gamepad")]
267            Input::Gamepad(_) => None,
268        }
269    }
270
271    pub fn mouse(self) -> Option<MouseInput> {
272        match self {
273            Input::Keyboard(_) => None,
274            Input::Mouse(mouse_input) => Some(mouse_input),
275            #[cfg(feature = "gamepad")]
276            Input::Gamepad(_) => None,
277        }
278    }
279
280    #[cfg(feature = "gamepad")]
281    pub fn gamepad(self) -> Option<GamepadInput> {
282        match self {
283            Input::Keyboard(_) | Input::Mouse(_) => None,
284            Input::Gamepad(gamepad_input) => Some(gamepad_input),
285        }
286    }
287
288    pub fn policy(self) -> Option<InputPolicy> {
289        match self {
290            Input::Keyboard(KeyboardInput::Left) => Some(InputPolicy::Left),
291            Input::Keyboard(KeyboardInput::Right) => Some(InputPolicy::Right),
292            Input::Keyboard(keys::RETURN) => Some(InputPolicy::Select),
293            Input::Mouse(MouseInput::MousePress { .. }) => Some(InputPolicy::Select),
294            Input::Mouse(MouseInput::MouseScroll { direction, .. }) => Some(match direction {
295                ScrollDirection::Up => InputPolicy::Up,
296                ScrollDirection::Down => InputPolicy::Down,
297                ScrollDirection::Left => InputPolicy::Left,
298                ScrollDirection::Right => InputPolicy::Right,
299            }),
300            #[cfg(feature = "gamepad")]
301            Input::Gamepad(GamepadInput { button, .. }) => match button {
302                GamepadButton::DPadLeft => Some(InputPolicy::Left),
303                GamepadButton::DPadRight => Some(InputPolicy::Right),
304                GamepadButton::Start | GamepadButton::South => Some(InputPolicy::Select),
305                _ => None,
306            },
307            _ => None,
308        }
309    }
310}
311
312pub mod keys {
313    use super::KeyboardInput;
314
315    pub const ESCAPE: KeyboardInput = KeyboardInput::Char('\u{1b}');
316    pub const ETX: KeyboardInput = KeyboardInput::Char('\u{3}');
317    pub const BACKSPACE: KeyboardInput = KeyboardInput::Char('\u{7f}');
318    pub const TAB: KeyboardInput = KeyboardInput::Char('\u{9}');
319    pub const RETURN: KeyboardInput = KeyboardInput::Char('\u{d}');
320}
321
322#[cfg(feature = "serialize")]
323#[cfg(test)]
324mod serde_test {
325    #[test]
326    fn reversable() {
327        use super::KeyboardInput;
328        fn t(input: KeyboardInput) {
329            let s = serde_json::to_string(&input).unwrap();
330            assert_eq!(serde_json::from_str::<KeyboardInput>(&s).unwrap(), input);
331        }
332        t(KeyboardInput::Up);
333        t(KeyboardInput::Down);
334        t(KeyboardInput::Left);
335        t(KeyboardInput::Right);
336        t(KeyboardInput::Home);
337        t(KeyboardInput::End);
338        t(KeyboardInput::PageUp);
339        t(KeyboardInput::PageDown);
340        t(KeyboardInput::Delete);
341        t(KeyboardInput::Function(42));
342        t(KeyboardInput::Char('a'));
343        t(KeyboardInput::Char('☃'));
344    }
345
346    #[test]
347    fn example() {
348        use super::KeyboardInput;
349        use std::collections::BTreeMap;
350        let mut map = BTreeMap::new();
351        map.insert(KeyboardInput::Up, "UP");
352        map.insert(KeyboardInput::Down, "DOWN");
353        map.insert(KeyboardInput::Function(42), "F42");
354        map.insert(KeyboardInput::Char('a'), "A");
355        map.insert(KeyboardInput::Char('☃'), "SNOWMAN");
356        let pretty_json_string = serde_json::to_string_pretty(&map).unwrap();
357        assert_eq!(
358            pretty_json_string,
359            "{
360  \"a\": \"A\",
361  \"☃\": \"SNOWMAN\",
362  \"f42\": \"F42\",
363  \"up\": \"UP\",
364  \"down\": \"DOWN\"
365}",
366        );
367    }
368}