src/video/uikit/SDL_uikitviewcontroller.m
author Alex Szpakowski
Thu, 15 Jan 2015 01:06:14 -0400
branchiOS-improvements
changeset 9532 318042c16b76
parent 9529 4bf9830d8153
child 9585 8339700453c6
permissions -rw-r--r--
Several improvements to the iOS backend:

- Added new custom launch screen code. It uses the launch screen nib when available on iOS 8+, the launch images dictionary if the launch screen nib isn't available, and the old standard image names if the launch image dictionary isn't in the plist.
The launch screen is now hidden during the first call to SDL_PumpEvents rather than SDL_CreateWindow so apps can have the launch screen still visible if they do time-consuming loading after creating their window. It also fades out in roughly the same way as the system launch screen behavior.
It can be disabled by setting the SDL_IPHONE_LAUNCHSCREEN define in SDL_config_iphoneos.h to 0.

- A blank UIView is now created and displayed when the window is first created. The old behavior was to defer creating any view until SDL_GL_CreateContext, which prevented rotation, touch events, and other windowing-related things from working until then. This also makes it easier to use SDL_GetWindowWMInfo after creating a window.

- Moved the keyboard and animation callback code from SDL's UIView subclasses to its UIViewController subclass, which lets them work properly in all cases when a SDL window is valid, even before SDL_GL_CreateContext is called and after SDL_GL_DeleteContext is called.

- SDL_GL_CreateContext, SDL_GL_SwapWindow, SDL_GL_MakeCurrent, and SDL_GL_DeleteContext are more robust.

- Fixed some edge cases where SDL windows weren't rotating properly or their reported sizes were out of sync with their actual sizes.

- Removed all calls to [UIApplication setStatusBarOrientation:]. It doesn't seem to work as expected in all cases in recent iOS versions.

- Some code style cleanup.
kees@5640
     1
/*
slouken@6044
     2
  Simple DirectMedia Layer
slouken@8149
     3
  Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org>
kees@6001
     4
slouken@6044
     5
  This software is provided 'as-is', without any express or implied
slouken@6044
     6
  warranty.  In no event will the authors be held liable for any damages
slouken@6044
     7
  arising from the use of this software.
slouken@6044
     8
slouken@6044
     9
  Permission is granted to anyone to use this software for any purpose,
slouken@6044
    10
  including commercial applications, and to alter it and redistribute it
slouken@6044
    11
  freely, subject to the following restrictions:
kees@6001
    12
slouken@6044
    13
  1. The origin of this software must not be misrepresented; you must not
slouken@6044
    14
     claim that you wrote the original software. If you use this software
slouken@6044
    15
     in a product, an acknowledgment in the product documentation would be
slouken@6044
    16
     appreciated but is not required.
slouken@6044
    17
  2. Altered source versions must be plainly marked as such, and must not be
slouken@6044
    18
     misrepresented as being the original software.
slouken@6044
    19
  3. This notice may not be removed or altered from any source distribution.
slouken@6044
    20
*/
icculus@8093
    21
#include "../../SDL_internal.h"
kees@6001
    22
slouken@6044
    23
#if SDL_VIDEO_DRIVER_UIKIT
kees@5640
    24
kees@5640
    25
#include "SDL_video.h"
kees@5640
    26
#include "SDL_assert.h"
kees@5640
    27
#include "SDL_hints.h"
kees@5640
    28
#include "../SDL_sysvideo.h"
kees@5640
    29
#include "../../events/SDL_events_c.h"
kees@5640
    30
slime73@9532
    31
#import "SDL_uikitviewcontroller.h"
slime73@9532
    32
#import "SDL_uikitmessagebox.h"
icculus@6093
    33
#include "SDL_uikitvideo.h"
slouken@6518
    34
#include "SDL_uikitmodes.h"
slouken@6518
    35
#include "SDL_uikitwindow.h"
slouken@6518
    36
slime73@9532
    37
#if SDL_IPHONE_KEYBOARD
slime73@9532
    38
#include "keyinfotable.h"
slime73@9532
    39
#endif
kees@5640
    40
slime73@9532
    41
@implementation SDL_uikitviewcontroller {
slime73@9532
    42
    CADisplayLink *displayLink;
slime73@9532
    43
    int animationInterval;
slime73@9532
    44
    void (*animationCallback)(void*);
slime73@9532
    45
    void *animationCallbackParam;
slime73@9532
    46
slime73@9532
    47
#if SDL_IPHONE_KEYBOARD
slime73@9532
    48
    UITextField *textField;
slime73@9532
    49
#endif
slime73@9532
    50
}
kees@5640
    51
