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