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
1 change: 1 addition & 0 deletions AGENT.md
11 changes: 10 additions & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ let openGesturesTarget = Target.target(
dependencies: [
.target(name: cOpenGesturesTarget.name),
.product(name: "OpenCoreGraphicsShims", package: "OpenCoreGraphics"),
.product(name: "OrderedCollections", package: "swift-collections"),
],
swiftSettings: sharedSwiftSettings
)
Expand Down Expand Up @@ -236,6 +237,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/OpenSwiftUIProject/OpenCoreGraphics.git", branch: "main"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),
],
targets: [
cOpenGesturesTarget,
Expand Down
60 changes: 60 additions & 0 deletions Sources/OpenGestures/Core/GestureNodeMatcher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// GestureNodeMatcher.swift
// OpenGestures
//
// Audited for 9126.1.5
// Status: Complete

// MARK: - GestureNodeMatcher

public enum GestureNodeMatcher: Hashable, Sendable {
case id(GestureNodeID)
case tag(GestureTag)
case traits(GestureTraitCollection, position: RelativePosition)
case any(position: RelativePosition)

public enum RelativePosition: Hashable, Sendable {
case any
case above
case below
}
}

// MARK: - GestureNodeMatcher + Comparable

extension GestureNodeMatcher: Comparable {
public static func < (lhs: GestureNodeMatcher, rhs: GestureNodeMatcher) -> Bool {
lhs.sortOrder < rhs.sortOrder
}

private var sortOrder: Int {
switch self {
case .id: 0
case .tag: 1
case .traits: 2
case .any: 3
}
}
}

// MARK: - GestureNodeMatcher + NestedCustomStringConvertible

extension GestureNodeMatcher: NestedCustomStringConvertible {
package func populateNestedDescription(_ nested: inout NestedDescription) {
nested.options.formUnion([.hideTypeName, .compact])
nested.customPrefix = ""
nested.customSuffix = ""
switch self {
case let .id(id):
nested.append(id)
case let .tag(tag):
nested.append(tag)
case let .traits(collection, position):
nested.append(collection)
nested.append(position, label: "position")
case let .any(position):
nested.append("any")
nested.append(position, label: "position")
}
}
}
167 changes: 167 additions & 0 deletions Sources/OpenGestures/Core/GestureRelation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
//
// GestureRelation.swift
// OpenGestures
//
// Audited for 9126.1.5
// Status: Complete

package import OrderedCollections

// MARK: - GestureRelationType

public enum GestureRelationType: Hashable, Sendable {
case exclusion
case activeExclusion
case failureRequirement
}

// MARK: - GestureRelationRole

public enum GestureRelationRole: Hashable, Sendable {
case regular
case blocking
}

// MARK: - GestureRelationDirection

public enum GestureRelationDirection: Hashable, Sendable {
case outgoing
case incoming
}

// MARK: - GestureRelation

public struct GestureRelation: Equatable, Sendable {
public var type: GestureRelationType
public var direction: GestureRelationDirection
public var role: GestureRelationRole?
public var target: GestureNodeMatcher

public init(
type: GestureRelationType,
direction: GestureRelationDirection,
role: GestureRelationRole?,
target: GestureNodeMatcher
) {
self.type = type
self.direction = direction
self.role = role
self.target = target
}
}

// MARK: - [GestureRelation] Default

extension [GestureRelation] {
public static var `default`: [GestureRelation] {
[
GestureRelation(type: .exclusion, direction: .outgoing, role: .regular, target: .any(position: .any)),
GestureRelation(type: .activeExclusion, direction: .outgoing, role: .regular, target: .any(position: .any)),
GestureRelation(type: .activeExclusion, direction: .incoming, role: .blocking, target: .any(position: .any)),
]
}
}

// MARK: - RelationMap

package struct RelationMap: Sendable {
private var relations: OrderedDictionary<GestureNodeMatcher, Set<RelationDefinition>>

package init() {
self.relations = [:]
}

package init(relations: OrderedDictionary<GestureNodeMatcher, Set<RelationDefinition>>) {
self.relations = relations
}

package mutating func add(_ definition: RelationDefinition, for matcher: GestureNodeMatcher) {
relations[matcher, default: []].insert(definition)
}

package mutating func remove(_ definition: RelationDefinition, for matcher: GestureNodeMatcher) {
relations[matcher]?.remove(definition)
if relations[matcher]?.isEmpty == true {
relations.removeValue(forKey: matcher)
}
}

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

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

package func toRelations() -> [GestureRelation] {
var result: [GestureRelation] = []
for (matcher, definitions) in relations {
for definition in definitions {
result.append(GestureRelation(
type: definition.type,
direction: definition.direction,
role: definition.role,
target: matcher
))
}
}
return result
}
}

// MARK: - RelationMap + Sequence

extension RelationMap: Sequence {
package func makeIterator() -> some IteratorProtocol {
relations.makeIterator()
}
}

// MARK: - RelationMap + NestedCustomStringConvertible

extension RelationMap: NestedCustomStringConvertible {
package func populateNestedDescription(_ nested: inout NestedDescription) {
nested.options.formUnion(.hideTypeName)
for (matcher, definition) in relations {
nested.append("\(matcher)", label: "\(definition)")
}
}
}

// MARK: - RelationDefinition

package struct RelationDefinition: Hashable, Sendable, CustomStringConvertible {
package var type: GestureRelationType
package var direction: GestureRelationDirection
package var role: GestureRelationRole?

package init(
type: GestureRelationType,
direction: GestureRelationDirection,
role: GestureRelationRole? = nil
) {
self.type = type
self.direction = direction
self.role = role
}

package var description: String {
let dir = switch direction {
case .outgoing: "out"
case .incoming: "in"
}
let roleStr = if let role { "\(role)" } else { "dynamic" }
return "\(type)[\(dir)]=\(roleStr)"
}
}
38 changes: 18 additions & 20 deletions Sources/OpenGestures/Core/GestureTrait.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,25 @@ public struct GestureTrait: Hashable, Identifiable, Sendable {
}
}

