Several improvements to the iOS backend:

- Added new custom launch screen code. It uses the launch screen nib when available on iOS 8+, the launch images dictionary if the launch screen nib isn't available, and the old standard image names if the launch image dictionary isn't in the plist.
The launch screen is now hidden during the first call to SDL_PumpEvents rather than SDL_CreateWindow so apps can have the launch screen still visible if they do time-consuming loading after creating their window. It also fades out in roughly the same way as the system launch screen behavior.
It can be disabled by setting the SDL_IPHONE_LAUNCHSCREEN define in SDL_config_iphoneos.h to 0.

- A blank UIView is now created and displayed when the window is first created. The old behavior was to defer creating any view until SDL_GL_CreateContext, which prevented rotation, touch events, and other windowing-related things from working until then. This also makes it easier to use SDL_GetWindowWMInfo after creating a window.

- Moved the keyboard and animation callback code from SDL's UIView subclasses to its UIViewController subclass, which lets them work properly in all cases when a SDL window is valid, even before SDL_GL_CreateContext is called and after SDL_GL_DeleteContext is called.

- SDL_GL_CreateContext, SDL_GL_SwapWindow, SDL_GL_MakeCurrent, and SDL_GL_DeleteContext are more robust.

- Fixed some edge cases where SDL windows weren't rotating properly or their reported sizes were out of sync with their actual sizes.

- Removed all calls to [UIApplication setStatusBarOrientation:]. It doesn't seem to work as expected in all cases in recent iOS versions.

- Some code style cleanup.
This commit is contained in:
Alex Szpakowski 2015-01-15 01:06:14 -04:00
parent cc0631a095
commit 01bfc2158a
16 changed files with 984 additions and 736 deletions

View File

@ -145,6 +145,9 @@
/* enable iPhone keyboard support */ /* enable iPhone keyboard support */
#define SDL_IPHONE_KEYBOARD 1 #define SDL_IPHONE_KEYBOARD 1
/* enable iOS extended launch screen */
#define SDL_IPHONE_LAUNCHSCREEN 1
/* enable joystick subsystem */ /* enable joystick subsystem */
#define SDL_JOYSTICK_DISABLED 0 #define SDL_JOYSTICK_DISABLED 0

View File

@ -126,6 +126,9 @@
#ifndef SDL_IPHONE_KEYBOARD #ifndef SDL_IPHONE_KEYBOARD
#define SDL_IPHONE_KEYBOARD 1 #define SDL_IPHONE_KEYBOARD 1
#endif #endif
#ifndef SDL_IPHONE_LAUNCHSCREEN
#define SDL_IPHONE_LAUNCHSCREEN 1
#endif
#ifndef SDL_POWER_UIKIT #ifndef SDL_POWER_UIKIT
#define SDL_POWER_UIKIT 1 #define SDL_POWER_UIKIT 1
#endif #endif

View File

@ -1100,22 +1100,22 @@ SDL_RestoreMousePosition(SDL_Window *window)
} }
} }
static void static int
SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen) SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen)
{ {
SDL_VideoDisplay *display; SDL_VideoDisplay *display;
SDL_Window *other; 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 we are in the process of hiding don't go back to fullscreen */
if ( window->is_hiding && fullscreen ) if ( window->is_hiding && fullscreen )
return; return 0;
#ifdef __MACOSX__ #ifdef __MACOSX__
if (Cocoa_SetWindowFullscreenSpace(window, fullscreen)) { if (Cocoa_SetWindowFullscreenSpace(window, fullscreen)) {
window->last_fullscreen_flags = window->flags; window->last_fullscreen_flags = window->flags;
return; return 0;
} }
#endif #endif
@ -1132,7 +1132,7 @@ SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen)
/* See if anything needs to be done now */ /* See if anything needs to be done now */
if ((display->fullscreen_window == window) == fullscreen) { if ((display->fullscreen_window == window) == fullscreen) {
if ((window->last_fullscreen_flags & FULLSCREEN_MASK) == (window->flags & FULLSCREEN_MASK)) { 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 */ /* only do the mode change if we want exclusive fullscreen */
if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP) { 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 { } else {
SDL_SetDisplayModeForDisplay(display, NULL); if (SDL_SetDisplayModeForDisplay(display, NULL) < 0) {
return -1;
}
} }
if (_this->SetWindowFullscreen) { if (_this->SetWindowFullscreen) {
@ -1182,7 +1186,7 @@ SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen)
SDL_RestoreMousePosition(other); SDL_RestoreMousePosition(other);
window->last_fullscreen_flags = window->flags; window->last_fullscreen_flags = window->flags;
return; return 0;
} }
} }
} }
@ -1202,6 +1206,7 @@ SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen)
SDL_RestoreMousePosition(window); SDL_RestoreMousePosition(window);
window->last_fullscreen_flags = window->flags; window->last_fullscreen_flags = window->flags;
return 0;
} }
#define CREATE_FLAGS \ #define CREATE_FLAGS \
@ -1927,9 +1932,7 @@ SDL_SetWindowFullscreen(SDL_Window * window, Uint32 flags)
window->flags &= ~FULLSCREEN_MASK; window->flags &= ~FULLSCREEN_MASK;
window->flags |= flags; window->flags |= flags;
SDL_UpdateFullscreenMode(window, FULLSCREEN_VISIBLE(window)); return SDL_UpdateFullscreenMode(window, FULLSCREEN_VISIBLE(window));
return 0;
} }
static SDL_Surface * static SDL_Surface *

View File

@ -21,12 +21,22 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface SDLUIKitDelegate : NSObject<UIApplicationDelegate> { @interface SDLLaunchScreenController : UIViewController
}
- (instancetype)init;
- (void)loadView;
- (BOOL)shouldAutorotate;
- (NSUInteger)supportedInterfaceOrientations;
@end
@interface SDLUIKitDelegate : NSObject<UIApplicationDelegate>
+ (id)sharedAppDelegate; + (id)sharedAppDelegate;
+ (NSString *)getAppDelegateClassName; + (NSString *)getAppDelegateClassName;
- (void)hideLaunchScreen;
@end @end
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */

View File

