SDL  2.0
SDL_uikitview.m
Go to the documentation of this file.
1  /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
4 
5  This software is provided 'as-is', without any express or implied
6  warranty. In no event will the authors be held liable for any damages
7  arising from the use of this software.
8 
9  Permission is granted to anyone to use this software for any purpose,
10  including commercial applications, and to alter it and redistribute it
11  freely, subject to the following restrictions:
12 
13  1. The origin of this software must not be misrepresented; you must not
14  claim that you wrote the original software. If you use this software
15  in a product, an acknowledgment in the product documentation would be
16  appreciated but is not required.
17  2. Altered source versions must be plainly marked as such, and must not be
18  misrepresented as being the original software.
19  3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22 
23 #if SDL_VIDEO_DRIVER_UIKIT
24 
25 #include "SDL_uikitview.h"
26 
27 #include "SDL_hints.h"
28 #include "../../events/SDL_mouse_c.h"
29 #include "../../events/SDL_touch_c.h"
30 #include "../../events/SDL_events_c.h"
31 
32 #include "SDL_uikitappdelegate.h"
33 #include "SDL_uikitevents.h"
34 #include "SDL_uikitmodes.h"
35 #include "SDL_uikitwindow.h"
36 
37 /* The maximum number of mouse buttons we support */
38 #define MAX_MOUSE_BUTTONS 5
39 
40 /* This is defined in SDL_sysjoystick.m */
41 #if !SDL_JOYSTICK_DISABLED
43 #endif
44 
45 @implementation SDL_uikitview {
46  SDL_Window *sdlwindow;
47 
48  SDL_TouchID directTouchId;
49  SDL_TouchID indirectTouchId;
50 }
51 
52 - (instancetype)initWithFrame:(CGRect)frame
53 {
54  if ((self = [super initWithFrame:frame])) {
55 #if TARGET_OS_TV
56  /* Apple TV Remote touchpad swipe gestures. */
57  UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
58  swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
59  [self addGestureRecognizer:swipeUp];
60 
61  UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
62  swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
63  [self addGestureRecognizer:swipeDown];
64 
65  UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
66  swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
67  [self addGestureRecognizer:swipeLeft];
68 
69  UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
70  swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
71  [self addGestureRecognizer:swipeRight];
72 #elif defined(__IPHONE_13_4)
73  if (@available(iOS 13.4, *)) {
74  UIPanGestureRecognizer *mouseWheelRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(mouseWheelGesture:)];
75  mouseWheelRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
76  mouseWheelRecognizer.allowedTouchTypes = @[ @(UITouchTypeIndirectPointer) ];
77  mouseWheelRecognizer.cancelsTouchesInView = NO;
78  mouseWheelRecognizer.delaysTouchesBegan = NO;
79  mouseWheelRecognizer.delaysTouchesEnded = NO;
80  [self addGestureRecognizer:mouseWheelRecognizer];
81  }
82 #endif
83 
84  self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
85  self.autoresizesSubviews = YES;
86 
87  directTouchId = 1;
88  indirectTouchId = 2;
89 
90 #if !TARGET_OS_TV
91  self.multipleTouchEnabled = YES;
92  SDL_AddTouch(directTouchId, SDL_TOUCH_DEVICE_DIRECT, "");
93 #endif
94 
95 #if !TARGET_OS_TV && defined(__IPHONE_13_4)
96  if (@available(iOS 13.4, *)) {
97  [self addInteraction:[[UIPointerInteraction alloc] initWithDelegate:self]];
98  }
99 #endif
100  }
101 
102  return self;
103 }
104 
105 - (void)setSDLWindow:(SDL_Window *)window
106 {
107  SDL_WindowData *data = nil;
108 
109  if (window == sdlwindow) {
110  return;
111  }
112 
113  /* Remove ourself from the old window. */
114  if (sdlwindow) {
115  SDL_uikitview *view = nil;
116  data = (__bridge SDL_WindowData *) sdlwindow->driverdata;
117 
118  [data.views removeObject:self];
119 
120  [self removeFromSuperview];
121 
122  /* Restore the next-oldest view in the old window. */
123  view = data.views.lastObject;
124 
125  data.viewcontroller.view = view;
126 
127  data.uiwindow.rootViewController = nil;
128  data.uiwindow.rootViewController = data.viewcontroller;
129 
130  [data.uiwindow layoutIfNeeded];
131  }
132 
133  /* Add ourself to the new window. */
134  if (window) {
135  data = (__bridge SDL_WindowData *) window->driverdata;
136 
137  /* Make sure the SDL window has a strong reference to this view. */
138  [data.views addObject:self];
139 
140  /* Replace the view controller's old view with this one. */
141  [data.viewcontroller.view removeFromSuperview];
142  data.viewcontroller.view = self;
143 
144  /* The root view controller handles rotation and the status bar.
145  * Assigning it also adds the controller's view to the window. We
146  * explicitly re-set it to make sure the view is properly attached to
147  * the window. Just adding the sub-view if the root view controller is
148  * already correct causes orientation issues on iOS 7 and below. */
149  data.uiwindow.rootViewController = nil;
150  data.uiwindow.rootViewController = data.viewcontroller;
151 
152  /* The view's bounds may not be correct until the next event cycle. That
153  * might happen after the current dimensions are queried, so we force a
154  * layout now to immediately update the bounds. */
155  [data.uiwindow layoutIfNeeded];
156  }
157 
158  sdlwindow = window;
159 }
160 
161 #if !TARGET_OS_TV && defined(__IPHONE_13_4)
162 - (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4)){
163  if (request != nil && !SDL_HasGCMouse()) {
164  CGPoint origin = self.bounds.origin;
165  CGPoint point = request.location;
166 
167  point.x -= origin.x;
168  point.y -= origin.y;
169 
170  SDL_SendMouseMotion(sdlwindow, 0, 0, (int)point.x, (int)point.y);
171  }
172  return [UIPointerRegion regionWithRect:self.bounds identifier:nil];
173 }
174 
175 - (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region API_AVAILABLE(ios(13.4)){
176  if (SDL_ShowCursor(-1)) {
177  return nil;
178  } else {
179  return [UIPointerStyle hiddenPointerStyle];
180  }
181 }
182 #endif /* !TARGET_OS_TV && __IPHONE_13_4 */
183 
184 - (SDL_TouchDeviceType)touchTypeForTouch:(UITouch *)touch
185 {
186 #ifdef __IPHONE_9_0
187  if ([touch respondsToSelector:@selector((type))]) {
188  if (touch.type == UITouchTypeIndirect) {
190  }
191  }
192 #endif
193 
195 }
196 
197 - (SDL_TouchID)touchIdForType:(SDL_TouchDeviceType)type
198 {
199  switch (type) {
201  default:
202  return directTouchId;
204  return indirectTouchId;
205  }
206 }
207 
208 - (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize
209 {
210  CGPoint point = [touch locationInView:self];
211 
212  if (normalize) {
213  CGRect bounds = self.bounds;
214  point.x /= bounds.size.width;
215  point.y /= bounds.size.height;
216  }
217 
218  return point;
219 }
220 
221 - (float)pressureForTouch:(UITouch *)touch
222 {
223 #ifdef __IPHONE_9_0
224  if ([touch respondsToSelector:@selector(force)]) {
225  return (float) touch.force;
226  }
227 #endif
228 
229  return 1.0f;
230 }
231 
232 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
233 {
234  for (UITouch *touch in touches) {
235  BOOL handled = NO;
236 
237 #if !TARGET_OS_TV && defined(__IPHONE_13_4)
238  if (@available(iOS 13.4, *)) {
239  if (touch.type == UITouchTypeIndirectPointer) {
240  if (!SDL_HasGCMouse()) {
241  int i;
242 
243  for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) {
244  if ((event.buttonMask & SDL_BUTTON(i)) != 0) {
245  Uint8 button;
246 
247  switch (i) {
248  case 1:
250  break;
251  case 2:
253  break;
254  case 3:
256  break;
257  default:
258  button = (Uint8)i;
259  break;
260  }
261  SDL_SendMouseButton(sdlwindow, 0, SDL_PRESSED, button);
262  }
263  }
264  }
265  handled = YES;
266  }
267  }
268 #endif
269  if (!handled) {
270  SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
271  SDL_TouchID touchId = [self touchIdForType:touchType];
272  float pressure = [self pressureForTouch:touch];
273 
274  if (SDL_AddTouch(touchId, touchType, "") < 0) {
275  continue;
276  }
277 
278  /* FIXME, need to send: int clicks = (int) touch.tapCount; ? */
279 
280  CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
281  SDL_SendTouch(touchId, (SDL_FingerID)((size_t)touch), sdlwindow,
282  SDL_TRUE, locationInView.x, locationInView.y, pressure);
283  }
284  }
285 }
286 
287 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
288 {
289  for (UITouch *touch in touches) {
290  BOOL handled = NO;
291 
292 #if !TARGET_OS_TV && defined(__IPHONE_13_4)
293  if (@available(iOS 13.4, *)) {
294  if (touch.type == UITouchTypeIndirectPointer) {
295  if (!SDL_HasGCMouse()) {
296  int i;
297 
298  for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) {
299  if ((event.buttonMask & SDL_BUTTON(i)) != 0) {
300  Uint8 button;
301 
302  switch (i) {
303  case 1:
305  break;
306  case 2:
308  break;
309  case 3:
311  break;
312  default:
313  button = (Uint8)i;
314  break;
315  }
316  SDL_SendMouseButton(sdlwindow, 0, SDL_RELEASED, button);
317  }
318  }
319  }
320  handled = YES;
321  }
322  }
323 #endif
324  if (!handled) {
325  SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
326  SDL_TouchID touchId = [self touchIdForType:touchType];
327  float pressure = [self pressureForTouch:touch];
328 
329  if (SDL_AddTouch(touchId, touchType, "") < 0) {
330  continue;
331  }
332 
333  /* FIXME, need to send: int clicks = (int) touch.tapCount; ? */
334 
335  CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
336  SDL_SendTouch(touchId, (SDL_FingerID)((size_t)touch), sdlwindow,
337  SDL_FALSE, locationInView.x, locationInView.y, pressure);
338  }
339  }
340 }
341 
342 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
343 {
344  [self touchesEnded:touches withEvent:event];
345 }
346 
347 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
348 {
349  for (UITouch *touch in touches) {
350  BOOL handled = NO;
351 
352 #if !TARGET_OS_TV && defined(__IPHONE_13_4)
353  if (@available(iOS 13.4, *)) {
354  if (touch.type == UITouchTypeIndirectPointer) {
355  /* Already handled in pointerInteraction callback */
356  handled = YES;
357  }
358  }
359 #endif
360  if (!handled) {
361  SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
362  SDL_TouchID touchId = [self touchIdForType:touchType];
363  float pressure = [self pressureForTouch:touch];
364 
365  if (SDL_AddTouch(touchId, touchType, "") < 0) {
366  continue;
367  }
368 
369  CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
370  SDL_SendTouchMotion(touchId, (SDL_FingerID)((size_t)touch), sdlwindow,
371  locationInView.x, locationInView.y, pressure);
372  }
373  }
374 }
375 
376 #if TARGET_OS_TV || defined(__IPHONE_9_1)
377 - (SDL_Scancode)scancodeFromPress:(UIPress*)press
378 {
379 #ifdef __IPHONE_13_4
380  if ([press respondsToSelector:@selector((key))]) {
381  if (press.key != nil) {
382  return (SDL_Scancode)press.key.keyCode;
383  }
384  }
385 #endif
386 
387 #if !SDL_JOYSTICK_DISABLED
388  /* Presses from Apple TV remote */
390  switch (press.type) {
391  case UIPressTypeUpArrow:
392  return SDL_SCANCODE_UP;
393  case UIPressTypeDownArrow:
394  return SDL_SCANCODE_DOWN;
395  case UIPressTypeLeftArrow:
396  return SDL_SCANCODE_LEFT;
397  case UIPressTypeRightArrow:
398  return SDL_SCANCODE_RIGHT;
399  case UIPressTypeSelect:
400  /* HIG says: "primary button behavior" */
401  return SDL_SCANCODE_RETURN;
402  case UIPressTypeMenu:
403  /* HIG says: "returns to previous screen" */
404  return SDL_SCANCODE_ESCAPE;
405  case UIPressTypePlayPause:
406  /* HIG says: "secondary button behavior" */
407  return SDL_SCANCODE_PAUSE;
408  default:
409  break;
410  }
411  }
412 #endif /* !SDL_JOYSTICK_DISABLED */
413 
414  return SDL_SCANCODE_UNKNOWN;
415 }
416 
417 - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
418 {
419  if (!SDL_HasGCKeyboard()) {
420  for (UIPress *press in presses) {
421  SDL_Scancode scancode = [self scancodeFromPress:press];
422  SDL_SendKeyboardKey(SDL_PRESSED, scancode);
423  }
424  }
425  [super pressesBegan:presses withEvent:event];
426 }
427 
428 - (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
429 {
430  if (!SDL_HasGCKeyboard()) {
431  for (UIPress *press in presses) {
432  SDL_Scancode scancode = [self scancodeFromPress:press];
434  }
435  }
436  [super pressesEnded:presses withEvent:event];
437 }
438 
439 - (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
440 {
441  if (!SDL_HasGCKeyboard()) {
442  for (UIPress *press in presses) {
443  SDL_Scancode scancode = [self scancodeFromPress:press];
445  }
446  }
447  [super pressesCancelled:presses withEvent:event];
448 }
449 
450 - (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
451 {
452  /* This is only called when the force of a press changes. */
453  [super pressesChanged:presses withEvent:event];
454 }
455 
456 #endif /* TARGET_OS_TV || defined(__IPHONE_9_1) */
457 
458 -(void)mouseWheelGesture:(UIPanGestureRecognizer *)gesture
459 {
460  if (gesture.state == UIGestureRecognizerStateBegan ||
461  gesture.state == UIGestureRecognizerStateChanged ||
462  gesture.state == UIGestureRecognizerStateEnded) {
463  CGPoint velocity = [gesture velocityInView:self];
464 
465  if (velocity.x > 0.0f) {
466  velocity.x = -1.0;
467  } else if (velocity.x < 0.0f) {
468  velocity.x = 1.0f;
469  }
470  if (velocity.y > 0.0f) {
471  velocity.y = -1.0;
472  } else if (velocity.y < 0.0f) {
473  velocity.y = 1.0f;
474  }
475  if (velocity.x != 0.0f || velocity.y != 0.0f) {
476  SDL_SendMouseWheel(sdlwindow, 0, velocity.x, velocity.y, SDL_MOUSEWHEEL_NORMAL);
477  }
478  }
479 }
480 
481 #if TARGET_OS_TV
482 -(void)swipeGesture:(UISwipeGestureRecognizer *)gesture
483 {
484  /* Swipe gestures don't trigger begin states. */
485  if (gesture.state == UIGestureRecognizerStateEnded) {
486 #if !SDL_JOYSTICK_DISABLED
488  /* Send arrow key presses for now, as we don't have an external API
489  * which better maps to swipe gestures. */
490  switch (gesture.direction) {
491  case UISwipeGestureRecognizerDirectionUp:
493  break;
494  case UISwipeGestureRecognizerDirectionDown:
496  break;
497  case UISwipeGestureRecognizerDirectionLeft:
499  break;
500  case UISwipeGestureRecognizerDirectionRight:
502  break;
503  }
504  }
505 #endif /* !SDL_JOYSTICK_DISABLED */
506  }
507 }
508 #endif /* TARGET_OS_TV */
509 
510 @end
511 
512 #endif /* SDL_VIDEO_DRIVER_UIKIT */
513 
514 /* vi: set ts=4 sw=4 expandtab: */
#define SDL_ShowCursor
SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char const char SDL_SCANF_FORMAT_STRING const char return SDL_ThreadFunction const char void return Uint32 return Uint32 void
#define SDL_RELEASED
Definition: SDL_events.h:49
#define SDL_PRESSED
Definition: SDL_events.h:50
int SDL_SendKeyboardKeyAutoRelease(SDL_Scancode scancode)
Definition: SDL_keyboard.c:812
int SDL_SendKeyboardKey(Uint8 state, SDL_Scancode scancode)
Definition: SDL_keyboard.c:806
int SDL_AppleTVRemoteOpenedAsJoystick
int SDL_SendMouseButton(SDL_Window *window, SDL_MouseID mouseID, Uint8 state, Uint8 button)
Definition: SDL_mouse.c:599
int SDL_SendMouseWheel(SDL_Window *window, SDL_MouseID mouseID, float x, float y, SDL_MouseWheelDirection direction)
Definition: SDL_mouse.c:605
int SDL_SendMouseMotion(SDL_Window *window, SDL_MouseID mouseID, int relative, int x, int y)
Definition: SDL_mouse.c:298
#define SDL_BUTTON_MIDDLE
Definition: SDL_mouse.h:283
#define SDL_BUTTON_RIGHT
Definition: SDL_mouse.h:284
#define SDL_BUTTON_LEFT
Definition: SDL_mouse.h:282
@ SDL_MOUSEWHEEL_NORMAL
Definition: SDL_mouse.h:68
#define SDL_BUTTON(X)
Definition: SDL_mouse.h:281
GLint GLint GLint GLint GLint GLint y
Definition: SDL_opengl.h:1574
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
GLint GLint GLint GLint GLint x
Definition: SDL_opengl.h:1574
GLuint GLuint GLsizei GLenum type
Definition: SDL_opengl.h:1571
struct _cl_event * event
GLuint in
SDL_Scancode
The SDL keyboard scancode representation.
Definition: SDL_scancode.h:44
@ SDL_SCANCODE_ESCAPE
Definition: SDL_scancode.h:93
@ SDL_SCANCODE_PAUSE
Definition: SDL_scancode.h:168
@ SDL_SCANCODE_RETURN
Definition: SDL_scancode.h:92
@ SDL_SCANCODE_UNKNOWN
Definition: SDL_scancode.h:45
@ SDL_SCANCODE_DOWN
Definition: SDL_scancode.h:178
@ SDL_SCANCODE_RIGHT
Definition: SDL_scancode.h:176
@ SDL_SCANCODE_UP
Definition: SDL_scancode.h:179
@ SDL_SCANCODE_LEFT
Definition: SDL_scancode.h:177
@ SDL_TRUE
Definition: SDL_stdinc.h:170
@ SDL_FALSE
Definition: SDL_stdinc.h:169
uint8_t Uint8
Definition: SDL_stdinc.h:185
int SDL_SendTouch(SDL_TouchID id, SDL_FingerID fingerid, SDL_Window *window, SDL_bool down, float x, float y, float pressure)
Definition: SDL_touch.c:241
int SDL_AddTouch(SDL_TouchID touchID, SDL_TouchDeviceType type, const char *name)
Definition: SDL_touch.c:154
int SDL_SendTouchMotion(SDL_TouchID id, SDL_FingerID fingerid, SDL_Window *window, float x, float y, float pressure)
Definition: SDL_touch.c:355
Sint64 SDL_FingerID
Definition: SDL_touch.h:42
SDL_TouchDeviceType
Definition: SDL_touch.h:45
@ SDL_TOUCH_DEVICE_DIRECT
Definition: SDL_touch.h:47
@ SDL_TOUCH_DEVICE_INDIRECT_RELATIVE
Definition: SDL_touch.h:49
Sint64 SDL_TouchID
Definition: SDL_touch.h:41
SDL_bool SDL_HasGCKeyboard(void)
SDL_bool SDL_HasGCMouse(void)
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:50
EGLSurface EGLNativeWindowType * window
Definition: eglext.h:1025
GLuint64 key
Definition: gl2ext.h:2192
The type used to identify a window.
Definition: SDL_sysvideo.h:75
SDL_Texture * button
int frame
Definition: teststreaming.c:60