summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/ios/qiosapplication.swift
blob: cfddbcd3fca3fb49045a2a2defa18e40aa16a5c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default

import SwiftUI
import CompositorServices
import QIOSIntegrationPlugin
import RealityKit

struct QIOSSwiftApplication: App {
    @UIApplicationDelegateAdaptor private var appDelegate: QIOSApplicationDelegate

    @State private var immersionStyle:ImmersionStyle = .automatic

    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))
                }
            }
        }
        .immersionStyle(selection: .constant(immersionStyle), in: immersionStyle)
    }
}

public struct QIOSLayerConfiguration: CompositorLayerConfiguration {
    public func makeConfiguration(capabilities: LayerRenderer.Capabilities,
                                  configuration: inout LayerRenderer.Configuration) {
        if #available(visionOS 2, *) {
            QIOSIntegration.instance().configureCompositorLayer(
                capabilities as cp_layer_renderer_capabilities_t,
                configuration as cp_layer_renderer_configuration_t)
        } else {
            // Use reflection to pull out underlying C handles
            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 "{}"
    }
}