| 
									
										
										
										
											2023-01-05 13:25:55 +01:00
										 |  |  | /**************************************************************************/ | 
					
						
							|  |  |  | /*  keyboard_input_view.mm                                                */ | 
					
						
							|  |  |  | /**************************************************************************/ | 
					
						
							|  |  |  | /*                         This file is part of:                          */ | 
					
						
							|  |  |  | /*                             GODOT ENGINE                               */ | 
					
						
							|  |  |  | /*                        https://godotengine.org                         */ | 
					
						
							|  |  |  | /**************************************************************************/ | 
					
						
							|  |  |  | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ | 
					
						
							|  |  |  | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */ | 
					
						
							|  |  |  | /*                                                                        */ | 
					
						
							|  |  |  | /* Permission is hereby granted, free of charge, to any person obtaining  */ | 
					
						
							|  |  |  | /* a copy of this software and associated documentation files (the        */ | 
					
						
							|  |  |  | /* "Software"), to deal in the Software without restriction, including    */ | 
					
						
							|  |  |  | /* without limitation the rights to use, copy, modify, merge, publish,    */ | 
					
						
							|  |  |  | /* distribute, sublicense, and/or sell copies of the Software, and to     */ | 
					
						
							|  |  |  | /* permit persons to whom the Software is furnished to do so, subject to  */ | 
					
						
							|  |  |  | /* the following conditions:                                              */ | 
					
						
							|  |  |  | /*                                                                        */ | 
					
						
							|  |  |  | /* The above copyright notice and this permission notice shall be         */ | 
					
						
							|  |  |  | /* included in all copies or substantial portions of the Software.        */ | 
					
						
							|  |  |  | /*                                                                        */ | 
					
						
							|  |  |  | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */ | 
					
						
							|  |  |  | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */ | 
					
						
							|  |  |  | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ | 
					
						
							|  |  |  | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */ | 
					
						
							|  |  |  | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */ | 
					
						
							|  |  |  | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */ | 
					
						
							|  |  |  | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */ | 
					
						
							|  |  |  | /**************************************************************************/ | 
					
						
							| 
									
										
										
										
											2020-11-15 15:11:25 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | #import "keyboard_input_view.h" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-08 14:51:32 +02:00
										 |  |  | #import "display_server_ios.h" | 
					
						
							|  |  |  | #import "os_ios.h" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-15 15:11:25 +03:00
										 |  |  | #include "core/os/keyboard.h" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @interface GodotKeyboardInputView () <UITextViewDelegate> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @property(nonatomic, copy) NSString *previousText; | 
					
						
							|  |  |  | @property(nonatomic, assign) NSRange previousSelectedRange; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @implementation GodotKeyboardInputView | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (instancetype)initWithCoder:(NSCoder *)coder { | 
					
						
							|  |  |  | 	self = [super initWithCoder:coder]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (self) { | 
					
						
							|  |  |  | 		[self godot_commonInit]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return self; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer { | 
					
						
							|  |  |  | 	self = [super initWithFrame:frame textContainer:textContainer]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (self) { | 
					
						
							|  |  |  | 		[self godot_commonInit]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return self; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (void)godot_commonInit { | 
					
						
							|  |  |  | 	self.hidden = YES; | 
					
						
							|  |  |  | 	self.delegate = self; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	[[NSNotificationCenter defaultCenter] addObserver:self | 
					
						
							|  |  |  | 											 selector:@selector(observeTextChange:) | 
					
						
							|  |  |  | 												 name:UITextViewTextDidChangeNotification | 
					
						
							|  |  |  | 											   object:self]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (void)dealloc { | 
					
						
							|  |  |  | 	self.delegate = nil; | 
					
						
							|  |  |  | 	[[NSNotificationCenter defaultCenter] removeObserver:self]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MARK: Keyboard | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (BOOL)canBecomeFirstResponder { | 
					
						
							|  |  |  | 	return YES; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-07 14:20:10 -04:00
										 |  |  | - (BOOL)becomeFirstResponderWithString:(NSString *)existingString cursorStart:(NSInteger)start cursorEnd:(NSInteger)end { | 
					
						
							| 
									
										
										
										
											2020-11-15 15:11:25 +03:00
										 |  |  | 	self.text = existingString; | 
					
						
							|  |  |  | 	self.previousText = existingString; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-30 11:40:12 +03:00
										 |  |  | 	NSInteger safeStartIndex = MAX(start, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-15 15:11:25 +03:00
										 |  |  | 	NSRange textRange; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Either a simple cursor or a selection. | 
					
						
							|  |  |  | 	if (end > 0) { | 
					
						
							| 
									
										
										
										
											2021-04-30 11:40:12 +03:00
										 |  |  | 		textRange = NSMakeRange(safeStartIndex, end - start); | 
					
						
							| 
									
										
										
										
											2020-11-15 15:11:25 +03:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2021-04-30 11:40:12 +03:00
										 |  |  | 		textRange = NSMakeRange(safeStartIndex, 0); | 
					
						
							| 
									
										
										
										
											2020-11-15 15:11:25 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	self.selectedRange = textRange; | 
					
						
							|  |  |  | 	self.previousSelectedRange = textRange; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return [self becomeFirstResponder]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (BOOL)resignFirstResponder { | 
					
						
							|  |  |  | 	self.text = nil; | 
					
						
							|  |  |  | 	self.previousText = nil; | 
					
						
							|  |  |  | 	return [super resignFirstResponder]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MARK: OS Messages | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (void)deleteText:(NSInteger)charactersToDelete { | 
					
						
							|  |  |  | 	for (int i = 0; i < charactersToDelete; i++) { | 
					
						
							| 
									
										
										
										
											2022-12-11 01:21:22 +02:00
										 |  |  | 		DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true); | 
					
						
							|  |  |  | 		DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false); | 
					
						
							| 
									
										
										
										
											2020-11-15 15:11:25 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (void)enterText:(NSString *)substring { | 
					
						
							|  |  |  | 	String characters; | 
					
						
							|  |  |  | 	characters.parse_utf8([substring UTF8String]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (int i = 0; i < characters.size(); i++) { | 
					
						
							|  |  |  | 		int character = characters[i]; | 
					
						
							| 
									
										
										
										
											2022-12-12 18:05:01 +02:00
										 |  |  | 		Key key = Key::NONE; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (character == '\t') { // 0x09 | 
					
						
							|  |  |  | 			key = Key::TAB; | 
					
						
							|  |  |  | 		} else if (character == '\n') { // 0x0A | 
					
						
							|  |  |  | 			key = Key::ENTER; | 
					
						
							|  |  |  | 		} else if (character == 0x2006) { | 
					
						
							|  |  |  | 			key = Key::SPACE; | 
					
						
							| 
									
										
										
										
											2020-11-15 15:11:25 +03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-11 01:21:22 +02:00
										 |  |  | 		DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true); | 
					
						
							|  |  |  | 		DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false); | 
					
						
							| 
									
										
										
										
											2020-11-15 15:11:25 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MARK: Observer | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (void)observeTextChange:(NSNotification *)notification { | 
					
						
							|  |  |  | 	if (notification.object != self) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (self.previousSelectedRange.length == 0) { | 
					
						
							|  |  |  | 		// We are deleting all text before cursor if no range was selected. | 
					
						
							|  |  |  | 		// This way any inserted or changed text will be updated. | 
					
						
							|  |  |  | 		NSString *substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location]; | 
					
						
							|  |  |  | 		[self deleteText:substringToDelete.length]; | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// If text was previously selected | 
					
						
							|  |  |  | 		// we are sending only one `backspace`. | 
					
						
							|  |  |  | 		// It will remove all text from text input. | 
					
						
							|  |  |  | 		[self deleteText:1]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	NSString *substringToEnter; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (self.selectedRange.length == 0) { | 
					
						
							|  |  |  | 		// If previous cursor had a selection | 
					
						
							|  |  |  | 		// we have to calculate an inserted text. | 
					
						
							|  |  |  | 		if (self.previousSelectedRange.length != 0) { | 
					
						
							|  |  |  | 			NSInteger rangeEnd = self.selectedRange.location + self.selectedRange.length; | 
					
						
							|  |  |  | 			NSInteger rangeStart = MIN(self.previousSelectedRange.location, self.selectedRange.location); | 
					
						
							|  |  |  | 			NSInteger rangeLength = MAX(0, rangeEnd - rangeStart); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			NSRange calculatedRange; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (rangeLength >= 0) { | 
					
						
							|  |  |  | 				calculatedRange = NSMakeRange(rangeStart, rangeLength); | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				calculatedRange = NSMakeRange(rangeStart, 0); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			substringToEnter = [self.text substringWithRange:calculatedRange]; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			substringToEnter = [self.text substringToIndex:self.selectedRange.location]; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		substringToEnter = [self.text substringWithRange:self.selectedRange]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	[self enterText:substringToEnter]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	self.previousText = self.text; | 
					
						
							|  |  |  | 	self.previousSelectedRange = self.selectedRange; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @end |