diff --git a/Sources/OpenSwiftUICore/Event/Event_TODO.swift b/Sources/OpenSwiftUICore/Event/Event_TODO.swift index 19ec7c57e..5adca6098 100644 --- a/Sources/OpenSwiftUICore/Event/Event_TODO.swift +++ b/Sources/OpenSwiftUICore/Event/Event_TODO.swift @@ -1,7 +1,5 @@ // FIXME -package struct ExclusiveGesture: PrimitiveGesture {} - package struct _MapGesture: PrimitiveGesture where Content: Gesture { nonisolated package static func _makeGesture(gesture: _GraphValue<_MapGesture>, inputs: _GestureInputs) -> _GestureOutputs { preconditionFailure("TODO") diff --git a/Sources/OpenSwiftUICore/Event/Gesture/ExclusiveGesture.swift b/Sources/OpenSwiftUICore/Event/Gesture/ExclusiveGesture.swift new file mode 100644 index 000000000..a5f1adab1 --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Gesture/ExclusiveGesture.swift @@ -0,0 +1,193 @@ +// +// ExclusiveGesture.swift +// OpenSwiftUICore +// +// Status: Complete +// ID: C6A5F4DE707A20D3CFD8B7768E28573B (SwiftUICore) + +import OpenGraphShims + +@available(OpenSwiftUI_v1_0, *) +extension Gesture { + /// Combines two gestures exclusively to create a new gesture where only one + /// gesture succeeds, giving precedence to the first gesture. + /// + /// - Parameter other: A gesture you combine with your gesture, to create a + /// new, combined gesture. + /// + /// - Returns: A gesture that's the result of combining two gestures where + /// only one of them can succeed. OpenSwiftUI gives precedence to the first + /// gesture. + @inlinable + public func exclusively(before other: Other) -> ExclusiveGesture where Other: Gesture { + return ExclusiveGesture(self, other) + } +} + +/// A gesture that consists of two gestures where only one of them can succeed. +/// +/// The `ExclusiveGesture` gives precedence to its first gesture. +@available(OpenSwiftUI_v1_0, *) +@frozen +public struct ExclusiveGesture: PrimitiveGesture, Gesture where First: Gesture, Second: Gesture { + /// The value of an exclusive gesture that indicates which of two gestures + /// succeeded. + @frozen + public enum Value { + /// The first of two gestures succeeded. + case first(First.Value) + + /// The second of two gestures succeeded. + case second(Second.Value) + } + + /// The first of two gestures. + public var first: First + + /// The second of two gestures. + public var second: Second + + /// Creates a gesture from two gestures where only one of them succeeds. + /// + /// - Parameters: + /// - first: The first of two gestures. This gesture has precedence over + /// the other gesture. + /// - second: The second of two gestures. + @inlinable + public init(_ first: First, _ second: Second) { + (self.first, self.second) = (first, second) + } + + nonisolated public static func _makeGesture( + gesture: _GraphValue, + inputs: _GestureInputs + ) -> _GestureOutputs { + var inputs = inputs + let outputs1 = First.makeDebuggableGesture( + gesture: gesture[offset: { .of(&$0.first) }], + inputs: inputs + ) + inputs.inheritedPhase = Attribute(ExclusiveState(state: inputs.inheritedPhase, phase: outputs1.phase)) + let outputs2 = Second.makeDebuggableGesture( + gesture: gesture[offset: { .of(&$0.second) }], + inputs: inputs + ) + let phase = Attribute(ExclusivePhase( + phase1: outputs1.phase, + phase2: outputs2.phase + )) + var outputs = _GestureOutputs(phase: phase) + outputs.wrapDebugOutputs( + Self.self, + kind: .combiner, + inputs: inputs, + combiningOutputs: (outputs1, outputs2) + ) + for key in inputs.preferences.keys { + func project(_ key: K.Type) where K: PreferenceKey { + outputs[anyKey: key] = Attribute(ExclusivePreference( + value1: .init(base: AnyOptionalAttribute(outputs1[anyKey: key])), + value2: .init(base: AnyOptionalAttribute(outputs2[anyKey: key])), + phase1: outputs1.phase, + phase2: outputs2.phase + )).identifier + } + project(key) + } + return outputs + + } +} + +@available(OpenSwiftUI_v1_0, *) +extension ExclusiveGesture.Value: Sendable where First.Value: Sendable, Second.Value: Sendable {} + +@available(*, unavailable) +extension ExclusiveGesture: Sendable {} + +@available(OpenSwiftUI_v1_0, *) +extension ExclusiveGesture: PrimitiveDebuggableGesture {} + +@available(OpenSwiftUI_v1_0, *) +extension ExclusiveGesture.Value: Equatable where First.Value: Equatable, Second.Value: Equatable {} + +extension ExclusiveGesture.Value: Hashable where First.Value: Hashable, Second.Value: Hashable {} + +private struct ExclusivePreference: Rule where First: Gesture, Second: Gesture, Key: PreferenceKey { + @OptionalAttribute var value1: Key.Value? + @OptionalAttribute var value2: Key.Value? + @Attribute var phase1: GesturePhase + @Attribute var phase2: GesturePhase + + typealias Value = Key.Value + + var value: Value { + switch (phase1, phase2) { + case (.active, _), (.ended, _), (.possible(.some), _), (.possible(.none), .failed): value1 ?? Key.defaultValue + case (_, .active), (_, .ended), (_, .possible(.some)), (.failed, .possible(.none)): value2 ?? Key.defaultValue + case (.possible(.none), .possible(.none)): mergedValue() + case (.failed, .failed): Key.defaultValue + } + } + + @inline(__always) + func mergedValue() -> Key.Value { + if let value1, let value2 { + var result = value1 + Key.reduce(value: &result) { value2 } + return result + } else { + return value1 ?? value2 ?? Key.defaultValue + } + } +} + +private struct ExclusivePhase: Rule where First: Gesture, Second: Gesture { + @Attribute var phase1: GesturePhase + @Attribute var phase2: GesturePhase + + typealias Value = GesturePhase.Value> + + var value: Value { + switch (phase1, phase2) { + case let (.active(value1), _): + .active(.first(value1)) + case let (.possible, .active(value2)): + .active(.second(value2)) + case let (.possible(.some(value1)), _): + .possible(.first(value1)) + case let (.possible, .possible(.some(value2))): + .possible(.second(value2)) + case (.possible, _): + .possible(nil) + case let (.ended(value1), _): + .ended(.first(value1)) + case let (.failed, .active(value2)): + .active(.second(value2)) + case let (.failed, .possible(.some(value2))): + .possible(.second(value2)) + case let (.failed, .ended(value2)): + .ended(.second(value2)) + case (.failed, _): + .failed + } + } +} + +private struct ExclusiveState: Rule { + @Attribute var state: _GestureInputs.InheritedPhase + @Attribute var phase: GesturePhase + + typealias Value = _GestureInputs.InheritedPhase + + var value: Value { + var result = state + if !phase.isFailed { + result.subtract(.failed) + } + if phase.isActive { + result.insert(.active) + } + return result + } +}