kees@6011
    52
@synthesize window;
kees@6011
    53
slime73@9532
    54
- (instancetype)initWithSDLWindow:(SDL_Window *)_window
kees@6003
    55
{
slime73@9505
    56
    if (self = [super initWithNibName:nil bundle:nil]) {
slime73@9505
    57
        self.window = _window;
slime73@9532
    58
slime73@9532
    59
#if SDL_IPHONE_KEYBOARD
slime73@9532
    60
        [self initKeyboard];
slime73@9532
    61
#endif
kees@5640
    62
    }
kees@5640
    63
    return self;
kees@5640
    64
}
kees@5640
    65
slime73@9532
    66
- (void)dealloc
slime73@9532
    67
{
slime73@9532
    68
#if SDL_IPHONE_KEYBOARD
slime73@9532
    69
    [self deinitKeyboard];
slime73@9532
    70
#endif
slime73@9532
    71
}
slime73@9532
    72
slime73@9532
    73
- (void)setAnimationCallback:(int)interval
slime73@9532
    74
                    callback:(void (*)(void*))callback
slime73@9532
    75
               callbackParam:(void*)callbackParam
slime73@9532
    76
{
slime73@9532
    77
    [self stopAnimation];
slime73@9532
    78
slime73@9532
    79
    animationInterval = interval;
slime73@9532
    80
    animationCallback = callback;
slime73@9532
    81
    animationCallbackParam = callbackParam;
slime73@9532
    82
slime73@9532
    83
    if (animationCallback) {
slime73@9532
    84
        [self startAnimation];
slime73@9532
    85
    }
slime73@9532
    86
}
slime73@9532
    87
slime73@9532
    88
- (void)startAnimation
slime73@9532
    89
{
slime73@9532
    90
    displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
slime73@9532
    91
    [displayLink setFrameInterval:animationInterval];
slime73@9532
    92
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
slime73@9532
    93
}
slime73@9532
    94
slime73@9532
    95
- (void)stopAnimation
slime73@9532
    96
{
slime73@9532
    97
    [displayLink invalidate];
slime73@9532
    98
    displayLink = nil;
slime73@9532
    99
}
slime73@9532
   100
slime73@9532
   101
- (void)doLoop:(CADisplayLink*)sender
slime73@9532
   102
{
slime73@9532
   103
    /* Don't run the game loop while a messagebox is up */
slime73@9532
   104
    if (!UIKit_ShowingMessageBox()) {
slime73@9532
   105
        animationCallback(animationCallbackParam);
slime73@9532
   106
    }
slime73@9532
   107
}
slime73@9532
   108
slouken@6440
   109
- (void)loadView
slouken@6440
   110
{
slime73@9532
   111
    /* Do nothing. */
slouken@6440
   112
}
slouken@6440
   113
slouken@6451
   114
- (void)viewDidLayoutSubviews
slouken@6440
   115
{
slime73@9505
   116
    const CGSize size = self.view.bounds.size;
slime73@9494
   117
    int w = (int) size.width;
slime73@9494
   118
    int h = (int) size.height;
slouken@7191
   119
slime73@9494
   120
    SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h);
slouken@6440
   121
}
slouken@6440
   122
slouken@6451
   123
- (NSUInteger)supportedInterfaceOrientations
kees@6003
   124
{
slime73@9523
   125
    return UIKit_GetSupportedOrientations(window);
kees@5640
   126
}
kees@5640
   127
slouken@6451
   128
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient
kees@6003
   129
{
slime73@9532
   130
    return ([self supportedInterfaceOrientations] & (1 << orient)) != 0;
kees@5640
   131
}
kees@5640
   132
slouken@7862
   133
