summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/ios/qiosapplication.swift
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/ios/qiosapplication.swift')
-rw-r--r--src/plugins/platforms/ios/qiosapplication.swift198
1 files changed, 198 insertions, 0 deletions
diff --git a/src/plugins/platforms/ios/qiosapplication.swift b/src/plugins/platforms/ios/qiosapplication.swift
new file mode 100644
index 0000000000..9eb172a896
--- /dev/null
+++ b/src/plugins/platforms/ios/qiosapplication.swift
@@ -0,0 +1,198 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import SwiftUI
+import CompositorServices
+import QIOSIntegrationPlugin
+import RealityKit
+
+struct QIOSSwiftApplication: App {
+ @UIApplicationDelegateAdaptor private var appDelegate: QIOSApplicationDelegate
+
+ var body: some SwiftUI.Scene {
+ WindowGroup() {
+ ImmersiveSpaceControlView()
+ }
+
+ ImmersiveSpace(id: "QIOSImmersiveSpace") {
+ CompositorLayer(configuration: QIOSLayerConfiguration()) { layerRenderer in
+ QIOSIntegration.instance().renderCompositorLayer(layerRenderer)
+
+ // Handle any events in the scene.
+ layerRenderer.onSpatialEvent = { eventCollection in
+ QIOSIntegration.instance().handleSpatialEvents(jsonStringFromEventCollection(eventCollection))
+ }
+ }
+ }
+ // CompositorLayer immersive spaces are always full, and should not need
+ // to set the immersion style, but lacking this we get a warning in the
+ // console about not being able to "configure an immersive space with
+ // selected style 'AutomaticImmersionStyle' since it is not in the list
+ // of supported styles for this type of content: 'FullImmersionStyle'."
+ .immersionStyle(selection: .constant(.full), in: .full)
+ }
+}
+
+public struct QIOSLayerConfiguration: CompositorLayerConfiguration {
+ public func makeConfiguration(capabilities: LayerRenderer.Capabilities,
+ configuration: inout LayerRenderer.Configuration) {
+ // Use reflection to pull out underlying C handles
+ // FIXME: Use proper bridging APIs when available
+ let capabilitiesMirror = Mirror(reflecting: capabilities)
+ let configurationMirror = Mirror(reflecting: configuration)
+ QIOSIntegration.instance().configureCompositorLayer(
+ capabilitiesMirror.descendant("c_capabilities") as? cp_layer_renderer_capabilities_t,
+ configurationMirror.descendant("box", "value") as? cp_layer_renderer_configuration_t
+ )
+ }
+}
+
+public func runSwiftAppMain() {
+ QIOSSwiftApplication.main()
+}
+
+public class ImmersiveState: ObservableObject {
+ static let shared = ImmersiveState()
+ @Published var showImmersiveSpace: Bool = false
+}
+
+struct ImmersiveSpaceControlView: View {
+ @ObservedObject private var immersiveState = ImmersiveState.shared
+
+ @Environment(\.openImmersiveSpace) var openImmersiveSpace
+ @Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace
+
+ var body: some View {
+ VStack {}
+ .onChange(of: immersiveState.showImmersiveSpace) { _, newValue in
+ Task {
+ if newValue {
+ await openImmersiveSpace(id: "QIOSImmersiveSpace")
+ } else {
+ await dismissImmersiveSpace()
+ }
+ }
+ }
+ }
+}
+
+public class ImmersiveSpaceManager : NSObject {
+ @objc public static func openImmersiveSpace() {
+ ImmersiveState.shared.showImmersiveSpace = true
+ }
+
+ @objc public static func dismissImmersiveSpace() {
+ ImmersiveState.shared.showImmersiveSpace = false
+ }
+}
+
+extension SpatialEventCollection.Event.Kind: Encodable {
+ enum CodingKeys: String, CodingKey {
+ case touch
+ case directPinch
+ case indirectPinch
+ case pointer
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ switch self {
+ case .touch:
+ try container.encode("touch")
+ case .directPinch:
+ try container.encode("directPinch")
+ case .indirectPinch:
+ try container.encode("indirectPinch")
+ case .pointer:
+ try container.encode("pointer")
+ @unknown default:
+ try container.encode("unknown")
+ }
+ }
+}
+extension SpatialEventCollection.Event.Phase: Encodable {
+ enum CodingKeys: String, CodingKey {
+ case active
+ case ending
+ case cancled
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ switch self {
+ case .active:
+ try container.encode("active")
+ case .ended:
+ try container.encode("ended")
+ case .cancelled:
+ try container.encode("canceled")
+ @unknown default:
+ try container.encode("unknown")
+ }
+ }
+}
+extension SpatialEventCollection.Event.InputDevicePose: Encodable {
+ enum CodingKeys: String, CodingKey {
+ case altitude
+ case azimuth
+ case pose3D
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(altitude.radians, forKey: .altitude)
+ try container.encode(azimuth.radians, forKey: .azimuth)
+ try container.encode(pose3D, forKey: .pose3D)
+ }
+}
+
+extension SpatialEventCollection.Event: Encodable {
+ enum CodingKeys: String, CodingKey {
+ case id
+ case timestamp
+ case kind
+ case location
+ case phase
+ case modifierKeys
+ case inputDevicePose
+ case location3D
+ case selectionRay
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(id.hashValue, forKey: .id)
+ try container.encode(timestamp, forKey: .timestamp)
+ try container.encode(kind, forKey: .kind)
+ try container.encode(location, forKey: .location)
+ try container.encode(phase, forKey: .phase)
+ try container.encode(modifierKeys.rawValue, forKey: .modifierKeys)
+ try container.encode(inputDevicePose, forKey: .inputDevicePose)
+ try container.encode(location3D, forKey: .location3D)
+ try container.encode(selectionRay, forKey: .selectionRay)
+ }
+}
+
+extension SpatialEventCollection: Encodable {
+ enum CodingKeys: String, CodingKey {
+ case events
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(Array(self), forKey: .events)
+ }
+}
+
+func jsonStringFromEventCollection(_ eventCollection: SpatialEventCollection) -> String {
+ let encoder = JSONEncoder()
+ encoder.dateEncodingStrategy = .iso8601
+
+ do {
+ let jsonData = try encoder.encode(eventCollection)
+ return String(data: jsonData, encoding: .utf8) ?? "{}"
+ } catch {
+ print("Failed to encode event collection: \(error)")
+ return "{}"
+ }
+}