1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "../../SDL_internal.h" 22 23#if SDL_VIDEO_DRIVER_UIKIT 24 25#include "SDL_video.h" 26#include "SDL_assert.h" 27#include "SDL_hints.h" 28#include "../SDL_sysvideo.h" 29#include "../../events/SDL_events_c.h" 30 31#import "SDL_uikitviewcontroller.h" 32#import "SDL_uikitmessagebox.h" 33#include "SDL_uikitvideo.h" 34#include "SDL_uikitmodes.h" 35#include "SDL_uikitwindow.h" 36#include "SDL_uikitopengles.h" 37 38#if SDL_IPHONE_KEYBOARD 39#include "keyinfotable.h" 40#endif 41 42#if TARGET_OS_TV 43static void 44SDL_AppleTVControllerUIHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) 45{ 46 @autoreleasepool { 47 SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *) userdata; 48 viewcontroller.controllerUserInteractionEnabled = hint && (*hint != '0'); 49 } 50} 51#endif 52 53@implementation SDL_uikitviewcontroller { 54 CADisplayLink *displayLink; 55 int animationInterval; 56 void (*animationCallback)(void*); 57 void *animationCallbackParam; 58 59#if SDL_IPHONE_KEYBOARD 60 UITextField *textField; 61#endif 62} 63 64@synthesize window; 65 66- (instancetype)initWithSDLWindow:(SDL_Window *)_window 67{ 68 if (self = [super initWithNibName:nil bundle:nil]) { 69 self.window = _window; 70 71#if SDL_IPHONE_KEYBOARD 72 [self initKeyboard]; 73#endif 74 75#if TARGET_OS_TV 76 SDL_AddHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS, 77 SDL_AppleTVControllerUIHintChanged, 78 (__bridge void *) self); 79#endif 80 } 81 return self; 82} 83 84- (void)dealloc 85{ 86#if SDL_IPHONE_KEYBOARD 87 [self deinitKeyboard]; 88#endif 89 90#if TARGET_OS_TV 91 SDL_DelHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS, 92 SDL_AppleTVControllerUIHintChanged, 93 (__bridge void *) self); 94#endif 95} 96 97- (void)setAnimationCallback:(int)interval 98 callback:(void (*)(void*))callback 99 callbackParam:(void*)callbackParam 100{ 101 [self stopAnimation]; 102 103 animationInterval = interval; 104 animationCallback = callback; 105 animationCallbackParam = callbackParam; 106 107 if (animationCallback) { 108 [self startAnimation]; 109 } 110} 111 112- (void)startAnimation 113{ 114 displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)]; 115 [displayLink setFrameInterval:animationInterval]; 116 [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 117} 118 119- (void)stopAnimation 120{ 121 [displayLink invalidate]; 122 displayLink = nil; 123} 124 125- (void)doLoop:(CADisplayLink*)sender 126{ 127 /* Don't run the game loop while a messagebox is up */ 128 if (!UIKit_ShowingMessageBox()) { 129 /* See the comment in the function definition. */ 130 UIKit_GL_RestoreCurrentContext(); 131 132 animationCallback(animationCallbackParam); 133 } 134} 135 136- (void)loadView 137{ 138 /* Do nothing. */ 139} 140 141- (void)viewDidLayoutSubviews 142{ 143 const CGSize size = self.view.bounds.size; 144 int w = (int) size.width; 145 int h = (int) size.height; 146 147 SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h); 148} 149 150#if !TARGET_OS_TV 151- (NSUInteger)supportedInterfaceOrientations 152{ 153 return UIKit_GetSupportedOrientations(window); 154} 155 156- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient 157{ 158 return ([self supportedInterfaceOrientations] & (1 << orient)) != 0; 159} 160 161- (BOOL)prefersStatusBarHidden 162{ 163 return (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0; 164} 165#endif 166 167/* 168 ---- Keyboard related functionality below this line ---- 169 */ 170#if SDL_IPHONE_KEYBOARD 171 172@synthesize textInputRect; 173@synthesize keyboardHeight; 174@synthesize keyboardVisible; 175 176/* Set ourselves up as a UITextFieldDelegate */ 177- (void)initKeyboard 178{ 179 textField = [[UITextField alloc] initWithFrame:CGRectZero]; 180 textField.delegate = self; 181 /* placeholder so there is something to delete! */ 182 textField.text = @" "; 183 184 /* set UITextInputTrait properties, mostly to defaults */ 185 textField.autocapitalizationType = UITextAutocapitalizationTypeNone; 186 textField.autocorrectionType = UITextAutocorrectionTypeNo; 187 textField.enablesReturnKeyAutomatically = NO; 188 textField.keyboardAppearance = UIKeyboardAppearanceDefault; 189 textField.keyboardType = UIKeyboardTypeDefault; 190 textField.returnKeyType = UIReturnKeyDefault; 191 textField.secureTextEntry = NO; 192 193 textField.hidden = YES; 194 keyboardVisible = NO; 195 196#if !TARGET_OS_TV 197 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 198 [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; 199 [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 200#endif 201} 202 203- (void)setView:(UIView *)view 204{ 205 [super setView:view]; 206 207 [view addSubview:textField]; 208 209 if (keyboardVisible) { 210 [self showKeyboard]; 211 } 212} 213 214- (void)deinitKeyboard 215{ 216#if !TARGET_OS_TV 217 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 218 [center removeObserver:self name:UIKeyboardWillShowNotification object:nil]; 219 [center removeObserver:self name:UIKeyboardWillHideNotification object:nil]; 220#endif 221} 222 223/* reveal onscreen virtual keyboard */ 224- (void)showKeyboard 225{ 226 keyboardVisible = YES; 227 if (textField.window) { 228 [textField becomeFirstResponder]; 229 } 230} 231 232/* hide onscreen virtual keyboard */ 233- (void)hideKeyboard 234{ 235 keyboardVisible = NO; 236 [textField resignFirstResponder]; 237} 238 239- (void)keyboardWillShow:(NSNotification *)notification 240{ 241#if !TARGET_OS_TV 242 CGRect kbrect = [[notification userInfo][UIKeyboardFrameBeginUserInfoKey] CGRectValue]; 243 244 /* The keyboard rect is in the coordinate space of the screen/window, but we 245 * want its height in the coordinate space of the view. */ 246 kbrect = [self.view convertRect:kbrect fromView:nil]; 247 248 [self setKeyboardHeight:(int)kbrect.size.height]; 249#endif 250} 251 252- (void)keyboardWillHide:(NSNotification *)notification 253{ 254 SDL_StopTextInput(); 255 [self setKeyboardHeight:0]; 256} 257 258- (void)updateKeyboard 259{ 260 CGAffineTransform t = self.view.transform; 261 CGPoint offset = CGPointMake(0.0, 0.0); 262 CGRect frame = UIKit_ComputeViewFrame(window, self.view.window.screen); 263 264 if (self.keyboardHeight) { 265 int rectbottom = self.textInputRect.y + self.textInputRect.h; 266 int keybottom = self.view.bounds.size.height - self.keyboardHeight; 267 if (keybottom < rectbottom) { 268 offset.y = keybottom - rectbottom; 269 } 270 } 271 272 /* Apply this view's transform (except any translation) to the offset, in 273 * order to orient it correctly relative to the frame's coordinate space. */ 274 t.tx = 0.0; 275 t.ty = 0.0; 276 offset = CGPointApplyAffineTransform(offset, t); 277 278 /* Apply the updated offset to the view's frame. */ 279 frame.origin.x += offset.x; 280 frame.origin.y += offset.y; 281 282 self.view.frame = frame; 283} 284 285- (void)setKeyboardHeight:(int)height 286{ 287 keyboardVisible = height > 0; 288 keyboardHeight = height; 289 [self updateKeyboard]; 290} 291 292/* UITextFieldDelegate method. Invoked when user types something. */ 293- (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 294{ 295 NSUInteger len = string.length; 296 297 if (len == 0) { 298 /* it wants to replace text with nothing, ie a delete */ 299 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_BACKSPACE); 300 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_BACKSPACE); 301 } else { 302 /* go through all the characters in the string we've been sent and 303 * convert them to key presses */ 304 int i; 305 for (i = 0; i < len; i++) { 306 unichar c = [string characterAtIndex:i]; 307 Uint16 mod = 0; 308 SDL_Scancode code; 309 310 if (c < 127) { 311 /* figure out the SDL_Scancode and SDL_keymod for this unichar */ 312 code = unicharToUIKeyInfoTable[c].code; 313 mod = unicharToUIKeyInfoTable[c].mod; 314 } else { 315 /* we only deal with ASCII right now */ 316 code = SDL_SCANCODE_UNKNOWN; 317 mod = 0; 318 } 319 320 if (mod & KMOD_SHIFT) { 321 /* If character uses shift, press shift down */ 322 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT); 323 } 324 325 /* send a keydown and keyup even for the character */ 326 SDL_SendKeyboardKey(SDL_PRESSED, code); 327 SDL_SendKeyboardKey(SDL_RELEASED, code); 328 329 if (mod & KMOD_SHIFT) { 330 /* If character uses shift, press shift back up */ 331 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT); 332 } 333 } 334 335 SDL_SendKeyboardText([string UTF8String]); 336 } 337 338 return NO; /* don't allow the edit! (keep placeholder text there) */ 339} 340 341/* Terminates the editing session */ 342- (BOOL)textFieldShouldReturn:(UITextField*)_textField 343{ 344 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_RETURN); 345 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RETURN); 346 SDL_StopTextInput(); 347 return YES; 348} 349 350#endif 351 352@end 353 354/* iPhone keyboard addition functions */ 355#if SDL_IPHONE_KEYBOARD 356 357static SDL_uikitviewcontroller * 358GetWindowViewController(SDL_Window * window) 359{ 360 if (!window || !window->driverdata) { 361 SDL_SetError("Invalid window"); 362 return nil; 363 } 364 365 SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata; 366 367 return data.viewcontroller; 368} 369 370SDL_bool 371UIKit_HasScreenKeyboardSupport(_THIS) 372{ 373 return SDL_TRUE; 374} 375 376void 377UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window) 378{ 379 @autoreleasepool { 380 SDL_uikitviewcontroller *vc = GetWindowViewController(window); 381 [vc showKeyboard]; 382 } 383} 384 385void 386UIKit_HideScreenKeyboard(_THIS, SDL_Window *window) 387{ 388 @autoreleasepool { 389 SDL_uikitviewcontroller *vc = GetWindowViewController(window); 390 [vc hideKeyboard]; 391 } 392} 393 394SDL_bool 395UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window) 396{ 397 @autoreleasepool { 398 SDL_uikitviewcontroller *vc = GetWindowViewController(window); 399 if (vc != nil) { 400 return vc.isKeyboardVisible; 401 } 402 return SDL_FALSE; 403 } 404} 405 406void 407UIKit_SetTextInputRect(_THIS, SDL_Rect *rect) 408{ 409 if (!rect) { 410 SDL_InvalidParamError("rect"); 411 return; 412 } 413 414 @autoreleasepool { 415 SDL_uikitviewcontroller *vc = GetWindowViewController(SDL_GetFocusWindow()); 416 if (vc != nil) { 417 vc.textInputRect = *rect; 418 419 if (vc.keyboardVisible) { 420 [vc updateKeyboard]; 421 } 422 } 423 } 424} 425 426 427#endif /* SDL_IPHONE_KEYBOARD */ 428 429#endif /* SDL_VIDEO_DRIVER_UIKIT */ 430 431/* vi: set ts=4 sw=4 expandtab: */ 432