2 Simple DirectMedia Layer
3 Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
21 #include "../../SDL_internal.h"
23 #if SDL_VIDEO_DRIVER_UIKIT
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"
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"
40 #import "SDL_uikitview.h"
41 #import "SDL_uikitopenglview.h"
43 #include <Foundation/Foundation.h>
45 @implementation SDL_WindowData
48 @synthesize viewcontroller;
53 if ((self = [super init])) {
54 views = [NSMutableArray new];
62 @interface SDL_uikitwindow : UIWindow
64 - (void)layoutSubviews;
68 @implementation SDL_uikitwindow
70 - (void)layoutSubviews
72 /* Workaround to fix window orientation issues in iOS 8+. */
73 self.frame = self.screen.bounds;
74 [super layoutSubviews];
80 static int SetupWindowData(_THIS, SDL_Window *window, UIWindow *uiwindow, SDL_bool created)
82 SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
83 SDL_DisplayData *displaydata = (__bridge SDL_DisplayData *) display->driverdata;
86 CGRect frame = UIKit_ComputeViewFrame(window, displaydata.uiscreen);
87 int width = (int) frame.size.width;
88 int height = (int) frame.size.height;
90 SDL_WindowData *data = [[SDL_WindowData alloc] init];
92 return SDL_OutOfMemory();
95 window->driverdata = (void *) CFBridgingRetain(data);
97 data.uiwindow = uiwindow;
99 /* only one window on iOS, always shown */
100 window->flags &= ~SDL_WINDOW_HIDDEN;
102 if (displaydata.uiscreen == [UIScreen mainScreen]) {
103 window->flags |= SDL_WINDOW_INPUT_FOCUS; /* always has input focus */
105 window->flags &= ~SDL_WINDOW_RESIZABLE; /* window is NEVER resizable */
106 window->flags &= ~SDL_WINDOW_INPUT_FOCUS; /* never has input focus */
107 window->flags |= SDL_WINDOW_BORDERLESS; /* never has a status bar. */
110 if (displaydata.uiscreen == [UIScreen mainScreen]) {
111 NSUInteger orients = UIKit_GetSupportedOrientations(window);
112 BOOL supportsLandscape = (orients & UIInterfaceOrientationMaskLandscape) != 0;
113 BOOL supportsPortrait = (orients & (UIInterfaceOrientationMaskPortrait|UIInterfaceOrientationMaskPortraitUpsideDown)) != 0;
115 /* Make sure the width/height are oriented correctly */
116 if ((width > height && !supportsLandscape) || (height > width && !supportsPortrait)) {
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];
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];
136 /* Sets this view as the controller's view, and adds the view to the window
138 [view setSDLWindow:window];
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);
150 UIKit_CreateWindow(_THIS, SDL_Window *window)
153 SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
154 SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
155 const CGSize origsize = data.uiscreen.currentMode.size;
157 /* SDL currently puts this window at the start of display's linked list. We rely on this. */
158 SDL_assert(_this->windows == window);
160 /* We currently only handle a single window per display on iOS */
161 if (window->next != NULL) {
162 return SDL_SetError("Only one window allowed per display.");
165 /* If monitor has a resolution of 0x0 (hasn't been explicitly set by the
166 * user, so it's in standby), try to force the display to a resolution
167 * that most closely matches the desired window size. */
168 if ((origsize.width == 0.0f) && (origsize.height == 0.0f)) {
169 if (display->num_display_modes == 0) {
170 _this->GetDisplayModes(_this, display);
174 const SDL_DisplayMode *bestmode = NULL;
175 for (i = display->num_display_modes; i >= 0; i--) {
176 const SDL_DisplayMode *mode = &display->display_modes[i];
177 if ((mode->w >= window->w) && (mode->h >= window->h)) {
183 SDL_DisplayModeData *modedata = (__bridge SDL_DisplayModeData *)bestmode->driverdata;
184 [data.uiscreen setCurrentMode:modedata.uiscreenmode];
186 /* desktop_mode doesn't change here (the higher level will
187 * use it to set all the screens back to their defaults
188 * upon window destruction, SDL_Quit(), etc. */
189 display->current_mode = *bestmode;
193 if (data.uiscreen == [UIScreen mainScreen]) {
194 if (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) {
195 [UIApplication sharedApplication].statusBarHidden = YES;
197 [UIApplication sharedApplication].statusBarHidden = NO;
201 /* ignore the size user requested, and make a fullscreen window */
202 /* !!! FIXME: can we have a smaller view? */
203 UIWindow *uiwindow = [[SDL_uikitwindow alloc] initWithFrame:data.uiscreen.bounds];
205 /* put the window on an external display if appropriate. */
206 if (data.uiscreen != [UIScreen mainScreen]) {
207 [uiwindow setScreen:data.uiscreen];
210 if (SetupWindowData(_this, window, uiwindow, SDL_TRUE) < 0) {
219 UIKit_SetWindowTitle(_THIS, SDL_Window * window)
222 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
223 data.viewcontroller.title = @(window->title);
228 UIKit_ShowWindow(_THIS, SDL_Window * window)
231 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
232 [data.uiwindow makeKeyAndVisible];
237 UIKit_HideWindow(_THIS, SDL_Window * window)
240 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
241 data.uiwindow.hidden = YES;
246 UIKit_RaiseWindow(_THIS, SDL_Window * window)
248 /* We don't currently offer a concept of "raising" the SDL window, since
249 * we only allow one per display, in the iOS fashion.
250 * However, we use this entry point to rebind the context to the view
251 * during OnWindowRestored processing. */
252 _this->GL_MakeCurrent(_this, _this->current_glwin, _this->current_glctx);
256 UIKit_UpdateWindowBorder(_THIS, SDL_Window * window)
258 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
259 SDL_uikitviewcontroller *viewcontroller = data.viewcontroller;
261 if (data.uiwindow.screen == [UIScreen mainScreen]) {
262 if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) {
263 [UIApplication sharedApplication].statusBarHidden = YES;
265 [UIApplication sharedApplication].statusBarHidden = NO;
268 /* iOS 7+ won't update the status bar until we tell it to. */
269 if ([viewcontroller respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) {
270 [viewcontroller setNeedsStatusBarAppearanceUpdate];
274 /* Update the view's frame to account for the status bar change. */
275 viewcontroller.view.frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen);
277 #ifdef SDL_IPHONE_KEYBOARD
278 /* Make sure the view is offset correctly when the keyboard is visible. */
279 [viewcontroller updateKeyboard];
282 [viewcontroller.view setNeedsLayout];
283 [viewcontroller.view layoutIfNeeded];
287 UIKit_SetWindowBordered(_THIS, SDL_Window * window, SDL_bool bordered)
290 UIKit_UpdateWindowBorder(_this, window);
295 UIKit_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
298 UIKit_UpdateWindowBorder(_this, window);
303 UIKit_DestroyWindow(_THIS, SDL_Window * window)
306 if (window->driverdata != NULL) {
307 SDL_WindowData *data = (SDL_WindowData *) CFBridgingRelease(window->driverdata);
308 NSArray *views = nil;
310 [data.viewcontroller stopAnimation];
312 /* Detach all views from this window. We use a copy of the array
313 * because setSDLWindow will remove the object from the original
314 * array, which would be undesirable if we were iterating over it. */
315 views = [data.views copy];
316 for (SDL_uikitview *view in views) {
317 [view setSDLWindow:NULL];
320 /* iOS may still hold a reference to the window after we release it.
321 * We want to make sure the SDL view controller isn't accessed in
322 * that case, because it would contain an invalid pointer to the old
324 data.uiwindow.rootViewController = nil;
325 data.uiwindow.hidden = YES;
328 window->driverdata = NULL;
332 UIKit_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
335 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
337 if (info->version.major <= SDL_MAJOR_VERSION) {
338 int versionnum = SDL_VERSIONNUM(info->version.major, info->version.minor, info->version.patch);
340 info->subsystem = SDL_SYSWM_UIKIT;
341 info->info.uikit.window = data.uiwindow;
343 /* These struct members were added in SDL 2.0.4. */
344 if (versionnum >= SDL_VERSIONNUM(2,0,4)) {
345 if ([data.viewcontroller.view isKindOfClass:[SDL_uikitopenglview class]]) {
346 SDL_uikitopenglview *glview = (SDL_uikitopenglview *)data.viewcontroller.view;
347 info->info.uikit.framebuffer = glview.drawableFramebuffer;
348 info->info.uikit.colorbuffer = glview.drawableRenderbuffer;
349 info->info.uikit.resolveFramebuffer = glview.msaaResolveFramebuffer;
351 info->info.uikit.framebuffer = 0;
352 info->info.uikit.colorbuffer = 0;
353 info->info.uikit.resolveFramebuffer = 0;
359 SDL_SetError("Application not compiled with SDL %d.%d\n",
360 SDL_MAJOR_VERSION, SDL_MINOR_VERSION);
367 UIKit_GetSupportedOrientations(SDL_Window * window)
369 const char *hint = SDL_GetHint(SDL_HINT_ORIENTATIONS);
370 NSUInteger validOrientations = UIInterfaceOrientationMaskAll;
371 NSUInteger orientationMask = 0;
374 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
375 UIApplication *app = [UIApplication sharedApplication];
377 /* Get all possible valid orientations. If the app delegate doesn't tell
378 * us, we get the orientations from Info.plist via UIApplication. */
379 if ([app.delegate respondsToSelector:@selector(application:supportedInterfaceOrientationsForWindow:)]) {
380 validOrientations = [app.delegate application:app supportedInterfaceOrientationsForWindow:data.uiwindow];
381 } else if ([app respondsToSelector:@selector(supportedInterfaceOrientationsForWindow:)]) {
382 validOrientations = [app supportedInterfaceOrientationsForWindow:data.uiwindow];
386 NSArray *orientations = [@(hint) componentsSeparatedByString:@" "];
388 if ([orientations containsObject:@"LandscapeLeft"]) {
389 orientationMask |= UIInterfaceOrientationMaskLandscapeLeft;
391 if ([orientations containsObject:@"LandscapeRight"]) {
392 orientationMask |= UIInterfaceOrientationMaskLandscapeRight;
394 if ([orientations containsObject:@"Portrait"]) {
395 orientationMask |= UIInterfaceOrientationMaskPortrait;
397 if ([orientations containsObject:@"PortraitUpsideDown"]) {
398 orientationMask |= UIInterfaceOrientationMaskPortraitUpsideDown;
402 if (orientationMask == 0 && (window->flags & SDL_WINDOW_RESIZABLE)) {
403 /* any orientation is okay. */
404 orientationMask = UIInterfaceOrientationMaskAll;
407 if (orientationMask == 0) {
408 if (window->w >= window->h) {
409 orientationMask |= UIInterfaceOrientationMaskLandscape;
411 if (window->h >= window->w) {
412 orientationMask |= (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
416 /* Don't allow upside-down orientation on phones, so answering calls is in the natural orientation */
417 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
418 orientationMask &= ~UIInterfaceOrientationMaskPortraitUpsideDown;
421 /* If none of the specified orientations are actually supported by the
422 * app, we'll revert to what the app supports. An exception would be
423 * thrown by the system otherwise. */
424 if ((validOrientations & orientationMask) == 0) {
425 orientationMask = validOrientations;
429 return orientationMask;
433 SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam)
435 if (!window || !window->driverdata) {
436 return SDL_SetError("Invalid window");
440 SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
441 [data.viewcontroller setAnimationCallback:interval
443 callbackParam:callbackParam];
449 #endif /* SDL_VIDEO_DRIVER_UIKIT */
451 /* vi: set ts=4 sw=4 expandtab: */