Skip to content

Commit cec18d0

Browse files
authored
Merge pull request #13 from PureSwift/feature/sdl3
Add SDL3Swift wrappers for OpenGL, keyboard, mouse, and misc APIs
2 parents 7d2a624 + 89ae462 commit cec18d0

13 files changed

Lines changed: 681 additions & 0 deletions

Sources/SDL3/Event.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ public enum SDLEvent {
3636
case gamepadRemoved(which: JoystickID)
3737
case gamepadButtonDown(which: JoystickID, button: SDLGamepad.Button)
3838
case gamepadButtonUp(which: JoystickID, button: SDLGamepad.Button)
39+
case mouseWheel(windowID: UInt32, x: Float, y: Float)
40+
case textInput(windowID: UInt32, text: String)
41+
case windowCloseRequested(windowID: UInt32)
3942

4043
/// An event that isn't yet modeled by `SDLEvent`. The raw `SDL_EventType` value is preserved.
4144
case unknown(UInt32)
@@ -83,6 +86,15 @@ public enum SDLEvent {
8386
case SDL_EVENT_GAMEPAD_BUTTON_UP:
8487
self = .gamepadButtonUp(which: JoystickID(rawValue: event.gbutton.which), button: SDLGamepad.Button(rawValue: Int32(event.gbutton.button)))
8588

89+
case SDL_EVENT_MOUSE_WHEEL:
90+
self = .mouseWheel(windowID: event.wheel.windowID, x: event.wheel.x, y: event.wheel.y)
91+
92+
case SDL_EVENT_TEXT_INPUT:
93+
self = .textInput(windowID: event.text.windowID, text: String(cString: event.text.text))
94+
95+
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
96+
self = .windowCloseRequested(windowID: event.window.windowID)
97+
8698
default:
8799
self = .unknown(event.type)
88100
}

Sources/SDL3/Gamepad.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,32 @@ public final class SDLGamepad: Identifiable {
4545
public func isPressed(_ button: Button) -> Bool {
4646
SDL_GetGamepadButton(internalPointer, SDL_GamepadButton(rawValue: button.rawValue))
4747
}
48+
49+
/// The implementation dependent name of the gamepad.
50+
public var name: String? {
51+
guard let cString = SDL_GetGamepadName(internalPointer) else { return nil }
52+
return String(cString: cString)
53+
}
54+
55+
/// The properties associated with this gamepad.
56+
public var properties: SDLProperties {
57+
SDLProperties(rawValue: SDL_GetGamepadProperties(internalPointer))
58+
}
59+
60+
/// Whether the gamepad has a rumble motor.
61+
public var hasRumble: Bool {
62+
properties.boolean(SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN)
63+
}
64+
65+
/// Start a rumble effect on the gamepad.
66+
public func rumble(lowFrequency: UInt16, highFrequency: UInt16, duration milliseconds: UInt32) throws(SDLError) {
67+
try SDL_RumbleGamepad(internalPointer, lowFrequency, highFrequency, milliseconds).sdlThrow(type: "SDLGamepad")
68+
}
69+
70+
/// Set the player index of the gamepad.
71+
public func setPlayerIndex(_ playerIndex: Int32) throws(SDLError) {
72+
try SDL_SetGamepadPlayerIndex(internalPointer, playerIndex).sdlThrow(type: "SDLGamepad")
73+
}
4874
}
4975

5076
// MARK: - Supporting Types
@@ -97,3 +123,37 @@ public extension SDLGamepad {
97123
public static var dpadRight: Button { Button(rawValue: SDL_GAMEPAD_BUTTON_DPAD_RIGHT.rawValue) }
98124
}
99125
}
126+
127+
public extension SDLGamepad.Axis {
128+
129+
/// A human-readable name for the axis, or `nil` if it doesn't have one.
130+
var stringValue: String? {
131+
guard let cString = SDL_GetGamepadStringForAxis(SDL_GamepadAxis(rawValue: rawValue)) else { return nil }
132+
return String(cString: cString)
133+
}
134+
}
135+
136+
public extension SDLGamepad.Button {
137+
138+
/// A human-readable name for the button, or `nil` if it doesn't have one.
139+
var stringValue: String? {
140+
guard let cString = SDL_GetGamepadStringForButton(SDL_GamepadButton(rawValue: rawValue)) else { return nil }
141+
return String(cString: cString)
142+
}
143+
}
144+
145+
public extension SDL {
146+
147+
/// Add support for gamepads that SDL is unaware of, loading a mapping file.
148+
static func addGamepadMappings(fromFile path: String) throws(SDLError) -> Int32 {
149+
150+
let count = SDL_AddGamepadMappingsFromFile(path)
151+
try (count >= 0).sdlThrow(type: "SDL")
152+
return count
153+
}
154+
155+
/// Whether the given joystick is supported by the gamepad interface.
156+
static func isGamepad(_ joystickID: JoystickID) -> Bool {
157+
SDL_IsGamepad(joystickID.rawValue)
158+
}
159+
}

