Atlas - SDL_uikitviewcontroller.m

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