src/video/uikit/SDL_uikitviewcontroller.m
author Sam Lantinga <slouken@libsdl.org>
Thu, 27 Feb 2020 13:53:32 -0800
changeset 13558 ed7c27865ea7
parent 13456 72013e9bedb4
permissions -rw-r--r--
Fixed trying to handle the HORI Wireless Switch Pad when connected via USB
     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_video.h"
    26 #include "SDL_assert.h"
    27 #include "SDL_hints.h"
    28 #include "../SDL_sysvideo.h"
    29 #include "../../events/SDL_events_c.h"
    30 
    31 #import "SDL_uikitviewcontroller.h"
    32 #import "SDL_uikitmessagebox.h"
    33 #include "SDL_uikitvideo.h"
    34 #include "SDL_uikitmodes.h"
    35 #include "SDL_uikitwindow.h"
    36 #include "SDL_uikitopengles.h"
    37 
    38 #if SDL_IPHONE_KEYBOARD
    39 #include "keyinfotable.h"
    40 #endif
    41 
    42 #if TARGET_OS_TV
    43 static void SDLCALL
    44 SDL_AppleTVControllerUIHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
    45 {
    46     @autoreleasepool {
    47         SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *) userdata;
    48         viewcontroller.controllerUserInteractionEnabled = hint && (*hint != '0');
    49     }
    50 }
    51 #endif
    52 
    53 #if !TARGET_OS_TV
    54 static void SDLCALL
    55 SDL_HideHomeIndicatorHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
    56 {
    57     @autoreleasepool {
    58         SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *) userdata;
    59         viewcontroller.homeIndicatorHidden = (hint && *hint) ? SDL_atoi(hint) : -1;
    60 #pragma clang diagnostic push
    61 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
    62         if ([viewcontroller respondsToSelector:@selector(setNeedsUpdateOfHomeIndicatorAutoHidden)]) {
    63             [viewcontroller setNeedsUpdateOfHomeIndicatorAutoHidden];
    64             [viewcontroller setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
    65         }
    66 #pragma clang diagnostic pop
    67     }
    68 }
    69 #endif
    70 
    71 @implementation SDL_uikitviewcontroller {
    72     CADisplayLink *displayLink;
    73     int animationInterval;
    74     void (*animationCallback)(void*);
    75     void *animationCallbackParam;
    76 
    77 #if SDL_IPHONE_KEYBOARD
    78     UITextField *textField;
    79     BOOL hardwareKeyboard;
    80     BOOL showingKeyboard;
    81     BOOL rotatingOrientation;
    82     NSString *changeText;
    83     NSString *obligateForBackspace;
    84 #endif
    85 }
    86 
    87 @synthesize window;
    88 
    89 - (instancetype)initWithSDLWindow:(SDL_Window *)_window
    90 {
    91     if (self = [super initWithNibName:nil bundle:nil]) {
    92         self.window = _window;
    93 
    94 #if SDL_IPHONE_KEYBOARD
    95         [self initKeyboard];
    96         hardwareKeyboard = NO;
    97         showingKeyboard = NO;
    98         rotatingOrientation = NO;
    99 #endif
   100 
   101 #if TARGET_OS_TV
   102         SDL_AddHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
   103                             SDL_AppleTVControllerUIHintChanged,
   104                             (__bridge void *) self);
   105 #endif
   106 
   107 #if !TARGET_OS_TV
   108         SDL_AddHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR,
   109                             SDL_HideHomeIndicatorHintChanged,
   110                             (__bridge void *) self);
   111 #endif
   112     }
   113     return self;
   114 }
   115 
   116 - (void)dealloc
   117 {
   118 #if SDL_IPHONE_KEYBOARD
   119     [self deinitKeyboard];
   120 #endif
   121 
   122 #if TARGET_OS_TV
   123     SDL_DelHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
   124                         SDL_AppleTVControllerUIHintChanged,
   125                         (__bridge void *) self);
   126 #endif
   127 
   128 #if !TARGET_OS_TV
   129     SDL_DelHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR,
   130                         SDL_HideHomeIndicatorHintChanged,
   131                         (__bridge void *) self);
   132 #endif
   133 }
   134 
   135 - (void)setAnimationCallback:(int)interval
   136                     callback:(void (*)(void*))callback
   137                callbackParam:(void*)callbackParam
   138 {
   139     [self stopAnimation];
   140 
   141     animationInterval = interval;
   142     animationCallback = callback;
   143     animationCallbackParam = callbackParam;
   144 
   145     if (animationCallback) {
   146         [self startAnimation];
   147     }
   148 }
   149 
   150 - (void)startAnimation
   151 {
   152     displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
   153 
   154 #ifdef __IPHONE_10_3
   155     SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
   156 
   157     if ([displayLink respondsToSelector:@selector(preferredFramesPerSecond)]
   158         && data != nil && data.uiwindow != nil
   159         && [data.uiwindow.screen respondsToSelector:@selector(maximumFramesPerSecond)]) {
   160         displayLink.preferredFramesPerSecond = data.uiwindow.screen.maximumFramesPerSecond / animationInterval;
   161     } else
   162 #endif
   163     {
   164 #if __IPHONE_OS_VERSION_MIN_REQUIRED < 100300
   165         [displayLink setFrameInterval:animationInterval];
   166 #endif
   167     }
   168 
   169     [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
   170 }
   171 
   172 - (void)stopAnimation
   173 {
   174     [displayLink invalidate];
   175     displayLink = nil;
   176 }
   177 
   178 - (void)doLoop:(CADisplayLink*)sender
   179 {
   180     /* Don't run the game loop while a messagebox is up */
   181     if (!UIKit_ShowingMessageBox()) {
   182         /* See the comment in the function definition. */
   183 #if SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2
   184         UIKit_GL_RestoreCurrentContext();
   185 #endif
   186 
   187         animationCallback(animationCallbackParam);
   188     }
   189 }
   190 
   191 - (void)loadView
   192 {
   193     /* Do nothing. */
   194 }
   195 
   196 - (void)viewDidLayoutSubviews
   197 {
   198     const CGSize size = self.view.bounds.size;
   199     int w = (int) size.width;
   200     int h = (int) size.height;
   201 
   202     SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h);
   203 }
   204 
   205 #if !TARGET_OS_TV
   206 - (NSUInteger)supportedInterfaceOrientations
   207 {
   208     return UIKit_GetSupportedOrientations(window);
   209 }
   210 
   211 #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
   212 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient
   213 {
   214     return ([self supportedInterfaceOrientations] & (1 << orient)) != 0;
   215 }
   216 #endif
   217 
   218 - (BOOL)prefersStatusBarHidden
   219 {
   220     BOOL hidden = (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0;
   221     return hidden;
   222 }
   223 
   224 - (BOOL)prefersHomeIndicatorAutoHidden
   225 {
   226     BOOL hidden = NO;
   227     if (self.homeIndicatorHidden == 1) {
   228         hidden = YES;
   229     }
   230     return hidden;
   231 }
   232 
   233 - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
   234 {
   235     if (self.homeIndicatorHidden >= 0) {
   236         if (self.homeIndicatorHidden == 2) {
   237             return UIRectEdgeAll;
   238         } else {
   239             return UIRectEdgeNone;
   240         }
   241     }
   242 
   243     /* By default, fullscreen and borderless windows get all screen gestures */
   244     if ((window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0) {
   245         return UIRectEdgeAll;
   246     } else {
   247         return UIRectEdgeNone;
   248     }
   249 }
   250 #endif
   251 
   252 /*
   253  ---- Keyboard related functionality below this line ----
   254  */
   255 #if SDL_IPHONE_KEYBOARD
   256 
   257 @synthesize textInputRect;
   258 @synthesize keyboardHeight;
   259 @synthesize keyboardVisible;
   260 
   261 /* Set ourselves up as a UITextFieldDelegate */
   262 - (void)initKeyboard
   263 {
   264     changeText = nil;
   265     obligateForBackspace = @"                                                                "; /* 64 space */
   266     textField = [[UITextField alloc] initWithFrame:CGRectZero];
   267     textField.delegate = self;
   268     /* placeholder so there is something to delete! */
   269     textField.text = obligateForBackspace;
   270 
   271     /* set UITextInputTrait properties, mostly to defaults */
   272     textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
   273     textField.autocorrectionType = UITextAutocorrectionTypeNo;
   274     textField.enablesReturnKeyAutomatically = NO;
   275     textField.keyboardAppearance = UIKeyboardAppearanceDefault;
   276     textField.keyboardType = UIKeyboardTypeDefault;
   277     textField.returnKeyType = UIReturnKeyDefault;
   278     textField.secureTextEntry = NO;
   279 
   280     textField.hidden = YES;
   281     keyboardVisible = NO;
   282 
   283     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   284 #if !TARGET_OS_TV
   285     [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
   286     [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
   287 #endif
   288     [center addObserver:self selector:@selector(textFieldTextDidChange:) name:UITextFieldTextDidChangeNotification object:nil];
   289 }
   290 
   291 - (NSArray *)keyCommands
   292 {
   293     NSMutableArray *commands = [[NSMutableArray alloc] init];
   294     [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
   295     [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
   296     [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
   297     [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
   298     [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:kNilOptions action:@selector(handleCommand:)]];
   299     return [NSArray arrayWithArray:commands];
   300 }
   301 
   302 - (void)handleCommand:(UIKeyCommand *)keyCommand
   303 {
   304     SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
   305     NSString *input = keyCommand.input;
   306 
   307     if (input == UIKeyInputUpArrow) {
   308         scancode = SDL_SCANCODE_UP;
   309     } else if (input == UIKeyInputDownArrow) {
   310         scancode = SDL_SCANCODE_DOWN;
   311     } else if (input == UIKeyInputLeftArrow) {
   312         scancode = SDL_SCANCODE_LEFT;
   313     } else if (input == UIKeyInputRightArrow) {
   314         scancode = SDL_SCANCODE_RIGHT;
   315     } else if (input == UIKeyInputEscape) {
   316         scancode = SDL_SCANCODE_ESCAPE;
   317     }
   318 
   319     if (scancode != SDL_SCANCODE_UNKNOWN) {
   320         SDL_SendKeyboardKey(SDL_PRESSED, scancode);
   321         SDL_SendKeyboardKey(SDL_RELEASED, scancode);
   322     }
   323 }
   324 
   325 - (void)setView:(UIView *)view
   326 {
   327     [super setView:view];
   328 
   329     [view addSubview:textField];
   330 
   331     if (keyboardVisible) {
   332         [self showKeyboard];
   333     }
   334 }
   335 
   336 /* willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are deprecated in iOS 8+ in favor of viewWillTransitionToSize */
   337 #if TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
   338 - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
   339 {
   340     [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
   341     rotatingOrientation = YES;
   342     [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {}
   343                                  completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
   344         self->rotatingOrientation = NO;
   345 	}];
   346 }
   347 #else
   348 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
   349     [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
   350     rotatingOrientation = YES;
   351 }
   352 
   353 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
   354     [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
   355     rotatingOrientation = NO;
   356 }
   357 #endif /* TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000 */
   358 
   359 - (void)deinitKeyboard
   360 {
   361     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   362 #if !TARGET_OS_TV
   363     [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
   364     [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
   365 #endif
   366     [center removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
   367 }
   368 
   369 /* reveal onscreen virtual keyboard */
   370 - (void)showKeyboard
   371 {
   372     keyboardVisible = YES;
   373     if (textField.window) {
   374         showingKeyboard = YES;
   375         [textField becomeFirstResponder];
   376         showingKeyboard = NO;
   377     }
   378 }
   379 
   380 /* hide onscreen virtual keyboard */
   381 - (void)hideKeyboard
   382 {
   383     keyboardVisible = NO;
   384     [textField resignFirstResponder];
   385 }
   386 
   387 - (void)keyboardWillShow:(NSNotification *)notification
   388 {
   389 #if !TARGET_OS_TV
   390     CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
   391 
   392     /* The keyboard rect is in the coordinate space of the screen/window, but we
   393      * want its height in the coordinate space of the view. */
   394     kbrect = [self.view convertRect:kbrect fromView:nil];
   395 
   396     [self setKeyboardHeight:(int)kbrect.size.height];
   397 #endif
   398 }
   399 
   400 - (void)keyboardWillHide:(NSNotification *)notification
   401 {
   402     if (!showingKeyboard && !rotatingOrientation) {
   403         SDL_StopTextInput();
   404     }
   405     [self setKeyboardHeight:0];
   406 }
   407 
   408 - (void)textFieldTextDidChange:(NSNotification *)notification
   409 {
   410     if (changeText!=nil && textField.markedTextRange == nil)
   411     {
   412         NSUInteger len = changeText.length;
   413         if (len > 0) {
   414             /* Go through all the characters in the string we've been sent and
   415              * convert them to key presses */
   416             int i;
   417             for (i = 0; i < len; i++) {
   418                 unichar c = [changeText characterAtIndex:i];
   419                 SDL_Scancode code;
   420                 Uint16 mod;
   421 
   422                 if (c < 127) {
   423                     /* Figure out the SDL_Scancode and SDL_keymod for this unichar */
   424                     code = unicharToUIKeyInfoTable[c].code;
   425                     mod  = unicharToUIKeyInfoTable[c].mod;
   426                 } else {
   427                     /* We only deal with ASCII right now */
   428                     code = SDL_SCANCODE_UNKNOWN;
   429                     mod = 0;
   430                 }
   431 
   432                 if (mod & KMOD_SHIFT) {
   433                     /* If character uses shift, press shift down */
   434                     SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT);
   435                 }
   436 
   437                 /* send a keydown and keyup even for the character */
   438                 SDL_SendKeyboardKey(SDL_PRESSED, code);
   439                 SDL_SendKeyboardKey(SDL_RELEASED, code);
   440 
   441                 if (mod & KMOD_SHIFT) {
   442                     /* If character uses shift, press shift back up */
   443                     SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT);
   444                 }
   445             }
   446             SDL_SendKeyboardText([changeText UTF8String]);
   447         }
   448         changeText = nil;
   449     }
   450 }
   451 
   452 - (void)updateKeyboard
   453 {
   454     CGAffineTransform t = self.view.transform;
   455     CGPoint offset = CGPointMake(0.0, 0.0);
   456     CGRect frame = UIKit_ComputeViewFrame(window, self.view.window.screen);
   457 
   458     if (self.keyboardHeight) {
   459         int rectbottom = self.textInputRect.y + self.textInputRect.h;
   460         int keybottom = self.view.bounds.size.height - self.keyboardHeight;
   461         if (keybottom < rectbottom) {
   462             offset.y = keybottom - rectbottom;
   463         }
   464     }
   465 
   466     /* Apply this view's transform (except any translation) to the offset, in
   467      * order to orient it correctly relative to the frame's coordinate space. */
   468     t.tx = 0.0;
   469     t.ty = 0.0;
   470     offset = CGPointApplyAffineTransform(offset, t);
   471 
   472     /* Apply the updated offset to the view's frame. */
   473     frame.origin.x += offset.x;
   474     frame.origin.y += offset.y;
   475 
   476     self.view.frame = frame;
   477 }
   478 
   479 - (void)setKeyboardHeight:(int)height
   480 {
   481     keyboardVisible = height > 0;
   482     keyboardHeight = height;
   483     [self updateKeyboard];
   484 }
   485 
   486 /* UITextFieldDelegate method.  Invoked when user types something. */
   487 - (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
   488 {
   489     NSUInteger len = string.length;
   490     if (len == 0) {
   491         changeText = nil;
   492         if (textField.markedTextRange == nil) {
   493             /* it wants to replace text with nothing, ie a delete */
   494             SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_BACKSPACE);
   495             SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_BACKSPACE);
   496         }
   497         if (textField.text.length < 16) {
   498             textField.text = obligateForBackspace;
   499         }
   500     } else {
   501         changeText = string;
   502     }
   503     return YES;
   504 }
   505 
   506 /* Terminates the editing session */
   507 - (BOOL)textFieldShouldReturn:(UITextField*)_textField
   508 {
   509     SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_RETURN);
   510     SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RETURN);
   511     if (keyboardVisible &&
   512         SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, SDL_FALSE)) {
   513          SDL_StopTextInput();
   514     }
   515     return YES;
   516 }
   517 
   518 #endif
   519 
   520 @end
   521 
   522 /* iPhone keyboard addition functions */
   523 #if SDL_IPHONE_KEYBOARD
   524 
   525 static SDL_uikitviewcontroller *
   526 GetWindowViewController(SDL_Window * window)
   527 {
   528     if (!window || !window->driverdata) {
   529         SDL_SetError("Invalid window");
   530         return nil;
   531     }
   532 
   533     SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
   534 
   535     return data.viewcontroller;
   536 }
   537 
   538 SDL_bool
   539 UIKit_HasScreenKeyboardSupport(_THIS)
   540 {
   541     return SDL_TRUE;
   542 }
   543 
   544 void
   545 UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window)
   546 {
   547     @autoreleasepool {
   548         SDL_uikitviewcontroller *vc = GetWindowViewController(window);
   549         [vc showKeyboard];
   550     }
   551 }
   552 
   553 void
   554 UIKit_HideScreenKeyboard(_THIS, SDL_Window *window)
   555 {
   556     @autoreleasepool {
   557         SDL_uikitviewcontroller *vc = GetWindowViewController(window);
   558         [vc hideKeyboard];
   559     }
   560 }
   561 
   562 SDL_bool
   563 UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window)
   564 {
   565     @autoreleasepool {
   566         SDL_uikitviewcontroller *vc = GetWindowViewController(window);
   567         if (vc != nil) {
   568             return vc.keyboardVisible;
   569         }
   570         return SDL_FALSE;
   571     }
   572 }
   573 
   574 void
   575 UIKit_SetTextInputRect(_THIS, SDL_Rect *rect)
   576 {
   577     if (!rect) {
   578         SDL_InvalidParamError("rect");
   579         return;
   580     }
   581 
   582     @autoreleasepool {
   583         SDL_uikitviewcontroller *vc = GetWindowViewController(SDL_GetFocusWindow());
   584         if (vc != nil) {
   585             vc.textInputRect = *rect;
   586 
   587             if (vc.keyboardVisible) {
   588                 [vc updateKeyboard];
   589             }
   590         }
   591     }
   592 }
   593 
   594 
   595 #endif /* SDL_IPHONE_KEYBOARD */
   596 
   597 #endif /* SDL_VIDEO_DRIVER_UIKIT */
   598 
   599 /* vi: set ts=4 sw=4 expandtab: */