src/video/uikit/SDL_uikitwindow.m
author Alex Szpakowski <slime73@gmail.com>
Wed, 08 Apr 2015 15:35:07 -0300
branchiOS-improvements
changeset 9537 ff30c198864e
parent 9532 318042c16b76
child 9540 32ddc92d78cf
permissions -rw-r--r--
Merged default into iOS-improvements
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2014 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_syswm.h"
    26 #include "SDL_video.h"
    27 #include "SDL_mouse.h"
    28 #include "SDL_assert.h"
    29 #include "SDL_hints.h"
    30 #include "../SDL_sysvideo.h"
    31 #include "../SDL_pixels_c.h"
    32 #include "../../events/SDL_events_c.h"
    33 
    34 #include "SDL_uikitvideo.h"
    35 #include "SDL_uikitevents.h"
    36 #include "SDL_uikitmodes.h"
    37 #include "SDL_uikitwindow.h"
    38 #import "SDL_uikitappdelegate.h"
    39 
    40 #import "SDL_uikitview.h"
    41 
    42 #include <Foundation/Foundation.h>
    43 
    44 @implementation SDL_WindowData
    45 
    46 @synthesize uiwindow;
    47 @synthesize viewcontroller;
    48 @synthesize views;
    49 
    50 - (instancetype)init
    51 {
    52     if ((self = [super init])) {
    53         views = [NSMutableArray new];
    54     }
    55 
    56     return self;
    57 }
    58 
    59 @end
    60 
    61 @interface SDL_uikitwindow : UIWindow
    62 
    63 - (void)layoutSubviews;
    64 
    65 @end
    66 
    67 @implementation SDL_uikitwindow
    68 
    69 - (void)layoutSubviews
    70 {
    71     /* Workaround to fix window orientation issues in iOS 8+. */
    72     self.frame = self.screen.bounds;
    73     [super layoutSubviews];
    74 }
    75 
    76 @end
    77 
    78 
    79 static int SetupWindowData(_THIS, SDL_Window *window, UIWindow *uiwindow, SDL_bool created)
    80 {
    81     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
    82     SDL_DisplayData *displaydata = (__bridge SDL_DisplayData *) display->driverdata;
    83     SDL_uikitview *view;
    84 
    85     CGRect frame = UIKit_ComputeViewFrame(window, displaydata.uiscreen);
    86     int width  = (int) frame.size.width;
    87     int height = (int) frame.size.height;
    88 
    89     SDL_WindowData *data = [[SDL_WindowData alloc] init];
    90     if (!data) {
    91         return SDL_OutOfMemory();
    92     }
    93 
    94     window->driverdata = (void *) CFBridgingRetain(data);
    95 
    96     data.uiwindow = uiwindow;
    97 
    98     /* only one window on iOS, always shown */
    99     window->flags &= ~SDL_WINDOW_HIDDEN;
   100 
   101     if (displaydata.uiscreen == [UIScreen mainScreen]) {
   102         window->flags |= SDL_WINDOW_INPUT_FOCUS;  /* always has input focus */
   103     } else {
   104         window->flags &= ~SDL_WINDOW_RESIZABLE;  /* window is NEVER resizable */
   105         window->flags &= ~SDL_WINDOW_INPUT_FOCUS;  /* never has input focus */
   106         window->flags |= SDL_WINDOW_BORDERLESS;  /* never has a status bar. */
   107     }
   108 
   109     if (displaydata.uiscreen == [UIScreen mainScreen]) {
   110         NSUInteger orients = UIKit_GetSupportedOrientations(window);
   111         BOOL supportsLandscape = (orients & UIInterfaceOrientationMaskLandscape) != 0;
   112         BOOL supportsPortrait = (orients & (UIInterfaceOrientationMaskPortrait|UIInterfaceOrientationMaskPortraitUpsideDown)) != 0;
   113 
   114         /* Make sure the width/height are oriented correctly */
   115         if ((width > height && !supportsLandscape) || (height > width && !supportsPortrait)) {
   116             int temp = width;
   117             width = height;
   118             height = temp;
   119         }
   120     }
   121 
   122     window->x = 0;
   123     window->y = 0;
   124     window->w = width;
   125     window->h = height;
   126 
   127     /* The View Controller will handle rotating the view when the device
   128      * orientation changes. This will trigger resize events, if appropriate. */
   129     data.viewcontroller = [[SDL_uikitviewcontroller alloc] initWithSDLWindow:window];
   130 
   131     /* The window will initially contain a generic view so resizes, touch events,
   132      * etc. can be handled without an active OpenGL view/context. */
   133     view = [[SDL_uikitview alloc] initWithFrame:frame];
   134 
   135     /* Sets this view as the controller's view, and adds the view to the window
   136      * heirarchy. */
   137     [view setSDLWindow:window];
   138 
   139     /* Make this window the current mouse focus for touch input */
   140     if (displaydata.uiscreen == [UIScreen mainScreen]) {
   141         SDL_SetMouseFocus(window);
   142         SDL_SetKeyboardFocus(window);
   143     }
   144 
   145     return 0;
   146 }
   147 
   148 int
   149 UIKit_CreateWindow(_THIS, SDL_Window *window)
   150 {
   151     @autoreleasepool {
   152         SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
   153         SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
   154         const CGSize origsize = data.uiscreen.currentMode.size;
   155 
   156         /* SDL currently puts this window at the start of display's linked list. We rely on this. */
   157         SDL_assert(_this->windows == window);
   158 
   159         /* We currently only handle a single window per display on iOS */
   160         if (window->next != NULL) {
   161             return SDL_SetError("Only one window allowed per display.");
   162         }
   163 
   164         /* If monitor has a resolution of 0x0 (hasn't been explicitly set by the
   165          * user, so it's in standby), try to force the display to a resolution
   166          * that most closely matches the desired window size. */
   167         if ((origsize.width == 0.0f) && (origsize.height == 0.0f)) {
   168             if (display->num_display_modes == 0) {
   169                 _this->GetDisplayModes(_this, display);
   170             }
   171 
   172             int i;
   173             const SDL_DisplayMode *bestmode = NULL;
   174             for (i = display->num_display_modes; i >= 0; i--) {
   175                 const SDL_DisplayMode *mode = &display->display_modes[i];
   176                 if ((mode->w >= window->w) && (mode->h >= window->h)) {
   177                     bestmode = mode;
   178                 }
   179             }
   180 
   181             if (bestmode) {
   182                 SDL_DisplayModeData *modedata = (__bridge SDL_DisplayModeData *)bestmode->driverdata;
   183                 [data.uiscreen setCurrentMode:modedata.uiscreenmode];
   184 
   185                 /* desktop_mode doesn't change here (the higher level will
   186                  * use it to set all the screens back to their defaults
   187                  * upon window destruction, SDL_Quit(), etc. */
   188                 display->current_mode = *bestmode;
   189             }
   190         }
   191 
   192         if (data.uiscreen == [UIScreen mainScreen]) {
   193             NSUInteger orientations = UIKit_GetSupportedOrientations(window);
   194             UIApplication *app = [UIApplication sharedApplication];
   195 
   196             if (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) {
   197                 app.statusBarHidden = YES;
   198             } else {
   199                 app.statusBarHidden = NO;
   200             }
   201         }
   202 
   203         /* ignore the size user requested, and make a fullscreen window */
   204         /* !!! FIXME: can we have a smaller view? */
   205         UIWindow *uiwindow = [[SDL_uikitwindow alloc] initWithFrame:data.uiscreen.bounds];
   206 
   207         /* put the window on an external display if appropriate. */
   208         if (data.uiscreen != [UIScreen mainScreen]) {
   209             [uiwindow setScreen:data.uiscreen];
   210         }
   211 
   212         if (SetupWindowData(_this, window, uiwindow, SDL_TRUE) < 0) {
   213             return -1;
   214         }
   215     }
   216 
   217     return 1;
   218 }
   219 
   220 void
   221 UIKit_SetWindowTitle(_THIS, SDL_Window * window)
   222 {
   223     @autoreleasepool {
   224         SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
   225         data.viewcontroller.title = @(window->title);
   226     }
   227 }
   228 
   229 void
   230 UIKit_ShowWindow(_THIS, SDL_Window * window)
   231 {
   232     @autoreleasepool {
   233         SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
   234         [data.uiwindow makeKeyAndVisible];
   235     }
   236 }
   237 
   238 void
   239 UIKit_HideWindow(_THIS, SDL_Window * window)
   240 {
   241     @autoreleasepool {
   242         SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
   243         data.uiwindow.hidden = YES;
   244     }
   245 }
   246 
   247 void
   248 UIKit_RaiseWindow(_THIS, SDL_Window * window)
   249 {
   250     /* We don't currently offer a concept of "raising" the SDL window, since
   251      * we only allow one per display, in the iOS fashion.
   252      * However, we use this entry point to rebind the context to the view
   253      * during OnWindowRestored processing. */
   254     _this->GL_MakeCurrent(_this, _this->current_glwin, _this->current_glctx);
   255 }
   256 
   257 static void
   258 UIKit_UpdateWindowBorder(_THIS, SDL_Window * window)
   259 {
   260     SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
   261     SDL_uikitviewcontroller *viewcontroller = data.viewcontroller;
   262 
   263     if (data.uiwindow.screen == [UIScreen mainScreen]) {
   264         if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) {
   265             [UIApplication sharedApplication].statusBarHidden = YES;
   266         } else {
   267             [UIApplication sharedApplication].statusBarHidden = NO;
   268         }
   269 
   270         /* iOS 7+ won't update the status bar until we tell it to. */
   271         if ([viewcontroller respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) {
   272             [viewcontroller setNeedsStatusBarAppearanceUpdate];
   273         }
   274     }
   275 
   276     /* Update the view's frame to account for the status bar change. */
   277     viewcontroller.view.frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen);
   278     [viewcontroller.view setNeedsLayout];
   279     [viewcontroller.view layoutIfNeeded];
   280 }
   281 
   282 void
   283 UIKit_SetWindowBordered(_THIS, SDL_Window * window, SDL_bool bordered)
   284 {
   285     @autoreleasepool {
   286         UIKit_UpdateWindowBorder(_this, window);
   287     }
   288 }
   289 
   290 void
   291 UIKit_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
   292 {
   293     @autoreleasepool {
   294         UIKit_UpdateWindowBorder(_this, window);
   295     }
   296 }
   297 
   298 void
   299 UIKit_DestroyWindow(_THIS, SDL_Window * window)
   300 {
   301     @autoreleasepool {
   302         if (window->driverdata != NULL) {
   303             SDL_WindowData *data = (SDL_WindowData *) CFBridgingRelease(window->driverdata);
   304             [data.viewcontroller stopAnimation];
   305         }
   306     }
   307     window->driverdata = NULL;
   308 }
   309 
   310 SDL_bool
   311 UIKit_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
   312 {
   313     @autoreleasepool {
   314         SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
   315 
   316         if (info->version.major <= SDL_MAJOR_VERSION) {
   317             info->subsystem = SDL_SYSWM_UIKIT;
   318             info->info.uikit.window = data.uiwindow;
   319             return SDL_TRUE;
   320         } else {
   321             SDL_SetError("Application not compiled with SDL %d.%d\n",
   322                          SDL_MAJOR_VERSION, SDL_MINOR_VERSION);
   323             return SDL_FALSE;
   324         }
   325     }
   326 }
   327 
   328 NSUInteger
   329 UIKit_GetSupportedOrientations(SDL_Window * window)
   330 {
   331     const char *hint = SDL_GetHint(SDL_HINT_ORIENTATIONS);
   332     NSUInteger orientationMask = 0;
   333 
   334     @autoreleasepool {
   335         if (hint != NULL) {
   336             NSArray *orientations = [@(hint) componentsSeparatedByString:@" "];
   337 
   338             if ([orientations containsObject:@"LandscapeLeft"]) {
   339                 orientationMask |= UIInterfaceOrientationMaskLandscapeLeft;
   340             }
   341             if ([orientations containsObject:@"LandscapeRight"]) {
   342                 orientationMask |= UIInterfaceOrientationMaskLandscapeRight;
   343             }
   344             if ([orientations containsObject:@"Portrait"]) {
   345                 orientationMask |= UIInterfaceOrientationMaskPortrait;
   346             }
   347             if ([orientations containsObject:@"PortraitUpsideDown"]) {
   348                 orientationMask |= UIInterfaceOrientationMaskPortraitUpsideDown;
   349             }
   350         }
   351 
   352         if (orientationMask == 0 && (window->flags & SDL_WINDOW_RESIZABLE)) {
   353             /* any orientation is okay. */
   354             orientationMask = UIInterfaceOrientationMaskAll;
   355         }
   356 
   357         if (orientationMask == 0) {
   358             if (window->w >= window->h) {
   359                 orientationMask |= UIInterfaceOrientationMaskLandscape;
   360             }
   361             if (window->h >= window->w) {
   362                 orientationMask |= (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
   363             }
   364         }
   365 
   366         /* Don't allow upside-down orientation on the phone, so answering calls is in the natural orientation */
   367         if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
   368             orientationMask &= ~UIInterfaceOrientationMaskPortraitUpsideDown;
   369         }
   370     }
   371 
   372     return orientationMask;
   373 }
   374 
   375 int
   376 SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam)
   377 {
   378     if (!window || !window->driverdata) {
   379         return SDL_SetError("Invalid window");
   380     }
   381 
   382     @autoreleasepool {
   383         SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
   384         [data.viewcontroller setAnimationCallback:interval
   385                                          callback:callback
   386                                     callbackParam:callbackParam];
   387     }
   388 
   389     return 0;
   390 }
   391 
   392 #endif /* SDL_VIDEO_DRIVER_UIKIT */
   393 
   394 /* vi: set ts=4 sw=4 expandtab: */