SDL  2.0
SDL_uikitviewcontroller.m
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2017 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 
32 #import "SDL_uikitmessagebox.h"
33 #include "SDL_uikitvideo.h"
34 #include "SDL_uikitmodes.h"
35 #include "SDL_uikitwindow.h"
36 #include "SDL_uikitopengles.h"
37 
38 #if SDL_IPHONE_KEYBOARD
39 #include "keyinfotable.h"
40 #endif
41 
42 #if TARGET_OS_TV
43 static void SDLCALL
44 SDL_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 @implementation SDL_uikitviewcontroller {
54  CADisplayLink *displayLink;
55  int animationInterval;
56  void (*animationCallback)(void*);
57  void *animationCallbackParam;
58 
59 #if SDL_IPHONE_KEYBOARD
60  UITextField *textField;
61  BOOL rotatingOrientation;
62 #endif
63 }
64 
65 @synthesize window;
66 
67 - (instancetype)initWithSDLWindow:(SDL_Window *)_window
68 {
69  if (self = [super initWithNibName:nil bundle:nil]) {
70  self.window = _window;
71 
72 #if SDL_IPHONE_KEYBOARD
73  [self initKeyboard];
74  rotatingOrientation = FALSE;
75 #endif
76 
77 #if TARGET_OS_TV
79  SDL_AppleTVControllerUIHintChanged,
80  (__bridge void *) self);
81 #endif
82  }
83  return self;
84 }
85 
86 - (void)dealloc
87 {
88 #if SDL_IPHONE_KEYBOARD
89  [self deinitKeyboard];
90 #endif
91 
92 #if TARGET_OS_TV
94  SDL_AppleTVControllerUIHintChanged,
95  (__bridge void *) self);
96 #endif
97 }
98 
99 - (void)setAnimationCallback:(int)interval
100  callback:(void (*)(void*))callback
101  callbackParam:(void*)callbackParam
102 {
103  [self stopAnimation];
104 
105  animationInterval = interval;
106  animationCallback = callback;
107  animationCallbackParam = callbackParam;
108 
109  if (animationCallback) {
110  [self startAnimation];
111  }
112 }
113 
115 {
116  displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
117 
118 #ifdef __IPHONE_10_3
119  SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
120 
121  if ([displayLink respondsToSelector:@selector(preferredFramesPerSecond)]
122  && data != nil && data.uiwindow != nil
123  && [data.uiwindow.screen respondsToSelector:@selector(maximumFramesPerSecond)]) {
124  displayLink.preferredFramesPerSecond = data.uiwindow.screen.maximumFramesPerSecond / animationInterval;
125  } else
126 #endif
127  {
128 #if __IPHONE_OS_VERSION_MIN_REQUIRED < 100300
129  [displayLink setFrameInterval:animationInterval];
130 #endif
131  }
132 
133  [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
134 }
135 
137 {
138  [displayLink invalidate];
139  displayLink = nil;
140 }
141 
142 - (void)doLoop:(CADisplayLink*)sender
143 {
144  /* Don't run the game loop while a messagebox is up */
145  if (!UIKit_ShowingMessageBox()) {
146  /* See the comment in the function definition. */
148 
149  animationCallback(animationCallbackParam);
150  }
151 }
152 
153 - (void)loadView
154 {
155  /* Do nothing. */
156 }
157 
159 {
160  const CGSize size = self.view.bounds.size;
161  int w = (int) size.width;
162  int h = (int) size.height;
163 
165 }
166 
167 #if !TARGET_OS_TV
168 - (NSUInteger)supportedInterfaceOrientations
169 {
171 }
172 
173 #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
174 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient
175 {
176  return ([self supportedInterfaceOrientations] & (1 << orient)) != 0;
177 }
178 #endif
179 
181 {
182  return (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0;
183 }
184 #endif
185 
186 /*
187  ---- Keyboard related functionality below this line ----
188  */
189 #if SDL_IPHONE_KEYBOARD
190 
191 @synthesize textInputRect;
192 @synthesize keyboardHeight;
193 @synthesize keyboardVisible;
194 
195 /* Set ourselves up as a UITextFieldDelegate */
196 - (void)initKeyboard
197 {
198  textField = [[UITextField alloc] initWithFrame:CGRectZero];
199  textField.delegate = self;
200  /* placeholder so there is something to delete! */
201  textField.text = @" ";
202 
203  /* set UITextInputTrait properties, mostly to defaults */
204  textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
205  textField.autocorrectionType = UITextAutocorrectionTypeNo;
206  textField.enablesReturnKeyAutomatically = NO;
207  textField.keyboardAppearance = UIKeyboardAppearanceDefault;
208  textField.keyboardType = UIKeyboardTypeDefault;
209  textField.returnKeyType = UIReturnKeyDefault;
210  textField.secureTextEntry = NO;
211 
212  textField.hidden = YES;
213  keyboardVisible = NO;
214 
215 #if !TARGET_OS_TV
216  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
217  [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
218  [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
219 #endif
220 }
221 
222 - (void)setView:(UIView *)view
223 {
224  [super setView:view];
225 
226  [view addSubview:textField];
227 
228  if (keyboardVisible) {
229  [self showKeyboard];
230  }
231 }
232 
233 /* willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are deprecated in iOS 8+ in favor of viewWillTransitionToSize */
234 #if TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
235 - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
236 {
237  [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
238  rotatingOrientation = TRUE;
239  [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {}
240  completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
241  rotatingOrientation = FALSE;
242  }];
243 }
244 #else
245 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
246  [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
247  rotatingOrientation = TRUE;
248 }
249 
250 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
251  [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
252  rotatingOrientation = FALSE;
253 }
254 #endif /* TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000 */
255 
256 - (void)deinitKeyboard
257 {
258 #if !TARGET_OS_TV
259  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
260  [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
261  [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
262 #endif
263 }
264 
265 /* reveal onscreen virtual keyboard */
266 - (void)showKeyboard
267 {
268  keyboardVisible = YES;
269  if (textField.window) {
270  [textField becomeFirstResponder];
271  }
272 }
273 
274 /* hide onscreen virtual keyboard */
275 - (void)hideKeyboard
276 {
277  keyboardVisible = NO;
278  [textField resignFirstResponder];
279 }
280 
281 - (void)keyboardWillShow:(NSNotification *)notification
282 {
283 #if !TARGET_OS_TV
284  CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
285 
286  /* The keyboard rect is in the coordinate space of the screen/window, but we
287  * want its height in the coordinate space of the view. */
288  kbrect = [self.view convertRect:kbrect fromView:nil];
289 
290  [self setKeyboardHeight:(int)kbrect.size.height];
291 #endif
292 }
293 
294 - (void)keyboardWillHide:(NSNotification *)notification
295 {
296  if (!rotatingOrientation) {
298  }
299  [self setKeyboardHeight:0];
300 }
301 
302 - (void)updateKeyboard
303 {
304  CGAffineTransform t = self.view.transform;
305  CGPoint offset = CGPointMake(0.0, 0.0);
306  CGRect frame = UIKit_ComputeViewFrame(window, self.view.window.screen);
307 
308  if (self.keyboardHeight) {
309  int rectbottom = self.textInputRect.y + self.textInputRect.h;
310  int keybottom = self.view.bounds.size.height - self.keyboardHeight;
311  if (keybottom < rectbottom) {
312  offset.y = keybottom - rectbottom;
313  }
314  }
315 
316  /* Apply this view's transform (except any translation) to the offset, in
317  * order to orient it correctly relative to the frame's coordinate space. */
318  t.tx = 0.0;
319  t.ty = 0.0;
320  offset = CGPointApplyAffineTransform(offset, t);
321 
322  /* Apply the updated offset to the view's frame. */
323  frame.origin.x += offset.x;
324  frame.origin.y += offset.y;
325 
326  self.view.frame = frame;
327 }
328 
329 - (void)setKeyboardHeight:(int)height
330 {
331  keyboardVisible = height > 0;
332  keyboardHeight = height;
333  [self updateKeyboard];
334 }
335 
336 /* UITextFieldDelegate method. Invoked when user types something. */
337 - (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
338 {
339  NSUInteger len = string.length;
340 
341  if (len == 0) {
342  /* it wants to replace text with nothing, ie a delete */
345  } else {
346  /* go through all the characters in the string we've been sent and
347  * convert them to key presses */
348  int i;
349  for (i = 0; i < len; i++) {
350  unichar c = [string characterAtIndex:i];
351  Uint16 mod = 0;
352  SDL_Scancode code;
353 
354  if (c < 127) {
355  /* figure out the SDL_Scancode and SDL_keymod for this unichar */
356  code = unicharToUIKeyInfoTable[c].code;
357  mod = unicharToUIKeyInfoTable[c].mod;
358  } else {
359  /* we only deal with ASCII right now */
360  code = SDL_SCANCODE_UNKNOWN;
361  mod = 0;
362  }
363 
364  if (mod & KMOD_SHIFT) {
365  /* If character uses shift, press shift down */
367  }
368 
369  /* send a keydown and keyup even for the character */
372 
373  if (mod & KMOD_SHIFT) {
374  /* If character uses shift, press shift back up */
376  }
377  }
378 
379  SDL_SendKeyboardText([string UTF8String]);
380  }
381 
382  return NO; /* don't allow the edit! (keep placeholder text there) */
383 }
384 
385 /* Terminates the editing session */
386 - (BOOL)textFieldShouldReturn:(UITextField*)_textField
387 {
391  return YES;
392 }
393 
394 #endif
395 
396 @end
397 
398 /* iPhone keyboard addition functions */
399 #if SDL_IPHONE_KEYBOARD
400 
402 GetWindowViewController(SDL_Window * window)
403 {
404  if (!window || !window->driverdata) {
405  SDL_SetError("Invalid window");
406  return nil;
407  }
408 
409  SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
410 
411  return data.viewcontroller;
412 }
413 
414 SDL_bool
415 UIKit_HasScreenKeyboardSupport(_THIS)
416 {
417  return SDL_TRUE;
418 }
419 
420 void
421 UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window)
422 {
423  @autoreleasepool {
424  SDL_uikitviewcontroller *vc = GetWindowViewController(window);
425  [vc showKeyboard];
426  }
427 }
428 
429 void
430 UIKit_HideScreenKeyboard(_THIS, SDL_Window *window)
431 {
432  @autoreleasepool {
433  SDL_uikitviewcontroller *vc = GetWindowViewController(window);
434  [vc hideKeyboard];
435  }
436 }
437 
438 SDL_bool
439 UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window)
440 {
441  @autoreleasepool {
442  SDL_uikitviewcontroller *vc = GetWindowViewController(window);
443  if (vc != nil) {
444  return vc.isKeyboardVisible;
445  }
446  return SDL_FALSE;
447  }
448 }
449 
450 void
451 UIKit_SetTextInputRect(_THIS, SDL_Rect *rect)
452 {
453  if (!rect) {
454  SDL_InvalidParamError("rect");
455  return;
456  }
457 
458  @autoreleasepool {
459  SDL_uikitviewcontroller *vc = GetWindowViewController(SDL_GetFocusWindow());
460  if (vc != nil) {
461  vc.textInputRect = *rect;
462 
463  if (vc.keyboardVisible) {
464  [vc updateKeyboard];
465  }
466  }
467  }
468 }
469 
470 
471 #endif /* SDL_IPHONE_KEYBOARD */
472 
473 #endif /* SDL_VIDEO_DRIVER_UIKIT */
474 
475 /* vi: set ts=4 sw=4 expandtab: */
GLsizei const GLchar *const * string
GLint GLint GLint GLint GLint x
Definition: SDL_opengl.h:1574
SDL_Rect rect
Definition: testrelative.c:27
GLfloat GLfloat GLfloat GLfloat h
SDL_uikitviewcontroller * viewcontroller
SDL_Scancode code
Definition: keyinfotable.h:36
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
GLintptr offset
int SDL_SendWindowEvent(SDL_Window *window, Uint8 windowevent, int data1, int data2)
#define SDL_InvalidParamError(param)
Definition: SDL_error.h:54
NSUInteger UIKit_GetSupportedOrientations(SDL_Window *window)
GLenum GLsizei len
GLuint const GLchar * name
void UIKit_GL_RestoreCurrentContext(void)
int SDL_SendKeyboardKey(Uint8 state, SDL_Scancode scancode)
Definition: SDL_keyboard.c:679
#define SDL_StopTextInput
#define KMOD_SHIFT
Definition: SDL_keycode.h:343
static UIKitKeyInfo unicharToUIKeyInfoTable[]
Definition: keyinfotable.h:41
#define _THIS
int SDL_SendKeyboardText(const char *text)
Definition: SDL_keyboard.c:789
int frame
Definition: teststreaming.c:60
SDL_Window * SDL_GetFocusWindow(void)
Definition: SDL_video.c:2594
#define TRUE
Definition: edid-parse.c:33
const GLubyte * c
GLubyte GLubyte GLubyte GLubyte w
static Uint32 callback(Uint32 interval, void *param)
Definition: testtimer.c:34
GLint GLint GLint GLint GLint GLint y
Definition: SDL_opengl.h:1574
UIWindow * uiwindow
GLsizeiptr size
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
SDL_bool
Definition: SDL_stdinc.h:139
#define SDL_SetError
GLint GLint GLsizei GLsizei height
Definition: SDL_opengl.h:1572
EGLSurface EGLNativeWindowType * window
Definition: eglext.h:1025
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
The type used to identify a window.
Definition: SDL_sysvideo.h:73
#define SDL_AddHintCallback
#define SDL_DelHintCallback
uint16_t Uint16
An unsigned 16-bit integer type.
Definition: SDL_stdinc.h:161
void * driverdata
Definition: SDL_sysvideo.h:111
#define SDL_PRESSED
Definition: SDL_events.h:50
#define FALSE
Definition: edid-parse.c:34
#define SDL_RELEASED
Definition: SDL_events.h:49
#define SDLCALL
Definition: SDL_internal.h:45
SDL_Scancode
The SDL keyboard scancode representation.
Definition: SDL_scancode.h:43
#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:345
GLdouble GLdouble t
Definition: SDL_opengl.h:2071
A rectangle, with the origin at the upper left.
Definition: SDL_rect.h:64
NSUInteger supportedInterfaceOrientations()