playdate_rs/
system.rs

1use core::ffi::{c_char, c_void, CStr};
2
3use alloc::{ffi::CString, vec::Vec};
4use sys::PDButtons;
5pub use sys::{
6    LCDFontData as FontData, PDDateTime as DateTime, PDLanguage as Language,
7    PDPeripherals as Peripherals, PDSystemEvent as SystemEvent,
8};
9
10use crate::{graphics::Bitmap, math::Vec2, PLAYDATE};
11
12pub struct PlaydateSystem {
13    handle: *const sys::playdate_sys,
14}
15
16impl PlaydateSystem {
17    pub(crate) fn new(handle: *const sys::playdate_sys) -> Self {
18        Self { handle }
19    }
20
21    /// Allocates heap space if ptr is NULL, else reallocates the given pointer. If size is zero, frees the given pointer.
22    pub(crate) fn realloc(&self, ptr: *mut c_void, size: usize) -> *mut c_void {
23        unsafe { (*self.handle).realloc.unwrap()(ptr, size) }
24    }
25
26    /// Calls the log function.
27    pub fn log_to_console(&self, msg: impl AsRef<str>) {
28        unsafe {
29            let c_string = CString::new(msg.as_ref()).unwrap();
30            (*self.handle).logToConsole.unwrap()(c_string.as_ptr() as *mut c_char);
31        }
32    }
33
34    /// Calls the log function, outputting an error in red to the console, then pauses execution.
35    pub fn error(&self, msg: impl AsRef<str>) {
36        unsafe {
37            let c_string = CString::new(msg.as_ref()).unwrap();
38            (*self.handle).error.unwrap()(c_string.as_ptr() as *mut c_char);
39        }
40    }
41
42    /// Returns the current language of the system.
43    pub fn get_language(&self) -> Language {
44        unsafe { (*self.handle).getLanguage.unwrap()() }
45    }
46
47    /// Returns the number of milliseconds since…​some arbitrary point in time. This should present a consistent timebase while a game is running, but the counter will be disabled when the device is sleeping.
48    pub fn get_current_time_milliseconds(&self) -> usize {
49        unsafe { (*self.handle).getCurrentTimeMilliseconds.unwrap()() as _ }
50    }
51
52    /// Returns the number of seconds (and sets milliseconds if not NULL) elapsed since midnight (hour 0), January 1, 2000.
53    pub fn get_seconds_since_epoch(&self) -> (usize, usize) {
54        let mut ms = 0;
55        unsafe {
56            let s = (*self.handle).getSecondsSinceEpoch.unwrap()(&mut ms);
57            (s as _, ms as _)
58        }
59    }
60
61    /// Calculates the current frames per second and draws that value at `x`, `y`.
62    pub fn draw_fps(&self, pos: Vec2<i32>) {
63        unsafe { (*self.handle).drawFPS.unwrap()(pos.x, pos.y) }
64    }
65
66    /// Replaces the default Lua run loop function with a custom update function. The update function should return a non-zero number to tell the system to update the display, or zero if update isn’t needed.
67    pub(crate) fn set_update_callback(&self, update: sys::PDCallbackFunction) {
68        unsafe {
69            (*self.handle).setUpdateCallback.unwrap()(update, core::ptr::null_mut());
70        }
71    }
72
73    /// Returns bitmasks indicating which buttons are currently down. pushed and released reflect which buttons were pushed or released over the previous update cycle—at the nominal frame rate of 50 ms, fast button presses can be missed if you just poll the instantaneous state.
74    pub fn get_button_state(&self) -> ButtonState {
75        let mut current = PDButtons(0);
76        let mut pushed = PDButtons(0);
77        let mut released = PDButtons(0);
78        unsafe {
79            (*self.handle).getButtonState.unwrap()(&mut current, &mut pushed, &mut released);
80        }
81        ButtonState {
82            current: Buttons::from(current.0 as u8),
83            pushed: Buttons::from(pushed.0 as u8),
84            released: Buttons::from(released.0 as u8),
85        }
86    }
87
88    /// By default, the accelerometer is disabled to save (a small amount of) power. To use a peripheral, it must first be enabled via this function. Accelerometer data is not available until the next update cycle after it’s enabled.
89    pub fn set_peripherals_enabled(&self, mask: Peripherals) {
90        unsafe {
91            (*self.handle).setPeripheralsEnabled.unwrap()(mask);
92        }
93    }
94
95    /// Returns the last-read accelerometer data.
96    pub fn get_accelerometer(&self) -> (f32, f32, f32) {
97        let x = core::ptr::null_mut();
98        let y = core::ptr::null_mut();
99        let z = core::ptr::null_mut();
100        unsafe {
101            (*self.handle).getAccelerometer.unwrap()(x, y, z);
102            (*x, *y, *z)
103        }
104    }
105
106    /// Returns the current position of the crank, in the range 0-360. Zero is pointing up, and the value increases as the crank moves clockwise, as viewed from the right side of the device.
107    pub fn get_crank_angle(&self) -> f32 {
108        unsafe { (*self.handle).getCrankAngle.unwrap()() }
109    }
110
111    /// Returns the angle change of the crank since the last time this function was called. Negative values are anti-clockwise.
112    pub fn get_crank_change(&self) -> f32 {
113        unsafe { (*self.handle).getCrankChange.unwrap()() }
114    }
115
116    /// Returns 1 or 0 indicating whether or not the crank is folded into the unit.
117    pub fn is_crank_docked(&self) -> bool {
118        unsafe {
119            let result = (*self.handle).isCrankDocked.unwrap()();
120            result == 1
121        }
122    }
123
124    /// The function returns the previous value for this setting.
125    pub fn set_crank_sounds_disabled(&self, flag: bool) -> bool {
126        unsafe {
127            let result = (*self.handle).setCrankSoundsDisabled.unwrap()(flag as i32);
128            result == 1
129        }
130    }
131
132    /// Returns 1 if the global "flipped" system setting is set, otherwise 0.
133    pub fn get_flipped(&self) -> bool {
134        unsafe {
135            let result = (*self.handle).getFlipped.unwrap()();
136            result == 1
137        }
138    }
139
140    /// Disables or enables the 60 second auto lock feature. When called, the timer is reset to 60 seconds.
141    pub fn set_auto_lock_disabled(&self, disable: bool) {
142        unsafe { (*self.handle).setAutoLockDisabled.unwrap()(disable as i32) }
143    }
144
145    /// A game can optionally provide an image to be displayed alongside the system menu. bitmap must be a 400x240 LCDBitmap. All important content should be in the left half of the image in an area 200 pixels wide, as the menu will obscure the rest. The right side of the image will be visible briefly as the menu animates in and out.
146    ///
147    /// Optionally, a non-zero xoffset, can be provided. This must be a number between 0 and 200 and will cause the menu image to animate to a position offset left by xoffset pixels as the menu is animated in.
148    ///
149    /// This function could be called in response to the kEventPause event in your implementation of eventHandler().
150    pub fn set_menu_image(&self, bitmap: impl AsRef<Bitmap>, x_offset: i32) {
151        unsafe { (*self.handle).setMenuImage.unwrap()(bitmap.as_ref().handle, x_offset) }
152    }
153
154    /// title will be the title displayed by the menu item.
155    ///
156    /// Adds a new menu item to the System Menu. When invoked by the user, this menu item will:
157    /// 1. Invoke your callback function.
158    /// 2. Hide the System Menu.
159    /// 3. Unpause your game and call eventHandler() with the kEventResume event.
160    ///
161    /// Your game can then present an options interface to the player, or take other action, in whatever manner you choose.
162    pub fn add_menu_item(&self, title: impl AsRef<str>, callback: fn()) -> MenuItem {
163        extern "C" fn callback_impl(payload: *mut c_void) {
164            let f: fn() = unsafe { core::mem::transmute(payload) };
165            f();
166        }
167        MenuItem::new(unsafe {
168            let c_string = CString::new(title.as_ref()).unwrap();
169            (*self.handle).addMenuItem.unwrap()(
170                c_string.as_ptr() as *mut c_char,
171                Some(callback_impl),
172                callback as _,
173            )
174        })
175    }
176
177    /// Adds a new menu item that can be checked or unchecked by the player.
178    ///
179    /// title will be the title displayed by the menu item.
180    ///
181    /// value should be false for unchecked, true for checked.
182    ///
183    /// If this menu item is interacted with while the system menu is open, callback will be called when the menu is closed.
184    pub fn add_checkmark_menu_item(
185        &self,
186        title: impl AsRef<str>,
187        value: bool,
188        callback: fn(),
189    ) -> MenuItem {
190        extern "C" fn callback_impl(payload: *mut c_void) {
191            let f: fn() = unsafe { core::mem::transmute(payload) };
192            f();
193        }
194        MenuItem::new(unsafe {
195            let c_string = CString::new(title.as_ref()).unwrap();
196            (*self.handle).addCheckmarkMenuItem.unwrap()(
197                c_string.as_ptr() as *mut c_char,
198                value as _,
199                Some(callback_impl),
200                callback as _,
201            )
202        })
203    }
204
205    /// Adds a new menu item that allows the player to cycle through a set of options.
206    ///
207    /// title will be the title displayed by the menu item.
208    ///
209    /// options should be an array of strings representing the states this menu item can cycle through. Due to limited horizontal space, the option strings and title should be kept short for this type of menu item.
210    ///
211    /// optionsCount should be the number of items contained in options.
212    ///
213    /// If this menu item is interacted with while the system menu is open, callback will be called when the menu is closed.
214    pub fn add_options_menu_item(
215        &self,
216        title: impl AsRef<str>,
217        option_titles: &[&str],
218        callback: fn(),
219    ) -> MenuItem {
220        extern "C" fn callback_impl(payload: *mut c_void) {
221            let f: fn() = unsafe { core::mem::transmute(payload) };
222            f();
223        }
224        MenuItem::new(unsafe {
225            let c_string = CString::new(title.as_ref()).unwrap();
226            let title_cstrings = option_titles
227                .iter()
228                .map(|s| CString::new(*s).unwrap())
229                .collect::<Vec<_>>();
230            let mut title_ptrs = title_cstrings
231                .iter()
232                .map(|s| s.as_ptr() as *const c_char)
233                .collect::<Vec<_>>();
234            (*self.handle).addOptionsMenuItem.unwrap()(
235                c_string.as_ptr() as *mut c_char,
236                title_ptrs.as_mut_ptr(),
237                option_titles.len() as _,
238                Some(callback_impl),
239                callback as _,
240            )
241        })
242    }
243
244    /// Removes all custom menu items from the system menu.
245    #[allow(unused)]
246    pub(crate) fn remove_all_menu_items(&self) {
247        unsafe { (*self.handle).removeAllMenuItems.unwrap()() }
248    }
249
250    /// Returns 1 if the global "reduce flashing" system setting is set, otherwise 0.
251    pub fn get_reduce_flashing(&self) -> bool {
252        unsafe {
253            let result = (*self.handle).getReduceFlashing.unwrap()();
254            result == 1
255        }
256    }
257
258    /// Returns the number of seconds since playdate.resetElapsedTime() was called. The value is a floating-point number with microsecond accuracy.
259    pub fn get_elapsed_time(&self) -> f32 {
260        unsafe { (*self.handle).getElapsedTime.unwrap()() }
261    }
262
263    /// Resets the high-resolution timer.
264    pub fn reset_elapsed_time(&self) {
265        unsafe { (*self.handle).resetElapsedTime.unwrap()() }
266    }
267
268    /// Returns a value from 0-100 denoting the current level of battery charge. 0 = empty; 100 = full.
269    pub fn get_battery_percentage(&self) -> f32 {
270        unsafe { (*self.handle).getBatteryPercentage.unwrap()() }
271    }
272
273    /// Returns the battery’s current voltage level.
274    pub fn get_battery_voltage(&self) -> f32 {
275        unsafe { (*self.handle).getBatteryVoltage.unwrap()() }
276    }
277
278    /// Returns the system timezone offset from GMT, in seconds.
279    pub fn get_timezone_offset(&self) -> i32 {
280        unsafe { (*self.handle).getTimezoneOffset.unwrap()() }
281    }
282
283    /// Returns 1 if the user has set the 24-Hour Time preference in the Settings program.
284    pub fn should_display_24_hour_time(&self) -> bool {
285        unsafe {
286            let result = (*self.handle).shouldDisplay24HourTime.unwrap()();
287            result == 1
288        }
289    }
290
291    /// Converts the given epoch time to a PDDateTime.
292    pub fn convert_epoch_to_date_time(&self, epoch: u32) -> DateTime {
293        let mut datetime = DateTime::default();
294        unsafe {
295            (*self.handle).convertEpochToDateTime.unwrap()(epoch, &mut datetime);
296            datetime
297        }
298    }
299
300    /// Converts the given PDDateTime to an epoch time.
301    pub fn convert_date_time_to_epoch(&self, mut datetime: DateTime) -> u32 {
302        unsafe { (*self.handle).convertDateTimeToEpoch.unwrap()(&mut datetime) }
303    }
304
305    /// Flush the CPU instruction cache, on the very unlikely chance you’re modifying instruction code on the fly. (If you don’t know what I’m talking about, you don’t need this. :smile:)
306    pub fn clear_icache(&self) {
307        unsafe { (*self.handle).clearICache.unwrap()() }
308    }
309}
310
311#[derive(PartialEq, Eq, Debug)]
312pub struct MenuItem {
313    handle: *mut sys::PDMenuItem,
314}
315
316unsafe impl Send for MenuItem {}
317unsafe impl Sync for MenuItem {}
318
319impl MenuItem {
320    fn new(handle: *mut sys::PDMenuItem) -> Self {
321        MenuItem { handle }
322    }
323
324    /// Gets the integer value of the menu item.
325    ///
326    /// For checkmark menu items, 1 means checked, 0 unchecked. For option menu items, the value indicates the array index of the currently selected option.
327    pub fn get_value(&self) -> i32 {
328        unsafe { (*PLAYDATE.system.handle).getMenuItemValue.unwrap()(self.handle) }
329    }
330
331    /// Sets the integer value of the menu item.
332    ///
333    /// For checkmark menu items, 1 means checked, 0 unchecked. For option menu items, the value indicates the array index of the currently selected option.
334    pub fn set_value(&self, value: i32) {
335        unsafe { (*PLAYDATE.system.handle).setMenuItemValue.unwrap()(self.handle, value) }
336    }
337
338    /// Gets the display title of the menu item.
339    pub fn get_title(&self) -> &str {
340        let c_buf = unsafe { (*PLAYDATE.system.handle).getMenuItemTitle.unwrap()(self.handle) };
341        let c_str: &CStr = unsafe { CStr::from_ptr(c_buf) };
342        let s: &str = c_str.to_str().unwrap();
343        s
344    }
345
346    /// Sets the display title of the menu item.
347    pub fn set_title(&self, title: impl AsRef<str>) {
348        let c_string = CString::new(title.as_ref()).unwrap();
349        unsafe {
350            (*PLAYDATE.system.handle).setMenuItemTitle.unwrap()(
351                self.handle,
352                c_string.as_ptr() as *mut c_char,
353            )
354        }
355    }
356
357    /// Gets the userdata value associated with this menu item.
358    #[allow(unused)]
359    pub(crate) fn get_userdata(&self) -> *mut c_void {
360        unsafe { (*PLAYDATE.system.handle).getMenuItemUserdata.unwrap()(self.handle) }
361    }
362
363    /// Sets the userdata value associated with this menu item.
364    #[allow(unused)]
365    pub(crate) fn set_userdata(&self, userdata: *mut c_void) {
366        unsafe { (*PLAYDATE.system.handle).setMenuItemUserdata.unwrap()(self.handle, userdata) }
367    }
368}
369
370impl Drop for MenuItem {
371    fn drop(&mut self) {
372        unsafe { (*PLAYDATE.system.handle).removeMenuItem.unwrap()(self.handle) }
373    }
374}
375
376#[derive(Debug)]
377pub struct ButtonState {
378    pub current: Buttons,
379    pub pushed: Buttons,
380    pub released: Buttons,
381}
382
383#[bitmask_enum::bitmask(u8)]
384pub enum Buttons {
385    Left = 1 << 0,
386    Right = 1 << 1,
387    Up = 1 << 2,
388    Down = 1 << 3,
389    B = 1 << 4,
390    A = 1 << 5,
391}