Initial Commit

This commit is contained in:
Signal 2026-06-22 15:50:54 -04:00
commit 9b2f49ea5c
8 changed files with 857 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ArtifactPlatform"
BuildableName = "ArtifactPlatform"
BlueprintName = "ArtifactPlatform"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ArtifactPlatformTests"
BuildableName = "ArtifactPlatformTests"
BlueprintName = "ArtifactPlatformTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ArtifactPlatform"
BuildableName = "ArtifactPlatform"
BlueprintName = "ArtifactPlatform"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

42
Package.swift Normal file
View file

@ -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"],
),
],
)

View file

@ -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<GLFWWindow>.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<GLFWWindow>.fromOpaque(glfwGetWindowUserPointer(window)).takeUnretainedValue()
for cb in me.resizeHandlers {
cb(Int(width), Int(height))
}
}
glfwSetCursorPosCallback(window) { window, x, y in
let me = Unmanaged<GLFWWindow>.fromOpaque(glfwGetWindowUserPointer(window)).takeUnretainedValue()
for cb in me.pointerMoveHandlers {
cb(x, y)
}
}
glfwSetScrollCallback(window) { window, x, y in
let me = Unmanaged<GLFWWindow>.fromOpaque(glfwGetWindowUserPointer(window)).takeUnretainedValue()
for cb in me.scrollHandlers {
cb(x, y)
}
}
// glfwSetKeyCallback(window) { window, key, scancode, action, mods in
// let me = Unmanaged<GLFWWindow>.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

View file

@ -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<MouseButton> = []
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]
}

View file

@ -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

View file

@ -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)
}
}

View file

@ -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
}
}