craydate/system.rs
1use core::cell::Cell;
2
3use crate::capi_state::CApiState;
4use crate::ctypes::*;
5use crate::system_event::SystemEventWatcher;
6use crate::time::{HighResolutionTimer, TimeTicks, WallClockTime};
7
8/// The state of the auto-lock system.
9#[derive(Debug, Copy, Clone, PartialEq, Eq)]
10pub enum AutoLock {
11 /// The auto-lock is disabled. The device will not lock when idle.
12 Disabled,
13 /// The auto-lock is enabled, and will lock when idle.
14 Enabled,
15}
16
17/// Whether using the crank makes sounds when docked or undocked.
18#[derive(Debug, Copy, Clone, PartialEq, Eq)]
19pub enum CrankSounds {
20 /// The crank is silent, in case the application wishes to provide their own sound effects.
21 Silent,
22 /// The crank makes sounds when docked or undocked.
23 DockingSounds,
24}
25
26/// Access to Playdate device's system resources such as the menu, clock, battery state, and system
27/// settings.
28#[derive(Debug)]
29pub struct System {
30 // Runtime tracking to ensure only one timer is active.
31 timer_active: Cell<bool>,
32}
33impl System {
34 pub(crate) fn new() -> Self {
35 System {
36 timer_active: Cell::new(false),
37 }
38 }
39
40 /// A watcher that lets you `await` for the next `SystemEvent`, such as the next frame with input
41 /// events from the Playdate device.
42 pub fn system_event_watcher(&self) -> SystemEventWatcher {
43 SystemEventWatcher::new()
44 }
45
46 /// Returns the current time in milliseconds.
47 pub fn current_time(&self) -> TimeTicks {
48 TimeTicks::from_milliseconds(unsafe { Self::fns().getCurrentTimeMilliseconds.unwrap()() })
49 }
50
51 /// Returns the current wall-clock time.
52 ///
53 /// This time is subject to drift and may go backwards. It can be useful when combined with
54 /// timezone information for displaying a clock, but prefer `current_time()` for most application
55 /// logic and for tracking elapsed time.
56 pub fn wall_clock_time(&self) -> WallClockTime {
57 let mut time = 0;
58 unsafe { Self::fns().getSecondsSinceEpoch.unwrap()(&mut time) };
59 WallClockTime(time)
60 }
61
62 /// Starts a high resolution timer, and returns an object representing it.
63 ///
64 /// # Panics
65 ///
66 /// There can only be one HighResolutionTimer active at a time, as multiple timers would clobber
67 /// each other inside Playdate. This function will panic if a HighResolutionTimer is started while
68 /// another is active. Drop the returned HighResolutionTimer to finish using it.
69 pub fn start_timer(&self) -> HighResolutionTimer {
70 if self.timer_active.get() {
71 panic!("HighResolutionTimer is already active.")
72 }
73 let timer = HighResolutionTimer::new(CApiState::get().csystem, &self.timer_active);
74 unsafe { Self::fns().resetElapsedTime.unwrap()() };
75 timer
76 }
77
78 /// Returns whether the global "flipped" system setting is set.
79 pub fn is_flipped_enabled(&self) -> bool {
80 unsafe { Self::fns().getFlipped.unwrap()() != 0 }
81 }
82
83 /// Returns whether the global "reduce flashing" system setting is set.
84 pub fn is_reduce_flashing_enabled(&self) -> bool {
85 unsafe { Self::fns().getReduceFlashing.unwrap()() != 0 }
86 }
87
88 /// Returns the battery percentage, which is a value between 0 and 1.
89 pub fn battery_percentage(&self) -> f32 {
90 unsafe { Self::fns().getBatteryPercentage.unwrap()() / 100f32 }
91 }
92
93 /// Returns the battery voltage.
94 pub fn battery_voltage(&self) -> f32 {
95 unsafe { Self::fns().getBatteryVoltage.unwrap()() }
96 }
97
98 /// Sets the bitmap to be displayed beside (and behind) the system menu.
99 ///
100 /// The bitmap _must_ be 400x240 pixels, and an error will be logged if it is not. All important
101 /// content should be in the left half of the image in an area 200 pixels wide, as the menu will
102 /// obscure the rest. The right side of the image will be visible briefly as the menu animates in
103 /// and out.
104 ///
105 /// The `xoffset` is clamped to between 0 and 200. If it is non-zero, the bitmap will be animated
106 /// to the left by `xoffset` pixels. For example, if the offset is 200 then the right 200 pixels
107 /// would be visible instead of the left 200 pixels while the menu is open.
108 ///
109 /// The bitmap will be copied, so the reference is not held.
110 pub fn set_menu_image(&mut self, bitmap: &crate::graphics::BitmapRef, xoffset: i32) {
111 // SAFETY: Playdate makes a copy from the given pointer, so we can pass it in and then drop the
112 // reference on `bitmap` when we leave the function.
113 //
114 // setMenuItem() takes a mutable pointer but does not write to the data inside it.
115 unsafe { Self::fns().setMenuImage.unwrap()(bitmap.cptr() as *mut _, xoffset.clamp(0, 200)) }
116 }
117
118 /// Removes the user-specified bitmap from beside the system menu. The default image is displayed
119 /// instead.
120 pub fn clear_menu_image(&mut self) {
121 unsafe { Self::fns().setMenuImage.unwrap()(core::ptr::null_mut(), 0) }
122 }
123
124 /// To use a peripheral, it must first be enabled via this function.
125 ///
126 /// By default, the accelerometer is disabled to save (a small amount of) power. Once enabled,
127 /// accelerometer data is not available until the next frame, and will be accessible from the
128 /// output of `FrameWatcher::next()`.
129 pub fn enable_peripherals(&mut self, which: Peripherals) {
130 CApiState::get().peripherals_enabled.set(which);
131 unsafe { Self::fns().setPeripheralsEnabled.unwrap()(which) }
132 }
133
134 /// Returns the current language of the system.
135 pub fn get_language(&self) -> Language {
136 unsafe { Self::fns().getLanguage.unwrap()() }
137 }
138
139 /// Disables or enables the 60 second auto-lock feature. When enabled, the timer is reset to 60
140 /// seconds.
141 ///
142 /// As of 0.10.3, the device will automatically lock if the user doesn’t press any buttons or use
143 /// the crank for more than 60 seconds. In order for games that expect longer periods without
144 /// interaction to continue to function, it is possible to manually disable the auto lock feature.
145 /// Note that when disabling the timeout, developers should take care to re-enable the timeout
146 /// when appropiate.
147 pub fn set_auto_lock(&mut self, val: AutoLock) {
148 let disabled = match val {
149 AutoLock::Disabled => 1,
150 AutoLock::Enabled => 0,
151 };
152 unsafe { Self::fns().setAutoLockDisabled.unwrap()(disabled) }
153 }
154
155 /// Disables or enables sound effects when the crank is docked or undocked.
156 ///
157 /// There are sound effects for various system events, such as the menu opening or closing, USB
158 /// cable plugged or unplugged, and the crank docked or undocked. Since games can receive
159 /// notification of the crank docking and undocking, and may incorporate this into the game, this
160 /// function can mute the default sounds for these events.
161 ///
162 /// # Return
163 ///
164 /// The function returns the previous value for this setting.
165 pub fn set_crank_sounds(&mut self, val: CrankSounds) -> CrankSounds {
166 let disabled = match val {
167 CrankSounds::Silent => 1,
168 CrankSounds::DockingSounds => 0,
169 };
170 let previous = unsafe { Self::fns().setCrankSoundsDisabled.unwrap()(disabled) };
171 match previous {
172 0 => CrankSounds::DockingSounds,
173 _ => CrankSounds::Silent,
174 }
175 }
176
177 pub(crate) fn fns() -> &'static craydate_sys::playdate_sys {
178 CApiState::get().csystem
179 }
180}