Initial Commit
This commit is contained in:
commit
9b2f49ea5c
8 changed files with 857 additions and 0 deletions
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm/configuration/registries.json
|
||||||
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
|
.netrc
|
||||||
|
|
@ -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
42
Package.swift
Normal 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"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
163
Sources/ArtifactPlatform/GLFWWindow.swift
Normal file
163
Sources/ArtifactPlatform/GLFWWindow.swift
Normal 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
|
||||||
140
Sources/ArtifactPlatform/Input.swift
Normal file
140
Sources/ArtifactPlatform/Input.swift
Normal 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]
|
||||||
|
}
|
||||||
320
Sources/ArtifactPlatform/SDL3Window.swift
Normal file
320
Sources/ArtifactPlatform/SDL3Window.swift
Normal 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
|
||||||
|
|
||||||
93
Sources/ArtifactPlatform/Window.swift
Normal file
93
Sources/ArtifactPlatform/Window.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
12
Tests/ArtifactPlatformTests/ArtifactPlatformTests.swift
Normal file
12
Tests/ArtifactPlatformTests/ArtifactPlatformTests.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue