src/video/uikit/SDL_uikitwindow.m
author Sam Lantinga
Fri, 27 Jan 2017 06:05:50 -0800
changeset 10856 486aa38c6a88
parent 10737 3406a0f8b041
child 10945 1300a3135d61
permissions -rw-r--r--
Added Thrustmaster Wheel FFB entry to the list of wheels
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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 #import "SDL_uikitopenglview.h"
    42 
    43 #include <Foundation/Foundation.h>
    44 
    45 @implementation SDL_WindowData
    46 
    47 @synthesize uiwindow;
    48 @synthesize viewcontroller;
    49 @synthesize views;
    50 
    51 - (instancetype)init
    52 {
    53     if ((self = [super init])) {
    54         views = [NSMutableArray new];
    55     }
    56 
    57     return self;
    58 }
    59 
    60 @end
    61 
    62 @interface SDL_uikitwindow : UIWindow
    63 
    64 - (void)layoutSubviews;
    65 
    66 @end
    67 
    68 @implementation SDL_uikitwindow
    69 
    70 - (void)layoutSubviews
    71 {
    72     /* Workaround to fix window orientation issues in iOS 8+. */
    73     self.frame = self.screen.bounds;
    74     [super layoutSubviews];
    75 }
    76 
    77 @end
    78 
    79 
    80 static int SetupWindowData(_THIS, SDL_Window *window, UIWindow *uiwindow, SDL_bool created)
    81 {
    82     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
    83     SDL_DisplayData *displaydata = (__bridge SDL_DisplayData *) display->driverdata;
    84     SDL_uikitview *view;
    85 
    86     CGRect frame = UIKit_ComputeViewFrame(window, displaydata.uiscreen);
    87     int width  = (int) frame.size.width;
    88     int height = (int) frame.size.height;
    89 
    90     SDL_WindowData *data = [[SDL_WindowData alloc] init];
    91     if (!data) {
    92         return SDL_OutOfMemory();
    93     }
    94 
    95     window->driverdata = (void *) CFBridgingRetain(data);
    96 
    97     data.uiwindow = uiwindow;
    98 
    99     /* only one window on iOS, always shown */
   100     window->flags &= ~SDL_WINDOW_HIDDEN;
   101 
   102     if (displaydata.uiscreen != [UIScreen mainScreen]) {
   103         window->flags &= ~SDL_WINDOW_RESIZABLE;  /* window is NEVER resizable */
   104         window->flags &= ~SDL_WINDOW_INPUT_FOCUS;  /* never has input focus */
   105         window->flags |= SDL_WINDOW_BORDERLESS;  /* never has a status bar. */
   106     }
   107 
   108 #if !TARGET_OS_TV
   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 #endif /* !TARGET_OS_TV */
   122 
   123     window->x = 0;
   124     window->y = 0;
   125     window->w = width;
   126     window->h = height;
   127 
   128     /* The View Controller will handle rotating the view when the device
   129      * orientation changes. This will trigger resize events, if appropriate. */
   130     data.viewcontroller = [[SDL_uikitviewcontroller alloc] initWithSDLWindow:window];
   131 
   132     /* The window will initially contain a generic view so resizes, touch events,
   133      * etc. can be handled without an active OpenGL view/context. */
   134     view = [[SDL_uikitview alloc] initWithFrame:frame];
   135 
   136     /* Sets this view as the controller's view, and adds the view to the window
   137      * heirarchy. */
   138     [view setSDLWindow:window];
   139 
   140     /* Make this window the current mouse focus for touch input */
   141     if (displaydata.uiscreen == [UIScreen mainScreen]) {
   142         SDL_SetMouseFocus(window);
   143         SDL_SetKeyboardFocus(window);
   144     }
   145 
   146     return 0;
   147 }
   148 
   149 int
   150 UIKit_CreateWindow(_THIS, SDL_Window *window)
   151 {
   152     @autoreleasepool {
   153         SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
   154         SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
   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 !TARGET_OS_TV
   168         const CGSize origsize = data.uiscreen.currentMode.size;
   169         if ((origsize.width == 0.0f) && (origsize.height == 0.0f)) {
   170             if (display->num_display_modes == 0) {
   171                 _this->GetDisplayModes(_this, display);
   172             }
   173 
   174             int i;
   175             const SDL_DisplayMode *bestmode = NULL;
   176             for (i = display->num_display_modes; i >= 0; i--) {
   177                 const SDL_DisplayMode *mode = &display->display_modes[i];
   178                 if ((mode->w >= window->w) && (mode->h >= window->h)) {
   179                     bestmode = mode;
   180                 }
   181             }
   182 
   183             if (bestmode) {
   184                 SDL_DisplayModeData *modedata = (__bridge SDL_DisplayModeData *)bestmode->driverdata;
   185                 [data.uiscreen setCurrentMode:modedata.uiscreenmode];
   186 
   187                 /* desktop_mode doesn't change here (the higher level will
   188                  * use it to set all the screens back to their defaults
   189                  * upon window destruction, SDL_Quit(), etc. */
   190                 display->current_mode = *bestmode;
   191             }
   192         }
   193 
   194         if (data.uiscreen == [UIScreen mainScreen]) {
   195             if (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) {
   196                 [UIApplication sharedApplication].statusBarHidden = YES;
   197             } else {
   198                 [UIApplication sharedApplication].statusBarHidden = NO;
   199             }
   200         }
   201 #endif /* !TARGET_OS_TV */
   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 !TARGET_OS_TV
   264     if (data.uiwindow.screen == [UIScreen mainScreen]) {
   265         if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) {
   266             [UIApplication sharedApplication].statusBarHidden = YES;
   267         } else {
   268             [UIApplication sharedApplication].statusBarHidden = NO;
   269         }
   270 
   271         /* iOS 7+ won't update the status bar until we tell it to. */
   272         if ([viewcontroller respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) {
   273             [viewcontroller setNeedsStatusBarAppearanceUpdate];
   274         }
   275     }
   276 
   277     /* Update the view's frame to account for the status bar change. */
   278     viewcontroller.view.frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen);
   279 #endif /* !TARGET_OS_TV */
   280 
   281 #ifdef SDL_IPHONE_KEYBOARD
   282     /* Make sure the view is offset correctly when the keyboard is visible. */
   283     [viewcontroller updateKeyboard];
   284 #endif
   285 
   286     [viewcontroller.view setNeedsLayout];
   287     [viewcontroller.view layoutIfNeeded];
   288 }
   289 
   290 void
   291 UIKit_SetWindowBordered(_THIS, SDL_Window * window, SDL_bool bordered)
   292 {
   293     @autoreleasepool {
   294         UIKit_UpdateWindowBorder(_this, window);
   295     }
   296 }
   297 
   298 void
   299 UIKit_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
   300 {
   301     @autoreleasepool {
   302         UIKit_UpdateWindowBorder(_this, window);
   303     }
   304 }
   305 
   306 void
   307 UIKit_DestroyWindow(_THIS, SDL_Window * window)
   308 {
   309     @autoreleasepool {
   310         if (window->driverdata != NULL) {
   311             SDL_WindowData *data = (SDL_WindowData *) CFBridgingRelease(window->driverdata);
   312             NSArray *views = nil;
   313 
   314             [data.viewcontroller stopAnimation];
   315 
   316             /* Detach all views from this window. We use a copy of the array
   317              * because setSDLWindow will remove the object from the original
   318              * array, which would be undesirable if we were iterating over it. */
   319             views = [data.views copy];
   320             for (SDL_uikitview *view in views) {
   321                 [view setSDLWindow:NULL];
   322             }
   323 
   324             /* iOS may still hold a reference to the window after we release it.
   325              * We want to make sure the SDL view controller isn't accessed in
   326              * that case, because it would contain an invalid pointer to the old
   327              * SDL window. */
   328             data.uiwindow.rootViewController = nil;
   329             data.uiwindow.hidden = YES;
   330         }
   331     }
   332     window->driverdata = NULL;
   333 }
   334 
   335 SDL_bool
   336 UIKit_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
   337 {
   338     @autoreleasepool {
   339         SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
   340 
   341         if (info->version.major <= SDL_MAJOR_VERSION) {
   342             int versionnum = SDL_VERSIONNUM(info->version.major, info->version.minor, info->version.patch);
   343 
   344             info->subsystem = SDL_SYSWM_UIKIT;
   345             info->info.uikit.window = data.uiwindow;
   346 
   347             /* These struct members were added in SDL 2.0.4. */
   348             if (versionnum >= SDL_VERSIONNUM(2,0,4)) {
   349                 if ([data.viewcontroller.view isKindOfClass:[SDL_uikitopenglview class]]) {
   350                     SDL_uikitopenglview *glview = (SDL_uikitopenglview *)data.viewcontroller.view;
   351                     info->info.uikit.framebuffer = glview.drawableFramebuffer;
   352                     info->info.uikit.colorbuffer = glview.drawableRenderbuffer;
   353                     info->info.uikit.resolveFramebuffer = glview.msaaResolveFramebuffer;
   354                 } else {
   355                     info->info.uikit.framebuffer = 0;
   356                     info->info.uikit.colorbuffer = 0;
   357                     info->info.uikit.resolveFramebuffer = 0;
   358                 }
   359             }
   360 
   361             return SDL_TRUE;
   362         } else {
   363             SDL_SetError("Application not compiled with SDL %d.%d\n",
   364                          SDL_MAJOR_VERSION, SDL_MINOR_VERSION);
   365             return SDL_FALSE;
   366         }
   367     }
   368 }
   369 
   370 #if !TARGET_OS_TV
   371 NSUInteger
   372 UIKit_GetSupportedOrientations(SDL_Window * window)
   373 {
   374     const char *hint = SDL_GetHint(SDL_HINT_ORIENTATIONS);
   375     NSUInteger validOrientations = UIInterfaceOrientationMaskAll;
   376     NSUInteger orientationMask = 0;
   377 
   378     @autoreleasepool {
   379         SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
   380         UIApplication *app = [UIApplication sharedApplication];
   381 
   382         /* Get all possible valid orientations. If the app delegate doesn't tell
   383          * us, we get the orientations from Info.plist via UIApplication. */
   384         if ([app.delegate respondsToSelector:@selector(application:supportedInterfaceOrientationsForWindow:)]) {
   385             validOrientations = [app.delegate application:app supportedInterfaceOrientationsForWindow:data.uiwindow];
   386         } else if ([app respondsToSelector:@selector(supportedInterfaceOrientationsForWindow:)]) {
   387             validOrientations = [app supportedInterfaceOrientationsForWindow:data.uiwindow];
   388         }
   389 
   390         if (hint != NULL) {
   391             NSArray *orientations = [@(hint) componentsSeparatedByString:@" "];
   392 
   393             if ([orientations containsObject:@"LandscapeLeft"]) {
   394                 orientationMask |= UIInterfaceOrientationMaskLandscapeLeft;
   395             }
   396             if ([orientations containsObject:@"LandscapeRight"]) {
   397                 orientationMask |= UIInterfaceOrientationMaskLandscapeRight;
   398             }
   399             if ([orientations containsObject:@"Portrait"]) {
   400                 orientationMask |= UIInterfaceOrientationMaskPortrait;
   401             }
   402             if ([orientations containsObject:@"PortraitUpsideDown"]) {
   403                 orientationMask |= UIInterfaceOrientationMaskPortraitUpsideDown;
   404             }
   405         }
   406 
   407         if (orientationMask == 0 && (window->flags & SDL_WINDOW_RESIZABLE)) {
   408             /* any orientation is okay. */
   409             orientationMask = UIInterfaceOrientationMaskAll;
   410         }
   411 
   412         if (orientationMask == 0) {
   413             if (window->w >= window->h) {
   414                 orientationMask |= UIInterfaceOrientationMaskLandscape;
   415             }
   416             if (window->h >= window->w) {
   417                 orientationMask |= (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
   418             }
   419         }
   420 
   421         /* Don't allow upside-down orientation on phones, so answering calls is in the natural orientation */
   422         if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
   423             orientationMask &= ~UIInterfaceOrientationMaskPortraitUpsideDown;
   424         }
   425 
   426         /* If none of the specified orientations are actually supported by the
   427          * app, we'll revert to what the app supports. An exception would be
   428          * thrown by the system otherwise. */
   429         if ((validOrientations & orientationMask) == 0) {
   430             orientationMask = validOrientations;
   431         }
   432     }
   433 
   434     return orientationMask;
   435 }
   436 #endif /* !TARGET_OS_TV */
   437 
   438 int
   439 SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam)
   440 {
   441     if (!window || !window->driverdata) {
   442         return SDL_SetError("Invalid window");
   443     }
   444 
   445     @autoreleasepool {
   446         SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
   447         [data.viewcontroller setAnimationCallback:interval
   448                                          callback:callback
   449                                     callbackParam:callbackParam];
   450     }
   451 
   452     return 0;
   453 }
   454 
   455 #endif /* SDL_VIDEO_DRIVER_UIKIT */
   456 
   457 /* vi: set ts=4 sw=4 expandtab: */