2026-07-24 10:33:45 +02:00
|
|
|
internal import CPlaydate
|
|
|
|
|
|
2026-07-24 11:10:25 +02:00
|
|
|
/// The system API: logging, input, time, menu items, and device state.
|
|
|
|
|
public enum System {}
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-07-24 11:10:25 +02:00
|
|
|
extension System {
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Cached `playdate->system` table.
|
2026-07-25 10:43:32 +02:00
|
|
|
private static var api: UnsafePointer<playdate_sys> { Playdate.systemAPI.unsafelyUnwrapped }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
|
|
|
|
// MARK: - Memory
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// System allocator (`realloc` semantics): `nil` allocates; `size` 0 frees, returns `nil`.
|
2026-07-24 10:33:45 +02:00
|
|
|
@discardableResult
|
|
|
|
|
public static func realloc(_ pointer: UnsafeMutableRawPointer?, size: Int) -> UnsafeMutableRawPointer? {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.realloc.unsafelyUnwrapped(pointer, size)
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Frees OS-allocated memory, e.g. `localizedText(forKey:language:)` strings.
|
2026-07-24 10:33:45 +02:00
|
|
|
static func systemFree(_ pointer: UnsafeMutableRawPointer?) {
|
2026-07-25 07:23:19 +02:00
|
|
|
_ = api.pointee.realloc.unsafelyUnwrapped(pointer, 0)
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Logging
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Logs to the device serial or simulator console.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func log(_ message: String) {
|
2026-09-18 11:58:57 +02:00
|
|
|
message.withCString { cplaydate_log(Playdate.apiPointer, $0) }
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Logs `message` as an error, then pauses execution.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func error(_ message: String) {
|
2026-09-18 11:58:57 +02:00
|
|
|
message.withCString { cplaydate_error(Playdate.apiPointer, $0) }
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
// MARK: - Language and time
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-07-25 07:23:19 +02:00
|
|
|
public static var language: Language { Language(api.pointee.getLanguage.unsafelyUnwrapped()) }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Milliseconds since an arbitrary point; pauses while asleep; wraps after ~49 days.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static var currentTimeMilliseconds: UInt32 {
|
2026-07-25 07:23:19 +02:00
|
|
|
UInt32(api.pointee.getCurrentTimeMilliseconds.unsafelyUnwrapped())
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Seconds, plus millisecond remainder, since 2000-01-01 00:00 UTC.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static var secondsSinceEpoch: (seconds: UInt32, milliseconds: UInt32) {
|
|
|
|
|
var milliseconds: UInt32 = 0
|
|
|
|
|
let seconds = withUnsafeMutablePointer(to: &milliseconds) {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.getSecondsSinceEpoch.unsafelyUnwrapped($0)
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
return (UInt32(seconds), milliseconds)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Seconds since `resetElapsedTime()`, with microsecond accuracy.
|
2026-07-25 07:23:19 +02:00
|
|
|
public static var elapsedTime: Float { api.pointee.getElapsedTime.unsafelyUnwrapped() }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-07-25 07:23:19 +02:00
|
|
|
public static func resetElapsedTime() { api.pointee.resetElapsedTime.unsafelyUnwrapped() }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Offset from UTC, in seconds.
|
2026-07-25 07:23:19 +02:00
|
|
|
public static var timezoneOffset: Int32 { api.pointee.getTimezoneOffset.unsafelyUnwrapped() }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// The user's 24-hour time setting.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static var shouldDisplay24HourTime: Bool {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.shouldDisplay24HourTime.unsafelyUnwrapped() != 0
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// `epoch` is seconds since 2000-01-01.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func convertEpochToDateTime(_ epoch: UInt32) -> DateTime {
|
|
|
|
|
var dateTime = PDDateTime()
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.convertEpochToDateTime.unsafelyUnwrapped(epoch, &dateTime)
|
2026-07-24 10:33:45 +02:00
|
|
|
return DateTime(dateTime)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Returns seconds since 2000-01-01.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func convertDateTimeToEpoch(_ dateTime: DateTime) -> UInt32 {
|
|
|
|
|
var cValue = dateTime.cValue
|
2026-07-25 07:23:19 +02:00
|
|
|
return api.pointee.convertDateTimeToEpoch.unsafelyUnwrapped(&cValue)
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Blocks execution.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func delay(milliseconds: UInt32) {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.delay.unsafelyUnwrapped(milliseconds)
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Asynchronously fetches the server time: `time` is seconds since 2000-01-01 UTC,
|
|
|
|
|
/// as a string. One completion at a time; calling again replaces a pending one.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func getServerTime(_ completion: @escaping (_ time: String?, _ error: String?) -> Void) {
|
|
|
|
|
serverTimeCompletion = completion
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.getServerTime.unsafelyUnwrapped { time, error in
|
2026-07-24 11:10:25 +02:00
|
|
|
let completion = System.serverTimeCompletion
|
|
|
|
|
System.serverTimeCompletion = nil
|
2026-07-24 10:33:45 +02:00
|
|
|
completion?(String(playdateCString: time), String(playdateCString: error))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nonisolated(unsafe) private static var serverTimeCompletion: ((String?, String?) -> Void)?
|
|
|
|
|
|
|
|
|
|
// MARK: - Update loop
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Sets the per-frame callback, replacing any previous one; return `true` to redraw.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func setUpdateCallback(_ callback: @escaping () -> Bool) {
|
|
|
|
|
updateCallback = callback
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.setUpdateCallback.unsafelyUnwrapped({ _ in
|
2026-07-24 11:10:25 +02:00
|
|
|
System.updateCallback?() == true ? 1 : 0
|
2026-07-24 10:33:45 +02:00
|
|
|
}, nil)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nonisolated(unsafe) private static var updateCallback: (() -> Bool)?
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Draws the current FPS at (`x`, `y`).
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func drawFPS(x: Int = 0, y: Int = 0) {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.drawFPS.unsafelyUnwrapped(Int32(x), Int32(y))
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Input
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Buttons held now, and those pushed or released during the previous update.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static var buttonState: (current: Buttons, pushed: Buttons, released: Buttons) {
|
|
|
|
|
var current = PDButtons(0), pushed = PDButtons(0), released = PDButtons(0)
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.getButtonState.unsafelyUnwrapped(¤t, &pushed, &released)
|
2026-07-24 10:33:45 +02:00
|
|
|
return (Buttons(current), Buttons(pushed), Buttons(released))
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Calls `callback` per button down/up in the previous update, replacing any previous
|
|
|
|
|
/// one; `nil` removes it. `queueSize`: events buffered per update (5 suffices at 30 FPS).
|
|
|
|
|
/// `callback` returns 0, or non-zero to signal an error.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func setButtonCallback(queueSize: Int = 5,
|
|
|
|
|
_ callback: ((_ button: Buttons, _ isDown: Bool, _ when: UInt32) -> Int32)?) {
|
|
|
|
|
buttonCallback = callback
|
|
|
|
|
if callback != nil {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.setButtonCallback.unsafelyUnwrapped({ button, down, when, _ in
|
2026-07-24 11:10:25 +02:00
|
|
|
System.buttonCallback?(Buttons(button), down != 0, when) ?? 0
|
2026-07-24 10:33:45 +02:00
|
|
|
}, nil, Int32(queueSize))
|
|
|
|
|
} else {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.setButtonCallback.unsafelyUnwrapped(nil, nil, Int32(queueSize))
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nonisolated(unsafe) private static var buttonCallback: ((Buttons, Bool, UInt32) -> Int32)?
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Enables `peripherals`, disabling the rest; accelerometer data arrives next update.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func setPeripheralsEnabled(_ peripherals: Peripherals) {
|
2026-07-25 10:10:43 +02:00
|
|
|
api.pointee.setPeripheralsEnabled.unsafelyUnwrapped(PDPeripherals(PDPeripherals.RawValue(peripherals.rawValue)))
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Last reading, in g; requires `setPeripheralsEnabled(.accelerometer)`.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static var accelerometer: (x: Float, y: Float, z: Float) {
|
|
|
|
|
var x: Float = 0, y: Float = 0, z: Float = 0
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.getAccelerometer.unsafelyUnwrapped(&x, &y, &z)
|
2026-07-24 10:33:45 +02:00
|
|
|
return (x, y, z)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Degrees moved since last read; negative is counterclockwise.
|
2026-07-25 07:23:19 +02:00
|
|
|
public static var crankChange: Float { api.pointee.getCrankChange.unsafelyUnwrapped() }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Degrees, 0...360; 0 points up, increasing clockwise viewed from the right side.
|
2026-07-25 07:23:19 +02:00
|
|
|
public static var crankAngle: Float { api.pointee.getCrankAngle.unsafelyUnwrapped() }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-07-25 07:23:19 +02:00
|
|
|
public static var isCrankDocked: Bool { api.pointee.isCrankDocked.unsafelyUnwrapped() != 0 }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Toggles crank dock/undock sounds; returns the previous `disabled` value.
|
2026-07-24 10:33:45 +02:00
|
|
|
@discardableResult
|
|
|
|
|
public static func setCrankSoundsDisabled(_ disabled: Bool) -> Bool {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.setCrankSoundsDisabled.unsafelyUnwrapped(disabled ? 1 : 0) != 0
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// The user's "flipped" system setting.
|
2026-07-25 07:23:19 +02:00
|
|
|
public static var isFlipped: Bool { api.pointee.getFlipped.unsafelyUnwrapped() != 0 }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Toggles the 3-minute auto lock; either call resets its timer.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func setAutoLockDisabled(_ disabled: Bool) {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.setAutoLockDisabled.unsafelyUnwrapped(disabled ? 1 : 0)
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Calls `callback` for serial `msg <text>` messages; `nil` removes it. One closure
|
|
|
|
|
/// at a time.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func setSerialMessageCallback(_ callback: ((String) -> Void)?) {
|
|
|
|
|
serialMessageCallback = callback
|
|
|
|
|
if callback != nil {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.setSerialMessageCallback.unsafelyUnwrapped { data in
|
2026-07-24 10:33:45 +02:00
|
|
|
guard let message = String(playdateCString: data) else { return }
|
2026-07-24 11:10:25 +02:00
|
|
|
System.serialMessageCallback?(message)
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.setSerialMessageCallback.unsafelyUnwrapped(nil)
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nonisolated(unsafe) private static var serialMessageCallback: ((String) -> Void)?
|
|
|
|
|
|
|
|
|
|
// MARK: - System menu
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
// Retains items until removed; the OS holds only unretained userdata pointers.
|
2026-07-24 10:33:45 +02:00
|
|
|
nonisolated(unsafe) private static var liveMenuItems: [MenuItem] = []
|
|
|
|
|
|
|
|
|
|
private static let menuItemTrampoline: @convention(c) (UnsafeMutableRawPointer?) -> Void = { userdata in
|
|
|
|
|
guard let userdata else { return }
|
|
|
|
|
let item = Unmanaged<MenuItem>.fromOpaque(userdata).takeUnretainedValue()
|
|
|
|
|
item.onSelect(item)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Adds an action item; `onSelect` runs when picked. `nil` if the OS can't add it.
|
2026-07-24 10:33:45 +02:00
|
|
|
@discardableResult
|
|
|
|
|
public static func addMenuItem(title: String, onSelect: @escaping (MenuItem) -> Void) -> MenuItem? {
|
|
|
|
|
var item: MenuItem?
|
2026-09-18 11:58:57 +02:00
|
|
|
title.withCString { cTitle in
|
2026-07-25 07:23:19 +02:00
|
|
|
let pointer = api.pointee.addMenuItem.unsafelyUnwrapped(cTitle, menuItemTrampoline, nil)
|
2026-07-24 10:33:45 +02:00
|
|
|
item = MenuItem(pointer: pointer, onSelect: onSelect)
|
|
|
|
|
}
|
|
|
|
|
return registered(item)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Adds a checkmark item; `onSelect` runs when the menu closes after a toggle.
|
|
|
|
|
/// `nil` if the OS can't add it.
|
2026-07-24 10:33:45 +02:00
|
|
|
@discardableResult
|
|
|
|
|
public static func addCheckmarkMenuItem(title: String, isChecked: Bool = false,
|
|
|
|
|
onSelect: @escaping (MenuItem) -> Void) -> MenuItem? {
|
|
|
|
|
var item: MenuItem?
|
2026-09-18 11:58:57 +02:00
|
|
|
title.withCString { cTitle in
|
2026-07-25 07:23:19 +02:00
|
|
|
let pointer = api.pointee.addCheckmarkMenuItem.unsafelyUnwrapped(
|
2026-07-24 10:33:45 +02:00
|
|
|
cTitle, isChecked ? 1 : 0, menuItemTrampoline, nil)
|
|
|
|
|
item = MenuItem(pointer: pointer, onSelect: onSelect)
|
|
|
|
|
}
|
|
|
|
|
return registered(item)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Adds an item cycling through `options`; `onSelect` runs when the menu closes
|
|
|
|
|
/// after a change. `nil` if the OS can't add it.
|
2026-07-24 10:33:45 +02:00
|
|
|
@discardableResult
|
|
|
|
|
public static func addOptionsMenuItem(title: String, options: [String],
|
|
|
|
|
onSelect: @escaping (MenuItem) -> Void) -> MenuItem? {
|
2026-09-18 12:45:39 +02:00
|
|
|
// The OS keeps the title pointers; the copies live until the item is removed.
|
2026-07-24 10:33:45 +02:00
|
|
|
let copies = options.map { $0.copiedPlaydateCString() }
|
|
|
|
|
var cOptions: [UnsafePointer<CChar>?] = copies.map { UnsafePointer($0) }
|
|
|
|
|
var item: MenuItem?
|
2026-09-18 11:58:57 +02:00
|
|
|
title.withCString { cTitle in
|
2026-07-24 10:33:45 +02:00
|
|
|
cOptions.withUnsafeMutableBufferPointer { buffer in
|
2026-07-25 07:23:19 +02:00
|
|
|
let pointer = api.pointee.addOptionsMenuItem.unsafelyUnwrapped(
|
2026-07-24 10:33:45 +02:00
|
|
|
cTitle, buffer.baseAddress, Int32(options.count), menuItemTrampoline, nil)
|
|
|
|
|
item = MenuItem(pointer: pointer, retainedOptionTitles: copies, onSelect: onSelect)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return registered(item)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Sets `item` as its own userdata and retains it until removed.
|
2026-07-24 10:33:45 +02:00
|
|
|
private static func registered(_ item: MenuItem?) -> MenuItem? {
|
|
|
|
|
guard let item else { return nil }
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.setMenuItemUserdata.unsafelyUnwrapped(
|
2026-07-24 10:33:45 +02:00
|
|
|
item.pointer, Unmanaged.passUnretained(item).toOpaque())
|
|
|
|
|
liveMenuItems.append(item)
|
|
|
|
|
return item
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Removes `item`; the OS frees it, so don't use `item` afterwards.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func removeMenuItem(_ item: MenuItem) {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.removeMenuItem.unsafelyUnwrapped(item.pointer)
|
2026-07-24 10:33:45 +02:00
|
|
|
item.deallocateRetainedTitles()
|
|
|
|
|
liveMenuItems.removeAll { $0 === item }
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Removes all custom items; existing `MenuItem`s must not be used afterwards.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func removeAllMenuItems() {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.removeAllMenuItems.unsafelyUnwrapped()
|
2026-07-24 10:33:45 +02:00
|
|
|
for item in liveMenuItems { item.deallocateRetainedTitles() }
|
|
|
|
|
liveMenuItems = []
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Sets the 400x240 menu image; only its left 200 px stay visible. `xOffset`
|
|
|
|
|
/// (0...200 px) shifts it left as the menu animates in.
|
2026-07-24 11:10:25 +02:00
|
|
|
public static func setMenuImage(_ bitmap: Graphics.Bitmap?, xOffset: Int = 0) {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.setMenuImage.unsafelyUnwrapped(bitmap?.pointer, Int32(xOffset))
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Device state
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// The user's "reduce flashing" accessibility setting.
|
2026-07-25 07:23:19 +02:00
|
|
|
public static var reduceFlashing: Bool { api.pointee.getReduceFlashing.unsafelyUnwrapped() != 0 }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// 0 (empty)...100 (full).
|
2026-07-25 07:23:19 +02:00
|
|
|
public static var batteryPercentage: Float { api.pointee.getBatteryPercentage.unsafelyUnwrapped() }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// In volts.
|
2026-07-25 07:23:19 +02:00
|
|
|
public static var batteryVoltage: Float { api.pointee.getBatteryVoltage.unsafelyUnwrapped() }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Flushes the CPU instruction cache; needed only after modifying code at runtime.
|
2026-07-25 07:23:19 +02:00
|
|
|
public static func clearICache() { api.pointee.clearICache.unsafelyUnwrapped() }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Reinitializes the runtime and restarts the game with `launchArguments`.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func restartGame(launchArguments: String? = nil) {
|
|
|
|
|
if let launchArguments {
|
2026-09-18 11:58:57 +02:00
|
|
|
launchArguments.withCString { api.pointee.restartGame.unsafelyUnwrapped($0) }
|
2026-07-24 10:33:45 +02:00
|
|
|
} else {
|
2026-07-25 07:23:19 +02:00
|
|
|
api.pointee.restartGame.unsafelyUnwrapped(nil)
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Launch arguments (simulator command line, device `run`, or `restartGame`) and
|
|
|
|
|
/// the loaded game's path.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static var launchArguments: (arguments: String?, path: String?) {
|
|
|
|
|
var path: UnsafePointer<CChar>?
|
2026-07-25 07:23:19 +02:00
|
|
|
let arguments = api.pointee.getLaunchArgs.unsafelyUnwrapped(&path)
|
2026-07-24 10:33:45 +02:00
|
|
|
return (String(playdateCString: arguments), String(playdateCString: path))
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Sends `data` with `command` over Mirror; `false` if not mirroring or the send fails.
|
2026-07-24 10:33:45 +02:00
|
|
|
@discardableResult
|
2026-09-18 12:06:21 +02:00
|
|
|
public static func sendMirrorData(command: UInt8, data: Span<UInt8>) -> Bool {
|
|
|
|
|
data.withUnsafeBufferPointer { buffer in
|
|
|
|
|
// The C API takes a non-const pointer but only reads the data.
|
|
|
|
|
api.pointee.sendMirrorData.unsafelyUnwrapped(
|
|
|
|
|
command, UnsafeMutableRawPointer(mutating: buffer.baseAddress), Int32(buffer.count))
|
|
|
|
|
}
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// OS version, system language, and the SDK version the game was built with.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static var info: Info {
|
2026-07-25 07:23:19 +02:00
|
|
|
let info = api.pointee.getSystemInfo.unsafelyUnwrapped().unsafelyUnwrapped.pointee
|
2026-07-24 10:33:45 +02:00
|
|
|
return Info(osVersion: info.osversion,
|
|
|
|
|
language: Language(info.language),
|
|
|
|
|
pdxVersion: info.pdxversion)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Looks up `key` in `language`'s `.strings` file; `nil` if the key or file is missing.
|
|
|
|
|
/// `.system` falls back to the other language's file if the system one can't load.
|
2026-07-24 10:33:45 +02:00
|
|
|
public static func localizedText(forKey key: String, language: Language = .system) -> String? {
|
2026-09-18 11:58:57 +02:00
|
|
|
key.withCString { cKey in
|
2026-07-25 07:23:19 +02:00
|
|
|
guard let cString = api.pointee.getLocalizedText.unsafelyUnwrapped(cKey, language.cValue) else {
|
2026-07-24 10:33:45 +02:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
let text = String(playdateCString: cString)
|
|
|
|
|
systemFree(cString)
|
|
|
|
|
return text
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Menu volume, 0...1.
|
2026-07-25 07:23:19 +02:00
|
|
|
public static var volume: Float { api.pointee.getVolume.unsafelyUnwrapped() }
|
2026-07-24 10:33:45 +02:00
|
|
|
|
|
|
|
|
public static var powerStatus: PowerStatus {
|
2026-07-25 10:10:43 +02:00
|
|
|
PowerStatus(rawValue: UInt32(api.pointee.getPowerStatus.unsafelyUnwrapped().rawValue))
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-18 12:45:39 +02:00
|
|
|
/// Sends the game `kEventTerminate`, then quits to the launcher.
|
2026-07-25 07:23:19 +02:00
|
|
|
public static func exitToLauncher() { api.pointee.exitToLauncher.unsafelyUnwrapped() }
|
2026-07-24 10:33:45 +02:00
|
|
|
}
|