Sources/SDL3/Joystick.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// Joystick.swift
3+
// SDL3
4+
//
5+
// Created by Alsey Coleman Miller on 7/7/26.
6+
//
7+
8+
import CSDL3
9+
10+
public extension SDL {
11+
12+
/// A list of currently connected joysticks.
13+
static var joystickIDs: [JoystickID] {
14+
15+
var count: Int32 = 0
16+
guard let pointer = SDL_GetJoysticks(&count) else { return [] }
17+
defer { SDL_free(pointer) }
18+
return (0 ..< Int(count)).map { JoystickID(rawValue: pointer[$0]) }
19+
}
20+
}
21+
22+
public extension JoystickID {
23+
24+
/// The implementation dependent name of the joystick.
25+
var name: String? {
26+
27+
guard let cString = SDL_GetJoystickNameForID(rawValue) else { return nil }
28+
return String(cString: cString)
29+
}
30+
}

Sources/SDL3/Keyboard.swift

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//
2+
// Keyboard.swift
3+
// SDL3
4+
//
5+
// Created by Alsey Coleman Miller on 7/7/26.
6+
//
7+
8+
import CSDL3
9+
10+
public extension SDL {
11+
12+
/// A snapshot of the current state of the keyboard, indexed by `Scancode.rawValue`.
13+
static var keyboardState: [Bool] {
14+
15+
var count: Int32 = 0
16+
guard let pointer = SDL_GetKeyboardState(&count) else { return [] }
17+
return (0 ..< Int(count)).map { pointer[$0] }
18+
}
19+
}
20+
21+
public extension Scancode {
22+
23+
/// Get a human-readable name for the scancode.
24+
///
25+
/// - Returns: `nil` if the scancode doesn't have a name.
26+
var name: String? {
27+
28+
let name = String(cString: SDL_GetScancodeName(SDL_Scancode(rawValue: rawValue)))
29+
return name.isEmpty ? nil : name
30+
}
31+
}
32+
33+
// MARK: - Named Scancodes
34+
35+
public extension Scancode {
36+
37+
static var a: Scancode { Scancode(rawValue: SDL_SCANCODE_A.rawValue) }
38+
static var b: Scancode { Scancode(rawValue: SDL_SCANCODE_B.rawValue) }
39+
static var c: Scancode { Scancode(rawValue: SDL_SCANCODE_C.rawValue) }
40+
static var d: Scancode { Scancode(rawValue: SDL_SCANCODE_D.rawValue) }
41+
static var e: Scancode { Scancode(rawValue: SDL_SCANCODE_E.rawValue) }
42+
static var f: Scancode { Scancode(rawValue: SDL_SCANCODE_F.rawValue) }
43+
static var g: Scancode { Scancode(rawValue: SDL_SCANCODE_G.rawValue) }
44+
static var h: Scancode { Scancode(rawValue: SDL_SCANCODE_H.rawValue) }
45+
static var i: Scancode { Scancode(rawValue: SDL_SCANCODE_I.rawValue) }
46+
static var j: Scancode { Scancode(rawValue: SDL_SCANCODE_J.rawValue) }
47+
static var k: Scancode { Scancode(rawValue: SDL_SCANCODE_K.rawValue) }
48+
static var l: Scancode { Scancode(rawValue: SDL_SCANCODE_L.rawValue) }
49+
static var m: Scancode { Scancode(rawValue: SDL_SCANCODE_M.rawValue) }
50+
static var n: Scancode { Scancode(rawValue: SDL_SCANCODE_N.rawValue) }
51+
static var o: Scancode { Scancode(rawValue: SDL_SCANCODE_O.rawValue) }
52+
static var p: Scancode { Scancode(rawValue: SDL_SCANCODE_P.rawValue) }
53+
static var q: Scancode { Scancode(rawValue: SDL_SCANCODE_Q.rawValue) }
54+
static var r: Scancode { Scancode(rawValue: SDL_SCANCODE_R.rawValue) }
55+
static var s: Scancode { Scancode(rawValue: SDL_SCANCODE_S.rawValue) }
56+
static var t: Scancode { Scancode(rawValue: SDL_SCANCODE_T.rawValue) }
57+
static var u: Scancode { Scancode(rawValue: SDL_SCANCODE_U.rawValue) }
58+
static var v: Scancode { Scancode(rawValue: SDL_SCANCODE_V.rawValue) }
59+
static var w: Scancode { Scancode(rawValue: SDL_SCANCODE_W.rawValue) }
60+
static var x: Scancode { Scancode(rawValue: SDL_SCANCODE_X.rawValue) }
61+
static var y: Scancode { Scancode(rawValue: SDL_SCANCODE_Y.rawValue) }
62+
static var z: Scancode { Scancode(rawValue: SDL_SCANCODE_Z.rawValue) }
63+
64+
static var escape: Scancode { Scancode(rawValue: SDL_SCANCODE_ESCAPE.rawValue) }
65+
static var `return`: Scancode { Scancode(rawValue: SDL_SCANCODE_RETURN.rawValue) }
66+
static var space: Scancode { Scancode(rawValue: SDL_SCANCODE_SPACE.rawValue) }
67+
static var tab: Scancode { Scancode(rawValue: SDL_SCANCODE_TAB.rawValue) }
68+
69+
static var up: Scancode { Scancode(rawValue: SDL_SCANCODE_UP.rawValue) }
70+
static var down: Scancode { Scancode(rawValue: SDL_SCANCODE_DOWN.rawValue) }
71+
static var left: Scancode { Scancode(rawValue: SDL_SCANCODE_LEFT.rawValue) }
72+
static var right: Scancode { Scancode(rawValue: SDL_SCANCODE_RIGHT.rawValue) }
73+
74+
static var f8: Scancode { Scancode(rawValue: SDL_SCANCODE_F8.rawValue) }
75+
static var f9: Scancode { Scancode(rawValue: SDL_SCANCODE_F9.rawValue) }
76+
static var f10: Scancode { Scancode(rawValue: SDL_SCANCODE_F10.rawValue) }
77+
78+
static var minus: Scancode { Scancode(rawValue: SDL_SCANCODE_MINUS.rawValue) }
79+
static var equals: Scancode { Scancode(rawValue: SDL_SCANCODE_EQUALS.rawValue) }
80+
static var leftBracket: Scancode { Scancode(rawValue: SDL_SCANCODE_LEFTBRACKET.rawValue) }
81+
static var rightBracket: Scancode { Scancode(rawValue: SDL_SCANCODE_RIGHTBRACKET.rawValue) }
82+
static var backslash: Scancode { Scancode(rawValue: SDL_SCANCODE_BACKSLASH.rawValue) }
83+
static var semicolon: Scancode { Scancode(rawValue: SDL_SCANCODE_SEMICOLON.rawValue) }
84+
static var apostrophe: Scancode { Scancode(rawValue: SDL_SCANCODE_APOSTROPHE.rawValue) }
85+
static var grave: Scancode { Scancode(rawValue: SDL_SCANCODE_GRAVE.rawValue) }
86+
87+
static var keypadDivide: Scancode { Scancode(rawValue: SDL_SCANCODE_KP_DIVIDE.rawValue) }
88+
static var keypadMultiply: Scancode { Scancode(rawValue: SDL_SCANCODE_KP_MULTIPLY.rawValue) }
89+
static var keypadMinus: Scancode { Scancode(rawValue: SDL_SCANCODE_KP_MINUS.rawValue) }
90+
static var keypadPlus: Scancode { Scancode(rawValue: SDL_SCANCODE_KP_PLUS.rawValue) }
91+
static var keypadEnter: Scancode { Scancode(rawValue: SDL_SCANCODE_KP_ENTER.rawValue) }
92+
static var keypadPeriod: Scancode { Scancode(rawValue: SDL_SCANCODE_KP_PERIOD.rawValue) }
93+
94+
static var leftAlt: Scancode { Scancode(rawValue: SDL_SCANCODE_LALT.rawValue) }
95+
static var rightAlt: Scancode { Scancode(rawValue: SDL_SCANCODE_RALT.rawValue) }
96+
static var leftGUI: Scancode { Scancode(rawValue: SDL_SCANCODE_LGUI.rawValue) }
97+
static var rightGUI: Scancode { Scancode(rawValue: SDL_SCANCODE_RGUI.rawValue) }
98+
static var leftControl: Scancode { Scancode(rawValue: SDL_SCANCODE_LCTRL.rawValue) }
99+
static var rightControl: Scancode { Scancode(rawValue: SDL_SCANCODE_RCTRL.rawValue) }
100+
static var leftShift: Scancode { Scancode(rawValue: SDL_SCANCODE_LSHIFT.rawValue) }
101+
static var rightShift: Scancode { Scancode(rawValue: SDL_SCANCODE_RSHIFT.rawValue) }
102+
}

