Keyboard.mm 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764
  1. #include "Keyboard.h"
  2. #include "DisplayManager.h"
  3. #include "UnityAppController.h"
  4. #include "UnityForwardDecls.h"
  5. #include <string>
  6. #ifndef FILTER_EMOJIS_IOS_KEYBOARD
  7. #define FILTER_EMOJIS_IOS_KEYBOARD 1
  8. #endif
  9. static KeyboardDelegate* _keyboard = nil;
  10. static bool _shouldHideInput = false;
  11. static bool _shouldHideInputChanged = false;
  12. static const unsigned kToolBarHeight = 40;
  13. static const unsigned kSystemButtonsSpace = 2 * 60 + 3 * 18; // empirical value, there is no way to know the exact widths of the system bar buttons
  14. @implementation KeyboardDelegate
  15. {
  16. // UI handling
  17. // in case of single line we use UITextField inside UIToolbar
  18. // in case of multi-line input we use UITextView with UIToolbar as accessory view
  19. // toolbar buttons are kept around to prevent releasing them
  20. // tvOS does not support multiline input thus only UITextField option is implemented
  21. #if PLATFORM_IOS
  22. UITextView* textView;
  23. UIToolbar* viewToolbar;
  24. NSArray* viewToolbarItems;
  25. NSLayoutConstraint* widthConstraint;
  26. #endif
  27. UITextField* textField;
  28. // keep toolbar items for both single- and multi- line edit in NSArray to make sure they are kept around
  29. #if PLATFORM_IOS
  30. UIToolbar* fieldToolbar;
  31. NSArray* fieldToolbarItems;
  32. #endif
  33. // inputView is view used for actual input (it will be responder): UITextField [single-line] or UITextView [multi-line]
  34. // editView is the "root" view for keyboard: UIToolbar [single-line] or UITextView [multi-line]
  35. UIView* inputView;
  36. UIView* editView;
  37. CGRect _area;
  38. NSString* initialText;
  39. UIKeyboardType keyboardType;
  40. BOOL _multiline;
  41. BOOL _inputHidden;
  42. BOOL _active;
  43. KeyboardStatus _status;
  44. // not pretty but seems like easiest way to keep "we are rotating" status
  45. BOOL _rotating;
  46. }
  47. @synthesize area;
  48. @synthesize active = _active;
  49. @synthesize status = _status;
  50. @synthesize text;
  51. @synthesize selection;
  52. - (BOOL)textFieldShouldReturn:(UITextField*)textFieldObj
  53. {
  54. [self textInputDone: nil];
  55. return YES;
  56. }
  57. - (void)textInputDone:(id)sender
  58. {
  59. if (_status == Visible)
  60. _status = Done;
  61. [self hide];
  62. }
  63. - (void)becomeFirstResponder
  64. {
  65. if (_status == Visible)
  66. {
  67. [_keyboard->inputView becomeFirstResponder];
  68. }
  69. }
  70. - (void)textInputCancel:(id)sender
  71. {
  72. _status = Canceled;
  73. [self hide];
  74. }
  75. - (void)textInputLostFocus
  76. {
  77. if (_status == Visible)
  78. _status = LostFocus;
  79. [self hide];
  80. }
  81. - (BOOL)textViewShouldBeginEditing:(UITextView*)view
  82. {
  83. #if !PLATFORM_TVOS
  84. view.inputAccessoryView = viewToolbar;
  85. #endif
  86. return YES;
  87. }
  88. #if PLATFORM_IOS
  89. - (void)keyboardDidShow:(NSNotification*)notification
  90. {
  91. if (notification.userInfo == nil || inputView == nil)
  92. return;
  93. CGRect srcRect = [[notification.userInfo objectForKey: UIKeyboardFrameEndUserInfoKey] CGRectValue];
  94. CGRect rect = [UnityGetGLView() convertRect: srcRect fromView: nil];
  95. rect.origin.y = [UnityGetGLView() frame].size.height - rect.size.height; // iPhone X sometimes reports wrong y value for keyboard
  96. [self positionInput: rect x: rect.origin.x y: rect.origin.y];
  97. _active = YES;
  98. }
  99. - (void)keyboardWillHide:(NSNotification*)notification
  100. {
  101. [self systemHideKeyboard];
  102. }
  103. - (void)keyboardDidChangeFrame:(NSNotification*)notification
  104. {
  105. _active = true;
  106. CGRect srcRect = [[notification.userInfo objectForKey: UIKeyboardFrameEndUserInfoKey] CGRectValue];
  107. CGRect rect = [UnityGetGLView() convertRect: srcRect fromView: nil];
  108. if (rect.origin.y >= [UnityGetGLView() bounds].size.height)
  109. [self systemHideKeyboard];
  110. else
  111. {
  112. rect.origin.y = [UnityGetGLView() frame].size.height - rect.size.height; // iPhone X sometimes reports wrong y value for keyboard
  113. [self positionInput: rect x: rect.origin.x y: rect.origin.y];
  114. }
  115. }
  116. #endif
  117. + (void)Initialize
  118. {
  119. NSAssert(_keyboard == nil, @"[KeyboardDelegate Initialize] called after creating keyboard");
  120. if (!_keyboard)
  121. _keyboard = [[KeyboardDelegate alloc] init];
  122. }
  123. + (KeyboardDelegate*)Instance
  124. {
  125. if (!_keyboard)
  126. _keyboard = [[KeyboardDelegate alloc] init];
  127. return _keyboard;
  128. }
  129. #if PLATFORM_IOS
  130. struct CreateToolbarResult
  131. {
  132. UIToolbar* toolbar;
  133. NSArray* items;
  134. };
  135. - (CreateToolbarResult)createToolbarWithView:(UIView*)view
  136. {
  137. UIToolbar* toolbar = [[UIToolbar alloc] initWithFrame: CGRectMake(0, 160, 320, kToolBarHeight)];
  138. UnitySetViewTouchProcessing(toolbar, touchesIgnored);
  139. toolbar.hidden = NO;
  140. UIBarButtonItem* inputItem = view ? [[UIBarButtonItem alloc] initWithCustomView: view] : nil;
  141. UIBarButtonItem* doneItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemDone target: self action: @selector(textInputDone:)];
  142. UIBarButtonItem* cancelItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemCancel target: self action: @selector(textInputCancel:)];
  143. NSArray* items = view ? @[inputItem, doneItem, cancelItem] : @[doneItem, cancelItem];
  144. toolbar.items = items;
  145. inputItem = nil;
  146. doneItem = nil;
  147. cancelItem = nil;
  148. CreateToolbarResult ret = {toolbar, items};
  149. return ret;
  150. }
  151. #endif
  152. - (id)init
  153. {
  154. NSAssert(_keyboard == nil, @"You can have only one instance of KeyboardDelegate");
  155. self = [super init];
  156. if (self)
  157. {
  158. #if PLATFORM_IOS
  159. textView = [[UITextView alloc] initWithFrame: CGRectMake(0, 480, 480, 30)];
  160. textView.delegate = self;
  161. textView.font = [UIFont systemFontOfSize: 18.0];
  162. textView.hidden = YES;
  163. #endif
  164. textField = [[UITextField alloc] initWithFrame: CGRectMake(0, 0, 120, 30)];
  165. textField.delegate = self;
  166. textField.borderStyle = UITextBorderStyleRoundedRect;
  167. textField.font = [UIFont systemFontOfSize: 20.0];
  168. textField.clearButtonMode = UITextFieldViewModeWhileEditing;
  169. #if PLATFORM_IOS
  170. widthConstraint = [NSLayoutConstraint constraintWithItem: textField attribute: NSLayoutAttributeWidth relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: textField.frame.size.width];
  171. [textField addConstraint: widthConstraint];
  172. #endif
  173. #define CREATE_TOOLBAR(t, i, v) \
  174. do { \
  175. CreateToolbarResult res = [self createToolbarWithView:v]; \
  176. t = res.toolbar; \
  177. i = res.items; \
  178. } while(0)
  179. #if PLATFORM_IOS
  180. CREATE_TOOLBAR(viewToolbar, viewToolbarItems, nil);
  181. CREATE_TOOLBAR(fieldToolbar, fieldToolbarItems, textField);
  182. #endif
  183. #undef CREATE_TOOLBAR
  184. #if PLATFORM_IOS
  185. [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(keyboardDidShow:) name: UIKeyboardDidShowNotification object: nil];
  186. [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(keyboardWillHide:) name: UIKeyboardWillHideNotification object: nil];
  187. [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(keyboardDidChangeFrame:) name: UIKeyboardDidChangeFrameNotification object: nil];
  188. #endif
  189. [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(textInputDone:) name: UITextFieldTextDidEndEditingNotification object: nil];
  190. }
  191. return self;
  192. }
  193. - (void)setTextInputTraits:(id<UITextInputTraits>)traits
  194. withParam:(KeyboardShowParam)param
  195. withCap:(UITextAutocapitalizationType)capitalization
  196. {
  197. traits.keyboardType = param.keyboardType;
  198. traits.autocorrectionType = param.autocorrectionType;
  199. traits.secureTextEntry = param.secure;
  200. traits.keyboardAppearance = param.appearance;
  201. traits.autocapitalizationType = capitalization;
  202. }
  203. - (void)setKeyboardParams:(KeyboardShowParam)param
  204. {
  205. if (_active)
  206. [self hide];
  207. initialText = param.text ? [[NSString alloc] initWithUTF8String: param.text] : @"";
  208. UITextAutocapitalizationType capitalization = UITextAutocapitalizationTypeSentences;
  209. if (param.keyboardType == UIKeyboardTypeURL || param.keyboardType == UIKeyboardTypeEmailAddress || param.keyboardType == UIKeyboardTypeWebSearch)
  210. capitalization = UITextAutocapitalizationTypeNone;
  211. #if PLATFORM_IOS
  212. _multiline = param.multiline;
  213. if (_multiline)
  214. {
  215. textView.text = initialText;
  216. [self setTextInputTraits: textView withParam: param withCap: capitalization];
  217. UITextPosition* end = [textView endOfDocument];
  218. UITextRange* endTextRange = [textView textRangeFromPosition: end toPosition: end];
  219. [textView setSelectedTextRange: endTextRange];
  220. }
  221. else
  222. {
  223. textField.text = initialText;
  224. [self setTextInputTraits: textField withParam: param withCap: capitalization];
  225. textField.placeholder = [NSString stringWithUTF8String: param.placeholder];
  226. UITextPosition* end = [textField endOfDocument];
  227. UITextRange* endTextRange = [textField textRangeFromPosition: end toPosition: end];
  228. [textField setSelectedTextRange: endTextRange];
  229. }
  230. inputView = _multiline ? textView : textField;
  231. editView = _multiline ? textView : fieldToolbar;
  232. #else // PLATFORM_TVOS
  233. textField.text = initialText;
  234. [self setTextInputTraits: textField withParam: param withCap: capitalization];
  235. textField.placeholder = [NSString stringWithUTF8String: param.placeholder];
  236. inputView = textField;
  237. editView = textField;
  238. UITextPosition* end = [textField endOfDocument];
  239. UITextRange* endTextRange = [textField textRangeFromPosition: end toPosition: end];
  240. [textField setSelectedTextRange: endTextRange];
  241. #endif
  242. [self shouldHideInput: _shouldHideInput];
  243. _status = Visible;
  244. _active = YES;
  245. }
  246. // we need to show/hide keyboard to react to orientation too, so extract we extract UI fiddling
  247. - (void)showUI
  248. {
  249. // if we unhide everything now the input will be shown smaller then needed quickly (and resized later)
  250. // so unhide only when keyboard is actually shown (we will update it when reacting to ios notifications)
  251. editView.hidden = YES;
  252. [UnityGetGLView() addSubview: editView];
  253. [inputView becomeFirstResponder];
  254. }
  255. - (void)hideUI
  256. {
  257. [inputView resignFirstResponder];
  258. [editView removeFromSuperview];
  259. editView.hidden = YES;
  260. }
  261. - (void)systemHideKeyboard
  262. {
  263. // when we are rotating os will bombard us with keyboardWillHide: and keyboardDidChangeFrame:
  264. // ignore all of them (we do it here only to simplify code: we call systemHideKeyboard only from these notification handlers)
  265. if (_rotating)
  266. return;
  267. _active = editView.isFirstResponder;
  268. editView.hidden = YES;
  269. _area = CGRectMake(0, 0, 0, 0);
  270. }
  271. - (void)show
  272. {
  273. [self showUI];
  274. }
  275. - (void)hide
  276. {
  277. [self hideUI];
  278. }
  279. - (void)updateInputHidden
  280. {
  281. if (_shouldHideInputChanged)
  282. {
  283. [self shouldHideInput: _shouldHideInput];
  284. _shouldHideInputChanged = false;
  285. }
  286. textField.returnKeyType = _inputHidden ? UIReturnKeyDone : UIReturnKeyDefault;
  287. editView.hidden = _inputHidden ? YES : NO;
  288. inputView.hidden = _inputHidden ? YES : NO;
  289. }
  290. #if PLATFORM_IOS
  291. - (void)positionInput:(CGRect)kbRect x:(float)x y:(float)y
  292. {
  293. float safeAreaInsetLeft = [UnityGetGLView() safeAreaInsets].left;
  294. float safeAreaInsetRight = [UnityGetGLView() safeAreaInsets].right;
  295. if (_multiline)
  296. {
  297. // use smaller area for iphones and bigger one for ipads
  298. int height = UnityDeviceDPI() > 300 ? 75 : 100;
  299. editView.frame = CGRectMake(safeAreaInsetLeft, y - height, kbRect.size.width - safeAreaInsetLeft - safeAreaInsetRight, height);
  300. }
  301. else
  302. {
  303. editView.frame = CGRectMake(0, y - kToolBarHeight, kbRect.size.width, kToolBarHeight);
  304. // old constraint must be removed, changing value while constraint is active causes conflict when changing inputView.frame
  305. [inputView removeConstraint: widthConstraint];
  306. inputView.frame = CGRectMake(inputView.frame.origin.x,
  307. inputView.frame.origin.y,
  308. kbRect.size.width - safeAreaInsetLeft - safeAreaInsetRight - kSystemButtonsSpace,
  309. inputView.frame.size.height);
  310. // required to avoid auto-resizing on iOS 11 in case if input text is too long
  311. widthConstraint.constant = inputView.frame.size.width;
  312. [inputView addConstraint: widthConstraint];
  313. }
  314. _area = CGRectMake(x, y, kbRect.size.width, kbRect.size.height);
  315. [self updateInputHidden];
  316. }
  317. #endif
  318. - (CGRect)queryArea
  319. {
  320. return editView.hidden ? _area : CGRectUnion(_area, editView.frame);
  321. }
  322. - (NSRange)querySelection
  323. {
  324. UIView<UITextInput>* textInput;
  325. #if PLATFORM_TVOS
  326. textInput = textField;
  327. #else
  328. textInput = _multiline ? textView : textField;
  329. #endif
  330. UITextPosition* beginning = textInput.beginningOfDocument;
  331. UITextRange* selectedRange = textInput.selectedTextRange;
  332. UITextPosition* selectionStart = selectedRange.start;
  333. UITextPosition* selectionEnd = selectedRange.end;
  334. const NSInteger location = [textInput offsetFromPosition: beginning toPosition: selectionStart];
  335. const NSInteger length = [textInput offsetFromPosition: selectionStart toPosition: selectionEnd];
  336. return NSMakeRange(location, length);
  337. }
  338. + (void)StartReorientation
  339. {
  340. if (_keyboard && _keyboard.active)
  341. _keyboard->_rotating = YES;
  342. }
  343. + (void)FinishReorientation
  344. {
  345. if (_keyboard)
  346. _keyboard->_rotating = NO;
  347. }
  348. - (NSString*)getText
  349. {
  350. if (_status == Canceled)
  351. return initialText;
  352. else
  353. {
  354. #if PLATFORM_TVOS
  355. return [textField text];
  356. #else
  357. return _multiline ? [textView text] : [textField text];
  358. #endif
  359. }
  360. }
  361. - (void)setTextWorkaround:(id<UITextInput>)textInput text:(NSString*)newText
  362. {
  363. UITextPosition* begin = [textInput beginningOfDocument];
  364. UITextPosition* end = [textInput endOfDocument];
  365. UITextRange* allText = [textInput textRangeFromPosition: begin toPosition: end];
  366. [textInput setSelectedTextRange: allText];
  367. [textInput insertText: newText];
  368. }
  369. - (void)setText:(NSString*)newText
  370. {
  371. #if PLATFORM_IOS
  372. // We can't use setText on iOS7 because it does not update the undo stack.
  373. // We still prefer setText on other iOSes, because an undo operation results
  374. // in a smaller selection shown on the UI
  375. if (_ios70orNewer && !_ios80orNewer)
  376. [self setTextWorkaround: (_multiline ? textView : textField) text: newText];
  377. if (_multiline)
  378. textView.text = newText;
  379. else
  380. textField.text = newText;
  381. #else
  382. textField.text = newText;
  383. #endif
  384. }
  385. - (void)shouldHideInput:(BOOL)hide
  386. {
  387. if (hide)
  388. {
  389. switch (keyboardType)
  390. {
  391. case UIKeyboardTypeDefault: hide = YES; break;
  392. case UIKeyboardTypeASCIICapable: hide = YES; break;
  393. case UIKeyboardTypeNumbersAndPunctuation: hide = YES; break;
  394. case UIKeyboardTypeURL: hide = YES; break;
  395. case UIKeyboardTypeNumberPad: hide = NO; break;
  396. case UIKeyboardTypePhonePad: hide = NO; break;
  397. case UIKeyboardTypeNamePhonePad: hide = NO; break;
  398. case UIKeyboardTypeEmailAddress: hide = YES; break;
  399. case UIKeyboardTypeTwitter: hide = YES; break;
  400. case UIKeyboardTypeWebSearch: hide = YES; break;
  401. default: hide = NO; break;
  402. }
  403. }
  404. _inputHidden = hide;
  405. }
  406. #if FILTER_EMOJIS_IOS_KEYBOARD
  407. static bool StringContainsEmoji(NSString *string);
  408. - (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string_
  409. {
  410. return !StringContainsEmoji(string_);
  411. }
  412. - (BOOL)textView:(UITextView*)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text_
  413. {
  414. return !StringContainsEmoji(text_);
  415. }
  416. #endif // FILTER_EMOJIS_IOS_KEYBOARD
  417. @end
  418. //==============================================================================
  419. //
  420. // Unity Interface:
  421. extern "C" void UnityKeyboard_Create(unsigned keyboardType, int autocorrection, int multiline, int secure, int alert, const char* text, const char* placeholder)
  422. {
  423. #if PLATFORM_TVOS
  424. // Not supported. The API for showing keyboard for editing multi-line text
  425. // is not available on tvOS
  426. multiline = false;
  427. #endif
  428. static const UIKeyboardType keyboardTypes[] =
  429. {
  430. UIKeyboardTypeDefault,
  431. UIKeyboardTypeASCIICapable,
  432. UIKeyboardTypeNumbersAndPunctuation,
  433. UIKeyboardTypeURL,
  434. UIKeyboardTypeNumberPad,
  435. UIKeyboardTypePhonePad,
  436. UIKeyboardTypeNamePhonePad,
  437. UIKeyboardTypeEmailAddress,
  438. UIKeyboardTypeDefault, // Default is used in case Wii U specific NintendoNetworkAccount type is selected (indexed at 8 in UnityEngine.TouchScreenKeyboardType)
  439. UIKeyboardTypeTwitter,
  440. UIKeyboardTypeWebSearch
  441. };
  442. static const UITextAutocorrectionType autocorrectionTypes[] =
  443. {
  444. UITextAutocorrectionTypeNo,
  445. UITextAutocorrectionTypeDefault,
  446. };
  447. static const UIKeyboardAppearance keyboardAppearances[] =
  448. {
  449. UIKeyboardAppearanceDefault,
  450. UIKeyboardAppearanceAlert,
  451. };
  452. KeyboardShowParam param =
  453. {
  454. text, placeholder,
  455. keyboardTypes[keyboardType],
  456. autocorrectionTypes[autocorrection],
  457. keyboardAppearances[alert],
  458. (BOOL)multiline, (BOOL)secure
  459. };
  460. [[KeyboardDelegate Instance] setKeyboardParams: param];
  461. }
  462. extern "C" void UnityKeyboard_Show()
  463. {
  464. // do not send hide if didnt create keyboard
  465. // TODO: probably assert?
  466. if (!_keyboard)
  467. return;
  468. [[KeyboardDelegate Instance] show];
  469. }
  470. extern "C" void UnityKeyboard_Hide()
  471. {
  472. // do not send hide if didnt create keyboard
  473. // TODO: probably assert?
  474. if (!_keyboard)
  475. return;
  476. [[KeyboardDelegate Instance] textInputLostFocus];
  477. }
  478. extern "C" void UnityKeyboard_SetText(const char* text)
  479. {
  480. [KeyboardDelegate Instance].text = [NSString stringWithUTF8String: text];
  481. }
  482. extern "C" NSString* UnityKeyboard_GetText()
  483. {
  484. return [KeyboardDelegate Instance].text;
  485. }
  486. extern "C" int UnityKeyboard_IsActive()
  487. {
  488. return (_keyboard && _keyboard.active) ? 1 : 0;
  489. }
  490. extern "C" int UnityKeyboard_IsDone()
  491. {
  492. // Preserving old behaviour where done was always set to true when the keyboard was not visible.
  493. return (_keyboard && _keyboard.status != Visible) ? 1 : 0;
  494. }
  495. extern "C" int UnityKeyboard_WasCanceled()
  496. {
  497. return (_keyboard && _keyboard.status == Canceled) ? 1 : 0;
  498. }
  499. extern "C" int UnityKeyboard_Status()
  500. {
  501. return _keyboard ? _keyboard.status : Canceled;
  502. }
  503. extern "C" void UnityKeyboard_SetInputHidden(int hidden)
  504. {
  505. _shouldHideInput = hidden;
  506. _shouldHideInputChanged = true;
  507. // update hidden status only if keyboard is on screen to avoid showing input view out of nowhere
  508. if (_keyboard && _keyboard.active)
  509. [_keyboard updateInputHidden];
  510. }
  511. extern "C" int UnityKeyboard_IsInputHidden()
  512. {
  513. return _shouldHideInput ? 1 : 0;
  514. }
  515. extern "C" void UnityKeyboard_GetRect(float* x, float* y, float* w, float* h)
  516. {
  517. CGRect area = _keyboard ? _keyboard.area : CGRectMake(0, 0, 0, 0);
  518. // convert to unity coord system
  519. float multX = (float)GetMainDisplaySurface()->targetW / UnityGetGLView().bounds.size.width;
  520. float multY = (float)GetMainDisplaySurface()->targetH / UnityGetGLView().bounds.size.height;
  521. *x = 0;
  522. *y = area.origin.y * multY;
  523. *w = area.size.width * multX;
  524. *h = area.size.height * multY;
  525. }
  526. extern "C" int UnityKeyboard_CanGetSelection()
  527. {
  528. return (_keyboard) ? 1 : 0;
  529. }
  530. extern "C" void UnityKeyboard_GetSelection(int* location, int* length)
  531. {
  532. if (_keyboard)
  533. {
  534. NSRange selection = _keyboard.selection;
  535. *location = (int)selection.location;
  536. *length = (int)selection.length;
  537. }
  538. else
  539. {
  540. *location = 0;
  541. *length = 0;
  542. }
  543. }
  544. //==============================================================================
  545. //
  546. // Emoji Filtering: unicode magic
  547. #if FILTER_EMOJIS_IOS_KEYBOARD
  548. static bool StringContainsEmoji(NSString *string)
  549. {
  550. __block BOOL returnValue = NO;
  551. [string enumerateSubstringsInRange: NSMakeRange(0, string.length)
  552. options: NSStringEnumerationByComposedCharacterSequences
  553. usingBlock:^(NSString* substring, NSRange substringRange, NSRange enclosingRange, BOOL* stop)
  554. {
  555. const unichar hs = [substring characterAtIndex: 0];
  556. const unichar ls = substring.length > 1 ? [substring characterAtIndex: 1] : 0;
  557. #define IS_IN(val, min, max) (((val) >= (min)) && ((val) <= (max)))
  558. if (IS_IN(hs, 0xD800, 0xDBFF))
  559. {
  560. if (substring.length > 1)
  561. {
  562. const int uc = ((hs - 0xD800) * 0x400) + (ls - 0xDC00) + 0x10000;
  563. // Musical: [U+1D000, U+1D24F]
  564. // Enclosed Alphanumeric Supplement: [U+1F100, U+1F1FF]
  565. // Enclosed Ideographic Supplement: [U+1F200, U+1F2FF]
  566. // Miscellaneous Symbols and Pictographs: [U+1F300, U+1F5FF]
  567. // Supplemental Symbols and Pictographs: [U+1F900, U+1F9FF]
  568. // Emoticons: [U+1F600, U+1F64F]
  569. // Transport and Map Symbols: [U+1F680, U+1F6FF]
  570. if (IS_IN(uc, 0x1D000, 0x1F9FF))
  571. returnValue = YES;
  572. }
  573. }
  574. else if (substring.length > 1 && ls == 0x20E3)
  575. {
  576. // emojis for numbers: number + modifier ls = U+20E3
  577. returnValue = YES;
  578. }
  579. else
  580. {
  581. if ( // Latin-1 Supplement
  582. hs == 0x00A9 || hs == 0x00AE
  583. // General Punctuation
  584. || hs == 0x203C || hs == 0x2049
  585. // Letterlike Symbols
  586. || hs == 0x2122 || hs == 0x2139
  587. // Arrows
  588. || IS_IN(hs, 0x2194, 0x2199) || IS_IN(hs, 0x21A9, 0x21AA)
  589. // Miscellaneous Technical
  590. || IS_IN(hs, 0x231A, 0x231B) || IS_IN(hs, 0x23E9, 0x23F3) || IS_IN(hs, 0x23F8, 0x23FA) || hs == 0x2328 || hs == 0x23CF
  591. // Geometric Shapes
  592. || IS_IN(hs, 0x25AA, 0x25AB) || IS_IN(hs, 0x25FB, 0x25FE) || hs == 0x25B6 || hs == 0x25C0
  593. // Miscellaneous Symbols
  594. || IS_IN(hs, 0x2600, 0x2604) || IS_IN(hs, 0x2614, 0x2615) || IS_IN(hs, 0x2622, 0x2623) || IS_IN(hs, 0x262E, 0x262F)
  595. || IS_IN(hs, 0x2638, 0x263A) || IS_IN(hs, 0x2648, 0x2653) || IS_IN(hs, 0x2665, 0x2666) || IS_IN(hs, 0x2692, 0x2694)
  596. || IS_IN(hs, 0x2696, 0x2697) || IS_IN(hs, 0x269B, 0x269C) || IS_IN(hs, 0x26A0, 0x26A1) || IS_IN(hs, 0x26AA, 0x26AB)
  597. || IS_IN(hs, 0x26B0, 0x26B1) || IS_IN(hs, 0x26BD, 0x26BE) || IS_IN(hs, 0x26C4, 0x26C5) || IS_IN(hs, 0x26CE, 0x26CF)
  598. || IS_IN(hs, 0x26D3, 0x26D4) || IS_IN(hs, 0x26D3, 0x26D4) || IS_IN(hs, 0x26E9, 0x26EA) || IS_IN(hs, 0x26F0, 0x26F5)
  599. || IS_IN(hs, 0x26F7, 0x26FA)
  600. || hs == 0x260E || hs == 0x2611 || hs == 0x2618 || hs == 0x261D || hs == 0x2620 || hs == 0x2626 || hs == 0x262A
  601. || hs == 0x2660 || hs == 0x2663 || hs == 0x2668 || hs == 0x267B || hs == 0x267F || hs == 0x2699 || hs == 0x26C8
  602. || hs == 0x26D1 || hs == 0x26FD
  603. // Dingbats
  604. || IS_IN(hs, 0x2708, 0x270D) || IS_IN(hs, 0x2733, 0x2734) || IS_IN(hs, 0x2753, 0x2755)
  605. || IS_IN(hs, 0x2763, 0x2764) || IS_IN(hs, 0x2795, 0x2797)
  606. || hs == 0x2702 || hs == 0x2705 || hs == 0x270F || hs == 0x2712 || hs == 0x2714 || hs == 0x2716 || hs == 0x271D
  607. || hs == 0x2721 || hs == 0x2728 || hs == 0x2744 || hs == 0x2747 || hs == 0x274C || hs == 0x274E || hs == 0x2757
  608. || hs == 0x27A1 || hs == 0x27B0 || hs == 0x27BF
  609. // CJK Symbols and Punctuation
  610. || hs == 0x3030 || hs == 0x303D
  611. // Enclosed CJK Letters and Months
  612. || hs == 0x3297 || hs == 0x3299
  613. // Supplemental Arrows-B
  614. || IS_IN(hs, 0x2934, 0x2935)
  615. // Miscellaneous Symbols and Arrows
  616. || IS_IN(hs, 0x2B05, 0x2B07) || IS_IN(hs, 0x2B1B, 0x2B1C) || hs == 0x2B50 || hs == 0x2B55
  617. )
  618. {
  619. returnValue = YES;
  620. }
  621. }
  622. #undef IS_IN
  623. }];
  624. return returnValue;
  625. }
  626. #endif // FILTER_EMOJIS_IOS_KEYBOARD