Skip to content
33 changes: 33 additions & 0 deletions Sources/COpenGestures/interpose.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// interpose.c
// OpenGestures

#include <OpenGestures/OGFBase.h>
#include <stdbool.h>
#include <string.h>

#if OGF_TARGET_OS_DARWIN
extern bool os_variant_has_internal_diagnostics(const char *subsystem);
#endif

bool ogf_variant_has_internal_diagnostics(const char *subsystem) {
if (strcmp(subsystem, "org.OpenSwiftUIProject.OpenGestures") == 0) {
return true;
} else if (strcmp(subsystem, "com.apple.Gestures") == 0) {
return true;
} else {
#if OGF_TARGET_OS_DARWIN
return os_variant_has_internal_diagnostics(subsystem);
#else
return false;
#endif
}
}

#if OGF_TARGET_OS_DARWIN
#define DYLD_INTERPOSE(_replacement,_replacee) \
__attribute__((used)) static struct{ const void* replacement; const void* replacee; } _interpose_##_replacee \
__attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee };

DYLD_INTERPOSE(ogf_variant_has_internal_diagnostics, os_variant_has_internal_diagnostics)
#endif
136 changes: 80 additions & 56 deletions Sources/OpenGestures/Component/GestureComponentController.swift
Original file line number Diff line number Diff line change
@@ -1,82 +1,106 @@
// MARK: - AnyGestureComponentController
//
// GestureComponentController.swift
// OpenGestures
//
// Audited for 9126.1.5
// Status: WIP

