SDL 2.0
SDL_uikitviewcontroller.m
Go to the documentation of this file.
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2019 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
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#pragma clang diagnostic push
61#pragma clang diagnostic ignored "-Wunguarded-availability-new"
62 if ([viewcontroller respondsToSelector:@selector(setNeedsUpdateOfHomeIndicatorAutoHidden)]) {
63 [viewcontroller setNeedsUpdateOfHomeIndicatorAutoHidden];
64 [viewcontroller setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
65 }
66#pragma clang diagnostic pop
67 }
68}
69#endif
70
71@implementation SDL_uikitviewcontroller {
72 CADisplayLink *displayLink;
73 int animationInterval;
74 void (*animationCallback)(void*);
75 void *animationCallbackParam;
76
77#if SDL_IPHONE_KEYBOARD
78 UITextField *textField;
79 BOOL hardwareKeyboard;
80 BOOL showingKeyboard;
81 BOOL rotatingOrientation;
82 NSString *changeText;
83 NSString *obligateForBackspace;
84#endif
85}
86
87@synthesize window;
88
89- (instancetype)initWithSDLWindow:(SDL_Window *)_window
90{
91 if (self = [super initWithNibName:nil bundle:nil]) {
92 self.window = _window;
93
94#if SDL_IPHONE_KEYBOARD
95 [self initKeyboard];
96 hardwareKeyboard = NO;
97 showingKeyboard = NO;
98 rotatingOrientation = NO;
99#endif
100
101#if TARGET_OS_TV
103 SDL_AppleTVControllerUIHintChanged,
104 (__bridge void *) self);
105#endif
106
107#if !TARGET_OS_TV
109 SDL_HideHomeIndicatorHintChanged,
110 (__bridge void *) self);
111#endif
112 }
113 return self;
114}
115
116- (void)dealloc
117{
118#if SDL_IPHONE_KEYBOARD
119 [self deinitKeyboard];
120#endif
121
122#if TARGET_OS_TV
124 SDL_AppleTVControllerUIHintChanged,
125 (__bridge void *) self);
126#endif
127
128#if !TARGET_OS_TV
130 SDL_HideHomeIndicatorHintChanged,
131 (__bridge void *) self);
132#endif
133}
134
135- (void)setAnimationCallback:(int)interval
136 callback:(void (*)(void*))callback
137 callbackParam:(void*)callbackParam
138{
139 [self stopAnimation];
140
141 animationInterval = interval;
142 animationCallback = callback;
143 animationCallbackParam = callbackParam;
144
145 if (animationCallback) {
146 [self startAnimation];
147 }
148}
149
150- (void)startAnimation
151{
152 displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
153
154#ifdef __IPHONE_10_3
155 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
156
157 if ([displayLink respondsToSelector:@selector(preferredFramesPerSecond)]
158 && data != nil && data.uiwindow != nil
159 && [data.uiwindow.screen respondsToSelector:@selector(maximumFramesPerSecond)]) {
160 displayLink.preferredFramesPerSecond = data.uiwindow.screen.maximumFramesPerSecond / animationInterval;
161 } else
162#endif
163 {
164#if __IPHONE_OS_VERSION_MIN_REQUIRED < 100300
165 [displayLink setFrameInterval:animationInterval];
166#endif
167 }
168
169 [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
170}
171
172- (void)stopAnimation
173{
174 [displayLink invalidate];
175 displayLink = nil;
176}
177
178- (void)doLoop:(CADisplayLink*)sender
179{
180 /* Don't run the game loop while a messagebox is up */
181 if (!UIKit_ShowingMessageBox()) {
182 /* See the comment in the function definition. */
184
185 animationCallback(animationCallbackParam);
186 }
187}
188
189- (void)loadView
190{
191 /* Do nothing. */
192}
193
194- (void)viewDidLayoutSubviews
195{
196 const CGSize size = self.view.bounds.size;
197 int w = (int) size.width;
198 int h = (int) size.height;
199
201}
202
203#if !TARGET_OS_TV
204- (NSUInteger)supportedInterfaceOrientations
205{
207}
208
209#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
210- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient
211{
212 return ([self supportedInterfaceOrientations] & (1 << orient)) != 0;
213}
214#endif
215
216- (BOOL)prefersStatusBarHidden
217{
218 BOOL hidden = (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0;
219 return hidden;
220}
221
222- (BOOL)prefersHomeIndicatorAutoHidden
223{
224 BOOL hidden = NO;
225 if (self.homeIndicatorHidden == 1) {
226 hidden = YES;
227 }
228 return hidden;
229}
230
231- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
232{
233 if (self.homeIndicatorHidden >= 0) {
234 if (self.homeIndicatorHidden == 2) {
235 return UIRectEdgeAll;
236 } else {
237 return UIRectEdgeNone;
238 }
239 }
240
241 /* By default, fullscreen and borderless windows get all screen gestures */
242 if ((window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0) {
243 return UIRectEdgeAll;
244 } else {
245 return UIRectEdgeNone;
246 }
247}
248#endif
249
250/*
251 ---- Keyboard related functionality below this line ----
252 */
253#if SDL_IPHONE_KEYBOARD
254
255@synthesize textInputRect;
256@synthesize keyboardHeight;
257@synthesize keyboardVisible;
258
259/* Set ourselves up as a UITextFieldDelegate */
260- (void)initKeyboard
261{
262 changeText = nil;
263 obligateForBackspace = @" "; /* 64 space */
264 textField = [[UITextField alloc] initWithFrame:CGRectZero];
265 textField.delegate = self;
266 /* placeholder so there is something to delete! */
267 textField.text = obligateForBackspace;
268
269 /* set UITextInputTrait properties, mostly to defaults */
270 textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
271 textField.autocorrectionType = UITextAutocorrectionTypeNo;
272 textField.enablesReturnKeyAutomatically = NO;
273 textField.keyboardAppearance = UIKeyboardAppearanceDefault;
274 textField.keyboardType = UIKeyboardTypeDefault;
275 textField.returnKeyType = UIReturnKeyDefault;
276 textField.secureTextEntry = NO;
277
278 textField.hidden = YES;
279 keyboardVisible = NO;
280
281 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
282#if !TARGET_OS_TV
283 [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
284 [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
285#endif
286 [center addObserver:self selector:@selector(textFieldTextDidChange:) name:UITextFieldTextDidChangeNotification object:nil];
287}
288
289- (NSArray *) keyCommands {
290 NSMutableArray *commands = [[NSMutableArray alloc] init];
291 [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
292 [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
293 [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
294 [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
295 [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:kNilOptions action:@selector(handleCommand:)]];
296 return [NSArray arrayWithArray:commands];
297}
298
299- (void) handleCommand: (UIKeyCommand *) keyCommand {
301
302 if (keyCommand.input == UIKeyInputUpArrow) {
303 scancode = SDL_SCANCODE_UP;
304 } else if (keyCommand.input == UIKeyInputDownArrow) {
305 scancode = SDL_SCANCODE_DOWN;
306 } else if (keyCommand.input == UIKeyInputLeftArrow) {
307 scancode = SDL_SCANCODE_LEFT;
308 } else if (keyCommand.input == UIKeyInputRightArrow) {
309 scancode = SDL_SCANCODE_RIGHT;
310 } else if (keyCommand.input == UIKeyInputEscape) {
311 scancode = SDL_SCANCODE_ESCAPE;
312 }
313
314 if (scancode != SDL_SCANCODE_UNKNOWN) {
317 }
318}
319
320- (void) downArrow: (UIKeyCommand *) keyCommand {
321 NSLog(@"down arrow!");
322}
323
324- (void)setView:(UIView *)view
325{
326 [super setView:view];
327
328 [view addSubview:textField];
329
330 if (keyboardVisible) {
331 [self showKeyboard];
332 }
333}
334
335/* willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are deprecated in iOS 8+ in favor of viewWillTransitionToSize */
336#if TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
337- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
338{
339 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
340 rotatingOrientation = YES;
341 [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {}
342 completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
343 rotatingOrientation = NO;
344 }];
345}
346#else
347- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
348 [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
349 rotatingOrientation = YES;
350}
351
352- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
353 [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
354 rotatingOrientation = NO;
355}
356#endif /* TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000 */
357
358- (void)deinitKeyboard
359{
360 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
361#if !TARGET_OS_TV
362 [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
363 [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
364#endif
365 [center removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
366}
367
368/* reveal onscreen virtual keyboard */
369- (void)showKeyboard
370{
371 keyboardVisible = YES;
372 if (textField.window) {
373 showingKeyboard = YES;
374 [textField becomeFirstResponder];
375 showingKeyboard = NO;
376 }
377}
378
379/* hide onscreen virtual keyboard */
380- (void)hideKeyboard
381{
382 keyboardVisible = NO;
383 [textField resignFirstResponder];
384}
385
386- (void)keyboardWillShow:(NSNotification *)notification
387{
388#if !TARGET_OS_TV
389 CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
390
391 /* The keyboard rect is in the coordinate space of the screen/window, but we
392 * want its height in the coordinate space of the view. */
393 kbrect = [self.view convertRect:kbrect fromView:nil];
394
395 [self setKeyboardHeight:(int)kbrect.size.height];
396#endif
397}
398
399- (void)keyboardWillHide:(NSNotification *)notification
400{
401 if (!showingKeyboard && !rotatingOrientation) {
403 }
404 [self setKeyboardHeight:0];
405}
406
407- (void)textFieldTextDidChange:(NSNotification *)notification
408{
409 if (changeText!=nil && textField.markedTextRange == nil)
410 {
411 NSUInteger len = changeText.length;
412 if (len > 0) {
413 /* Go through all the characters in the string we've been sent and
414 * convert them to key presses */
415 int i;
416 for (i = 0; i < len; i++) {
417 unichar c = [changeText characterAtIndex:i];
418 SDL_Scancode code;
419 Uint16 mod;
420
421 if (c < 127) {
422 /* Figure out the SDL_Scancode and SDL_keymod for this unichar */
425 } else {
426 /* We only deal with ASCII right now */
428 mod = 0;
429 }
430
431 if (mod & KMOD_SHIFT) {
432 /* If character uses shift, press shift down */
434 }
435
436 /* send a keydown and keyup even for the character */
439
440 if (mod & KMOD_SHIFT) {
441 /* If character uses shift, press shift back up */
443 }
444 }
445 SDL_SendKeyboardText([changeText UTF8String]);
446 }
447 changeText = nil;
448 }
449}
450
451- (void)updateKeyboard
452{
453 CGAffineTransform t = self.view.transform;
454 CGPoint offset = CGPointMake(0.0, 0.0);
455 CGRect frame = UIKit_ComputeViewFrame(window, self.view.window.screen);
456
457 if (self.keyboardHeight) {
458 int rectbottom = self.textInputRect.y + self.textInputRect.h;
459 int keybottom = self.view.bounds.size.height - self.keyboardHeight;
460 if (keybottom < rectbottom) {
461 offset.y = keybottom - rectbottom;
462 }
463 }
464
465 /* Apply this view's transform (except any translation) to the offset, in
466 * order to orient it correctly relative to the frame's coordinate space. */
467 t.tx = 0.0;
468 t.ty = 0.0;
469 offset = CGPointApplyAffineTransform(offset, t);
470
471 /* Apply the updated offset to the view's frame. */
472 frame.origin.x += offset.x;
473 frame.origin.y += offset.y;
474
475 self.view.frame = frame;
476}
477
478- (void)setKeyboardHeight:(int)height
479{
480 keyboardVisible = height > 0;
481 keyboardHeight = height;
482 [self updateKeyboard];
483}
484
485/* UITextFieldDelegate method. Invoked when user types something. */
486- (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
487{
488 NSUInteger len = string.length;
489 if (len == 0) {
490 changeText = nil;
491 if (textField.markedTextRange == nil) {
492 /* it wants to replace text with nothing, ie a delete */
495 }
496 if (textField.text.length < 16) {
497 textField.text = obligateForBackspace;
498 }
499 } else {
500 changeText = string;
501 }
502 return YES;
503}
504
505/* Terminates the editing session */
506- (BOOL)textFieldShouldReturn:(UITextField*)_textField
507{
510 if (keyboardVisible &&
513 }
514 return YES;
515}
516
517#endif
518
519@end
520
521/* iPhone keyboard addition functions */
522#if SDL_IPHONE_KEYBOARD
523
525GetWindowViewController(SDL_Window * window)
526{
527 if (!window || !window->driverdata) {
528 SDL_SetError("Invalid window");
529 return nil;
530 }
531
532 SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
533
534 return data.viewcontroller;
535}
536
538UIKit_HasScreenKeyboardSupport(_THIS)
539{
540 return SDL_TRUE;
541}
542
543void
544UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window)
545{
546 @autoreleasepool {
547 SDL_uikitviewcontroller *vc = GetWindowViewController(window);
548 [vc showKeyboard];
549 }
550}
551
552void
553UIKit_HideScreenKeyboard(_THIS, SDL_Window *window)
554{
555 @autoreleasepool {
556 SDL_uikitviewcontroller *vc = GetWindowViewController(window);
557 [vc hideKeyboard];
558 }
559}
560
562UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window)
563{
564 @autoreleasepool {
565 SDL_uikitviewcontroller *vc = GetWindowViewController(window);
566 if (vc != nil) {
567 return vc.keyboardVisible;
568 }
569 return SDL_FALSE;
570 }
571}
572
573void
574UIKit_SetTextInputRect(_THIS, SDL_Rect *rect)
575{
576 if (!rect) {
577 SDL_InvalidParamError("rect");
578 return;
579 }
580
581 @autoreleasepool {
582 SDL_uikitviewcontroller *vc = GetWindowViewController(SDL_GetFocusWindow());
583 if (vc != nil) {
584 vc.textInputRect = *rect;
585
586 if (vc.keyboardVisible) {
587 [vc updateKeyboard];
588 }
589 }
590 }
591}
592
593
594#endif /* SDL_IPHONE_KEYBOARD */
595
596#endif /* SDL_VIDEO_DRIVER_UIKIT */
597
598/* vi: set ts=4 sw=4 expandtab: */
#define _THIS
#define SDL_SetError
#define SDL_DelHintCallback
#define SDL_StopTextInput
#define SDL_GetHintBoolean
#define SDL_atoi
#define SDL_AddHintCallback
SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char const char SDL_SCANF_FORMAT_STRING const char return SDL_ThreadFunction const char void return Uint32 return Uint32 void
#define SDL_InvalidParamError(param)
Definition: SDL_error.h:54
#define SDL_RELEASED
Definition: SDL_events.h:49
#define SDL_PRESSED
Definition: SDL_events.h:50
#define SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS
A variable controlling whether controllers used with the Apple TV generate UI events.
Definition: SDL_hints.h:378
#define SDL_HINT_IOS_HIDE_HOME_INDICATOR
A variable controlling whether the home indicator bar on iPhone X should be hidden.
Definition: SDL_hints.h:399
#define SDL_HINT_RETURN_KEY_HIDES_IME
A variable to control whether the return key on the soft keyboard should hide the soft keyboard on An...
Definition: SDL_hints.h:897
#define SDLCALL
Definition: SDL_internal.h:49
int SDL_SendKeyboardText(const char *text)
Definition: SDL_keyboard.c:789
int SDL_SendKeyboardKey(Uint8 state, SDL_Scancode scancode)
Definition: SDL_keyboard.c:679
#define KMOD_SHIFT
Definition: SDL_keycode.h:343
GLint GLint GLint GLint GLint GLint y
Definition: SDL_opengl.h:1574
GLdouble GLdouble t
Definition: SDL_opengl.h:2071
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
GLint GLint GLint GLint GLint x
Definition: SDL_opengl.h:1574
GLint GLint GLsizei GLsizei height
Definition: SDL_opengl.h:1572
GLintptr offset
GLsizei const GLubyte * commands
GLenum GLsizei len
const GLubyte * c
GLuint const GLchar * name
GLsizeiptr size
GLsizei const GLchar *const * string
GLfloat GLfloat GLfloat GLfloat h
GLubyte GLubyte GLubyte GLubyte w
SDL_Scancode
The SDL keyboard scancode representation.
Definition: SDL_scancode.h:44
@ SDL_SCANCODE_ESCAPE
Definition: SDL_scancode.h:93
@ SDL_SCANCODE_RETURN
Definition: SDL_scancode.h:92
@ SDL_SCANCODE_LSHIFT
Definition: SDL_scancode.h:329
@ SDL_SCANCODE_UNKNOWN
Definition: SDL_scancode.h:45
@ SDL_SCANCODE_DOWN
Definition: SDL_scancode.h:178
@ SDL_SCANCODE_RIGHT
Definition: SDL_scancode.h:176
@ SDL_SCANCODE_UP
Definition: SDL_scancode.h:179
@ SDL_SCANCODE_LEFT
Definition: SDL_scancode.h:177
@ SDL_SCANCODE_BACKSPACE
Definition: SDL_scancode.h:94
SDL_bool
Definition: SDL_stdinc.h:162
@ SDL_TRUE
Definition: SDL_stdinc.h:164
@ SDL_FALSE
Definition: SDL_stdinc.h:163
uint16_t Uint16
Definition: SDL_stdinc.h:191
SDL_Window * SDL_GetFocusWindow(void)
Definition: SDL_video.c:2696
void UIKit_GL_RestoreCurrentContext(void)
NSUInteger UIKit_GetSupportedOrientations(SDL_Window *window)
@ SDL_WINDOW_FULLSCREEN
Definition: SDL_video.h:100
@ SDL_WINDOW_BORDERLESS
Definition: SDL_video.h:104
@ SDL_WINDOWEVENT_RESIZED
Definition: SDL_video.h:155
int SDL_SendWindowEvent(SDL_Window *window, Uint8 windowevent, int data1, int data2)
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:50
EGLSurface EGLNativeWindowType * window
Definition: eglext.h:1025
A rectangle, with the origin at the upper left (integer).
Definition: SDL_rect.h:78
UIWindow * uiwindow
SDL_uikitviewcontroller * viewcontroller
The type used to identify a window.
Definition: SDL_sysvideo.h:74
SDL_Scancode code
Definition: keyinfotable.h:36
SDL_Rect rect
Definition: testrelative.c:27
int frame
Definition: teststreaming.c:60
static Uint32 callback(Uint32 interval, void *param)
Definition: testtimer.c:34
static UIKitKeyInfo unicharToUIKeyInfoTable[]
Definition: keyinfotable.h:41