1/*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2016 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
31#import "SDL_uikitviewcontroller.h"
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
43static void
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@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#endif
62}
63
64@synthesize window;
65
66- (instancetype)initWithSDLWindow:(SDL_Window *)_window
67{
68    if (self = [super initWithNibName:nil bundle:nil]) {
69        self.window = _window;
70
71#if SDL_IPHONE_KEYBOARD
72        [self initKeyboard];
73#endif
74
75#if TARGET_OS_TV
76        SDL_AddHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
77                            SDL_AppleTVControllerUIHintChanged,
78                            (__bridge void *) self);
79#endif
80    }
81    return self;
82}
83
84- (void)dealloc
85{
86#if SDL_IPHONE_KEYBOARD
87    [self deinitKeyboard];
88#endif
89
90#if TARGET_OS_TV
91    SDL_DelHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
92                        SDL_AppleTVControllerUIHintChanged,
93                        (__bridge void *) self);
94#endif
95}
96
97- (void)setAnimationCallback:(int)interval
98                    callback:(void (*)(void*))callback
99               callbackParam:(void*)callbackParam
100{
101    [self stopAnimation];
102
103    animationInterval = interval;
104    animationCallback = callback;
105    animationCallbackParam = callbackParam;
106
107    if (animationCallback) {
108        [self startAnimation];
109    }
110}
111
112- (void)startAnimation
113{
114    displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
115    [displayLink setFrameInterval:animationInterval];
116    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
117}
118
119- (void)stopAnimation
120{
121    [displayLink invalidate];
122    displayLink = nil;
123}
124
125- (void)doLoop:(CADisplayLink*)sender
126{
127    /* Don't run the game loop while a messagebox is up */
128    if (!UIKit_ShowingMessageBox()) {
129        /* See the comment in the function definition. */
130        UIKit_GL_RestoreCurrentContext();
131
132        animationCallback(animationCallbackParam);
133    }
134}
135
136- (void)loadView
137{
138    /* Do nothing. */
139}
140
141- (void)viewDidLayoutSubviews
142{
143    const CGSize size = self.view.bounds.size;
144    int w = (int) size.width;
145    int h = (int) size.height;
146
147    SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h);
148}
149
150#if !TARGET_OS_TV
151- (NSUInteger)supportedInterfaceOrientations
152{
153    return UIKit_GetSupportedOrientations(window);
154}
155
156- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient
157{
158    return ([self supportedInterfaceOrientations] & (1 << orient)) != 0;
159}
160
161- (BOOL)prefersStatusBarHidden
162{
163    return (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0;
164}
165#endif
166
167/*
168 ---- Keyboard related functionality below this line ----
169 */
170#if SDL_IPHONE_KEYBOARD
171
172@synthesize textInputRect;
173@synthesize keyboardHeight;
174@synthesize keyboardVisible;
175
176/* Set ourselves up as a UITextFieldDelegate */
177- (void)initKeyboard
178{
179    textField = [[UITextField alloc] initWithFrame:CGRectZero];
180    textField.delegate = self;
181    /* placeholder so there is something to delete! */
182    textField.text = @" ";
183
184    /* set UITextInputTrait properties, mostly to defaults */
185    textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
186    textField.autocorrectionType = UITextAutocorrectionTypeNo;
187    textField.enablesReturnKeyAutomatically = NO;
188    textField.keyboardAppearance = UIKeyboardAppearanceDefault;
189    textField.keyboardType = UIKeyboardTypeDefault;
190    textField.returnKeyType = UIReturnKeyDefault;
191    textField.secureTextEntry = NO;
192
193    textField.hidden = YES;
194    keyboardVisible = NO;
195
196#if !TARGET_OS_TV
197    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
198    [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
199    [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
200#endif
201}
202
203- (void)setView:(UIView *)view
204{
205    [super setView:view];
206
207    [view addSubview:textField];
208
209    if (keyboardVisible) {
210        [self showKeyboard];
211    }
212}
213
214- (void)deinitKeyboard
215{
216#if !TARGET_OS_TV
217    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
218    [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
219    [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
220#endif
221}
222
223/* reveal onscreen virtual keyboard */
224- (void)showKeyboard
225{
226    keyboardVisible = YES;
227    if (textField.window) {
228        [textField becomeFirstResponder];
229    }
230}
231
232/* hide onscreen virtual keyboard */
233- (void)hideKeyboard
234{
235    keyboardVisible = NO;
236    [textField resignFirstResponder];
237}
238
239- (void)keyboardWillShow:(NSNotification *)notification
240{
241#if !TARGET_OS_TV
242    CGRect kbrect = [[notification userInfo][UIKeyboardFrameBeginUserInfoKey] CGRectValue];
243
244    /* The keyboard rect is in the coordinate space of the screen/window, but we
245     * want its height in the coordinate space of the view. */
246    kbrect = [self.view convertRect:kbrect fromView:nil];
247
248    [self setKeyboardHeight:(int)kbrect.size.height];
249#endif
250}
251
252- (void)keyboardWillHide:(NSNotification *)notification
253{
254    SDL_StopTextInput();
255    [self setKeyboardHeight:0];
256}
257
258- (void)updateKeyboard
259{
260    CGAffineTransform t = self.view.transform;
261    CGPoint offset = CGPointMake(0.0, 0.0);
262    CGRect frame = UIKit_ComputeViewFrame(window, self.view.window.screen);
263
264    if (self.keyboardHeight) {
265        int rectbottom = self.textInputRect.y + self.textInputRect.h;
266        int keybottom = self.view.bounds.size.height - self.keyboardHeight;
267        if (keybottom < rectbottom) {
268            offset.y = keybottom - rectbottom;
269        }
270    }
271
272    /* Apply this view's transform (except any translation) to the offset, in
273     * order to orient it correctly relative to the frame's coordinate space. */
274    t.tx = 0.0;
275    t.ty = 0.0;
276    offset = CGPointApplyAffineTransform(offset, t);
277
278    /* Apply the updated offset to the view's frame. */
279    frame.origin.x += offset.x;
280    frame.origin.y += offset.y;
281
282    self.view.frame = frame;
283}
284
285- (void)setKeyboardHeight:(int)height
286{
287    keyboardVisible = height > 0;
288    keyboardHeight = height;
289    [self updateKeyboard];
290}
291
292/* UITextFieldDelegate method.  Invoked when user types something. */
293- (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
294{
295    NSUInteger len = string.length;
296
297    if (len == 0) {
298        /* it wants to replace text with nothing, ie a delete */
299        SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_BACKSPACE);
300        SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_BACKSPACE);
301    } else {
302        /* go through all the characters in the string we've been sent and
303         * convert them to key presses */
304        int i;
305        for (i = 0; i < len; i++) {
306            unichar c = [string characterAtIndex:i];
307            Uint16 mod = 0;
308            SDL_Scancode code;
309
310            if (c < 127) {
311                /* figure out the SDL_Scancode and SDL_keymod for this unichar */
312                code = unicharToUIKeyInfoTable[c].code;
313                mod  = unicharToUIKeyInfoTable[c].mod;
314            } else {
315                /* we only deal with ASCII right now */
316                code = SDL_SCANCODE_UNKNOWN;
317                mod = 0;
318            }
319
320            if (mod & KMOD_SHIFT) {
321                /* If character uses shift, press shift down */
322                SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT);
323            }
324
325            /* send a keydown and keyup even for the character */
326            SDL_SendKeyboardKey(SDL_PRESSED, code);
327            SDL_SendKeyboardKey(SDL_RELEASED, code);
328
329            if (mod & KMOD_SHIFT) {
330                /* If character uses shift, press shift back up */
331                SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT);
332            }
333        }
334
335        SDL_SendKeyboardText([string UTF8String]);
336    }
337
338    return NO; /* don't allow the edit! (keep placeholder text there) */
339}
340
341/* Terminates the editing session */
342- (BOOL)textFieldShouldReturn:(UITextField*)_textField
343{
344    SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_RETURN);
345    SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RETURN);
346    SDL_StopTextInput();
347    return YES;
348}
349
350#endif
351
352@end
353
354/* iPhone keyboard addition functions */
355#if SDL_IPHONE_KEYBOARD
356
357static SDL_uikitviewcontroller *
358GetWindowViewController(SDL_Window * window)
359{
360    if (!window || !window->driverdata) {
361        SDL_SetError("Invalid window");
362        return nil;
363    }
364
365    SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
366
367    return data.viewcontroller;
368}
369
370SDL_bool
371UIKit_HasScreenKeyboardSupport(_THIS)
372{
373    return SDL_TRUE;
374}
375
376void
377UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window)
378{
379    @autoreleasepool {
380        SDL_uikitviewcontroller *vc = GetWindowViewController(window);
381        [vc showKeyboard];
382    }
383}
384
385void
386UIKit_HideScreenKeyboard(_THIS, SDL_Window *window)
387{
388    @autoreleasepool {
389        SDL_uikitviewcontroller *vc = GetWindowViewController(window);
390        [vc hideKeyboard];
391    }
392}
393
394SDL_bool
395UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window)
396{
397    @autoreleasepool {
398        SDL_uikitviewcontroller *vc = GetWindowViewController(window);
399        if (vc != nil) {
400            return vc.isKeyboardVisible;
401        }
402        return SDL_FALSE;
403    }
404}
405
406void
407UIKit_SetTextInputRect(_THIS, SDL_Rect *rect)
408{
409    if (!rect) {
410        SDL_InvalidParamError("rect");
411        return;
412    }
413
414    @autoreleasepool {
415        SDL_uikitviewcontroller *vc = GetWindowViewController(SDL_GetFocusWindow());
416        if (vc != nil) {
417            vc.textInputRect = *rect;
418
419            if (vc.keyboardVisible) {
420                [vc updateKeyboard];
421            }
422        }
423    }
424}
425
426
427#endif /* SDL_IPHONE_KEYBOARD */
428
429#endif /* SDL_VIDEO_DRIVER_UIKIT */
430
431/* vi: set ts=4 sw=4 expandtab: */
432