@_spi(Private)
extension GestureTrait: NestedCustomStringConvertible {
public var label: String {
TraitLabelStore.shared.label(for: id.rawValue)
}

public var description: String {
package func populateNestedDescription(_ nested: inout NestedDescription) {
nested.options.formUnion([.hideTypeName, .compact])
nested.customPrefix = ""
nested.customSuffix = ""
nested.options.formUnion(.hideIdentity)
if attributes.isEmpty {
return label
nested.append(label)
} else {
nested.customPrefix = label + " {"
nested.customSuffix = "}"
for (key, value) in attributes {
nested.append("\(key.label): \(value)")
}
}
let attrs = attributes.map { "\($0.key.label): \($0.value)" }.joined(separator: ", ")
return "\(label) {\(attrs)}"
}

public var debugDescription: String {
description
}
}

Expand Down Expand Up @@ -173,24 +176,19 @@ extension GestureTraitCollection: Sequence {

// MARK: - GestureTraitCollection + CustomStringConvertible

@_spi(Private)
extension GestureTraitCollection: NestedCustomStringConvertible {
public var label: String { "GestureTraitCollection" }

public var description: String {
"[\(_traits.values.map(\.description).joined(separator: ", "))]"
}

public var debugDescription: String {
description
package func populateNestedDescription(_ nested: inout NestedDescription) {
nested.options.formUnion([.hideTypeName, .compact])
nested.customPrefix = ""
nested.customSuffix = ""
nested.append(_traits.values)
}
}

// MARK: - GestureTraitCollection + Mergeable

@_spi(Private)
extension GestureTraitCollection: Mergeable {
public mutating func merge(_ other: GestureTraitCollection) {
package mutating func merge(_ other: GestureTraitCollection) {
_traits.merge(other._traits) { $1 }
}
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/OpenGestures/GestureNode/AnyGestureNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,18 @@ open class AnyGestureNode: Hashable, Identifiable, @unchecked Sendable {

// MARK: - Relations

private var _relations: [GestureRelation] = []
package var relationMap = RelationMap()

public var relations: [GestureRelation] {
_relations
relationMap.toRelations()
}

open func addRelation(_ relation: GestureRelation) {
_relations.append(relation)
relationMap.addRelation(relation)
}

open func removeRelation(_ relation: GestureRelation) {
// TODO: Equatable-based removal
relationMap.removeRelation(relation)
}

open func addRelations(_ relations: [GestureRelation]) {
Expand Down
14 changes: 0 additions & 14 deletions Sources/OpenGestures/GestureRelation/GestureNodeMatcher.swift

This file was deleted.

20 changes: 0 additions & 20 deletions Sources/OpenGestures/GestureRelation/GestureRelation.swift

This file was deleted.

Loading
Loading