SDL_uikitviewcontroller.m 18 KB

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