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#import "SDL_uikitappdelegate.h" 33#import "SDL_uikitmodes.h" 34#import "SDL_uikitwindow.h" 35 36/* The maximum number of mouse buttons we support */ 37#define MAX_MOUSE_BUTTONS 5 38 39/* This is defined in SDL_sysjoystick.m */ 40#if !SDL_JOYSTICK_DISABLED 41extern int SDL_AppleTVRemoteOpenedAsJoystick; 42#endif 43 44@implementation SDL_uikitview { 45 SDL_Window *sdlwindow; 46 47 SDL_TouchID directTouchId; 48 SDL_TouchID indirectTouchId; 49} 50 51- (instancetype)initWithFrame:(CGRect)frame 52{ 53 if ((self = [super initWithFrame:frame])) { 54#if TARGET_OS_TV 55 /* Apple TV Remote touchpad swipe gestures. */ 56 UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)]; 57 swipeUp.direction = UISwipeGestureRecognizerDirectionUp; 58 [self addGestureRecognizer:swipeUp]; 59 60 UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)]; 61 swipeDown.direction = UISwipeGestureRecognizerDirectionDown; 62 [self addGestureRecognizer:swipeDown]; 63 64 UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)]; 65 swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft; 66 [self addGestureRecognizer:swipeLeft]; 67 68 UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)]; 69 swipeRight.direction = UISwipeGestureRecognizerDirectionRight; 70 [self addGestureRecognizer:swipeRight]; 71#else 72 if (@available(iOS 13.4, *)) { 73 UIPanGestureRecognizer *mouseWheelRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(mouseWheelGesture:)]; 74 mouseWheelRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete; 75 mouseWheelRecognizer.allowedTouchTypes = @[ @(UITouchTypeIndirectPointer) ]; 76 mouseWheelRecognizer.cancelsTouchesInView = NO; 77 mouseWheelRecognizer.delaysTouchesBegan = NO; 78 mouseWheelRecognizer.delaysTouchesEnded = NO; 79 [self addGestureRecognizer:mouseWheelRecognizer]; 80 } 81#endif 82 83 self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 84 self.autoresizesSubviews = YES; 85 86 directTouchId = 1; 87 indirectTouchId = 2; 88 89#if !TARGET_OS_TV 90 self.multipleTouchEnabled = YES; 91 SDL_AddTouch(directTouchId, SDL_TOUCH_DEVICE_DIRECT, ""); 92#endif 93 94#if !TARGET_OS_TV && defined(__IPHONE_13_4) 95 if (@available(iOS 13.4, *)) { 96 [self addInteraction:[[UIPointerInteraction alloc] initWithDelegate:self]]; 97 } 98#endif 99 } 100 101 return self; 102} 103 104- (void)setSDLWindow:(SDL_Window *)window 105{ 106 SDL_WindowData *data = nil; 107 108 if (window == sdlwindow) { 109 return; 110 } 111 112 /* Remove ourself from the old window. */ 113 if (sdlwindow) { 114 SDL_uikitview *view = nil; 115 data = (__bridge SDL_WindowData *) sdlwindow->driverdata; 116 117 [data.views removeObject:self]; 118 119 [self removeFromSuperview]; 120 121 /* Restore the next-oldest view in the old window. */ 122 view = data.views.lastObject; 123 124 data.viewcontroller.view = view; 125 126 data.uiwindow.rootViewController = nil; 127 data.uiwindow.rootViewController = data.viewcontroller; 128 129 [data.uiwindow layoutIfNeeded]; 130 } 131 132 /* Add ourself to the new window. */ 133 if (window) { 134 data = (__bridge SDL_WindowData *) window->driverdata; 135 136 /* Make sure the SDL window has a strong reference to this view. */ 137 [data.views addObject:self]; 138 139 /* Replace the view controller's old view with this one. */ 140 [data.viewcontroller.view removeFromSuperview]; 141 data.viewcontroller.view = self; 142 143 /* The root view controller handles rotation and the status bar. 144 * Assigning it also adds the controller's view to the window. We 145 * explicitly re-set it to make sure the view is properly attached to 146 * the window. Just adding the sub-view if the root view controller is 147 * already correct causes orientation issues on iOS 7 and below. */ 148 data.uiwindow.rootViewController = nil; 149 data.uiwindow.rootViewController = data.viewcontroller; 150 151 /* The view's bounds may not be correct until the next event cycle. That 152 * might happen after the current dimensions are queried, so we force a 153 * layout now to immediately update the bounds. */ 154 [data.uiwindow layoutIfNeeded]; 155 } 156 157 sdlwindow = window; 158} 159 160#if !TARGET_OS_TV && defined(__IPHONE_13_4) 161- (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4)){ 162 if (request != nil) { 163 CGPoint origin = self.bounds.origin; 164 CGPoint point = request.location; 165 166 point.x -= origin.x; 167 point.y -= origin.y; 168 169 SDL_SendMouseMotion(sdlwindow, 0, 0, (int)point.x, (int)point.y); 170 } 171 return [UIPointerRegion regionWithRect:self.bounds identifier:nil]; 172} 173 174- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region API_AVAILABLE(ios(13.4)){ 175 if (SDL_ShowCursor(-1)) { 176 return nil; 177 } else { 178 return [UIPointerStyle hiddenPointerStyle]; 179 } 180} 181#endif /* !TARGET_OS_TV && __IPHONE_13_4 */ 182 183- (SDL_TouchDeviceType)touchTypeForTouch:(UITouch *)touch 184{ 185#ifdef __IPHONE_9_0 186 if ([touch respondsToSelector:@selector((type))]) { 187 if (touch.type == UITouchTypeIndirect) { 188 return SDL_TOUCH_DEVICE_INDIRECT_RELATIVE; 189 } 190 } 191#endif 192 193 return SDL_TOUCH_DEVICE_DIRECT; 194} 195 196- (SDL_TouchID)touchIdForType:(SDL_TouchDeviceType)type 197{ 198 switch (type) { 199 case SDL_TOUCH_DEVICE_DIRECT: 200 default: 201 return directTouchId; 202 case SDL_TOUCH_DEVICE_INDIRECT_RELATIVE: 203 return indirectTouchId; 204 } 205} 206 207- (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize 208{ 209 CGPoint point = [touch locationInView:self]; 210 211 if (normalize) { 212 CGRect bounds = self.bounds; 213 point.x /= bounds.size.width; 214 point.y /= bounds.size.height; 215 } 216 217 return point; 218} 219 220- (float)pressureForTouch:(UITouch *)touch 221{ 222#ifdef __IPHONE_9_0 223 if ([touch respondsToSelector:@selector(force)]) { 224 return (float) touch.force; 225 } 226#endif 227 228 return 1.0f; 229} 230 231- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 232{ 233 for (UITouch *touch in touches) { 234 BOOL handled = NO; 235 236#if !TARGET_OS_TV && defined(__IPHONE_13_4) 237 if (@available(iOS 13.4, *)) { 238 if (touch.type == UITouchTypeIndirectPointer) { 239 int i; 240 241 for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) { 242 if (event.buttonMask & SDL_BUTTON(i)) { 243 Uint8 button; 244 245 switch (i) { 246 case 1: 247 button = SDL_BUTTON_LEFT; 248 break; 249 case 2: 250 button = SDL_BUTTON_RIGHT; 251 break; 252 case 3: 253 button = SDL_BUTTON_MIDDLE; 254 break; 255 default: 256 button = (Uint8)i; 257 break; 258 } 259 SDL_SendMouseButton(sdlwindow, 0, SDL_PRESSED, button); 260 } 261 } 262 handled = YES; 263 } 264 } 265#endif 266 if (!handled) { 267 SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch]; 268 SDL_TouchID touchId = [self touchIdForType:touchType]; 269 float pressure = [self pressureForTouch:touch]; 270 271 if (SDL_AddTouch(touchId, touchType, "") < 0) { 272 continue; 273 } 274 275 /* FIXME, need to send: int clicks = (int) touch.tapCount; ? */ 276 277 CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES]; 278 SDL_SendTouch(touchId, (SDL_FingerID)((size_t)touch), sdlwindow, 279 SDL_TRUE, locationInView.x, locationInView.y, pressure); 280 } 281 } 282} 283 284- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 285{ 286 for (UITouch *touch in touches) { 287 BOOL handled = NO; 288 289#if !TARGET_OS_TV && defined(__IPHONE_13_4) 290 if (@available(iOS 13.4, *)) { 291 if (touch.type == UITouchTypeIndirectPointer) { 292 int i; 293 294 for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) { 295 if (!(event.buttonMask & SDL_BUTTON(i))) { 296 Uint8 button; 297 298 switch (i) { 299 case 1: 300 button = SDL_BUTTON_LEFT; 301 break; 302 case 2: 303 button = SDL_BUTTON_RIGHT; 304 break; 305 case 3: 306 button = SDL_BUTTON_MIDDLE; 307 break; 308 default: 309 button = (Uint8)i; 310 break; 311 } 312 SDL_SendMouseButton(sdlwindow, 0, SDL_RELEASED, button); 313 } 314 } 315 handled = YES; 316 } 317 } 318#endif 319 if (!handled) { 320 SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch]; 321 SDL_TouchID touchId = [self touchIdForType:touchType]; 322 float pressure = [self pressureForTouch:touch]; 323 324 if (SDL_AddTouch(touchId, touchType, "") < 0) { 325 continue; 326 } 327 328 /* FIXME, need to send: int clicks = (int) touch.tapCount; ? */ 329 330 CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES]; 331 SDL_SendTouch(touchId, (SDL_FingerID)((size_t)touch), sdlwindow, 332 SDL_FALSE, locationInView.x, locationInView.y, pressure); 333 } 334 } 335} 336 337- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 338{ 339 [self touchesEnded:touches withEvent:event]; 340} 341 342- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 343{ 344 for (UITouch *touch in touches) { 345 BOOL handled = NO; 346 347#if !TARGET_OS_TV && defined(__IPHONE_13_4) 348 if (@available(iOS 13.4, *)) { 349 if (touch.type == UITouchTypeIndirectPointer) { 350 /* Already handled in pointerInteraction callback */ 351 handled = YES; 352 } 353 } 354#endif 355 if (!handled) { 356 SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch]; 357 SDL_TouchID touchId = [self touchIdForType:touchType]; 358 float pressure = [self pressureForTouch:touch]; 359 360 if (SDL_AddTouch(touchId, touchType, "") < 0) { 361 continue; 362 } 363 364 CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES]; 365 SDL_SendTouchMotion(touchId, (SDL_FingerID)((size_t)touch), sdlwindow, 366 locationInView.x, locationInView.y, pressure); 367 } 368 } 369} 370 371#if TARGET_OS_TV || defined(__IPHONE_9_1) 372- (SDL_Scancode)scancodeFromPress:(UIPress*)press 373{ 374#ifdef __IPHONE_13_4 375 if ([press respondsToSelector:@selector((key))]) { 376 if (press.key != nil) { 377 return (SDL_Scancode)press.key.keyCode; 378 } 379 } 380#endif 381 382#if !SDL_JOYSTICK_DISABLED 383 /* Presses from Apple TV remote */ 384 if (!SDL_AppleTVRemoteOpenedAsJoystick) { 385 switch (press.type) { 386 case UIPressTypeUpArrow: 387 return SDL_SCANCODE_UP; 388 case UIPressTypeDownArrow: 389 return SDL_SCANCODE_DOWN; 390 case UIPressTypeLeftArrow: 391 return SDL_SCANCODE_LEFT; 392 case UIPressTypeRightArrow: 393 return SDL_SCANCODE_RIGHT; 394 case UIPressTypeSelect: 395 /* HIG says: "primary button behavior" */ 396 return SDL_SCANCODE_RETURN; 397 case UIPressTypeMenu: 398 /* HIG says: "returns to previous screen" */ 399 return SDL_SCANCODE_ESCAPE; 400 case UIPressTypePlayPause: 401 /* HIG says: "secondary button behavior" */ 402 return SDL_SCANCODE_PAUSE; 403 default: 404 break; 405 } 406 } 407#endif /* !SDL_JOYSTICK_DISABLED */ 408 409 return SDL_SCANCODE_UNKNOWN; 410} 411 412- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event 413{ 414 for (UIPress *press in presses) { 415 SDL_Scancode scancode = [self scancodeFromPress:press]; 416 SDL_SendKeyboardKey(SDL_PRESSED, scancode); 417 } 418 [super pressesBegan:presses withEvent:event]; 419} 420 421- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event 422{ 423 for (UIPress *press in presses) { 424 SDL_Scancode scancode = [self scancodeFromPress:press]; 425 SDL_SendKeyboardKey(SDL_RELEASED, scancode); 426 } 427 [super pressesEnded:presses withEvent:event]; 428} 429 430- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event 431{ 432 for (UIPress *press in presses) { 433 SDL_Scancode scancode = [self scancodeFromPress:press]; 434 SDL_SendKeyboardKey(SDL_RELEASED, scancode); 435 } 436 [super pressesCancelled:presses withEvent:event]; 437} 438 439- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event 440{ 441 /* This is only called when the force of a press changes. */ 442 [super pressesChanged:presses withEvent:event]; 443} 444 445#endif /* TARGET_OS_TV || defined(__IPHONE_9_1) */ 446 447-(void)mouseWheelGesture:(UIPanGestureRecognizer *)gesture 448{ 449 if (gesture.state == UIGestureRecognizerStateBegan || 450 gesture.state == UIGestureRecognizerStateChanged || 451 gesture.state == UIGestureRecognizerStateEnded) { 452 CGPoint velocity = [gesture velocityInView:self]; 453 454 if (velocity.x > 0.0f) { 455 velocity.x = -1.0; 456 } else if (velocity.x < 0.0f) { 457 velocity.x = 1.0f; 458 } 459 if (velocity.y > 0.0f) { 460 velocity.y = -1.0; 461 } else if (velocity.y < 0.0f) { 462 velocity.y = 1.0f; 463 } 464 if (velocity.x != 0.0f || velocity.y != 0.0f) { 465 SDL_SendMouseWheel(sdlwindow, 0, velocity.x, velocity.y, SDL_MOUSEWHEEL_NORMAL); 466 } 467 } 468} 469 470#if TARGET_OS_TV 471-(void)swipeGesture:(UISwipeGestureRecognizer *)gesture 472{ 473 /* Swipe gestures don't trigger begin states. */ 474 if (gesture.state == UIGestureRecognizerStateEnded) { 475#if !SDL_JOYSTICK_DISABLED 476 if (!SDL_AppleTVRemoteOpenedAsJoystick) { 477 /* Send arrow key presses for now, as we don't have an external API 478 * which better maps to swipe gestures. */ 479 switch (gesture.direction) { 480 case UISwipeGestureRecognizerDirectionUp: 481 SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_UP); 482 break; 483 case UISwipeGestureRecognizerDirectionDown: 484 SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_DOWN); 485 break; 486 case UISwipeGestureRecognizerDirectionLeft: 487 SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_LEFT); 488 break; 489 case UISwipeGestureRecognizerDirectionRight: 490 SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_RIGHT); 491 break; 492 } 493 } 494#endif /* !SDL_JOYSTICK_DISABLED */ 495 } 496} 497#endif /* TARGET_OS_TV */ 498 499@end 500 501#endif /* SDL_VIDEO_DRIVER_UIKIT */ 502 503/* vi: set ts=4 sw=4 expandtab: */ 504