@ -28,8 +28,10 @@
#include "SDL_system.h" #include "SDL_system.h"
#include "SDL_main.h" #include "SDL_main.h"
#include "SDL_uikitappdelegate.h" #import "SDL_uikitappdelegate.h"
#include "SDL_uikitmodes.h" #import "SDL_uikitmodes.h"
#import "SDL_uikitwindow.h"
#include "../../events/SDL_events_c.h" #include "../../events/SDL_events_c.h"
#ifdef main #ifdef main
@ -74,46 +76,256 @@ SDL_IdleTimerDisabledChanged(void *userdata, const char *name, const char *oldVa
[UIApplication sharedApplication].idleTimerDisabled = disable; [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 */ /* convenience method */
+ (id)sharedAppDelegate + (id)sharedAppDelegate
{ {
/* the delegate is set in UIApplicationMain(), which is guaranteed to be called before this method */ /* the delegate is set in UIApplicationMain(), which is guaranteed to be
return [[UIApplication sharedApplication] delegate]; * called before this method */
return [UIApplication sharedApplication].delegate;
} }
+ (NSString *)getAppDelegateClassName + (NSString *)getAppDelegateClassName
{ {
/* subclassing notice: when you subclass this appdelegate, make sure to add a category to override /* subclassing notice: when you subclass this appdelegate, make sure to add
this method and return the actual name of the delegate */ * a category to override this method and return the actual name of the
* delegate */
return @"SDLUIKitDelegate"; return @"SDLUIKitDelegate";
} }
- (id)init - (void)hideLaunchScreen
{ {
self = [super init]; UIWindow *window = launchWindow;
return self;
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 - (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 */ /* run the user's application, passing argc and argv */
SDL_iPhoneSetEventPump(SDL_TRUE); SDL_iPhoneSetEventPump(SDL_TRUE);
exit_status = SDL_main(forward_argc, forward_argv); exit_status = SDL_main(forward_argc, forward_argv);
SDL_iPhoneSetEventPump(SDL_FALSE); SDL_iPhoneSetEventPump(SDL_FALSE);
if (launchWindow) {
launchWindow.hidden = YES;
launchWindow = nil;
}
/* exit, passing the return status from the user's application */ /* exit, passing the return status from the user's application */
/* We don't actually exit to support applications that do setup in /* We don't actually exit to support applications that do setup in their
* their main function and then allow the Cocoa event loop to run. * main function and then allow the Cocoa event loop to run. */
*/
/* exit(exit_status); */ /* exit(exit_status); */
} }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions - (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 */ /* Set working directory to resource path */
[[NSFileManager defaultManager] changeCurrentDirectoryPath:[[NSBundle mainBundle] resourcePath]]; [[NSFileManager defaultManager] changeCurrentDirectoryPath:[bundle resourcePath]];
/* register a callback for the idletimer hint */ /* register a callback for the idletimer hint */
SDL_AddHintCallback(SDL_HINT_IDLE_TIMER_DISABLED, SDL_AddHintCallback(SDL_HINT_IDLE_TIMER_DISABLED,
@ -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 /* The desktop display mode should be kept in sync with the screen
* orientation so that updating a window's fullscreen state to * orientation so that updating a window's fullscreen state to
* SDL_WINDOW_FULLSCREEN_DESKTOP keeps the window dimensions in the * SDL_WINDOW_FULLSCREEN_DESKTOP keeps the window dimensions in the
* correct orientation. * correct orientation. */
*/
if (isLandscape != (desktopmode->w > desktopmode->h)) { if (isLandscape != (desktopmode->w > desktopmode->h)) {
int height = desktopmode->w; int height = desktopmode->w;
desktopmode->w = desktopmode->h; desktopmode->w = desktopmode->h;
@ -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 - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{ {
NSURL *fileURL = [url filePathURL]; NSURL *fileURL = url.filePathURL;
if (fileURL != nil) { if (fileURL != nil) {
SDL_SendDropFile([[fileURL path] UTF8String]); SDL_SendDropFile([fileURL.path UTF8String]);
} else { } else {
SDL_SendDropFile([[url absoluteString] UTF8String]); SDL_SendDropFile([url.absoluteString UTF8String]);
} }
return YES; return YES;
} }

View File

@ -30,35 +30,20 @@
static SDL_bool s_showingMessageBox = SDL_FALSE; static SDL_bool s_showingMessageBox = SDL_FALSE;
@interface UIKit_UIAlertViewDelegate : NSObject <UIAlertViewDelegate> @interface SDLAlertViewDelegate : NSObject <UIAlertViewDelegate>
- (id)initWithButtonIndex:(int *)buttonIndex; @property (nonatomic, assign) int clickedIndex;
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex;
@end @end
@implementation UIKit_UIAlertViewDelegate { @implementation SDLAlertViewDelegate
int *clickedButtonIndex;
}
- (id)initWithButtonIndex:(int *)buttonIndex
{
self = [self init];
if (self == nil) {
return nil;
}
clickedButtonIndex = buttonIndex;
return self;
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{ {
*clickedButtonIndex = (int)buttonIndex; _clickedIndex = (int)buttonIndex;
} }
@end /* UIKit_UIAlertViewDelegate */ @end
SDL_bool SDL_bool
@ -70,36 +55,35 @@ UIKit_ShowingMessageBox()
int int
UIKit_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) UIKit_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
{ {
int clicked;
int i; int i;
const SDL_MessageBoxButtonData *buttons = messageboxdata->buttons; const SDL_MessageBoxButtonData *buttons = messageboxdata->buttons;
@autoreleasepool { @autoreleasepool {
UIAlertView *alert = [[UIAlertView alloc] init]; UIAlertView *alert = [[UIAlertView alloc] init];
UIKit_UIAlertViewDelegate *delegate = [[UIKit_UIAlertViewDelegate alloc] initWithButtonIndex:&clicked]; SDLAlertViewDelegate *delegate = [[SDLAlertViewDelegate alloc] init];
alert.delegate = delegate;
alert.title = @(messageboxdata->title); alert.title = @(messageboxdata->title);
alert.message = @(messageboxdata->message); alert.message = @(messageboxdata->message);
alert.delegate = delegate;
for (i = 0; i < messageboxdata->numbuttons; ++i) { for (i = 0; i < messageboxdata->numbuttons; ++i) {
[alert addButtonWithTitle:@(buttons[i].text)]; [alert addButtonWithTitle:@(buttons[i].text)];
} }
/* Set up for showing the alert */ /* Set up for showing the alert */
clicked = messageboxdata->numbuttons; delegate.clickedIndex = messageboxdata->numbuttons;
[alert show]; [alert show];
/* Run the main event loop until the alert has finished */ /* Run the main event loop until the alert has finished */
/* Note that this needs to be done on the main thread */ /* Note that this needs to be done on the main thread */
s_showingMessageBox = SDL_TRUE; s_showingMessageBox = SDL_TRUE;
while (clicked == messageboxdata->numbuttons) { while (delegate.clickedIndex == messageboxdata->numbuttons) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} }
s_showingMessageBox = SDL_FALSE; s_showingMessageBox = SDL_FALSE;
*buttonid = messageboxdata->buttons[clicked].buttonid; *buttonid = messageboxdata->buttons[delegate.clickedIndex].buttonid;
alert.delegate = nil; alert.delegate = nil;
} }

View File

@ -112,7 +112,7 @@ UIKit_AddDisplayMode(SDL_VideoDisplay * display, int w, int h,
static int static int
UIKit_AddDisplay(UIScreen *uiscreen) UIKit_AddDisplay(UIScreen *uiscreen)
{ {
CGSize size = [uiscreen bounds].size; CGSize size = uiscreen.bounds.size;
/* Make sure the width/height are oriented correctly */ /* Make sure the width/height are oriented correctly */
if (UIKit_IsDisplayLandscape(uiscreen) != (size.width > size.height)) { if (UIKit_IsDisplayLandscape(uiscreen) != (size.width > size.height)) {
@ -128,7 +128,7 @@ UIKit_AddDisplay(UIScreen *uiscreen)
mode.w = (int) size.width; mode.w = (int) size.width;
mode.h = (int) size.height; mode.h = (int) size.height;
UIScreenMode *uiscreenmode = [uiscreen currentMode]; UIScreenMode *uiscreenmode = uiscreen.currentMode;
if (UIKit_AllocateDisplayModeData(&mode, uiscreenmode) < 0) { if (UIKit_AllocateDisplayModeData(&mode, uiscreenmode) < 0) {
return -1; return -1;
@ -157,9 +157,9 @@ SDL_bool
UIKit_IsDisplayLandscape(UIScreen *uiscreen) UIKit_IsDisplayLandscape(UIScreen *uiscreen)
{ {
if (uiscreen == [UIScreen mainScreen]) { if (uiscreen == [UIScreen mainScreen]) {
return UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]); return UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation);
} else { } else {
CGSize size = [uiscreen bounds].size; CGSize size = uiscreen.bounds.size;
return (size.width > size.height); return (size.width > size.height);
} }
} }
@ -196,7 +196,7 @@ UIKit_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
} }
#endif #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 /* The size of a UIScreenMode is in pixels, but we deal exclusively
* in points (except in SDL_GL_GetDrawableSize.) */ * in points (except in SDL_GL_GetDrawableSize.) */
int w = (int)(uimode.size.width / scale); int w = (int)(uimode.size.width / scale);
@ -209,7 +209,6 @@ UIKit_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
h = tmp; h = tmp;
} }
/* Add the native screen resolution. */
UIKit_AddDisplayMode(display, w, h, uimode, addRotation); 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]; [data.uiscreen setCurrentMode:modedata.uiscreenmode];
if (data.uiscreen == [UIScreen mainScreen]) { 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 (mode->w > mode->h) {
if (!UIKit_IsDisplayLandscape(data.uiscreen)) { 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) { } else if (mode->w < mode->h) {
if (UIKit_IsDisplayLandscape(data.uiscreen)) { if (UIKit_IsDisplayLandscape(data.uiscreen)) {
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO]; return SDL_SetError("Screen orientation does not match display mode size");
} }
} }
} }

View File

@ -23,10 +23,10 @@
#if SDL_VIDEO_DRIVER_UIKIT #if SDL_VIDEO_DRIVER_UIKIT
#include "SDL_uikitopengles.h" #include "SDL_uikitopengles.h"
#include "SDL_uikitopenglview.h" #import "SDL_uikitopenglview.h"
#include "SDL_uikitappdelegate.h"
#include "SDL_uikitmodes.h" #include "SDL_uikitmodes.h"
#include "SDL_uikitwindow.h" #include "SDL_uikitwindow.h"
#include "SDL_uikitevents.h"
#include "../SDL_sysvideo.h" #include "../SDL_sysvideo.h"
#include "../../events/SDL_keyboard_c.h" #include "../../events/SDL_keyboard_c.h"
#include "../../events/SDL_mouse_c.h" #include "../../events/SDL_mouse_c.h"
@ -40,21 +40,29 @@ void *
UIKit_GL_GetProcAddress(_THIS, const char *proc) UIKit_GL_GetProcAddress(_THIS, const char *proc)
{ {
/* Look through all SO's for the proc symbol. Here's why: /* 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. * -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. * -We don't know that the path won't change in the future. */
*/
return dlsym(RTLD_DEFAULT, proc); 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 int
UIKit_GL_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context) UIKit_GL_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context)
{ {
@autoreleasepool { @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; return 0;
} }
@ -63,27 +71,26 @@ UIKit_GL_GetDrawableSize(_THIS, SDL_Window * window, int * w, int * h)
{ {
@autoreleasepool { @autoreleasepool {
SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata; SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
UIView *view = data.viewcontroller.view;
if ([view isKindOfClass:[SDL_uikitopenglview class]]) {
SDL_uikitopenglview *glview = (SDL_uikitopenglview *) view;
if (w) { if (w) {
*w = data.view.backingWidth; *w = glview.backingWidth;
} }
if (h) { if (h) {
*h = data.view.backingHeight; *h = glview.backingHeight;
}
} }
} }
} }
int int
UIKit_GL_LoadLibrary(_THIS, const char *path) UIKit_GL_LoadLibrary(_THIS, const char *path)
{ {
/* /* We shouldn't pass a path to this function, since we've already loaded the
shouldn't be passing a path into this function * library. */
why? Because we've already loaded the library
and because the SDK forbids loading an external SO
*/
if (path != NULL) { 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; return 0;
} }
@ -91,22 +98,18 @@ UIKit_GL_LoadLibrary(_THIS, const char *path)
void UIKit_GL_SwapWindow(_THIS, SDL_Window * window) void UIKit_GL_SwapWindow(_THIS, SDL_Window * window)
{ {
@autoreleasepool { @autoreleasepool {
SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata; SDLEAGLContext *context = (__bridge SDLEAGLContext *) SDL_GL_GetCurrentContext();
#if SDL_POWER_UIKIT #if SDL_POWER_UIKIT
/* Check once a frame to see if we should turn off the battery monitor. */ /* Check once a frame to see if we should turn off the battery monitor. */
SDL_UIKit_UpdateBatteryMonitoring(); SDL_UIKit_UpdateBatteryMonitoring();
#endif #endif
if (data.view == nil) { [context.sdlView swapBuffers];
return;
}
[data.view swapBuffers];
/* You need to pump events in order for the OS to make changes visible. /* 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 * We don't pump events here because we don't want iOS application events
(low memory, terminate, etc.) to happen inside low level rendering. * (low memory, terminate, etc.) to happen inside low level rendering. */
*/
} }
} }
@ -116,29 +119,27 @@ UIKit_GL_CreateContext(_THIS, SDL_Window * window)
@autoreleasepool { @autoreleasepool {
SDL_uikitopenglview *view; SDL_uikitopenglview *view;
SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
UIWindow *uiwindow = data.uiwindow; CGRect frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen);
CGRect frame = UIKit_ComputeViewFrame(window, uiwindow.screen); EAGLSharegroup *sharegroup = nil;
EAGLSharegroup *share_group = nil;
CGFloat scale = 1.0; 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) { if (_this->gl_config.share_with_current_context) {
EAGLContext *context = (__bridge EAGLContext *) SDL_GL_GetCurrentContext(); 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 */ /* 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 stencilBits:_this->gl_config.stencil_size
sRGB:_this->gl_config.framebuffer_srgb_capable sRGB:_this->gl_config.framebuffer_srgb_capable
majorVersion:_this->gl_config.major_version majorVersion:_this->gl_config.major_version
shareGroup:share_group]; shareGroup:sharegroup];
if (!view) { if (!view) {
return NULL; return NULL;
} }
view.sdlwindow = window; SDLEAGLContext *context = view.context;
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;
if (UIKit_GL_MakeCurrent(_this, window, (__bridge SDL_GLContext) context) < 0) { if (UIKit_GL_MakeCurrent(_this, window, (__bridge SDL_GLContext) context) < 0) {
UIKit_GL_DeleteContext(_this, (SDL_GLContext) CFBridgingRetain(context)); UIKit_GL_DeleteContext(_this, (SDL_GLContext) CFBridgingRetain(context));
return NULL; return NULL;
} }
/* Make this window the current mouse focus for touch input */ /* We return a +1'd context. The window's driverdata owns the view (via
if (uiwindow.screen == [UIScreen mainScreen]) { * MakeCurrent.) */
SDL_SetMouseFocus(window);
SDL_SetKeyboardFocus(window);
}
/* We return a +1'd context. The window's driverdata owns the view. */
return (SDL_GLContext) CFBridgingRetain(context); return (SDL_GLContext) CFBridgingRetain(context);
} }
} }
@ -191,29 +176,10 @@ UIKit_GL_DeleteContext(_THIS, SDL_GLContext context)
{ {
@autoreleasepool { @autoreleasepool {
/* Transfer ownership the +1'd context to ARC. */ /* Transfer ownership the +1'd context to ARC. */
EAGLContext *eaglcontext = (EAGLContext *) CFBridgingRelease(context); SDLEAGLContext *eaglcontext = (SDLEAGLContext *) CFBridgingRelease(context);
SDL_Window *window;
/* Find the view associated with this context */ /* Detach the context's view from its window. */
for (window = _this->windows; window; window = window->next) { [eaglcontext.sdlView setSDLWindow:NULL];
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;
}
}
} }
} }
@ -227,13 +193,16 @@ SDL_iPhoneGetViewRenderbuffer(SDL_Window * window)
@autoreleasepool { @autoreleasepool {
SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
if (data.view != nil) { UIView *view = data.viewcontroller.view;
return data.view.drawableRenderbuffer; if ([view isKindOfClass:[SDL_uikitopenglview class]]) {
} else { SDL_uikitopenglview *glview = (SDL_uikitopenglview *) view;
return glview.drawableRenderbuffer;
}
}
SDL_SetError("Window does not have an attached OpenGL view");
return 0; return 0;
} }
}
}
Uint32 Uint32
SDL_iPhoneGetViewFramebuffer(SDL_Window * window) SDL_iPhoneGetViewFramebuffer(SDL_Window * window)
@ -245,13 +214,16 @@ SDL_iPhoneGetViewFramebuffer(SDL_Window * window)
@autoreleasepool { @autoreleasepool {
SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
if (data.view != nil) { UIView *view = data.viewcontroller.view;
return data.view.drawableFramebuffer; if ([view isKindOfClass:[SDL_uikitopenglview class]]) {
} else { SDL_uikitopenglview *glview = (SDL_uikitopenglview *) view;
return glview.drawableFramebuffer;
}
}
SDL_SetError("Window does not have an attached OpenGL view");
return 0; return 0;
} }
}
}
#endif /* SDL_VIDEO_DRIVER_UIKIT */ #endif /* SDL_VIDEO_DRIVER_UIKIT */