- (BOOL)prefersStatusBarHidden
slouken@7862
   134
{
slime73@9529
   135
    return (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0;
slouken@7862
   136
}
slouken@7862
   137
slime73@9492
   138
- (UIStatusBarStyle)preferredStatusBarStyle
slime73@9492
   139
{
slime73@9523
   140
    /* We assume most SDL apps don't have a bright white background. */
slime73@9492
   141
    return UIStatusBarStyleLightContent;
slime73@9492
   142
}
slime73@9492
   143
slime73@9532
   144
/*
slime73@9532
   145
 ---- Keyboard related functionality below this line ----
slime73@9532
   146
 */
slime73@9532
   147
#if SDL_IPHONE_KEYBOARD
slime73@9532
   148
slime73@9532
   149
@synthesize textInputRect;
slime73@9532
   150
@synthesize keyboardHeight;
slime73@9532
   151
@synthesize keyboardVisible;
slime73@9532
   152
slime73@9532
   153
/* Set ourselves up as a UITextFieldDelegate */
slime73@9532
   154
- (void)initKeyboard
slime73@9532
   155
{
slime73@9532
   156
    textField = [[UITextField alloc] initWithFrame:CGRectZero];
slime73@9532
   157
    textField.delegate = self;
slime73@9532
   158
    /* placeholder so there is something to delete! */
slime73@9532
   159
    textField.text = @" ";
slime73@9532
   160
slime73@9532
   161
    /* set UITextInputTrait properties, mostly to defaults */
slime73@9532
   162
    textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
slime73@9532
   163
    textField.autocorrectionType = UITextAutocorrectionTypeNo;
slime73@9532
   164
    textField.enablesReturnKeyAutomatically = NO;
slime73@9532
   165
    textField.keyboardAppearance = UIKeyboardAppearanceDefault;
slime73@9532
   166
    textField.keyboardType = UIKeyboardTypeDefault;
slime73@9532
   167
    textField.returnKeyType = UIReturnKeyDefault;
slime73@9532
   168
    textField.secureTextEntry = NO;
slime73@9532
   169
slime73@9532
   170
    textField.hidden = YES;
slime73@9532
   171
    keyboardVisible = NO;
slime73@9532
   172
slime73@9532
   173
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
slime73@9532
   174
    [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
slime73@9532
   175
    [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
slime73@9532
   176
}
slime73@9532
   177
slime73@9532
   178
- (void)setView:(UIView *)view
slime73@9532
   179
{
slime73@9532
   180
    [super setView:view];
slime73@9532
   181
slime73@9532
   182
    [view addSubview:textField];
slime73@9532
   183
slime73@9532
   184
    if (keyboardVisible) {
slime73@9532
   185
        [self showKeyboard];
slime73@9532
   186
    }
slime73@9532
   187
}
slime73@9532
   188
slime73@9532
   189
- (void)deinitKeyboard
slime73@9532
   190
{
slime73@9532
   191
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
slime73@9532
   192
    [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
slime73@9532
   193
    [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
slime73@9532
   194
}
slime73@9532
   195
slime73@9532
   196
/* reveal onscreen virtual keyboard */
slime73@9532
   197
- (void)showKeyboard
slime73@9532
   198
{
slime73@9532
   199
    keyboardVisible = YES;
slime73@9532
   200
    if (textField.window) {
slime73@9532
   201
        [textField becomeFirstResponder];
slime73@9532
   202
    }
slime73@9532
   203
}
slime73@9532
   204
slime73@9532
   205
/* hide onscreen virtual keyboard */
slime73@9532
   206
- (void)hideKeyboard
slime73@9532
   207
{
slime73@9532
   208
    keyboardVisible = NO;
slime73@9532
   209
    [textField resignFirstResponder];
slime73@9532
   210
}
slime73@9532
   211
slime73@9532
   212
- (void)keyboardWillShow:(NSNotification *)notification
slime73@9532
   213
{
slime73@9532
   214
    CGRect kbrect = [[notification userInfo][UIKeyboardFrameBeginUserInfoKey] CGRectValue];
slime73@9532
   215
    UIView *view = self.view;
slime73@9532
   216
    int height = 0;
slime73@9532
   217
slime73@9532
   218
    /* The keyboard rect is in the coordinate space of the screen, but we want
slime73@9532
   219
     * its height in the view's coordinate space. */
slime73@9532
   220
#ifdef __IPHONE_8_0
slime73@9532
   221
    if ([view respondsToSelector:@selector(convertRect:fromCoordinateSpace:)]) {
slime73@9532
   222
        UIScreen *screen = view.window.screen;
slime73@9532
   223
        kbrect = [view convertRect:kbrect fromCoordinateSpace:screen.coordinateSpace];
slime73@9532
   224
        height = kbrect.size.height;
slime73@9532
   225
    } else
slime73@9532
   226
#endif
slime73@9532
   227
    {
slime73@9532
   228
        /* In iOS 7 and below, the screen's coordinate space is never rotated. */
slime73@9532
   229
        if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
slime73@9532
   230
            height = kbrect.size.width;
slime73@9532
   231
        } else {
slime73@9532
   232
            height = kbrect.size.height;
slime73@9532
   233
        }
slime73@9532
   234
    }
slime73@9532
   235
slime73@9532
   236
    [self setKeyboardHeight:height];
slime73@9532
   237
}
slime73@9532
   238
slime73@9532
   239
- (void)keyboardWillHide:(NSNotification *)notification
slime73@9532
   240
{
slime73@9532
   241
    [self setKeyboardHeight:0];
slime73@9532
   242
}
slime73@9532
   243
slime73@9532
   244
- (void)updateKeyboard
slime73@9532
   245
{
slime73@9532
   246
    SDL_Rect textrect = self.textInputRect;
slime73@9532
   247
    CGAffineTransform t = self.view.transform;
slime73@9532
   248
    CGPoint offset = CGPointMake(0.0, 0.0);
slime73@9532
   249
slime73@9532
   250
    if (self.keyboardHeight) {
slime73@9532
   251
        int rectbottom = textrect.y + textrect.h;
slime73@9532
   252
        int kbottom = self.view.bounds.size.height - self.keyboardHeight;
slime73@9532
   253
        if (kbottom < rectbottom) {
slime73@9532
   254
            offset.y = kbottom - rectbottom;
slime73@9532
   255
        }
slime73@9532
   256
    }
slime73@9532
   257
slime73@9532
   258
    /* Put the offset into the this view transform's coordinate space. */
slime73@9532
   259
    t.tx = 0.0;
slime73@9532
   260
    t.ty = 0.0;
slime73@9532
   261
    offset = CGPointApplyAffineTransform(offset, t);
slime73@9532
   262
slime73@9532
   263
    t.tx = offset.x;
slime73@9532
   264
    t.ty = offset.y;
slime73@9532
   265
slime73@9532
   266
    /* Move the view by applying the updated transform. */
slime73@9532
   267
    self.view.transform = t;
slime73@9532
   268
}
slime73@9532
   269
slime73@9532
   270
- (void)setKeyboardHeight:(int)height
slime73@9532
   271
{
slime73@9532
   272
    keyboardVisible = height > 0;
slime73@9532
   273
    keyboardHeight = height;
slime73@9532
   274
    [self updateKeyboard];
slime73@9532
   275
}
slime73@9532
   276
slime73@9532
   277
/* UITextFieldDelegate method.  Invoked when user types something. */
slime73@9532
   278
- (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
slime73@9532
   279
{
slime73@9532
   280
    NSUInteger len = string.length;
slime73@9532
   281
slime73@9532
   282
    if (len == 0) {
slime73@9532
   283
        /* it wants to replace text with nothing, ie a delete */
slime73@9532
   284
        SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_BACKSPACE);
slime73@9532
   285
        SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_BACKSPACE);
slime73@9532
   286
    } else {
slime73@9532
   287
        /* go through all the characters in the string we've been sent and
slime73@9532
   288
         * convert them to key presses */
slime73@9532
   289
        int i;
slime73@9532
   290
        for (i = 0; i < len; i++) {
slime73@9532
   291
            unichar c = [string characterAtIndex:i];
slime73@9532
   292
            Uint16 mod = 0;
slime73@9532
   293
            SDL_Scancode code;
slime73@9532
   294
slime73@9532
   295
            if (c < 127) {
slime73@9532
   296
                /* figure out the SDL_Scancode and SDL_keymod for this unichar */
slime73@9532
   297
                code = unicharToUIKeyInfoTable[c].code;
slime73@9532
   298
                mod  = unicharToUIKeyInfoTable[c].mod;
slime73@9532
   299
            } else {
slime73@9532
   300
                /* we only deal with ASCII right now */
slime73@9532
   301
                code = SDL_SCANCODE_UNKNOWN;
slime73@9532
   302
                mod = 0;
slime73@9532
   303
            }
slime73@9532
   304
slime73@9532
   305
            if (mod & KMOD_SHIFT) {
slime73@9532
   306
                /* If character uses shift, press shift down */
slime73@9532
   307
                SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT);
slime73@9532
   308
            }
slime73@9532
   309
slime73@9532
   310
            /* send a keydown and keyup even for the character */
slime73@9532
   311
            SDL_SendKeyboardKey(SDL_PRESSED, code);
slime73@9532
   312
            SDL_SendKeyboardKey(SDL_RELEASED, code);
slime73@9532
   313
slime73@9532
   314
            if (mod & KMOD_SHIFT) {
slime73@9532
   315
                /* If character uses shift, press shift back up */
slime73@9532
   316
                SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT);
slime73@9532
   317
            }
slime73@9532
   318
        }
slime73@9532
   319
slime73@9532
   320
        SDL_SendKeyboardText([string UTF8String]);
slime73@9532
   321
    }
slime73@9532
   322
slime73@9532
   323
    return NO; /* don't allow the edit! (keep placeholder text there) */
slime73@9532
   324
}
slime73@9532
   325
slime73@9532
   326
/* Terminates the editing session */
slime73@9532
   327
- (BOOL)textFieldShouldReturn:(UITextField*)_textField
slime73@9532
   328
{
slime73@9532
   329
    SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_RETURN);
slime73@9532
   330
    SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RETURN);