Sources/SDL3/Log.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// Log.swift
3+
// SDL3
4+
//
5+
// Created by Alsey Coleman Miller on 7/7/26.
6+
//
7+
8+
import CSDL3
9+
10+
public extension SDL {
11+
12+
/// Log a message with the given category and priority.
13+
///
14+
/// - Note: SDL's variadic logging functions (`SDL_Log`, `SDL_LogError`, ...) aren't callable
15+
/// from Swift, so this routes through `SDL_LogMessageV` with a fixed `"%s"` format string.
16+
static func log(_ message: String, category: LogCategory = .application, priority: LogPriority = .info) {
17+
18+
message.withCString { cString in
19+
withVaList([cString]) { arguments in
20+
SDL_LogMessageV(category.rawValue, priority.sdlValue, "%s", arguments)
21+
}
22+
}
23+
}
24+
25+
/// Set the priority of all log categories.
26+
static func setLogPriorities(_ priority: LogPriority) {
27+
SDL_SetLogPriorities(priority.sdlValue)
28+
}
29+
}
30+
31+
// MARK: - Supporting Types
32+
33+
/// A category of a log message.
34+
public struct LogCategory: RawRepresentable, Equatable, Hashable, Sendable {
35+
36+
public let rawValue: Int32
37+
38+
public init(rawValue: Int32) {
39+
self.rawValue = rawValue
40+
}
41+
42+
public static var application: LogCategory { LogCategory(rawValue: Int32(SDL_LOG_CATEGORY_APPLICATION.rawValue)) }
43+
}
44+
45+
/// The priority of a log message.
46+
public struct LogPriority: RawRepresentable, Equatable, Hashable, Sendable {
47+
48+
public let rawValue: UInt32
49+
50+
public init(rawValue: UInt32) {
51+
self.rawValue = rawValue
52+
}
53+
54+
internal var sdlValue: SDL_LogPriority {
55+
SDL_LogPriority(rawValue: rawValue)
56+
}
57+
58+
public static var verbose: LogPriority { LogPriority(rawValue: SDL_LOG_PRIORITY_VERBOSE.rawValue) }
59+
public static var info: LogPriority { LogPriority(rawValue: SDL_LOG_PRIORITY_INFO.rawValue) }
60+
public static var warn: LogPriority { LogPriority(rawValue: SDL_LOG_PRIORITY_WARN.rawValue) }
61+
public static var error: LogPriority { LogPriority(rawValue: SDL_LOG_PRIORITY_ERROR.rawValue) }
62+
public static var critical: LogPriority { LogPriority(rawValue: SDL_LOG_PRIORITY_CRITICAL.rawValue) }
63+
}

