diff --git a/include/SDL_config_iphoneos.h b/include/SDL_config_iphoneos.h index 4e3eb2c92..79da3667b 100644 --- a/include/SDL_config_iphoneos.h +++ b/include/SDL_config_iphoneos.h @@ -145,6 +145,9 @@ /* enable iPhone keyboard support */ #define SDL_IPHONE_KEYBOARD 1 +/* enable iOS extended launch screen */ +#define SDL_IPHONE_LAUNCHSCREEN 1 + /* enable joystick subsystem */ #define SDL_JOYSTICK_DISABLED 0 diff --git a/premake/Xcode-iOS/SDL_config_premake.h b/premake/Xcode-iOS/SDL_config_premake.h index e0a864173..bae64b187 100644 --- a/premake/Xcode-iOS/SDL_config_premake.h +++ b/premake/Xcode-iOS/SDL_config_premake.h @@ -126,6 +126,9 @@ #ifndef SDL_IPHONE_KEYBOARD #define SDL_IPHONE_KEYBOARD 1 #endif +#ifndef SDL_IPHONE_LAUNCHSCREEN +#define SDL_IPHONE_LAUNCHSCREEN 1 +#endif #ifndef SDL_POWER_UIKIT #define SDL_POWER_UIKIT 1 #endif diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 43d674fe0..895643b6b 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -1100,22 +1100,22 @@ SDL_RestoreMousePosition(SDL_Window *window) } } -static void +static int SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen) { SDL_VideoDisplay *display; SDL_Window *other; - CHECK_WINDOW_MAGIC(window,); + CHECK_WINDOW_MAGIC(window,-1); /* if we are in the process of hiding don't go back to fullscreen */ if ( window->is_hiding && fullscreen ) - return; + return 0; #ifdef __MACOSX__ if (Cocoa_SetWindowFullscreenSpace(window, fullscreen)) { window->last_fullscreen_flags = window->flags; - return; + return 0; } #endif @@ -1132,7 +1132,7 @@ SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen) /* See if anything needs to be done now */ if ((display->fullscreen_window == window) == fullscreen) { if ((window->last_fullscreen_flags & FULLSCREEN_MASK) == (window->flags & FULLSCREEN_MASK)) { - return; + return 0; } } @@ -1161,9 +1161,13 @@ SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen) /* only do the mode change if we want exclusive fullscreen */ if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP) { - SDL_SetDisplayModeForDisplay(display, &fullscreen_mode); + if (SDL_SetDisplayModeForDisplay(display, &fullscreen_mode) < 0) { + return -1; + } } else { - SDL_SetDisplayModeForDisplay(display, NULL); + if (SDL_SetDisplayModeForDisplay(display, NULL) < 0) { + return -1; + } } if (_this->SetWindowFullscreen) { @@ -1182,7 +1186,7 @@ SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen) SDL_RestoreMousePosition(other); window->last_fullscreen_flags = window->flags; - return; + return 0; } } } @@ -1202,6 +1206,7 @@ SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen) SDL_RestoreMousePosition(window); window->last_fullscreen_flags = window->flags; + return 0; } #define CREATE_FLAGS \ @@ -1927,9 +1932,7 @@ SDL_SetWindowFullscreen(SDL_Window * window, Uint32 flags) window->flags &= ~FULLSCREEN_MASK; window->flags |= flags; - SDL_UpdateFullscreenMode(window, FULLSCREEN_VISIBLE(window)); - - return 0; + return SDL_UpdateFullscreenMode(window, FULLSCREEN_VISIBLE(window)); } static SDL_Surface * diff --git a/src/video/uikit/SDL_uikitappdelegate.h b/src/video/uikit/SDL_uikitappdelegate.h index d5430beb1..cc4494dbb 100644 --- a/src/video/uikit/SDL_uikitappdelegate.h +++ b/src/video/uikit/SDL_uikitappdelegate.h @@ -21,12 +21,22 @@ #import -@interface SDLUIKitDelegate : NSObject { -} +@interface SDLLaunchScreenController : UIViewController -+ (id) sharedAppDelegate; +- (instancetype)init; +- (void)loadView; +- (BOOL)shouldAutorotate; +- (NSUInteger)supportedInterfaceOrientations; + +@end + +@interface SDLUIKitDelegate : NSObject + ++ (id)sharedAppDelegate; + (NSString *)getAppDelegateClassName; +- (void)hideLaunchScreen; + @end /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/uikit/SDL_uikitappdelegate.m b/src/video/uikit/SDL_uikitappdelegate.m index 6a07d5d14..3306f38e9 100644 --- a/src/video/uikit/SDL_uikitappdelegate.m +++ b/src/video/uikit/SDL_uikitappdelegate.m @@ -28,8 +28,10 @@ #include "SDL_system.h" #include "SDL_main.h" -#include "SDL_uikitappdelegate.h" -#include "SDL_uikitmodes.h" +#import "SDL_uikitappdelegate.h" +#import "SDL_uikitmodes.h" +#import "SDL_uikitwindow.h" + #include "../../events/SDL_events_c.h" #ifdef main @@ -74,46 +76,256 @@ SDL_IdleTimerDisabledChanged(void *userdata, const char *name, const char *oldVa [UIApplication sharedApplication].idleTimerDisabled = disable; } -@implementation SDLUIKitDelegate +/* Load a launch image using the old UILaunchImageFile-era naming rules. */ +static UIImage * +SDL_LoadLaunchImageNamed(NSString *name, int screenh) +{ + UIInterfaceOrientation curorient = [UIApplication sharedApplication].statusBarOrientation; + UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom; + UIImage *image = nil; + + if (idiom == UIUserInterfaceIdiomPhone && screenh == 568) { + /* The image name for the iPhone 5 uses its height as a suffix. */ + image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-568h", name]]; + } else if (idiom == UIUserInterfaceIdiomPad) { + /* iPad apps can launch in any orientation. */ + if (UIInterfaceOrientationIsLandscape(curorient)) { + if (curorient == UIInterfaceOrientationLandscapeLeft) { + image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-LandscapeLeft", name]]; + } else { + image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-LandscapeRight", name]]; + } + if (!image) { + image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-Landscape", name]]; + } + } else { + if (curorient == UIInterfaceOrientationPortraitUpsideDown) { + image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-PortraitUpsideDown", name]]; + } + if (!image) { + image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-Portrait", name]]; + } + } + } + + if (!image) { + image = [UIImage imageNamed:name]; + } + + return image; +} + +@implementation SDLLaunchScreenController + +- (instancetype)init +{ + if (!(self = [super initWithNibName:nil bundle:nil])) { + return nil; + } + + NSBundle *bundle = [NSBundle mainBundle]; + NSString *screenname = [bundle objectForInfoDictionaryKey:@"UILaunchStoryboardName"]; + + /* Launch screens were added in iOS 8. Otherwise we use launch images. */ + if (screenname && UIKit_IsSystemVersionAtLeast(8.0)) { + @try { + self.view = [bundle loadNibNamed:screenname owner:self options:nil][0]; + } + @catch (NSException *exception) { + /* iOS displays a blank screen rather than falling back to an image, + * if a launch screen name is specified but it fails to load. */ + return nil; + } + } + + if (!self.view) { + NSArray *launchimages = [bundle objectForInfoDictionaryKey:@"UILaunchImages"]; + UIInterfaceOrientation curorient = [UIApplication sharedApplication].statusBarOrientation; + NSString *imagename = nil; + UIImage *image = nil; + + int screenw = (int)([UIScreen mainScreen].bounds.size.width + 0.5); + int screenh = (int)([UIScreen mainScreen].bounds.size.height + 0.5); + + /* We always want portrait-oriented size, to match UILaunchImageSize. */ + if (screenw > screenh) { + int width = screenw; + screenw = screenh; + screenh = width; + } + + /* Xcode 5 introduced a dictionary of launch images in Info.plist. */ + if (launchimages) { + for (NSDictionary *dict in launchimages) { + UIInterfaceOrientationMask orientmask = UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; + NSString *minversion = dict[@"UILaunchImageMinimumOSVersion"]; + NSString *sizestring = dict[@"UILaunchImageSize"]; + NSString *orientstring = dict[@"UILaunchImageOrientation"]; + + /* Ignore this image if the current version is too low. */ + if (minversion && !UIKit_IsSystemVersionAtLeast(minversion.doubleValue)) { + continue; + } + + /* Ignore this image if the size doesn't match. */ + if (sizestring) { + CGSize size = CGSizeFromString(sizestring); + if ((int)(size.width + 0.5) != screenw || (int)(size.height + 0.5) != screenh) { + continue; + } + } + + if (orientstring) { + if ([orientstring isEqualToString:@"PortraitUpsideDown"]) { + orientmask = UIInterfaceOrientationMaskPortraitUpsideDown; + } else if ([orientstring isEqualToString:@"Landscape"]) { + orientmask = UIInterfaceOrientationMaskLandscape; + } else if ([orientstring isEqualToString:@"LandscapeLeft"]) { + orientmask = UIInterfaceOrientationMaskLandscapeLeft; + } else if ([orientstring isEqualToString:@"LandscapeRight"]) { + orientmask = UIInterfaceOrientationMaskLandscapeRight; + } + } + + /* Ignore this image if the orientation doesn't match. */ + if ((orientmask & (1 << curorient)) == 0) { + continue; + } + + imagename = dict[@"UILaunchImageName"]; + } + + if (imagename) { + image = [UIImage imageNamed:imagename]; + } + } else { + imagename = [bundle objectForInfoDictionaryKey:@"UILaunchImageFile"]; + + if (imagename) { + image = SDL_LoadLaunchImageNamed(imagename, screenh); + } + + if (!image) { + image = SDL_LoadLaunchImageNamed(@"Default", screenh); + } + } + + if (image) { + self.view = [[UIImageView alloc] initWithImage:image]; + } + } + + return self; +} + +- (void)loadView +{ + /* Do nothing. */ +} + +- (BOOL)shouldAutorotate +{ + return YES; +} + +- (NSUInteger)supportedInterfaceOrientations +{ + return UIInterfaceOrientationMaskAll; +} + +@end + +@implementation SDLUIKitDelegate { + UIWindow *launchWindow; +} /* convenience method */ -+ (id) sharedAppDelegate ++ (id)sharedAppDelegate { - /* the delegate is set in UIApplicationMain(), which is guaranteed to be called before this method */ - return [[UIApplication sharedApplication] delegate]; + /* the delegate is set in UIApplicationMain(), which is guaranteed to be + * called before this method */ + return [UIApplication sharedApplication].delegate; } + (NSString *)getAppDelegateClassName { - /* subclassing notice: when you subclass this appdelegate, make sure to add a category to override - this method and return the actual name of the delegate */ + /* subclassing notice: when you subclass this appdelegate, make sure to add + * a category to override this method and return the actual name of the + * delegate */ return @"SDLUIKitDelegate"; } -- (id)init +- (void)hideLaunchScreen { - self = [super init]; - return self; + UIWindow *window = launchWindow; + + if (!window || window.hidden) { + return; + } + + launchWindow = nil; + + /* Do a nice animated fade-out (roughly matches the real launch behavior.) */ + [UIView animateWithDuration:0.2 animations:^{ + window.alpha = 0.0; + } completion:^(BOOL finished) { + window.hidden = YES; + }]; } - (void)postFinishLaunch { + /* Hide the launch screen the next time the run loop is run. SDL apps will + * have a chance to load resources while the launch screen is still up. */ + [self performSelector:@selector(hideLaunchScreen) withObject:nil afterDelay:0.0]; + /* run the user's application, passing argc and argv */ SDL_iPhoneSetEventPump(SDL_TRUE); exit_status = SDL_main(forward_argc, forward_argv); SDL_iPhoneSetEventPump(SDL_FALSE); + if (launchWindow) { + launchWindow.hidden = YES; + launchWindow = nil; + } + /* exit, passing the return status from the user's application */ - /* We don't actually exit to support applications that do setup in - * their main function and then allow the Cocoa event loop to run. - */ + /* We don't actually exit to support applications that do setup in their + * main function and then allow the Cocoa event loop to run. */ /* exit(exit_status); */ } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + NSBundle *bundle = [NSBundle mainBundle]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + +#if SDL_IPHONE_LAUNCHSCREEN + /* The normal launch screen is displayed until didFinishLaunching returns, + * but SDL_main is called after that happens and there may be a noticeable + * delay between the start of SDL_main and when the first real frame is + * displayed (e.g. if resources are loaded before SDL_GL_SwapWindow is + * called), so we show the launch screen programmatically until the first + * time events are pumped. */ + UIViewController *viewcontroller = [[SDLLaunchScreenController alloc] init]; + + if (viewcontroller.view) { + launchWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + + /* We don't want the launch window immediately hidden when a real SDL + * window is shown - we fade it out ourselves when we're ready. */ + launchWindow.windowLevel = UIWindowLevelNormal + 1.0; + + /* Show the window but don't make it key. Events should always go to + * other windows when possible. */ + launchWindow.hidden = NO; + + launchWindow.rootViewController = viewcontroller; + } +#endif + /* Set working directory to resource path */ - [[NSFileManager defaultManager] changeCurrentDirectoryPath:[[NSBundle mainBundle] resourcePath]]; + [[NSFileManager defaultManager] changeCurrentDirectoryPath:[bundle resourcePath]]; /* register a callback for the idletimer hint */ SDL_AddHintCallback(SDL_HINT_IDLE_TIMER_DISABLED, @@ -121,7 +333,7 @@ SDL_IdleTimerDisabledChanged(void *userdata, const char *name, const char *oldVa SDL_SetMainReady(); [self performSelector:@selector(postFinishLaunch) withObject:nil afterDelay:0.0]; - + return YES; } @@ -147,8 +359,7 @@ SDL_IdleTimerDisabledChanged(void *userdata, const char *name, const char *oldVa /* The desktop display mode should be kept in sync with the screen * orientation so that updating a window's fullscreen state to * SDL_WINDOW_FULLSCREEN_DESKTOP keeps the window dimensions in the - * correct orientation. - */ + * correct orientation. */ if (isLandscape != (desktopmode->w > desktopmode->h)) { int height = desktopmode->w; desktopmode->w = desktopmode->h; @@ -164,7 +375,7 @@ SDL_IdleTimerDisabledChanged(void *userdata, const char *name, const char *oldVa } } -- (void) applicationWillResignActive:(UIApplication*)application +- (void)applicationWillResignActive:(UIApplication*)application { SDL_VideoDevice *_this = SDL_GetVideoDevice(); if (_this) { @@ -177,17 +388,17 @@ SDL_IdleTimerDisabledChanged(void *userdata, const char *name, const char *oldVa SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND); } -- (void) applicationDidEnterBackground:(UIApplication*)application +- (void)applicationDidEnterBackground:(UIApplication*)application { SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND); } -- (void) applicationWillEnterForeground:(UIApplication*)application +- (void)applicationWillEnterForeground:(UIApplication*)application { SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND); } -- (void) applicationDidBecomeActive:(UIApplication*)application +- (void)applicationDidBecomeActive:(UIApplication*)application { SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND); @@ -203,11 +414,11 @@ SDL_IdleTimerDisabledChanged(void *userdata, const char *name, const char *oldVa - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { - NSURL *fileURL = [url filePathURL]; + NSURL *fileURL = url.filePathURL; if (fileURL != nil) { - SDL_SendDropFile([[fileURL path] UTF8String]); + SDL_SendDropFile([fileURL.path UTF8String]); } else { - SDL_SendDropFile([[url absoluteString] UTF8String]); + SDL_SendDropFile([url.absoluteString UTF8String]); } return YES; } diff --git a/src/video/uikit/SDL_uikitmessagebox.m b/src/video/uikit/SDL_uikitmessagebox.m index 9a99db170..f540f0c5c 100644 --- a/src/video/uikit/SDL_uikitmessagebox.m +++ b/src/video/uikit/SDL_uikitmessagebox.m @@ -30,35 +30,20 @@ static SDL_bool s_showingMessageBox = SDL_FALSE; -@interface UIKit_UIAlertViewDelegate : NSObject +@interface SDLAlertViewDelegate : NSObject -- (id)initWithButtonIndex:(int *)buttonIndex; -- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex; +@property (nonatomic, assign) int clickedIndex; @end -@implementation UIKit_UIAlertViewDelegate { - int *clickedButtonIndex; -} - -- (id)initWithButtonIndex:(int *)buttonIndex -{ - self = [self init]; - if (self == nil) { - return nil; - } - - clickedButtonIndex = buttonIndex; - - return self; -} +@implementation SDLAlertViewDelegate - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { - *clickedButtonIndex = (int)buttonIndex; + _clickedIndex = (int)buttonIndex; } -@end /* UIKit_UIAlertViewDelegate */ +@end SDL_bool @@ -70,36 +55,35 @@ UIKit_ShowingMessageBox() int UIKit_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) { - int clicked; int i; const SDL_MessageBoxButtonData *buttons = messageboxdata->buttons; @autoreleasepool { - UIAlertView* alert = [[UIAlertView alloc] init]; - UIKit_UIAlertViewDelegate *delegate = [[UIKit_UIAlertViewDelegate alloc] initWithButtonIndex:&clicked]; + UIAlertView *alert = [[UIAlertView alloc] init]; + SDLAlertViewDelegate *delegate = [[SDLAlertViewDelegate alloc] init]; + alert.delegate = delegate; alert.title = @(messageboxdata->title); alert.message = @(messageboxdata->message); - alert.delegate = delegate; for (i = 0; i < messageboxdata->numbuttons; ++i) { [alert addButtonWithTitle:@(buttons[i].text)]; } /* Set up for showing the alert */ - clicked = messageboxdata->numbuttons; + delegate.clickedIndex = messageboxdata->numbuttons; [alert show]; /* Run the main event loop until the alert has finished */ /* Note that this needs to be done on the main thread */ s_showingMessageBox = SDL_TRUE; - while (clicked == messageboxdata->numbuttons) { + while (delegate.clickedIndex == messageboxdata->numbuttons) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } s_showingMessageBox = SDL_FALSE; - *buttonid = messageboxdata->buttons[clicked].buttonid; + *buttonid = messageboxdata->buttons[delegate.clickedIndex].buttonid; alert.delegate = nil; } diff --git a/src/video/uikit/SDL_uikitmodes.m b/src/video/uikit/SDL_uikitmodes.m index 8b9d7c945..1fb9a03c7 100644 --- a/src/video/uikit/SDL_uikitmodes.m +++ b/src/video/uikit/SDL_uikitmodes.m @@ -112,7 +112,7 @@ UIKit_AddDisplayMode(SDL_VideoDisplay * display, int w, int h, static int UIKit_AddDisplay(UIScreen *uiscreen) { - CGSize size = [uiscreen bounds].size; + CGSize size = uiscreen.bounds.size; /* Make sure the width/height are oriented correctly */ if (UIKit_IsDisplayLandscape(uiscreen) != (size.width > size.height)) { @@ -128,7 +128,7 @@ UIKit_AddDisplay(UIScreen *uiscreen) mode.w = (int) size.width; mode.h = (int) size.height; - UIScreenMode *uiscreenmode = [uiscreen currentMode]; + UIScreenMode *uiscreenmode = uiscreen.currentMode; if (UIKit_AllocateDisplayModeData(&mode, uiscreenmode) < 0) { return -1; @@ -157,9 +157,9 @@ SDL_bool UIKit_IsDisplayLandscape(UIScreen *uiscreen) { if (uiscreen == [UIScreen mainScreen]) { - return UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]); + return UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation); } else { - CGSize size = [uiscreen bounds].size; + CGSize size = uiscreen.bounds.size; return (size.width > size.height); } } @@ -196,7 +196,7 @@ UIKit_GetDisplayModes(_THIS, SDL_VideoDisplay * display) } #endif - for (UIScreenMode *uimode in [data.uiscreen availableModes]) { + for (UIScreenMode *uimode in data.uiscreen.availableModes) { /* The size of a UIScreenMode is in pixels, but we deal exclusively * in points (except in SDL_GL_GetDrawableSize.) */ int w = (int)(uimode.size.width / scale); @@ -209,7 +209,6 @@ UIKit_GetDisplayModes(_THIS, SDL_VideoDisplay * display) h = tmp; } - /* Add the native screen resolution. */ UIKit_AddDisplayMode(display, w, h, uimode, addRotation); } } @@ -225,13 +224,16 @@ UIKit_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode) [data.uiscreen setCurrentMode:modedata.uiscreenmode]; if (data.uiscreen == [UIScreen mainScreen]) { + /* [UIApplication setStatusBarOrientation:] no longer works reliably + * in recent iOS versions, so we can't rotate the screen when setting + * the display mode. */ if (mode->w > mode->h) { if (!UIKit_IsDisplayLandscape(data.uiscreen)) { - [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight animated:NO]; + return SDL_SetError("Screen orientation does not match display mode size"); } } else if (mode->w < mode->h) { if (UIKit_IsDisplayLandscape(data.uiscreen)) { - [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO]; + return SDL_SetError("Screen orientation does not match display mode size"); } } } diff --git a/src/video/uikit/SDL_uikitopengles.m b/src/video/uikit/SDL_uikitopengles.m index a003882f6..e9178c183 100644 --- a/src/video/uikit/SDL_uikitopengles.m +++ b/src/video/uikit/SDL_uikitopengles.m @@ -23,10 +23,10 @@ #if SDL_VIDEO_DRIVER_UIKIT #include "SDL_uikitopengles.h" -#include "SDL_uikitopenglview.h" -#include "SDL_uikitappdelegate.h" +#import "SDL_uikitopenglview.h" #include "SDL_uikitmodes.h" #include "SDL_uikitwindow.h" +#include "SDL_uikitevents.h" #include "../SDL_sysvideo.h" #include "../../events/SDL_keyboard_c.h" #include "../../events/SDL_mouse_c.h" @@ -40,21 +40,29 @@ void * UIKit_GL_GetProcAddress(_THIS, const char *proc) { /* Look through all SO's for the proc symbol. Here's why: - -Looking for the path to the OpenGL Library seems not to work in the iPhone Simulator. - -We don't know that the path won't change in the future. - */ + * -Looking for the path to the OpenGL Library seems not to work in the iOS Simulator. + * -We don't know that the path won't change in the future. */ return dlsym(RTLD_DEFAULT, proc); } /* - note that SDL_GL_Delete context makes it current without passing the window + note that SDL_GL_DeleteContext makes it current without passing the window */ int UIKit_GL_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context) { @autoreleasepool { - [EAGLContext setCurrentContext:(__bridge EAGLContext *)context]; + SDLEAGLContext *eaglcontext = (__bridge SDLEAGLContext *) context; + + if (![EAGLContext setCurrentContext:eaglcontext]) { + return SDL_SetError("Could not make EAGL context current"); + } + + if (eaglcontext) { + [eaglcontext.sdlView setSDLWindow:window]; + } } + return 0; } @@ -63,27 +71,26 @@ UIKit_GL_GetDrawableSize(_THIS, SDL_Window * window, int * w, int * h) { @autoreleasepool { SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata; - - if (w) { - *w = data.view.backingWidth; - } - if (h) { - *h = data.view.backingHeight; + UIView *view = data.viewcontroller.view; + if ([view isKindOfClass:[SDL_uikitopenglview class]]) { + SDL_uikitopenglview *glview = (SDL_uikitopenglview *) view; + if (w) { + *w = glview.backingWidth; + } + if (h) { + *h = glview.backingHeight; + } } } } - int UIKit_GL_LoadLibrary(_THIS, const char *path) { - /* - shouldn't be passing a path into this function - why? Because we've already loaded the library - and because the SDK forbids loading an external SO - */ + /* We shouldn't pass a path to this function, since we've already loaded the + * library. */ if (path != NULL) { - return SDL_SetError("iPhone GL Load Library just here for compatibility"); + return SDL_SetError("iOS GL Load Library just here for compatibility"); } return 0; } @@ -91,22 +98,18 @@ UIKit_GL_LoadLibrary(_THIS, const char *path) void UIKit_GL_SwapWindow(_THIS, SDL_Window * window) { @autoreleasepool { - SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata; + SDLEAGLContext *context = (__bridge SDLEAGLContext *) SDL_GL_GetCurrentContext(); #if SDL_POWER_UIKIT /* Check once a frame to see if we should turn off the battery monitor. */ SDL_UIKit_UpdateBatteryMonitoring(); #endif - if (data.view == nil) { - return; - } - [data.view swapBuffers]; + [context.sdlView swapBuffers]; /* You need to pump events in order for the OS to make changes visible. - We don't pump events here because we don't want iOS application events - (low memory, terminate, etc.) to happen inside low level rendering. - */ + * We don't pump events here because we don't want iOS application events + * (low memory, terminate, etc.) to happen inside low level rendering. */ } } @@ -116,29 +119,27 @@ UIKit_GL_CreateContext(_THIS, SDL_Window * window) @autoreleasepool { SDL_uikitopenglview *view; SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; - UIWindow *uiwindow = data.uiwindow; - CGRect frame = UIKit_ComputeViewFrame(window, uiwindow.screen); - EAGLSharegroup *share_group = nil; + CGRect frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen); + EAGLSharegroup *sharegroup = nil; CGFloat scale = 1.0; - if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) { - /* Set the scale to the natural scale factor of the screen - the - backing dimensions of the OpenGL view will match the pixel - dimensions of the screen rather than the dimensions in points. - */ -#ifdef __IPHONE_8_0 - if ([uiwindow.screen respondsToSelector:@selector(nativeScale)]) { - scale = uiwindow.screen.nativeScale; - } else -#endif - { - scale = uiwindow.screen.scale; - } - } - if (_this->gl_config.share_with_current_context) { EAGLContext *context = (__bridge EAGLContext *) SDL_GL_GetCurrentContext(); - share_group = context.sharegroup; + sharegroup = context.sharegroup; + } + + if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) { + /* Set the scale to the natural scale factor of the screen - the + * backing dimensions of the OpenGL view will match the pixel + * dimensions of the screen rather than the dimensions in points. */ +#ifdef __IPHONE_8_0 + if ([data.uiwindow.screen respondsToSelector:@selector(nativeScale)]) { + scale = data.uiwindow.screen.nativeScale; + } else +#endif + { + scale = data.uiwindow.screen.scale; + } } /* construct our view, passing in SDL's OpenGL configuration data */ @@ -153,35 +154,19 @@ UIKit_GL_CreateContext(_THIS, SDL_Window * window) stencilBits:_this->gl_config.stencil_size sRGB:_this->gl_config.framebuffer_srgb_capable majorVersion:_this->gl_config.major_version - shareGroup:share_group]; + shareGroup:sharegroup]; if (!view) { return NULL; } - view.sdlwindow = window; - data.view = view; - data.viewcontroller.view = view; - - /* The view controller needs to be the root in order to control rotation */ - if (uiwindow.rootViewController == nil) { - uiwindow.rootViewController = data.viewcontroller; - } else { - [uiwindow addSubview:view]; - } - - EAGLContext *context = view.context; + SDLEAGLContext *context = view.context; if (UIKit_GL_MakeCurrent(_this, window, (__bridge SDL_GLContext) context) < 0) { UIKit_GL_DeleteContext(_this, (SDL_GLContext) CFBridgingRetain(context)); return NULL; } - /* Make this window the current mouse focus for touch input */ - if (uiwindow.screen == [UIScreen mainScreen]) { - SDL_SetMouseFocus(window); - SDL_SetKeyboardFocus(window); - } - - /* We return a +1'd context. The window's driverdata owns the view. */ + /* We return a +1'd context. The window's driverdata owns the view (via + * MakeCurrent.) */ return (SDL_GLContext) CFBridgingRetain(context); } } @@ -191,29 +176,10 @@ UIKit_GL_DeleteContext(_THIS, SDL_GLContext context) { @autoreleasepool { /* Transfer ownership the +1'd context to ARC. */ - EAGLContext *eaglcontext = (EAGLContext *) CFBridgingRelease(context); - SDL_Window *window; + SDLEAGLContext *eaglcontext = (SDLEAGLContext *) CFBridgingRelease(context); - /* Find the view associated with this context */ - for (window = _this->windows; window; window = window->next) { - SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; - SDL_uikitopenglview *view = data.view; - if (view.context == eaglcontext) { - /* the view controller has retained the view */ - if (data.viewcontroller) { - UIWindow *uiwindow = (UIWindow *)view.superview; - if (uiwindow.rootViewController == data.viewcontroller) { - uiwindow.rootViewController = nil; - } - data.viewcontroller.view = nil; - } - - [view removeFromSuperview]; - view.sdlwindow = NULL; - data.view = nil; - return; - } - } + /* Detach the context's view from its window. */ + [eaglcontext.sdlView setSDLWindow:NULL]; } } @@ -227,12 +193,15 @@ SDL_iPhoneGetViewRenderbuffer(SDL_Window * window) @autoreleasepool { SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; - if (data.view != nil) { - return data.view.drawableRenderbuffer; - } else { - return 0; + UIView *view = data.viewcontroller.view; + if ([view isKindOfClass:[SDL_uikitopenglview class]]) { + SDL_uikitopenglview *glview = (SDL_uikitopenglview *) view; + return glview.drawableRenderbuffer; } } + + SDL_SetError("Window does not have an attached OpenGL view"); + return 0; } Uint32 @@ -245,12 +214,15 @@ SDL_iPhoneGetViewFramebuffer(SDL_Window * window) @autoreleasepool { SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; - if (data.view != nil) { - return data.view.drawableFramebuffer; - } else { - return 0; + UIView *view = data.viewcontroller.view; + if ([view isKindOfClass:[SDL_uikitopenglview class]]) { + SDL_uikitopenglview *glview = (SDL_uikitopenglview *) view; + return glview.drawableFramebuffer; } } + + SDL_SetError("Window does not have an attached OpenGL view"); + return 0; } #endif /* SDL_VIDEO_DRIVER_UIKIT */ diff --git a/src/video/uikit/SDL_uikitopenglview.h b/src/video/uikit/SDL_uikitopenglview.h index 8221988b8..1dcac28b7 100644 --- a/src/video/uikit/SDL_uikitopenglview.h +++ b/src/video/uikit/SDL_uikitopenglview.h @@ -21,29 +21,35 @@ #import #import -#import +#import + #import "SDL_uikitview.h" -/* - This class wraps the CAEAGLLayer from CoreAnimation into a convenient UIView subclass. - The view content is basically an EAGL surface you render your OpenGL scene into. - Note that setting the view non-opaque will only work if the EAGL surface has an alpha channel. - */ +#include "SDL_uikitvideo.h" + +@class SDL_uikitopenglview; + +@interface SDLEAGLContext : EAGLContext + +@property (nonatomic, weak) SDL_uikitopenglview *sdlView; + +@end + @interface SDL_uikitopenglview : SDL_uikitview -- (id)initWithFrame:(CGRect)frame - scale:(CGFloat)scale - retainBacking:(BOOL)retained - rBits:(int)rBits - gBits:(int)gBits - bBits:(int)bBits - aBits:(int)aBits - depthBits:(int)depthBits - stencilBits:(int)stencilBits - sRGB:(BOOL)sRGB - majorVersion:(int)majorVersion - shareGroup:(EAGLSharegroup*)shareGroup; +- (instancetype)initWithFrame:(CGRect)frame + scale:(CGFloat)scale + retainBacking:(BOOL)retained + rBits:(int)rBits + gBits:(int)gBits + bBits:(int)bBits + aBits:(int)aBits + depthBits:(int)depthBits + stencilBits:(int)stencilBits + sRGB:(BOOL)sRGB + majorVersion:(int)majorVersion + shareGroup:(EAGLSharegroup*)shareGroup; -@property (nonatomic, strong, readonly) EAGLContext *context; +@property (nonatomic, readonly, strong) SDLEAGLContext *context; /* The width and height of the drawable in pixels (as opposed to points.) */ @property (nonatomic, readonly) int backingWidth; @@ -57,17 +63,6 @@ - (void)updateFrame; -- (void)setDebugLabels; - -- (void)setAnimationCallback:(int)interval - callback:(void (*)(void*))callback - callbackParam:(void*)callbackParam; - -- (void)startAnimation; -- (void)stopAnimation; - -- (void)doLoop:(CADisplayLink*)sender; - @end /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/uikit/SDL_uikitopenglview.m b/src/video/uikit/SDL_uikitopenglview.m index 542c7d88b..df848f3d5 100644 --- a/src/video/uikit/SDL_uikitopenglview.m +++ b/src/video/uikit/SDL_uikitopenglview.m @@ -22,34 +22,27 @@ #if SDL_VIDEO_DRIVER_UIKIT -#include #include -#include #include -#include "SDL_uikitopenglview.h" -#include "SDL_uikitmessagebox.h" -#include "SDL_uikitvideo.h" +#import "SDL_uikitopenglview.h" +#include "SDL_uikitwindow.h" +@implementation SDLEAGLContext + +@end @implementation SDL_uikitopenglview { - - /* OpenGL names for the renderbuffer and framebuffers used to render to this view */ + /* The renderbuffer and framebuffer used to render to this layer. */ GLuint viewRenderbuffer, viewFramebuffer; - /* OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist) */ + /* The depth buffer that is attached to viewFramebuffer, if it exists. */ GLuint depthRenderbuffer; /* format of depthRenderbuffer */ GLenum depthBufferFormat; - - id displayLink; - int animationInterval; - void (*animationCallback)(void*); - void *animationCallbackParam; } @synthesize context; - @synthesize backingWidth; @synthesize backingHeight; @@ -58,38 +51,36 @@ return [CAEAGLLayer class]; } -- (id)initWithFrame:(CGRect)frame - scale:(CGFloat)scale - retainBacking:(BOOL)retained - rBits:(int)rBits - gBits:(int)gBits - bBits:(int)bBits - aBits:(int)aBits - depthBits:(int)depthBits - stencilBits:(int)stencilBits - sRGB:(BOOL)sRGB - majorVersion:(int)majorVersion - shareGroup:(EAGLSharegroup*)shareGroup +- (instancetype)initWithFrame:(CGRect)frame + scale:(CGFloat)scale + retainBacking:(BOOL)retained + rBits:(int)rBits + gBits:(int)gBits + bBits:(int)bBits + aBits:(int)aBits + depthBits:(int)depthBits + stencilBits:(int)stencilBits + sRGB:(BOOL)sRGB + majorVersion:(int)majorVersion + shareGroup:(EAGLSharegroup*)shareGroup { if ((self = [super initWithFrame:frame])) { const BOOL useStencilBuffer = (stencilBits != 0); const BOOL useDepthBuffer = (depthBits != 0); NSString *colorFormat = nil; - self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - self.autoresizesSubviews = YES; - /* The EAGLRenderingAPI enum values currently map 1:1 to major GLES - versions, and this allows us to handle future OpenGL ES versions. - */ + * versions, and this allows us to handle future OpenGL ES versions. */ EAGLRenderingAPI api = majorVersion; - context = [[EAGLContext alloc] initWithAPI:api sharegroup:shareGroup]; + context = [[SDLEAGLContext alloc] initWithAPI:api sharegroup:shareGroup]; if (!context || ![EAGLContext setCurrentContext:context]) { SDL_SetError("OpenGL ES %d not supported", majorVersion); return nil; } + context.sdlView = self; + if (sRGB) { /* sRGB EAGL drawable support was added in iOS 7. */ if (UIKit_IsSystemVersionAtLeast(7.0)) { @@ -102,11 +93,10 @@ /* if user specifically requests rbg888 or some color format higher than 16bpp */ colorFormat = kEAGLColorFormatRGBA8; } else { - /* default case (faster) */ + /* default case (potentially faster) */ colorFormat = kEAGLColorFormatRGB565; } - /* Get the layer */ CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; eaglLayer.opaque = YES; @@ -142,7 +132,8 @@ /* Apparently you need to pack stencil and depth into one buffer. */ depthBufferFormat = GL_DEPTH24_STENCIL8_OES; } else if (useDepthBuffer) { - /* iOS only has 24-bit depth buffers, even with GL_DEPTH_COMPONENT16 */ + /* iOS only uses 32-bit float (exposed as fixed point 24-bit) + * depth buffers. */ depthBufferFormat = GL_DEPTH_COMPONENT24_OES; } @@ -186,7 +177,7 @@ glGetIntegerv(GL_RENDERBUFFER_BINDING, &prevRenderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer); - [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; + [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer]; glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth); glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight); @@ -218,42 +209,6 @@ } } -- (void)setAnimationCallback:(int)interval - callback:(void (*)(void*))callback - callbackParam:(void*)callbackParam -{ - [self stopAnimation]; - - animationInterval = interval; - animationCallback = callback; - animationCallbackParam = callbackParam; - - if (animationCallback) { - [self startAnimation]; - } -} - -- (void)startAnimation -{ - displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)]; - [displayLink setFrameInterval:animationInterval]; - [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; -} - -- (void)stopAnimation -{ - [displayLink invalidate]; - displayLink = nil; -} - -- (void)doLoop:(CADisplayLink*)sender -{ - /* Don't run the game loop while a messagebox is up */ - if (!UIKit_ShowingMessageBox()) { - animationCallback(animationCallbackParam); - } -} - - (void)setCurrentContext { [EAGLContext setCurrentContext:context]; @@ -262,8 +217,8 @@ - (void)swapBuffers { /* viewRenderbuffer should always be bound here. Code that binds something - else is responsible for rebinding viewRenderbuffer, to reduce duplicate - state changes. */ + * else is responsible for rebinding viewRenderbuffer, to reduce duplicate + * state changes. */ [context presentRenderbuffer:GL_RENDERBUFFER]; } @@ -271,14 +226,21 @@ { [super layoutSubviews]; - CGSize layersize = self.layer.bounds.size; - int width = (int) (layersize.width * self.layer.contentsScale); - int height = (int) (layersize.height * self.layer.contentsScale); + int width = (int) (self.bounds.size.width * self.contentScaleFactor); + int height = (int) (self.bounds.size.height * self.contentScaleFactor); /* Update the color and depth buffer storage if the layer size has changed. */ if (width != backingWidth || height != backingHeight) { - [EAGLContext setCurrentContext:context]; + EAGLContext *prevContext = [EAGLContext currentContext]; + if (prevContext != context) { + [EAGLContext setCurrentContext:context]; + } + [self updateFrame]; + + if (prevContext != context) { + [EAGLContext setCurrentContext:prevContext]; + } } } @@ -300,7 +262,6 @@ } } - - (void)dealloc { if ([EAGLContext currentContext] == context) { diff --git a/src/video/uikit/SDL_uikitview.h b/src/video/uikit/SDL_uikitview.h index adee330be..dda94b32e 100644 --- a/src/video/uikit/SDL_uikitview.h +++ b/src/video/uikit/SDL_uikitview.h @@ -20,46 +20,22 @@ */ #import + #include "../SDL_sysvideo.h" #include "SDL_touch.h" -#if SDL_IPHONE_KEYBOARD -@interface SDL_uikitview : UIView -#else @interface SDL_uikitview : UIView -#endif -@property (nonatomic, assign) SDL_Window *sdlwindow; +- (instancetype)initWithFrame:(CGRect)frame; + +- (void)setSDLWindow:(SDL_Window *)window; - (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; -#if SDL_IPHONE_KEYBOARD -- (void)showKeyboard; -- (void)hideKeyboard; -- (void)initKeyboard; -- (void)deinitKeyboard; - -- (void)keyboardWillShow:(NSNotification *)notification; -- (void)keyboardWillHide:(NSNotification *)notification; - -- (void)updateKeyboard; - -@property (nonatomic, assign, getter=isKeyboardVisible) BOOL keyboardVisible; -@property (nonatomic, assign) SDL_Rect textInputRect; -@property (nonatomic, assign) int keyboardHeight; - -SDL_bool UIKit_HasScreenKeyboardSupport(_THIS); -void UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window); -void UIKit_HideScreenKeyboard(_THIS, SDL_Window *window); -SDL_bool UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window); -void UIKit_SetTextInputRect(_THIS, SDL_Rect *rect); - -#endif - @end /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/uikit/SDL_uikitview.m b/src/video/uikit/SDL_uikitview.m index d7679c19e..8bd1f3198 100644 --- a/src/video/uikit/SDL_uikitview.m +++ b/src/video/uikit/SDL_uikitview.m @@ -24,36 +24,26 @@ #include "SDL_uikitview.h" -#include "../../events/SDL_keyboard_c.h" #include "../../events/SDL_mouse_c.h" #include "../../events/SDL_touch_c.h" +#include "../../events/SDL_events_c.h" -#if SDL_IPHONE_KEYBOARD -#include "keyinfotable.h" -#endif -#include "SDL_uikitappdelegate.h" -#include "SDL_uikitmodes.h" -#include "SDL_uikitwindow.h" +#import "SDL_uikitappdelegate.h" +#import "SDL_uikitmodes.h" +#import "SDL_uikitwindow.h" @implementation SDL_uikitview { + SDL_Window *sdlwindow; SDL_TouchID touchId; - UITouch *leftFingerDown; - -#if SDL_IPHONE_KEYBOARD - UITextField *textField; -#endif - + UITouch * __weak firstFingerDown; } -@synthesize sdlwindow; - -- (id)initWithFrame:(CGRect)frame +- (instancetype)initWithFrame:(CGRect)frame { - if (self = [super initWithFrame:frame]) { -#if SDL_IPHONE_KEYBOARD - [self initKeyboard]; -#endif + if ((self = [super initWithFrame:frame])) { + self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + self.autoresizesSubviews = YES; self.multipleTouchEnabled = YES; @@ -62,14 +52,65 @@ } return self; - } -- (void)dealloc +- (void)setSDLWindow:(SDL_Window *)window { -#if SDL_IPHONE_KEYBOARD - [self deinitKeyboard]; -#endif + SDL_WindowData *data = nil; + + if (window == sdlwindow) { + return; + } + + if (sdlwindow) { + SDL_uikitview *view = nil; + data = (__bridge SDL_WindowData *) sdlwindow->driverdata; + + [data.views removeObject:self]; + + [self removeFromSuperview]; + + /* Restore the next-oldest view in the old window. */ + if (data.views.count > 0) { + view = data.views[data.views.count - 1]; + } + + data.viewcontroller.view = view; + + if (data.uiwindow.rootViewController != data.viewcontroller) { + data.uiwindow.rootViewController = data.viewcontroller; + } else if (view) { + [data.uiwindow addSubview:view]; + } + + [data.uiwindow layoutIfNeeded]; + } + + if (window) { + data = (__bridge SDL_WindowData *) window->driverdata; + + /* Make sure the SDL window has a strong reference to this view. */ + [data.views addObject:self]; + + /* Replace the view controller's old view with this one. */ + [data.viewcontroller.view removeFromSuperview]; + data.viewcontroller.view = self; + + if (data.uiwindow.rootViewController != data.viewcontroller) { + /* The root view controller handles rotation and the status bar. + * Assigning it also adds the controller's view to the window. */ + data.uiwindow.rootViewController = data.viewcontroller; + } else { + [data.uiwindow addSubview:self]; + } + + /* The view's bounds may not be correct until the next event cycle. That + * might happen after the current dimensions are queried, so we force a + * layout now to immediately update the bounds. */ + [data.uiwindow layoutIfNeeded]; + } + + sdlwindow = window; } - (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize @@ -88,16 +129,16 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { for (UITouch *touch in touches) { - if (!leftFingerDown) { + if (!firstFingerDown) { CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO]; - /* send moved event */ + /* send mouse moved event */ SDL_SendMouseMotion(sdlwindow, SDL_TOUCH_MOUSEID, 0, locationInView.x, locationInView.y); /* send mouse down event */ SDL_SendMouseButton(sdlwindow, SDL_TOUCH_MOUSEID, SDL_PRESSED, SDL_BUTTON_LEFT); - leftFingerDown = touch; + firstFingerDown = touch; } CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES]; @@ -109,10 +150,10 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { for (UITouch *touch in touches) { - if (touch == leftFingerDown) { + if (touch == firstFingerDown) { /* send mouse up */ SDL_SendMouseButton(sdlwindow, SDL_TOUCH_MOUSEID, SDL_RELEASED, SDL_BUTTON_LEFT); - leftFingerDown = nil; + firstFingerDown = nil; } CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES]; @@ -123,18 +164,13 @@ - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - /* - this can happen if the user puts more than 5 touches on the screen - at once, or perhaps in other circumstances. Usually (it seems) - all active touches are canceled. - */ [self touchesEnded:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { for (UITouch *touch in touches) { - if (touch == leftFingerDown) { + if (touch == firstFingerDown) { CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO]; /* send moved event */ @@ -147,274 +183,8 @@ } } -/* - ---- Keyboard related functionality below this line ---- -*/ -#if SDL_IPHONE_KEYBOARD - -@synthesize textInputRect; -@synthesize keyboardHeight; -@synthesize keyboardVisible; - -/* Set ourselves up as a UITextFieldDelegate */ -- (void)initKeyboard -{ - textField = [[UITextField alloc] initWithFrame:CGRectZero]; - textField.delegate = self; - /* placeholder so there is something to delete! */ - textField.text = @" "; - - /* set UITextInputTrait properties, mostly to defaults */ - textField.autocapitalizationType = UITextAutocapitalizationTypeNone; - textField.autocorrectionType = UITextAutocorrectionTypeNo; - textField.enablesReturnKeyAutomatically = NO; - textField.keyboardAppearance = UIKeyboardAppearanceDefault; - textField.keyboardType = UIKeyboardTypeDefault; - textField.returnKeyType = UIReturnKeyDefault; - textField.secureTextEntry = NO; - - textField.hidden = YES; - keyboardVisible = NO; - /* add the UITextField (hidden) to our view */ - [self addSubview:textField]; - - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; - [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; -} - -- (void)deinitKeyboard -{ - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center removeObserver:self name:UIKeyboardWillShowNotification object:nil]; - [center removeObserver:self name:UIKeyboardWillHideNotification object:nil]; -} - -/* reveal onscreen virtual keyboard */ -- (void)showKeyboard -{ - keyboardVisible = YES; - [textField becomeFirstResponder]; -} - -/* hide onscreen virtual keyboard */ -- (void)hideKeyboard -{ - keyboardVisible = NO; - [textField resignFirstResponder]; -} - -- (void)keyboardWillShow:(NSNotification *)notification -{ - CGRect kbrect = [[notification userInfo][UIKeyboardFrameBeginUserInfoKey] CGRectValue]; - int height; - - /* The keyboard rect is in the coordinate space of the screen, but we want - * its height in the view's coordinate space. - */ -#ifdef __IPHONE_8_0 - if ([self respondsToSelector:@selector(convertRect:fromCoordinateSpace:)]) { - UIScreen *screen = self.window.screen; - kbrect = [self convertRect:kbrect fromCoordinateSpace:screen.coordinateSpace]; - height = kbrect.size.height; - } else -#endif - { - /* In iOS 7 and below, the screen's coordinate space is never rotated. */ - if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) { - height = kbrect.size.width; - } else { - height = kbrect.size.height; - } - } - - [self setKeyboardHeight:height]; -} - - - (void)keyboardWillHide:(NSNotification *)notification -{ - [self setKeyboardHeight:0]; -} - -- (void)updateKeyboard -{ - SDL_Rect textrect = self.textInputRect; - CGAffineTransform t = self.transform; - CGPoint offset = CGPointMake(0.0, 0.0); - - if (self.keyboardHeight) { - int rectbottom = textrect.y + textrect.h; - int kbottom = self.bounds.size.height - self.keyboardHeight; - if (kbottom < rectbottom) { - offset.y = kbottom - rectbottom; - } - } - - /* Put the offset into the this view transform's coordinate space. */ - t.tx = 0.0; - t.ty = 0.0; - offset = CGPointApplyAffineTransform(offset, t); - - t.tx = offset.x; - t.ty = offset.y; - - /* Move the view by applying the updated transform. */ - self.transform = t; -} - -- (void)setKeyboardHeight:(int)height -{ - keyboardVisible = height > 0; - keyboardHeight = height; - [self updateKeyboard]; -} - -/* UITextFieldDelegate method. Invoked when user types something. */ -- (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string -{ - NSUInteger len = string.length; - - if (len == 0) { - /* it wants to replace text with nothing, ie a delete */ - SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_BACKSPACE); - SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_BACKSPACE); - } else { - /* go through all the characters in the string we've been sent - and convert them to key presses */ - for (int i = 0; i < len; i++) { - unichar c = [string characterAtIndex:i]; - Uint16 mod = 0; - SDL_Scancode code; - - if (c < 127) { - /* figure out the SDL_Scancode and SDL_keymod for this unichar */ - code = unicharToUIKeyInfoTable[c].code; - mod = unicharToUIKeyInfoTable[c].mod; - } - else { - /* we only deal with ASCII right now */ - code = SDL_SCANCODE_UNKNOWN; - mod = 0; - } - - if (mod & KMOD_SHIFT) { - /* If character uses shift, press shift down */ - SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT); - } - - /* send a keydown and keyup even for the character */ - SDL_SendKeyboardKey(SDL_PRESSED, code); - SDL_SendKeyboardKey(SDL_RELEASED, code); - - if (mod & KMOD_SHIFT) { - /* If character uses shift, press shift back up */ - SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT); - } - } - - SDL_SendKeyboardText([string UTF8String]); - } - - return NO; /* don't allow the edit! (keep placeholder text there) */ -} - -/* Terminates the editing session */ -- (BOOL)textFieldShouldReturn:(UITextField*)_textField -{ - SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_RETURN); - SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RETURN); - SDL_StopTextInput(); - return YES; -} - -#endif - @end -/* iPhone keyboard addition functions */ -#if SDL_IPHONE_KEYBOARD - -static SDL_uikitview * -GetWindowView(SDL_Window * window) -{ - if (window == NULL) { - SDL_SetError("Window does not exist"); - return nil; - } - - SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata; - SDL_uikitview *view = data != nil ? data.view : nil; - - if (view == nil) { - SDL_SetError("Window has no view"); - } - - return view; -} - -SDL_bool -UIKit_HasScreenKeyboardSupport(_THIS) -{ - return SDL_TRUE; -} - -void -UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window) -{ - @autoreleasepool { - SDL_uikitview *view = GetWindowView(window); - if (view != nil) { - [view showKeyboard]; - } - } -} - -void -UIKit_HideScreenKeyboard(_THIS, SDL_Window *window) -{ - @autoreleasepool { - SDL_uikitview *view = GetWindowView(window); - if (view != nil) { - [view hideKeyboard]; - } - } -} - -SDL_bool -UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window) -{ - @autoreleasepool { - SDL_uikitview *view = GetWindowView(window); - if (view != nil) { - return view.isKeyboardVisible; - } - return 0; - } -} - -void -UIKit_SetTextInputRect(_THIS, SDL_Rect *rect) -{ - if (!rect) { - SDL_InvalidParamError("rect"); - return; - } - - @autoreleasepool { - SDL_uikitview *view = GetWindowView(SDL_GetFocusWindow()); - if (view != nil) { - view.textInputRect = *rect; - - if (view.keyboardVisible) { - [view updateKeyboard]; - } - } - } -} - - -#endif /* SDL_IPHONE_KEYBOARD */ - #endif /* SDL_VIDEO_DRIVER_UIKIT */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/uikit/SDL_uikitviewcontroller.h b/src/video/uikit/SDL_uikitviewcontroller.h index 0ca98eb40..b8cf3dc55 100644 --- a/src/video/uikit/SDL_uikitviewcontroller.h +++ b/src/video/uikit/SDL_uikitviewcontroller.h @@ -23,11 +23,27 @@ #include "../SDL_sysvideo.h" +#include "SDL_touch.h" + +#if SDL_IPHONE_KEYBOARD +@interface SDL_uikitviewcontroller : UIViewController +#else @interface SDL_uikitviewcontroller : UIViewController +#endif @property (nonatomic, assign) SDL_Window *window; -- (id)initWithSDLWindow:(SDL_Window *)_window; +- (instancetype)initWithSDLWindow:(SDL_Window *)_window; + +- (void)setAnimationCallback:(int)interval + callback:(void (*)(void*))callback + callbackParam:(void*)callbackParam; + +- (void)startAnimation; +- (void)stopAnimation; + +- (void)doLoop:(CADisplayLink*)sender; + - (void)loadView; - (void)viewDidLayoutSubviews; - (NSUInteger)supportedInterfaceOrientations; @@ -35,4 +51,28 @@ - (BOOL)prefersStatusBarHidden; - (UIStatusBarStyle)preferredStatusBarStyle; +#if SDL_IPHONE_KEYBOARD +- (void)showKeyboard; +- (void)hideKeyboard; +- (void)initKeyboard; +- (void)deinitKeyboard; + +- (void)keyboardWillShow:(NSNotification *)notification; +- (void)keyboardWillHide:(NSNotification *)notification; + +- (void)updateKeyboard; + +@property (nonatomic, assign, getter=isKeyboardVisible) BOOL keyboardVisible; +@property (nonatomic, assign) SDL_Rect textInputRect; +@property (nonatomic, assign) int keyboardHeight; +#endif + @end + +#if SDL_IPHONE_KEYBOARD +SDL_bool UIKit_HasScreenKeyboardSupport(_THIS); +void UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window); +void UIKit_HideScreenKeyboard(_THIS, SDL_Window *window); +SDL_bool UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window); +void UIKit_SetTextInputRect(_THIS, SDL_Rect *rect); +#endif diff --git a/src/video/uikit/SDL_uikitviewcontroller.m b/src/video/uikit/SDL_uikitviewcontroller.m index 77cbc0ba5..af1f68dc1 100644 --- a/src/video/uikit/SDL_uikitviewcontroller.m +++ b/src/video/uikit/SDL_uikitviewcontroller.m @@ -28,27 +28,87 @@ #include "../SDL_sysvideo.h" #include "../../events/SDL_events_c.h" -#include "SDL_uikitviewcontroller.h" +#import "SDL_uikitviewcontroller.h" +#import "SDL_uikitmessagebox.h" #include "SDL_uikitvideo.h" #include "SDL_uikitmodes.h" #include "SDL_uikitwindow.h" +#if SDL_IPHONE_KEYBOARD +#include "keyinfotable.h" +#endif -@implementation SDL_uikitviewcontroller +@implementation SDL_uikitviewcontroller { + CADisplayLink *displayLink; + int animationInterval; + void (*animationCallback)(void*); + void *animationCallbackParam; + +#if SDL_IPHONE_KEYBOARD + UITextField *textField; +#endif +} @synthesize window; -- (id)initWithSDLWindow:(SDL_Window *)_window +- (instancetype)initWithSDLWindow:(SDL_Window *)_window { if (self = [super initWithNibName:nil bundle:nil]) { self.window = _window; + +#if SDL_IPHONE_KEYBOARD + [self initKeyboard]; +#endif } return self; } +- (void)dealloc +{ +#if SDL_IPHONE_KEYBOARD + [self deinitKeyboard]; +#endif +} + +- (void)setAnimationCallback:(int)interval + callback:(void (*)(void*))callback + callbackParam:(void*)callbackParam +{ + [self stopAnimation]; + + animationInterval = interval; + animationCallback = callback; + animationCallbackParam = callbackParam; + + if (animationCallback) { + [self startAnimation]; + } +} + +- (void)startAnimation +{ + displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)]; + [displayLink setFrameInterval:animationInterval]; + [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; +} + +- (void)stopAnimation +{ + [displayLink invalidate]; + displayLink = nil; +} + +- (void)doLoop:(CADisplayLink*)sender +{ + /* Don't run the game loop while a messagebox is up */ + if (!UIKit_ShowingMessageBox()) { + animationCallback(animationCallbackParam); + } +} + - (void)loadView { - /* do nothing. */ + /* Do nothing. */ } - (void)viewDidLayoutSubviews @@ -67,8 +127,7 @@ - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient { - NSUInteger orientationMask = [self supportedInterfaceOrientations]; - return (orientationMask & (1 << orient)); + return ([self supportedInterfaceOrientations] & (1 << orient)) != 0; } - (BOOL)prefersStatusBarHidden @@ -82,8 +141,276 @@ return UIStatusBarStyleLightContent; } +/* + ---- Keyboard related functionality below this line ---- + */ +#if SDL_IPHONE_KEYBOARD + +@synthesize textInputRect; +@synthesize keyboardHeight; +@synthesize keyboardVisible; + +/* Set ourselves up as a UITextFieldDelegate */ +- (void)initKeyboard +{ + textField = [[UITextField alloc] initWithFrame:CGRectZero]; + textField.delegate = self; + /* placeholder so there is something to delete! */ + textField.text = @" "; + + /* set UITextInputTrait properties, mostly to defaults */ + textField.autocapitalizationType = UITextAutocapitalizationTypeNone; + textField.autocorrectionType = UITextAutocorrectionTypeNo; + textField.enablesReturnKeyAutomatically = NO; + textField.keyboardAppearance = UIKeyboardAppearanceDefault; + textField.keyboardType = UIKeyboardTypeDefault; + textField.returnKeyType = UIReturnKeyDefault; + textField.secureTextEntry = NO; + + textField.hidden = YES; + keyboardVisible = NO; + + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; + [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; +} + +- (void)setView:(UIView *)view +{ + [super setView:view]; + + [view addSubview:textField]; + + if (keyboardVisible) { + [self showKeyboard]; + } +} + +- (void)deinitKeyboard +{ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center removeObserver:self name:UIKeyboardWillShowNotification object:nil]; + [center removeObserver:self name:UIKeyboardWillHideNotification object:nil]; +} + +/* reveal onscreen virtual keyboard */ +- (void)showKeyboard +{ + keyboardVisible = YES; + if (textField.window) { + [textField becomeFirstResponder]; + } +} + +/* hide onscreen virtual keyboard */ +- (void)hideKeyboard +{ + keyboardVisible = NO; + [textField resignFirstResponder]; +} + +- (void)keyboardWillShow:(NSNotification *)notification +{ + CGRect kbrect = [[notification userInfo][UIKeyboardFrameBeginUserInfoKey] CGRectValue]; + UIView *view = self.view; + int height = 0; + + /* The keyboard rect is in the coordinate space of the screen, but we want + * its height in the view's coordinate space. */ +#ifdef __IPHONE_8_0 + if ([view respondsToSelector:@selector(convertRect:fromCoordinateSpace:)]) { + UIScreen *screen = view.window.screen; + kbrect = [view convertRect:kbrect fromCoordinateSpace:screen.coordinateSpace]; + height = kbrect.size.height; + } else +#endif + { + /* In iOS 7 and below, the screen's coordinate space is never rotated. */ + if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { + height = kbrect.size.width; + } else { + height = kbrect.size.height; + } + } + + [self setKeyboardHeight:height]; +} + +- (void)keyboardWillHide:(NSNotification *)notification +{ + [self setKeyboardHeight:0]; +} + +- (void)updateKeyboard +{ + SDL_Rect textrect = self.textInputRect; + CGAffineTransform t = self.view.transform; + CGPoint offset = CGPointMake(0.0, 0.0); + + if (self.keyboardHeight) { + int rectbottom = textrect.y + textrect.h; + int kbottom = self.view.bounds.size.height - self.keyboardHeight; + if (kbottom < rectbottom) { + offset.y = kbottom - rectbottom; + } + } + + /* Put the offset into the this view transform's coordinate space. */ + t.tx = 0.0; + t.ty = 0.0; + offset = CGPointApplyAffineTransform(offset, t); + + t.tx = offset.x; + t.ty = offset.y; + + /* Move the view by applying the updated transform. */ + self.view.transform = t; +} + +- (void)setKeyboardHeight:(int)height +{ + keyboardVisible = height > 0; + keyboardHeight = height; + [self updateKeyboard]; +} + +/* UITextFieldDelegate method. Invoked when user types something. */ +- (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string +{ + NSUInteger len = string.length; + + if (len == 0) { + /* it wants to replace text with nothing, ie a delete */ + SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_BACKSPACE); + SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_BACKSPACE); + } else { + /* go through all the characters in the string we've been sent and + * convert them to key presses */ + int i; + for (i = 0; i < len; i++) { + unichar c = [string characterAtIndex:i]; + Uint16 mod = 0; + SDL_Scancode code; + + if (c < 127) { + /* figure out the SDL_Scancode and SDL_keymod for this unichar */ + code = unicharToUIKeyInfoTable[c].code; + mod = unicharToUIKeyInfoTable[c].mod; + } else { + /* we only deal with ASCII right now */ + code = SDL_SCANCODE_UNKNOWN; + mod = 0; + } + + if (mod & KMOD_SHIFT) { + /* If character uses shift, press shift down */ + SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT); + } + + /* send a keydown and keyup even for the character */ + SDL_SendKeyboardKey(SDL_PRESSED, code); + SDL_SendKeyboardKey(SDL_RELEASED, code); + + if (mod & KMOD_SHIFT) { + /* If character uses shift, press shift back up */ + SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT); + } + } + + SDL_SendKeyboardText([string UTF8String]); + } + + return NO; /* don't allow the edit! (keep placeholder text there) */ +} + +/* Terminates the editing session */ +- (BOOL)textFieldShouldReturn:(UITextField*)_textField +{ + SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_RETURN); + SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RETURN); + SDL_StopTextInput(); + return YES; +} + +#endif + @end +/* iPhone keyboard addition functions */ +#if SDL_IPHONE_KEYBOARD + +static SDL_uikitviewcontroller * +GetWindowViewController(SDL_Window * window) +{ + if (!window || !window->driverdata) { + SDL_SetError("Invalid window"); + return nil; + } + + SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata; + + return data.viewcontroller; +} + +SDL_bool +UIKit_HasScreenKeyboardSupport(_THIS) +{ + return SDL_TRUE; +} + +void +UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window) +{ + @autoreleasepool { + SDL_uikitviewcontroller *vc = GetWindowViewController(window); + [vc showKeyboard]; + } +} + +void +UIKit_HideScreenKeyboard(_THIS, SDL_Window *window) +{ + @autoreleasepool { + SDL_uikitviewcontroller *vc = GetWindowViewController(window); + [vc hideKeyboard]; + } +} + +SDL_bool +UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window) +{ + @autoreleasepool { + SDL_uikitviewcontroller *vc = GetWindowViewController(window); + if (vc != nil) { + return vc.isKeyboardVisible; + } + return SDL_FALSE; + } +} + +void +UIKit_SetTextInputRect(_THIS, SDL_Rect *rect) +{ + if (!rect) { + SDL_InvalidParamError("rect"); + return; + } + + @autoreleasepool { + SDL_uikitviewcontroller *vc = GetWindowViewController(SDL_GetFocusWindow()); + if (vc != nil) { + vc.textInputRect = *rect; + + if (vc.keyboardVisible) { + [vc updateKeyboard]; + } + } + } +} + + +#endif /* SDL_IPHONE_KEYBOARD */ + #endif /* SDL_VIDEO_DRIVER_UIKIT */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/uikit/SDL_uikitwindow.h b/src/video/uikit/SDL_uikitwindow.h index cc7d8839a..73203d189 100644 --- a/src/video/uikit/SDL_uikitwindow.h +++ b/src/video/uikit/SDL_uikitwindow.h @@ -23,7 +23,7 @@ #include "../SDL_sysvideo.h" #import "SDL_uikitvideo.h" -#import "SDL_uikitopenglview.h" +#import "SDL_uikitview.h" #import "SDL_uikitviewcontroller.h" extern int UIKit_CreateWindow(_THIS, SDL_Window * window); @@ -44,9 +44,11 @@ extern NSUInteger UIKit_GetSupportedOrientations(SDL_Window * window); @interface SDL_WindowData : NSObject @property (nonatomic, strong) UIWindow *uiwindow; -@property (nonatomic, strong) SDL_uikitopenglview *view; @property (nonatomic, strong) SDL_uikitviewcontroller *viewcontroller; +/* Array of SDL_uikitviews owned by this window. */ +@property (nonatomic, copy) NSMutableArray *views; + @end #endif /* _SDL_uikitwindow_h */ diff --git a/src/video/uikit/SDL_uikitwindow.m b/src/video/uikit/SDL_uikitwindow.m index 849c90888..550b6f91c 100644 --- a/src/video/uikit/SDL_uikitwindow.m +++ b/src/video/uikit/SDL_uikitwindow.m @@ -37,15 +37,41 @@ #include "SDL_uikitwindow.h" #import "SDL_uikitappdelegate.h" -#import "SDL_uikitopenglview.h" +#import "SDL_uikitview.h" #include @implementation SDL_WindowData @synthesize uiwindow; -@synthesize view; @synthesize viewcontroller; +@synthesize views; + +- (instancetype)init +{ + if ((self = [super init])) { + views = [NSMutableArray new]; + } + + return self; +} + +@end + +@interface SDL_uikitwindow : UIWindow + +- (void)layoutSubviews; + +@end + +@implementation SDL_uikitwindow + +- (void)layoutSubviews +{ + /* Workaround to fix window orientation issues in iOS 8+. */ + self.frame = self.screen.bounds; + [super layoutSubviews]; +} @end @@ -54,65 +80,67 @@ static int SetupWindowData(_THIS, SDL_Window *window, UIWindow *uiwindow, SDL_bo { SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window); SDL_DisplayData *displaydata = (__bridge SDL_DisplayData *) display->driverdata; + SDL_uikitview *view; + + CGRect frame = UIKit_ComputeViewFrame(window, displaydata.uiscreen); + int width = (int) frame.size.width; + int height = (int) frame.size.height; SDL_WindowData *data = [[SDL_WindowData alloc] init]; if (!data) { return SDL_OutOfMemory(); } - data.uiwindow = uiwindow; - - /* Fill in the SDL window with the window data */ - { - CGRect frame = UIKit_ComputeViewFrame(window, displaydata.uiscreen); - - int width = (int) frame.size.width; - int height = (int) frame.size.height; - - /* Make sure the width/height are oriented correctly */ - if (UIKit_IsDisplayLandscape(displaydata.uiscreen) != (width > height)) { - int temp = width; - width = height; - height = temp; - } - - window->x = 0; - window->y = 0; - window->w = width; - window->h = height; - } - window->driverdata = (void *) CFBridgingRetain(data); + data.uiwindow = uiwindow; + /* only one window on iOS, always shown */ window->flags &= ~SDL_WINDOW_HIDDEN; - /* SDL_WINDOW_BORDERLESS controls whether status bar is hidden. - * This is only set if the window is on the main screen. Other screens - * just force the window to have the borderless flag. - */ if (displaydata.uiscreen == [UIScreen mainScreen]) { window->flags |= SDL_WINDOW_INPUT_FOCUS; /* always has input focus */ - - /* This was setup earlier for our window, and in iOS 7 is controlled by the view, not the application - if ([UIApplication sharedApplication].statusBarHidden) { - window->flags |= SDL_WINDOW_BORDERLESS; - } else { - window->flags &= ~SDL_WINDOW_BORDERLESS; - } - */ } else { - window->flags &= ~SDL_WINDOW_RESIZABLE; /* window is NEVER resizeable */ + window->flags &= ~SDL_WINDOW_RESIZABLE; /* window is NEVER resizable */ window->flags &= ~SDL_WINDOW_INPUT_FOCUS; /* never has input focus */ window->flags |= SDL_WINDOW_BORDERLESS; /* never has a status bar. */ } - /* The View Controller will handle rotating the view when the - * device orientation changes. This will trigger resize events, if - * appropriate. - */ + if (displaydata.uiscreen == [UIScreen mainScreen]) { + NSUInteger orients = UIKit_GetSupportedOrientations(window); + BOOL supportsLandscape = (orients & UIInterfaceOrientationMaskLandscape) != 0; + BOOL supportsPortrait = (orients & (UIInterfaceOrientationMaskPortrait|UIInterfaceOrientationMaskPortraitUpsideDown)) != 0; + + /* Make sure the width/height are oriented correctly */ + if ((width > height && !supportsLandscape) || (height > width && !supportsPortrait)) { + int temp = width; + width = height; + height = temp; + } + } + + window->x = 0; + window->y = 0; + window->w = width; + window->h = height; + + /* The View Controller will handle rotating the view when the device + * orientation changes. This will trigger resize events, if appropriate. */ data.viewcontroller = [[SDL_uikitviewcontroller alloc] initWithSDLWindow:window]; - data.viewcontroller.title = @""; + + /* The window will initially contain a generic view so resizes, touch events, + * etc. can be handled without an active OpenGL view/context. */ + view = [[SDL_uikitview alloc] initWithFrame:frame]; + + /* Sets this view as the controller's view, and adds the view to the window + * heirarchy. */ + [view setSDLWindow:window]; + + /* Make this window the current mouse focus for touch input */ + if (displaydata.uiscreen == [UIScreen mainScreen]) { + SDL_SetMouseFocus(window); + SDL_SetKeyboardFocus(window); + } return 0; } @@ -123,8 +151,7 @@ UIKit_CreateWindow(_THIS, SDL_Window *window) @autoreleasepool { SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window); SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata; - const BOOL external = ([UIScreen mainScreen] != data.uiscreen); - const CGSize origsize = [[data.uiscreen currentMode] size]; + const CGSize origsize = data.uiscreen.currentMode.size; /* SDL currently puts this window at the start of display's linked list. We rely on this. */ SDL_assert(_this->windows == window); @@ -136,8 +163,7 @@ UIKit_CreateWindow(_THIS, SDL_Window *window) /* If monitor has a resolution of 0x0 (hasn't been explicitly set by the * user, so it's in standby), try to force the display to a resolution - * that most closely matches the desired window size. - */ + * that most closely matches the desired window size. */ if ((origsize.width == 0.0f) && (origsize.height == 0.0f)) { if (display->num_display_modes == 0) { _this->GetDisplayModes(_this, display); @@ -147,8 +173,9 @@ UIKit_CreateWindow(_THIS, SDL_Window *window) const SDL_DisplayMode *bestmode = NULL; for (i = display->num_display_modes; i >= 0; i--) { const SDL_DisplayMode *mode = &display->display_modes[i]; - if ((mode->w >= window->w) && (mode->h >= window->h)) + if ((mode->w >= window->w) && (mode->h >= window->h)) { bestmode = mode; + } } if (bestmode) { @@ -157,8 +184,7 @@ UIKit_CreateWindow(_THIS, SDL_Window *window) /* desktop_mode doesn't change here (the higher level will * use it to set all the screens back to their defaults - * upon window destruction, SDL_Quit(), etc. - */ + * upon window destruction, SDL_Quit(), etc. */ display->current_mode = *bestmode; } } @@ -172,36 +198,14 @@ UIKit_CreateWindow(_THIS, SDL_Window *window) } else { app.statusBarHidden = NO; } - - /* Make sure the screen is using a supported orientation. We do it - * now so that SetupWindowData assigns the properly oriented width - * and height to the window's w and h variables. - */ - if (UIKit_IsDisplayLandscape(data.uiscreen)) { - if (!(orientations & UIInterfaceOrientationMaskLandscape)) { - [app setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO]; - } - } else { - if (!(orientations & (UIInterfaceOrientationMaskPortrait|UIInterfaceOrientationMaskPortraitUpsideDown))) { - UIInterfaceOrientation orient = UIInterfaceOrientationLandscapeLeft; - if (orientations & UIInterfaceOrientationMaskLandscapeRight) { - orient = UIInterfaceOrientationLandscapeRight; - } - [app setStatusBarOrientation:orient animated:NO]; - } - } } /* ignore the size user requested, and make a fullscreen window */ /* !!! FIXME: can we have a smaller view? */ - UIWindow *uiwindow = [[UIWindow alloc] initWithFrame:data.uiscreen.bounds]; + UIWindow *uiwindow = [[SDL_uikitwindow alloc] initWithFrame:data.uiscreen.bounds]; - /* put the window on an external display if appropriate. This implicitly - * does [uiwindow setframe:[uiscreen bounds]], so don't do it on the - * main display, where we land by default, as that would eat the - * status bar real estate. - */ - if (external) { + /* put the window on an external display if appropriate. */ + if (data.uiscreen != [UIScreen mainScreen]) { [uiwindow setScreen:data.uiscreen]; } @@ -217,11 +221,11 @@ void UIKit_SetWindowTitle(_THIS, SDL_Window * window) { @autoreleasepool { - SDL_uikitviewcontroller *vc = ((__bridge SDL_WindowData *) window->driverdata).viewcontroller; + SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; if (window->title) { - vc.title = @(window->title); + data.viewcontroller.title = @(window->title); } else { - vc.title = @""; + data.viewcontroller.title = nil; } } } @@ -230,8 +234,8 @@ void UIKit_ShowWindow(_THIS, SDL_Window * window) { @autoreleasepool { - UIWindow *uiwindow = ((__bridge SDL_WindowData *) window->driverdata).uiwindow; - [uiwindow makeKeyAndVisible]; + SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; + [data.uiwindow makeKeyAndVisible]; } } @@ -239,8 +243,8 @@ void UIKit_HideWindow(_THIS, SDL_Window * window) { @autoreleasepool { - UIWindow *uiwindow = ((__bridge SDL_WindowData *) window->driverdata).uiwindow; - uiwindow.hidden = YES; + SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; + data.uiwindow.hidden = YES; } } @@ -248,21 +252,19 @@ void UIKit_RaiseWindow(_THIS, SDL_Window * window) { /* We don't currently offer a concept of "raising" the SDL window, since - * we only allow one per display, in the iOS fashion. + * we only allow one per display, in the iOS fashion. * However, we use this entry point to rebind the context to the view - * during OnWindowRestored processing. - */ + * during OnWindowRestored processing. */ _this->GL_MakeCurrent(_this, _this->current_glwin, _this->current_glctx); } static void UIKit_UpdateWindowBorder(_THIS, SDL_Window * window) { - SDL_WindowData *windowdata = (__bridge SDL_WindowData *) window->driverdata; - SDL_uikitviewcontroller *viewcontroller = windowdata.viewcontroller; - CGRect frame; + SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; + SDL_uikitviewcontroller *viewcontroller = data.viewcontroller; - if (windowdata.uiwindow.screen == [UIScreen mainScreen]) { + if (data.uiwindow.screen == [UIScreen mainScreen]) { if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) { [UIApplication sharedApplication].statusBarHidden = YES; } else { @@ -276,24 +278,9 @@ UIKit_UpdateWindowBorder(_THIS, SDL_Window * window) } /* Update the view's frame to account for the status bar change. */ - frame = UIKit_ComputeViewFrame(window, windowdata.uiwindow.screen); - - windowdata.view.frame = frame; - [windowdata.view setNeedsLayout]; - [windowdata.view layoutIfNeeded]; - - /* Get frame dimensions */ - int w = (int) frame.size.width; - int h = (int) frame.size.height; - - /* We can pick either width or height here and we'll rotate the - screen to match, so we pick the closest to what we wanted. - */ - if (window->w >= window->h) { - SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, SDL_max(w, h), SDL_min(w, h)); - } else { - SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, SDL_min(w, h), SDL_max(w, h)); - } + viewcontroller.view.frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen); + [viewcontroller.view setNeedsLayout]; + [viewcontroller.view layoutIfNeeded]; } void @@ -317,7 +304,8 @@ UIKit_DestroyWindow(_THIS, SDL_Window * window) { @autoreleasepool { if (window->driverdata != NULL) { - CFRelease(window->driverdata); + SDL_WindowData *data = (SDL_WindowData *) CFBridgingRelease(window->driverdata); + [data.viewcontroller stopAnimation]; } } window->driverdata = NULL; @@ -327,11 +315,11 @@ SDL_bool UIKit_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info) { @autoreleasepool { - UIWindow *uiwindow = ((__bridge SDL_WindowData *) window->driverdata).uiwindow; + SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; if (info->version.major <= SDL_MAJOR_VERSION) { info->subsystem = SDL_SYSWM_UIKIT; - info->info.uikit.window = uiwindow; + info->info.uikit.window = data.uiwindow; return SDL_TRUE; } else { SDL_SetError("Application not compiled with SDL %d.%d\n", @@ -380,7 +368,7 @@ UIKit_GetSupportedOrientations(SDL_Window * window) } /* Don't allow upside-down orientation on the phone, so answering calls is in the natural orientation */ - if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { + if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) { orientationMask &= ~UIInterfaceOrientationMaskPortraitUpsideDown; } } @@ -391,14 +379,15 @@ UIKit_GetSupportedOrientations(SDL_Window * window) int SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam) { + if (!window || !window->driverdata) { + return SDL_SetError("Invalid window"); + } + @autoreleasepool { - SDL_WindowData *data = window ? (__bridge SDL_WindowData *)window->driverdata : nil; - - if (!data || !data.view) { - return SDL_SetError("Invalid window or view not set"); - } - - [data.view setAnimationCallback:interval callback:callback callbackParam:callbackParam]; + SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata; + [data.viewcontroller setAnimationCallback:interval + callback:callback + callbackParam:callbackParam]; } return 0;