-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGestureState.swift
More file actions
136 lines (121 loc) · 4.1 KB
/
Copy pathGestureState.swift
File metadata and controls
136 lines (121 loc) · 4.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import CoreGraphics
enum GestureAction: Equatable {
case controlArrow(ArrowDirection)
case disabledDown
}
enum ButtonDownDecision: Equatable {
case beginGesture
case suppressSideButton
case commandBracket(left: Bool)
case passThrough
}
struct GestureState {
static let staleHoldTimeout: CGEventTimestamp = 30_000_000_000
private(set) var isActive = false
private(set) var isConsumed = false
private(set) var lastLocation: CGPoint?
private(set) var startedAt: CGEventTimestamp?
private(set) var accumulatedDx = 0.0
private(set) var accumulatedDy = 0.0
private(set) var accumulatedScrollX = 0.0
private(set) var accumulatedScrollY = 0.0
static func buttonDownDecision(button: Int, buttons: ButtonConfig, gestureActive: Bool) -> ButtonDownDecision {
if button == buttons.gesture {
return .beginGesture
}
if button == buttons.back {
return gestureActive ? .suppressSideButton : .commandBracket(left: true)
}
if button == buttons.forward {
return gestureActive ? .suppressSideButton : .commandBracket(left: false)
}
return .passThrough
}
mutating func begin(at location: CGPoint, timestamp: CGEventTimestamp = 0) {
isActive = true
isConsumed = false
lastLocation = location
startedAt = timestamp
accumulatedDx = 0
accumulatedDy = 0
accumulatedScrollX = 0
accumulatedScrollY = 0
}
mutating func end() {
isActive = false
isConsumed = false
lastLocation = nil
startedAt = nil
accumulatedDx = 0
accumulatedDy = 0
accumulatedScrollX = 0
accumulatedScrollY = 0
}
func isStale(at timestamp: CGEventTimestamp, timeout: CGEventTimestamp = staleHoldTimeout) -> Bool {
guard isActive, let startedAt, timestamp >= startedAt else {
return false
}
return timestamp - startedAt >= timeout
}
mutating func addMovement(to location: CGPoint, invertY: Bool) {
guard isActive else {
return
}
let previousLocation = lastLocation ?? location
accumulatedDx += Double(location.x - previousLocation.x)
let stepDy = Double(location.y - previousLocation.y)
accumulatedDy += invertY ? -stepDy : stepDy
lastLocation = location
}
mutating func addScroll(axis1: Double, axis2: Double) {
guard isActive else {
return
}
accumulatedScrollY += axis1
accumulatedScrollX += axis2
}
mutating func consumeAction(
threshold: Double,
downAction: DownAction,
invertDesktopDirection: Bool
) -> GestureAction? {
guard isActive, !isConsumed else {
return nil
}
let threshold = max(threshold, 1)
let physicalDirection: ArrowDirection?
if accumulatedDx < -threshold {
physicalDirection = .left
} else if accumulatedDx > threshold {
physicalDirection = .right
} else if accumulatedDy < -threshold {
physicalDirection = .up
} else if accumulatedDy > threshold {
physicalDirection = .down
} else if accumulatedScrollX < -threshold {
physicalDirection = .left
} else if accumulatedScrollX > threshold {
physicalDirection = .right
} else if accumulatedScrollY < -threshold {
physicalDirection = .up
} else if accumulatedScrollY > threshold {
physicalDirection = .down
} else {
physicalDirection = nil
}
guard let physicalDirection else {
return nil
}
isConsumed = true
switch physicalDirection {
case .left:
return .controlArrow(invertDesktopDirection ? .right : .left)
case .right:
return .controlArrow(invertDesktopDirection ? .left : .right)
case .up:
return .controlArrow(.up)
case .down:
return downAction == .ctrlDown ? .controlArrow(.down) : .disabledDown
}
}
}