Sources/SDL3/MessageBox.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// MessageBox.swift
3+
// SDL3
4+
//
5+
// Created by Alsey Coleman Miller on 7/7/26.
6+
//
7+
8+
import CSDL3
9+
10+
public extension SDL {
11+
12+
/// Display a simple modal message box.
13+
static func showSimpleMessageBox(flags: MessageBoxFlags, title: String, message: String, window: SDLWindow? = nil) throws(SDLError) {
14+
15+
try SDL_ShowSimpleMessageBox(flags.rawValue, title, message, window?.internalPointer).sdlThrow(type: "SDL")
16+
}
17+
}
18+
19+
// MARK: - Supporting Types
20+
21+
/// Flags for `SDL.showSimpleMessageBox(flags:title:message:window:)`.
22+
public struct MessageBoxFlags: OptionSet, Sendable {
23+
24+
public let rawValue: UInt32
25+
26+
public init(rawValue: UInt32) {
27+
self.rawValue = rawValue
28+
}
29+
30+
public static var error: MessageBoxFlags { MessageBoxFlags(rawValue: 0x00000010) }
31+
public static var warning: MessageBoxFlags { MessageBoxFlags(rawValue: 0x00000020) }
32+
public static var information: MessageBoxFlags { MessageBoxFlags(rawValue: 0x00000040) }
33+
}

