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}