1#import "RNGestureHandlerPointerTracker.h"
2#import "RNGestureHandler.h"
3
4#import <React/UIView+React.h>
5
6@implementation RNGestureHandlerPointerTracker {
7  __weak RNGestureHandler *_gestureHandler;
8  UITouch *_trackedPointers[MAX_POINTERS_COUNT];
9  int _trackedPointersCount;
10}
11
12- (id)initWithGestureHandler:(id)gestureHandler
13{
14  _gestureHandler = gestureHandler;
15  _trackedPointersCount = 0;
16  _changedPointersData = nil;
17  _allPointersData = nil;
18
19  for (int i = 0; i < MAX_POINTERS_COUNT; i++) {
20    _trackedPointers[i] = nil;
21  }
22
23  return self;
24}
25
26- (int)registerTouch:(UITouch *)touch
27{
28  for (int index = 0; index < MAX_POINTERS_COUNT; index++) {
29    if (_trackedPointers[index] == nil) {
30      _trackedPointers[index] = touch;
31      return index;
32    }
33  }
34
35  return -1;
36}
37
38- (int)unregisterTouch:(UITouch *)touch
39{
40  for (int index = 0; index < MAX_POINTERS_COUNT; index++) {
41    if (_trackedPointers[index] == touch) {
42      _trackedPointers[index] = nil;
43      return index;
44    }
45  }
46
47  return -1;
48}
49
50- (int)findTouchIndex:(UITouch *)touch
51{
52  for (int index = 0; index < MAX_POINTERS_COUNT; index++) {
53    if (_trackedPointers[index] == touch) {
54      return index;
55    }
56  }
57  return -1;
58}
59
60- (int)registeredTouchesCount
61{
62  int count = 0;
63  for (int i = 0; i < MAX_POINTERS_COUNT; i++) {
64    if (_trackedPointers[i] != nil) {
65      count++;
66    }
67  }
68  return count;
69}
70
71- (NSDictionary *)extractPointerData:(int)index forTouch:(UITouch *)touch
72{
73  CGPoint relativePos = [touch locationInView:_gestureHandler.recognizer.view];
74  CGPoint absolutePos = [touch locationInView:_gestureHandler.recognizer.view.window];
75
76  return @{
77    @"id" : @(index),
78    @"x" : @(relativePos.x),
79    @"y" : @(relativePos.y),
80    @"absoluteX" : @(absolutePos.x),
81    @"absoluteY" : @(absolutePos.y)
82  };
83}
84
85- (void)extractAllTouches
86{
87  int registeredTouches = [self registeredTouchesCount];
88
89  NSDictionary *data[registeredTouches];
90  int nextIndex = 0;
91
92  for (int i = 0; i < MAX_POINTERS_COUNT; i++) {
93    UITouch *touch = _trackedPointers[i];
94    if (touch != nil) {
95      data[nextIndex++] = [self extractPointerData:i forTouch:touch];
96    }
97  }
98
99  _allPointersData = [[NSArray alloc] initWithObjects:data count:registeredTouches];
100}
101
102- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
103{
104  if (!_gestureHandler.needsPointerData) {
105    return;
106  }
107
108  _eventType = RNGHTouchEventTypePointerDown;
109
110  NSDictionary *data[touches.count];
111
112  for (int i = 0; i < [touches count]; i++) {
113    UITouch *touch = [[touches allObjects] objectAtIndex:i];
114    int index = [self registerTouch:touch];
115    if (index >= 0) {
116      _trackedPointersCount++;
117    }
118
119    data[i] = [self extractPointerData:index forTouch:touch];
120  }
121
122  _changedPointersData = [[NSArray alloc] initWithObjects:data count:[touches count]];
123  // extract all touches last to include the ones that were just added
124  [self extractAllTouches];
125  [self sendEvent];
126}
127
128- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
129{
130  if (!_gestureHandler.needsPointerData) {
131    return;
132  }
133
134  _eventType = RNGHTouchEventTypePointerMove;
135
136  NSDictionary *data[touches.count];
137
138  for (int i = 0; i < [touches count]; i++) {
139    UITouch *touch = [[touches allObjects] objectAtIndex:i];
140    int index = [self findTouchIndex:touch];
141    data[i] = [self extractPointerData:index forTouch:touch];
142  }
143
144  _changedPointersData = [[NSArray alloc] initWithObjects:data count:[touches count]];
145  [self extractAllTouches];
146  [self sendEvent];
147}
148
149- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
150{
151  if (!_gestureHandler.needsPointerData) {
152    return;
153  }
154
155  // extract all touches first to include the ones that were just lifted
156  [self extractAllTouches];
157
158  _eventType = RNGHTouchEventTypePointerUp;
159
160  NSDictionary *data[touches.count];
161
162  for (int i = 0; i < [touches count]; i++) {
163    UITouch *touch = [[touches allObjects] objectAtIndex:i];
164    int index = [self unregisterTouch:touch];
165    if (index >= 0) {
166      _trackedPointersCount--;
167    }
168
169    data[i] = [self extractPointerData:index forTouch:touch];
170  }
171
172  _changedPointersData = [[NSArray alloc] initWithObjects:data count:[touches count]];
173  [self sendEvent];
174}
175
176- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
177{
178  if (!_gestureHandler.needsPointerData) {
179    return;
180  }
181
182  [self reset];
183}
184
185- (void)reset
186{
187  if (!_gestureHandler.needsPointerData) {
188    return;
189  }
190
191  if (_trackedPointersCount == 0) {
192    // gesture has finished because all pointers were lifted, reset event type to send state change event
193    _eventType = RNGHTouchEventTypeUndetermined;
194  } else {
195    // turns out that the gesture may be made to fail without calling touchesCancelled in that case there
196    // are still tracked pointers but the recognizer state is already set to UIGestureRecognizerStateFailed
197    // we need to clear the pointers and send info about their cancellation
198    [self cancelPointers];
199  }
200
201  [_gestureHandler reset];
202}
203
204- (void)cancelPointers
205{
206  // extract all touches first to include the ones that were just cancelled
207  [self extractAllTouches];
208
209  int registeredTouches = [self registeredTouchesCount];
210
211  if (registeredTouches > 0) {
212    int nextIndex = 0;
213    NSDictionary *data[registeredTouches];
214
215    for (int i = 0; i < MAX_POINTERS_COUNT; i++) {
216      UITouch *touch = _trackedPointers[i];
217      if (touch != nil) {
218        data[nextIndex++] = [self extractPointerData:i forTouch:touch];
219        [self unregisterTouch:touch];
220      }
221    }
222
223    _eventType = RNGHTouchEventTypeCancelled;
224    _changedPointersData = [[NSArray alloc] initWithObjects:data count:registeredTouches];
225    [self sendEvent];
226    _trackedPointersCount = 0;
227  }
228}
229
230- (void)sendEvent
231{
232  // it may happen that the gesture recognizer is reset after it's been unbound from the view,
233  // it that recognizer tried to send event, the app would crash because the target of the event
234  // would be nil.
235  if (!_gestureHandler.needsPointerData || _gestureHandler.recognizer.view.reactTag == nil) {
236    return;
237  }
238
239  [_gestureHandler sendTouchEventInState:[_gestureHandler state]
240                          forViewWithTag:_gestureHandler.recognizer.view.reactTag];
241}
242
243@end
244