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