TextBox.cpp 14 KB

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