src/video/uikit/SDL_uikitmodes.m
author Sam Lantinga <slouken@libsdl.org>
Mon, 10 Sep 2018 23:00:09 -0700
changeset 12183 364f514f94d8
parent 12144 c48e1ae67968
child 12201 8bdc4d340419
permissions -rw-r--r--
Fixed building on tvOS
     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_assert.h"
    26 #include "SDL_uikitmodes.h"
    27 
    28 #include "../../events/SDL_events_c.h"
    29 
    30 @implementation SDL_DisplayData
    31 
    32 @synthesize uiscreen;
    33 
    34 @end
    35 
    36 @implementation SDL_DisplayModeData
    37 
    38 @synthesize uiscreenmode;
    39 
    40 @end
    41 
    42 
    43 static int
    44 UIKit_AllocateDisplayModeData(SDL_DisplayMode * mode,
    45     UIScreenMode * uiscreenmode)
    46 {
    47     SDL_DisplayModeData *data = nil;
    48 
    49     if (uiscreenmode != nil) {
    50         /* Allocate the display mode data */
    51         data = [[SDL_DisplayModeData alloc] init];
    52         if (!data) {
    53             return SDL_OutOfMemory();
    54         }
    55 
    56         data.uiscreenmode = uiscreenmode;
    57     }
    58 
    59     mode->driverdata = (void *) CFBridgingRetain(data);
    60 
    61     return 0;
    62 }
    63 
    64 static void
    65 UIKit_FreeDisplayModeData(SDL_DisplayMode * mode)
    66 {
    67     if (mode->driverdata != NULL) {
    68         CFRelease(mode->driverdata);
    69         mode->driverdata = NULL;
    70     }
    71 }
    72 
    73 static NSUInteger
    74 UIKit_GetDisplayModeRefreshRate(UIScreen *uiscreen)
    75 {
    76 #ifdef __IPHONE_10_3
    77     if ([uiscreen respondsToSelector:@selector(maximumFramesPerSecond)]) {
    78         return uiscreen.maximumFramesPerSecond;
    79     }
    80 #endif
    81     return 0;
    82 }
    83 
    84 static int
    85 UIKit_AddSingleDisplayMode(SDL_VideoDisplay * display, int w, int h,
    86     UIScreen * uiscreen, UIScreenMode * uiscreenmode)
    87 {
    88     SDL_DisplayMode mode;
    89     SDL_zero(mode);
    90 
    91     if (UIKit_AllocateDisplayModeData(&mode, uiscreenmode) < 0) {
    92         return -1;
    93     }
    94 
    95     mode.format = SDL_PIXELFORMAT_ABGR8888;
    96     mode.refresh_rate = (int) UIKit_GetDisplayModeRefreshRate(uiscreen);
    97     mode.w = w;
    98     mode.h = h;
    99 
   100     if (SDL_AddDisplayMode(display, &mode)) {
   101         return 0;
   102     } else {
   103         UIKit_FreeDisplayModeData(&mode);
   104         return -1;
   105     }
   106 }
   107 
   108 static int
   109 UIKit_AddDisplayMode(SDL_VideoDisplay * display, int w, int h, UIScreen * uiscreen,
   110                      UIScreenMode * uiscreenmode, SDL_bool addRotation)
   111 {
   112     if (UIKit_AddSingleDisplayMode(display, w, h, uiscreen, uiscreenmode) < 0) {
   113         return -1;
   114     }
   115 
   116     if (addRotation) {
   117         /* Add the rotated version */
   118         if (UIKit_AddSingleDisplayMode(display, h, w, uiscreen, uiscreenmode) < 0) {
   119             return -1;
   120         }
   121     }
   122 
   123     return 0;
   124 }
   125 
   126 static int
   127 UIKit_AddDisplay(UIScreen *uiscreen)
   128 {
   129     UIScreenMode *uiscreenmode = uiscreen.currentMode;
   130     CGSize size = uiscreen.bounds.size;
   131     SDL_VideoDisplay display;
   132     SDL_DisplayMode mode;
   133     SDL_zero(mode);
   134 
   135     /* Make sure the width/height are oriented correctly */
   136     if (UIKit_IsDisplayLandscape(uiscreen) != (size.width > size.height)) {
   137         CGFloat height = size.width;
   138         size.width = size.height;
   139         size.height = height;
   140     }
   141 
   142     mode.format = SDL_PIXELFORMAT_ABGR8888;
   143     mode.refresh_rate = (int) UIKit_GetDisplayModeRefreshRate(uiscreen);
   144     mode.w = (int) size.width;
   145     mode.h = (int) size.height;
   146 
   147     if (UIKit_AllocateDisplayModeData(&mode, uiscreenmode) < 0) {
   148         return -1;
   149     }
   150 
   151     SDL_zero(display);
   152     display.desktop_mode = mode;
   153     display.current_mode = mode;
   154 
   155     /* Allocate the display data */
   156     SDL_DisplayData *data = [[SDL_DisplayData alloc] init];
   157     if (!data) {
   158         UIKit_FreeDisplayModeData(&display.desktop_mode);
   159         return SDL_OutOfMemory();
   160     }
   161 
   162     data.uiscreen = uiscreen;
   163 
   164     display.driverdata = (void *) CFBridgingRetain(data);
   165     SDL_AddVideoDisplay(&display);
   166 
   167     return 0;
   168 }
   169 
   170 SDL_bool
   171 UIKit_IsDisplayLandscape(UIScreen *uiscreen)
   172 {
   173 #if !TARGET_OS_TV
   174     if (uiscreen == [UIScreen mainScreen]) {
   175         return UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation);
   176     } else
   177 #endif /* !TARGET_OS_TV */
   178     {
   179         CGSize size = uiscreen.bounds.size;
   180         return (size.width > size.height);
   181     }
   182 }
   183 
   184 int
   185 UIKit_InitModes(_THIS)
   186 {
   187     @autoreleasepool {
   188         for (UIScreen *uiscreen in [UIScreen screens]) {
   189             if (UIKit_AddDisplay(uiscreen) < 0) {
   190                 return -1;
   191             }
   192         }
   193 #if !TARGET_OS_TV
   194 		SDL_OnApplicationDidChangeStatusBarOrientation();
   195 #endif
   196     }
   197 
   198     return 0;
   199 }
   200 
   201 void
   202 UIKit_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
   203 {
   204     @autoreleasepool {
   205         SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
   206 
   207         SDL_bool isLandscape = UIKit_IsDisplayLandscape(data.uiscreen);
   208         SDL_bool addRotation = (data.uiscreen == [UIScreen mainScreen]);
   209         CGFloat scale = data.uiscreen.scale;
   210         NSArray *availableModes = nil;
   211 
   212 #if TARGET_OS_TV
   213         addRotation = SDL_FALSE;
   214         availableModes = @[data.uiscreen.currentMode];
   215 #else
   216         availableModes = data.uiscreen.availableModes;
   217 #endif
   218 
   219 #ifdef __IPHONE_8_0
   220         /* The UIScreenMode of an iPhone 6 Plus should be 1080x1920 rather than
   221          * 1242x2208 (414x736@3x), so we should use the native scale. */
   222         if ([data.uiscreen respondsToSelector:@selector(nativeScale)]) {
   223             scale = data.uiscreen.nativeScale;
   224         }
   225 #endif
   226 
   227         for (UIScreenMode *uimode in availableModes) {
   228             /* The size of a UIScreenMode is in pixels, but we deal exclusively
   229              * in points (except in SDL_GL_GetDrawableSize.) */
   230             int w = (int)(uimode.size.width / scale);
   231             int h = (int)(uimode.size.height / scale);
   232 
   233             /* Make sure the width/height are oriented correctly */
   234             if (isLandscape != (w > h)) {
   235                 int tmp = w;
   236                 w = h;
   237                 h = tmp;
   238             }
   239 
   240             UIKit_AddDisplayMode(display, w, h, data.uiscreen, uimode, addRotation);
   241         }
   242     }
   243 }
   244 
   245 int
   246 UIKit_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
   247 {
   248     @autoreleasepool {
   249         SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
   250 
   251 #if !TARGET_OS_TV
   252         SDL_DisplayModeData *modedata = (__bridge SDL_DisplayModeData *)mode->driverdata;
   253         [data.uiscreen setCurrentMode:modedata.uiscreenmode];
   254 #endif
   255 
   256         if (data.uiscreen == [UIScreen mainScreen]) {
   257             /* [UIApplication setStatusBarOrientation:] no longer works reliably
   258              * in recent iOS versions, so we can't rotate the screen when setting
   259              * the display mode. */
   260             if (mode->w > mode->h) {
   261                 if (!UIKit_IsDisplayLandscape(data.uiscreen)) {
   262                     return SDL_SetError("Screen orientation does not match display mode size");
   263                 }
   264             } else if (mode->w < mode->h) {
   265                 if (UIKit_IsDisplayLandscape(data.uiscreen)) {
   266                     return SDL_SetError("Screen orientation does not match display mode size");
   267                 }
   268             }
   269         }
   270     }
   271 
   272     return 0;
   273 }
   274 
   275 int
   276 UIKit_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
   277 {
   278     @autoreleasepool {
   279         int displayIndex = (int) (display - _this->displays);
   280         SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
   281         CGRect frame = data.uiscreen.bounds;
   282 
   283         /* the default function iterates displays to make a fake offset,
   284          as if all the displays were side-by-side, which is fine for iOS. */
   285         if (SDL_GetDisplayBounds(displayIndex, rect) < 0) {
   286             return -1;
   287         }
   288 
   289 #if !TARGET_OS_TV && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
   290         if (!UIKit_IsSystemVersionAtLeast(7.0)) {
   291             frame = [data.uiscreen applicationFrame];
   292         }
   293 #endif
   294 
   295         rect->x += frame.origin.x;
   296         rect->y += frame.origin.y;
   297         rect->w = frame.size.width;
   298         rect->h = frame.size.height;
   299     }
   300 
   301     return 0;
   302 }
   303 
   304 void
   305 UIKit_QuitModes(_THIS)
   306 {
   307     /* Release Objective-C objects, so higher level doesn't free() them. */
   308     int i, j;
   309     @autoreleasepool {
   310         for (i = 0; i < _this->num_displays; i++) {
   311             SDL_VideoDisplay *display = &_this->displays[i];
   312 
   313             UIKit_FreeDisplayModeData(&display->desktop_mode);
   314             for (j = 0; j < display->num_display_modes; j++) {
   315                 SDL_DisplayMode *mode = &display->display_modes[j];
   316                 UIKit_FreeDisplayModeData(mode);
   317             }
   318 
   319             if (display->driverdata != NULL) {
   320                 CFRelease(display->driverdata);
   321                 display->driverdata = NULL;
   322             }
   323         }
   324     }
   325 }
   326 
   327 #if !TARGET_OS_TV
   328 void SDL_OnApplicationDidChangeStatusBarOrientation()
   329 {
   330     BOOL isLandscape = UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation);
   331     SDL_VideoDisplay *display = SDL_GetDisplay(0);
   332 
   333     if (display) {
   334         SDL_DisplayMode *desktopmode = &display->desktop_mode;
   335         SDL_DisplayMode *currentmode = &display->current_mode;
   336         SDL_DisplayOrientation orientation = SDL_ORIENTATION_UNKNOWN;
   337 
   338         /* The desktop display mode should be kept in sync with the screen
   339          * orientation so that updating a window's fullscreen state to
   340          * SDL_WINDOW_FULLSCREEN_DESKTOP keeps the window dimensions in the
   341          * correct orientation. */
   342         if (isLandscape != (desktopmode->w > desktopmode->h)) {
   343             int height = desktopmode->w;
   344             desktopmode->w = desktopmode->h;
   345             desktopmode->h = height;
   346         }
   347 
   348         /* Same deal with the current mode + SDL_GetCurrentDisplayMode. */
   349         if (isLandscape != (currentmode->w > currentmode->h)) {
   350             int height = currentmode->w;
   351             currentmode->w = currentmode->h;
   352             currentmode->h = height;
   353         }
   354 
   355         switch ([UIApplication sharedApplication].statusBarOrientation) {
   356         case UIInterfaceOrientationPortrait:
   357             orientation = SDL_ORIENTATION_PORTRAIT;
   358             break;
   359         case UIInterfaceOrientationPortraitUpsideDown:
   360             orientation = SDL_ORIENTATION_PORTRAIT_FLIPPED;
   361             break;
   362         case UIInterfaceOrientationLandscapeLeft:
   363             /* Bug: UIInterfaceOrientationLandscapeLeft/Right are reversed - http://openradar.appspot.com/7216046 */
   364             orientation = SDL_ORIENTATION_LANDSCAPE_FLIPPED;
   365             break;
   366         case UIInterfaceOrientationLandscapeRight:
   367             /* Bug: UIInterfaceOrientationLandscapeLeft/Right are reversed - http://openradar.appspot.com/7216046 */
   368             orientation = SDL_ORIENTATION_LANDSCAPE;
   369             break;
   370         default:
   371             break;
   372         }
   373         SDL_SendDisplayEvent(display, SDL_DISPLAYEVENT_ORIENTATION, orientation);
   374     }
   375 }
   376 #endif /* !TARGET_OS_TV */
   377 
   378 #endif /* SDL_VIDEO_DRIVER_UIKIT */
   379 
   380 /* vi: set ts=4 sw=4 expandtab: */