/// Type-erased base for gesture component controllers.
open class AnyGestureComponentController: @unchecked Sendable {
// MARK: - GestureComponentController [WIP]

/// Weak reference to the bound node.
public weak var node: AnyGestureNode?
/// Concrete controller wrapping a specific `GestureComponent`.
public final class GestureComponentController<C: GestureComponent>: AnyGestureComponentController, @unchecked Sendable {

public init() {}
public var component: C
let timeScheduler: any TimeScheduler
// TODO: var eventStores: [ObjectIdentifier: AnyEventStore] = [:]
var _traits: GestureTraitCollection?
var startTime: Timestamp?
var updateListener: ((Result<GestureOutput<C.Value>, any Error>) -> Void)?
// TODO: lazy var updateTracer: UpdateTracer?
lazy var updateScheduler: UpdateScheduler? = nil

public init(component: C, timeScheduler: any TimeScheduler) {
self.component = component
self.timeScheduler = timeScheduler
super.init()
}

/// Handles incoming events and drives the component's update cycle.
open func handleEvents(_ events: [any Event]) {
fatalError("Subclass must override")
public override var traits: GestureTraitCollection? {
component.traits()
}

/// Checks if this controller can handle events of a given type and count.
open func canHandleEvents<E: Event>(ofType: E.Type, count: Int) -> Bool {
false
public override var timeSource: any TimeSource {
timeScheduler
}

/// Resets the controller and its component.
open func reset() {
fatalError("Subclass must override")
public override func canHandleEvents<E: Event>(ofType: E.Type, count: Int) -> Bool {
component.capacity(for: ofType) >= count
}

public override func handleEvents<E: Event>(_ events: [E]) throws {
let currentTime = timeScheduler.timestamp
let startTime = self.startTime ?? currentTime
if self.startTime == nil {
self.startTime = currentTime
}

let context = GestureComponentContext(startTime: startTime, currentTime: currentTime)
let output = try component.update(context: context)

guard let node else { return }
switch output {
case .empty:
break
case .value(let value, _):
try node.update(someValue: value, isFinalUpdate: false)
case .finalValue(let value, _):
try node.update(someValue: value, isFinalUpdate: true)
}
}

/// Returns the component's traits.
open var traits: GestureTraitCollection? { nil }
public override func reset() {
component.reset()
startTime = nil
}
}

// MARK: - GestureComponentController
// MARK: - AnyGestureComponentController

/// Concrete controller wrapping a specific GestureComponent.
public final class GestureComponentController<C: GestureComponent>: AnyGestureComponentController, @unchecked Sendable {
/// Type-erased base for gesture component controllers.
open class AnyGestureComponentController: @unchecked Sendable {

public var component: C
private let timeSource: any TimeSource
private var cachedStartTime: Timestamp?
/// Weak back-reference to the owning gesture node.
open weak var node: AnyGestureNode?

public init(component: C, timeSource: any TimeSource) {
self.component = component
self.timeSource = timeSource
/// Traits exposed by the wrapped component.
open var traits: GestureTraitCollection? {
_openGesturesBaseClassAbstractMethod()
}

public override func handleEvents(_ events: [any Event]) {
let currentTime = timeSource.timestamp
let startTime = cachedStartTime ?? currentTime
if cachedStartTime == nil {
cachedStartTime = currentTime
}

let context = GestureComponentContext(startTime: startTime, currentTime: currentTime)
/// Time source used by `handleEvents` to build `GestureComponentContext`.
open var timeSource: any TimeSource {
_openGesturesBaseClassAbstractMethod()
}

do {
let output = try component.update(context: context)

guard let node else { return }
switch output {
case .empty(_, _):
break
case .value(let value, _):
try node.update(someValue: value, isFinalUpdate: false)
case .finalValue(let value, _):
try node.update(someValue: value, isFinalUpdate: true)
}
} catch {
// Handle error
}
/// Whether this controller can consume `count` events of the given type.
open func canHandleEvents<E: Event>(ofType: E.Type, count: Int) -> Bool {
_openGesturesBaseClassAbstractMethod()
}

public override func reset() {
component.reset()
cachedStartTime = nil
/// Whether this controller can consume a single event.
open func canHandleEvent<E: Event>(_ event: E) -> Bool {
_openGesturesBaseClassAbstractMethod()
}

public override var traits: GestureTraitCollection? {
component.traits()
/// Drives the wrapped component with the given events.
open func handleEvents<E: Event>(_ events: [E]) throws {
_openGesturesBaseClassAbstractMethod()
}

public override func canHandleEvents<E: Event>(ofType: E.Type, count: Int) -> Bool {
component.capacity(for: ofType) >= count
/// Resets the controller and its wrapped component.
open func reset() {
_openGesturesBaseClassAbstractMethod()
}

package init() {}
}
58 changes: 55 additions & 3 deletions Sources/OpenGestures/Core/GesturePhaseQueue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,36 @@ package struct GesturePhaseQueue<Value: Sendable> {
package var pendingPhases: RingBuffer<GesturePhase<Value>>

package init(
timeSource: (any TimeSource)?,
currentPhase: GesturePhase<Value>,
pendingPhases: RingBuffer<GesturePhase<Value>>
timeSource: (any TimeSource)? = nil,
currentPhase: GesturePhase<Value> = .idle,
pendingPhases: RingBuffer<GesturePhase<Value>> = .init(capacity: 5, emptyValue: .idle)
) {
self.timeSource = timeSource
self.currentPhase = currentPhase
self.pendingPhases = pendingPhases
}

package var latestPhase: GesturePhase<Value> {
pendingPhases.last ?? currentPhase
}

// TBA
package mutating func enqueue(_ phase: GesturePhase<Value>) throws {
let latestPhase = latestPhase
guard latestPhase.canTransition(to: phase) else {
throw InvalidTransition(phase: latestPhase, targetPhase: phase)
}
pendingPhases.append(phase)
}

// TBA
package mutating func processNextPhase() -> (oldPhase: GesturePhase<Value>, newPhase: GesturePhase<Value>)? {
guard !pendingPhases.isEmpty else { return nil }
let oldPhase = currentPhase
let newPhase = pendingPhases.removeFirst()
currentPhase = newPhase
return (oldPhase, newPhase)
}
}

// MARK: - GesturePhaseQueue.InvalidTransition
Expand All @@ -40,3 +62,33 @@ extension GesturePhaseQueue {

extension GesturePhaseQueue.InvalidTransition: NestedCustomStringConvertible {}

// MARK: - GesturePhase Transition Rules

extension GesturePhase {
// TBA
package func canTransition(to targetPhase: GesturePhase<Value>) -> Bool {
switch (self, targetPhase) {
case (.idle, .possible):
true
case (.possible, .blocked),
(.possible, .active),
(.possible, .ended),
(.possible, .failed):
true
case (.blocked, .blocked),
(.blocked, .active),
(.blocked, .ended),
(.blocked, .failed):
true
case (.active, .active),
(.active, .ended),
(.active, .failed):
true
case (.ended, .idle),
(.failed, .idle):
true
default:
false
}
}
}
29 changes: 20 additions & 9 deletions Sources/OpenGestures/Core/GestureRelation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,33 +75,44 @@ package struct RelationMap: Sendable {
self.relations = relations
}

package mutating func add(_ definition: RelationDefinition, for matcher: GestureNodeMatcher) {
relations[matcher, default: []].insert(definition)
@discardableResult
package mutating func add(_ definition: RelationDefinition, for matcher: GestureNodeMatcher) -> Bool {
var set = relations[matcher] ?? []
let (inserted, _) = set.insert(definition)
relations[matcher] = set
return inserted
}

package mutating func remove(_ definition: RelationDefinition, for matcher: GestureNodeMatcher) {
relations[matcher]?.remove(definition)
if relations[matcher]?.isEmpty == true {
@discardableResult
package mutating func remove(_ definition: RelationDefinition, for matcher: GestureNodeMatcher) -> Bool {
guard var set = relations[matcher] else { return false }
let removed = set.remove(definition) != nil
if set.isEmpty {
relations.removeValue(forKey: matcher)
} else {
relations[matcher] = set
}
return removed
}

package mutating func addRelation(_ relation: GestureRelation) {
@discardableResult
package mutating func addRelation(_ relation: GestureRelation) -> Bool {
let definition = RelationDefinition(
type: relation.type,
direction: relation.direction,
role: relation.role
)
add(definition, for: relation.target)
return add(definition, for: relation.target)
}

package mutating func removeRelation(_ relation: GestureRelation) {
@discardableResult
package mutating func removeRelation(_ relation: GestureRelation) -> Bool {
let definition = RelationDefinition(
type: relation.type,
direction: relation.direction,
role: relation.role
)
remove(definition, for: relation.target)
return remove(definition, for: relation.target)
}

package func toRelations() -> [GestureRelation] {
Expand Down
26 changes: 26 additions & 0 deletions Sources/OpenGestures/Extension/OGFGestureNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// OGFGestureNode.swift
// OpenGestures
//
// Created by Kyle on 4/19/26.
//

#if canImport(ObjectiveC)

import Foundation

@objc
class AnyGestureNodeShim: NSObject, @unchecked Sendable {

// override var container: (any GestureNodeContainer)? {
// didSet {
// // TODO
// }
// }
//
// override func abort() throws {
// _openGesturesBaseClassAbstractMethod()
// }
}

#endif
Loading
Loading