BsGUIInputBox.cpp 33 KB

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