rich_sdl2_rust/event/
game_controller.rs

1//! A logical game controller that attached to a physical device.
2
3use static_assertions::assert_not_impl_all;
4use std::{
5    ffi::{CStr, CString},
6    ptr::NonNull,
7};
8
9use crate::{bind, Result, Sdl, SdlError};
10
11use self::{axis::Axis, button::Button, map::MapInput};
12
13pub mod axis;
14pub mod button;
15pub mod event;
16pub mod map;
17
18/// A logical game controller manages binding of the physical devices.
19pub struct GameController {
20    pub(in crate::event) ptr: NonNull<bind::SDL_GameController>,
21}
22
23impl std::fmt::Debug for GameController {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        f.debug_struct("GameController")
26            .field("name", &self.name())
27            .finish_non_exhaustive()
28    }
29}
30
31assert_not_impl_all!(GameController: Send, Sync);
32
33impl GameController {
34    /// Returns the string of all mapping `GameController` holds.
35    #[must_use]
36    pub fn mapping(&self) -> String {
37        let ptr = unsafe { bind::SDL_GameControllerMapping(self.ptr.as_ptr()) };
38        let cstr = unsafe { CStr::from_ptr(ptr) };
39        let ret = cstr.to_string_lossy().to_string();
40        unsafe { bind::SDL_free(ptr.cast()) };
41        ret
42    }
43
44    /// Returns the name of the game controller.
45    #[must_use]
46    pub fn name(&self) -> String {
47        let ptr = unsafe { bind::SDL_GameControllerName(self.ptr.as_ptr()) };
48        if ptr.is_null() {
49            return "".into();
50        }
51        let cstr = unsafe { CStr::from_ptr(ptr) };
52        cstr.to_string_lossy().to_string()
53    }
54
55    /// Returns the bind for an axis if exists.
56    #[must_use]
57    pub fn bind_for_axis(&self, axis: Axis) -> Option<MapInput> {
58        let ret =
59            unsafe { bind::SDL_GameControllerGetBindForAxis(self.ptr.as_ptr(), axis.as_raw()) };
60        (ret.bindType != bind::SDL_CONTROLLER_BINDTYPE_NONE).then(|| ret.into())
61    }
62
63    /// Returns the bind for a button if exists.
64    #[must_use]
65    pub fn bind_for_button(&self, button: Button) -> Option<MapInput> {
66        let ret =
67            unsafe { bind::SDL_GameControllerGetBindForButton(self.ptr.as_ptr(), button.as_raw()) };
68        (ret.bindType != bind::SDL_CONTROLLER_BINDTYPE_NONE).then(|| ret.into())
69    }
70}
71
72/// All of recognized game controllers at initialized.
73#[derive(Debug)]
74pub struct GameControllerSet {
75    controls: Vec<GameController>,
76}
77
78impl GameControllerSet {
79    /// Constructs and initializes the system and recognizes controllers.
80    #[must_use]
81    pub fn new() -> Self {
82        let num_controls = unsafe {
83            bind::SDL_InitSubSystem(bind::SDL_INIT_JOYSTICK);
84            bind::SDL_NumJoysticks()
85        };
86        let controls = (0..num_controls)
87            .filter(|&index| unsafe { bind::SDL_IsGameController(index) != 0 })
88            .filter_map(|index| {
89                let raw = unsafe { bind::SDL_GameControllerOpen(index) };
90                NonNull::new(raw)
91            })
92            .map(|ptr| GameController { ptr })
93            .collect();
94        Self { controls }
95    }
96
97    /// Applies mapping string.
98    ///
99    /// # Errors
100    ///
101    /// Returns `Err` if failed to apply the mapping `string`.
102    pub fn add_mapping(string: &str) -> Result<bool> {
103        let cstr = CString::new(string).expect("string must not be empty");
104        let ret = unsafe { bind::SDL_GameControllerAddMapping(cstr.as_ptr()) };
105        if ret == -1 {
106            Err(SdlError::Others { msg: Sdl::error() })
107        } else {
108            Ok(ret == 1)
109        }
110    }
111
112    /// Applies mapping file, or returns `Err` on failure.
113    ///
114    /// # Errors
115    ///
116    /// Returns `Err` if failed to open the mapping file, or it contains invalid mapping data.
117    ///
118    /// # Panics
119    ///
120    /// Panics if `file_name` contains a null character.
121    pub fn add_mapping_from_file(file_name: &str) -> Result<u32> {
122        let cstr = CString::new(file_name).expect("string must not be empty");
123        let read_binary_mode = CStr::from_bytes_with_nul(b"rb\0").unwrap();
124        let ret = unsafe {
125            bind::SDL_GameControllerAddMappingsFromRW(
126                bind::SDL_RWFromFile(cstr.as_ptr(), read_binary_mode.as_ptr()),
127                1,
128            )
129        };
130        if ret < 0 {
131            Err(SdlError::Others { msg: Sdl::error() })
132        } else {
133            Ok(ret as u32)
134        }
135    }
136
137    /// Returns the `GameController` list.
138    #[must_use]
139    pub fn controllers(&self) -> &[GameController] {
140        &self.controls
141    }
142}
143
144impl Default for GameControllerSet {
145    fn default() -> Self {
146        Self::new()
147    }
148}
149
150impl Drop for GameControllerSet {
151    fn drop(&mut self) {
152        for control in &mut self.controls {
153            unsafe { bind::SDL_GameControllerClose(control.ptr.as_ptr()) }
154        }
155        unsafe { bind::SDL_QuitSubSystem(bind::SDL_INIT_JOYSTICK) }
156    }
157}