slime73@9532
   331
    SDL_StopTextInput();
slime73@9532
   332
    return YES;
slime73@9532
   333
}
slime73@9532
   334
slime73@9532
   335
#endif
slime73@9532
   336
slouken@6451
   337
@end
slouken@6451
   338
slime73@9532
   339
/* iPhone keyboard addition functions */
slime73@9532
   340
#if SDL_IPHONE_KEYBOARD
slime73@9532
   341
slime73@9532
   342
static SDL_uikitviewcontroller *
slime73@9532
   343
GetWindowViewController(SDL_Window * window)
slime73@9532
   344
{
slime73@9532
   345
    if (!window || !window->driverdata) {
slime73@9532
   346
        SDL_SetError("Invalid window");
slime73@9532
   347
        return nil;
slime73@9532
   348
    }
slime73@9532
   349
slime73@9532
   350
    SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
slime73@9532
   351
slime73@9532
   352
    return data.viewcontroller;
slime73@9532
   353
}
slime73@9532
   354
slime73@9532
   355
SDL_bool
slime73@9532
   356
UIKit_HasScreenKeyboardSupport(_THIS)
slime73@9532
   357
{
slime73@9532
   358
    return SDL_TRUE;
slime73@9532
   359
}
slime73@9532
   360
slime73@9532
   361
