diff --git a/Package.resolved b/Package.resolved index c35f679df..ccb3212db 100644 --- a/Package.resolved +++ b/Package.resolved @@ -7,7 +7,7 @@ "location" : "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", "state" : { "branch" : "main", - "revision" : "d115c29b7468940969b0c2f5cb0d612a28563a02" + "revision" : "af7ef1c0a0e2c2b87e287743997a22ab3678924c" } }, { @@ -25,7 +25,7 @@ "location" : "https://github.com/OpenSwiftUIProject/OpenGraph", "state" : { "branch" : "main", - "revision" : "cbe7eaf78ee9a3b29714131f7699d79b61bf4f81" + "revision" : "83bedb98f5e54f75f225c003da7eaa9f7871ccab" } }, { diff --git a/Sources/OpenSwiftUICore/Event/Gesture/AnyGesture.swift b/Sources/OpenSwiftUICore/Event/Gesture/AnyGesture.swift index 75f2d70eb..753d87384 100644 --- a/Sources/OpenSwiftUICore/Event/Gesture/AnyGesture.swift +++ b/Sources/OpenSwiftUICore/Event/Gesture/AnyGesture.swift @@ -27,11 +27,12 @@ public struct AnyGesture: PrimitiveGesture, Gesture { inputs: _GestureInputs ) -> _GestureOutputs { let outputs: _GestureOutputs = inputs.makeIndirectOutputs() + let currentSubgraph = Subgraph.current! let info = Attribute(AnyGestureInfo( gesture: gesture.value, inputs: inputs, outputs: outputs, - parentSubgraph: Subgraph.current! + parentSubgraph: currentSubgraph )) info.setFlags(.active, mask: .mask) outputs.setIndirectDependency(info.identifier) diff --git a/Sources/OpenSwiftUICore/Event/Gesture/GestureContainerFactory.swift b/Sources/OpenSwiftUICore/Event/Gesture/GestureContainerFactory.swift new file mode 100644 index 000000000..e54e71ee3 --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Gesture/GestureContainerFactory.swift @@ -0,0 +1,26 @@ +// +// GestureContainerFactory.swift +// OpenSwiftUICore +// +// Status: Complete + +// MARK: - GestureContainerFactory [6.5.4] + +package protocol GestureContainerFactory { + static func makeGestureContainer(responder: any AnyGestureContainingResponder) -> AnyObject +} + +package struct GestureContainerFactoryInput: ViewInput { + package static let defaultValue: (any GestureContainerFactory.Type)? = nil + + package typealias Value = (any GestureContainerFactory.Type)? +} + +extension _ViewInputs { + package func makeGestureContainer(responder: any AnyGestureContainingResponder) -> AnyObject { + guard let factory = self[GestureContainerFactoryInput.self] else { + preconditionFailure("Gesture container factory must be configured") + } + return factory.makeGestureContainer(responder: responder) + } +} diff --git a/Sources/OpenSwiftUICore/Event/Gesture/GestureContainerFeature.swift b/Sources/OpenSwiftUICore/Event/Gesture/GestureContainerFeature.swift new file mode 100644 index 000000000..ba1c9b40b --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Gesture/GestureContainerFeature.swift @@ -0,0 +1,11 @@ +// +// GestureContainerFeature.swift +// OpenSwiftUICore +// +// Status: WIP + +struct GestureContainerFeature { + static var isEnabled: Bool { + false + } +} diff --git a/Sources/OpenSwiftUICore/Event/Gesture/GestureGraph.swift b/Sources/OpenSwiftUICore/Event/Gesture/GestureGraph.swift index 36965cdd3..98e1569aa 100644 --- a/Sources/OpenSwiftUICore/Event/Gesture/GestureGraph.swift +++ b/Sources/OpenSwiftUICore/Event/Gesture/GestureGraph.swift @@ -101,15 +101,45 @@ final package class GestureGraph: GraphHost, EventGraphHost, CustomStringConvert delegate?.enqueueAction(action) } + @inline(__always) + func access(_ body: @autoclosure () -> T) -> T { + Update.perform { + instantiateIfNeeded() + return body() + } + } + package func gestureCategory() -> GestureCategory? { guard let rootResponder, rootResponder.isValid else { return nil } + return access(gestureCategoryAttr) + } + + @inline(__always) + var gestureLabel: String? { + guard isInstantiated else { + return nil + } return Update.perform { - instantiateIfNeeded() - return gestureCategoryAttr + gestureLabelAttr ?? nil } } + + @inline(__always) + var isCancellable: Bool { + access(isCancellableAttr ?? false) + } + + @inline(__always) + var requiredTapCount: Int? { + access(requiredTapCountAttr ?? nil) + } + + @inline(__always) + var gestureDependency: GestureDependency { + access(gestureDependencyAttr ?? .none) + } } extension GestureGraph { diff --git a/Sources/OpenSwiftUICore/Event/Gesture/GestureViewModifier.swift b/Sources/OpenSwiftUICore/Event/Gesture/GestureViewModifier.swift index cdbcf4e34..486bbf533 100644 --- a/Sources/OpenSwiftUICore/Event/Gesture/GestureViewModifier.swift +++ b/Sources/OpenSwiftUICore/Event/Gesture/GestureViewModifier.swift @@ -14,7 +14,7 @@ package protocol GestureViewModifier: MultiViewModifier, PrimitiveViewModifier { associatedtype Combiner: GestureCombiner = DefaultGestureCombiner - var gesture: Self.ContentGesture { get } + var gesture: ContentGesture { get } var name: String? { get } @@ -40,13 +40,13 @@ extension GestureResponderExclusionPolicy: Sendable {} // MARK: - GestureCombiner [6.5.4] package protocol GestureCombiner { - associatedtype Result: Gesture /*where Result.Value == ()*/ + associatedtype Result: Gesture where Result.Value == () static func combine( _ gesture1: AnyGesture, _ gesture2: AnyGesture - ) -> Self.Result - + ) -> Result + static var exclusionPolicy: GestureResponderExclusionPolicy { get } } @@ -90,40 +90,6 @@ extension GestureViewModifier { } } -// MARK: - GestureFilter [6.5.4] [Blocked by ViewResponder] - -private struct GestureFilter: StatefulRule where Modifier: GestureViewModifier { - typealias Value = [ViewResponder] - - @Attribute var children: [ViewResponder] - - @Attribute var modifier: Modifier - - var inputs: _ViewInputs - - var viewSubgraph: Subgraph - - lazy var responder: GestureResponder = { - viewSubgraph.apply { - GestureResponder( - modifier: $modifier, - inputs: inputs - ) - } - }() - - mutating func updateValue() { - let responder = responder - let (children, childrenChanged) = $children.changedValue() - if childrenChanged { - // responder.children = children - } - if !hasValue { - // value = [self.responder] - } - } -} - // MARK: - AddGestureModifier [6.5.4] package struct AddGestureModifier: GestureViewModifier where T: Gesture { @@ -146,10 +112,12 @@ package struct AddGestureModifier: GestureViewModifier where T: Gesture { package typealias ContentGesture = T } +// MARK: - DefaultGestureCombiner [6.5.4] + package struct DefaultGestureCombiner: GestureCombiner { package typealias Base = ExclusiveGesture, AnyGesture> - package typealias Result = _MapGesture + package typealias Result = _MapGesture package static var exclusionPolicy: GestureResponderExclusionPolicy { .default } @@ -157,68 +125,319 @@ package struct DefaultGestureCombiner: GestureCombiner { _ first: AnyGesture, _ second: AnyGesture ) -> DefaultGestureCombiner.Result { - preconditionFailure("TODO") + first.exclusively(before: second).map { _ in } } } +// MARK: - AnyGestureContainingResponder [6.5.4] + package protocol AnyGestureContainingResponder: ViewResponder { var viewSubgraph: Subgraph { get } + var eventSources: [any EventBindingSource] { get } + var gestureType: any Any.Type { get } + var isValid: Bool { get } - + func detachContainer() } +// MARK: - AnyGestureResponder [6.5.4] + package protocol AnyGestureResponder: AnyGestureContainingResponder { var inputs: _ViewInputs { get } + var childSubgraph: Subgraph? { get set } + var childViewSubgraph: Subgraph? { get set } + var exclusionPolicy: GestureResponderExclusionPolicy { get } + + var label: String? { get } + var gestureGraph: GestureGraph { get } - + + var relatedAttribute: AnyAttribute { get } + func makeSubviewsGesture(inputs: _GestureInputs) -> _GestureOutputs } extension AnyGestureResponder { - package var exclusionPolicy: GestureResponderExclusionPolicy { - get { preconditionFailure("TODO") } - } - + package var exclusionPolicy: GestureResponderExclusionPolicy { .default } + package func makeSubviewsGesture(inputs: _GestureInputs) -> _GestureOutputs { - preconditionFailure("TODO") + _GestureOutputs(phase: inputs.failedPhase) } package func makeWrappedGesture( inputs: _GestureInputs, makeChild: (_GestureInputs) -> _GestureOutputs - ) -> _GestureOutputs { preconditionFailure("TODO") } - - package var label: String? { - get { preconditionFailure("TODO") } + ) -> _GestureOutputs { + let inputs = inputs + let outputs: _GestureOutputs = inputs.makeDefaultOutputs() + guard viewSubgraph.isValid else { + return outputs + } + let currentSubgraph = Subgraph.current! + let needGestureGraph = inputs.options.contains(.gestureGraph) + childSubgraph = Subgraph(graph: (needGestureGraph ? currentSubgraph : viewSubgraph).graph) + viewSubgraph.addChild(childSubgraph!, tag: 1) + currentSubgraph.addChild(childSubgraph!) + if needGestureGraph { + childViewSubgraph = Subgraph(graph: viewSubgraph.graph) + childSubgraph!.addChild(childViewSubgraph!, tag: 1) + } + childSubgraph!.apply { + let subgraph = (childViewSubgraph ?? childSubgraph)! + var childInputs = inputs + childInputs.viewInputs = self.inputs + childInputs.copyCaches() + childInputs.viewSubgraph = subgraph + let childOutputs = makeChild(childInputs) + outputs.overrideDefaultValues(childOutputs) + } + return outputs } + package var label: String? { nil } + package var isCancellable: Bool { - get { preconditionFailure("TODO") } + gestureGraph.isCancellable } package var requiredTapCount: Int? { - get { preconditionFailure("TODO") } + gestureGraph.requiredTapCount } package func canPrevent( _ other: ViewResponder, otherExclusionPolicy: GestureResponderExclusionPolicy - ) -> Bool { preconditionFailure("TODO") } + ) -> Bool { + guard isPrioritized(over: other, otherExclusionPolicy: otherExclusionPolicy) else { + return false + } + guard let other = other as? any AnyGestureResponder else { + return true + } + return other.dependency == .none + } package func shouldRequireFailure(of other: any AnyGestureResponder) -> Bool { - preconditionFailure("TODO") + guard exclusionPolicy != .simultaneous, + other.exclusionPolicy != .simultaneous, + let requiredTapCount, + let otherRequiredTapCount = other.requiredTapCount, + otherRequiredTapCount != requiredTapCount + else { + return other.isPrioritized(over: self, otherExclusionPolicy: exclusionPolicy) && dependency != .none + } + return requiredTapCount < otherRequiredTapCount + } + + private func isPrioritized(over other: ViewResponder, otherExclusionPolicy: GestureResponderExclusionPolicy) -> Bool { + switch (exclusionPolicy, otherExclusionPolicy) { + case (.default, .default): + var resonder: ResponderNode = other + while true { + guard resonder !== self else { + return false + } + guard let nextResponder = resonder.nextResponder else { + return true + } + resonder = nextResponder + } + case (.highPriority, .highPriority): + var resonder: ResponderNode = other + while true { + guard resonder !== self else { + return true + } + guard let nextResponder = resonder.nextResponder else { + return false + } + resonder = nextResponder + } + case (.highPriority, .default): + return true + default: + return false + } + } + + private var dependency: GestureDependency { + gestureGraph.gestureDependency } } -private class GestureResponder/*: AnyGestureResponder where Modifier: GestureViewModifier*/ { +// MARK: - GestureResponder [6.5.4] [WIP] + +private class GestureResponder: DefaultLayoutViewResponder, AnyGestureResponder where Modifier: GestureViewModifier { + let modifier: Attribute + + var childSubgraph: Subgraph? + + var childViewSubgraph: Subgraph? + + lazy var gestureGraph: GestureGraph = { + GestureGraph(rootResponder: self) + }() + + lazy var bindingBridge: EventBindingBridge & GestureGraphDelegate = { + let bridge = inputs.makeEventBindingBridge(bindingManager: gestureGraph.eventBindingManager, responder: self) + gestureGraph.delegate = bridge + return bridge + }() + + var _gestureContainer: AnyObject? + init(modifier: Attribute, inputs: _ViewInputs) { - preconditionFailure("TODO") + self.modifier = modifier + super.init(inputs: inputs) + } + + var gestureType: any Any.Type { + Modifier.ContentGesture.self + } + + var relatedAttribute: AnyAttribute { + modifier.identifier + } + + var eventSources: [any EventBindingSource] { + bindingBridge.eventSources + } + + var exclusionPolicy: GestureResponderExclusionPolicy { + Modifier.Combiner.exclusionPolicy + } + + var label: String? { + guard viewSubgraph.isValid else { return nil } + return Graph.withoutUpdate { + viewSubgraph.apply { + modifier.name.value + } + } ?? gestureGraph.gestureLabel + } + + var isValid: Bool { + _gestureContainer != nil && viewSubgraph.isValid + } + + func detachContainer() { + _gestureContainer = nil + } + + func makeSubviewsGesture(inputs: _GestureInputs) -> _GestureOutputs { + makeGesture(inputs: inputs) + } + + override var gestureContainer: AnyObject? { + guard let gestureContainer = _gestureContainer else { + guard viewSubgraph.isValid else { + return nil + } + _gestureContainer = inputs.makeGestureContainer(responder: self) + return _gestureContainer! + } + return gestureContainer + } + + override func containsGlobalPoints( + _ points: [PlatformPoint], + cacheKey: UInt32?, + options: ViewResponder.ContainsPointsOptions + ) -> ViewResponder.ContainsPointsResult { + openSwiftUIUnimplementedFailure() + } + + override func bindEvent(_ event: any EventType) -> ResponderNode? { + guard GestureContainerFeature.isEnabled else { + return super.bindEvent(event) + } + guard let hitTestableEvent = HitTestableEvent(event) else { + return nil + } + return hitTest( + globalPoint: hitTestableEvent.hitTestLocation, + radius: hitTestableEvent.hitTestRadius + ) + } + + override func makeGesture(inputs: _GestureInputs) -> _GestureOutputs { + makeWrappedGesture(inputs: inputs) { childInputs in +// let childViewInputs = childInputs.viewInputs +// let closure: () -> _GestureOutputs = { [self] in +// if inputs.options.contains(.skipCombiners) { +// let childGesture = Attribute(GestureViewChild( +// modifier: modifier, +// isEnabled: childViewInputs.isEnabled, +// viewPhase: childViewInputs.viewPhase +// )) +// return AnyGesture.makeDebuggableGesture(gesture: _GraphValue(childGesture), inputs: childInputs) +// } else { +// let childGesture = Attribute(CombiningGestureViewChild( +// modifier: modifier, +// isEnabled: childViewInputs.isEnabled, +// viewPhase: childViewInputs.viewPhase, +// node: self +// )) +// return Modifier.Combiner.Result.makeDebuggableGesture(gesture: _GraphValue(childGesture), inputs: childInputs) +// } +// } +// guard inputs.options.contains(.includeDebugOutput) else { +// return closure() +// } +// // TODO: GestureViewDebug +// return closure() + preconditionFailure("TODO") + } + } + + override func resetGesture() { + childSubgraph = nil + childViewSubgraph = nil + super.resetGesture() + } + + override func extendPrintTree(string: inout String) { + string.append("\(Modifier.ContentGesture.self)") + } +} + +// MARK: - GestureFilter [6.5.4] + +private struct GestureFilter: StatefulRule where Modifier: GestureViewModifier { + typealias Value = [ViewResponder] + + @Attribute var children: [ViewResponder] + + @Attribute var modifier: Modifier + + var inputs: _ViewInputs + + var viewSubgraph: Subgraph + + lazy var responder: GestureResponder = { + viewSubgraph.apply { + GestureResponder( + modifier: $modifier, + inputs: inputs + ) + } + }() + + mutating func updateValue() { + let responder = responder + let (children, childrenChanged) = $children.changedValue() + if childrenChanged { + responder.children = children + } + if !hasValue { + value = [self.responder] + } } } @@ -232,10 +451,6 @@ package protocol GestureAccessibilityProvider { ) } -private struct GestureAccessibilityProviderKey: GraphInput { - static let defaultValue: (any GestureAccessibilityProvider.Type) = EmptyGestureAccessibilityProvider.self -} - struct EmptyGestureAccessibilityProvider: GestureAccessibilityProvider { nonisolated static func makeGesture( mask: @autoclosure () -> Attribute, @@ -246,6 +461,10 @@ struct EmptyGestureAccessibilityProvider: GestureAccessibilityProvider { } extension _GraphInputs { + private struct GestureAccessibilityProviderKey: GraphInput { + static let defaultValue: (any GestureAccessibilityProvider.Type) = EmptyGestureAccessibilityProvider.self + } + package var gestureAccessibilityProvider: (any GestureAccessibilityProvider.Type) { get { self[GestureAccessibilityProviderKey.self] } set { self[GestureAccessibilityProviderKey.self] = newValue } @@ -259,6 +478,179 @@ extension _ViewInputs { } } +// MARK: - GestureViewChild [6.5.4] + +private struct GestureViewChild: Rule where Modifier: GestureViewModifier { + @Attribute var modifier: Modifier + @Attribute var isEnabled: Bool + @Attribute var viewPhase: _GraphInputs.Phase + + typealias Value = AnyGesture + + var value: Value { + let shouldReceiveEvents = modifier.gestureMask.contains(.gesture) && isEnabled + guard shouldReceiveEvents else { + return AnyGesture(EmptyGesture()) + } + return AnyGesture(modifier.gesture.map { _ in }) + } +} + +// MARK: - CombiningGestureViewChild [6.5.4] + +private struct CombiningGestureViewChild: Rule where Modifier: GestureViewModifier { + @Attribute var modifier: Modifier + @Attribute var isEnabled: Bool + @Attribute var viewPhase: _GraphInputs.Phase + + let node: any AnyGestureResponder + + typealias Value = Modifier.Combiner.Result + + @inline(__always) + private var shouldReceiveEvents: Bool { + modifier.gestureMask.contains(.gesture) && isEnabled + } + + @inline(__always) + private var shouldReceiveSubviewEvents: Bool { + modifier.gestureMask.contains(.subviews) + } + + @inline(__always) + private var subviewsGesture: AnyGesture { + if modifier.gestureMask.contains(.subviews) { + return AnyGesture(SubviewsGesture(node: node)) + } else { + return AnyGesture(EmptyGesture()) + } + } + + @inline(__always) + private var contentGesture: AnyGesture { + if shouldReceiveEvents { + AnyGesture(modifier.gesture + .modifier(ContentGesture()) + ) + } else { + AnyGesture(EmptyGesture()) + } + } + + var value: Value { + Modifier.Combiner.combine(subviewsGesture, contentGesture) + } +} + +// MARK: - GestureViewDebug [6.5.4] [WIP] + +private struct GestureViewDebug where Modifier: GestureViewModifier { +} + +// MARK: - SubviewsGesture [6.5.4] + +private struct SubviewsGesture: PrimitiveGesture, PrimitiveDebuggableGesture { + typealias Value = () + + typealias Body = Never + + let node: AnyGestureResponder + + static func _makeGesture(gesture: _GraphValue, inputs: _GestureInputs) -> _GestureOutputs { + let outputs: _GestureOutputs = inputs.makeIndirectOutputs() + let currentSubgraph = Subgraph.current! + let subviewValue = Attribute(SubviewsPhase( + gesture: gesture.value, + resetSeed: inputs.resetSeed, + inputs: inputs, + outputs: outputs, + parentSubgraph: currentSubgraph, + oldNode: nil, + oldSeed: 0, + childSubgraph: nil, + childPhase: .init(), + childDebugData: .init() + )) + outputs.setIndirectDependency(subviewValue.identifier) + return outputs + } +} + +// MARK: - SimultaneousGestureCombiner [6.5.4] [WIP] + +struct SimultaneousGestureCombiner {} + +// MARK: - HighPriorityGestureCombiner [6.5.4] [WIP] + +struct HighPriorityGestureCombiner {} + +// MARK: - SubviewsPhase [6.5.4] [WIP] + +private struct SubviewsPhase: StatefulRule, ObservedAttribute { + struct Value { + var phase: GesturePhase + var debugData: GestureDebug.Data + } + + @Attribute var gesture: SubviewsGesture + @Attribute var resetSeed: UInt32 + let inputs: _GestureInputs + let outputs: _GestureOutputs + let parentSubgraph: Subgraph + var oldNode: AnyGestureResponder? + var oldSeed: UInt32 + var childSubgraph: Subgraph? + @OptionalAttribute var childPhase: GesturePhase? + @OptionalAttribute var childDebugData: GestureDebug.Data? + + mutating func updateValue() { + openSwiftUIUnimplementedFailure() + } + + func destroy() { + oldNode?.resetGesture() + } +} + +// MARK: - ContentGesture [6.5.4] + +private struct ContentGesture: GestureModifier { + typealias Value = Void + + typealias BodyValue = V + + nonisolated static func _makeGesture( + modifier: _GraphValue>, + inputs: _GestureInputs, + body: (_GestureInputs) -> _GestureOutputs + ) -> _GestureOutputs { + let outputs = body(inputs) + let phase = Attribute(ContentPhase( + phase: outputs.phase, + resetSeed: inputs.resetSeed, + lastResetSeed: 0 + )) + return outputs.withPhase(phase) + } +} + +// MARK: - ContentPhase [6.5.4] + +private struct ContentPhase: ResettableGestureRule { + @Attribute var phase: GesturePhase + @Attribute var resetSeed: UInt32 + var lastResetSeed: UInt32 + + typealias Value = GesturePhase + + mutating func updateValue() { + guard resetIfNeeded() else { + return + } + value = phase.withValue(()) + } +} + // MARK: - Optional: Gesture [WIP] extension Optional: Gesture where Wrapped: Gesture { diff --git a/Sources/OpenSwiftUICore/Event/Gesture/LayoutGesture.swift b/Sources/OpenSwiftUICore/Event/Gesture/LayoutGesture.swift new file mode 100644 index 000000000..c0b786265 --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Gesture/LayoutGesture.swift @@ -0,0 +1,73 @@ +// +// LayoutGesture.swift +// OpenSwiftUICore +// +// Status: WIP + +// MARK: - LayoutGesture [6.5.4] [WIP] + +package protocol LayoutGesture: PrimitiveDebuggableGesture, PrimitiveGesture where Value == () { + var responder: MultiViewResponder { get } + + func updateEventBindings( + _ events: inout [EventID : any EventType], + proxy: LayoutGestureChildProxy + ) +} + +extension LayoutGesture { + package static func _makeGesture( + gesture: _GraphValue, + inputs: _GestureInputs + ) -> _GestureOutputs { + openSwiftUIUnimplementedFailure() + } + + package func updateEventBindings( + _ events: inout [EventID : any EventType], + proxy: LayoutGestureChildProxy + ) {} +} + +// MARK: - DefaultLayoutGesture [6.5.4] [WIP] + +package struct DefaultLayoutGesture: LayoutGesture { + package var responder: MultiViewResponder + + package typealias Body = Never + package typealias Value = () +} + +// MARK: - LayoutGestureChildProxy [6.5.4] [WIP] + +package struct LayoutGestureChildProxy: RandomAccessCollection { + package struct Child { + package func binds(_ binding: EventBinding) -> Bool { + preconditionFailure("TODO") + } + + package func containsGlobalLocation(_ p: PlatformPoint) -> Bool { + preconditionFailure("TODO") + } + } + + package var startIndex: Int { + get { preconditionFailure("TODO") } + } + + package var endIndex: Int { + get { preconditionFailure("TODO") } + } + + package subscript(index: Int) -> LayoutGestureChildProxy.Child { + get { preconditionFailure("TODO") } + } + + package func bindChild( + index: Int, + event: any EventType, + id: EventID + ) -> (from: EventBinding?, to: EventBinding?)? { + preconditionFailure("TODO") + } +} diff --git a/Sources/OpenSwiftUICore/Event/Responder/ContentPathObservers.swift b/Sources/OpenSwiftUICore/Event/Responder/ContentPathObservers.swift new file mode 100644 index 000000000..9ca614620 --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Responder/ContentPathObservers.swift @@ -0,0 +1,43 @@ +// +// ContentPathObservers.swift +// OpenSwiftUICore +// +// Status: Complete +// ID: A7CB304DFEF7D87240811B051B15E2CD (SwiftUICore?) + +struct ContentPathObservers { + private struct Observer { + weak var value: (any ContentPathObserver)? + } + + private var observers: [Observer] = [] + + @inline(__always) + mutating func addObserver(_ observer: any ContentPathObserver) { + guard !observers.contains(where: { $0.value === observer }) else { return } + observers.append(Observer(value: observer)) + } + + @inline(__always) + mutating func notifyDidChange(for parent: ViewResponder) { + let oldObservers = observers + observers = [] + for observer in oldObservers { + guard let value = observer.value else { continue } + value.respondersDidChange(for: parent) + } + } + + mutating func notifyPathChanged(for parent: ViewResponder, changes: ContentPathChanges, transform: (old: ViewTransform, new: ViewTransform)) { + let oldObservers = observers + observers = [] + var failedObservers: [Observer] = [] + for observer in oldObservers { + var result = true + guard let value = observer.value else { continue } + value.contentPathDidChange(for: parent, changes: changes, transform: transform, finished: &result) + guard !result else { continue } + failedObservers.append(observer) + } + } +} diff --git a/Sources/OpenSwiftUICore/Event/Responder/DefaultLayoutViewResponder.swift b/Sources/OpenSwiftUICore/Event/Responder/DefaultLayoutViewResponder.swift new file mode 100644 index 000000000..33acd27ca --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Responder/DefaultLayoutViewResponder.swift @@ -0,0 +1,118 @@ +// +// DefaultLayoutViewResponder.swift +// OpenSwiftUICore +// +// Status: Complete + +package import OpenGraphShims + +// MARK: - DefaultLayoutResponderFilter [6.5.4] + +package struct DefaultLayoutResponderFilter: StatefulRule { + @Attribute + package var children: [ViewResponder] + + package let responder: MultiViewResponder + + package init( + children: Attribute<[ViewResponder]>, + responder: MultiViewResponder + ) { + self._children = children + self.responder = responder + } + + package typealias Value = [ViewResponder] + + package mutating func updateValue() { + let (children, childrenChanged) = $children.changedValue() + if childrenChanged { + responder.children = children + } + if !hasValue { + value = [responder] + } + } +} + +// MARK: - DefaultLayoutViewResponder [6.5.4] + +@_spi(ForOpenSwiftUIOnly) +@available(OpenSwiftUI_v6_0, *) +open class DefaultLayoutViewResponder: MultiViewResponder { + final package let inputs: _ViewInputs + final package let viewSubgraph: Subgraph + private var childSubgraph: Subgraph? + private var childViewSubgraph: Subgraph? + private var invalidateChildren: (() -> Void)? + + package init(inputs: _ViewInputs) { + self.inputs = inputs + self.viewSubgraph = Subgraph.current! + super.init() + } + + package init(inputs: _ViewInputs, viewSubgraph: Subgraph) { + self.inputs = inputs + self.viewSubgraph = Subgraph.current! + super.init() + } + + // MARK: - DefaultLayoutViewResponder: ResponderNode + + override open func makeGesture(inputs: _GestureInputs) -> _GestureOutputs { + let outputs: _GestureOutputs = inputs.makeDefaultOutputs() + guard viewSubgraph.isValid else { + return outputs + } + let currentSubgraph = Subgraph.current! + let needGestureGraph = inputs.options.contains(.gestureGraph) + childSubgraph = Subgraph(graph: (needGestureGraph ? currentSubgraph : viewSubgraph).graph) + viewSubgraph.addChild(childSubgraph!, tag: 1) + currentSubgraph.addChild(childSubgraph!) + if needGestureGraph { + childViewSubgraph = Subgraph(graph: viewSubgraph.graph) + childSubgraph!.addChild(childViewSubgraph!, tag: 1) + } + childSubgraph!.apply { + let gesture = Attribute(value: DefaultLayoutGesture(responder: self)) + let weakGesture = WeakAttribute(gesture) + invalidateChildren = { + Update.enqueueAction { // TODO: enqueAction(reason: 0x5) + weakGesture.attribute?.invalidateValue() + } + } + let subgraph = (childViewSubgraph ?? childSubgraph)! + var childInputs = inputs + childInputs.viewInputs = self.inputs + childInputs.copyCaches() + childInputs.viewSubgraph = subgraph + let childOutputs = DefaultLayoutGesture.makeDebuggableGesture( + gesture: _GraphValue(gesture), + inputs: childInputs + ) + outputs.overrideDefaultValues(childOutputs) + } + return outputs + } + + override open func resetGesture() { + invalidateChildren = nil + childSubgraph = nil + childViewSubgraph = nil + super.resetGesture() + } + + // MARK: - DefaultLayoutViewResponder: MultiViewResponder + + open override func childrenDidChange() { + if let invalidateChildren { + invalidateChildren() + } + super.childrenDidChange() + } +} + +@_spi(ForOpenSwiftUIOnly) +@available(*, unavailable) +extension DefaultLayoutViewResponder: Sendable {} diff --git a/Sources/OpenSwiftUICore/Event/Responder/HitTestBindingModifier.swift b/Sources/OpenSwiftUICore/Event/Responder/HitTestBindingModifier.swift new file mode 100644 index 000000000..514cfea11 --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Responder/HitTestBindingModifier.swift @@ -0,0 +1,52 @@ +// +// HitTestBindingModifier.swift +// OpenSwiftUICore +// +// Status: WIP +// ID: D16C83991EAE21A87411739F6DC01498 (SwiftUICore) + +package import Foundation + +package typealias PlatformHitTestableEvent = HitTestableEvent + +package struct HitTestBindingModifier: ViewModifier, MultiViewModifier, PrimitiveViewModifier { + nonisolated package static func _makeView( + modifier: _GraphValue, + inputs: _ViewInputs, + body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs + ) -> _ViewOutputs { + openSwiftUIUnimplementedWarning() + return body(_Graph(), inputs) + } + + package typealias Body = Never +} + +extension ViewResponder { + package static var hitTestKey: UInt32 { preconditionFailure("TODO") } + + package static let minOpacityForHitTest: Double = 0.0 + + package func hitTest( + globalPoint: PlatformPoint, + radius: CGFloat, + cacheKey: UInt32? = nil, + options: ContainsPointsOptions = .platformDefault + ) -> ViewResponder? { + openSwiftUIUnimplementedFailure() + } + + private func hitTest( + globalPoints: [PlatformPoint], + weights: [Double], + mask: BitVector64, + cacheKey: UInt32?, + options: ContainsPointsOptions + ) -> ViewResponder? { + openSwiftUIUnimplementedFailure() + } +} + +private func hitPoints(point: PlatformPoint, radius: CGFloat) -> ([PlatformPoint], [Double]) { + openSwiftUIUnimplementedFailure() +} diff --git a/Sources/OpenSwiftUICore/Event/Responder/MultiViewResponder.swift b/Sources/OpenSwiftUICore/Event/Responder/MultiViewResponder.swift new file mode 100644 index 000000000..3deccb25a --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Responder/MultiViewResponder.swift @@ -0,0 +1,115 @@ +// +// MultiViewResponder.swift +// OpenSwiftUICore +// +// Status: Blocked by children.setter +// ID: 4A74C6B0E69BD6BC864CC77E33CF2D28 (SwiftUICore?) + +public import Foundation + +// MARK: - MultiViewResponder [6.5.4] [WIP] + +@_spi(ForOpenSwiftUIOnly) +open class MultiViewResponder: ViewResponder { + private var _children: [ViewResponder] + private var cache: ContainsPointsCache + private var observers: ContentPathObservers + + override public init() { + _children = [] + cache = ContainsPointsCache() + observers = .init() + super.init() + } + + // MARK: - MultiViewResponder: ResponderNode + + override open func bindEvent(_ event: any EventType) -> ResponderNode? { + for child in children { + guard let result = child.bindEvent(event) else { + continue + } + return result + } + return nil + } + + @discardableResult + override final public func visit(applying visitor: (ResponderNode) -> ResponderVisitorResult) -> ResponderVisitorResult { + let result = visitor(self) + guard result == .next else { + return result + } + for child in children { + let childResult = child.visit(applying: visitor) + guard childResult != .cancel else { + return .cancel + } + } + return .next + } + + override open func resetGesture() { + for child in children { + child.resetGesture() + } + } + + // MARK: - MultiViewResponder: ViewResponder + + override open func containsGlobalPoints( + _ points: [PlatformPoint], + cacheKey: UInt32?, + options: ViewResponder.ContainsPointsOptions + ) -> ViewResponder.ContainsPointsResult { + cache.fetch(key: cacheKey) { + var mask: BitVector64 = [] + var priority: Double = 0 + for child in children { + let childResult = child.containsGlobalPoints( + points, + cacheKey: cacheKey, + options: options + ) + mask.formUnion(childResult.mask) + priority = max(priority, childResult.priority) + } + return ContainsPointsResult(mask: mask, priority: priority, children: children) + } + } + + override open func addContentPath( + to path: inout Path, + kind: ContentShapeKinds, + in space: CoordinateSpace, + observer: (any ContentPathObserver)? + ) { + if let observer { + observers.addObserver(observer) + } + for child in children { + child.addContentPath( + to: &path, + kind: kind, + in: space, + observer: observer + ) + } + } + + override open func addObserver(_ observer: any ContentPathObserver) { + observers.addObserver(observer) + } + + override final public var children: [ViewResponder] { + get { _children } + set { openSwiftUIUnimplementedFailure() } + } + + open func childrenDidChange() { + observers.notifyDidChange(for: self) + } +} + +@available(*, unavailable) +extension MultiViewResponder: Sendable {} diff --git a/Sources/OpenSwiftUICore/Event/Responder/ViewResponder.swift b/Sources/OpenSwiftUICore/Event/Responder/ViewResponder.swift index 56457f115..a6497ff31 100644 --- a/Sources/OpenSwiftUICore/Event/Responder/ViewResponder.swift +++ b/Sources/OpenSwiftUICore/Event/Responder/ViewResponder.swift @@ -182,17 +182,3 @@ extension ViewResponder: Sendable {} @_spi(ForOpenSwiftUIOnly) @available(*, unavailable) extension ViewResponder.ContainsPointsOptions: Sendable {} - -// FIXME -package struct HitTestBindingModifier: ViewModifier, /*MultiViewModifier,*/ PrimitiveViewModifier { - nonisolated package static func _makeView( - modifier: _GraphValue, - inputs: _ViewInputs, - body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs - ) -> _ViewOutputs { - // preconditionFailure("TODO") - return body(_Graph(), inputs) - } - - package typealias Body = Never -}