src/video/uikit/SDL_uikitview.m
author Alex Szpakowski <slime73@gmail.com>
Sat, 10 Nov 2018 16:15:48 -0400
changeset 12404 eb60e952b13f
parent 12201 8bdc4d340419
child 12503 806492103856
permissions -rw-r--r--
Add SDL_TouchDeviceType enum and SDL_GetTouchDeviceType(SDL_TouchID id).

Touch device types include SDL_TOUCH_DEVICE_DIRECT (a touch screen with window-relative coordinates for touches), SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE (a trackpad-style device with absolute device coordinates), and SDL_TOUCH_DEVICE_INDIRECT_RELATIVE (a trackpad-style device with screen cursor-relative coordinates).

Phone screens are an example of a direct device type. Mac trackpads are the indirect-absolute touch device type. The Apple TV remote is an indirect-relative touch device type.
     1  /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2018 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 #import "SDL_uikitappdelegate.h"
    33 #import "SDL_uikitmodes.h"
    34 #import "SDL_uikitwindow.h"
    35 
    36 /* This is defined in SDL_sysjoystick.m */
    37 extern int SDL_AppleTVRemoteOpenedAsJoystick;
    38 
    39 @implementation SDL_uikitview {
    40     SDL_Window *sdlwindow;
    41 
    42     SDL_TouchID directTouchId;
    43     SDL_TouchID indirectTouchId;
    44 
    45     UITouch * __weak firstFingerDown;
    46 }
    47 
    48 - (instancetype)initWithFrame:(CGRect)frame
    49 {
    50     if ((self = [super initWithFrame:frame])) {
    51 #if TARGET_OS_TV
    52         /* Apple TV Remote touchpad swipe gestures. */
    53         UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
    54         swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
    55         [self addGestureRecognizer:swipeUp];
    56 
    57         UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
    58         swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
    59         [self addGestureRecognizer:swipeDown];
    60 
    61         UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
    62         swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
    63         [self addGestureRecognizer:swipeLeft];
    64 
    65         UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
    66         swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
    67         [self addGestureRecognizer:swipeRight];
    68 #endif
    69 
    70         self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    71         self.autoresizesSubviews = YES;
    72 
    73         directTouchId = 1;
    74         indirectTouchId = 2;
    75 
    76 #if !TARGET_OS_TV
    77         self.multipleTouchEnabled = YES;
    78         SDL_AddTouch(directTouchId, SDL_TOUCH_DEVICE_DIRECT, "");
    79 #endif
    80     }
    81 
    82     return self;
    83 }
    84 
    85 - (void)setSDLWindow:(SDL_Window *)window
    86 {
    87     SDL_WindowData *data = nil;
    88 
    89     if (window == sdlwindow) {
    90         return;
    91     }
    92 
    93     /* Remove ourself from the old window. */
    94     if (sdlwindow) {
    95         SDL_uikitview *view = nil;
    96         data = (__bridge SDL_WindowData *) sdlwindow->driverdata;
    97 
    98         [data.views removeObject:self];
    99 
   100         [self removeFromSuperview];
   101 
   102         /* Restore the next-oldest view in the old window. */
   103         view = data.views.lastObject;
   104 
   105         data.viewcontroller.view = view;
   106 
   107         data.uiwindow.rootViewController = nil;
   108         data.uiwindow.rootViewController = data.viewcontroller;
   109 
   110         [data.uiwindow layoutIfNeeded];
   111     }
   112 
   113     /* Add ourself to the new window. */
   114     if (window) {
   115         data = (__bridge SDL_WindowData *) window->driverdata;
   116 
   117         /* Make sure the SDL window has a strong reference to this view. */
   118         [data.views addObject:self];
   119 
   120         /* Replace the view controller's old view with this one. */
   121         [data.viewcontroller.view removeFromSuperview];
   122         data.viewcontroller.view = self;
   123 
   124         /* The root view controller handles rotation and the status bar.
   125          * Assigning it also adds the controller's view to the window. We
   126          * explicitly re-set it to make sure the view is properly attached to
   127          * the window. Just adding the sub-view if the root view controller is
   128          * already correct causes orientation issues on iOS 7 and below. */
   129         data.uiwindow.rootViewController = nil;
   130         data.uiwindow.rootViewController = data.viewcontroller;
   131 
   132         /* The view's bounds may not be correct until the next event cycle. That
   133          * might happen after the current dimensions are queried, so we force a
   134          * layout now to immediately update the bounds. */
   135         [data.uiwindow layoutIfNeeded];
   136     }
   137 
   138     sdlwindow = window;
   139 }
   140 
   141 - (SDL_TouchDeviceType)touchTypeForTouch:(UITouch *)touch
   142 {
   143 #ifdef __IPHONE_9_0
   144     if ([touch respondsToSelector:@selector((type))]) {
   145         if (touch.type == UITouchTypeIndirect) {
   146             return SDL_TOUCH_DEVICE_INDIRECT_RELATIVE;
   147         }
   148     }
   149 #endif
   150 
   151     return SDL_TOUCH_DEVICE_DIRECT;
   152 }
   153 
   154 - (SDL_TouchID)touchIdForType:(SDL_TouchDeviceType)type
   155 {
   156     switch (type) {
   157         case SDL_TOUCH_DEVICE_DIRECT:
   158         default:
   159             return directTouchId;
   160         case SDL_TOUCH_DEVICE_INDIRECT_RELATIVE:
   161             return indirectTouchId;
   162     }
   163 }
   164 
   165 - (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize
   166 {
   167     CGPoint point = [touch locationInView:self];
   168 
   169     if (normalize) {
   170         CGRect bounds = self.bounds;
   171         point.x /= bounds.size.width;
   172         point.y /= bounds.size.height;
   173     }
   174 
   175     return point;
   176 }
   177 
   178 - (float)pressureForTouch:(UITouch *)touch
   179 {
   180 #ifdef __IPHONE_9_0
   181     if ([touch respondsToSelector:@selector(force)]) {
   182         return (float) touch.force;
   183     }
   184 #endif
   185 
   186     return 1.0f;
   187 }
   188 
   189 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
   190 {
   191     for (UITouch *touch in touches) {
   192         SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
   193         SDL_TouchID touchId = [self touchIdForType:touchType];
   194         float pressure = [self pressureForTouch:touch];
   195 
   196         if (SDL_AddTouch(touchId, touchType, "") < 0) {
   197             continue;
   198         }
   199 
   200         if (!firstFingerDown) {
   201             CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO];
   202             int clicks = (int) touch.tapCount;
   203 
   204             /* send mouse moved event */
   205             SDL_SendMouseMotion(sdlwindow, SDL_TOUCH_MOUSEID, 0, locationInView.x, locationInView.y);
   206 
   207             /* send mouse down event */
   208             SDL_SendMouseButtonClicks(sdlwindow, SDL_TOUCH_MOUSEID, SDL_PRESSED, SDL_BUTTON_LEFT, clicks);
   209 
   210             firstFingerDown = touch;
   211         }
   212 
   213         CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
   214         SDL_SendTouch(touchId, (SDL_FingerID)((size_t)touch),
   215                       SDL_TRUE, locationInView.x, locationInView.y, pressure);
   216     }
   217 }
   218 
   219 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
   220 {
   221     for (UITouch *touch in touches) {
   222         SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
   223         SDL_TouchID touchId = [self touchIdForType:touchType];
   224         float pressure = [self pressureForTouch:touch];
   225 
   226         if (SDL_AddTouch(touchId, touchType, "") < 0) {
   227             continue;
   228         }
   229 
   230         if (touch == firstFingerDown) {
   231             /* send mouse up */
   232             int clicks = (int) touch.tapCount;
   233             SDL_SendMouseButtonClicks(sdlwindow, SDL_TOUCH_MOUSEID, SDL_RELEASED, SDL_BUTTON_LEFT, clicks);
   234             firstFingerDown = nil;
   235         }
   236 
   237         CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
   238         SDL_SendTouch(touchId, (SDL_FingerID)((size_t)touch),
   239                       SDL_FALSE, locationInView.x, locationInView.y, pressure);
   240     }
   241 }
   242 
   243 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
   244 {
   245     [self touchesEnded:touches withEvent:event];
   246 }
   247 
   248 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
   249 {
   250     for (UITouch *touch in touches) {
   251         SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
   252         SDL_TouchID touchId = [self touchIdForType:touchType];
   253         float pressure = [self pressureForTouch:touch];
   254 
   255         if (SDL_AddTouch(touchId, touchType, "") < 0) {
   256             continue;
   257         }
   258 
   259         if (touch == firstFingerDown) {
   260             CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO];
   261 
   262             /* send moved event */
   263             SDL_SendMouseMotion(sdlwindow, SDL_TOUCH_MOUSEID, 0, locationInView.x, locationInView.y);
   264         }
   265 
   266         CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
   267         SDL_SendTouchMotion(touchId, (SDL_FingerID)((size_t)touch),
   268                             locationInView.x, locationInView.y, pressure);
   269     }
   270 }
   271 
   272 #if TARGET_OS_TV || defined(__IPHONE_9_1)
   273 - (SDL_Scancode)scancodeFromPressType:(UIPressType)presstype
   274 {
   275     switch (presstype) {
   276     case UIPressTypeUpArrow:
   277         return SDL_SCANCODE_UP;
   278     case UIPressTypeDownArrow:
   279         return SDL_SCANCODE_DOWN;
   280     case UIPressTypeLeftArrow:
   281         return SDL_SCANCODE_LEFT;
   282     case UIPressTypeRightArrow:
   283         return SDL_SCANCODE_RIGHT;
   284     case UIPressTypeSelect:
   285         /* HIG says: "primary button behavior" */
   286         return SDL_SCANCODE_RETURN;
   287     case UIPressTypeMenu:
   288         /* HIG says: "returns to previous screen" */
   289         return SDL_SCANCODE_ESCAPE;
   290     case UIPressTypePlayPause:
   291         /* HIG says: "secondary button behavior" */
   292         return SDL_SCANCODE_PAUSE;
   293     default:
   294         return SDL_SCANCODE_UNKNOWN;
   295     }
   296 }
   297 
   298 - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
   299 {
   300     if (!SDL_AppleTVRemoteOpenedAsJoystick) {
   301         for (UIPress *press in presses) {
   302             SDL_Scancode scancode = [self scancodeFromPressType:press.type];
   303             SDL_SendKeyboardKey(SDL_PRESSED, scancode);
   304         }
   305     }
   306     [super pressesBegan:presses withEvent:event];
   307 }
   308 
   309 - (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
   310 {
   311     if (!SDL_AppleTVRemoteOpenedAsJoystick) {
   312         for (UIPress *press in presses) {
   313             SDL_Scancode scancode = [self scancodeFromPressType:press.type];
   314             SDL_SendKeyboardKey(SDL_RELEASED, scancode);
   315         }
   316     }
   317     [super pressesEnded:presses withEvent:event];
   318 }
   319 
   320 - (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
   321 {
   322     if (!SDL_AppleTVRemoteOpenedAsJoystick) {
   323         for (UIPress *press in presses) {
   324             SDL_Scancode scancode = [self scancodeFromPressType:press.type];
   325             SDL_SendKeyboardKey(SDL_RELEASED, scancode);
   326         }
   327     }
   328     [super pressesCancelled:presses withEvent:event];
   329 }
   330 
   331 - (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
   332 {
   333     /* This is only called when the force of a press changes. */
   334     [super pressesChanged:presses withEvent:event];
   335 }
   336 #endif /* TARGET_OS_TV || defined(__IPHONE_9_1) */
   337 
   338 #if TARGET_OS_TV
   339 -(void)swipeGesture:(UISwipeGestureRecognizer *)gesture
   340 {
   341     /* Swipe gestures don't trigger begin states. */
   342     if (gesture.state == UIGestureRecognizerStateEnded) {
   343         if (!SDL_AppleTVRemoteOpenedAsJoystick) {
   344             /* Send arrow key presses for now, as we don't have an external API
   345              * which better maps to swipe gestures. */
   346             switch (gesture.direction) {
   347             case UISwipeGestureRecognizerDirectionUp:
   348                 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_UP);
   349                 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_UP);
   350                 break;
   351             case UISwipeGestureRecognizerDirectionDown:
   352                 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_DOWN);
   353                 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_DOWN);
   354                 break;
   355             case UISwipeGestureRecognizerDirectionLeft:
   356                 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LEFT);
   357                 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LEFT);
   358                 break;
   359             case UISwipeGestureRecognizerDirectionRight:
   360                 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_RIGHT);
   361                 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RIGHT);
   362                 break;
   363             }
   364         }
   365     }
   366 }
   367 #endif /* TARGET_OS_TV */
   368 
   369 @end
   370 
   371 #endif /* SDL_VIDEO_DRIVER_UIKIT */
   372 
   373 /* vi: set ts=4 sw=4 expandtab: */