diff --git a/Sources/OpenSwiftUICore/Event/Event/Event.swift b/Sources/OpenSwiftUICore/Event/Event/Event.swift index feee8738b..9e2e1d39b 100644 --- a/Sources/OpenSwiftUICore/Event/Event/Event.swift +++ b/Sources/OpenSwiftUICore/Event/Event/Event.swift @@ -2,161 +2,10 @@ // Event.swift // OpenSwiftUICore // +// Audited for 6.5.4 // Status: Complete -package import Foundation - -// MARK: - MouseEvent [6.5.4] - -package struct MouseEvent: SpatialEventType, TappableEventType, ModifiersEventType, Equatable { - package struct Button: RawRepresentable, Equatable { - package let rawValue: Int - - package init(rawValue: Int) { - self.rawValue = rawValue - } - - package static let primary: MouseEvent.Button = .init(rawValue: 1 << 0) - - package static let secondary: MouseEvent.Button = .init(rawValue: 1 << 1) - - package static func other(_ index: Int) -> MouseEvent.Button { - .init(rawValue: index) - } - } - - package var timestamp: Time - package var binding: EventBinding? - package var button: MouseEvent.Button - package var phase: EventPhase - package var location: CGPoint - package var globalLocation: CGPoint - package var modifiers: EventModifiers - - package init( - timestamp: Time, - binding: EventBinding? = nil, - button: MouseEvent.Button, - phase: EventPhase, - location: CGPoint, - globalLocation: CGPoint, - modifiers: EventModifiers - ) { - self.timestamp = timestamp - self.binding = binding - self.button = button - self.phase = phase - self.location = location - self.globalLocation = globalLocation - self.modifiers = modifiers - } - - package var radius: CGFloat { .zero } - - package var kind: SpatialEvent.Kind? { .mouse } -} - -extension MouseEvent: HitTestableEventType {} - -// MARK: - EventModifiers [6.5.4] - -/// A set of key modifiers that you can add to a gesture. -@available(OpenSwiftUI_v1_0, *) -@frozen -public struct EventModifiers: OptionSet { - public let rawValue: Int - - public init(rawValue: Int) { - self.rawValue = rawValue - } - - /// The Caps Lock key. - public static let capsLock: EventModifiers = .init(rawValue: 1 << 0) - - /// The Shift key. - public static let shift: EventModifiers = .init(rawValue: 1 << 1) - - /// The Control key. - public static let control: EventModifiers = .init(rawValue: 1 << 2) - - /// The Option key. - public static let option: EventModifiers = .init(rawValue: 1 << 3) - - /// The Command key. - public static let command: EventModifiers = .init(rawValue: 1 << 4) - - /// Any key on the numeric keypad. - public static let numericPad: EventModifiers = .init(rawValue: 1 << 5) - - /// The Function key. - @available(*, deprecated, message: "Function modifier is reserved for system applications") - public static let function: EventModifiers = .init(rawValue: 1 << 6) - - package static let _function: EventModifiers = .init(rawValue: 1 << 6) - - /// All possible modifier keys. - public static let all: EventModifiers = [.capsLock, .shift, .control, .option, .command, .numericPad] - - package static let _all: EventModifiers = [.capsLock, .shift, .control, .option, .command, .numericPad, ._function] -} - -// MARK: - ModifiersEventType [6.5.4] - -package protocol ModifiersEventType: EventType { - var modifiers: EventModifiers { get set } -} - -// MARK: - EventPhase [6.5.4] - -@_spi(ForOpenSwiftUIOnly) -@available(OpenSwiftUI_v6_0, *) -public enum EventPhase: Hashable { - case began - case active - case ended - case failed -} - -@available(*, unavailable) -extension EventPhase: Sendable {} - -extension EventPhase { - package var isTerminal: Bool { - switch self { - case .began, .active: false - case .ended, .failed: true - } - } -} - -// MARK: - EventType [6.5.4] - -@_spi(ForOpenSwiftUIOnly) -@available(OpenSwiftUI_v6_0, *) -public protocol EventType { - var phase: EventPhase { get } - - var timestamp: Time { get } - - var binding: EventBinding? { get set } - - init?(_ event: any EventType) -} - -extension EventType { - package init?(_ event: any EventType) { - guard let event = event as? Self else { - return nil - } - self = event - } - - package var isFocusEvent: Bool { - HitTestableEvent(self) == nil - } -} - -// MARK: - Event [6.5.4] +// MARK: - Event package struct Event: EventType { package var phase: EventPhase @@ -175,43 +24,3 @@ package struct Event: EventType { binding = event.binding } } - -// MARK: - EventID [6.5.4] - -@_spi(ForOpenSwiftUIOnly) -@available(OpenSwiftUI_v6_0, *) -public struct EventID: Hashable { - package var type: any Any.Type - - package var serial: Int - - package init(type: any Any.Type, serial: Int) { - self.type = type - self.serial = serial - } - - public static func == (lhs: EventID, rhs: EventID) -> Bool { - lhs.type == rhs.type && lhs.serial == rhs.serial - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(type)) - hasher.combine(serial) - } -} - -@available(*, unavailable) -extension EventID: Sendable {} - -extension EventID { - package init(_ obj: T, subtype: S.Type) where T: NSObject { - type = (T, S).self - serial = unsafeBitCast(obj, to: Int.self) - } -} - -extension EventID: CustomStringConvertible { - public var description: String { - "\(type)#\(serial)" - } -} diff --git a/Sources/OpenSwiftUICore/Event/Event/EventBinding.swift b/Sources/OpenSwiftUICore/Event/Event/EventBinding.swift index 6010eddbd..20bda64e1 100644 --- a/Sources/OpenSwiftUICore/Event/Event/EventBinding.swift +++ b/Sources/OpenSwiftUICore/Event/Event/EventBinding.swift @@ -2,9 +2,10 @@ // EventBinding.swift // OpenSwiftUICore // +// Audited for 6.5.4 // Status: Complete -// MARK: - EventBinding [6.5.4] +// MARK: - EventBinding @_spi(ForOpenSwiftUIOnly) @available(OpenSwiftUI_v6_0, *) diff --git a/Sources/OpenSwiftUICore/Event/Event/EventBindingBridge.swift b/Sources/OpenSwiftUICore/Event/Event/EventBindingBridge.swift index ff913572c..d5871f904 100644 --- a/Sources/OpenSwiftUICore/Event/Event/EventBindingBridge.swift +++ b/Sources/OpenSwiftUICore/Event/Event/EventBindingBridge.swift @@ -2,10 +2,11 @@ // EventBindingBridge.swift // OpenSwiftUICore // -// Status: WIP +// Audited for 6.5.4 +// Status: Complete // ID: E11AC34B5BFF53E1001A61D61F5B9E0F (SwiftUICore) -// MARK: - EventBindingBridge [6.5.4] [WIP] +// MARK: - EventBindingBridge @_spi(ForOpenSwiftUIOnly) @available(OpenSwiftUI_v6_0, *) @@ -25,27 +26,87 @@ open class EventBindingBridge { self.eventBindingManager = eventBindingManager } - public init() {} + public init() { + _openSwiftUIEmptyStub() + } open var eventSources: [any EventBindingSource] { [] } + // MARK: - Event dispatch tracking + @discardableResult open func send( _ events: [EventID: any EventType], source: any EventBindingSource ) -> Set { - _openSwiftUIUnimplementedFailure() + guard !events.isEmpty else { + return [] + } + var downstreamEvents: [EventID: any EventType] = [:] + let sourceID = ObjectIdentifier(source) + for (id, event) in events { + guard !(event is any NonGestureEventType) else { + _ = Event(event) + downstreamEvents[id] = event + continue + } + let trackedEvent: TrackedEventState + let reset: Bool + if let existingState = trackedEvents[id] { + trackedEvent = existingState + reset = trackedEvent.reset + } else { + trackedEvent = TrackedEventState( + sourceID: sourceID, + reset: false + ) + reset = false + } + if event.phase == .active { + trackedEvents[id] = trackedEvent + } else { + trackedEvents.removeValue(forKey: id) + } + if !reset { + downstreamEvents[id] = event + } + } + guard !downstreamEvents.isEmpty, + let eventBindingManager else { + return [] + } + return eventBindingManager.send(downstreamEvents) } + // MARK: - Event source reset + open func reset( eventSource: any EventBindingSource, resetForwardedEventDispatchers: Bool = false ) { - _openSwiftUIUnimplementedFailure() + let sourceID = ObjectIdentifier(eventSource) + var hasActiveEvent = false + for (id, trackedEvent) in trackedEvents { + if trackedEvent.sourceID == sourceID { + trackedEvents.removeValue(forKey: id) + } else if !trackedEvent.reset { + hasActiveEvent = true + } + } + guard !hasActiveEvent else { + return + } + eventBindingManager?.reset( + resetForwardedEventDispatchers: resetForwardedEventDispatchers + ) } - private func resetEvent() { - _openSwiftUIUnimplementedFailure() + private func resetEvents() { + for (id, trackedEvent) in trackedEvents { + var trackedEvent = trackedEvent + trackedEvent.reset = true + trackedEvents[id] = trackedEvent + } } open func setInheritedPhase(_ phase: _GestureInputs.InheritedPhase) { @@ -61,7 +122,7 @@ open class EventBindingBridge { @available(*, unavailable) extension EventBindingBridge: Sendable {} -// MARK: - EventBindingBridge + EventBindingManagerDelegate [6.5.4] +// MARK: - EventBindingBridge + EventBindingManagerDelegate @_spi(ForOpenSwiftUIOnly) extension EventBindingBridge: EventBindingManagerDelegate { @@ -70,8 +131,7 @@ extension EventBindingBridge: EventBindingManagerDelegate { id: EventID ) { if let responderWasBoundHandler { - // TODO: Update.enqueueAction(reason:_:) - Update.enqueueAction { + Update.enqueueAction(reason: nil) { responderWasBoundHandler(newBinding.responder) } } @@ -87,6 +147,9 @@ extension EventBindingBridge: EventBindingManagerDelegate { for eventSource in eventSources { eventSource.didUpdate(phase: phase, in: self) } + if phase.isTerminal { + resetEvents() + } } package func didUpdate( @@ -97,4 +160,14 @@ extension EventBindingBridge: EventBindingManagerDelegate { eventSource.didUpdate(gestureCategory: gestureCategory, in: self) } } + + #if os(macOS) + package func requestHoverUpdate( + in eventBindingManager: EventBindingManager + ) { + for eventSource in eventSources { + eventSource.didRequestHoverUpdate(in: self) + } + } + #endif } diff --git a/Sources/OpenSwiftUICore/Event/Event/EventBindingManager.swift b/Sources/OpenSwiftUICore/Event/Event/EventBindingManager.swift index b76d65e38..3348042a7 100644 --- a/Sources/OpenSwiftUICore/Event/Event/EventBindingManager.swift +++ b/Sources/OpenSwiftUICore/Event/Event/EventBindingManager.swift @@ -2,12 +2,13 @@ // EventBindingManager.swift // OpenSwiftUICore // -// Status: WIP +// Audited for 6.5.4 +// Status: Complete // ID: D63F4C292364B83D9F441CFC1A31B3F3 (SwiftUICore) import Foundation -// MARK: - EventBindingManager [6.5.4] [WIP] +// MARK: - EventBindingManager @_spi(ForOpenSwiftUIOnly) @available(OpenSwiftUI_v6_0, *) @@ -20,7 +21,7 @@ final public class EventBindingManager { private var eventBindings: [EventID: EventBinding] = [:] - private(set) package var isActive: Bool = false + package private(set) var isActive: Bool = false package static var current: EventBindingManager? { guard let delegate = ViewGraph.current.delegate, @@ -34,6 +35,10 @@ final public class EventBindingManager { private var eventTimer: Timer? + #if os(macOS) + private var hasPendingHoverUpdate: Bool = false + #endif + package init() { _openSwiftUIEmptyStub() } @@ -42,6 +47,106 @@ final public class EventBindingManager { eventTimer?.invalidate() } + private func sendDownstream(_ events: [EventID: any EventType]) -> Set { + guard let host else { + return [] + } + let rootResponder = self.rootResponder + if _eventDebugTriggers.contains(.responders) { + Log.eventDebug("RESPONDERS") + if let rootResponder = rootResponder as? ViewResponder { + rootResponder.printTree() + } + Log.eventDebug("") + } + printEvents(events) + var dispatchedIDs = dispatchNonGestureEvents(events) + let gestureEvents = events.optimisticFilter { _, event in + forwardedEventDispatchers[ObjectIdentifier(type(of: event))] == nil + } + guard isActive || !gestureEvents.isEmpty else { + return dispatchedIDs + } + var downstreamEvents: [EventID: any EventType] = [:] + var terminalEventIDs: [EventID] = [] + for (id, event) in gestureEvents { + let binding: EventBinding? + let createdBinding: Bool + if let existingBinding = eventBindings[id] { + binding = existingBinding + createdBinding = false + } else { + let responder: ResponderNode? + if let focusedResponder, event.isFocusEvent { + responder = focusedResponder.bindEvent(event) ?? focusedResponder + } else { + responder = rootResponder?.bindEvent(event) + } + binding = responder.map(EventBinding.init(responder:)) + createdBinding = binding != nil + } + if let binding { + var event = event + event.binding = binding + eventBindings[id] = binding + downstreamEvents[id] = event + if createdBinding { + isActive = true + delegate?.didBind(to: binding, id: id) + } + } + if event.phase.isTerminal { + terminalEventIDs.append(id) + } + } + printEventBindings(eventBindings) + var phase: GesturePhase = .failed + var nextUpdateTime: Time = .infinity + var gestureCategory: GestureCategory = [] + if isActive, let rootResponder { + phase = host.sendEvents( + downstreamEvents, + rootNode: rootResponder, + at: .systemUptime + ) + nextUpdateTime = host.nextGestureUpdateTime + gestureCategory = host.gestureCategory() ?? [] + if !phase.isFailed { + dispatchedIDs.formUnion(downstreamEvents.keys) + } + } + if _eventDebugTriggers.contains(.eventPhases), let rootResponder { + rootResponder.log(action: "root-phase", data: phase) + } + delegate?.didUpdate(phase: phase, in: self) + delegate?.didUpdate(gestureCategory: gestureCategory, in: self) + if isActive, nextUpdateTime < .infinity { + scheduleNextEventUpdate(time: nextUpdateTime) + } + for id in terminalEventIDs { + eventBindings.removeValue(forKey: id) + } + return dispatchedIDs + } + + private func scheduleNextEventUpdate(time: Time) { + eventTimer?.invalidate() + eventTimer = nil + let interval = time - .systemUptime + guard interval > 0, interval.isFinite else { + return + } + let timer = Timer(timeInterval: interval, repeats: false) { [weak self] _ in + guard let self else { + return + } + eventTimer = nil + _ = send([:]) + } + RunLoop.main.add(timer, forMode: .common) + eventTimer = timer + } + package func addForwardedEventDispatcher(_ dispatcher: any ForwardedEventDispatcher) { forwardedEventDispatchers[ObjectIdentifier(type(of: dispatcher).eventType)] = dispatcher } @@ -50,24 +155,44 @@ final public class EventBindingManager { _ identifier: EventID, to: ResponderNode? ) -> (from: EventBinding?, to: EventBinding?)? { - _openSwiftUIUnimplementedFailure() + let oldBinding = eventBindings[identifier] + guard oldBinding?.responder !== to else { + return nil + } + let newBinding = to.map(EventBinding.init(responder:)) + eventBindings[identifier] = newBinding + return (oldBinding, newBinding) } package func willRemoveResponder(_ from: ResponderNode) { - _openSwiftUIUnimplementedFailure() + let nextResponder = from.nextResponder + for (identifier, binding) in eventBindings { + var responder: ResponderNode? = binding.responder + while let currentResponder = responder, + currentResponder !== from, + currentResponder !== nextResponder { + responder = currentResponder.nextResponder + } + guard responder === from else { + continue + } + eventBindings[identifier] = nextResponder.map(EventBinding.init(responder:)) + } } package func setInheritedPhase(_ phase: _GestureInputs.InheritedPhase) { - _openSwiftUIUnimplementedFailure() - } - - private func sendDownstream(_ events: [EventID: any EventType]) -> Set { - _openSwiftUIUnimplementedFailure() + guard let host else { + return + } + Update.locked { + host.setInheritedPhase(phase) + _ = send([:]) + } } @discardableResult package func send(_ events: [EventID: any EventType]) -> Set { - Update.locked { [weak self] in + Update.ensure { [weak self] in guard let self else { return [] } @@ -76,23 +201,50 @@ final public class EventBindingManager { } package func send(_ event: E, id: Int) where E: EventType { - _openSwiftUIUnimplementedFailure() + send([EventID(type: E.self, serial: id): event]) } - package var rootResponder: ResponderNode? { _openSwiftUIUnimplementedFailure() } + package var rootResponder: ResponderNode? { host?.responderNode } - package var focusedResponder: ResponderNode? { _openSwiftUIUnimplementedFailure() } + package var focusedResponder: ResponderNode? { host?.focusedResponder } package func reset(resetForwardedEventDispatchers: Bool = false) { - _openSwiftUIUnimplementedFailure() + Update.enqueueAction(reason: nil) { [weak self] in + guard let self, + let host else { + return + } + host.resetEvents() + } + if resetForwardedEventDispatchers { + for (key, dispatcher) in forwardedEventDispatchers { + var dispatcher = dispatcher + dispatcher.reset() + forwardedEventDispatchers[key] = dispatcher + } + } + eventBindings = [:] + eventTimer?.invalidate() + eventTimer = nil + isActive = false } package func isActive(for eventType: E.Type) -> Bool where E: EventType { - _openSwiftUIUnimplementedFailure() + for dispatcher in forwardedEventDispatchers.values { + if type(of: dispatcher).eventType == eventType { + return dispatcher.isActive + } + } + return isActive } package func binds(_ event: E) -> Bool where E: EventType { - _openSwiftUIUnimplementedFailure() + for dispatcher in forwardedEventDispatchers.values { + if type(of: event) == type(of: dispatcher).eventType { + return dispatcher.wantsEvent(event, manager: self) + } + } + return false } } @@ -100,7 +252,45 @@ final public class EventBindingManager { @available(*, unavailable) extension EventBindingManager: Sendable {} -// MARK: - ForwardedEventDispatcher [6.5.4] +@_spi(ForOpenSwiftUIOnly) +extension EventBindingManager { + #if os(macOS) + package func enqueueHoverUpdateIfNeeded() { + guard !hasPendingHoverUpdate else { + return + } + hasPendingHoverUpdate = true + Update.enqueueAction(reason: nil) { [weak self] in + guard let self else { + return + } + hasPendingHoverUpdate = false + delegate?.requestHoverUpdate(in: self) + } + } + #endif + + private func dispatchNonGestureEvents(_ events: [EventID: any EventType]) -> Set { + var dispatchedIDs: Set = [] + for key in forwardedEventDispatchers.keys { + var dispatcher = forwardedEventDispatchers[key]! + let matchingEvents = events.optimisticFilter { _, event in + type(of: event) == type(of: dispatcher).eventType + } + if !matchingEvents.isEmpty { + let receivedIDs = dispatcher.receiveEvents(matchingEvents, manager: self) + forwardedEventDispatchers[key] = dispatcher + dispatchedIDs.formUnion(receivedIDs) + } + if dispatchedIDs.count == events.count { + break + } + } + return dispatchedIDs + } +} + +// MARK: - ForwardedEventDispatcher package protocol ForwardedEventDispatcher { static var eventType: any EventType.Type { get } @@ -121,7 +311,9 @@ package protocol ForwardedEventDispatcher { } extension ForwardedEventDispatcher { - package var isActive: Bool { false } + package var isActive: Bool { + false + } package func wantsEvent( _ event: any EventType, @@ -130,10 +322,12 @@ extension ForwardedEventDispatcher { true } - package mutating func reset() {} + package mutating func reset() { + _openSwiftUIEmptyStub() + } } -// MARK: - EventBindingManagerDelegate [6.5.4] +// MARK: - EventBindingManagerDelegate package protocol EventBindingManagerDelegate: AnyObject { func didBind( @@ -150,6 +344,12 @@ package protocol EventBindingManagerDelegate: AnyObject { gestureCategory: GestureCategory, in eventBindingManager: EventBindingManager ) + + #if os(macOS) + func requestHoverUpdate( + in eventBindingManager: EventBindingManager + ) + #endif } extension EventBindingManagerDelegate { @@ -166,4 +366,12 @@ extension EventBindingManagerDelegate { ) { _openSwiftUIEmptyStub() } + + #if os(macOS) + package func requestHoverUpdate( + in eventBindingManager: EventBindingManager + ) { + _openSwiftUIEmptyStub() + } + #endif } diff --git a/Sources/OpenSwiftUICore/Event/Event/EventBindingSource.swift b/Sources/OpenSwiftUICore/Event/Event/EventBindingSource.swift index fb4faabd2..16a21a9b6 100644 --- a/Sources/OpenSwiftUICore/Event/Event/EventBindingSource.swift +++ b/Sources/OpenSwiftUICore/Event/Event/EventBindingSource.swift @@ -2,9 +2,10 @@ // EventBindingSource.swift // OpenSwiftUICore // +// Audited for 6.5.4 // Status: Complete -// MARK: - EventBindingSource [6.5.4] +// MARK: - EventBindingSource @_spi(ForOpenSwiftUIOnly) @available(OpenSwiftUI_v6_0, *) @@ -28,6 +29,12 @@ public protocol EventBindingSource: AnyObject { id: EventID, in eventBridge: EventBindingBridge ) + + #if os(macOS) + func didRequestHoverUpdate( + in eventBridge: EventBindingBridge + ) + #endif } @_spi(ForOpenSwiftUIOnly) @@ -37,21 +44,35 @@ extension EventBindingSource { public func didUpdate( phase: GesturePhase, in eventBridge: EventBindingBridge - ) {} + ) { + _openSwiftUIEmptyStub() + } public func didUpdate( gestureCategory: GestureCategory, in eventBridge: EventBindingBridge - ) {} + ) { + _openSwiftUIEmptyStub() + } public func didBind( to newBinding: EventBinding, id: EventID, in eventBridge: EventBindingBridge - ) {} + ) { + _openSwiftUIEmptyStub() + } + + #if os(macOS) + public func didRequestHoverUpdate( + in eventBridge: EventBindingBridge + ) { + _openSwiftUIEmptyStub() + } + #endif } -// MARK: - EventBindingBridgeFactory [6.5.4] +// MARK: - EventBindingBridgeFactory package protocol EventBindingBridgeFactory { static func makeEventBindingBridge( diff --git a/Sources/OpenSwiftUICore/Event/Event/EventGraphHost.swift b/Sources/OpenSwiftUICore/Event/Event/EventGraphHost.swift index 60605f92b..c74198571 100644 --- a/Sources/OpenSwiftUICore/Event/Event/EventGraphHost.swift +++ b/Sources/OpenSwiftUICore/Event/Event/EventGraphHost.swift @@ -2,9 +2,10 @@ // EventGraphHost.swift // OpenSwiftUICore // +// Audited for 6.5.4 // Status: Complete -// MARK: - EventGraphHost [6.5.4] +// MARK: - EventGraphHost package protocol EventGraphHost: AnyObject { var eventBindingManager: EventBindingManager { get } diff --git a/Sources/OpenSwiftUICore/Event/Event/EventID.swift b/Sources/OpenSwiftUICore/Event/Event/EventID.swift new file mode 100644 index 000000000..7c9312dba --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Event/EventID.swift @@ -0,0 +1,48 @@ +// +// EventID.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +package import Foundation + +// MARK: - EventID + +@_spi(ForOpenSwiftUIOnly) +@available(OpenSwiftUI_v6_0, *) +public struct EventID: Hashable { + package var type: any Any.Type + + package var serial: Int + + package init(type: any Any.Type, serial: Int) { + self.type = type + self.serial = serial + } + + public static func == (lhs: EventID, rhs: EventID) -> Bool { + lhs.type == rhs.type && lhs.serial == rhs.serial + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(type)) + hasher.combine(serial) + } +} + +@available(*, unavailable) +extension EventID: Sendable {} + +extension EventID { + package init(_ obj: T, subtype: S.Type) where T: NSObject { + type = (T, S).self + serial = unsafeBitCast(obj, to: Int.self) + } +} + +extension EventID: CustomStringConvertible { + public var description: String { + "\(type)#\(serial)" + } +} diff --git a/Sources/OpenSwiftUICore/Event/Event/EventModifiers.swift b/Sources/OpenSwiftUICore/Event/Event/EventModifiers.swift new file mode 100644 index 000000000..6b8584d07 --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Event/EventModifiers.swift @@ -0,0 +1,54 @@ +// +// EventModifiers.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +// MARK: - EventModifiers + +/// A set of key modifiers that you can add to a gesture. +@available(OpenSwiftUI_v1_0, *) +@frozen +public struct EventModifiers: OptionSet { + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + /// The Caps Lock key. + public static let capsLock: EventModifiers = .init(rawValue: 1 << 0) + + /// The Shift key. + public static let shift: EventModifiers = .init(rawValue: 1 << 1) + + /// The Control key. + public static let control: EventModifiers = .init(rawValue: 1 << 2) + + /// The Option key. + public static let option: EventModifiers = .init(rawValue: 1 << 3) + + /// The Command key. + public static let command: EventModifiers = .init(rawValue: 1 << 4) + + /// Any key on the numeric keypad. + public static let numericPad: EventModifiers = .init(rawValue: 1 << 5) + + /// The Function key. + @available(*, deprecated, message: "Function modifier is reserved for system applications") + public static let function: EventModifiers = .init(rawValue: 1 << 6) + + package static let _function: EventModifiers = .init(rawValue: 1 << 6) + + /// All possible modifier keys. + public static let all: EventModifiers = [.capsLock, .shift, .control, .option, .command, .numericPad] + + package static let _all: EventModifiers = [.capsLock, .shift, .control, .option, .command, .numericPad, ._function] +} + +// MARK: - ModifiersEventType + +package protocol ModifiersEventType: EventType { + var modifiers: EventModifiers { get set } +} diff --git a/Sources/OpenSwiftUICore/Event/Event/EventPhase.swift b/Sources/OpenSwiftUICore/Event/Event/EventPhase.swift new file mode 100644 index 000000000..88d4adbb1 --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Event/EventPhase.swift @@ -0,0 +1,29 @@ +// +// EventPhase.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +// MARK: - EventPhase + +@_spi(ForOpenSwiftUIOnly) +@available(OpenSwiftUI_v6_0, *) +public enum EventPhase: Hashable { + case began + case active + case ended + case failed +} + +@available(*, unavailable) +extension EventPhase: Sendable {} + +extension EventPhase { + package var isTerminal: Bool { + switch self { + case .began, .active: false + case .ended, .failed: true + } + } +} diff --git a/Sources/OpenSwiftUICore/Event/Event/EventSourceType.swift b/Sources/OpenSwiftUICore/Event/Event/EventSourceType.swift index 2fd8506ef..dca46fa16 100644 --- a/Sources/OpenSwiftUICore/Event/Event/EventSourceType.swift +++ b/Sources/OpenSwiftUICore/Event/Event/EventSourceType.swift @@ -2,9 +2,10 @@ // EventSourceType.swift // OpenSwiftUICore // +// Audited for 6.5.4 // Status: Complete -// MARK: - EventSourceType [6.5.4] +// MARK: - EventSourceType @_spi(ForOpenSwiftUIOnly) @available(OpenSwiftUI_v6_0, *) diff --git a/Sources/OpenSwiftUICore/Event/Event/EventType.swift b/Sources/OpenSwiftUICore/Event/Event/EventType.swift new file mode 100644 index 000000000..be5d2a0ea --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Event/EventType.swift @@ -0,0 +1,33 @@ +// +// EventType.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +// MARK: - EventType + +@_spi(ForOpenSwiftUIOnly) +@available(OpenSwiftUI_v6_0, *) +public protocol EventType { + var phase: EventPhase { get } + + var timestamp: Time { get } + + var binding: EventBinding? { get set } + + init?(_ event: any EventType) +} + +extension EventType { + package init?(_ event: any EventType) { + guard let event = event as? Self else { + return nil + } + self = event + } + + package var isFocusEvent: Bool { + HitTestableEvent(self) == nil + } +} diff --git a/Sources/OpenSwiftUICore/Event/Event/HitTestableEvent.swift b/Sources/OpenSwiftUICore/Event/Event/HitTestableEvent.swift index 55684b9e4..e7f354cb7 100644 --- a/Sources/OpenSwiftUICore/Event/Event/HitTestableEvent.swift +++ b/Sources/OpenSwiftUICore/Event/Event/HitTestableEvent.swift @@ -2,18 +2,19 @@ // HitTestableEvent.swift // OpenSwiftUICore // +// Audited for 6.5.4 // Status: Complete package import Foundation -// MARK: - HitTestableEventType [6.5.4] +// MARK: - HitTestableEventType package protocol HitTestableEventType: EventType { var hitTestLocation: CGPoint { get } var hitTestRadius: CGFloat { get } } -// MARK: - HitTestableEvent [6.5.4] +// MARK: - HitTestableEvent package struct HitTestableEvent: HitTestableEventType, Equatable { package var phase: EventPhase diff --git a/Sources/OpenSwiftUICore/Event/Event/MouseEvent.swift b/Sources/OpenSwiftUICore/Event/Event/MouseEvent.swift new file mode 100644 index 000000000..88a56200e --- /dev/null +++ b/Sources/OpenSwiftUICore/Event/Event/MouseEvent.swift @@ -0,0 +1,60 @@ +// +// MouseEvent.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +package import Foundation + +// MARK: - MouseEvent + +package struct MouseEvent: SpatialEventType, TappableEventType, ModifiersEventType, Equatable { + package struct Button: RawRepresentable, Equatable { + package let rawValue: Int + + package init(rawValue: Int) { + self.rawValue = rawValue + } + + package static let primary: MouseEvent.Button = .init(rawValue: 1 << 0) + + package static let secondary: MouseEvent.Button = .init(rawValue: 1 << 1) + + package static func other(_ index: Int) -> MouseEvent.Button { + .init(rawValue: index) + } + } + + package var timestamp: Time + package var binding: EventBinding? + package var button: MouseEvent.Button + package var phase: EventPhase + package var location: CGPoint + package var globalLocation: CGPoint + package var modifiers: EventModifiers + + package init( + timestamp: Time, + binding: EventBinding? = nil, + button: MouseEvent.Button, + phase: EventPhase, + location: CGPoint, + globalLocation: CGPoint, + modifiers: EventModifiers + ) { + self.timestamp = timestamp + self.binding = binding + self.button = button + self.phase = phase + self.location = location + self.globalLocation = globalLocation + self.modifiers = modifiers + } + + package var radius: CGFloat { .zero } + + package var kind: SpatialEvent.Kind? { .mouse } +} + +extension MouseEvent: HitTestableEventType {} diff --git a/Sources/OpenSwiftUICore/Event/Responder/ContentPathObserver.swift b/Sources/OpenSwiftUICore/Event/Responder/ContentPathObserver.swift index 1b2edc6fc..8fa29cd93 100644 --- a/Sources/OpenSwiftUICore/Event/Responder/ContentPathObserver.swift +++ b/Sources/OpenSwiftUICore/Event/Responder/ContentPathObserver.swift @@ -2,6 +2,7 @@ // ContentPathObserver.swift // OpenSwiftUICore // +// Audited for 6.5.4 // Status: Complete @_spi(ForOpenSwiftUIOnly) diff --git a/Sources/OpenSwiftUICore/Event/Responder/DefaultLayoutViewResponder.swift b/Sources/OpenSwiftUICore/Event/Responder/DefaultLayoutViewResponder.swift index e9b940c57..c793ccb03 100644 --- a/Sources/OpenSwiftUICore/Event/Responder/DefaultLayoutViewResponder.swift +++ b/Sources/OpenSwiftUICore/Event/Responder/DefaultLayoutViewResponder.swift @@ -2,11 +2,12 @@ // DefaultLayoutViewResponder.swift // OpenSwiftUICore // +// Audited for 6.5.4 // Status: Complete package import OpenAttributeGraphShims -// MARK: - DefaultLayoutResponderFilter [6.5.4] +// MARK: - DefaultLayoutResponderFilter package struct DefaultLayoutResponderFilter: StatefulRule { @Attribute @@ -35,7 +36,7 @@ package struct DefaultLayoutResponderFilter: StatefulRule { } } -// MARK: - DefaultLayoutViewResponder [6.5.4] +// MARK: - DefaultLayoutViewResponder @_spi(ForOpenSwiftUIOnly) @available(OpenSwiftUI_v6_0, *) @@ -78,7 +79,7 @@ open class DefaultLayoutViewResponder: MultiViewResponder { let gesture = Attribute(value: DefaultLayoutGesture(responder: self)) let weakGesture = WeakAttribute(gesture) invalidateChildren = { - Update.enqueueAction { // TODO: enqueAction(reason: 0x5) + Update.enqueueAction(reason: nil) { weakGesture.attribute?.invalidateValue() } } diff --git a/Sources/OpenSwiftUICore/Event/Responder/ResponderNode.swift b/Sources/OpenSwiftUICore/Event/Responder/ResponderNode.swift index f5ed3e2f4..020a66f44 100644 --- a/Sources/OpenSwiftUICore/Event/Responder/ResponderNode.swift +++ b/Sources/OpenSwiftUICore/Event/Responder/ResponderNode.swift @@ -2,9 +2,10 @@ // ResponderNode.swift // OpenSwiftUICore // +// Audited for 6.5.4 // Status: Complete -// MARK: - ResponderNode [6.5.4] +// MARK: - ResponderNode @_spi(ForOpenSwiftUIOnly) @available(OpenSwiftUI_v6_0, *) @@ -48,8 +49,7 @@ open class ResponderNode { } final package func firstAncestor(ofType type: T.Type = T.self) -> T? { - Swift.sequence(first: self) { $0.nextResponder } - .first(ofType: type) + sequence.first(ofType: type) } } @@ -57,7 +57,7 @@ open class ResponderNode { @available(*, unavailable) extension ResponderNode: Sendable {} -// MARK: - ResponderVisitorResult [6.5.4] +// MARK: - ResponderVisitorResult @_spi(ForOpenSwiftUIOnly) @available(OpenSwiftUI_v6_0, *)