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
//! A logical game controller that attached to a physical device.

use static_assertions::assert_not_impl_all;
use std::{
    ffi::{CStr, CString},
    ptr::NonNull,
};

use crate::{bind, Result, Sdl, SdlError};

use self::{axis::Axis, button::Button, map::MapInput};

pub mod axis;
pub mod button;
pub mod event;
pub mod map;

/// A logical game controller manages binding of the physical devices.
pub struct GameController {
    pub(in crate::event) ptr: NonNull<bind::SDL_GameController>,
}

impl std::fmt::Debug for GameController {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("GameController")
            .field("name", &self.name())
            .finish_non_exhaustive()
    }
}

assert_not_impl_all!(GameController: Send, Sync);

impl GameController {
    /// Returns the string of all mapping `GameController` holds.
    #[must_use]
    pub fn mapping(&self) -> String {
        let ptr = unsafe { bind::SDL_GameControllerMapping(self.ptr.as_ptr()) };
        let cstr = unsafe { CStr::from_ptr(ptr) };
        let ret = cstr.to_string_lossy().to_string();
        unsafe { bind::SDL_free(ptr.cast()) };
        ret
    }

    /// Returns the name of the game controller.
    #[must_use]
    pub fn name(&self) -> String {
        let ptr = unsafe { bind::SDL_GameControllerName(self.ptr.as_ptr()) };
        if ptr.is_null() {
            return "".into();
        }
        let cstr = unsafe { CStr::from_ptr(ptr) };
        cstr.to_string_lossy().to_string()
    }

    /// Returns the bind for an axis if exists.
    #[must_use]
    pub fn bind_for_axis(&self, axis: Axis) -> Option<MapInput> {
        let ret =
            unsafe { bind::SDL_GameControllerGetBindForAxis(self.ptr.as_ptr(), axis.as_raw()) };
        (ret.bindType != bind::SDL_CONTROLLER_BINDTYPE_NONE).then(|| ret.into())
    }

    /// Returns the bind for a button if exists.
    #[must_use]
    pub fn bind_for_button(&self, button: Button) -> Option<MapInput> {
        let ret =
            unsafe { bind::SDL_GameControllerGetBindForButton(self.ptr.as_ptr(), button.as_raw()) };
        (ret.bindType != bind::SDL_CONTROLLER_BINDTYPE_NONE).then(|| ret.into())
    }
}

/// All of recognized game controllers at initialized.
#[derive(Debug)]
pub struct GameControllerSet {
    controls: Vec<GameController>,
}

impl GameControllerSet {
    /// Constructs and initializes the system and recognizes controllers.
    #[must_use]
    pub fn new() -> Self {
        let num_controls = unsafe {
            bind::SDL_InitSubSystem(bind::SDL_INIT_JOYSTICK);
            bind::SDL_NumJoysticks()
        };
        let controls = (0..num_controls)
            .filter(|&index| unsafe { bind::SDL_IsGameController(index) != 0 })
            .filter_map(|index| {
                let raw = unsafe { bind::SDL_GameControllerOpen(index) };
                NonNull::new(raw)
            })
            .map(|ptr| GameController { ptr })
            .collect();
        Self { controls }
    }

    /// Applies mapping string.
    ///
    /// # Errors
    ///
    /// Returns `Err` if failed to apply the mapping `string`.
    pub fn add_mapping(string: &str) -> Result<bool> {
        let cstr = CString::new(string).expect("string must not be empty");
        let ret = unsafe { bind::SDL_GameControllerAddMapping(cstr.as_ptr()) };
        if ret == -1 {
            Err(SdlError::Others { msg: Sdl::error() })
        } else {
            Ok(ret == 1)
        }
    }

    /// Applies mapping file, or returns `Err` on failure.
    ///
    /// # Errors
    ///
    /// Returns `Err` if failed to open the mapping file, or it contains invalid mapping data.
    ///
    /// # Panics
    ///
    /// Panics if `file_name` contains a null character.
    pub fn add_mapping_from_file(file_name: &str) -> Result<u32> {
        let cstr = CString::new(file_name).expect("string must not be empty");
        let read_binary_mode = CStr::from_bytes_with_nul(b"rb\0").unwrap();
        let ret = unsafe {
            bind::SDL_GameControllerAddMappingsFromRW(
                bind::SDL_RWFromFile(cstr.as_ptr(), read_binary_mode.as_ptr()),
                1,
            )
        };
        if ret < 0 {
            Err(SdlError::Others { msg: Sdl::error() })
        } else {
            Ok(ret as u32)
        }
    }

    /// Returns the `GameController` list.
    #[must_use]
    pub fn controllers(&self) -> &[GameController] {
        &self.controls
    }
}

impl Default for GameControllerSet {
    fn default() -> Self {
        Self::new()
    }
}

impl Drop for GameControllerSet {
    fn drop(&mut self) {
        for control in &mut self.controls {
            unsafe { bind::SDL_GameControllerClose(control.ptr.as_ptr()) }
        }
        unsafe { bind::SDL_QuitSubSystem(bind::SDL_INIT_JOYSTICK) }
    }
}