SDL_uikitview.m (17427B)
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 42 extern int SDL_AppleTVRemoteOpenedAsJoystick; 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) { 189 return SDL_TOUCH_DEVICE_INDIRECT_RELATIVE; 190 } 191 } 192 #endif 193 194 return SDL_TOUCH_DEVICE_DIRECT; 195 } 196 197 - (SDL_TouchID)touchIdForType:(SDL_TouchDeviceType)type 198 { 199 switch (type) { 200 case SDL_TOUCH_DEVICE_DIRECT: 201 default: 202 return directTouchId; 203 case SDL_TOUCH_DEVICE_INDIRECT_RELATIVE: 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: 249 button = SDL_BUTTON_LEFT; 250 break; 251 case 2: 252 button = SDL_BUTTON_RIGHT; 253 break; 254 case 3: 255 button = SDL_BUTTON_MIDDLE; 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: 304 button = SDL_BUTTON_LEFT; 305 break; 306 case 2: 307 button = SDL_BUTTON_RIGHT; 308 break; 309 case 3: 310 button = SDL_BUTTON_MIDDLE; 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 */ 389 if (!SDL_AppleTVRemoteOpenedAsJoystick) { 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]; 433 SDL_SendKeyboardKey(SDL_RELEASED, scancode); 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]; 444 SDL_SendKeyboardKey(SDL_RELEASED, scancode); 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 487 if (!SDL_AppleTVRemoteOpenedAsJoystick) { 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: 492 SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_UP); 493 break; 494 case UISwipeGestureRecognizerDirectionDown: 495 SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_DOWN); 496 break; 497 case UISwipeGestureRecognizerDirectionLeft: 498 SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_LEFT); 499 break; 500 case UISwipeGestureRecognizerDirectionRight: 501 SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_RIGHT); 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: */