TextBox.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. #include "TextBox.h"
  2. #include "Game.h"
  3. namespace gameplay
  4. {
  5. TextBox::TextBox() : _caretLocation(0), _lastKeypress(0), _fontSize(0), _caretImage(NULL), _passwordChar('*'), _inputMode(TEXT), _ctrlPressed(false), _shiftPressed(false)
  6. {
  7. _canFocus = true;
  8. }
  9. TextBox::~TextBox()
  10. {
  11. }
  12. TextBox* TextBox::create(const char* id, Theme::Style* style)
  13. {
  14. TextBox* textBox = new TextBox();
  15. textBox->_id = id ? id : "";
  16. textBox->initialize("TextBox", style, NULL);
  17. return textBox;
  18. }
  19. Control* TextBox::create(Theme::Style* style, Properties* properties)
  20. {
  21. TextBox* textBox = new TextBox();
  22. textBox->initialize("TextBox", style, properties);
  23. return textBox;
  24. }
  25. void TextBox::addListener(Control::Listener* listener, int eventFlags)
  26. {
  27. if ((eventFlags & Control::Listener::VALUE_CHANGED) == Control::Listener::VALUE_CHANGED)
  28. {
  29. GP_ERROR("VALUE_CHANGED event is not applicable to this control.");
  30. }
  31. Control::addListener(listener, eventFlags);
  32. }
  33. void TextBox::initialize(const char* typeName, Theme::Style* style, Properties* properties)
  34. {
  35. Label::initialize(typeName, style, properties);
  36. if (properties)
  37. {
  38. _inputMode = getInputMode(properties->getString("inputMode"));
  39. }
  40. }
  41. int TextBox::getLastKeypress()
  42. {
  43. return _lastKeypress;
  44. }
  45. unsigned int TextBox::getCaretLocation() const
  46. {
  47. return _caretLocation;
  48. }
  49. void TextBox::setCaretLocation(unsigned int index)
  50. {
  51. _caretLocation = index;
  52. if (_caretLocation > _text.length())
  53. _caretLocation = (unsigned int)_text.length();
  54. }
  55. bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
  56. {
  57. if (getState() == ACTIVE) {
  58. switch (evt)
  59. {
  60. case Touch::TOUCH_PRESS:
  61. setCaretLocation(x, y);
  62. break;
  63. case Touch::TOUCH_MOVE:
  64. setCaretLocation(x, y);
  65. break;
  66. default:
  67. break;
  68. }
  69. }
  70. return Label::touchEvent(evt, x, y, contactIndex);
  71. }
  72. static bool isWhitespace(char c)
  73. {
  74. switch (c)
  75. {
  76. case ' ':
  77. case '\t':
  78. case '\r':
  79. case '\n':
  80. return true;
  81. default:
  82. return false;
  83. }
  84. }
  85. static unsigned int findNextWord(const std::string& text, unsigned int from, bool backwards)
  86. {
  87. int pos = (int)from;
  88. if (backwards)
  89. {
  90. if (pos > 0)
  91. {
  92. // Moving backwards: skip all consecutive whitespace characters
  93. while (pos > 0 && isWhitespace(text.at(pos-1)))
  94. --pos;
  95. // Now search back to the first whitespace character
  96. while (pos > 0 && !isWhitespace(text.at(pos-1)))
  97. --pos;
  98. }
  99. }
  100. else
  101. {
  102. const int len = (const int)text.length();
  103. if (pos < len)
  104. {
  105. // Moving forward: skip all consecutive non-whitespace characters
  106. ++pos;
  107. while (pos < len && !isWhitespace(text.at(pos)))
  108. ++pos;
  109. // Now search for the first non-whitespace character
  110. while (pos < len && isWhitespace(text.at(pos)))
  111. ++pos;
  112. }
  113. }
  114. return (unsigned int)pos;
  115. }
  116. bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
  117. {
  118. switch (evt)
  119. {
  120. case Keyboard::KEY_PRESS:
  121. {
  122. switch (key)
  123. {
  124. case Keyboard::KEY_SHIFT:
  125. {
  126. _shiftPressed = true;
  127. break;
  128. }
  129. case Keyboard::KEY_CTRL:
  130. {
  131. _ctrlPressed = true;
  132. break;
  133. }
  134. case Keyboard::KEY_HOME:
  135. {
  136. _caretLocation = 0;
  137. break;
  138. }
  139. case Keyboard::KEY_END:
  140. {
  141. _caretLocation = _text.length();
  142. break;
  143. }
  144. case Keyboard::KEY_DELETE:
  145. {
  146. if (_caretLocation < _text.length())
  147. {
  148. int newCaretLocation;
  149. if (_ctrlPressed)
  150. {
  151. newCaretLocation = findNextWord(getDisplayedText(), _caretLocation, false);
  152. }
  153. else
  154. {
  155. newCaretLocation = _caretLocation + 1;
  156. }
  157. _text.erase(_caretLocation, newCaretLocation - _caretLocation);
  158. notifyListeners(Control::Listener::TEXT_CHANGED);
  159. }
  160. break;
  161. }
  162. case Keyboard::KEY_TAB:
  163. {
  164. // Allow tab to move the focus forward.
  165. return false;
  166. }
  167. case Keyboard::KEY_LEFT_ARROW:
  168. {
  169. if (_caretLocation > 0)
  170. {
  171. if (_ctrlPressed)
  172. {
  173. _caretLocation = findNextWord(getDisplayedText(), _caretLocation, true);
  174. }
  175. else
  176. {
  177. --_caretLocation;
  178. }
  179. }
  180. break;
  181. }
  182. case Keyboard::KEY_RIGHT_ARROW:
  183. {
  184. if (_caretLocation < _text.length())
  185. {
  186. if (_ctrlPressed)
  187. {
  188. _caretLocation = findNextWord(getDisplayedText(), _caretLocation, false);
  189. }
  190. else
  191. {
  192. ++_caretLocation;
  193. }
  194. }
  195. break;
  196. }
  197. case Keyboard::KEY_UP_ARROW:
  198. {
  199. // TODO: Support multiline
  200. break;
  201. }
  202. case Keyboard::KEY_DOWN_ARROW:
  203. {
  204. // TODO: Support multiline
  205. break;
  206. }
  207. case Keyboard::KEY_BACKSPACE:
  208. {
  209. if (_caretLocation > 0)
  210. {
  211. int newCaretLocation;
  212. if (_ctrlPressed)
  213. {
  214. newCaretLocation = findNextWord(getDisplayedText(), _caretLocation, true);
  215. }
  216. else
  217. {
  218. newCaretLocation = _caretLocation - 1;
  219. }
  220. _text.erase(newCaretLocation, _caretLocation - newCaretLocation);
  221. _caretLocation = newCaretLocation;
  222. notifyListeners(Control::Listener::TEXT_CHANGED);
  223. }
  224. break;
  225. }
  226. }
  227. break;
  228. }
  229. case Keyboard::KEY_CHAR:
  230. {
  231. switch (key)
  232. {
  233. case Keyboard::KEY_RETURN:
  234. // TODO: Support multi-line
  235. break;
  236. case Keyboard::KEY_ESCAPE:
  237. break;
  238. case Keyboard::KEY_BACKSPACE:
  239. break;
  240. case Keyboard::KEY_TAB:
  241. // Allow tab to move the focus forward.
  242. return false;
  243. default:
  244. {
  245. // Insert character into string, only if our font supports this character
  246. if (_shiftPressed && islower(key))
  247. {
  248. key = toupper(key);
  249. }
  250. // Insert character into string, only if our font supports this character
  251. if (_font && _font->isCharacterSupported(key))
  252. {
  253. if (_caretLocation <= _text.length())
  254. {
  255. _text.insert(_caretLocation, 1, (char)key);
  256. ++_caretLocation;
  257. }
  258. notifyListeners(Control::Listener::TEXT_CHANGED);
  259. }
  260. break;
  261. }
  262. break;
  263. }
  264. break;
  265. }
  266. case Keyboard::KEY_RELEASE:
  267. switch (key)
  268. {
  269. case Keyboard::KEY_SHIFT:
  270. {
  271. _shiftPressed = false;
  272. break;
  273. }
  274. case Keyboard::KEY_CTRL:
  275. {
  276. _ctrlPressed = false;
  277. break;
  278. }
  279. }
  280. }
  281. _lastKeypress = key;
  282. return Label::keyEvent(evt, key);
  283. }
  284. void TextBox::controlEvent(Control::Listener::EventType evt)
  285. {
  286. Label::controlEvent(evt);
  287. switch (evt)
  288. {
  289. case Control::Listener::FOCUS_GAINED:
  290. Game::getInstance()->displayKeyboard(true);
  291. break;
  292. case Control::Listener::FOCUS_LOST:
  293. Game::getInstance()->displayKeyboard(false);
  294. break;
  295. default:
  296. break;
  297. }
  298. }
  299. void TextBox::updateState(State state)
  300. {
  301. Label::updateState(state);
  302. _fontSize = getFontSize(state);
  303. _caretImage = getImage("textCaret", state);
  304. }
  305. unsigned int TextBox::drawImages(Form* form, const Rectangle& clip)
  306. {
  307. Control::State state = getState();
  308. if (_caretImage && (state == ACTIVE || hasFocus()))
  309. {
  310. // Draw the cursor at its current location.
  311. const Rectangle& region = _caretImage->getRegion();
  312. if (!region.isEmpty())
  313. {
  314. const Theme::UVs& uvs = _caretImage->getUVs();
  315. Vector4 color = _caretImage->getColor();
  316. color.w *= _opacity;
  317. float caretWidth = region.width * _fontSize / region.height;
  318. Font* font = getFont(state);
  319. unsigned int fontSize = getFontSize(state);
  320. Vector2 point;
  321. font->getLocationAtIndex(getDisplayedText().c_str(), _textBounds, fontSize, &point, _caretLocation,
  322. getTextAlignment(state), true, getTextRightToLeft(state));
  323. SpriteBatch* batch = _style->getTheme()->getSpriteBatch();
  324. startBatch(form, batch);
  325. batch->draw(point.x - caretWidth * 0.5f, point.y, caretWidth, fontSize, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
  326. finishBatch(form, batch);
  327. return 1;
  328. }
  329. }
  330. return 0;
  331. }
  332. unsigned int TextBox::drawText(Form* form, const Rectangle& clip)
  333. {
  334. if (_text.size() <= 0)
  335. return 0;
  336. // Draw the text.
  337. if (_font)
  338. {
  339. Control::State state = getState();
  340. const std::string displayedText = getDisplayedText();
  341. unsigned int fontSize = getFontSize(state);
  342. SpriteBatch* batch = _font->getSpriteBatch(fontSize);
  343. startBatch(form, batch);
  344. _font->drawText(displayedText.c_str(), _textBounds, _textColor, fontSize, getTextAlignment(state), true, getTextRightToLeft(state), &_viewportClipBounds);
  345. finishBatch(form, batch);
  346. return 1;
  347. }
  348. return 0;
  349. }
  350. void TextBox::setCaretLocation(int x, int y)
  351. {
  352. Control::State state = getState();
  353. Vector2 point(x + _absoluteBounds.x, y + _absoluteBounds.y);
  354. // Get index into string and cursor location from the latest touch location.
  355. Font* font = getFont(state);
  356. unsigned int fontSize = getFontSize(state);
  357. Font::Justify textAlignment = getTextAlignment(state);
  358. bool rightToLeft = getTextRightToLeft(state);
  359. const std::string displayedText = getDisplayedText();
  360. int index = font->getIndexAtLocation(displayedText.c_str(), _textBounds, fontSize, point, &point,
  361. textAlignment, true, rightToLeft);
  362. if (index == -1)
  363. {
  364. // Attempt to find the nearest valid caret location.
  365. Rectangle textBounds;
  366. font->measureText(displayedText.c_str(), _textBounds, fontSize, &textBounds, textAlignment, true, true);
  367. if (point.x > textBounds.x + textBounds.width &&
  368. point.y > textBounds.y + textBounds.height)
  369. {
  370. font->getLocationAtIndex(displayedText.c_str(), _textBounds, fontSize, &point, (unsigned int)_text.length(),
  371. textAlignment, true, rightToLeft);
  372. return;
  373. }
  374. if (point.x < textBounds.x)
  375. {
  376. point.x = textBounds.x;
  377. }
  378. else if (point.x > textBounds.x + textBounds.width)
  379. {
  380. point.x = textBounds.x + textBounds.width;
  381. }
  382. if (point.y < textBounds.y)
  383. {
  384. point.y = textBounds.y;
  385. }
  386. else if (point.y > textBounds.y + textBounds.height)
  387. {
  388. Font* font = getFont(state);
  389. GP_ASSERT(font);
  390. unsigned int fontSize = getFontSize(state);
  391. point.y = textBounds.y + textBounds.height - fontSize;
  392. }
  393. index = font->getIndexAtLocation(displayedText.c_str(), _textBounds, fontSize, point, &point,
  394. textAlignment, true, rightToLeft);
  395. }
  396. if (index != -1)
  397. _caretLocation = index;
  398. }
  399. void TextBox::getCaretLocation(Vector2* p)
  400. {
  401. GP_ASSERT(p);
  402. State state = getState();
  403. getFont(state)->getLocationAtIndex(getDisplayedText().c_str(), _textBounds, getFontSize(state), p, _caretLocation, getTextAlignment(state), true, getTextRightToLeft(state));
  404. }
  405. const char* TextBox::getType() const
  406. {
  407. return "textBox";
  408. }
  409. void TextBox::setPasswordChar(char character)
  410. {
  411. _passwordChar = character;
  412. }
  413. char TextBox::getPasswordChar() const
  414. {
  415. return _passwordChar;
  416. }
  417. void TextBox::setInputMode(InputMode inputMode)
  418. {
  419. _inputMode = inputMode;
  420. }
  421. TextBox::InputMode TextBox::getInputMode() const
  422. {
  423. return _inputMode;
  424. }
  425. TextBox::InputMode TextBox::getInputMode(const char* inputMode)
  426. {
  427. if (!inputMode)
  428. {
  429. return TextBox::TEXT;
  430. }
  431. if (strcmp(inputMode, "TEXT") == 0)
  432. {
  433. return TextBox::TEXT;
  434. }
  435. else if (strcmp(inputMode, "PASSWORD") == 0)
  436. {
  437. return TextBox::PASSWORD;
  438. }
  439. else
  440. {
  441. GP_ERROR("Failed to get corresponding textbox inputmode for unsupported value '%s'.", inputMode);
  442. }
  443. // Default.
  444. return TextBox::TEXT;
  445. }
  446. std::string TextBox::getDisplayedText() const
  447. {
  448. std::string displayedText;
  449. switch (_inputMode) {
  450. case PASSWORD:
  451. displayedText.insert((size_t)0, _text.length(), _passwordChar);
  452. break;
  453. case TEXT:
  454. default:
  455. displayedText = _text;
  456. break;
  457. }
  458. return displayedText;
  459. }
  460. }