BsGUIInputTool.cpp 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. #include "BsGUIInputTool.h"
  2. #include "CmMath.h"
  3. #include "CmVector2.h"
  4. #include "CmFont.h"
  5. using namespace CamelotFramework;
  6. namespace BansheeEngine
  7. {
  8. GUIInputTool::GUIInputTool(const TEXT_SPRITE_DESC& textDesc, const Int2& offset, const Int2 clipOffset)
  9. :mTextDesc(textDesc), mTextOffset(offset), mClipOffset(clipOffset), mQuads(nullptr), mNumQuads(0)
  10. {
  11. updateText(textDesc, offset, clipOffset);
  12. }
  13. GUIInputTool::~GUIInputTool()
  14. { }
  15. void GUIInputTool::updateText(const TEXT_SPRITE_DESC& textDesc, const Int2& offset, const Int2 clipOffset)
  16. {
  17. mTextDesc = textDesc;
  18. mTextOffset = offset;
  19. mClipOffset = clipOffset;
  20. mLineDescs.clear();
  21. std::shared_ptr<TextUtility::TextData> textData = TextUtility::getTextData(mTextDesc.text, mTextDesc.font, mTextDesc.fontSize,
  22. mTextDesc.width, mTextDesc.height, mTextDesc.wordWrap);
  23. if(textData == nullptr)
  24. return;
  25. const CM::Vector<TextUtility::TextLine>::type& lines = textData->getLines();
  26. const CM::Vector<UINT32>::type& quadsPerPage = textData->getNumQuadsPerPage();
  27. mNumQuads = 0;
  28. for(auto& numQuads : quadsPerPage)
  29. mNumQuads += numQuads;
  30. if(mQuads != nullptr)
  31. cm_delete<ScratchAlloc>(mQuads);
  32. mQuads = cm_newN<Vector2, ScratchAlloc>(mNumQuads * 4);
  33. TextSprite::genTextQuads(*textData, mTextDesc.width, mTextDesc.height, mTextDesc.horzAlign, mTextDesc.vertAlign, mTextDesc.anchor,
  34. mQuads, nullptr, nullptr, mNumQuads);
  35. UINT32 numVerts = mNumQuads * 4;
  36. Vector2 vecOffset((float)mTextOffset.x, (float)mTextOffset.y);
  37. for(UINT32 i = 0; i < numVerts; i++)
  38. mQuads[i] = mQuads[i] + vecOffset;
  39. // Store cached line data
  40. UINT32 curCharIdx = 0;
  41. UINT32 cachedLineY = 0;
  42. UINT32 curLineIdx = 0;
  43. Vector<Int2>::type alignmentOffsets = TextSprite::getAlignmentOffsets(lines, mTextDesc.width,
  44. mTextDesc.height, mTextDesc.horzAlign, mTextDesc.vertAlign);
  45. for(auto& line : lines)
  46. {
  47. // Line has a newline char only if it wasn't created by word wrap and it isn't the last line
  48. bool hasNewline = line.hasNewlineChar() && (curLineIdx != ((UINT32)lines.size() - 1));
  49. UINT32 startChar = curCharIdx;
  50. UINT32 endChar = curCharIdx + line.getNumChars() + (hasNewline ? 1 : 0);
  51. UINT32 lineHeight = line.getYOffset();
  52. INT32 lineYStart = alignmentOffsets[curLineIdx].y + mTextOffset.y;
  53. GUIInputLineDesc lineDesc(startChar, endChar, lineHeight, lineYStart, hasNewline);
  54. mLineDescs.push_back(lineDesc);
  55. curCharIdx = lineDesc.getEndChar();
  56. cachedLineY += lineDesc.getLineHeight();
  57. curLineIdx++;
  58. }
  59. }
  60. CM::Rect GUIInputTool::getCharRect(UINT32 charIdx) const
  61. {
  62. UINT32 lineIdx = getLineForChar(charIdx);
  63. // If char is newline we don't have any geometry to return
  64. const GUIInputLineDesc& lineDesc = getLineDesc(lineIdx);
  65. if(lineDesc.isNewline(charIdx))
  66. return Rect();
  67. UINT32 numNewlineChars = 0;
  68. for(UINT32 i = 0; i < lineIdx; i++)
  69. numNewlineChars += (getLineDesc(i).hasNewlineChar() ? 1 : 0);
  70. UINT32 quadIdx = charIdx - numNewlineChars;
  71. if(quadIdx >= 0 && quadIdx < mNumQuads)
  72. {
  73. UINT32 vertIdx = quadIdx * 4;
  74. Rect charRect;
  75. charRect.x = Math::RoundToInt(mQuads[vertIdx + 0].x);
  76. charRect.y = Math::RoundToInt(mQuads[vertIdx + 0].y);
  77. charRect.width = Math::RoundToInt(mQuads[vertIdx + 3].x - charRect.x);
  78. charRect.height = Math::RoundToInt(mQuads[vertIdx + 3].y - charRect.y);
  79. return charRect;
  80. }
  81. CM_EXCEPT(InternalErrorException, "Invalid character index: " + toString(charIdx));
  82. }
  83. INT32 GUIInputTool::getCharIdxAtPos(const Int2& pos) const
  84. {
  85. Vector2 vecPos((float)pos.x, (float)pos.y);
  86. UINT32 lineStartChar = 0;
  87. UINT32 lineEndChar = 0;
  88. UINT32 numNewlineChars = 0;
  89. UINT32 lineIdx = 0;
  90. for(auto& line : mLineDescs)
  91. {
  92. if(pos.y >= line.getLineYStart() && pos.y < (line.getLineYStart() + (INT32)line.getLineHeight()))
  93. {
  94. lineStartChar = line.getStartChar();
  95. lineEndChar = line.getEndChar(false);
  96. break;
  97. }
  98. // Newline chars count in the startChar/endChar variables, but don't actually exist in the buffers
  99. // so we need to filter them out
  100. numNewlineChars += (line.hasNewlineChar() ? 1 : 0);
  101. lineIdx++;
  102. }
  103. UINT32 lineStartQuad = lineStartChar - numNewlineChars;
  104. UINT32 lineEndQuad = lineEndChar - numNewlineChars;
  105. float nearestDist = std::numeric_limits<float>::max();
  106. UINT32 nearestChar = 0;
  107. bool foundChar = false;
  108. for(UINT32 i = lineStartQuad; i < lineEndQuad; i++)
  109. {
  110. UINT32 curVert = i * 4;
  111. float centerX = mQuads[curVert + 0].x + mQuads[curVert + 1].x;
  112. centerX *= 0.5f;
  113. float dist = Math::Abs(centerX - vecPos.x);
  114. if(dist < nearestDist)
  115. {
  116. nearestChar = i + numNewlineChars;
  117. nearestDist = dist;
  118. foundChar = true;
  119. }
  120. }
  121. if(!foundChar)
  122. return -1;
  123. return nearestChar;
  124. }
  125. CM::UINT32 GUIInputTool::getLineForChar(CM::UINT32 charIdx, bool newlineCountsOnNextLine) const
  126. {
  127. UINT32 idx = 0;
  128. for(auto& line : mLineDescs)
  129. {
  130. if(charIdx >= line.getStartChar() && charIdx < line.getEndChar())
  131. {
  132. if(line.isNewline(charIdx) && newlineCountsOnNextLine)
  133. return idx + 1; // Incrementing is safe because next line must exist, since we just found a newline char
  134. return idx;
  135. }
  136. idx++;
  137. }
  138. CM_EXCEPT(InternalErrorException, "Invalid character index: " + toString(charIdx));
  139. }
  140. GUIInputLineDesc::GUIInputLineDesc(CM::UINT32 startChar, CM::UINT32 endChar, CM::UINT32 lineHeight, CM::INT32 lineYStart, bool includesNewline)
  141. :mStartChar(startChar), mEndChar(endChar), mLineHeight(lineHeight), mLineYStart(lineYStart), mIncludesNewline(includesNewline)
  142. {
  143. }
  144. UINT32 GUIInputLineDesc::getEndChar(bool includeNewline) const
  145. {
  146. if(mIncludesNewline)
  147. {
  148. if(includeNewline)
  149. return mEndChar;
  150. else
  151. {
  152. if(mEndChar > 0)
  153. return mEndChar - 1;
  154. else
  155. return mStartChar;
  156. }
  157. }
  158. else
  159. return mEndChar;
  160. }
  161. bool GUIInputLineDesc::isNewline(UINT32 charIdx) const
  162. {
  163. if(mIncludesNewline)
  164. {
  165. return (mEndChar - 1) == charIdx;
  166. }
  167. else
  168. return false;
  169. }
  170. }