View File

@ -21,16 +21,22 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h> #import <OpenGLES/EAGL.h>
#import <OpenGLES/gltypes.h> #import <OpenGLES/ES2/gl.h>
#import "SDL_uikitview.h" #import "SDL_uikitview.h"
/* #include "SDL_uikitvideo.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. @class SDL_uikitopenglview;
Note that setting the view non-opaque will only work if the EAGL surface has an alpha channel.
*/ @interface SDLEAGLContext : EAGLContext
@property (nonatomic, weak) SDL_uikitopenglview *sdlView;
@end
@interface SDL_uikitopenglview : SDL_uikitview @interface SDL_uikitopenglview : SDL_uikitview
- (id)initWithFrame:(CGRect)frame - (instancetype)initWithFrame:(CGRect)frame
scale:(CGFloat)scale scale:(CGFloat)scale
retainBacking:(BOOL)retained retainBacking:(BOOL)retained
rBits:(int)rBits rBits:(int)rBits
@ -43,7 +49,7 @@
majorVersion:(int)majorVersion majorVersion:(int)majorVersion
shareGroup:(EAGLSharegroup*)shareGroup; 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.) */ /* The width and height of the drawable in pixels (as opposed to points.) */
@property (nonatomic, readonly) int backingWidth; @property (nonatomic, readonly) int backingWidth;
@ -57,17 +63,6 @@
- (void)updateFrame; - (void)updateFrame;
- (void)setDebugLabels;
- (void)setAnimationCallback:(int)interval
callback:(void (*)(void*))callback
callbackParam:(void*)callbackParam;
- (void)startAnimation;
- (void)stopAnimation;
- (void)doLoop:(CADisplayLink*)sender;
@end @end
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */

