sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_uikitviewcontroller.m (18177B)


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