Atlas - SDL_uikitviewcontroller.m

Home / ext / SDL2 / src / video / uikit Lines: 1 | Size: 15312 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2018 Sam Lantinga <[email protected]> 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 SDLCALL 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#if !TARGET_OS_TV 54static void SDLCALL 55SDL_HideHomeIndicatorHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) 56{ 57 @autoreleasepool { 58 SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *) userdata; 59 viewcontroller.homeIndicatorHidden = (hint && *hint) ? SDL_atoi(hint) : -1; 60 if (@available(iOS 11.0, *)) { 61 [viewcontroller setNeedsUpdateOfHomeIndicatorAutoHidden]; 62 [viewcontroller setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; 63 } 64 } 65} 66#endif 67 68@implementation SDL_uikitviewcontroller { 69 CADisplayLink *displayLink; 70 int animationInterval; 71 void (*animationCallback)(void*); 72 void *animationCallbackParam; 73 74#if SDL_IPHONE_KEYBOARD 75 UITextField *textField; 76 BOOL rotatingOrientation; 77#endif 78} 79 80@synthesize window; 81 82- (instancetype)initWithSDLWindow:(SDL_Window *)_window 83{ 84 if (self = [super initWithNibName:nil bundle:nil]) { 85 self.window = _window; 86 87#if SDL_IPHONE_KEYBOARD 88 [self initKeyboard]; 89 rotatingOrientation = FALSE; 90#endif 91 92#if TARGET_OS_TV 93 SDL_AddHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS, 94 SDL_AppleTVControllerUIHintChanged, 95 (__bridge void *) self); 96#endif 97 98#if !TARGET_OS_TV 99 SDL_AddHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR, 100 SDL_HideHomeIndicatorHintChanged, 101 (__bridge void *) self); 102#endif 103 } 104 return self; 105} 106 107- (void)dealloc 108{ 109#if SDL_IPHONE_KEYBOARD 110 [self deinitKeyboard]; 111#endif 112 113#if TARGET_OS_TV 114 SDL_DelHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS, 115 SDL_AppleTVControllerUIHintChanged, 116 (__bridge void *) self); 117#endif 118 119#if !TARGET_OS_TV 120 SDL_DelHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR, 121 SDL_HideHomeIndicatorHintChanged, 122 (__bridge void *) self); 123#endif 124} 125 126- (void)setAnimationCallback:(int)interval 127 callback:(void (*)(void*))callback 128 callbackParam:(void*)callbackParam 129{ 130 [self stopAnimation]; 131 132 animationInterval = interval; 133 animationCallback = callback; 134 animationCallbackParam = callbackParam; 135 136 if (animationCallback) { 137 [self startAnimation]; 138 } 139} 140 141- (void)startAnimation 142{ 143 displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)]; 144 145#ifdef __IPHONE_10_3 146 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; 147 148 if ([displayLink respondsToSelector:@selector(preferredFramesPerSecond)] 149 && data != nil && data.uiwindow != nil 150 && [data.uiwindow.screen respondsToSelector:@selector(maximumFramesPerSecond)]) { 151 displayLink.preferredFramesPerSecond = data.uiwindow.screen.maximumFramesPerSecond / animationInterval; 152 } else 153#endif 154 { 155#if __IPHONE_OS_VERSION_MIN_REQUIRED < 100300 156 [displayLink setFrameInterval:animationInterval]; 157#endif 158 } 159 160 [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 161} 162 163- (void)stopAnimation 164{ 165 [displayLink invalidate]; 166 displayLink = nil; 167} 168 169- (void)doLoop:(CADisplayLink*)sender 170{ 171 /* Don't run the game loop while a messagebox is up */ 172 if (!UIKit_ShowingMessageBox()) { 173 /* See the comment in the function definition. */ 174 UIKit_GL_RestoreCurrentContext(); 175 176 animationCallback(animationCallbackParam); 177 } 178} 179 180- (void)loadView 181{ 182 /* Do nothing. */ 183} 184 185- (void)viewDidLayoutSubviews 186{ 187 const CGSize size = self.view.bounds.size; 188 int w = (int) size.width; 189 int h = (int) size.height; 190 191 SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h); 192} 193 194#if !TARGET_OS_TV 195- (NSUInteger)supportedInterfaceOrientations 196{ 197 return UIKit_GetSupportedOrientations(window); 198} 199 200#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 201- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient 202{ 203 return ([self supportedInterfaceOrientations] & (1 << orient)) != 0; 204} 205#endif 206 207- (BOOL)prefersStatusBarHidden 208{ 209 BOOL hidden = (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0; 210 return hidden; 211} 212 213- (BOOL)prefersHomeIndicatorAutoHidden 214{ 215 BOOL hidden = NO; 216 if (self.homeIndicatorHidden == 1) { 217 hidden = YES; 218 } 219 return hidden; 220} 221 222- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures 223{ 224 if (self.homeIndicatorHidden >= 0) { 225 if (self.homeIndicatorHidden == 2) { 226 return UIRectEdgeAll; 227 } else { 228 return UIRectEdgeNone; 229 } 230 } 231 232 /* By default, fullscreen and borderless windows get all screen gestures */ 233 if ((window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0) { 234 return UIRectEdgeAll; 235 } else { 236 return UIRectEdgeNone; 237 } 238} 239#endif 240 241/* 242 ---- Keyboard related functionality below this line ---- 243 */ 244#if SDL_IPHONE_KEYBOARD 245 246@synthesize textInputRect; 247@synthesize keyboardHeight; 248@synthesize keyboardVisible; 249 250/* Set ourselves up as a UITextFieldDelegate */ 251- (void)initKeyboard 252{ 253 textField = [[UITextField alloc] initWithFrame:CGRectZero]; 254 textField.delegate = self; 255 /* placeholder so there is something to delete! */ 256 textField.text = @" "; 257 258 /* set UITextInputTrait properties, mostly to defaults */ 259 textField.autocapitalizationType = UITextAutocapitalizationTypeNone; 260 textField.autocorrectionType = UITextAutocorrectionTypeNo; 261 textField.enablesReturnKeyAutomatically = NO; 262 textField.keyboardAppearance = UIKeyboardAppearanceDefault; 263 textField.keyboardType = UIKeyboardTypeDefault; 264 textField.returnKeyType = UIReturnKeyDefault; 265 textField.secureTextEntry = NO; 266 267 textField.hidden = YES; 268 keyboardVisible = NO; 269 270#if !TARGET_OS_TV 271 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 272 [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; 273 [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 274#endif 275} 276 277- (void)setView:(UIView *)view 278{ 279 [super setView:view]; 280 281 [view addSubview:textField]; 282 283 if (keyboardVisible) { 284 [self showKeyboard]; 285 } 286} 287 288/* willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are deprecated in iOS 8+ in favor of viewWillTransitionToSize */ 289#if TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000 290- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator 291{ 292 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; 293 rotatingOrientation = TRUE; 294 [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {} 295 completion:^(id<UIViewControllerTransitionCoordinatorContext> context) { 296 rotatingOrientation = FALSE; 297 }]; 298} 299#else 300- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { 301 [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; 302 rotatingOrientation = TRUE; 303} 304 305- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { 306 [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; 307 rotatingOrientation = FALSE; 308} 309#endif /* TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000 */ 310 311- (void)deinitKeyboard 312{ 313#if !TARGET_OS_TV 314 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 315 [center removeObserver:self name:UIKeyboardWillShowNotification object:nil]; 316 [center removeObserver:self name:UIKeyboardWillHideNotification object:nil]; 317#endif 318} 319 320/* reveal onscreen virtual keyboard */ 321- (void)showKeyboard 322{ 323 keyboardVisible = YES; 324 if (textField.window) { 325 [textField becomeFirstResponder]; 326 } 327} 328 329/* hide onscreen virtual keyboard */ 330- (void)hideKeyboard 331{ 332 keyboardVisible = NO; 333 [textField resignFirstResponder]; 334} 335 336- (void)keyboardWillShow:(NSNotification *)notification 337{ 338#if !TARGET_OS_TV 339 CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue]; 340 341 /* The keyboard rect is in the coordinate space of the screen/window, but we 342 * want its height in the coordinate space of the view. */ 343 kbrect = [self.view convertRect:kbrect fromView:nil]; 344 345 [self setKeyboardHeight:(int)kbrect.size.height]; 346#endif 347} 348 349- (void)keyboardWillHide:(NSNotification *)notification 350{ 351 if (!rotatingOrientation) { 352 SDL_StopTextInput(); 353 } 354 [self setKeyboardHeight:0]; 355} 356 357- (void)updateKeyboard 358{ 359 CGAffineTransform t = self.view.transform; 360 CGPoint offset = CGPointMake(0.0, 0.0); 361 CGRect frame = UIKit_ComputeViewFrame(window, self.view.window.screen); 362 363 if (self.keyboardHeight) { 364 int rectbottom = self.textInputRect.y + self.textInputRect.h; 365 int keybottom = self.view.bounds.size.height - self.keyboardHeight; 366 if (keybottom < rectbottom) { 367 offset.y = keybottom - rectbottom; 368 } 369 } 370 371 /* Apply this view's transform (except any translation) to the offset, in 372 * order to orient it correctly relative to the frame's coordinate space. */ 373 t.tx = 0.0; 374 t.ty = 0.0; 375 offset = CGPointApplyAffineTransform(offset, t); 376 377 /* Apply the updated offset to the view's frame. */ 378 frame.origin.x += offset.x; 379 frame.origin.y += offset.y; 380 381 self.view.frame = frame; 382} 383 384- (void)setKeyboardHeight:(int)height 385{ 386 keyboardVisible = height > 0; 387 keyboardHeight = height; 388 [self updateKeyboard]; 389} 390 391/* UITextFieldDelegate method. Invoked when user types something. */ 392- (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 393{ 394 NSUInteger len = string.length; 395 396 if (len == 0) { 397 /* it wants to replace text with nothing, ie a delete */ 398 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_BACKSPACE); 399 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_BACKSPACE); 400 } else { 401 /* go through all the characters in the string we've been sent and 402 * convert them to key presses */ 403 int i; 404 for (i = 0; i < len; i++) { 405 unichar c = [string characterAtIndex:i]; 406 Uint16 mod = 0; 407 SDL_Scancode code; 408 409 if (c < 127) { 410 /* figure out the SDL_Scancode and SDL_keymod for this unichar */ 411 code = unicharToUIKeyInfoTable[c].code; 412 mod = unicharToUIKeyInfoTable[c].mod; 413 } else { 414 /* we only deal with ASCII right now */ 415 code = SDL_SCANCODE_UNKNOWN; 416 mod = 0; 417 } 418 419 if (mod & KMOD_SHIFT) { 420 /* If character uses shift, press shift down */ 421 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT); 422 } 423 424 /* send a keydown and keyup even for the character */ 425 SDL_SendKeyboardKey(SDL_PRESSED, code); 426 SDL_SendKeyboardKey(SDL_RELEASED, code); 427 428 if (mod & KMOD_SHIFT) { 429 /* If character uses shift, press shift back up */ 430 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT); 431 } 432 } 433 434 SDL_SendKeyboardText([string UTF8String]); 435 } 436 437 return NO; /* don't allow the edit! (keep placeholder text there) */ 438} 439 440/* Terminates the editing session */ 441- (BOOL)textFieldShouldReturn:(UITextField*)_textField 442{ 443 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_RETURN); 444 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RETURN); 445 if (SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, SDL_FALSE)) { 446 SDL_StopTextInput(); 447 } 448 return YES; 449} 450 451#endif 452 453@end 454 455/* iPhone keyboard addition functions */ 456#if SDL_IPHONE_KEYBOARD 457 458static SDL_uikitviewcontroller * 459GetWindowViewController(SDL_Window * window) 460{ 461 if (!window || !window->driverdata) { 462 SDL_SetError("Invalid window"); 463 return nil; 464 } 465 466 SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata; 467 468 return data.viewcontroller; 469} 470 471SDL_bool 472UIKit_HasScreenKeyboardSupport(_THIS) 473{ 474 return SDL_TRUE; 475} 476 477void 478UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window) 479{ 480 @autoreleasepool { 481 SDL_uikitviewcontroller *vc = GetWindowViewController(window); 482 [vc showKeyboard]; 483 } 484} 485 486void 487UIKit_HideScreenKeyboard(_THIS, SDL_Window *window) 488{ 489 @autoreleasepool { 490 SDL_uikitviewcontroller *vc = GetWindowViewController(window); 491 [vc hideKeyboard]; 492 } 493} 494 495SDL_bool 496UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window) 497{ 498 @autoreleasepool { 499 SDL_uikitviewcontroller *vc = GetWindowViewController(window); 500 if (vc != nil) { 501 return vc.isKeyboardVisible; 502 } 503 return SDL_FALSE; 504 } 505} 506 507void 508UIKit_SetTextInputRect(_THIS, SDL_Rect *rect) 509{ 510 if (!rect) { 511 SDL_InvalidParamError("rect"); 512 return; 513 } 514 515 @autoreleasepool { 516 SDL_uikitviewcontroller *vc = GetWindowViewController(SDL_GetFocusWindow()); 517 if (vc != nil) { 518 vc.textInputRect = *rect; 519 520 if (vc.keyboardVisible) { 521 [vc updateKeyboard]; 522 } 523 } 524 } 525} 526 527 528#endif /* SDL_IPHONE_KEYBOARD */ 529 530#endif /* SDL_VIDEO_DRIVER_UIKIT */ 531 532/* vi: set ts=4 sw=4 expandtab: */ 533
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.