View File

@ -22,34 +22,27 @@
#if SDL_VIDEO_DRIVER_UIKIT #if SDL_VIDEO_DRIVER_UIKIT
#include <QuartzCore/QuartzCore.h>
#include <OpenGLES/EAGLDrawable.h> #include <OpenGLES/EAGLDrawable.h>
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h> #include <OpenGLES/ES2/glext.h>
#include "SDL_uikitopenglview.h" #import "SDL_uikitopenglview.h"
#include "SDL_uikitmessagebox.h" #include "SDL_uikitwindow.h"
#include "SDL_uikitvideo.h"
@implementation SDLEAGLContext
@end
@implementation SDL_uikitopenglview { @implementation SDL_uikitopenglview {
/* The renderbuffer and framebuffer used to render to this layer. */
/* OpenGL names for the renderbuffer and framebuffers used to render to this view */
GLuint viewRenderbuffer, viewFramebuffer; 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; GLuint depthRenderbuffer;
/* format of depthRenderbuffer */ /* format of depthRenderbuffer */
GLenum depthBufferFormat; GLenum depthBufferFormat;
id displayLink;
int animationInterval;
void (*animationCallback)(void*);
void *animationCallbackParam;
} }
@synthesize context; @synthesize context;
@synthesize backingWidth; @synthesize backingWidth;
@synthesize backingHeight; @synthesize backingHeight;
@ -58,7 +51,7 @@
return [CAEAGLLayer class]; return [CAEAGLLayer class];
} }
- (id)initWithFrame:(CGRect)frame - (instancetype)initWithFrame:(CGRect)frame
scale:(CGFloat)scale scale:(CGFloat)scale
retainBacking:(BOOL)retained retainBacking:(BOOL)retained
rBits:(int)rBits rBits:(int)rBits
@ -76,20 +69,18 @@
const BOOL useDepthBuffer = (depthBits != 0); const BOOL useDepthBuffer = (depthBits != 0);
NSString *colorFormat = nil; NSString *colorFormat = nil;
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.autoresizesSubviews = YES;
/* The EAGLRenderingAPI enum values currently map 1:1 to major GLES /* 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; EAGLRenderingAPI api = majorVersion;
context = [[EAGLContext alloc] initWithAPI:api sharegroup:shareGroup]; context = [[SDLEAGLContext alloc] initWithAPI:api sharegroup:shareGroup];
if (!context || ![EAGLContext setCurrentContext:context]) { if (!context || ![EAGLContext setCurrentContext:context]) {
SDL_SetError("OpenGL ES %d not supported", majorVersion); SDL_SetError("OpenGL ES %d not supported", majorVersion);
return nil; return nil;
} }
context.sdlView = self;
if (sRGB) { if (sRGB) {
/* sRGB EAGL drawable support was added in iOS 7. */ /* sRGB EAGL drawable support was added in iOS 7. */
if (UIKit_IsSystemVersionAtLeast(7.0)) { if (UIKit_IsSystemVersionAtLeast(7.0)) {
@ -102,11 +93,10 @@
/* if user specifically requests rbg888 or some color format higher than 16bpp */ /* if user specifically requests rbg888 or some color format higher than 16bpp */
colorFormat = kEAGLColorFormatRGBA8; colorFormat = kEAGLColorFormatRGBA8;
} else { } else {
/* default case (faster) */ /* default case (potentially faster) */
colorFormat = kEAGLColorFormatRGB565; colorFormat = kEAGLColorFormatRGB565;
} }
/* Get the layer */
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
eaglLayer.opaque = YES; eaglLayer.opaque = YES;
@ -142,7 +132,8 @@
/* Apparently you need to pack stencil and depth into one buffer. */ /* Apparently you need to pack stencil and depth into one buffer. */
depthBufferFormat = GL_DEPTH24_STENCIL8_OES; depthBufferFormat = GL_DEPTH24_STENCIL8_OES;
} else if (useDepthBuffer) { } 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; depthBufferFormat = GL_DEPTH_COMPONENT24_OES;
} }
@ -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 - (void)setCurrentContext
{ {
[EAGLContext setCurrentContext:context]; [EAGLContext setCurrentContext:context];
@ -262,8 +217,8 @@
- (void)swapBuffers - (void)swapBuffers
{ {
/* viewRenderbuffer should always be bound here. Code that binds something /* viewRenderbuffer should always be bound here. Code that binds something
else is responsible for rebinding viewRenderbuffer, to reduce duplicate * else is responsible for rebinding viewRenderbuffer, to reduce duplicate
state changes. */ * state changes. */
[context presentRenderbuffer:GL_RENDERBUFFER]; [context presentRenderbuffer:GL_RENDERBUFFER];
} }
@ -271,14 +226,21 @@
{ {
[super layoutSubviews]; [super layoutSubviews];
CGSize layersize = self.layer.bounds.size; int width = (int) (self.bounds.size.width * self.contentScaleFactor);
int width = (int) (layersize.width * self.layer.contentsScale); int height = (int) (self.bounds.size.height * self.contentScaleFactor);
int height = (int) (layersize.height * self.layer.contentsScale);
/* Update the color and depth buffer storage if the layer size has changed. */ /* Update the color and depth buffer storage if the layer size has changed. */
if (width != backingWidth || height != backingHeight) { if (width != backingWidth || height != backingHeight) {
EAGLContext *prevContext = [EAGLContext currentContext];
if (prevContext != context) {
[EAGLContext setCurrentContext:context]; [EAGLContext setCurrentContext:context];
}
[self updateFrame]; [self updateFrame];
if (prevContext != context) {
[EAGLContext setCurrentContext:prevContext];
}
} }
} }
@ -300,7 +262,6 @@
} }
} }
- (void)dealloc - (void)dealloc
{ {
if ([EAGLContext currentContext] == context) { if ([EAGLContext currentContext] == context) {

View File

@ -20,46 +20,22 @@
*/ */
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#include "../SDL_sysvideo.h" #include "../SDL_sysvideo.h"
#include "SDL_touch.h" #include "SDL_touch.h"
#if SDL_IPHONE_KEYBOARD
@interface SDL_uikitview : UIView <UITextFieldDelegate>
#else
@interface SDL_uikitview : UIView @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; - (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(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 @end
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */

View File

@ -24,36 +24,26 @@
#include "SDL_uikitview.h" #include "SDL_uikitview.h"
#include "../../events/SDL_keyboard_c.h"
#include "../../events/SDL_mouse_c.h" #include "../../events/SDL_mouse_c.h"
#include "../../events/SDL_touch_c.h" #include "../../events/SDL_touch_c.h"
#include "../../events/SDL_events_c.h"
#if SDL_IPHONE_KEYBOARD #import "SDL_uikitappdelegate.h"
#include "keyinfotable.h" #import "SDL_uikitmodes.h"
#endif #import "SDL_uikitwindow.h"
#include "SDL_uikitappdelegate.h"
#include "SDL_uikitmodes.h"
#include "SDL_uikitwindow.h"
@implementation SDL_uikitview { @implementation SDL_uikitview {
SDL_Window *sdlwindow;
SDL_TouchID touchId; SDL_TouchID touchId;
UITouch *leftFingerDown; UITouch * __weak firstFingerDown;
#if SDL_IPHONE_KEYBOARD
UITextField *textField;
#endif
} }
@synthesize sdlwindow; - (instancetype)initWithFrame:(CGRect)frame
- (id)initWithFrame:(CGRect)frame
{ {
if (self = [super initWithFrame:frame]) { if ((self = [super initWithFrame:frame])) {
#if SDL_IPHONE_KEYBOARD self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self initKeyboard]; self.autoresizesSubviews = YES;
#endif
self.multipleTouchEnabled = YES; self.multipleTouchEnabled = YES;
@ -62,14 +52,65 @@
} }
return self; return self;
} }
- (void)dealloc - (void)setSDLWindow:(SDL_Window *)window
{ {
#if SDL_IPHONE_KEYBOARD SDL_WindowData *data = nil;
[self deinitKeyboard];
#endif 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 - (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize
@ -88,16 +129,16 @@
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{ {
for (UITouch *touch in touches) { for (UITouch *touch in touches) {
if (!leftFingerDown) { if (!firstFingerDown) {
CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO]; 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); SDL_SendMouseMotion(sdlwindow, SDL_TOUCH_MOUSEID, 0, locationInView.x, locationInView.y);
/* send mouse down event */ /* send mouse down event */
SDL_SendMouseButton(sdlwindow, SDL_TOUCH_MOUSEID, SDL_PRESSED, SDL_BUTTON_LEFT); SDL_SendMouseButton(sdlwindow, SDL_TOUCH_MOUSEID, SDL_PRESSED, SDL_BUTTON_LEFT);
leftFingerDown = touch; firstFingerDown = touch;
} }
CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES]; CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
@ -109,10 +150,10 @@
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{ {
for (UITouch *touch in touches) { for (UITouch *touch in touches) {
if (touch == leftFingerDown) { if (touch == firstFingerDown) {
/* send mouse up */ /* send mouse up */
SDL_SendMouseButton(sdlwindow, SDL_TOUCH_MOUSEID, SDL_RELEASED, SDL_BUTTON_LEFT); SDL_SendMouseButton(sdlwindow, SDL_TOUCH_MOUSEID, SDL_RELEASED, SDL_BUTTON_LEFT);
leftFingerDown = nil; firstFingerDown = nil;
} }
CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES]; CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
@ -123,18 +164,13 @@
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event - (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]; [self touchesEnded:touches withEvent:event];
} }
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{ {
for (UITouch *touch in touches) { for (UITouch *touch in touches) {
if (touch == leftFingerDown) { if (touch == firstFingerDown) {
CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO]; CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO];
/* send moved event */ /* 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 @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 */ #endif /* SDL_VIDEO_DRIVER_UIKIT */
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */

View File

@ -23,11 +23,27 @@
#include "../SDL_sysvideo.h" #include "../SDL_sysvideo.h"
#include "SDL_touch.h"
#if SDL_IPHONE_KEYBOARD
@interface SDL_uikitviewcontroller : UIViewController <UITextFieldDelegate>
#else
@interface SDL_uikitviewcontroller : UIViewController @interface SDL_uikitviewcontroller : UIViewController
#endif
@property (nonatomic, assign) SDL_Window *window; @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)loadView;
- (void)viewDidLayoutSubviews; - (void)viewDidLayoutSubviews;
- (NSUInteger)supportedInterfaceOrientations; - (NSUInteger)supportedInterfaceOrientations;
@ -35,4 +51,28 @@
- (BOOL)prefersStatusBarHidden; - (BOOL)prefersStatusBarHidden;
- (UIStatusBarStyle)preferredStatusBarStyle; - (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 @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

View File

@ -28,27 +28,87 @@
#include "../SDL_sysvideo.h" #include "../SDL_sysvideo.h"
#include "../../events/SDL_events_c.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_uikitvideo.h"
#include "SDL_uikitmodes.h" #include "SDL_uikitmodes.h"
#include "SDL_uikitwindow.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; @synthesize window;
- (id)initWithSDLWindow:(SDL_Window *)_window - (instancetype)initWithSDLWindow:(SDL_Window *)_window
{ {
if (self = [super initWithNibName:nil bundle:nil]) { if (self = [super initWithNibName:nil bundle:nil]) {
self.window = _window; self.window = _window;
#if SDL_IPHONE_KEYBOARD
[self initKeyboard];
#endif
} }
return self; 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 - (void)loadView
{ {
/* do nothing. */ /* Do nothing. */
} }
- (void)viewDidLayoutSubviews - (void)viewDidLayoutSubviews
@ -67,8 +127,7 @@
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient
{ {
NSUInteger orientationMask = [self supportedInterfaceOrientations]; return ([self supportedInterfaceOrientations] & (1 << orient)) != 0;
return (orientationMask & (1 << orient));
} }
- (BOOL)prefersStatusBarHidden - (BOOL)prefersStatusBarHidden
@ -82,8 +141,276 @@
return UIStatusBarStyleLightContent; 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 @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 */ #endif /* SDL_VIDEO_DRIVER_UIKIT */
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */

View File

@ -23,7 +23,7 @@
#include "../SDL_sysvideo.h" #include "../SDL_sysvideo.h"
#import "SDL_uikitvideo.h" #import "SDL_uikitvideo.h"
#import "SDL_uikitopenglview.h" #import "SDL_uikitview.h"
#import "SDL_uikitviewcontroller.h" #import "SDL_uikitviewcontroller.h"
extern int UIKit_CreateWindow(_THIS, SDL_Window * window); extern int UIKit_CreateWindow(_THIS, SDL_Window * window);
@ -44,9 +44,11 @@ extern NSUInteger UIKit_GetSupportedOrientations(SDL_Window * window);
@interface SDL_WindowData : NSObject @interface SDL_WindowData : NSObject
@property (nonatomic, strong) UIWindow *uiwindow; @property (nonatomic, strong) UIWindow *uiwindow;
@property (nonatomic, strong) SDL_uikitopenglview *view;
@property (nonatomic, strong) SDL_uikitviewcontroller *viewcontroller; @property (nonatomic, strong) SDL_uikitviewcontroller *viewcontroller;
/* Array of SDL_uikitviews owned by this window. */
@property (nonatomic, copy) NSMutableArray *views;
@end @end
#endif /* _SDL_uikitwindow_h */ #endif /* _SDL_uikitwindow_h */

View File

@ -37,15 +37,41 @@
#include "SDL_uikitwindow.h" #include "SDL_uikitwindow.h"
#import "SDL_uikitappdelegate.h" #import "SDL_uikitappdelegate.h"
#import "SDL_uikitopenglview.h" #import "SDL_uikitview.h"
#include <Foundation/Foundation.h> #include <Foundation/Foundation.h>
@implementation SDL_WindowData @implementation SDL_WindowData
@synthesize uiwindow; @synthesize uiwindow;
@synthesize view;
@synthesize viewcontroller; @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 @end
@ -54,65 +80,67 @@ static int SetupWindowData(_THIS, SDL_Window *window, UIWindow *uiwindow, SDL_bo
{ {
SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window); SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
SDL_DisplayData *displaydata = (__bridge SDL_DisplayData *) display->driverdata; 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]; SDL_WindowData *data = [[SDL_WindowData alloc] init];
if (!data) { if (!data) {
return SDL_OutOfMemory(); return SDL_OutOfMemory();
} }
window->driverdata = (void *) CFBridgingRetain(data);
data.uiwindow = uiwindow; data.uiwindow = uiwindow;
/* Fill in the SDL window with the window data */ /* only one window on iOS, always shown */
{ window->flags &= ~SDL_WINDOW_HIDDEN;
CGRect frame = UIKit_ComputeViewFrame(window, displaydata.uiscreen);
int width = (int) frame.size.width; if (displaydata.uiscreen == [UIScreen mainScreen]) {
int height = (int) frame.size.height; window->flags |= SDL_WINDOW_INPUT_FOCUS; /* always has input focus */
} else {
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. */
}
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 */ /* Make sure the width/height are oriented correctly */
if (UIKit_IsDisplayLandscape(displaydata.uiscreen) != (width > height)) { if ((width > height && !supportsLandscape) || (height > width && !supportsPortrait)) {
int temp = width; int temp = width;
width = height; width = height;
height = temp; height = temp;
} }
}
window->x = 0; window->x = 0;
window->y = 0; window->y = 0;
window->w = width; window->w = width;
window->h = height; window->h = height;
}
window->driverdata = (void *) CFBridgingRetain(data); /* The View Controller will handle rotating the view when the device
* orientation changes. This will trigger resize events, if appropriate. */
/* 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_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.
*/
data.viewcontroller = [[SDL_uikitviewcontroller alloc] initWithSDLWindow:window]; 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; return 0;
} }
@ -123,8 +151,7 @@ UIKit_CreateWindow(_THIS, SDL_Window *window)
@autoreleasepool { @autoreleasepool {
SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window); SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata; 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 currently puts this window at the start of display's linked list. We rely on this. */
SDL_assert(_this->windows == window); 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 /* 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 * 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 ((origsize.width == 0.0f) && (origsize.height == 0.0f)) {
if (display->num_display_modes == 0) { if (display->num_display_modes == 0) {
_this->GetDisplayModes(_this, display); _this->GetDisplayModes(_this, display);
@ -147,9 +173,10 @@ UIKit_CreateWindow(_THIS, SDL_Window *window)
const SDL_DisplayMode *bestmode = NULL; const SDL_DisplayMode *bestmode = NULL;
for (i = display->num_display_modes; i >= 0; i--) { for (i = display->num_display_modes; i >= 0; i--) {
const SDL_DisplayMode *mode = &display->display_modes[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; bestmode = mode;
} }
}
if (bestmode) { if (bestmode) {
SDL_DisplayModeData *modedata = (__bridge SDL_DisplayModeData *)bestmode->driverdata; SDL_DisplayModeData *modedata = (__bridge SDL_DisplayModeData *)bestmode->driverdata;
@ -157,8 +184,7 @@ UIKit_CreateWindow(_THIS, SDL_Window *window)
/* desktop_mode doesn't change here (the higher level will /* desktop_mode doesn't change here (the higher level will
* use it to set all the screens back to their defaults * 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; display->current_mode = *bestmode;
} }
} }
@ -172,36 +198,14 @@ UIKit_CreateWindow(_THIS, SDL_Window *window)
} else { } else {
app.statusBarHidden = NO; 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 */ /* ignore the size user requested, and make a fullscreen window */
/* !!! FIXME: can we have a smaller view? */ /* !!! 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 /* put the window on an external display if appropriate. */
* does [uiwindow setframe:[uiscreen bounds]], so don't do it on the if (data.uiscreen != [UIScreen mainScreen]) {
* main display, where we land by default, as that would eat the
* status bar real estate.
*/
if (external) {
[uiwindow setScreen:data.uiscreen]; [uiwindow setScreen:data.uiscreen];
} }
@ -217,11 +221,11 @@ void
UIKit_SetWindowTitle(_THIS, SDL_Window * window) UIKit_SetWindowTitle(_THIS, SDL_Window * window)
{ {
@autoreleasepool { @autoreleasepool {
SDL_uikitviewcontroller *vc = ((__bridge SDL_WindowData *) window->driverdata).viewcontroller; SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
if (window->title) { if (window->title) {
vc.title = @(window->title); data.viewcontroller.title = @(window->title);
} else { } else {
vc.title = @""; data.viewcontroller.title = nil;
} }
} }
} }
@ -230,8 +234,8 @@ void
UIKit_ShowWindow(_THIS, SDL_Window * window) UIKit_ShowWindow(_THIS, SDL_Window * window)
{ {
@autoreleasepool { @autoreleasepool {
UIWindow *uiwindow = ((__bridge SDL_WindowData *) window->driverdata).uiwindow; SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
[uiwindow makeKeyAndVisible]; [data.uiwindow makeKeyAndVisible];
} }
} }
@ -239,8 +243,8 @@ void
UIKit_HideWindow(_THIS, SDL_Window * window) UIKit_HideWindow(_THIS, SDL_Window * window)
{ {
@autoreleasepool { @autoreleasepool {
UIWindow *uiwindow = ((__bridge SDL_WindowData *) window->driverdata).uiwindow; SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
uiwindow.hidden = YES; data.uiwindow.hidden = YES;
} }
} }
@ -250,19 +254,17 @@ UIKit_RaiseWindow(_THIS, SDL_Window * window)
/* We don't currently offer a concept of "raising" the SDL window, since /* 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 * 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); _this->GL_MakeCurrent(_this, _this->current_glwin, _this->current_glctx);
} }
static void static void
UIKit_UpdateWindowBorder(_THIS, SDL_Window * window) UIKit_UpdateWindowBorder(_THIS, SDL_Window * window)
{ {
SDL_WindowData *windowdata = (__bridge SDL_WindowData *) window->driverdata; SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
SDL_uikitviewcontroller *viewcontroller = windowdata.viewcontroller; SDL_uikitviewcontroller *viewcontroller = data.viewcontroller;
CGRect frame;
if (windowdata.uiwindow.screen == [UIScreen mainScreen]) { if (data.uiwindow.screen == [UIScreen mainScreen]) {
if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) { if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) {
[UIApplication sharedApplication].statusBarHidden = YES; [UIApplication sharedApplication].statusBarHidden = YES;
} else { } else {
@ -276,24 +278,9 @@ UIKit_UpdateWindowBorder(_THIS, SDL_Window * window)
} }
/* Update the view's frame to account for the status bar change. */ /* Update the view's frame to account for the status bar change. */
frame = UIKit_ComputeViewFrame(window, windowdata.uiwindow.screen); viewcontroller.view.frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen);
[viewcontroller.view setNeedsLayout];
windowdata.view.frame = frame; [viewcontroller.view layoutIfNeeded];
[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));
}
} }
void void
@ -317,7 +304,8 @@ UIKit_DestroyWindow(_THIS, SDL_Window * window)
{ {
@autoreleasepool { @autoreleasepool {
if (window->driverdata != NULL) { if (window->driverdata != NULL) {
CFRelease(window->driverdata); SDL_WindowData *data = (SDL_WindowData *) CFBridgingRelease(window->driverdata);
[data.viewcontroller stopAnimation];
} }
} }
window->driverdata = NULL; window->driverdata = NULL;
@ -327,11 +315,11 @@ SDL_bool
UIKit_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info) UIKit_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
{ {
@autoreleasepool { @autoreleasepool {
UIWindow *uiwindow = ((__bridge SDL_WindowData *) window->driverdata).uiwindow; SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
if (info->version.major <= SDL_MAJOR_VERSION) { if (info->version.major <= SDL_MAJOR_VERSION) {
info->subsystem = SDL_SYSWM_UIKIT; info->subsystem = SDL_SYSWM_UIKIT;
info->info.uikit.window = uiwindow; info->info.uikit.window = data.uiwindow;
return SDL_TRUE; return SDL_TRUE;
} else { } else {
SDL_SetError("Application not compiled with SDL %d.%d\n", 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 */ /* 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; orientationMask &= ~UIInterfaceOrientationMaskPortraitUpsideDown;
} }
} }
@ -391,14 +379,15 @@ UIKit_GetSupportedOrientations(SDL_Window * window)
int int
SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam) SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam)
{ {
@autoreleasepool { if (!window || !window->driverdata) {
SDL_WindowData *data = window ? (__bridge SDL_WindowData *)window->driverdata : nil; return SDL_SetError("Invalid window");
if (!data || !data.view) {
return SDL_SetError("Invalid window or view not set");
} }
[data.view setAnimationCallback:interval callback:callback callbackParam:callbackParam]; @autoreleasepool {
SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
[data.viewcontroller setAnimationCallback:interval
callback:callback
callbackParam:callbackParam];
} }
return 0; return 0;