Sources/SDL3/Mouse.swift

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// Mouse.swift
3+
// SDL3
4+
//
5+
// Created by Alsey Coleman Miller on 7/7/26.
6+
//
7+
8+
import CSDL3
9+
10+
public extension SDL {
11+
12+
/// The current state of the mouse, in window-relative coordinates.
13+
static var mouseState: (buttons: MouseButtonFlags, x: Float, y: Float) {
14+
15+
var x: Float = 0
16+
var y: Float = 0
17+
let buttons = SDL_GetMouseState(&x, &y)
18+
return (MouseButtonFlags(rawValue: buttons), x, y)
19+
}
20+
21+
/// The current state of the mouse, relative to the last call (or since event initialization).
22+
static var relativeMouseState: (buttons: MouseButtonFlags, x: Float, y: Float) {
23+
24+
var x: Float = 0
25+
var y: Float = 0
26+
let buttons = SDL_GetRelativeMouseState(&x, &y)
27+
return (MouseButtonFlags(rawValue: buttons), x, y)
28+
}
29+
}
30+
31+
public extension SDLWindow {
32+
33+
/// Whether relative mouse mode is enabled for the window.
34+
var relativeMouseMode: Bool {
35+
36+
get { SDL_GetWindowRelativeMouseMode(internalPointer) }
37+
}
38+
39+
/// Set relative mouse mode for the window.
40+
func setRelativeMouseMode(_ enabled: Bool) throws(SDLError) {
41+
42+
try SDL_SetWindowRelativeMouseMode(internalPointer, enabled).sdlThrow(type: "SDLWindow")
43+
}
44+
45+
/// Set the window's mouse grab mode.
46+
func setMouseGrab(_ grabbed: Bool) throws(SDLError) {
47+
48+
try SDL_SetWindowMouseGrab(internalPointer, grabbed).sdlThrow(type: "SDLWindow")
49+
}
50+
}
51+
52+
// MARK: - Supporting Types
53+
54+
/// A bitmask of pressed mouse buttons, as reported by `SDL.mouseState`.
55+
public struct MouseButtonFlags: OptionSet, Sendable {
56+
57+
public let rawValue: UInt32
58+
59+
public init(rawValue: UInt32) {
60+
self.rawValue = rawValue
61+
}
62+
63+
public static var left: MouseButtonFlags { MouseButtonFlags(rawValue: 1 << (0)) }
64+
public static var middle: MouseButtonFlags { MouseButtonFlags(rawValue: 1 << (1)) }
65+
public static var right: MouseButtonFlags { MouseButtonFlags(rawValue: 1 << (2)) }
66+
public static var x1: MouseButtonFlags { MouseButtonFlags(rawValue: 1 << (3)) }
67+
public static var x2: MouseButtonFlags { MouseButtonFlags(rawValue: 1 << (4)) }
68+
}

0 commit comments

Comments
 (0)