visionOS: Handle Spatial Events
This patch enables the handling of spatialEvents from SwiftUI. Since these events do not have ObjectiveC structs, for now we serialize them to JSON and pass them to Qt for usage later in Spatial applications. These events just cover the built-in gestures like pinch "clicking" etc. Change-Id: I8368683259eb64277083cf345ca3a5ed9af32ecf Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
parent
73a11c9d6a
commit
f65d11ecbf
@ -84,6 +84,7 @@ struct Q_GUI_EXPORT QVisionOSApplication
|
|||||||
struct ImmersiveSpaceCompositorLayer {
|
struct ImmersiveSpaceCompositorLayer {
|
||||||
virtual void configure(cp_layer_renderer_capabilities_t, cp_layer_renderer_configuration_t) const {}
|
virtual void configure(cp_layer_renderer_capabilities_t, cp_layer_renderer_configuration_t) const {}
|
||||||
virtual void render(cp_layer_renderer_t) = 0;
|
virtual void render(cp_layer_renderer_t) = 0;
|
||||||
|
virtual void handleSpatialEvents(const QJsonObject &) {};
|
||||||
};
|
};
|
||||||
virtual void setImmersiveSpaceCompositorLayer(ImmersiveSpaceCompositorLayer *layer) = 0;
|
virtual void setImmersiveSpaceCompositorLayer(ImmersiveSpaceCompositorLayer *layer) = 0;
|
||||||
virtual void openImmersiveSpace() = 0;
|
virtual void openImmersiveSpace() = 0;
|
||||||
|
@ -17,6 +17,11 @@ struct QIOSSwiftApplication: App {
|
|||||||
ImmersiveSpace(id: "QIOSImmersiveSpace") {
|
ImmersiveSpace(id: "QIOSImmersiveSpace") {
|
||||||
CompositorLayer(configuration: QIOSLayerConfiguration()) { layerRenderer in
|
CompositorLayer(configuration: QIOSLayerConfiguration()) { layerRenderer in
|
||||||
QIOSIntegration.instance().renderCompositorLayer(layerRenderer)
|
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
|
// CompositorLayer immersive spaces are always full, and should not need
|
||||||
@ -80,3 +85,114 @@ public class ImmersiveSpaceManager : NSObject {
|
|||||||
ImmersiveState.shared.showImmersiveSpace = false
|
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 "{}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -99,6 +99,7 @@ public:
|
|||||||
|
|
||||||
void configureCompositorLayer(cp_layer_renderer_capabilities_t, cp_layer_renderer_configuration_t);
|
void configureCompositorLayer(cp_layer_renderer_capabilities_t, cp_layer_renderer_configuration_t);
|
||||||
void renderCompositorLayer(cp_layer_renderer_t);
|
void renderCompositorLayer(cp_layer_renderer_t);
|
||||||
|
void handleSpatialEvents(const char *jsonString);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -329,6 +329,19 @@ void QIOSIntegration::renderCompositorLayer(cp_layer_renderer_t renderer)
|
|||||||
m_immersiveSpaceCompositorLayer->render(renderer);
|
m_immersiveSpaceCompositorLayer->render(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QIOSIntegration::handleSpatialEvents(const char *jsonString)
|
||||||
|
{
|
||||||
|
if (m_immersiveSpaceCompositorLayer) {
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(QByteArray(jsonString), &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Error parsing JSON: " << error.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_immersiveSpaceCompositorLayer->handleSpatialEvents(doc.object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
Loading…
x
Reference in New Issue
Block a user