From 9b2f49ea5cade28af1859060fd78ae8e5336f47b Mon Sep 17 00:00:00 2001 From: Signal Date: Mon, 22 Jun 2026 15:50:54 -0400 Subject: [PATCH] Initial Commit --- .gitignore | 8 + .../xcschemes/ArtifactPlatform.xcscheme | 79 +++++ Package.swift | 42 +++ Sources/ArtifactPlatform/GLFWWindow.swift | 163 +++++++++ Sources/ArtifactPlatform/Input.swift | 140 ++++++++ Sources/ArtifactPlatform/SDL3Window.swift | 320 ++++++++++++++++++ Sources/ArtifactPlatform/Window.swift | 93 +++++ .../ArtifactPlatformTests.swift | 12 + 8 files changed, 857 insertions(+) create mode 100644 .gitignore create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/ArtifactPlatform.xcscheme create mode 100644 Package.swift create mode 100644 Sources/ArtifactPlatform/GLFWWindow.swift create mode 100644 Sources/ArtifactPlatform/Input.swift create mode 100644 Sources/ArtifactPlatform/SDL3Window.swift create mode 100644 Sources/ArtifactPlatform/Window.swift create mode 100644 Tests/ArtifactPlatformTests/ArtifactPlatformTests.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ArtifactPlatform.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ArtifactPlatform.xcscheme new file mode 100644 index 0000000..ee4c15e --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/ArtifactPlatform.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..0fdb60f --- /dev/null +++ b/Package.swift @@ -0,0 +1,42 @@ +// swift-tools-version: 6.1 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "ArtifactPlatform", + platforms: [.macOS(.v14)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "ArtifactPlatform", + targets: ["ArtifactPlatform"] + ), + ], + traits: [ + .default(enabledTraits: ["GLFW"]), + .trait(name: "GLFW", description: "Enables the builtin GLFW backend."), + .trait(name: "SDL3", description: "Enables the builtin SDL3 backend."), + ], + dependencies: [ + .package(path: "../GLFW"), + .package(path: "../SDL3"), + .package(path: "../ArtifactMath"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "ArtifactPlatform", + dependencies: [ + "ArtifactMath", + .product(name: "GLFW", package: "GLFW", condition: .when(traits: ["GLFW"])), + .product(name: "SDL3", package: "SDL3", condition: .when(traits: ["SDL3"])), + ] + ), + .testTarget( + name: "ArtifactPlatformTests", + dependencies: ["ArtifactPlatform"], + ), + ], +) diff --git a/Sources/ArtifactPlatform/GLFWWindow.swift b/Sources/ArtifactPlatform/GLFWWindow.swift new file mode 100644 index 0000000..3372893 --- /dev/null +++ b/Sources/ArtifactPlatform/GLFWWindow.swift @@ -0,0 +1,163 @@ + +#if GLFW + +import GLFW + +let MIN_STEP_TIME = 0.2 + +@MainActor +public class GLFWWindow: Window { + public static var initialized = false + + public let id: Int = 0 + public private(set) var width: Int + public private(set) var height: Int + public private(set) var actions = ActionManager() + public var inputState = InputState() + + public let window: OpaquePointer! + let clock = ContinuousClock() + var previousTime = ContinuousClock.Instant.now + var accumulator: Double = 0 + var running = false + var pointerLocked = false + + var tickHandlers: [(Double) -> Void] = [] + var drawHandlers: [(Double) -> Void] = [] + var resizeHandlers: [(Int, Int) -> Void] = [] + var pointerMoveHandlers: [(Double, Double) -> Void] = [] + var keyDownHandlers: [(Int) -> Void] = [] + var keyUpHandlers: [(Int) -> Void] = [] + var scrollHandlers: [(Double, Double) -> Void] = [] + + required public convenience init(width: Int, height: Int) { + self.init(width: width, height: height, title: "Artifact Engine") + } + + required public init(width: Int, height: Int, title: String) { + if !GLFWWindow.initialized { glfwInit() } + + self.width = width + self.height = height + + glfwInitHint(GLFW_CLIENT_API, GLFW_NO_API) + window = glfwCreateWindow(Int32(width), Int32(height), title, nil, nil) + glfwSetWindowUserPointer(window, Unmanaged.passUnretained(self).toOpaque()) + + //setPointerLock() + + glfwSetKeyCallback(window) { window, key, scancode, action, mods in + let me = Unmanaged.fromOpaque(glfwGetWindowUserPointer(window)).takeUnretainedValue() + if key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE { + me.close() + } + for cb in action == GLFW_RELEASE ? me.keyUpHandlers : me.keyDownHandlers { + cb(Int(key)) + } + } + + glfwSetFramebufferSizeCallback(window) { window, width, height in + let me = Unmanaged.fromOpaque(glfwGetWindowUserPointer(window)).takeUnretainedValue() + for cb in me.resizeHandlers { + cb(Int(width), Int(height)) + } + } + + glfwSetCursorPosCallback(window) { window, x, y in + let me = Unmanaged.fromOpaque(glfwGetWindowUserPointer(window)).takeUnretainedValue() + for cb in me.pointerMoveHandlers { + cb(x, y) + } + } + + glfwSetScrollCallback(window) { window, x, y in + let me = Unmanaged.fromOpaque(glfwGetWindowUserPointer(window)).takeUnretainedValue() + for cb in me.scrollHandlers { + cb(x, y) + } + } + +// glfwSetKeyCallback(window) { window, key, scancode, action, mods in +// let me = Unmanaged.fromOpaque(glfwGetWindowUserPointer(window)).takeUnretainedValue() +// +// } + } + + public func setTitle(title: String) { + glfwSetWindowTitle(window, title) + } + + public func setPointerLock() { + pointerLocked = true + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED) + } + + public func close() { + glfwSetWindowShouldClose(window, GLFW_TRUE) + running = false + } + + public func run() { + running = true + previousTime = clock.now + + while glfwWindowShouldClose(window) == 0 { + glfwPollEvents() + + let currentTime = clock.now + let frameTime = Double(previousTime.duration(to: currentTime).components.attoseconds) / 1_000_000_000_000_000_000.0 + previousTime = currentTime + accumulator += frameTime + + while accumulator >= MIN_STEP_TIME { + tick(dtime: MIN_STEP_TIME) + accumulator -= MIN_STEP_TIME + } + + let alpha = accumulator / MIN_STEP_TIME + for cb in drawHandlers { + cb(alpha) + } + } + + running = false + + glfwDestroyWindow(window) + } + + public func tick(dtime: Double) { + for handler in tickHandlers { + handler(dtime) + } + } + + public func onTick(_ callback: @escaping (Double) -> Void) { + tickHandlers.append(callback) + } + + public func onDraw(_ callback: @escaping (Double) -> Void) { + drawHandlers.append(callback) + } + + public func onResize(_ callback: @escaping (Int, Int) -> Void) { + resizeHandlers.append(callback) + } + + public func onPointerMove(_ callback: @escaping (Double, Double) -> Void) { + pointerMoveHandlers.append(callback) + } + + public func onKeyDown(_ callback: @escaping (Int) -> Void) { + keyDownHandlers.append(callback) + } + + public func onKeyUp(_ callback: @escaping (Int) -> Void) { + keyUpHandlers.append(callback) + } + + public func onScroll(_ callback: @escaping (Double, Double) -> Void) { + scrollHandlers.append(callback) + } +} + +#endif diff --git a/Sources/ArtifactPlatform/Input.swift b/Sources/ArtifactPlatform/Input.swift new file mode 100644 index 0000000..5da9276 --- /dev/null +++ b/Sources/ArtifactPlatform/Input.swift @@ -0,0 +1,140 @@ + +import Observation +import ArtifactMath + +public enum Key: Int, Codable, Hashable, CaseIterable, Sendable { + // Letters + case a = 0, 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 + + // Numbers (top row) + case digit0, digit1, digit2, digit3, digit4, digit5, digit6, digit7, digit8, digit9 + + // Function keys + case f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24 + + // Modifiers + case leftShift, rightShift + case leftControl, rightControl + case leftAlt, rightAlt + case leftMeta, rightMeta // Command/Windows key + + // Navigation + case up, down, left, right + case home, end, pageUp, pageDown + case insert, delete + + // Special + case space + case tab + case enter // Return + case escape + case backspace + + // Punctuation / Symbols (US layout reference) + case grave // ` ~ + case minus // - _ + case equal // = + + case leftBracket // [ { + case rightBracket // ] } + case backslash // \ | + case semicolon // ; : + case quote // ' " + case comma // , < + case period // . > + case slash // / ? + + // Numpad + case numpad0, numpad1, numpad2, numpad3, numpad4, + numpad5, numpad6, numpad7, numpad8, numpad9 + case numpadDecimal, numpadDivide, numpadMultiply, + numpadMinus, numpadPlus, numpadEnter + + // Other common + case capsLock + case printScreen + case scrollLock + case pause + case numLock + + // Reserved for backend-specific or future expansion + case unknown +} + +// MARK: - Mouse + +public enum MouseButton: Int, Codable, Hashable, Sendable { + case left = 0 + case right + case middle + case extra1 // Side buttons, etc. + case extra2 + case unknown +} + +// MARK: - Gamepad / Controller (common across SDL, GLFW, XInput, etc.) + +public enum GamepadButton: Int, Codable, Hashable, Sendable { + case a, b, x, y + case dpadUp, dpadDown, dpadLeft, dpadRight + case leftStick, rightStick // Click/press + case leftShoulder, rightShoulder + case leftTrigger, rightTrigger // Often analog, but can be digital threshold + case start, select, menu // Start / Options / Menu + case guide // Home / Xbox button + case unknown +} + +public enum GamepadAxis: Int, Codable, Hashable, Sendable { + case leftStickX, leftStickY + case rightStickX, rightStickY + case leftTrigger, rightTrigger // Often 0..1 range +} + +// MARK: - Unified Input Source (for bindings) + +public enum InputSource: Codable, Hashable { + case key(Key) + case pointer(PointerState) + case mouseMove + case mouseWheel + case gamepadButton(GamepadButton, device: Int = 0) + case gamepadAxis(GamepadAxis, device: Int = 0) + + // Future: .mouseWheel, .touch, etc. +} + +@Observable +open class InputState { + private var nextPointerId = 1 + public var newPointerId: Int { + defer { nextPointerId += 1 } + return nextPointerId + } + + public var keyboard: [Key: Bool] = [:] + public var mouseButtons: Set = [] + public var pointers: [Int: PointerState] = [:] + public var mousePointer: PointerState? { pointers.values.first { $0.type == .mouse } } + public var wheelDelta: Vec2f = .zero + public var gamepads: [Int: GamepadState] = [:] + + public init() {} +} + +public struct PointerState: Codable, Hashable { + public typealias ID = Int + + public let id: Int + public var position: Vec2f + public var delta: Vec2f + public var pressure: Float + public var type: PointerType +} + +public enum PointerType: Codable, Hashable, Sendable { case mouse, pen, touch} + +public struct GamepadState { + public var buttons: [GamepadButton: Bool] + public var axes: [GamepadAxis: Float] +} diff --git a/Sources/ArtifactPlatform/SDL3Window.swift b/Sources/ArtifactPlatform/SDL3Window.swift new file mode 100644 index 0000000..4874db3 --- /dev/null +++ b/Sources/ArtifactPlatform/SDL3Window.swift @@ -0,0 +1,320 @@ + +#if SDL3 + +import SDL3 +import ArtifactMath + +private let SDLScancodes: [SDL_Scancode: Key] = [ + SDL_SCANCODE_A: .a, + SDL_SCANCODE_B: .b, + SDL_SCANCODE_C: .c, + SDL_SCANCODE_D: .d, + SDL_SCANCODE_E: .e, + SDL_SCANCODE_F: .f, + SDL_SCANCODE_G: .g, + SDL_SCANCODE_H: .h, + SDL_SCANCODE_I: .i, + SDL_SCANCODE_J: .j, + SDL_SCANCODE_K: .k, + SDL_SCANCODE_L: .l, + SDL_SCANCODE_M: .m, + SDL_SCANCODE_N: .n, + SDL_SCANCODE_O: .o, + SDL_SCANCODE_P: .p, + SDL_SCANCODE_Q: .q, + SDL_SCANCODE_R: .r, + SDL_SCANCODE_S: .s, + SDL_SCANCODE_T: .t, + SDL_SCANCODE_U: .u, + SDL_SCANCODE_V: .v, + SDL_SCANCODE_W: .w, + SDL_SCANCODE_X: .x, + SDL_SCANCODE_Y: .y, + SDL_SCANCODE_Z: .z, + + SDL_SCANCODE_0: .digit0, + SDL_SCANCODE_1: .digit1, + SDL_SCANCODE_2: .digit2, + SDL_SCANCODE_3: .digit3, + SDL_SCANCODE_4: .digit4, + SDL_SCANCODE_5: .digit5, + SDL_SCANCODE_6: .digit6, + SDL_SCANCODE_7: .digit7, + SDL_SCANCODE_8: .digit8, + SDL_SCANCODE_9: .digit9, + + SDL_SCANCODE_F1: .f1, + SDL_SCANCODE_F2: .f2, + SDL_SCANCODE_F3: .f3, + SDL_SCANCODE_F4: .f4, + SDL_SCANCODE_F5: .f5, + SDL_SCANCODE_F6: .f6, + SDL_SCANCODE_F7: .f7, + SDL_SCANCODE_F8: .f8, + SDL_SCANCODE_F9: .f9, + SDL_SCANCODE_F10: .f10, + SDL_SCANCODE_F11: .f11, + SDL_SCANCODE_F12: .f12, + SDL_SCANCODE_F13: .f13, + SDL_SCANCODE_F14: .f14, + SDL_SCANCODE_F15: .f15, + SDL_SCANCODE_F16: .f16, + SDL_SCANCODE_F17: .f17, + SDL_SCANCODE_F18: .f18, + SDL_SCANCODE_F19: .f19, + SDL_SCANCODE_F20: .f20, + SDL_SCANCODE_F21: .f21, + SDL_SCANCODE_F22: .f22, + SDL_SCANCODE_F23: .f23, + SDL_SCANCODE_F24: .f24, + + SDL_SCANCODE_LSHIFT: .leftShift, + SDL_SCANCODE_RSHIFT: .rightShift, + SDL_SCANCODE_LCTRL: .leftControl, + SDL_SCANCODE_RCTRL: .rightControl, + SDL_SCANCODE_LALT: .leftAlt, + SDL_SCANCODE_RALT: .rightAlt, + SDL_SCANCODE_LGUI: .leftMeta, + SDL_SCANCODE_RGUI: .rightMeta, + + SDL_SCANCODE_UP: .up, + SDL_SCANCODE_DOWN: .down, + SDL_SCANCODE_LEFT: .left, + SDL_SCANCODE_RIGHT: .right, + + SDL_SCANCODE_HOME: .home, + SDL_SCANCODE_END: .end, + SDL_SCANCODE_PAGEUP: .pageUp, + SDL_SCANCODE_PAGEDOWN: .pageDown, + SDL_SCANCODE_INSERT: .insert, + SDL_SCANCODE_DELETE: .delete, + + SDL_SCANCODE_SPACE: .space, + SDL_SCANCODE_TAB: .tab, + SDL_SCANCODE_RETURN: .enter, + SDL_SCANCODE_ESCAPE: .escape, + SDL_SCANCODE_BACKSPACE: .backspace, + + SDL_SCANCODE_KP_0: .numpad0, + SDL_SCANCODE_KP_1: .numpad1, + SDL_SCANCODE_KP_2: .numpad2, + SDL_SCANCODE_KP_3: .numpad3, + SDL_SCANCODE_KP_4: .numpad4, + SDL_SCANCODE_KP_5: .numpad5, + SDL_SCANCODE_KP_6: .numpad6, + SDL_SCANCODE_KP_7: .numpad7, + SDL_SCANCODE_KP_8: .numpad8, + SDL_SCANCODE_KP_9: .numpad9, +] + +private let SDLMouseButtons: [UInt8: MouseButton] = [ + 0: .left, + 1: .right, + 2: .middle, + 3: .extra1, + 4: .extra2, +] + +@MainActor +public class SDL3Window: Window { + static let __sdl_init__ = SDL_Init(0) + + public let window: OpaquePointer! + public let id: Int + public var width: Int + public var height: Int + public var actions = ActionManager() + public var inputState = InputState() + public var mice: [SDL_MouseID] = [] + + let clock = ContinuousClock() + var previousTime = ContinuousClock.Instant.now + var accumulator: Double = 0 + var running = false + var pointerLocked = false + + var drawHandlers: [(Double) -> Void] = [] + var tickHandlers: [(Double) -> Void] = [] + var resizeHandlers: [(Int, Int) -> Void] = [] + + public required convenience init(width: Int, height: Int) { + self.init(width: width, height: height, title: "Artifact Engine") + } + + public required init(width: Int, height: Int, title: String) { + self.width = width + self.height = height + + _ = Self.__sdl_init__ + + window = SDL_CreateWindow( + title, + Int32(width), + Int32(height), + 0 + ) + id = Int(SDL_GetWindowID(window)) + } + + public func setTitle(title: String) { + SDL_SetWindowTitle(window, title) + } + + public func close() { + running = false + if let window = window { + SDL_DestroyWindow(window) + } + } + + public func tick(dtime: Double) { + for handler in tickHandlers { + handler(dtime) + } + } + + public func run() { + running = true + defer { running = false } + previousTime = clock.now + + var event = SDL_Event() + + while running { + + while SDL_PollEvent(&event) { + switch SDL_EventType(event.type) { + case SDL_EVENT_QUIT: + close() + return + case SDL_EVENT_WINDOW_RESIZED: + // Update size + var w: Int32 = 0, h: Int32 = 0 + SDL_GetWindowSize(window, &w, &h) + self.width = Int(w) + self.height = Int(h) + for handler in resizeHandlers { + handler(Int(w), Int(h)) + } + case SDL_EVENT_KEY_DOWN: + if let key = SDLScancodes[event.key.scancode] { + inputState.keyboard[key] = true + actions.dispatchKeyDown(key: key) + } + case SDL_EVENT_KEY_UP: + if let key = SDLScancodes[event.key.scancode] { + inputState.keyboard[key] = false + actions.dispatchKeyUp(key: key) + } + case SDL_EVENT_MOUSE_ADDED: + if mice.isEmpty { + inputState.pointers[0] = PointerState(id: 0, position: .zero, delta: .zero, pressure: 0, type: .mouse) + } + mice.append(event.mdevice.which) + case SDL_EVENT_MOUSE_REMOVED: + mice.removeAll { $0 == event.mdevice.which } + if mice.isEmpty { + inputState.pointers.removeValue(forKey: 0) + } + case SDL_EVENT_MOUSE_WHEEL: + inputState.wheelDelta = [event.wheel.x, event.wheel.y] + if let state = inputState.pointers[0] { + actions.dispatchMouseWheel(state, delta: inputState.wheelDelta) + } + case SDL_EVENT_MOUSE_MOTION: + let delta = Vec2(event.motion.xrel, event.motion.yrel) + let pos = Vec2(event.motion.x, event.motion.y) + if inputState.pointers[0] != nil { + inputState.pointers[0]!.delta = delta + inputState.pointers[0]!.position = pos + actions.dispatchPointerMove(inputState.pointers[0]!) + } + case SDL_EVENT_MOUSE_BUTTON_DOWN: + if let button = SDLMouseButtons[event.button.button] { + inputState.mouseButtons.insert(button) + if var state = inputState.pointers[0] { + state.position = [event.button.x, event.button.y] + actions.dispatchPointerDown(state) + } + } + case SDL_EVENT_MOUSE_BUTTON_UP: + if let button = SDLMouseButtons[event.button.button] { + inputState.mouseButtons.remove(button) + if var state = inputState.pointers[0] { + state.position = [event.button.x, event.button.y] + actions.dispatchPointerUp(state) + } + } + case SDL_EVENT_FINGER_DOWN: + let id = Int(event.tfinger.fingerID) + inputState.pointers[id] = PointerState(id: id, position: .zero, delta: .zero, pressure: 0, type: .touch) + case SDL_EVENT_FINGER_MOTION: + let id = Int(event.tfinger.fingerID) + if var pointer = inputState.pointers[id] { + pointer.delta = [event.tfinger.dx + Float(width), event.tfinger.dy + Float(height)] + pointer.position = [event.tfinger.x + Float(width), event.tfinger.y + Float(height)] + } + case SDL_EVENT_FINGER_UP: + let id = Int(event.tfinger.fingerID) + inputState.pointers.removeValue(forKey: id) + case SDL_EVENT_PEN_AXIS: + let id = Int(event.paxis.which) + if var pointer = inputState.pointers[id] { + switch event.paxis.axis { + case SDL_PEN_AXIS_PRESSURE: + pointer.pressure = event.paxis.value + default: + break + } + } + case SDL_EVENT_PEN_DOWN: + let id = Int(event.ptouch.which) + inputState.pointers[id] = PointerState(id: id, position: [event.ptouch.x, event.ptouch.y], delta: .zero, pressure: 0, type: .pen) + case SDL_EVENT_PEN_MOTION: + let id = Int(event.pmotion.which) + if var pointer = inputState.pointers[id] { + let pos = Vec2(event.pmotion.x, event.pmotion.y) + pointer.delta = pos - pointer.position + pointer.position = pos + } + case SDL_EVENT_PEN_UP: + let id = Int(event.ptouch.which) + inputState.pointers.removeValue(forKey: id) + default: + break + } + } + + let currentTime = clock.now + let frameTime = Double(previousTime.duration(to: currentTime).components.attoseconds) / 1_000_000_000_000_000_000.0 + previousTime = currentTime + accumulator += frameTime + + while accumulator >= MIN_STEP_TIME { + tick(dtime: MIN_STEP_TIME) + accumulator -= MIN_STEP_TIME + } + + let alpha = accumulator / MIN_STEP_TIME + for cb in drawHandlers { + cb(frameTime * 1000) // TODO: Handle alpha? + } + } + } + + public func onTick(_ callback: @escaping (Double) -> Void) { + tickHandlers.append(callback) + } + + public func onDraw(_ callback: @escaping (Double) -> Void) { + drawHandlers.append(callback) + } + + public func onResize(_ callback: @escaping (Int, Int) -> Void) { + resizeHandlers.append(callback) + } + +} + +#endif + diff --git a/Sources/ArtifactPlatform/Window.swift b/Sources/ArtifactPlatform/Window.swift new file mode 100644 index 0000000..bef6b5e --- /dev/null +++ b/Sources/ArtifactPlatform/Window.swift @@ -0,0 +1,93 @@ + +import ArtifactMath + +@MainActor +public protocol Window { + init(width: Int, height: Int) + init(width: Int, height: Int, title: String) + + var id: Int { get } + + var width: Int { get } + var height: Int { get } + var actions: ActionManager { get } + var inputState: InputState { get } + + func setTitle(title: String) + func close() + func run() + + func onTick(_ callback: @escaping (Double) -> Void) + func onDraw(_ callback: @escaping (Double) -> Void) + func onResize(_ callback: @escaping (Int, Int) -> Void) +} + +public class ActionManager { + public var activationCallbacks: [InputSource: [() -> Void]] = [:] + public var deactivationCallbacks: [InputSource: [() -> Void]] = [:] + public var mouseWheelCallbacks: [(PointerState, Vec2f) -> Void] = [] + public var pointerDownCallbacks: [(PointerState) -> Void] = [] + public var pointerUpCallbacks: [(PointerState) -> Void] = [] + public var pointerMoveCallbacks: [(PointerState) -> Void] = [] + + public func dispatchMouseWheel(_ pointer: PointerState, delta: Vec2f) { + for handler in mouseWheelCallbacks { + handler(pointer, delta) + } + } + + public func dispatchPointerDown(_ pointer: PointerState) { + for handler in pointerDownCallbacks { + handler(pointer) + } + } + + public func dispatchPointerMove(_ pointer: PointerState) { + for handler in pointerMoveCallbacks { + handler(pointer) + } + } + + public func dispatchPointerUp(_ pointer: PointerState) { + for handler in pointerUpCallbacks { + handler(pointer) + } + } + + public func dispatchKeyDown(key: Key) { + for handler in activationCallbacks[.key(key)] ?? [] { + handler() + } + } + + public func dispatchKeyUp(key: Key) { + for handler in deactivationCallbacks[.key(key)] ?? [] { + handler() + } + } + + public func onWheel(_ handler: @escaping (PointerState, Vec2f) -> Void) { + mouseWheelCallbacks.append(handler) + } + + public func onPointerMove(_ handler: @escaping (PointerState) -> Void) { + pointerMoveCallbacks.append(handler) + } + + public func onPointerDown(_ handler: @escaping (PointerState) -> Void) { + pointerDownCallbacks.append(handler) + } + + public func onPointerUp(_ handler: @escaping (PointerState) -> Void) { + pointerUpCallbacks.append(handler) + } + + public func onKeyDown(_ key: Key, _ handler: @escaping () -> Void) { + activationCallbacks[.key(key), default: []].append(handler) + } + + public func onKeyUp(_ key: Key, _ handler: @escaping () -> Void) { + deactivationCallbacks[.key(key), default: []].append(handler) + } +} + diff --git a/Tests/ArtifactPlatformTests/ArtifactPlatformTests.swift b/Tests/ArtifactPlatformTests/ArtifactPlatformTests.swift new file mode 100644 index 0000000..93a5c54 --- /dev/null +++ b/Tests/ArtifactPlatformTests/ArtifactPlatformTests.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import ArtifactPlatform + +final class ArtifactPlatformTests: XCTestCase { + func testExample() throws { + // XCTest Documentation + // https://developer.apple.com/documentation/xctest + + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods + } +}