SDL_uikitviewcontroller.m 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. /*
  2. Simple DirectMedia Layer
  3. Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
  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. #ifdef SDL_VIDEO_DRIVER_UIKIT
  20. #include "../SDL_sysvideo.h"
  21. #include "../../events/SDL_events_c.h"
  22. #include "SDL_uikitviewcontroller.h"
  23. #include "SDL_uikitmessagebox.h"
  24. #include "SDL_uikitevents.h"
  25. #include "SDL_uikitvideo.h"
  26. #include "SDL_uikitmodes.h"
  27. #include "SDL_uikitwindow.h"
  28. #include "SDL_uikitopengles.h"
  29. #ifdef SDL_PLATFORM_TVOS
  30. static void SDLCALL SDL_AppleTVControllerUIHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
  31. {
  32. @autoreleasepool {
  33. SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *)userdata;
  34. viewcontroller.controllerUserInteractionEnabled = hint && (*hint != '0');
  35. }
  36. }
  37. #endif
  38. #ifndef SDL_PLATFORM_TVOS
  39. static void SDLCALL SDL_HideHomeIndicatorHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
  40. {
  41. @autoreleasepool {
  42. SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *)userdata;
  43. viewcontroller.homeIndicatorHidden = (hint && *hint) ? SDL_atoi(hint) : -1;
  44. [viewcontroller setNeedsUpdateOfHomeIndicatorAutoHidden];
  45. [viewcontroller setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
  46. }
  47. }
  48. #endif
  49. @implementation SDLUITextField : UITextField
  50. - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
  51. {
  52. if (action == @selector(paste:)) {
  53. return NO;
  54. }
  55. return [super canPerformAction:action withSender:sender];
  56. }
  57. @end
  58. @implementation SDL_uikitviewcontroller
  59. {
  60. CADisplayLink *displayLink;
  61. int animationInterval;
  62. void (*animationCallback)(void *);
  63. void *animationCallbackParam;
  64. #ifdef SDL_IPHONE_KEYBOARD
  65. SDLUITextField *textField;
  66. BOOL hidingKeyboard;
  67. BOOL rotatingOrientation;
  68. NSString *committedText;
  69. NSString *obligateForBackspace;
  70. #endif
  71. }
  72. @synthesize window;
  73. - (instancetype)initWithSDLWindow:(SDL_Window *)_window
  74. {
  75. if (self = [super initWithNibName:nil bundle:nil]) {
  76. self.window = _window;
  77. #ifdef SDL_IPHONE_KEYBOARD
  78. [self initKeyboard];
  79. hidingKeyboard = NO;
  80. rotatingOrientation = NO;
  81. #endif
  82. #ifdef SDL_PLATFORM_TVOS
  83. SDL_AddHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
  84. SDL_AppleTVControllerUIHintChanged,
  85. (__bridge void *)self);
  86. #endif
  87. #ifndef SDL_PLATFORM_TVOS
  88. SDL_AddHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR,
  89. SDL_HideHomeIndicatorHintChanged,
  90. (__bridge void *)self);
  91. #endif
  92. // Enable high refresh rates on iOS
  93. // To enable this on phones, you should add the following line to Info.plist:
  94. // <key>CADisableMinimumFrameDurationOnPhone</key> <true/>
  95. if (@available(iOS 15.0, tvOS 15.0, *)) {
  96. const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay());
  97. if (mode && mode->refresh_rate > 60.0f) {
  98. int frame_rate = (int)mode->refresh_rate;
  99. displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
  100. displayLink.preferredFrameRateRange = CAFrameRateRangeMake((frame_rate * 2) / 3, frame_rate, frame_rate);
  101. [displayLink addToRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode];
  102. }
  103. }
  104. }
  105. return self;
  106. }
  107. - (void)dealloc
  108. {
  109. #ifdef SDL_IPHONE_KEYBOARD
  110. [self deinitKeyboard];
  111. #endif
  112. #ifdef SDL_PLATFORM_TVOS
  113. SDL_RemoveHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
  114. SDL_AppleTVControllerUIHintChanged,
  115. (__bridge void *)self);
  116. #endif
  117. #ifndef SDL_PLATFORM_TVOS
  118. SDL_RemoveHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR,
  119. SDL_HideHomeIndicatorHintChanged,
  120. (__bridge void *)self);
  121. #endif
  122. }
  123. - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
  124. {
  125. SDL_SetSystemTheme(UIKit_GetSystemTheme());
  126. }
  127. - (void)setAnimationCallback:(int)interval
  128. callback:(void (*)(void *))callback
  129. callbackParam:(void *)callbackParam
  130. {
  131. [self stopAnimation];
  132. if (interval <= 0) {
  133. interval = 1;
  134. }
  135. animationInterval = interval;
  136. animationCallback = callback;
  137. animationCallbackParam = callbackParam;
  138. if (animationCallback) {
  139. [self startAnimation];
  140. }
  141. }
  142. - (void)startAnimation
  143. {
  144. displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
  145. #ifdef SDL_PLATFORM_VISIONOS
  146. displayLink.preferredFramesPerSecond = 90 / animationInterval; //TODO: Get frame max frame rate on visionOS
  147. #else
  148. SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
  149. displayLink.preferredFramesPerSecond = data.uiwindow.screen.maximumFramesPerSecond / animationInterval;
  150. #endif
  151. [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
  152. }
  153. - (void)stopAnimation
  154. {
  155. [displayLink invalidate];
  156. displayLink = nil;
  157. }
  158. - (void)doLoop:(CADisplayLink *)sender
  159. {
  160. // Don't run the game loop while a messagebox is up
  161. if (animationCallback && !UIKit_ShowingMessageBox()) {
  162. // See the comment in the function definition.
  163. #if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
  164. UIKit_GL_RestoreCurrentContext();
  165. #endif
  166. animationCallback(animationCallbackParam);
  167. }
  168. }
  169. - (void)loadView
  170. {
  171. // Do nothing.
  172. }
  173. - (void)viewDidLayoutSubviews
  174. {
  175. const CGSize size = self.view.bounds.size;
  176. int w = (int)size.width;
  177. int h = (int)size.height;
  178. SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, w, h);
  179. }
  180. #ifndef SDL_PLATFORM_TVOS
  181. - (NSUInteger)supportedInterfaceOrientations
  182. {
  183. return UIKit_GetSupportedOrientations(window);
  184. }
  185. - (BOOL)prefersStatusBarHidden
  186. {
  187. BOOL hidden = (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) != 0;
  188. return hidden;
  189. }
  190. - (BOOL)prefersHomeIndicatorAutoHidden
  191. {
  192. BOOL hidden = NO;
  193. if (self.homeIndicatorHidden == 1) {
  194. hidden = YES;
  195. }
  196. return hidden;
  197. }
  198. - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
  199. {
  200. if (self.homeIndicatorHidden >= 0) {
  201. if (self.homeIndicatorHidden == 2) {
  202. return UIRectEdgeAll;
  203. } else {
  204. return UIRectEdgeNone;
  205. }
  206. }
  207. // By default, fullscreen and borderless windows get all screen gestures
  208. if ((window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) != 0) {
  209. return UIRectEdgeAll;
  210. } else {
  211. return UIRectEdgeNone;
  212. }
  213. }
  214. - (BOOL)prefersPointerLocked
  215. {
  216. return SDL_GCMouseRelativeMode() ? YES : NO;
  217. }
  218. #endif // !SDL_PLATFORM_TVOS
  219. /*
  220. ---- Keyboard related functionality below this line ----
  221. */
  222. #ifdef SDL_IPHONE_KEYBOARD
  223. @synthesize textInputRect;
  224. @synthesize keyboardHeight;
  225. @synthesize textFieldFocused;
  226. // Set ourselves up as a UITextFieldDelegate
  227. - (void)initKeyboard
  228. {
  229. obligateForBackspace = @" "; // 64 space
  230. textField = [[SDLUITextField alloc] initWithFrame:CGRectZero];
  231. textField.delegate = self;
  232. // placeholder so there is something to delete!
  233. textField.text = obligateForBackspace;
  234. committedText = textField.text;
  235. textField.hidden = YES;
  236. textFieldFocused = NO;
  237. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  238. #ifndef SDL_PLATFORM_TVOS
  239. [center addObserver:self
  240. selector:@selector(keyboardWillShow:)
  241. name:UIKeyboardWillShowNotification
  242. object:nil];
  243. [center addObserver:self
  244. selector:@selector(keyboardWillHide:)
  245. name:UIKeyboardWillHideNotification
  246. object:nil];
  247. [center addObserver:self
  248. selector:@selector(keyboardDidHide:)
  249. name:UIKeyboardDidHideNotification
  250. object:nil];
  251. #endif
  252. [center addObserver:self
  253. selector:@selector(textFieldTextDidChange:)
  254. name:UITextFieldTextDidChangeNotification
  255. object:nil];
  256. }
  257. - (NSArray *)keyCommands
  258. {
  259. NSMutableArray *commands = [[NSMutableArray alloc] init];
  260. [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
  261. [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
  262. [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
  263. [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
  264. [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:kNilOptions action:@selector(handleCommand:)]];
  265. return [NSArray arrayWithArray:commands];
  266. }
  267. - (void)handleCommand:(UIKeyCommand *)keyCommand
  268. {
  269. SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
  270. NSString *input = keyCommand.input;
  271. if (input == UIKeyInputUpArrow) {
  272. scancode = SDL_SCANCODE_UP;
  273. } else if (input == UIKeyInputDownArrow) {
  274. scancode = SDL_SCANCODE_DOWN;
  275. } else if (input == UIKeyInputLeftArrow) {
  276. scancode = SDL_SCANCODE_LEFT;
  277. } else if (input == UIKeyInputRightArrow) {
  278. scancode = SDL_SCANCODE_RIGHT;
  279. } else if (input == UIKeyInputEscape) {
  280. scancode = SDL_SCANCODE_ESCAPE;
  281. }
  282. if (scancode != SDL_SCANCODE_UNKNOWN) {
  283. SDL_SendKeyboardKeyAutoRelease(0, scancode);
  284. }
  285. }
  286. - (void)setView:(UIView *)view
  287. {
  288. [super setView:view];
  289. [view addSubview:textField];
  290. if (textFieldFocused) {
  291. /* startTextInput has been called before the text field was added to the view,
  292. * call it again for the text field to actually become first responder. */
  293. [self startTextInput];
  294. }
  295. }
  296. - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
  297. {
  298. [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
  299. rotatingOrientation = YES;
  300. [coordinator
  301. animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
  302. }
  303. completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
  304. self->rotatingOrientation = NO;
  305. }];
  306. }
  307. - (void)deinitKeyboard
  308. {
  309. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  310. #ifndef SDL_PLATFORM_TVOS
  311. [center removeObserver:self
  312. name:UIKeyboardWillShowNotification
  313. object:nil];
  314. [center removeObserver:self
  315. name:UIKeyboardWillHideNotification
  316. object:nil];
  317. [center removeObserver:self
  318. name:UIKeyboardDidHideNotification
  319. object:nil];
  320. #endif
  321. [center removeObserver:self
  322. name:UITextFieldTextDidChangeNotification
  323. object:nil];
  324. }
  325. - (void)setTextFieldProperties:(SDL_PropertiesID) props
  326. {
  327. textField.secureTextEntry = NO;
  328. switch (SDL_GetTextInputType(props)) {
  329. default:
  330. case SDL_TEXTINPUT_TYPE_TEXT:
  331. textField.keyboardType = UIKeyboardTypeDefault;
  332. textField.textContentType = nil;
  333. break;
  334. case SDL_TEXTINPUT_TYPE_TEXT_NAME:
  335. textField.keyboardType = UIKeyboardTypeDefault;
  336. textField.textContentType = UITextContentTypeName;
  337. break;
  338. case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
  339. textField.keyboardType = UIKeyboardTypeEmailAddress;
  340. textField.textContentType = UITextContentTypeEmailAddress;
  341. break;
  342. case SDL_TEXTINPUT_TYPE_TEXT_USERNAME:
  343. textField.keyboardType = UIKeyboardTypeDefault;
  344. textField.textContentType = UITextContentTypeUsername;
  345. break;
  346. case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
  347. textField.keyboardType = UIKeyboardTypeDefault;
  348. textField.textContentType = UITextContentTypePassword;
  349. textField.secureTextEntry = YES;
  350. break;
  351. case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE:
  352. textField.keyboardType = UIKeyboardTypeDefault;
  353. textField.textContentType = UITextContentTypePassword;
  354. break;
  355. case SDL_TEXTINPUT_TYPE_NUMBER:
  356. textField.keyboardType = UIKeyboardTypeDecimalPad;
  357. textField.textContentType = nil;
  358. break;
  359. case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
  360. textField.keyboardType = UIKeyboardTypeNumberPad;
  361. if (@available(iOS 12.0, tvOS 12.0, *)) {
  362. textField.textContentType = UITextContentTypeOneTimeCode;
  363. } else {
  364. textField.textContentType = nil;
  365. }
  366. textField.secureTextEntry = YES;
  367. break;
  368. case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
  369. textField.keyboardType = UIKeyboardTypeNumberPad;
  370. if (@available(iOS 12.0, tvOS 12.0, *)) {
  371. textField.textContentType = UITextContentTypeOneTimeCode;
  372. } else {
  373. textField.textContentType = nil;
  374. }
  375. break;
  376. }
  377. switch (SDL_GetTextInputCapitalization(props)) {
  378. default:
  379. case SDL_CAPITALIZE_NONE:
  380. textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
  381. break;
  382. case SDL_CAPITALIZE_LETTERS:
  383. textField.autocapitalizationType = UITextAutocapitalizationTypeAllCharacters;
  384. break;
  385. case SDL_CAPITALIZE_WORDS:
  386. textField.autocapitalizationType = UITextAutocapitalizationTypeWords;
  387. break;
  388. case SDL_CAPITALIZE_SENTENCES:
  389. textField.autocapitalizationType = UITextAutocapitalizationTypeSentences;
  390. break;
  391. }
  392. if (SDL_GetTextInputAutocorrect(props)) {
  393. textField.autocorrectionType = UITextAutocorrectionTypeYes;
  394. textField.spellCheckingType = UITextSpellCheckingTypeYes;
  395. } else {
  396. textField.autocorrectionType = UITextAutocorrectionTypeNo;
  397. textField.spellCheckingType = UITextSpellCheckingTypeNo;
  398. }
  399. if (SDL_GetTextInputMultiline(props)) {
  400. textField.enablesReturnKeyAutomatically = YES;
  401. } else {
  402. textField.enablesReturnKeyAutomatically = NO;
  403. }
  404. if (!textField.window) {
  405. /* textField has not been added to the view yet,
  406. we don't have to do anything. */
  407. return;
  408. }
  409. // the text field needs to be re-added to the view in order to update correctly.
  410. UIView *superview = textField.superview;
  411. [textField removeFromSuperview];
  412. [superview addSubview:textField];
  413. if (SDL_TextInputActive(window)) {
  414. [textField becomeFirstResponder];
  415. }
  416. }
  417. /* requests the SDL text field to become focused and accept text input.
  418. * also shows the onscreen virtual keyboard if no hardware keyboard is attached. */
  419. - (bool)startTextInput
  420. {
  421. textFieldFocused = YES;
  422. if (!textField.window) {
  423. /* textField has not been added to the view yet,
  424. * we will try again when that happens. */
  425. return true;
  426. }
  427. return [textField becomeFirstResponder];
  428. }
  429. /* requests the SDL text field to lose focus and stop accepting text input.
  430. * also hides the onscreen virtual keyboard if no hardware keyboard is attached. */
  431. - (bool)stopTextInput
  432. {
  433. textFieldFocused = NO;
  434. if (!textField.window) {
  435. /* textField has not been added to the view yet,
  436. * we will try again when that happens. */
  437. return true;
  438. }
  439. [self resetTextState];
  440. return [textField resignFirstResponder];
  441. }
  442. - (void)keyboardWillShow:(NSNotification *)notification
  443. {
  444. #ifndef SDL_PLATFORM_TVOS
  445. CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
  446. /* The keyboard rect is in the coordinate space of the screen/window, but we
  447. * want its height in the coordinate space of the view. */
  448. kbrect = [self.view convertRect:kbrect fromView:nil];
  449. [self setKeyboardHeight:(int)kbrect.size.height];
  450. #endif
  451. /* A keyboard hide transition has been interrupted with a show (keyboardWillHide has been called but keyboardDidHide didn't).
  452. * since text input was stopped by the hide, we have to start it again. */
  453. if (hidingKeyboard) {
  454. SDL_StartTextInput(window);
  455. hidingKeyboard = NO;
  456. }
  457. }
  458. - (void)keyboardWillHide:(NSNotification *)notification
  459. {
  460. hidingKeyboard = YES;
  461. [self setKeyboardHeight:0];
  462. /* When the user dismisses the software keyboard by the "hide" button in the bottom right corner,
  463. * we want to reflect that on SDL_TextInputActive by calling SDL_StopTextInput...on certain conditions */
  464. if (SDL_TextInputActive(window)
  465. /* keyboardWillHide gets called when a hardware keyboard is attached,
  466. * keep text input state active if hiding while there is a hardware keyboard.
  467. * if the hardware keyboard gets detached, the software keyboard will appear anyway. */
  468. && !SDL_HasKeyboard()
  469. /* When the device changes orientation, a sequence of hide and show transitions are triggered.
  470. * keep text input state active in this case. */
  471. && !rotatingOrientation) {
  472. SDL_StopTextInput(window);
  473. }
  474. }
  475. - (void)keyboardDidHide:(NSNotification *)notification
  476. {
  477. hidingKeyboard = NO;
  478. }
  479. - (void)textFieldTextDidChange:(NSNotification *)notification
  480. {
  481. // When opening a password manager overlay to select a password and have it auto-filled,
  482. // text input becomes stopped as a result of the keyboard being hidden or the text field losing focus.
  483. // As a workaround, ensure text input is activated on any changes to the text field.
  484. bool startTextInputMomentarily = !SDL_TextInputActive(window);
  485. if (startTextInputMomentarily)
  486. SDL_StartTextInput(window);
  487. if (textField.markedTextRange == nil) {
  488. NSUInteger compareLength = SDL_min(textField.text.length, committedText.length);
  489. NSUInteger matchLength;
  490. // Backspace over characters that are no longer in the string
  491. for (matchLength = 0; matchLength < compareLength; ++matchLength) {
  492. if ([committedText characterAtIndex:matchLength] != [textField.text characterAtIndex:matchLength]) {
  493. break;
  494. }
  495. }
  496. if (matchLength < committedText.length) {
  497. size_t deleteLength = SDL_utf8strlen([[committedText substringFromIndex:matchLength] UTF8String]);
  498. while (deleteLength > 0) {
  499. // Send distinct down and up events for each backspace action
  500. SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_BACKSPACE, true);
  501. SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_BACKSPACE, false);
  502. --deleteLength;
  503. }
  504. }
  505. if (matchLength < textField.text.length) {
  506. NSString *pendingText = [textField.text substringFromIndex:matchLength];
  507. if (!SDL_HardwareKeyboardKeyPressed()) {
  508. /* Go through all the characters in the string we've been sent and
  509. * convert them to key presses */
  510. NSUInteger i;
  511. for (i = 0; i < pendingText.length; i++) {
  512. SDL_SendKeyboardUnicodeKey(0, [pendingText characterAtIndex:i]);
  513. }
  514. }
  515. SDL_SendKeyboardText([pendingText UTF8String]);
  516. }
  517. committedText = textField.text;
  518. }
  519. if (startTextInputMomentarily)
  520. SDL_StopTextInput(window);
  521. }
  522. - (void)updateKeyboard
  523. {
  524. SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *) window->internal;
  525. CGAffineTransform t = self.view.transform;
  526. CGPoint offset = CGPointMake(0.0, 0.0);
  527. #ifdef SDL_PLATFORM_VISIONOS
  528. CGRect frame = UIKit_ComputeViewFrame(window);
  529. #else
  530. CGRect frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen);
  531. #endif
  532. if (self.keyboardHeight && self.textInputRect.h) {
  533. int rectbottom = (int)(self.textInputRect.y + self.textInputRect.h);
  534. int keybottom = (int)(self.view.bounds.size.height - self.keyboardHeight);
  535. if (keybottom < rectbottom) {
  536. offset.y = keybottom - rectbottom;
  537. }
  538. }
  539. /* Apply this view's transform (except any translation) to the offset, in
  540. * order to orient it correctly relative to the frame's coordinate space. */
  541. t.tx = 0.0;
  542. t.ty = 0.0;
  543. offset = CGPointApplyAffineTransform(offset, t);
  544. // Apply the updated offset to the view's frame.
  545. frame.origin.x += offset.x;
  546. frame.origin.y += offset.y;
  547. self.view.frame = frame;
  548. }
  549. - (void)setKeyboardHeight:(int)height
  550. {
  551. keyboardHeight = height;
  552. [self updateKeyboard];
  553. }
  554. // UITextFieldDelegate method. Invoked when user types something.
  555. - (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
  556. {
  557. if (textField.markedTextRange == nil) {
  558. if ([string length] == 0 && textField.text.length < 16) {
  559. [self resetTextState];
  560. }
  561. }
  562. return YES;
  563. }
  564. // Terminates the editing session
  565. - (BOOL)textFieldShouldReturn:(UITextField *)_textField
  566. {
  567. SDL_SendKeyboardKeyAutoRelease(0, SDL_SCANCODE_RETURN);
  568. if (textFieldFocused &&
  569. SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, false)) {
  570. SDL_StopTextInput(window);
  571. }
  572. return YES;
  573. }
  574. - (void)resetTextState
  575. {
  576. textField.text = obligateForBackspace;
  577. committedText = textField.text;
  578. }
  579. #endif
  580. @end
  581. // iPhone keyboard addition functions
  582. #ifdef SDL_IPHONE_KEYBOARD
  583. static SDL_uikitviewcontroller *GetWindowViewController(SDL_Window *window)
  584. {
  585. if (!window || !window->internal) {
  586. SDL_SetError("Invalid window");
  587. return nil;
  588. }
  589. SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
  590. return data.viewcontroller;
  591. }
  592. bool UIKit_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
  593. {
  594. return true;
  595. }
  596. bool UIKit_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
  597. {
  598. @autoreleasepool {
  599. SDL_uikitviewcontroller *vc = GetWindowViewController(window);
  600. return [vc startTextInput];
  601. }
  602. }
  603. bool UIKit_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window)
  604. {
  605. @autoreleasepool {
  606. SDL_uikitviewcontroller *vc = GetWindowViewController(window);
  607. return [vc stopTextInput];
  608. }
  609. }
  610. void UIKit_SetTextInputProperties(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
  611. {
  612. @autoreleasepool {
  613. SDL_uikitviewcontroller *vc = GetWindowViewController(window);
  614. [vc setTextFieldProperties:props];
  615. }
  616. }
  617. bool UIKit_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window)
  618. {
  619. @autoreleasepool {
  620. SDL_uikitviewcontroller *vc = GetWindowViewController(window);
  621. if (vc != nil) {
  622. return vc.textFieldFocused;
  623. }
  624. return false;
  625. }
  626. }
  627. bool UIKit_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window)
  628. {
  629. @autoreleasepool {
  630. SDL_uikitviewcontroller *vc = GetWindowViewController(window);
  631. if (vc != nil) {
  632. vc.textInputRect = window->text_input_rect;
  633. if (vc.textFieldFocused) {
  634. [vc updateKeyboard];
  635. }
  636. }
  637. }
  638. return true;
  639. }
  640. #endif // SDL_IPHONE_KEYBOARD
  641. #endif // SDL_VIDEO_DRIVER_UIKIT