src/video/uikit/SDL_uikitview.m
author Alex Szpakowski <slime73@gmail.com>
Sun, 23 Nov 2014 23:23:47 -0400
branchiOS-improvements
changeset 9527 bbd9326ecacf
parent 9525 64e3f446d6d7
child 9529 4bf9830d8153
permissions -rw-r--r--
Cleaned up the iOS text input code, fixed the orientation of the view offset when the onscreen keyboard is shown in iOS 8+, and changed UIKit_SetTextInputRect to update the view offset if the keyboard is currently visible.
     1  /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2014 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_uikitview.h"
    26 
    27 #include "../../events/SDL_keyboard_c.h"
    28 #include "../../events/SDL_mouse_c.h"
    29 #include "../../events/SDL_touch_c.h"
    30 
    31 #if SDL_IPHONE_KEYBOARD
    32 #include "keyinfotable.h"
    33 #endif
    34 #include "SDL_uikitappdelegate.h"
    35 #include "SDL_uikitmodes.h"
    36 #include "SDL_uikitwindow.h"
    37 
    38 @implementation SDL_uikitview {
    39 
    40     SDL_TouchID touchId;
    41     UITouch *leftFingerDown;
    42 
    43 #if SDL_IPHONE_KEYBOARD
    44     UITextField *textField;
    45 #endif
    46 
    47 }
    48 
    49 @synthesize viewcontroller;
    50 
    51 - (id)initWithFrame:(CGRect)frame
    52 {
    53     if (self = [super initWithFrame:frame]) {
    54 #if SDL_IPHONE_KEYBOARD
    55         [self initKeyboard];
    56 #endif
    57 
    58         self.multipleTouchEnabled = YES;
    59 
    60         touchId = 1;
    61         SDL_AddTouch(touchId, "");
    62     }
    63 
    64     return self;
    65 
    66 }
    67 
    68 - (void)dealloc
    69 {
    70 #if SDL_IPHONE_KEYBOARD
    71     [self deinitKeyboard];
    72 #endif
    73 }
    74 
    75 - (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize
    76 {
    77     CGPoint point = [touch locationInView:self];
    78 
    79     if (normalize) {
    80         CGRect bounds = self.bounds;
    81         point.x /= bounds.size.width;
    82         point.y /= bounds.size.height;
    83     }
    84 
    85     return point;
    86 }
    87 
    88 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    89 {
    90     for (UITouch *touch in touches) {
    91         if (!leftFingerDown) {
    92             CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO];
    93 
    94             /* send moved event */
    95             SDL_SendMouseMotion(self.viewcontroller.window, SDL_TOUCH_MOUSEID, 0, locationInView.x, locationInView.y);
    96 
    97             /* send mouse down event */
    98             SDL_SendMouseButton(self.viewcontroller.window, SDL_TOUCH_MOUSEID, SDL_PRESSED, SDL_BUTTON_LEFT);
    99 
   100             leftFingerDown = touch;
   101         }
   102 
   103         CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
   104         SDL_SendTouch(touchId, (SDL_FingerID)((size_t)touch),
   105                       SDL_TRUE, locationInView.x, locationInView.y, 1.0f);
   106     }
   107 }
   108 
   109 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
   110 {
   111     for (UITouch *touch in touches) {
   112         if (touch == leftFingerDown) {
   113             /* send mouse up */
   114             SDL_SendMouseButton(self.viewcontroller.window, SDL_TOUCH_MOUSEID, SDL_RELEASED, SDL_BUTTON_LEFT);
   115             leftFingerDown = nil;
   116         }
   117 
   118         CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
   119         SDL_SendTouch(touchId, (SDL_FingerID)((size_t)touch),
   120                       SDL_FALSE, locationInView.x, locationInView.y, 1.0f);
   121     }
   122 }
   123 
   124 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
   125 {
   126     /*
   127         this can happen if the user puts more than 5 touches on the screen
   128         at once, or perhaps in other circumstances.  Usually (it seems)
   129         all active touches are canceled.
   130     */
   131     [self touchesEnded:touches withEvent:event];
   132 }
   133 
   134 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
   135 {
   136     for (UITouch *touch in touches) {
   137         if (touch == leftFingerDown) {
   138             CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO];
   139 
   140             /* send moved event */
   141             SDL_SendMouseMotion(self.viewcontroller.window, SDL_TOUCH_MOUSEID, 0, locationInView.x, locationInView.y);
   142         }
   143 
   144         CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
   145         SDL_SendTouchMotion(touchId, (SDL_FingerID)((size_t)touch),
   146                             locationInView.x, locationInView.y, 1.0f);
   147     }
   148 }
   149 
   150 /*
   151     ---- Keyboard related functionality below this line ----
   152 */
   153 #if SDL_IPHONE_KEYBOARD
   154 
   155 @synthesize textInputRect;
   156 @synthesize keyboardHeight;
   157 @synthesize keyboardVisible;
   158 
   159 /* Set ourselves up as a UITextFieldDelegate */
   160 - (void)initKeyboard
   161 {
   162     textField = [[UITextField alloc] initWithFrame:CGRectZero];
   163     textField.delegate = self;
   164     /* placeholder so there is something to delete! */
   165     textField.text = @" ";
   166 
   167     /* set UITextInputTrait properties, mostly to defaults */
   168     textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
   169     textField.autocorrectionType = UITextAutocorrectionTypeNo;
   170     textField.enablesReturnKeyAutomatically = NO;
   171     textField.keyboardAppearance = UIKeyboardAppearanceDefault;
   172     textField.keyboardType = UIKeyboardTypeDefault;
   173     textField.returnKeyType = UIReturnKeyDefault;
   174     textField.secureTextEntry = NO;
   175 
   176     textField.hidden = YES;
   177     keyboardVisible = NO;
   178     /* add the UITextField (hidden) to our view */
   179     [self addSubview:textField];
   180 
   181     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   182     [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
   183     [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
   184 }
   185 
   186 - (void)deinitKeyboard
   187 {
   188     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   189     [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
   190     [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
   191 }
   192 
   193 /* reveal onscreen virtual keyboard */
   194 - (void)showKeyboard
   195 {
   196     keyboardVisible = YES;
   197     [textField becomeFirstResponder];
   198 }
   199 
   200 /* hide onscreen virtual keyboard */
   201 - (void)hideKeyboard
   202 {
   203     keyboardVisible = NO;
   204     [textField resignFirstResponder];
   205 }
   206 
   207 - (void)keyboardWillShow:(NSNotification *)notification
   208 {
   209     CGRect kbrect = [[notification userInfo][UIKeyboardFrameBeginUserInfoKey] CGRectValue];
   210     int height;
   211 
   212     /* The keyboard rect is in the coordinate space of the screen, but we want
   213      * its height in the view's coordinate space.
   214      */
   215 #ifdef __IPHONE_8_0
   216     if ([self respondsToSelector:@selector(convertRect:fromCoordinateSpace:)]) {
   217         UIScreen *screen = self.window.screen;
   218         kbrect = [self convertRect:kbrect fromCoordinateSpace:screen.coordinateSpace];
   219         height = kbrect.size.height;
   220     } else
   221 #endif
   222     {
   223         /* In iOS 7 and below, the screen's coordinate space is never rotated. */
   224         if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
   225             height = kbrect.size.width;
   226         } else {
   227             height = kbrect.size.height;
   228         }
   229     }
   230 
   231     [self setKeyboardHeight:height];
   232 }
   233 
   234  - (void)keyboardWillHide:(NSNotification *)notification
   235 {
   236     [self setKeyboardHeight:0];
   237 }
   238 
   239 - (void)updateKeyboard
   240 {
   241     SDL_Rect textrect = self.textInputRect;
   242     CGAffineTransform t = self.transform;
   243     CGPoint offset = CGPointMake(0.0, 0.0);
   244 
   245     if (self.keyboardHeight) {
   246         int rectbottom = textrect.y + textrect.h;
   247         int kbottom = self.bounds.size.height - self.keyboardHeight;
   248         if (kbottom < rectbottom) {
   249             offset.y = kbottom - rectbottom;
   250         }
   251     }
   252 
   253     /* Put the offset into the this view transform's coordinate space. */
   254     t.tx = 0.0;
   255     t.ty = 0.0;
   256     offset = CGPointApplyAffineTransform(offset, t);
   257 
   258     t.tx = offset.x;
   259     t.ty = offset.y;
   260 
   261     /* Move the view by applying the updated transform. */
   262     self.transform = t;
   263 }
   264 
   265 - (void)setKeyboardHeight:(int)height
   266 {
   267     keyboardVisible = height > 0;
   268     keyboardHeight = height;
   269     [self updateKeyboard];
   270 }
   271 
   272 /* UITextFieldDelegate method.  Invoked when user types something. */
   273 - (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
   274 {
   275     NSUInteger len = string.length;
   276 
   277     if (len == 0) {
   278         /* it wants to replace text with nothing, ie a delete */
   279         SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_BACKSPACE);
   280         SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_BACKSPACE);
   281     } else {
   282         /* go through all the characters in the string we've been sent
   283            and convert them to key presses */
   284         for (int i = 0; i < len; i++) {
   285             unichar c = [string characterAtIndex:i];
   286             Uint16 mod = 0;
   287             SDL_Scancode code;
   288 
   289             if (c < 127) {
   290                 /* figure out the SDL_Scancode and SDL_keymod for this unichar */
   291                 code = unicharToUIKeyInfoTable[c].code;
   292                 mod  = unicharToUIKeyInfoTable[c].mod;
   293             }
   294             else {
   295                 /* we only deal with ASCII right now */
   296                 code = SDL_SCANCODE_UNKNOWN;
   297                 mod = 0;
   298             }
   299 
   300             if (mod & KMOD_SHIFT) {
   301                 /* If character uses shift, press shift down */
   302                 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT);
   303             }
   304 
   305             /* send a keydown and keyup even for the character */
   306             SDL_SendKeyboardKey(SDL_PRESSED, code);
   307             SDL_SendKeyboardKey(SDL_RELEASED, code);
   308 
   309             if (mod & KMOD_SHIFT) {
   310                 /* If character uses shift, press shift back up */
   311                 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT);
   312             }
   313         }
   314 
   315         SDL_SendKeyboardText([string UTF8String]);
   316     }
   317 
   318     return NO; /* don't allow the edit! (keep placeholder text there) */
   319 }
   320 
   321 /* Terminates the editing session */
   322 - (BOOL)textFieldShouldReturn:(UITextField*)_textField
   323 {
   324     SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_RETURN);
   325     SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RETURN);
   326     SDL_StopTextInput();
   327     return YES;
   328 }
   329 
   330 #endif
   331 
   332 @end
   333 
   334 /* iPhone keyboard addition functions */
   335 #if SDL_IPHONE_KEYBOARD
   336 
   337 static SDL_uikitview *
   338 GetWindowView(SDL_Window * window)
   339 {
   340     if (window == NULL) {
   341         SDL_SetError("Window does not exist");
   342         return nil;
   343     }
   344 
   345     SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
   346     SDL_uikitview *view = data != nil ? data.view : nil;
   347 
   348     if (view == nil) {
   349         SDL_SetError("Window has no view");
   350     }
   351 
   352     return view;
   353 }
   354 
   355 SDL_bool
   356 UIKit_HasScreenKeyboardSupport(_THIS)
   357 {
   358     return SDL_TRUE;
   359 }
   360 
   361 void
   362 UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window)
   363 {
   364     @autoreleasepool {
   365         SDL_uikitview *view = GetWindowView(window);
   366         if (view != nil) {
   367             [view showKeyboard];
   368         }
   369     }
   370 }
   371 
   372 void
   373 UIKit_HideScreenKeyboard(_THIS, SDL_Window *window)
   374 {
   375     @autoreleasepool {
   376         SDL_uikitview *view = GetWindowView(window);
   377         if (view != nil) {
   378             [view hideKeyboard];
   379         }
   380     }
   381 }
   382 
   383 SDL_bool
   384 UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window)
   385 {
   386     @autoreleasepool {
   387         SDL_uikitview *view = GetWindowView(window);
   388         if (view != nil) {
   389             return view.isKeyboardVisible;
   390         }
   391         return 0;
   392     }
   393 }
   394 
   395 void
   396 UIKit_SetTextInputRect(_THIS, SDL_Rect *rect)
   397 {
   398     if (!rect) {
   399         SDL_InvalidParamError("rect");
   400         return;
   401     }
   402 
   403     @autoreleasepool {
   404         SDL_uikitview *view = GetWindowView(SDL_GetFocusWindow());
   405         if (view != nil) {
   406             view.textInputRect = *rect;
   407 
   408             if (view.keyboardVisible) {
   409                 [view updateKeyboard];
   410             }
   411         }
   412     }
   413 }
   414 
   415 
   416 #endif /* SDL_IPHONE_KEYBOARD */
   417 
   418 #endif /* SDL_VIDEO_DRIVER_UIKIT */
   419 
   420 /* vi: set ts=4 sw=4 expandtab: */