BsGUIInputBox.cpp 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139
  1. #include "BsGUIInputBox.h"
  2. #include "BsGUIManager.h"
  3. #include "BsImageSprite.h"
  4. #include "BsGUIWidget.h"
  5. #include "BsGUISkin.h"
  6. #include "BsSpriteTexture.h"
  7. #include "BsTextSprite.h"
  8. #include "BsGUILayoutOptions.h"
  9. #include "BsGUITextInputEvent.h"
  10. #include "BsGUIMouseEvent.h"
  11. #include "BsGUICommandEvent.h"
  12. #include "CmFont.h"
  13. #include "CmTextData.h"
  14. #include "CmTexture.h"
  15. #include "CmPlatform.h"
  16. #include "BsGUIInputCaret.h"
  17. #include "BsGUIInputSelection.h"
  18. #include "BsDragAndDropManager.h"
  19. #include "BsGUIContextMenu.h"
  20. #include "BsGUIHelper.h"
  21. namespace BansheeEngine
  22. {
  23. VirtualButton GUIInputBox::mCopyVB = VirtualButton("Copy");
  24. VirtualButton GUIInputBox::mPasteVB = VirtualButton("Paste");
  25. VirtualButton GUIInputBox::mCutVB = VirtualButton("Cut");
  26. VirtualButton GUIInputBox::mSelectAllVB = VirtualButton("SelectAll");
  27. const String& GUIInputBox::getGUITypeName()
  28. {
  29. static String name = "InputBox";
  30. return name;
  31. }
  32. GUIInputBox::GUIInputBox(const String& styleName, const GUILayoutOptions& layoutOptions, bool multiline)
  33. :GUIElement(styleName, layoutOptions), mDragInProgress(false),
  34. mCaretShown(false), mSelectionShown(false), mIsMultiline(multiline), mHasFocus(false), mIsMouseOver(false),
  35. mState(State::Normal)
  36. {
  37. mImageSprite = cm_new<ImageSprite, PoolAlloc>();
  38. mTextSprite = cm_new<TextSprite, PoolAlloc>();
  39. }
  40. GUIInputBox::~GUIInputBox()
  41. {
  42. cm_delete<PoolAlloc>(mTextSprite);
  43. cm_delete<PoolAlloc>(mImageSprite);
  44. }
  45. GUIInputBox* GUIInputBox::create(bool multiline, const String& styleName)
  46. {
  47. return new (cm_alloc<GUIInputBox, PoolAlloc>()) GUIInputBox(getStyleName<GUIInputBox>(styleName), GUILayoutOptions::create(), multiline);
  48. }
  49. GUIInputBox* GUIInputBox::create(bool multiline, const GUIOptions& layoutOptions, const String& styleName)
  50. {
  51. return new (cm_alloc<GUIInputBox, PoolAlloc>()) GUIInputBox(getStyleName<GUIInputBox>(styleName), GUILayoutOptions::create(layoutOptions), multiline);
  52. }
  53. GUIInputBox* GUIInputBox::create(const GUIOptions& layoutOptions, const String& styleName)
  54. {
  55. return new (cm_alloc<GUIInputBox, PoolAlloc>()) GUIInputBox(getStyleName<GUIInputBox>(styleName), GUILayoutOptions::create(layoutOptions), false);
  56. }
  57. void GUIInputBox::setText(const WString& text)
  58. {
  59. bool filterOkay = true;
  60. if(mFilter != nullptr)
  61. {
  62. filterOkay = mFilter(text);
  63. }
  64. if(filterOkay)
  65. {
  66. WString oldText = mText;
  67. mText = text;
  68. TEXT_SPRITE_DESC textDesc = getTextDesc();
  69. Vector2I offset = getTextOffset();
  70. gGUIManager().getInputCaretTool()->updateText(this, textDesc);
  71. gGUIManager().getInputSelectionTool()->updateText(this, textDesc);
  72. if(mText.size() > 0)
  73. gGUIManager().getInputCaretTool()->moveCaretToChar((UINT32)mText.size() - 1, CARET_AFTER);
  74. else
  75. gGUIManager().getInputCaretTool()->moveCaretToChar(0, CARET_BEFORE);
  76. scrollTextToCaret();
  77. markContentAsDirty();
  78. }
  79. }
  80. UINT32 GUIInputBox::getNumRenderElements() const
  81. {
  82. UINT32 numElements = mImageSprite->getNumRenderElements();
  83. numElements += mTextSprite->getNumRenderElements();
  84. if(mCaretShown && gGUIManager().getCaretBlinkState())
  85. numElements += gGUIManager().getInputCaretTool()->getSprite()->getNumRenderElements();
  86. if(mSelectionShown)
  87. {
  88. const Vector<ImageSprite*>::type& sprites = gGUIManager().getInputSelectionTool()->getSprites();
  89. for(auto& selectionSprite : sprites)
  90. {
  91. numElements += selectionSprite->getNumRenderElements();
  92. }
  93. }
  94. return numElements;
  95. }
  96. const GUIMaterialInfo& GUIInputBox::getMaterial(UINT32 renderElementIdx) const
  97. {
  98. UINT32 localRenderElementIdx;
  99. Sprite* sprite = renderElemToSprite(renderElementIdx, localRenderElementIdx);
  100. return sprite->getMaterial(localRenderElementIdx);
  101. }
  102. UINT32 GUIInputBox::getNumQuads(UINT32 renderElementIdx) const
  103. {
  104. UINT32 localRenderElementIdx;
  105. Sprite* sprite = renderElemToSprite(renderElementIdx, localRenderElementIdx);
  106. return sprite->getNumQuads(localRenderElementIdx);
  107. }
  108. void GUIInputBox::updateRenderElementsInternal()
  109. {
  110. mImageDesc.width = mWidth;
  111. mImageDesc.height = mHeight;
  112. mImageDesc.borderLeft = _getStyle()->border.left;
  113. mImageDesc.borderRight = _getStyle()->border.right;
  114. mImageDesc.borderTop = _getStyle()->border.top;
  115. mImageDesc.borderBottom = _getStyle()->border.bottom;
  116. const HSpriteTexture& activeTex = getActiveTexture();
  117. if(SpriteTexture::checkIsLoaded(activeTex))
  118. {
  119. mImageDesc.texture = activeTex.getInternalPtr();
  120. }
  121. mImageSprite->update(mImageDesc);
  122. TEXT_SPRITE_DESC textDesc = getTextDesc();
  123. mTextSprite->update(textDesc);
  124. if(mCaretShown && gGUIManager().getCaretBlinkState())
  125. {
  126. gGUIManager().getInputCaretTool()->updateText(this, textDesc); // TODO - These shouldn't be here. Only call this when one of these parameters changes.
  127. gGUIManager().getInputCaretTool()->updateSprite();
  128. }
  129. if(mSelectionShown)
  130. {
  131. gGUIManager().getInputSelectionTool()->updateText(this, textDesc); // TODO - These shouldn't be here. Only call this when one of these parameters changes.
  132. gGUIManager().getInputSelectionTool()->updateSprite();
  133. }
  134. // When text bounds are reduced the scroll needs to be adjusted so that
  135. // input box isn't filled with mostly empty space.
  136. clampScrollToBounds(mTextSprite->getBounds(mOffset, RectI()));
  137. GUIElement::updateRenderElementsInternal();
  138. }
  139. void GUIInputBox::updateClippedBounds()
  140. {
  141. mClippedBounds = mImageSprite->getBounds(mOffset, mClipRect);
  142. }
  143. Sprite* GUIInputBox::renderElemToSprite(UINT32 renderElemIdx, UINT32& localRenderElemIdx) const
  144. {
  145. UINT32 oldNumElements = 0;
  146. UINT32 newNumElements = oldNumElements + mTextSprite->getNumRenderElements();
  147. if(renderElemIdx < newNumElements)
  148. {
  149. localRenderElemIdx = renderElemIdx - oldNumElements;
  150. return mTextSprite;
  151. }
  152. oldNumElements = newNumElements;
  153. newNumElements += mImageSprite->getNumRenderElements();
  154. if(renderElemIdx < newNumElements)
  155. {
  156. localRenderElemIdx = renderElemIdx - oldNumElements;
  157. return mImageSprite;
  158. }
  159. if(mCaretShown && gGUIManager().getCaretBlinkState())
  160. {
  161. oldNumElements = newNumElements;
  162. newNumElements += gGUIManager().getInputCaretTool()->getSprite()->getNumRenderElements();
  163. if(renderElemIdx < newNumElements)
  164. {
  165. localRenderElemIdx = renderElemIdx - oldNumElements;
  166. return gGUIManager().getInputCaretTool()->getSprite();
  167. }
  168. }
  169. if(mSelectionShown)
  170. {
  171. const Vector<ImageSprite*>::type& sprites = gGUIManager().getInputSelectionTool()->getSprites();
  172. for(auto& selectionSprite : sprites)
  173. {
  174. oldNumElements = newNumElements;
  175. newNumElements += selectionSprite->getNumRenderElements();
  176. if(renderElemIdx < newNumElements)
  177. {
  178. localRenderElemIdx = renderElemIdx - oldNumElements;
  179. return selectionSprite;
  180. }
  181. }
  182. }
  183. localRenderElemIdx = renderElemIdx;
  184. return nullptr;
  185. }
  186. Vector2I GUIInputBox::renderElemToOffset(UINT32 renderElemIdx) const
  187. {
  188. UINT32 oldNumElements = 0;
  189. UINT32 newNumElements = oldNumElements + mTextSprite->getNumRenderElements();
  190. if(renderElemIdx < newNumElements)
  191. return getTextOffset();
  192. oldNumElements = newNumElements;
  193. newNumElements += mImageSprite->getNumRenderElements();
  194. if(renderElemIdx < newNumElements)
  195. return mOffset;
  196. if(mCaretShown && gGUIManager().getCaretBlinkState())
  197. {
  198. oldNumElements = newNumElements;
  199. newNumElements += gGUIManager().getInputCaretTool()->getSprite()->getNumRenderElements();
  200. if(renderElemIdx < newNumElements)
  201. return gGUIManager().getInputCaretTool()->getSpriteOffset();
  202. }
  203. if(mSelectionShown)
  204. {
  205. UINT32 spriteIdx = 0;
  206. const Vector<ImageSprite*>::type& sprites = gGUIManager().getInputSelectionTool()->getSprites();
  207. for(auto& selectionSprite : sprites)
  208. {
  209. oldNumElements = newNumElements;
  210. newNumElements += selectionSprite->getNumRenderElements();
  211. if(renderElemIdx < newNumElements)
  212. return gGUIManager().getInputSelectionTool()->getSelectionSpriteOffset(spriteIdx);
  213. spriteIdx++;
  214. }
  215. }
  216. return Vector2I();
  217. }
  218. RectI GUIInputBox::renderElemToClipRect(UINT32 renderElemIdx) const
  219. {
  220. UINT32 oldNumElements = 0;
  221. UINT32 newNumElements = oldNumElements + mTextSprite->getNumRenderElements();
  222. if(renderElemIdx < newNumElements)
  223. return getTextClipRect();
  224. oldNumElements = newNumElements;
  225. newNumElements += mImageSprite->getNumRenderElements();
  226. if(renderElemIdx < newNumElements)
  227. return mClipRect;
  228. if(mCaretShown && gGUIManager().getCaretBlinkState())
  229. {
  230. oldNumElements = newNumElements;
  231. newNumElements += gGUIManager().getInputCaretTool()->getSprite()->getNumRenderElements();
  232. if(renderElemIdx < newNumElements)
  233. {
  234. return gGUIManager().getInputCaretTool()->getSpriteClipRect(getTextClipRect());
  235. }
  236. }
  237. if(mSelectionShown)
  238. {
  239. UINT32 spriteIdx = 0;
  240. const Vector<ImageSprite*>::type& sprites = gGUIManager().getInputSelectionTool()->getSprites();
  241. for(auto& selectionSprite : sprites)
  242. {
  243. oldNumElements = newNumElements;
  244. newNumElements += selectionSprite->getNumRenderElements();
  245. if(renderElemIdx < newNumElements)
  246. return gGUIManager().getInputSelectionTool()->getSelectionSpriteClipRect(spriteIdx, getTextClipRect());
  247. spriteIdx++;
  248. }
  249. }
  250. return RectI();
  251. }
  252. Vector2I GUIInputBox::_getOptimalSize() const
  253. {
  254. UINT32 imageWidth = 0;
  255. UINT32 imageHeight = 0;
  256. const HSpriteTexture& activeTex = getActiveTexture();
  257. if(SpriteTexture::checkIsLoaded(activeTex))
  258. {
  259. imageWidth = activeTex->getTexture()->getWidth();
  260. imageHeight = activeTex->getTexture()->getHeight();
  261. }
  262. Vector2I contentSize = GUIHelper::calcOptimalContentsSize(mText, *_getStyle(), _getLayoutOptions());
  263. UINT32 contentWidth = std::max(imageWidth, (UINT32)contentSize.x);
  264. UINT32 contentHeight = std::max(imageHeight, (UINT32)contentSize.y);
  265. return Vector2I(contentWidth, contentHeight);
  266. }
  267. Vector2I GUIInputBox::_getTextInputOffset() const
  268. {
  269. return mTextOffset;
  270. }
  271. RectI GUIInputBox::_getTextInputRect() const
  272. {
  273. RectI textBounds = getContentBounds();
  274. textBounds.x -= mOffset.x;
  275. textBounds.y -= mOffset.y;
  276. return textBounds;
  277. }
  278. UINT32 GUIInputBox::_getRenderElementDepth(UINT32 renderElementIdx) const
  279. {
  280. UINT32 localRenderElementIdx;
  281. Sprite* sprite = renderElemToSprite(renderElementIdx, localRenderElementIdx);
  282. if(sprite == mImageSprite)
  283. return _getDepth();
  284. else if(sprite == mTextSprite)
  285. return _getDepth() - 2;
  286. else if(sprite == gGUIManager().getInputCaretTool()->getSprite())
  287. return _getDepth() - 3;
  288. else // Selection sprites
  289. return _getDepth() - 1;
  290. }
  291. bool GUIInputBox::_hasCustomCursor(const Vector2I position, CursorType& type) const
  292. {
  293. if(_isInBounds(position))
  294. {
  295. type = CursorType::IBeam;
  296. return true;
  297. }
  298. return false;
  299. }
  300. void GUIInputBox::fillBuffer(UINT8* vertices, UINT8* uv, UINT32* indices, UINT32 startingQuad, UINT32 maxNumQuads,
  301. UINT32 vertexStride, UINT32 indexStride, UINT32 renderElementIdx) const
  302. {
  303. UINT32 localRenderElementIdx;
  304. Sprite* sprite = renderElemToSprite(renderElementIdx, localRenderElementIdx);
  305. Vector2I offset = renderElemToOffset(renderElementIdx);
  306. RectI clipRect = renderElemToClipRect(renderElementIdx);
  307. sprite->fillBuffer(vertices, uv, indices, startingQuad, maxNumQuads, vertexStride, indexStride, localRenderElementIdx, offset, clipRect);
  308. }
  309. bool GUIInputBox::mouseEvent(const GUIMouseEvent& ev)
  310. {
  311. if(ev.getType() == GUIMouseEventType::MouseOver)
  312. {
  313. if(!mHasFocus)
  314. {
  315. mState = State::Hover;
  316. markContentAsDirty();
  317. }
  318. mIsMouseOver = true;
  319. return true;
  320. }
  321. else if(ev.getType() == GUIMouseEventType::MouseOut)
  322. {
  323. if(!mHasFocus)
  324. {
  325. mState = State::Normal;
  326. markContentAsDirty();
  327. }
  328. mIsMouseOver = false;
  329. return true;
  330. }
  331. else if(ev.getType() == GUIMouseEventType::MouseDoubleClick && ev.getButton() == GUIMouseButton::Left)
  332. {
  333. showSelection(0);
  334. gGUIManager().getInputSelectionTool()->selectAll();
  335. markContentAsDirty();
  336. return true;
  337. }
  338. else if(ev.getType() == GUIMouseEventType::MouseDown && ev.getButton() == GUIMouseButton::Left)
  339. {
  340. if(mHasFocus)
  341. {
  342. if(ev.isShiftDown())
  343. {
  344. if(!mSelectionShown)
  345. showSelection(gGUIManager().getInputCaretTool()->getCaretPos());
  346. }
  347. else
  348. clearSelection();
  349. if(mText.size() > 0)
  350. gGUIManager().getInputCaretTool()->moveCaretToPos(ev.getPosition());
  351. else
  352. gGUIManager().getInputCaretTool()->moveCaretToStart();
  353. if(ev.isShiftDown())
  354. gGUIManager().getInputSelectionTool()->moveSelectionToCaret(gGUIManager().getInputCaretTool()->getCaretPos());
  355. }
  356. else
  357. {
  358. clearSelection();
  359. showCaret();
  360. if(mText.size() > 0)
  361. gGUIManager().getInputCaretTool()->moveCaretToPos(ev.getPosition());
  362. else
  363. gGUIManager().getInputCaretTool()->moveCaretToStart();
  364. }
  365. scrollTextToCaret();
  366. markContentAsDirty();
  367. return true;
  368. }
  369. else if(ev.getType() == GUIMouseEventType::MouseDragStart)
  370. {
  371. if(!ev.isShiftDown())
  372. {
  373. mDragInProgress = true;
  374. UINT32 caretPos = gGUIManager().getInputCaretTool()->getCaretPos();
  375. showSelection(caretPos);
  376. gGUIManager().getInputSelectionTool()->selectionDragStart(caretPos);
  377. return true;
  378. }
  379. }
  380. else if(ev.getType() == GUIMouseEventType::MouseDragEnd)
  381. {
  382. if(!ev.isShiftDown())
  383. {
  384. mDragInProgress = false;
  385. gGUIManager().getInputSelectionTool()->selectionDragEnd();
  386. return true;
  387. }
  388. }
  389. else if(ev.getType() == GUIMouseEventType::MouseDrag)
  390. {
  391. if(!ev.isShiftDown())
  392. {
  393. if(mText.size() > 0)
  394. gGUIManager().getInputCaretTool()->moveCaretToPos(ev.getPosition());
  395. else
  396. gGUIManager().getInputCaretTool()->moveCaretToStart();
  397. gGUIManager().getInputSelectionTool()->selectionDragUpdate(gGUIManager().getInputCaretTool()->getCaretPos());
  398. scrollTextToCaret();
  399. markContentAsDirty();
  400. return true;
  401. }
  402. }
  403. return false;
  404. }
  405. bool GUIInputBox::textInputEvent(const GUITextInputEvent& ev)
  406. {
  407. if(mSelectionShown)
  408. deleteSelectedText();
  409. UINT32 charIdx = gGUIManager().getInputCaretTool()->getCharIdxAtCaretPos();
  410. bool filterOkay = true;
  411. if(mFilter != nullptr)
  412. {
  413. WString newText = mText;
  414. newText.insert(newText.begin() + charIdx, ev.getInputChar());
  415. filterOkay = mFilter(newText);
  416. }
  417. if(filterOkay)
  418. {
  419. insertChar(charIdx, ev.getInputChar());
  420. gGUIManager().getInputCaretTool()->moveCaretToChar(charIdx, CARET_AFTER);
  421. scrollTextToCaret();
  422. markContentAsDirty();
  423. if(!onValueChanged.empty())
  424. onValueChanged(mText);
  425. }
  426. return true;
  427. }
  428. bool GUIInputBox::commandEvent(const GUICommandEvent& ev)
  429. {
  430. if(ev.getType() == GUICommandEventType::Redraw)
  431. {
  432. markMeshAsDirty();
  433. return true;
  434. }
  435. if(ev.getType() == GUICommandEventType::FocusGained)
  436. {
  437. mState = State::Focused;
  438. clearSelection();
  439. showCaret();
  440. markContentAsDirty();
  441. mHasFocus = true;
  442. if(!onFocusGained.empty())
  443. onFocusGained();
  444. return true;
  445. }
  446. if(ev.getType() == GUICommandEventType::FocusLost)
  447. {
  448. mState = State::Normal;
  449. hideCaret();
  450. clearSelection();
  451. markContentAsDirty();
  452. mHasFocus = false;
  453. if(!onFocusLost.empty())
  454. onFocusLost();
  455. return true;
  456. }
  457. if(ev.getType() == GUICommandEventType::Backspace)
  458. {
  459. if(mText.size() > 0)
  460. {
  461. if(mSelectionShown)
  462. {
  463. deleteSelectedText();
  464. }
  465. else
  466. {
  467. UINT32 charIdx = gGUIManager().getInputCaretTool()->getCharIdxAtCaretPos() - 1;
  468. if(charIdx < (UINT32)mText.size())
  469. {
  470. bool filterOkay = true;
  471. if(mFilter != nullptr)
  472. {
  473. WString newText = mText;
  474. newText.erase(charIdx, 1);
  475. filterOkay = mFilter(newText);
  476. }
  477. if(filterOkay)
  478. {
  479. eraseChar(charIdx);
  480. if(charIdx > 0)
  481. charIdx--;
  482. gGUIManager().getInputCaretTool()->moveCaretToChar(charIdx, CARET_AFTER);
  483. scrollTextToCaret();
  484. if(!onValueChanged.empty())
  485. onValueChanged(mText);
  486. }
  487. }
  488. }
  489. markContentAsDirty();
  490. }
  491. return true;
  492. }
  493. if(ev.getType() == GUICommandEventType::Delete)
  494. {
  495. if(mText.size() > 0)
  496. {
  497. if(mSelectionShown)
  498. {
  499. deleteSelectedText();
  500. }
  501. else
  502. {
  503. UINT32 charIdx = gGUIManager().getInputCaretTool()->getCharIdxAtCaretPos();
  504. if(charIdx < (UINT32)mText.size())
  505. {
  506. bool filterOkay = true;
  507. if(mFilter != nullptr)
  508. {
  509. WString newText = mText;
  510. newText.erase(charIdx, 1);
  511. filterOkay = mFilter(newText);
  512. }
  513. if(filterOkay)
  514. {
  515. eraseChar(charIdx);
  516. if(charIdx > 0)
  517. charIdx--;
  518. gGUIManager().getInputCaretTool()->moveCaretToChar(charIdx, CARET_AFTER);
  519. scrollTextToCaret();
  520. if(!onValueChanged.empty())
  521. onValueChanged(mText);
  522. }
  523. }
  524. }
  525. markContentAsDirty();
  526. }
  527. return true;
  528. }
  529. if(ev.getType() == GUICommandEventType::MoveLeft)
  530. {
  531. if(mSelectionShown)
  532. {
  533. UINT32 selStart = gGUIManager().getInputSelectionTool()->getSelectionStart();
  534. clearSelection();
  535. if(selStart > 0)
  536. gGUIManager().getInputCaretTool()->moveCaretToChar(selStart - 1, CARET_AFTER);
  537. else
  538. gGUIManager().getInputCaretTool()->moveCaretToChar(0, CARET_BEFORE);
  539. }
  540. else
  541. gGUIManager().getInputCaretTool()->moveCaretLeft();
  542. scrollTextToCaret();
  543. markContentAsDirty();
  544. return true;
  545. }
  546. if(ev.getType() == GUICommandEventType::SelectLeft)
  547. {
  548. if(!mSelectionShown)
  549. showSelection(gGUIManager().getInputCaretTool()->getCaretPos());
  550. gGUIManager().getInputCaretTool()->moveCaretLeft();
  551. gGUIManager().getInputSelectionTool()->moveSelectionToCaret(gGUIManager().getInputCaretTool()->getCaretPos());
  552. scrollTextToCaret();
  553. markContentAsDirty();
  554. return true;
  555. }
  556. if(ev.getType() == GUICommandEventType::MoveRight)
  557. {
  558. if(mSelectionShown)
  559. {
  560. UINT32 selEnd = gGUIManager().getInputSelectionTool()->getSelectionEnd();
  561. clearSelection();
  562. if(selEnd > 0)
  563. gGUIManager().getInputCaretTool()->moveCaretToChar(selEnd - 1, CARET_AFTER);
  564. else
  565. gGUIManager().getInputCaretTool()->moveCaretToChar(0, CARET_BEFORE);
  566. }
  567. else
  568. gGUIManager().getInputCaretTool()->moveCaretRight();
  569. scrollTextToCaret();
  570. markContentAsDirty();
  571. return true;
  572. }
  573. if(ev.getType() == GUICommandEventType::SelectRight)
  574. {
  575. if(!mSelectionShown)
  576. showSelection(gGUIManager().getInputCaretTool()->getCaretPos());
  577. gGUIManager().getInputCaretTool()->moveCaretRight();
  578. gGUIManager().getInputSelectionTool()->moveSelectionToCaret(gGUIManager().getInputCaretTool()->getCaretPos());
  579. scrollTextToCaret();
  580. markContentAsDirty();
  581. return true;
  582. }
  583. if(ev.getType() == GUICommandEventType::MoveUp)
  584. {
  585. clearSelection();
  586. gGUIManager().getInputCaretTool()->moveCaretUp();
  587. scrollTextToCaret();
  588. markContentAsDirty();
  589. return true;
  590. }
  591. if(ev.getType() == GUICommandEventType::SelectUp)
  592. {
  593. if(!mSelectionShown)
  594. showSelection(gGUIManager().getInputCaretTool()->getCaretPos());;
  595. gGUIManager().getInputCaretTool()->moveCaretUp();
  596. gGUIManager().getInputSelectionTool()->moveSelectionToCaret(gGUIManager().getInputCaretTool()->getCaretPos());
  597. scrollTextToCaret();
  598. markContentAsDirty();
  599. return true;
  600. }
  601. if(ev.getType() == GUICommandEventType::MoveDown)
  602. {
  603. clearSelection();
  604. gGUIManager().getInputCaretTool()->moveCaretDown();
  605. scrollTextToCaret();
  606. markContentAsDirty();
  607. return true;
  608. }
  609. if(ev.getType() == GUICommandEventType::SelectDown)
  610. {
  611. if(!mSelectionShown)
  612. showSelection(gGUIManager().getInputCaretTool()->getCaretPos());
  613. gGUIManager().getInputCaretTool()->moveCaretDown();
  614. gGUIManager().getInputSelectionTool()->moveSelectionToCaret(gGUIManager().getInputCaretTool()->getCaretPos());
  615. scrollTextToCaret();
  616. markContentAsDirty();
  617. return true;
  618. }
  619. if(ev.getType() == GUICommandEventType::Return)
  620. {
  621. if(mIsMultiline)
  622. {
  623. if(mSelectionShown)
  624. deleteSelectedText();
  625. UINT32 charIdx = gGUIManager().getInputCaretTool()->getCharIdxAtCaretPos();
  626. bool filterOkay = true;
  627. if(mFilter != nullptr)
  628. {
  629. WString newText = mText;
  630. newText.insert(newText.begin() + charIdx, '\n');
  631. filterOkay = mFilter(newText);
  632. }
  633. if(filterOkay)
  634. {
  635. insertChar(charIdx, '\n');
  636. gGUIManager().getInputCaretTool()->moveCaretRight();
  637. scrollTextToCaret();
  638. markContentAsDirty();
  639. if(!onValueChanged.empty())
  640. onValueChanged(mText);
  641. }
  642. return true;
  643. }
  644. }
  645. return false;
  646. }
  647. bool GUIInputBox::virtualButtonEvent(const GUIVirtualButtonEvent& ev)
  648. {
  649. if(ev.getButton() == mCutVB)
  650. {
  651. cutText();
  652. markContentAsDirty();
  653. return true;
  654. }
  655. else if(ev.getButton() == mCopyVB)
  656. {
  657. copyText();
  658. return true;
  659. }
  660. else if(ev.getButton() == mPasteVB)
  661. {
  662. pasteText();
  663. markContentAsDirty();
  664. return true;
  665. }
  666. else if(ev.getButton() == mSelectAllVB)
  667. {
  668. showSelection(0);
  669. gGUIManager().getInputSelectionTool()->selectAll();
  670. markContentAsDirty();
  671. return true;
  672. }
  673. return false;
  674. }
  675. void GUIInputBox::showCaret()
  676. {
  677. mCaretShown = true;
  678. TEXT_SPRITE_DESC textDesc = getTextDesc();
  679. Vector2I offset = getTextOffset();
  680. gGUIManager().getInputCaretTool()->updateText(this, textDesc);
  681. markContentAsDirty();
  682. }
  683. void GUIInputBox::hideCaret()
  684. {
  685. mCaretShown = false;
  686. markContentAsDirty();
  687. }
  688. void GUIInputBox::showSelection(UINT32 anchorCaretPos)
  689. {
  690. TEXT_SPRITE_DESC textDesc = getTextDesc();
  691. Vector2I offset = getTextOffset();
  692. gGUIManager().getInputSelectionTool()->updateText(this, textDesc);
  693. gGUIManager().getInputSelectionTool()->showSelection(anchorCaretPos);
  694. mSelectionShown = true;
  695. markContentAsDirty();
  696. }
  697. void GUIInputBox::clearSelection()
  698. {
  699. gGUIManager().getInputSelectionTool()->clearSelection();
  700. mSelectionShown = false;
  701. markContentAsDirty();
  702. }
  703. void GUIInputBox::scrollTextToCaret()
  704. {
  705. TEXT_SPRITE_DESC textDesc = getTextDesc();
  706. Vector2I textOffset = getTextOffset();
  707. Vector2I caretPos = gGUIManager().getInputCaretTool()->getCaretPosition(textOffset);
  708. UINT32 caretHeight = gGUIManager().getInputCaretTool()->getCaretHeight();
  709. UINT32 caretWidth = 1;
  710. INT32 caretRight = caretPos.x + (INT32)caretWidth;
  711. INT32 caretBottom = caretPos.y + (INT32)caretHeight;
  712. INT32 left = textOffset.x - mTextOffset.x;
  713. // Include caret width here because we don't want to scroll if just the caret is outside the bounds
  714. // (Possible if the text width is exactly the maximum width)
  715. INT32 right = left + (INT32)textDesc.width + caretWidth;
  716. INT32 top = textOffset.y - mTextOffset.y;
  717. INT32 bottom = top + (INT32)textDesc.height;
  718. Vector2I offset;
  719. if(caretPos.x < left)
  720. {
  721. offset.x = left - caretPos.x;
  722. }
  723. else if(caretRight > right)
  724. {
  725. offset.x = -(caretRight - right);
  726. }
  727. if(caretPos.y < top)
  728. {
  729. offset.y = top - caretPos.y;
  730. }
  731. else if(caretBottom > bottom)
  732. {
  733. offset.y = -(caretBottom - bottom);
  734. }
  735. mTextOffset += offset;
  736. gGUIManager().getInputCaretTool()->updateText(this, textDesc);
  737. gGUIManager().getInputSelectionTool()->updateText(this, textDesc);
  738. markContentAsDirty();
  739. }
  740. void GUIInputBox::clampScrollToBounds(RectI unclippedTextBounds)
  741. {
  742. TEXT_SPRITE_DESC textDesc = getTextDesc();
  743. Vector2I newTextOffset;
  744. INT32 maxScrollableWidth = std::max(0, (INT32)unclippedTextBounds.width - (INT32)textDesc.width);
  745. INT32 maxScrollableHeight = std::max(0, (INT32)unclippedTextBounds.height - (INT32)textDesc.height);
  746. newTextOffset.x = Math::clamp(mTextOffset.x, -maxScrollableWidth, 0);
  747. newTextOffset.y = Math::clamp(mTextOffset.y, -maxScrollableHeight, 0);
  748. if(newTextOffset != mTextOffset)
  749. {
  750. mTextOffset = newTextOffset;
  751. gGUIManager().getInputCaretTool()->updateText(this, textDesc);
  752. gGUIManager().getInputSelectionTool()->updateText(this, textDesc);
  753. markContentAsDirty();
  754. }
  755. }
  756. void GUIInputBox::insertString(UINT32 charIdx, const WString& string)
  757. {
  758. mText.insert(mText.begin() + charIdx, string.begin(), string.end());
  759. TEXT_SPRITE_DESC textDesc = getTextDesc();
  760. Vector2I offset = getTextOffset();
  761. gGUIManager().getInputCaretTool()->updateText(this, textDesc);
  762. gGUIManager().getInputSelectionTool()->updateText(this, textDesc);
  763. }
  764. void GUIInputBox::insertChar(UINT32 charIdx, UINT32 charCode)
  765. {
  766. mText.insert(mText.begin() + charIdx, charCode);
  767. TEXT_SPRITE_DESC textDesc = getTextDesc();
  768. Vector2I offset = getTextOffset();
  769. gGUIManager().getInputCaretTool()->updateText(this, textDesc);
  770. gGUIManager().getInputSelectionTool()->updateText(this, textDesc);
  771. }
  772. void GUIInputBox::eraseChar(UINT32 charIdx)
  773. {
  774. mText.erase(charIdx, 1);
  775. TEXT_SPRITE_DESC textDesc = getTextDesc();
  776. Vector2I offset = getTextOffset();
  777. gGUIManager().getInputCaretTool()->updateText(this, textDesc);
  778. gGUIManager().getInputSelectionTool()->updateText(this, textDesc);
  779. }
  780. void GUIInputBox::deleteSelectedText()
  781. {
  782. UINT32 selStart = gGUIManager().getInputSelectionTool()->getSelectionStart();
  783. UINT32 selEnd = gGUIManager().getInputSelectionTool()->getSelectionEnd();
  784. bool filterOkay = true;
  785. if(mFilter != nullptr)
  786. {
  787. WString newText = mText;
  788. newText.erase(newText.begin() + selStart, newText.begin() + selEnd);
  789. filterOkay = mFilter(newText);
  790. }
  791. if(filterOkay)
  792. {
  793. mText.erase(mText.begin() + selStart, mText.begin() + selEnd);
  794. TEXT_SPRITE_DESC textDesc = getTextDesc();
  795. Vector2I offset = getTextOffset();
  796. gGUIManager().getInputCaretTool()->updateText(this, textDesc);
  797. gGUIManager().getInputSelectionTool()->updateText(this, textDesc);
  798. if(selStart > 0)
  799. {
  800. UINT32 newCaretPos = selStart - 1;
  801. gGUIManager().getInputCaretTool()->moveCaretToChar(newCaretPos, CARET_AFTER);
  802. }
  803. else
  804. {
  805. gGUIManager().getInputCaretTool()->moveCaretToChar(0, CARET_BEFORE);
  806. }
  807. scrollTextToCaret();
  808. if(!onValueChanged.empty())
  809. onValueChanged(mText);
  810. }
  811. clearSelection();
  812. }
  813. WString GUIInputBox::getSelectedText()
  814. {
  815. UINT32 selStart = gGUIManager().getInputSelectionTool()->getSelectionStart();
  816. UINT32 selEnd = gGUIManager().getInputSelectionTool()->getSelectionEnd();
  817. return mText.substr(selStart, selEnd - selStart);
  818. mText.erase(mText.begin() + selStart, mText.begin() + gGUIManager().getInputSelectionTool()->getSelectionEnd());
  819. }
  820. Vector2I GUIInputBox::getTextOffset() const
  821. {
  822. RectI textBounds = getContentBounds();
  823. return Vector2I(textBounds.x, textBounds.y) + mTextOffset;
  824. }
  825. RectI GUIInputBox::getTextClipRect() const
  826. {
  827. RectI contentClipRect = getContentClipRect();
  828. return RectI(contentClipRect.x - mTextOffset.x, contentClipRect.y - mTextOffset.y, contentClipRect.width, contentClipRect.height);
  829. }
  830. TEXT_SPRITE_DESC GUIInputBox::getTextDesc() const
  831. {
  832. TEXT_SPRITE_DESC textDesc;
  833. textDesc.text = mText;
  834. textDesc.font = _getStyle()->font;
  835. textDesc.fontSize = _getStyle()->fontSize;
  836. RectI textBounds = getContentBounds();
  837. textDesc.width = textBounds.width;
  838. textDesc.height = textBounds.height;
  839. textDesc.horzAlign = _getStyle()->textHorzAlign;
  840. textDesc.vertAlign = _getStyle()->textVertAlign;
  841. textDesc.wordWrap = mIsMultiline;
  842. return textDesc;
  843. }
  844. const HSpriteTexture& GUIInputBox::getActiveTexture() const
  845. {
  846. switch(mState)
  847. {
  848. case State::Focused:
  849. return _getStyle()->focused.texture;
  850. case State::Hover:
  851. return _getStyle()->hover.texture;
  852. case State::Normal:
  853. return _getStyle()->normal.texture;
  854. }
  855. return _getStyle()->normal.texture;
  856. }
  857. GUIContextMenu* GUIInputBox::getContextMenu() const
  858. {
  859. static bool initialized = false;
  860. static GUIContextMenu mContextMenu;
  861. if(!initialized)
  862. {
  863. mContextMenu.addMenuItem(L"Cut", std::bind(&GUIInputBox::cutText, const_cast<GUIInputBox*>(this)));
  864. mContextMenu.addMenuItem(L"Copy", std::bind(&GUIInputBox::copyText, const_cast<GUIInputBox*>(this)));
  865. mContextMenu.addMenuItem(L"Paste", std::bind(&GUIInputBox::pasteText, const_cast<GUIInputBox*>(this)));
  866. mContextMenu.setLocalizedName(L"Cut", HString(L"Cut"));
  867. mContextMenu.setLocalizedName(L"Copy", HString(L"Copy"));
  868. mContextMenu.setLocalizedName(L"Paste", HString(L"Paste"));
  869. initialized = true;
  870. }
  871. return &mContextMenu;
  872. }
  873. void GUIInputBox::cutText()
  874. {
  875. copyText();
  876. deleteSelectedText();
  877. }
  878. void GUIInputBox::copyText()
  879. {
  880. Platform::copyToClipboard(getSelectedText());
  881. }
  882. void GUIInputBox::pasteText()
  883. {
  884. WString textInClipboard = Platform::copyFromClipboard();
  885. UINT32 charIdx = gGUIManager().getInputCaretTool()->getCharIdxAtCaretPos();
  886. bool filterOkay = true;
  887. if(mFilter != nullptr)
  888. {
  889. WString newText = mText;
  890. newText.insert(newText.begin() + charIdx, textInClipboard.begin(), textInClipboard.end());
  891. filterOkay = mFilter(newText);
  892. }
  893. if(filterOkay)
  894. {
  895. insertString(charIdx, textInClipboard);
  896. if(textInClipboard.size() > 0)
  897. gGUIManager().getInputCaretTool()->moveCaretToChar(charIdx + ((UINT32)textInClipboard.size() - 1), CARET_AFTER);
  898. scrollTextToCaret();
  899. markContentAsDirty();
  900. if(!onValueChanged.empty())
  901. onValueChanged(mText);
  902. }
  903. }
  904. }