Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions Sources/OpenSwiftUICore/Event/Gesture/LayoutGesture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,16 @@ private final class LayoutGestureBox {
debugData = .reset(attribute.value)
}
phase = nil
subgraph?.willInvalidate(isInserted: true)
subgraph?.invalidate()
if let subgraph {
subgraph.willInvalidate(isInserted: true)
subgraph.invalidate()
}
subgraph = nil
responder.resetGesture()
}
events = [:]
seenEventIDs = []
resetDelta &+= 1
resetDelta.unsafeIncrement()
}
}

Expand All @@ -194,8 +196,7 @@ private final class LayoutGestureBox {
}
self.resetSeed = resetSeed
for index in children.indices {
children[index].reset()
seed &+= 1
resetChild(at: index)
}
seed &+= 1
}
Expand Down Expand Up @@ -223,8 +224,7 @@ private final class LayoutGestureBox {
}
while index < count {
count -= 1
children[count].reset()
seed &+= 1
resetChild(at: count)
children.removeLast()
changed = true
}
Expand Down Expand Up @@ -292,10 +292,22 @@ private final class LayoutGestureBox {
children[index].phase!.value.isTerminal else {
continue
}
children[index].reset()
seed &+= 1
resetChild(at: index)
}
}

private func resetChild(at index: Int) {
// OpenSwiftUI Addition:
// SwiftUI's original implementation will trigger a crash with
// SWIFTUI_GESTURE_CONTAINER=0 && SWIFTUI_EVENT_DEBUG=G
//
// children[index].reset()
var child = children[index]
child.reset()
children[index] = child

seed.unsafeIncrement()
}
}

// MARK: - LayoutGestureChildProxy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,49 @@ struct LayoutGestureTests {
}
}

@Test(arguments: ResetTrigger.allCases)
func childResetEvaluatesLazyDebugInputs(trigger: ResetTrigger) {
withFixture(debugOutput: true) { fixture in
let child = LazyDebugResponder(phase: trigger == .terminal ? .ended(()) : .active(()))
fixture.setChildren([child])
fixture.send(to: child)

if trigger != .terminal {
#expect(fixture.outputs.phase.value.isActive)
#expect(child.debugData == nil)
switch trigger {
case .terminal: break
case .seed: fixture.resetSeed.value = 1
case .removal: fixture.setChildren([])
}
}

let resetsBeforeEvaluation = child.resetCount
let phase = fixture.outputs.phase.value
switch trigger {
case .terminal: #expect(phase.isEnded)
case .seed: #expect(phase.isActive)
case .removal: #expect(phase.isFailed)
}
#expect(child.resetCount - resetsBeforeEvaluation == 1)
#expect(child.debugData?.resetSeed == (trigger == .seed ? 1 : 0))
#expect(child.debugEventCount == 1)

if trigger == .terminal {
let data = fixture.outputs.debugData!.value
#expect(data.children.count == 1)
#expect(data.children[0].phase.isEnded)
#expect(data.children[0].resetSeed == 0)
}
}
}

enum ResetTrigger: CaseIterable {
case terminal
case seed
case removal
}

@Test(arguments: [
({ @Sendable in [] }, "failed"),
({ @Sendable in [.failed, .failed] }, "failed"),
Expand Down Expand Up @@ -407,6 +450,56 @@ private final class TestResponder: ViewResponder {
}
}

private final class LazyDebugResponder: ViewResponder {
let phase: GesturePhase<Void>
var debugData: GestureDebug.Data?
var debugEventCount: Int?
var resetCount = 0

init(phase: GesturePhase<Void>) {
self.phase = phase
super.init()
}

override func makeGesture(inputs: _GestureInputs) -> _GestureOutputs<Void> {
var outputs = _GestureOutputs(phase: Attribute(value: phase))
outputs.debugData = Attribute(Debug(
responder: self,
phase: outputs.phase,
events: inputs.events,
resetSeed: inputs.resetSeed
))
return outputs
}

override func resetGesture() {
resetCount += 1
}

private struct Debug: Rule {
let responder: LazyDebugResponder
@Attribute var phase: GesturePhase<Void>
@Attribute var events: [EventID: any EventType]
@Attribute var resetSeed: UInt32

var value: GestureDebug.Data {
let data = GestureDebug.Data(
kind: .primitive,
type: TestGesture.self,
children: .init(),
phase: phase,
attribute: $phase.identifier,
resetSeed: resetSeed,
frame: .zero,
properties: .init()
)
responder.debugEventCount = events.count
responder.debugData = data
return data
}
}
}

private final class TestHost: ViewRendererHost, EventGraphHost {
let viewGraph: ViewGraph
let eventBindingManager = EventBindingManager()
Expand Down
Loading