123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2020 Sam Lantinga <[email protected]>
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- */
- #include "../../SDL_internal.h"
- #if SDL_VIDEO_DRIVER_UIKIT
- #include "SDL_video.h"
- #include "SDL_assert.h"
- #include "SDL_hints.h"
- #include "../SDL_sysvideo.h"
- #include "../../events/SDL_events_c.h"
- #import "SDL_uikitviewcontroller.h"
- #import "SDL_uikitmessagebox.h"
- #include "SDL_uikitvideo.h"
- #include "SDL_uikitmodes.h"
- #include "SDL_uikitwindow.h"
- #include "SDL_uikitopengles.h"
- #if SDL_IPHONE_KEYBOARD
- #include "keyinfotable.h"
- #endif
- #if TARGET_OS_TV
- static void SDLCALL
- SDL_AppleTVControllerUIHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
- {
- @autoreleasepool {
- SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *) userdata;
- viewcontroller.controllerUserInteractionEnabled = hint && (*hint != '0');
- }
- }
- #endif
- #if !TARGET_OS_TV
- static void SDLCALL
- SDL_HideHomeIndicatorHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
- {
- @autoreleasepool {
- SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *) userdata;
- viewcontroller.homeIndicatorHidden = (hint && *hint) ? SDL_atoi(hint) : -1;
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wunguarded-availability-new"
- if ([viewcontroller respondsToSelector:@selector(setNeedsUpdateOfHomeIndicatorAutoHidden)]) {
- [viewcontroller setNeedsUpdateOfHomeIndicatorAutoHidden];
- [viewcontroller setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
- }
- #pragma clang diagnostic pop
- }
- }
- #endif
- @implementation SDL_uikitviewcontroller {
- CADisplayLink *displayLink;
- int animationInterval;
- void (*animationCallback)(void*);
- void *animationCallbackParam;
- #if SDL_IPHONE_KEYBOARD
- UITextField *textField;
- BOOL hardwareKeyboard;
- BOOL showingKeyboard;
- BOOL rotatingOrientation;
- NSString *changeText;
- NSString *obligateForBackspace;
- #endif
- }
- @synthesize window;
- - (instancetype)initWithSDLWindow:(SDL_Window *)_window
- {
- if (self = [super initWithNibName:nil bundle:nil]) {
- self.window = _window;
- #if SDL_IPHONE_KEYBOARD
- [self initKeyboard];
- hardwareKeyboard = NO;
- showingKeyboard = NO;
- rotatingOrientation = NO;
- #endif
- #if TARGET_OS_TV
- SDL_AddHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
- SDL_AppleTVControllerUIHintChanged,
- (__bridge void *) self);
- #endif
- #if !TARGET_OS_TV
- SDL_AddHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR,
- SDL_HideHomeIndicatorHintChanged,
- (__bridge void *) self);
- #endif
- }
- return self;
- }
- - (void)dealloc
- {
- #if SDL_IPHONE_KEYBOARD
- [self deinitKeyboard];
- #endif
- #if TARGET_OS_TV
- SDL_DelHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
- SDL_AppleTVControllerUIHintChanged,
- (__bridge void *) self);
- #endif
- #if !TARGET_OS_TV
- SDL_DelHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR,
- SDL_HideHomeIndicatorHintChanged,
- (__bridge void *) self);
- #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:)];
- #ifdef __IPHONE_10_3
- SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
- if ([displayLink respondsToSelector:@selector(preferredFramesPerSecond)]
- && data != nil && data.uiwindow != nil
- && [data.uiwindow.screen respondsToSelector:@selector(maximumFramesPerSecond)]) {
- displayLink.preferredFramesPerSecond = data.uiwindow.screen.maximumFramesPerSecond / animationInterval;
- } else
- #endif
- {
- #if __IPHONE_OS_VERSION_MIN_REQUIRED < 100300
- [displayLink setFrameInterval:animationInterval];
- #endif
- }
- [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()) {
- /* See the comment in the function definition. */
- #if SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2
- UIKit_GL_RestoreCurrentContext();
- #endif
- animationCallback(animationCallbackParam);
- }
- }
- - (void)loadView
- {
- /* Do nothing. */
- }
- - (void)viewDidLayoutSubviews
- {
- const CGSize size = self.view.bounds.size;
- int w = (int) size.width;
- int h = (int) size.height;
- SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h);
- }
- #if !TARGET_OS_TV
- - (NSUInteger)supportedInterfaceOrientations
- {
- return UIKit_GetSupportedOrientations(window);
- }
- #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
- - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient
- {
- return ([self supportedInterfaceOrientations] & (1 << orient)) != 0;
- }
- #endif
- - (BOOL)prefersStatusBarHidden
- {
- BOOL hidden = (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0;
- return hidden;
- }
- - (BOOL)prefersHomeIndicatorAutoHidden
- {
- BOOL hidden = NO;
- if (self.homeIndicatorHidden == 1) {
- hidden = YES;
- }
- return hidden;
- }
- - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
- {
- if (self.homeIndicatorHidden >= 0) {
- if (self.homeIndicatorHidden == 2) {
- return UIRectEdgeAll;
- } else {
- return UIRectEdgeNone;
- }
- }
- /* By default, fullscreen and borderless windows get all screen gestures */
- if ((window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0) {
- return UIRectEdgeAll;
- } else {
- return UIRectEdgeNone;
- }
- }
- #endif
- /*
- ---- Keyboard related functionality below this line ----
- */
- #if SDL_IPHONE_KEYBOARD
- @synthesize textInputRect;
- @synthesize keyboardHeight;
- @synthesize keyboardVisible;
- /* Set ourselves up as a UITextFieldDelegate */
- - (void)initKeyboard
- {
- changeText = nil;
- obligateForBackspace = @" "; /* 64 space */
- textField = [[UITextField alloc] initWithFrame:CGRectZero];
- textField.delegate = self;
- /* placeholder so there is something to delete! */
- textField.text = obligateForBackspace;
- /* 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];
- #if !TARGET_OS_TV
- [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
- [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
- #endif
- [center addObserver:self selector:@selector(textFieldTextDidChange:) name:UITextFieldTextDidChangeNotification object:nil];
- }
- - (NSArray *)keyCommands
- {
- NSMutableArray *commands = [[NSMutableArray alloc] init];
- [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
- [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
- [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
- [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
- [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:kNilOptions action:@selector(handleCommand:)]];
- return [NSArray arrayWithArray:commands];
- }
- - (void)handleCommand:(UIKeyCommand *)keyCommand
- {
- SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
- NSString *input = keyCommand.input;
- if (input == UIKeyInputUpArrow) {
- scancode = SDL_SCANCODE_UP;
- } else if (input == UIKeyInputDownArrow) {
- scancode = SDL_SCANCODE_DOWN;
- } else if (input == UIKeyInputLeftArrow) {
- scancode = SDL_SCANCODE_LEFT;
- } else if (input == UIKeyInputRightArrow) {
- scancode = SDL_SCANCODE_RIGHT;
- } else if (input == UIKeyInputEscape) {
- scancode = SDL_SCANCODE_ESCAPE;
- }
- if (scancode != SDL_SCANCODE_UNKNOWN) {
- SDL_SendKeyboardKeyAutoRelease(scancode);
- }
- }
- - (void)setView:(UIView *)view
- {
- [super setView:view];
- [view addSubview:textField];
- if (keyboardVisible) {
- [self showKeyboard];
- }
- }
- /* willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are deprecated in iOS 8+ in favor of viewWillTransitionToSize */
- #if TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
- - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
- {
- [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
- rotatingOrientation = YES;
- [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {}
- completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
- self->rotatingOrientation = NO;
- }];
- }
- #else
- - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
- [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
- rotatingOrientation = YES;
- }
- - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
- [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
- rotatingOrientation = NO;
- }
- #endif /* TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000 */
- - (void)deinitKeyboard
- {
- NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
- #if !TARGET_OS_TV
- [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
- [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
- #endif
- [center removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
- }
- /* reveal onscreen virtual keyboard */
- - (void)showKeyboard
- {
- keyboardVisible = YES;
- if (textField.window) {
- showingKeyboard = YES;
- [textField becomeFirstResponder];
- showingKeyboard = NO;
- }
- }
- /* hide onscreen virtual keyboard */
- - (void)hideKeyboard
- {
- keyboardVisible = NO;
- [textField resignFirstResponder];
- }
- - (void)keyboardWillShow:(NSNotification *)notification
- {
- #if !TARGET_OS_TV
- CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
- /* The keyboard rect is in the coordinate space of the screen/window, but we
- * want its height in the coordinate space of the view. */
- kbrect = [self.view convertRect:kbrect fromView:nil];
- [self setKeyboardHeight:(int)kbrect.size.height];
- #endif
- }
- - (void)keyboardWillHide:(NSNotification *)notification
- {
- if (!showingKeyboard && !rotatingOrientation) {
- SDL_StopTextInput();
- }
- [self setKeyboardHeight:0];
- }
- - (void)textFieldTextDidChange:(NSNotification *)notification
- {
- if (changeText!=nil && textField.markedTextRange == nil)
- {
- NSUInteger len = changeText.length;
- if (len > 0) {
- if (!SDL_HardwareKeyboardKeyPressed()) {
- /* 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 = [changeText characterAtIndex:i];
- SDL_Scancode code;
- Uint16 mod;
- 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 */
- 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, release shift */
- SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT);
- }
- }
- }
- SDL_SendKeyboardText([changeText UTF8String]);
- }
- changeText = nil;
- }
- }
- - (void)updateKeyboard
- {
- CGAffineTransform t = self.view.transform;
- CGPoint offset = CGPointMake(0.0, 0.0);
- CGRect frame = UIKit_ComputeViewFrame(window, self.view.window.screen);
- if (self.keyboardHeight) {
- int rectbottom = self.textInputRect.y + self.textInputRect.h;
- int keybottom = self.view.bounds.size.height - self.keyboardHeight;
- if (keybottom < rectbottom) {
- offset.y = keybottom - rectbottom;
- }
- }
- /* Apply this view's transform (except any translation) to the offset, in
- * order to orient it correctly relative to the frame's coordinate space. */
- t.tx = 0.0;
- t.ty = 0.0;
- offset = CGPointApplyAffineTransform(offset, t);
- /* Apply the updated offset to the view's frame. */
- frame.origin.x += offset.x;
- frame.origin.y += offset.y;
- self.view.frame = frame;
- }
- - (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) {
- changeText = nil;
- if (textField.markedTextRange == nil) {
- /* it wants to replace text with nothing, ie a delete */
- SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_BACKSPACE);
- }
- if (textField.text.length < 16) {
- textField.text = obligateForBackspace;
- }
- } else {
- changeText = string;
- }
- return YES;
- }
- /* Terminates the editing session */
- - (BOOL)textFieldShouldReturn:(UITextField*)_textField
- {
- SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_RETURN);
- if (keyboardVisible &&
- SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, SDL_FALSE)) {
- 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.keyboardVisible;
- }
- 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: */
|