diff --git a/Sources/Voxelotl/Application.swift b/Sources/Voxelotl/Application.swift
index 1288ed9..3c57e72 100644
--- a/Sources/Voxelotl/Application.swift
+++ b/Sources/Voxelotl/Application.swift
@@ -2,6 +2,10 @@ import Foundation
import SDL3
import QuartzCore.CAMetalLayer
+#if canImport(GameController)
+import GameController
+#endif
+
public class Application {
private let cfg: ApplicationConfiguration
private var del: GameDelegate!
@@ -12,12 +16,24 @@ public class Application {
private var lastCounter: UInt64 = 0
private var time: Duration = .zero
+#if os(iOS)
+ private var onScreenVirtualController: GCVirtualController? = nil
+ private var onScreenVirtualControllerShown: Bool = false
+#endif
+
public init(delegate: GameDelegate, configuration: ApplicationConfiguration) {
self.cfg = configuration
self.del = delegate
}
private func initialize() -> ApplicationExecutionState {
+#if os(iOS)
+ if cfg.flags.contains(.onScreenVirtualController) {
+ onScreenVirtualController = initializeOnScreenVirtualController()
+ self.showVirtualGameController(true)
+ }
+#endif
+
guard SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD) else {
printErr("SDL_Init() error: \(String(cString: SDL_GetError()))")
return .exitFailure
@@ -31,6 +47,12 @@ public class Application {
if cfg.flags.contains(.highDPI) {
windowFlags |= SDL_WindowFlags(SDL_WINDOW_HIGH_PIXEL_DENSITY)
}
+ if cfg.flags.contains(.borderless) {
+ windowFlags |= SDL_WindowFlags(SDL_WINDOW_BORDERLESS)
+ }
+ if cfg.flags.contains(.fullscreen) {
+ windowFlags |= SDL_WindowFlags(SDL_WINDOW_FULLSCREEN)
+ }
window = SDL_CreateWindow(cfg.title, cfg.frame.w, cfg.frame.h, windowFlags)
guard window != nil else {
printErr("SDL_CreateWindow() error: \(String(cString: SDL_GetError()))")
@@ -70,6 +92,22 @@ public class Application {
return .running
}
+#if os(iOS)
+ private func initializeOnScreenVirtualController() -> GCVirtualController {
+ let configuration = GCVirtualController.Configuration()
+ configuration.elements = [
+ GCInputLeftThumbstick,
+ GCInputRightThumbstick,
+ GCInputLeftTrigger,
+ GCInputRightTrigger,
+ GCInputButtonA,
+ GCInputButtonB,
+ ]
+ let controller = GCVirtualController(configuration: configuration)
+ return controller
+ }
+#endif
+
private func deinitialize() {
self.del = nil
self.renderer = nil
@@ -141,6 +179,33 @@ public class Application {
}
}
+ private func showVirtualGameController(_ shown: Bool) {
+#if os(iOS)
+ guard let onScreenVirtualController = self.onScreenVirtualController else {
+ return
+ }
+
+ if shown {
+ if !onScreenVirtualControllerShown {
+ let semaphore = DispatchSemaphore(value: 1)
+ DispatchQueue.global().async {
+ Task.detached {
+ try? await onScreenVirtualController.connect()
+ semaphore.signal()
+ }
+ }
+ semaphore.wait()
+ onScreenVirtualControllerShown = true
+ }
+ } else {
+ if onScreenVirtualControllerShown {
+ onScreenVirtualController.disconnect()
+ onScreenVirtualControllerShown = false
+ }
+ }
+#endif
+ }
+
private func update() -> ApplicationExecutionState {
let deltaTime = getDeltaTime()
time += deltaTime
@@ -201,6 +266,9 @@ public struct ApplicationConfiguration {
static let resizable = Flags(rawValue: 1 << 0)
static let highDPI = Flags(rawValue: 1 << 1)
+ static let borderless = Flags(rawValue: 1 << 2)
+ static let fullscreen = Flags(rawValue: 1 << 3)
+ static let onScreenVirtualController = Flags(rawValue: 1 << 4)
}
public enum VSyncMode: Equatable {
diff --git a/Sources/Voxelotl/Info.plist.in b/Sources/Voxelotl/Info.plist.in
index 4977f0d..480fae9 100644
--- a/Sources/Voxelotl/Info.plist.in
+++ b/Sources/Voxelotl/Info.plist.in
@@ -36,5 +36,7 @@
UIApplicationSupportsIndirectInputEvents
+ UIRequiresFullScreen
+
diff --git a/Sources/Voxelotl/Program.swift b/Sources/Voxelotl/Program.swift
index 576e156..b6fb183 100644
--- a/Sources/Voxelotl/Program.swift
+++ b/Sources/Voxelotl/Program.swift
@@ -4,14 +4,33 @@ import Foundation
@objc public static func run() -> Int32 {
Thread.current.qualityOfService = .userInteractive
+ var flags: ApplicationConfiguration.Flags = [ .resizable, .highDPI, .onScreenVirtualController ]
+ if enableFullscreenWindow() {
+ flags = flags.union(.fullscreen)
+ }
+
let app = Application(
delegate: Game(),
configuration: ApplicationConfiguration(
frame: Size(1280, 720),
title: "Voxelotl Demo",
- flags: [ .resizable, .highDPI ],
+ flags: flags,
vsyncMode: .on(interval: 1)))
return app.run()
}
+
+ static func enableFullscreenWindow() -> Bool {
+ return Program.isFrontAndCenterGamingDevice()
+ }
+
+ static func isFrontAndCenterGamingDevice() -> Bool {
+#if os(iOS)
+ return !(ProcessInfo.processInfo.isiOSAppOnMac || ProcessInfo.processInfo.isMacCatalystApp)
+#elseif os(tvOS)
+ return true
+#else
+ return false
+#endif
+ }
}