1use crate::{InputPort, raw};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub enum Sensor {
10 Accelerometer,
11 Gyroscope,
12 Illuminance,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub enum SensorAction {
17 Enable(Sensor),
18 Disable(Sensor),
19}
20
21impl SensorAction {
22 pub(crate) const fn as_raw(self) -> raw::retro_sensor_action {
23 match self {
24 Self::Enable(Sensor::Accelerometer) => raw::retro_sensor_action::AccelerometerEnable,
25 Self::Disable(Sensor::Accelerometer) => raw::retro_sensor_action::AccelerometerDisable,
26 Self::Enable(Sensor::Gyroscope) => raw::retro_sensor_action::GyroscopeEnable,
27 Self::Disable(Sensor::Gyroscope) => raw::retro_sensor_action::GyroscopeDisable,
28 Self::Enable(Sensor::Illuminance) => raw::retro_sensor_action::IlluminanceEnable,
29 Self::Disable(Sensor::Illuminance) => raw::retro_sensor_action::IlluminanceDisable,
30 }
31 }
32}
33
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
35pub struct SensorRateHz(u32);
36
37impl SensorRateHz {
38 pub const fn new(rate: u32) -> Self {
39 Self(rate)
40 }
41
42 pub const fn as_raw(self) -> u32 {
43 self.0
44 }
45}
46
47impl From<u32> for SensorRateHz {
48 fn from(rate: u32) -> Self {
49 Self::new(rate)
50 }
51}
52
53#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
54pub struct LocationIntervalMillis(u32);
55
56impl LocationIntervalMillis {
57 pub const fn new(milliseconds: u32) -> Self {
58 Self(milliseconds)
59 }
60
61 pub const fn as_millis(self) -> u32 {
62 self.0
63 }
64}
65
66impl From<u32> for LocationIntervalMillis {
67 fn from(milliseconds: u32) -> Self {
68 Self::new(milliseconds)
69 }
70}
71
72#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
73pub struct LocationIntervalMeters(u32);
74
75impl LocationIntervalMeters {
76 pub const fn new(meters: u32) -> Self {
77 Self(meters)
78 }
79
80 pub const fn as_meters(self) -> u32 {
81 self.0
82 }
83}
84
85impl From<u32> for LocationIntervalMeters {
86 fn from(meters: u32) -> Self {
87 Self::new(meters)
88 }
89}
90
91#[derive(Clone, Copy, Debug, Default, PartialEq)]
92pub struct LocationPosition {
93 pub latitude_degrees: f64,
94 pub longitude_degrees: f64,
95 pub horizontal_accuracy: f64,
96 pub vertical_accuracy: f64,
97}
98
99#[derive(Clone, Copy, Debug, Default)]
100pub struct LocationInterface {
101 raw: raw::retro_location_callback,
102}
103
104impl LocationInterface {
105 pub(crate) const fn from_raw(raw: raw::retro_location_callback) -> Self {
106 Self { raw }
107 }
108
109 pub const fn is_available(self) -> bool {
110 self.raw.start.is_some()
111 && self.raw.stop.is_some()
112 && self.raw.get_position.is_some()
113 && self.raw.set_interval.is_some()
114 }
115
116 pub fn start(self) -> bool {
117 self.raw.start.is_some_and(|start| unsafe { start() })
118 }
119
120 pub fn stop(self) -> bool {
121 let Some(stop) = self.raw.stop else {
122 return false;
123 };
124 unsafe { stop() };
125 true
126 }
127
128 pub fn set_interval(
129 self,
130 interval: impl Into<LocationIntervalMillis>,
131 distance: impl Into<LocationIntervalMeters>,
132 ) -> bool {
133 let Some(set_interval) = self.raw.set_interval else {
134 return false;
135 };
136 unsafe { set_interval(interval.into().as_millis(), distance.into().as_meters()) };
137 true
138 }
139
140 pub fn position(self) -> Option<LocationPosition> {
141 let get_position = self.raw.get_position?;
142 let mut latitude_degrees = 0.0;
143 let mut longitude_degrees = 0.0;
144 let mut horizontal_accuracy = 0.0;
145 let mut vertical_accuracy = 0.0;
146 if unsafe {
147 get_position(
148 &mut latitude_degrees,
149 &mut longitude_degrees,
150 &mut horizontal_accuracy,
151 &mut vertical_accuracy,
152 )
153 } {
154 Some(LocationPosition {
155 latitude_degrees,
156 longitude_degrees,
157 horizontal_accuracy,
158 vertical_accuracy,
159 })
160 } else {
161 None
162 }
163 }
164}
165
166#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
167pub enum SensorInput {
168 AccelerometerX,
169 AccelerometerY,
170 AccelerometerZ,
171 GyroscopeX,
172 GyroscopeY,
173 GyroscopeZ,
174 Illuminance,
175}
176
177impl SensorInput {
178 pub const fn as_raw(self) -> u32 {
179 match self {
180 Self::AccelerometerX => raw::RETRO_SENSOR_ACCELEROMETER_X,
181 Self::AccelerometerY => raw::RETRO_SENSOR_ACCELEROMETER_Y,
182 Self::AccelerometerZ => raw::RETRO_SENSOR_ACCELEROMETER_Z,
183 Self::GyroscopeX => raw::RETRO_SENSOR_GYROSCOPE_X,
184 Self::GyroscopeY => raw::RETRO_SENSOR_GYROSCOPE_Y,
185 Self::GyroscopeZ => raw::RETRO_SENSOR_GYROSCOPE_Z,
186 Self::Illuminance => raw::RETRO_SENSOR_ILLUMINANCE,
187 }
188 }
189}
190
191#[derive(Clone, Copy, Debug, Default)]
192pub struct SensorInterface {
193 raw: raw::retro_sensor_interface,
194}
195
196impl SensorInterface {
197 pub(crate) const fn from_raw(raw: raw::retro_sensor_interface) -> Self {
198 Self { raw }
199 }
200
201 pub const fn is_available(self) -> bool {
202 self.raw.set_sensor_state.is_some() && self.raw.get_sensor_input.is_some()
203 }
204
205 pub fn set_state(
206 self,
207 port: impl Into<InputPort>,
208 action: SensorAction,
209 rate: impl Into<SensorRateHz>,
210 ) -> bool {
211 let Some(set_sensor_state) = self.raw.set_sensor_state else {
212 return false;
213 };
214
215 unsafe { set_sensor_state(port.into().as_raw(), action.as_raw(), rate.into().as_raw()) }
216 }
217
218 pub fn enable(
219 self,
220 port: impl Into<InputPort>,
221 sensor: Sensor,
222 rate: impl Into<SensorRateHz>,
223 ) -> bool {
224 self.set_state(port, SensorAction::Enable(sensor), rate)
225 }
226
227 pub fn disable(self, port: impl Into<InputPort>, sensor: Sensor) -> bool {
228 self.set_state(port, SensorAction::Disable(sensor), SensorRateHz::new(0))
229 }
230
231 pub fn input(self, port: impl Into<InputPort>, input: SensorInput) -> Option<f32> {
232 self.raw.get_sensor_input.map(|get_sensor_input| unsafe {
233 get_sensor_input(port.into().as_raw(), input.as_raw())
234 })
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::{
241 LocationInterface, LocationIntervalMeters, LocationIntervalMillis, LocationPosition,
242 Sensor, SensorAction, SensorInput, SensorInterface, SensorRateHz,
243 };
244
245 #[test]
246 fn sensor_actions_encode_libretro_values() {
247 assert_eq!(
248 SensorAction::Enable(Sensor::Accelerometer).as_raw(),
249 crate::raw::retro_sensor_action::AccelerometerEnable
250 );
251 assert_eq!(
252 SensorAction::Disable(Sensor::Illuminance).as_raw(),
253 crate::raw::retro_sensor_action::IlluminanceDisable
254 );
255 }
256
257 #[test]
258 fn sensor_inputs_encode_libretro_ids() {
259 assert_eq!(
260 SensorInput::AccelerometerX.as_raw(),
261 crate::raw::RETRO_SENSOR_ACCELEROMETER_X
262 );
263 assert_eq!(
264 SensorInput::Illuminance.as_raw(),
265 crate::raw::RETRO_SENSOR_ILLUMINANCE
266 );
267 }
268
269 #[test]
270 fn empty_sensor_interface_reports_unavailable() {
271 let sensors = SensorInterface::default();
272
273 assert!(!sensors.is_available());
274 assert!(!sensors.enable(0, Sensor::Accelerometer, SensorRateHz::new(60)));
275 assert_eq!(sensors.input(0, SensorInput::AccelerometerX), None);
276 }
277
278 #[test]
279 fn empty_location_interface_reports_unavailable() {
280 let location = LocationInterface::default();
281
282 assert!(!location.is_available());
283 assert!(!location.start());
284 assert!(!location.stop());
285 assert!(!location.set_interval(LocationIntervalMillis::new(1000), 10));
286 assert_eq!(location.position(), None);
287 }
288
289 #[test]
290 fn location_interval_newtypes_preserve_units() {
291 assert_eq!(LocationIntervalMillis::new(500).as_millis(), 500);
292 assert_eq!(LocationIntervalMeters::new(20).as_meters(), 20);
293 assert_eq!(
294 LocationPosition {
295 latitude_degrees: 1.0,
296 longitude_degrees: 2.0,
297 horizontal_accuracy: 3.0,
298 vertical_accuracy: 4.0,
299 }
300 .horizontal_accuracy,
301 3.0
302 );
303 }
304}