mirror of
https://github.com/GayPizzaSpecifications/voxelotl-engine.git
synced 2025-08-03 13:11:33 +00:00
add mouse support
This commit is contained in:
226
Sources/Voxelotl/Input/GameController.swift
Normal file
226
Sources/Voxelotl/Input/GameController.swift
Normal file
@ -0,0 +1,226 @@
|
||||
import SDL3
|
||||
|
||||
public class GameController {
|
||||
public struct Pad {
|
||||
public enum Axes {
|
||||
case leftStickX, leftStickY
|
||||
case rightStickX, rightStickY
|
||||
case leftTrigger, rightTrigger
|
||||
|
||||
internal var sdlEnum: SDL_GamepadAxis {
|
||||
switch self {
|
||||
case .leftStickX: SDL_GAMEPAD_AXIS_LEFTX
|
||||
case .leftStickY: SDL_GAMEPAD_AXIS_LEFTY
|
||||
case .rightStickX: SDL_GAMEPAD_AXIS_RIGHTX
|
||||
case .rightStickY: SDL_GAMEPAD_AXIS_RIGHTY
|
||||
case .leftTrigger: SDL_GAMEPAD_AXIS_LEFT_TRIGGER
|
||||
case .rightTrigger: SDL_GAMEPAD_AXIS_RIGHT_TRIGGER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Buttons: OptionSet {
|
||||
public let rawValue: Int
|
||||
public init(rawValue: Int) { self.rawValue = rawValue }
|
||||
|
||||
static let east = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_EAST.rawValue)
|
||||
static let south = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_SOUTH.rawValue)
|
||||
static let north = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_NORTH.rawValue)
|
||||
static let west = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_WEST.rawValue)
|
||||
static let back = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_BACK.rawValue)
|
||||
static let start = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_START.rawValue)
|
||||
static let guide = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_GUIDE.rawValue)
|
||||
static let leftStick = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_LEFT_STICK.rawValue)
|
||||
static let rightStick = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK.rawValue)
|
||||
static let leftBumper = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER.rawValue)
|
||||
static let rightBumper = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER.rawValue)
|
||||
static let dpadLeft = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT.rawValue)
|
||||
static let dpadRight = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT.rawValue)
|
||||
static let dpadUp = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_DPAD_UP.rawValue)
|
||||
static let dpadDown = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN.rawValue)
|
||||
static let misc1 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_MISC1.rawValue)
|
||||
static let misc2 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_MISC2.rawValue)
|
||||
static let misc3 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_MISC3.rawValue)
|
||||
static let misc4 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_MISC4.rawValue)
|
||||
static let misc5 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_MISC5.rawValue)
|
||||
static let misc6 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_MISC6.rawValue)
|
||||
static let paddle1 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1.rawValue)
|
||||
static let paddle2 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE1.rawValue)
|
||||
static let paddle3 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2.rawValue)
|
||||
static let paddle4 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE2.rawValue)
|
||||
static let touchPad = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_TOUCHPAD.rawValue)
|
||||
}
|
||||
|
||||
public struct State {
|
||||
private let _axes: [Int16]
|
||||
private let _btns: Buttons, _btnImpulse: Buttons
|
||||
|
||||
internal init(axes: [Int16], btns: Buttons, btnImpulse: Buttons) {
|
||||
self._axes = axes
|
||||
self._btns = btns
|
||||
self._btnImpulse = btnImpulse
|
||||
}
|
||||
|
||||
public func axis(_ axis: Axes) -> Float {
|
||||
let raw = rawAxis(axis)
|
||||
let rescale = raw < 0 ? 1 / Float(-Int(Int16.min)) : 1 / Float(Int16.max)
|
||||
return Float(raw) * rescale
|
||||
}
|
||||
@inline(__always) func rawAxis(_ axis: Axes) -> Int16 {
|
||||
_axes[Int(axis.sdlEnum.rawValue)]
|
||||
}
|
||||
|
||||
public func down(_ btn: Buttons) -> Bool {
|
||||
btn.isSubset(of: _btns)
|
||||
}
|
||||
public func pressed(_ btn: Buttons) -> Bool {
|
||||
btn.isSubset(of: _btns.intersection(_btnImpulse))
|
||||
}
|
||||
public func released(_ btn: Buttons) -> Bool {
|
||||
btn.isSubset(of: _btnImpulse.subtracting(_btns))
|
||||
}
|
||||
}
|
||||
|
||||
public var name: String { String(cString: SDL_GetGamepadName(_sdlPad)) }
|
||||
public var state: State {
|
||||
.init(
|
||||
axes: self._axes,
|
||||
btns: self._btnCur,
|
||||
btnImpulse: self._btnCur.symmetricDifference(self._btnPrv))
|
||||
}
|
||||
|
||||
//MARK: - Private
|
||||
|
||||
private var _joyInstance: SDL_JoystickID, _sdlPad: OpaquePointer
|
||||
private var _axes = [Int16](repeating: 0, count: Int(SDL_GAMEPAD_AXIS_MAX.rawValue))
|
||||
private var _btnCur: Buttons = [], _btnPrv: Buttons = []
|
||||
|
||||
internal var instanceID: SDL_JoystickID { _joyInstance }
|
||||
|
||||
private init(instance: SDL_JoystickID, pad: OpaquePointer) {
|
||||
self._joyInstance = instance
|
||||
self._sdlPad = pad
|
||||
}
|
||||
|
||||
internal static func open(joystickID: SDL_JoystickID) -> Self? {
|
||||
return if let sdlPad = SDL_OpenGamepad(joystickID) {
|
||||
.init(instance: joystickID, pad: sdlPad)
|
||||
} else { nil }
|
||||
}
|
||||
|
||||
internal func close() {
|
||||
SDL_CloseGamepad(self._sdlPad)
|
||||
}
|
||||
|
||||
internal mutating func buttonEvent(_ btn: SDL_GamepadButton, _ down: Bool) {
|
||||
if down {
|
||||
self._btnCur.formUnion(.init(rawValue: 1 << btn.rawValue))
|
||||
} else {
|
||||
self._btnCur.subtract(.init(rawValue: 1 << btn.rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
internal mutating func axisEvent(_ axis: SDL_GamepadAxis, _ value: Int16) {
|
||||
self._axes[Int(axis.rawValue)] = value
|
||||
}
|
||||
|
||||
internal mutating func newTick() {
|
||||
self._btnPrv = self._btnCur
|
||||
}
|
||||
}
|
||||
|
||||
public static func getPad(id: Int32) -> Pad? {
|
||||
_instance._pads[SDL_JoystickID(id)] ?? nil
|
||||
}
|
||||
|
||||
@inline(__always) public static var current: Pad? {
|
||||
getPad(id: Int32(_instance._firstID))
|
||||
}
|
||||
|
||||
//MARK: - Private
|
||||
|
||||
private static let _instance = GameController()
|
||||
public static var instance: GameController { _instance }
|
||||
|
||||
private var _pads = Dictionary<SDL_JoystickID, Pad>()
|
||||
private var _firstID: SDL_JoystickID = 0
|
||||
|
||||
internal func connectedEvent(id: SDL_JoystickID) {
|
||||
if _pads.keys.contains(id) {
|
||||
return
|
||||
}
|
||||
if let pad = Pad.open(joystickID: id) {
|
||||
printErr("Using gamepad #\(pad.instanceID), \"\(pad.name)\"")
|
||||
if self._firstID == 0 {
|
||||
self._firstID = id
|
||||
}
|
||||
self._pads[id] = pad
|
||||
}
|
||||
}
|
||||
|
||||
internal func removedEvent(id: SDL_JoystickID) {
|
||||
if let pad = self._pads.removeValue(forKey: id) {
|
||||
pad.close()
|
||||
}
|
||||
if id == _firstID {
|
||||
_firstID = _pads.keys.first ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
internal func buttonEvent(id: SDL_JoystickID, btn: SDL_GamepadButton, state: UInt8) {
|
||||
_pads[id]?.buttonEvent(btn, state == SDL_PRESSED)
|
||||
}
|
||||
|
||||
internal func axisEvent(id: SDL_JoystickID, axis: SDL_GamepadAxis, value: Int16) {
|
||||
_pads[id]?.axisEvent(axis, value)
|
||||
}
|
||||
|
||||
internal func newFrame() {
|
||||
for idx in _pads.values.indices {
|
||||
_pads.values[idx].newTick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//MARK: - Stick convenience functions
|
||||
|
||||
public extension GameController.Pad.State {
|
||||
var leftStick: SIMD2<Float> {
|
||||
.init(axis(.leftStickX), axis(.leftStickY))
|
||||
}
|
||||
var rightStick: SIMD2<Float> {
|
||||
.init(axis(.rightStickX), axis(.rightStickY))
|
||||
}
|
||||
var leftTrigger: Float { axis(.leftTrigger) }
|
||||
var rightTrigger: Float { axis(.rightTrigger) }
|
||||
}
|
||||
|
||||
public extension FloatingPoint {
|
||||
@inline(__always) internal func axisDeadzone(_ min: Self, _ max: Self) -> Self {
|
||||
let x = abs(self)
|
||||
return if x <= min { 0 } else if x >= max {
|
||||
.init(signOf: self, magnitudeOf: 1)
|
||||
} else {
|
||||
.init(signOf: self, magnitudeOf: x - min) / (max - min)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension SIMD2 where Scalar: FloatingPoint {
|
||||
func cardinalDeadzone(min: Scalar, max: Scalar) -> Self {
|
||||
.init(self.x.axisDeadzone(min, max), self.y.axisDeadzone(min, max))
|
||||
}
|
||||
|
||||
func radialDeadzone(min: Scalar, max: Scalar) -> Self {
|
||||
let magnitude = (x * x + y * y).squareRoot()
|
||||
if magnitude == .zero || magnitude < min {
|
||||
return .zero
|
||||
} else if magnitude > max {
|
||||
return self / magnitude
|
||||
} else {
|
||||
let rescale = (magnitude - min) / (max - min)
|
||||
return self / magnitude * rescale
|
||||
}
|
||||
}
|
||||
}
|
139
Sources/Voxelotl/Input/Keyboard.swift
Normal file
139
Sources/Voxelotl/Input/Keyboard.swift
Normal file
@ -0,0 +1,139 @@
|
||||
import SDL3
|
||||
|
||||
public class Keyboard {
|
||||
public enum Keys {
|
||||
case a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z
|
||||
case right, left, up, down
|
||||
case space, tab
|
||||
}
|
||||
|
||||
public static func down(_ key: Keys) -> Bool {
|
||||
keyState(key) & Self._DOWN == Self._DOWN
|
||||
}
|
||||
|
||||
public static func pressed(_ key: Keys, repeat rep: Bool = false) -> Bool {
|
||||
var state = keyState(key)
|
||||
if rep {
|
||||
state &= ~Self._REPEAT
|
||||
}
|
||||
return state == Self._PRESS
|
||||
}
|
||||
|
||||
public static func released(_ key: Keys) -> Bool {
|
||||
keyState(key) == Self._RELEASE
|
||||
}
|
||||
|
||||
//MARK: - Private
|
||||
|
||||
private static let _instance = Keyboard()
|
||||
public static var instance: Keyboard { _instance }
|
||||
|
||||
@inline(__always) private static func keyState(_ key: Keys) -> UInt8 {
|
||||
self._instance._state[Int(key.sdlScancode.rawValue)]
|
||||
}
|
||||
|
||||
private static let _UP = UInt8(0b000), _DOWN = UInt8(0b010), _IMPULSE = UInt8(0b001)
|
||||
private static let _REPEAT = UInt8(0b100)
|
||||
private static let _PRESS: UInt8 = _DOWN | _IMPULSE
|
||||
private static let _RELEASE: UInt8 = _UP | _IMPULSE
|
||||
|
||||
private var _state = [UInt8](repeating: _UP, count: Int(SDL_NUM_SCANCODES.rawValue))
|
||||
|
||||
internal func keyDownEvent(scan: SDL_Scancode, repeat rep: Bool) {
|
||||
var newState = Self._PRESS
|
||||
if rep {
|
||||
newState |= Self._REPEAT
|
||||
}
|
||||
self._state[Int(scan.rawValue)] = newState
|
||||
}
|
||||
|
||||
internal func keyUpEvent(scan: SDL_Scancode) {
|
||||
self._state[Int(scan.rawValue)] = Self._RELEASE
|
||||
}
|
||||
|
||||
internal func newFrame() {
|
||||
self._state = self._state.map({ $0 & ~(Self._IMPULSE | Self._REPEAT) })
|
||||
}
|
||||
}
|
||||
|
||||
internal extension Keyboard.Keys {
|
||||
var sdlKeycode: SDL_Keycode {
|
||||
switch self {
|
||||
case .a: SDLK_A
|
||||
case .b: SDLK_B
|
||||
case .c: SDLK_C
|
||||
case .d: SDLK_D
|
||||
case .e: SDLK_E
|
||||
case .f: SDLK_F
|
||||
case .g: SDLK_G
|
||||
case .h: SDLK_H
|
||||
case .i: SDLK_I
|
||||
case .j: SDLK_J
|
||||
case .k: SDLK_K
|
||||
case .l: SDLK_L
|
||||
case .m: SDLK_M
|
||||
case .n: SDLK_N
|
||||
case .o: SDLK_O
|
||||
case .p: SDLK_P
|
||||
case .q: SDLK_Q
|
||||
case .r: SDLK_R
|
||||
case .s: SDLK_S
|
||||
case .t: SDLK_T
|
||||
case .u: SDLK_U
|
||||
case .v: SDLK_V
|
||||
case .w: SDLK_W
|
||||
case .x: SDLK_X
|
||||
case .y: SDLK_Y
|
||||
case .z: SDLK_Z
|
||||
case .left: SDLK_LEFT
|
||||
case .right: SDLK_RIGHT
|
||||
case .up: SDLK_UP
|
||||
case .down: SDLK_DOWN
|
||||
case .space: SDLK_SPACE
|
||||
case .tab: SDLK_TAB
|
||||
}
|
||||
}
|
||||
|
||||
var sdlScancode: SDL_Scancode {
|
||||
switch self {
|
||||
case .a: SDL_SCANCODE_A
|
||||
case .b: SDL_SCANCODE_B
|
||||
case .c: SDL_SCANCODE_C
|
||||
case .d: SDL_SCANCODE_D
|
||||
case .e: SDL_SCANCODE_E
|
||||
case .f: SDL_SCANCODE_F
|
||||
case .g: SDL_SCANCODE_G
|
||||
case .h: SDL_SCANCODE_H
|
||||
case .i: SDL_SCANCODE_I
|
||||
case .j: SDL_SCANCODE_J
|
||||
case .k: SDL_SCANCODE_K
|
||||
case .l: SDL_SCANCODE_L
|
||||
case .m: SDL_SCANCODE_M
|
||||
case .n: SDL_SCANCODE_N
|
||||
case .o: SDL_SCANCODE_O
|
||||
case .p: SDL_SCANCODE_P
|
||||
case .q: SDL_SCANCODE_Q
|
||||
case .r: SDL_SCANCODE_R
|
||||
case .s: SDL_SCANCODE_S
|
||||
case .t: SDL_SCANCODE_T
|
||||
case .u: SDL_SCANCODE_U
|
||||
case .v: SDL_SCANCODE_V
|
||||
case .w: SDL_SCANCODE_W
|
||||
case .x: SDL_SCANCODE_X
|
||||
case .y: SDL_SCANCODE_Y
|
||||
case .z: SDL_SCANCODE_Z
|
||||
case .left: SDL_SCANCODE_LEFT
|
||||
case .right: SDL_SCANCODE_RIGHT
|
||||
case .up: SDL_SCANCODE_UP
|
||||
case .down: SDL_SCANCODE_DOWN
|
||||
case .space: SDL_SCANCODE_SPACE
|
||||
case .tab: SDL_SCANCODE_TAB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension SDL_Keycode {
|
||||
init(scancode: SDL_Scancode) {
|
||||
self.init(scancode.rawValue | UInt32(SDLK_SCANCODE_MASK))
|
||||
}
|
||||
}
|
81
Sources/Voxelotl/Input/Mouse.swift
Normal file
81
Sources/Voxelotl/Input/Mouse.swift
Normal file
@ -0,0 +1,81 @@
|
||||
import SDL3
|
||||
|
||||
public class Mouse {
|
||||
public struct Buttons: OptionSet {
|
||||
public let rawValue: UInt32
|
||||
public init(rawValue: UInt32) { self.rawValue = rawValue }
|
||||
|
||||
static let left = Self(rawValue: UInt32(SDL_BUTTON_LEFT).buttonMask)
|
||||
static let middle = Self(rawValue: UInt32(SDL_BUTTON_MIDDLE).buttonMask)
|
||||
static let right = Self(rawValue: UInt32(SDL_BUTTON_RIGHT).buttonMask)
|
||||
static let button4 = Self(rawValue: UInt32(SDL_BUTTON_X1).buttonMask)
|
||||
static let button5 = Self(rawValue: UInt32(SDL_BUTTON_X2).buttonMask)
|
||||
}
|
||||
|
||||
public static var capture: Bool {
|
||||
get { self._instance.getCapture() }
|
||||
set { self._instance.setCapture(newValue) }
|
||||
}
|
||||
|
||||
public static var position: SIMD2<Float> { self._instance.getAbsolute() }
|
||||
public static var relative: SIMD2<Float> { self._instance.getDelta() }
|
||||
|
||||
public static func down(_ btn: Buttons) -> Bool {
|
||||
btn.isSubset(of: self._instance._btns)
|
||||
}
|
||||
public static func pressed(_ btn: Buttons) -> Bool {
|
||||
btn.isSubset(of: self._instance._btns.intersection(self._instance._btnImpulse))
|
||||
}
|
||||
public static func released(_ btn: Buttons) -> Bool {
|
||||
btn.isSubset(of: self._instance._btnImpulse.subtracting(self._instance._btns))
|
||||
}
|
||||
|
||||
//MARK: - Private
|
||||
|
||||
private static let _instance = Mouse()
|
||||
public static var instance: Mouse { self._instance }
|
||||
|
||||
private var _window: OpaquePointer!
|
||||
private var _captured: Bool = false
|
||||
private var _abs: SIMD2<Float> = .zero, _delta: SIMD2<Float> = .zero
|
||||
private var _btns: Buttons = [], _btnImpulse: Buttons = []
|
||||
|
||||
private func getCapture() -> Bool { self._captured }
|
||||
private func setCapture(_ toggle: Bool) {
|
||||
let sdlBool = toggle ? SDL_TRUE : SDL_FALSE
|
||||
if SDL_SetRelativeMouseMode(sdlBool) >= 0 && SDL_SetWindowMouseGrab(self._window, sdlBool) >= 0 {
|
||||
self._captured = toggle
|
||||
}
|
||||
}
|
||||
|
||||
private func getAbsolute() -> SIMD2<Float> { self._abs }
|
||||
private func getDelta() -> SIMD2<Float> { self._delta }
|
||||
|
||||
internal func buttonEvent(btn: UInt32, state: UInt8) {
|
||||
if state == SDL_PRESSED {
|
||||
self._btns.formUnion(.init(rawValue: btn.buttonMask))
|
||||
} else {
|
||||
self._btns.subtract(.init(rawValue: btn.buttonMask))
|
||||
}
|
||||
self._btnImpulse.formUnion(.init(rawValue: btn.buttonMask))
|
||||
}
|
||||
|
||||
internal func motionEvent(absolute: SIMD2<Float>, relative: SIMD2<Float>) {
|
||||
self._abs = absolute
|
||||
self._delta += relative
|
||||
}
|
||||
|
||||
internal func newFrame(_ window: OpaquePointer) {
|
||||
self._window = window
|
||||
|
||||
let grabbedFlag = SDL_WindowFlags(SDL_WINDOW_MOUSE_GRABBED)
|
||||
self._captured = (SDL_GetWindowFlags(window) & grabbedFlag) == grabbedFlag
|
||||
|
||||
self._delta = .zero
|
||||
self._btnImpulse = []
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension UInt32 {
|
||||
var buttonMask: UInt32 { 1 &<< (self &- 1) }
|
||||
}
|
Reference in New Issue
Block a user