EditBox-mac.mm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. /****************************************************************************
  2. Copyright (c) 2018 Xiamen Yaji Software Co., Ltd.
  3. http://www.cocos2d-x.org
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. THE SOFTWARE.
  19. ****************************************************************************/
  20. #include "EditBox.h"
  21. #include "platform/desktop/CCGLView-desktop.h"
  22. #include "platform/CCApplication.h"
  23. #include "cocos/scripting/js-bindings/jswrapper/SeApi.h"
  24. #include "cocos/scripting/js-bindings/manual/jsb_global.h"
  25. /*************************************************************************
  26. Forward declaration of global functions.
  27. ************************************************************************/
  28. namespace
  29. {
  30. void callJSFunc(const std::string& type, const std::string& text);
  31. }
  32. /*************************************************************************
  33. Global variables.
  34. ************************************************************************/
  35. namespace
  36. {
  37. NSTextView* g_textView = nil;
  38. NSScrollView* g_scrollView = nil;
  39. NSTextField* g_textField = nil;
  40. NSSecureTextField* g_secureTextField = nil;
  41. bool g_isMultiline = false;
  42. bool g_isPassword = false;
  43. int g_maxLength = INT_MAX;
  44. se::Value g_textInputCallback;
  45. }
  46. /*************************************************************************
  47. TextViewDelegate
  48. ************************************************************************/
  49. @interface TextViewDelegate : NSObject<NSTextViewDelegate>
  50. @end
  51. @implementation TextViewDelegate
  52. // Get notification when something is input.
  53. - (void) textDidChange:(NSNotification *)notification
  54. {
  55. callJSFunc("input", [[g_textView.textStorage string] UTF8String]);
  56. }
  57. // Max length limitaion
  58. - (BOOL) textView:(NSTextView *)textView
  59. shouldChangeTextInRange:(NSRange)affectedCharRange
  60. replacementString:(NSString *)replacementString
  61. {
  62. NSUInteger newLength = [textView.string length] + [replacementString length] - affectedCharRange.length;
  63. if (newLength > g_maxLength)
  64. return FALSE;
  65. if (!g_isMultiline && [replacementString containsString:@"\n"])
  66. return FALSE;
  67. return TRUE;
  68. }
  69. @end
  70. /*************************************************************************
  71. TextFieldDelegate
  72. ************************************************************************/
  73. @interface TextFieldDelegate: NSObject<NSTextFieldDelegate>
  74. @end
  75. @implementation TextFieldDelegate
  76. - (void)controlTextDidChange:(NSNotification *)notification
  77. {
  78. NSTextField *textField = [notification object];
  79. callJSFunc("input", [textField.stringValue UTF8String]);
  80. }
  81. @end
  82. /*************************************************************************
  83. TextFieldFormatter: used for textfield length limitation.
  84. ************************************************************************/
  85. @interface TextFieldFormatter : NSFormatter
  86. {
  87. int maxLength;
  88. }
  89. - (void)setMaximumLength:(int)len;
  90. @end
  91. @implementation TextFieldFormatter
  92. - (id)init
  93. {
  94. if(self = [super init])
  95. maxLength = INT_MAX;
  96. return self;
  97. }
  98. - (void)setMaximumLength:(int)len
  99. {
  100. maxLength = len;
  101. }
  102. - (NSString *)stringForObjectValue:(id)object
  103. {
  104. return (NSString *)object;
  105. }
  106. - (BOOL)getObjectValue:(id *)object forString:(NSString *)string errorDescription:(NSString **)error
  107. {
  108. *object = string;
  109. return YES;
  110. }
  111. - (BOOL)isPartialStringValid:(NSString **)partialStringPtr
  112. proposedSelectedRange:(NSRangePointer)proposedSelRangePtr
  113. originalString:(NSString *)origString
  114. originalSelectedRange:(NSRange)origSelRange
  115. errorDescription:(NSString **)error
  116. {
  117. if ([*partialStringPtr length] > maxLength)
  118. return NO;
  119. return YES;
  120. }
  121. - (NSAttributedString *)attributedStringForObjectValue:(id)anObject withDefaultAttributes:(NSDictionary *)attributes
  122. {
  123. return nil;
  124. }
  125. @end
  126. /*************************************************************************
  127. Implementation of global helper functions.
  128. ************************************************************************/
  129. namespace
  130. {
  131. void getTextInputCallback()
  132. {
  133. if (! g_textInputCallback.isUndefined())
  134. return;
  135. auto global = se::ScriptEngine::getInstance()->getGlobalObject();
  136. se::Value jsbVal;
  137. if (global->getProperty("jsb", &jsbVal) && jsbVal.isObject())
  138. {
  139. jsbVal.toObject()->getProperty("onTextInput", &g_textInputCallback);
  140. // free globle se::Value before ScriptEngine clean up
  141. se::ScriptEngine::getInstance()->addBeforeCleanupHook([](){
  142. g_textInputCallback.setUndefined();
  143. });
  144. }
  145. }
  146. void callJSFunc(const std::string& type, const std::string& text)
  147. {
  148. getTextInputCallback();
  149. se::AutoHandleScope scope;
  150. se::ValueArray args;
  151. args.push_back(se::Value(type));
  152. args.push_back(se::Value(text));
  153. g_textInputCallback.toObject()->call(args, nullptr);
  154. }
  155. void initTextView(const cocos2d::EditBox::ShowInfo& showInfo)
  156. {
  157. CGRect rect = CGRectMake(showInfo.x, showInfo.y, showInfo.width, showInfo.height);
  158. if (! g_textView)
  159. {
  160. g_textView = [[NSTextView alloc] initWithFrame:rect];
  161. g_textView.textColor = [NSColor blackColor];
  162. g_textView.backgroundColor = [NSColor whiteColor];
  163. g_textView.editable = TRUE;
  164. g_textView.hidden = FALSE;
  165. g_textView.delegate = [[TextViewDelegate alloc] init];
  166. g_scrollView = [[NSScrollView alloc] initWithFrame:rect];
  167. [g_scrollView setBorderType:NSNoBorder];
  168. [g_scrollView setHasVerticalScroller:TRUE];
  169. [g_scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
  170. [g_scrollView setDocumentView:g_textView];
  171. }
  172. g_textView.string = [NSString stringWithUTF8String:showInfo.defaultValue.c_str()];
  173. g_textView.frame = rect;
  174. g_scrollView.frame = rect;
  175. auto glfwWindow = ((cocos2d::GLView*)cocos2d::Application::getInstance()->getView())->getGLFWWindow();
  176. NSWindow* nsWindow = glfwGetCocoaWindow(glfwWindow);
  177. [nsWindow.contentView addSubview:g_scrollView];
  178. [nsWindow makeFirstResponder:g_scrollView];
  179. }
  180. void doInitTextField(NSTextField* textField, const CGRect& rect, const cocos2d::EditBox::ShowInfo& showInfo)
  181. {
  182. textField.editable = TRUE;
  183. textField.wantsLayer = TRUE;
  184. textField.frame = rect;
  185. textField.stringValue = [NSString stringWithUTF8String:showInfo.defaultValue.c_str()];
  186. [(TextFieldFormatter*)textField.formatter setMaximumLength: showInfo.maxLength];
  187. auto glfwWindow = ((cocos2d::GLView*)cocos2d::Application::getInstance()->getView())->getGLFWWindow();
  188. NSWindow* nsWindow = glfwGetCocoaWindow(glfwWindow);
  189. [nsWindow.contentView addSubview:textField];
  190. [textField becomeFirstResponder];
  191. }
  192. void initTextField(const cocos2d::EditBox::ShowInfo& showInfo)
  193. {
  194. CGRect rect = CGRectMake(showInfo.x, showInfo.y, showInfo.width, showInfo.height);
  195. // Use NSSecureTextField for password, use NSTextField for others.
  196. if (g_isPassword)
  197. {
  198. if (! g_secureTextField)
  199. {
  200. g_secureTextField = [[NSSecureTextField alloc] init];
  201. g_secureTextField.textColor = [NSColor blackColor];
  202. g_secureTextField.backgroundColor = [NSColor whiteColor];
  203. g_secureTextField.delegate = [[TextFieldDelegate alloc] init];
  204. g_secureTextField.formatter = [[TextFieldFormatter alloc] init];
  205. }
  206. doInitTextField(g_secureTextField, rect, showInfo);
  207. }
  208. else
  209. {
  210. if (! g_textField)
  211. {
  212. g_textField = [[NSTextField alloc] init];
  213. g_textField.textColor = [NSColor blackColor];
  214. g_textField.backgroundColor = [NSColor whiteColor];
  215. g_textField.delegate = [[TextFieldDelegate alloc] init];
  216. g_textField.formatter = [[TextFieldFormatter alloc] init];
  217. }
  218. doInitTextField(g_textField, rect, showInfo);
  219. }
  220. }
  221. void init(const cocos2d::EditBox::ShowInfo& showInfo)
  222. {
  223. if (showInfo.isMultiline)
  224. initTextView(showInfo);
  225. else
  226. initTextField(showInfo);
  227. }
  228. }
  229. /*************************************************************************
  230. Implementation of EditBox.
  231. ************************************************************************/
  232. NS_CC_BEGIN
  233. void EditBox::show(const ShowInfo& showInfo)
  234. {
  235. g_isMultiline = showInfo.isMultiline;
  236. g_maxLength = showInfo.maxLength;
  237. g_isPassword = showInfo.inputType == "password";
  238. init(showInfo);
  239. ((GLView*)Application::getInstance()->getView())->setIsEditboxEditing(true);
  240. }
  241. void EditBox::hide()
  242. {
  243. if (g_scrollView)
  244. [g_scrollView removeFromSuperview];
  245. if (g_textField)
  246. {
  247. [g_textField resignFirstResponder];
  248. [g_textField removeFromSuperview];
  249. }
  250. if (g_secureTextField)
  251. {
  252. [g_secureTextField resignFirstResponder];
  253. [g_secureTextField removeFromSuperview];
  254. }
  255. ((GLView*)Application::getInstance()->getView())->setIsEditboxEditing(false);
  256. }
  257. void EditBox::complete()
  258. {
  259. if (g_isMultiline)
  260. callJSFunc("complete", [[g_textView.textStorage string] UTF8String]);
  261. else
  262. {
  263. if (g_isPassword)
  264. callJSFunc("complete", [g_secureTextField.stringValue UTF8String]);
  265. else
  266. callJSFunc("complete", [g_textField.stringValue UTF8String]);
  267. }
  268. EditBox::hide();
  269. }
  270. void EditBox::updateRect(int x, int y, int width, int height) {
  271. CGRect rect = CGRectMake(x, y, width, height);
  272. auto glfwWindow = ((cocos2d::GLView*)cocos2d::Application::getInstance()->getView())->getGLFWWindow();
  273. NSWindow* nsWindow = glfwGetCocoaWindow(glfwWindow);
  274. const auto& subviews = [nsWindow.contentView subviews];
  275. if (g_scrollView && [subviews containsObject:g_scrollView])
  276. {
  277. g_scrollView.frame = rect;
  278. if (g_textView)
  279. {
  280. g_textView.frame = rect;
  281. }
  282. }
  283. else if (g_textField && [subviews containsObject:g_textField])
  284. {
  285. g_textField.frame = rect;
  286. }
  287. else if (g_secureTextField && [subviews containsObject:g_secureTextField])
  288. {
  289. g_secureTextField.frame = rect;
  290. }
  291. }
  292. NS_CC_END