rich_sdl2_rust/event/
game_controller.rs1use 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
18pub 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 #[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 #[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 #[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 #[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#[derive(Debug)]
74pub struct GameControllerSet {
75 controls: Vec<GameController>,
76}
77
78impl GameControllerSet {
79 #[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 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 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 #[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}