void
slime73@9532
   362
UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window)
slime73@9532
   363
{
slime73@9532
   364
    @autoreleasepool {
slime73@9532
   365
        SDL_uikitviewcontroller *vc = GetWindowViewController(window);
slime73@9532
   366
        [vc showKeyboard];
slime73@9532
   367
    }
slime73@9532
   368
}
slime73@9532
   369
slime73@9532
   370
void
slime73@9532
   371
UIKit_HideScreenKeyboard(_THIS, SDL_Window *window)
slime73@9532
   372
{
slime73@9532
   373
    @autoreleasepool {
slime73@9532
   374
        SDL_uikitviewcontroller *vc = GetWindowViewController(window);
slime73@9532
   375
        [vc hideKeyboard];
slime73@9532
   376
    }
slime73@9532
   377
}
slime73@9532
   378
slime73@9532
   379
SDL_bool
slime73@9532
   380
UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window)
slime73@9532
   381
{
slime73@9532
   382
    @autoreleasepool {
slime73@9532
   383
        SDL_uikitviewcontroller *vc = GetWindowViewController(window);
slime73@9532
   384
        if (vc != nil) {
slime73@9532
   385
            return vc.isKeyboardVisible;
slime73@9532
   386
        }
slime73@9532
   387
        return SDL_FALSE;
slime73@9532
   388
    }
slime73@9532
   389
}
slime73@9532
   390
slime73@9532
   391
void
slime73@9532
   392
UIKit_SetTextInputRect(_THIS, SDL_Rect *rect)
slime73@9532
   393
{
slime73@9532
   394
    if (!rect) {
slime73@9532
   395
        SDL_InvalidParamError("rect");
slime73@9532
   396
        return;
slime73@9532
   397
    }
slime73@9532
   398
slime73@9532
   399
    @autoreleasepool {
slime73@9532
   400
        SDL_uikitviewcontroller *vc = GetWindowViewController(SDL_GetFocusWindow());
slime73@9532
   401
        if (vc != nil) {
slime73@9532
   402
            vc.textInputRect = *rect;
slime73@9532
   403
slime73@9532
   404
            if (vc.keyboardVisible) {
slime73@9532
   405
                [vc updateKeyboard];
slime73@9532
   406
            }
slime73@9532
   407
        }
slime73@9532
   408
    }
slime73@9532
   409
}
slime73@9532
   410
slime73@9532
   411
slime73@9532
   412
#endif /* SDL_IPHONE_KEYBOARD */
slime73@9532
   413
slouken@6044
   414
#endif /* SDL_VIDEO_DRIVER_UIKIT */
slouken@6044
   415
slouken@6440
   416
/* vi: set ts=4 sw=4 expandtab: */