| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- #include "BsGUIInputCaret.h"
- #include "BsGUIManager.h"
- #include "BsImageSprite.h"
- #include "CmFont.h"
- using namespace CamelotFramework;
- namespace BansheeEngine
- {
- GUIInputCaret::GUIInputCaret(const TEXT_SPRITE_DESC& textDesc)
- :mCaretPos(0), mTextDesc(textDesc)
- {
- mCaretSprite = cm_new<ImageSprite, PoolAlloc>();
- mTextSprite = cm_new<TextSprite, PoolAlloc>();
- mTextSprite->update(mTextDesc);
- }
- GUIInputCaret::~GUIInputCaret()
- {
- cm_delete<PoolAlloc>(mCaretSprite);
- cm_delete<PoolAlloc>(mTextSprite);
- }
- void GUIInputCaret::updateText(const TEXT_SPRITE_DESC& textDesc)
- {
- mTextDesc = textDesc;
- mTextDesc.clipRect = Rect(0, 0, 0, 0); // No clipping otherwise we don't know position of chars
- // outside of the element, which is something we need when moving the cursor
- mTextSprite->update(mTextDesc);
- }
- void GUIInputCaret::updateSprite(const CM::Int2& offset)
- {
- IMAGE_SPRITE_DESC mCaretDesc;
- mCaretDesc.offset = getCaretPosition(mTextDesc.offset);
- mCaretDesc.width = 1;
- mCaretDesc.height = getCaretHeight();
- mCaretDesc.clipRect = Rect(-mCaretDesc.offset.x + mTextDesc.offset.x - offset.x, -mCaretDesc.offset.y + mTextDesc.offset.y - offset.y,
- mTextDesc.width, mTextDesc.height);
- mCaretDesc.texture = GUIManager::instance().getCaretTexture();
- mCaretSprite->update(mCaretDesc);
- }
- void GUIInputCaret::moveCaretToStart()
- {
- mCaretPos = 0;
- }
- void GUIInputCaret::moveCaretToEnd()
- {
- mCaretPos = getMaxCaretPos();
- }
- void GUIInputCaret::moveCaretLeft()
- {
- mCaretPos = (UINT32)std::max(0, (INT32)mCaretPos - 1);
- }
- void GUIInputCaret::moveCaretRight()
- {
- UINT32 maxCaretPos = (UINT32)mTextDesc.text.size(); // One extra because beginning of first line has an extra "fake" char
- mCaretPos = std::min(maxCaretPos, mCaretPos + 1);
- }
- void GUIInputCaret::moveCaretUp()
- {
- UINT32 charIdx = getCharIdxAtCaretPos();
- if(charIdx > 0)
- charIdx -= 1;
- UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
- const SpriteLineDesc& desc = mTextSprite->getLineDesc(lineIdx);
- if(lineIdx != (mTextSprite->getNumLines() - 1)) // If not the last line
- {
- if(charIdx == (desc.endChar - 1)) // If char is a newline, I want that to count as being on the next line because that's
- lineIdx++; // how user sees it
- }
- if(lineIdx == 0)
- return;
- Int2 caretCoords = getCaretPosition(mTextDesc.offset);
- caretCoords.y -= getCaretHeight();
- moveCaretToPos(caretCoords);
- }
- void GUIInputCaret::moveCaretDown()
- {
- UINT32 charIdx = getCharIdxAtCaretPos();
- if(charIdx > 0)
- charIdx -= 1;
- UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
- const SpriteLineDesc& desc = mTextSprite->getLineDesc(lineIdx);
- if(lineIdx != (mTextSprite->getNumLines() - 1)) // If not the last line
- {
- if(charIdx == (desc.endChar - 1)) // If char is a newline, I want that to count as being on the next line because that's
- lineIdx++; // how user sees it
- }
- if(lineIdx == (mTextSprite->getNumLines() - 1))
- return;
- Int2 caretCoords = getCaretPosition(mTextDesc.offset);
- caretCoords.y += getCaretHeight();
- moveCaretToPos(caretCoords);
- }
- void GUIInputCaret::moveCaretToPos(const CM::Int2& pos)
- {
- INT32 charIdx = mTextSprite->getCharIdxAtPos(pos);
- if(charIdx != -1)
- {
- Rect charRect = mTextSprite->getCharRect(charIdx);
- float xCenter = charRect.x + charRect.width * 0.5f;
- if(pos.x <= xCenter)
- moveCaretToChar(charIdx, CARET_BEFORE);
- else
- moveCaretToChar(charIdx, CARET_AFTER);
- }
- else
- {
- UINT32 numLines = mTextSprite->getNumLines();
- UINT32 curPos = 0;
- for(UINT32 i = 0; i < numLines; i++)
- {
- const SpriteLineDesc& line = mTextSprite->getLineDesc(i);
- if(pos.y >= line.lineYStart && pos.y < (line.lineYStart + (INT32)line.lineHeight))
- {
- mCaretPos = curPos;
- return;
- }
- UINT32 numChars = line.endChar - line.startChar;
- curPos += numChars;
- }
- mCaretPos = curPos;
- }
- }
- void GUIInputCaret::moveCaretToChar(UINT32 charIdx, CaretPos caretPos)
- {
- if(charIdx >= (UINT32)mTextDesc.text.size())
- {
- mCaretPos = 0;
- return;
- }
- UINT32 numLines = mTextSprite->getNumLines();
- UINT32 curPos = 0;
- UINT32 curCharIdx = 0;
- for(UINT32 i = 0; i < numLines; i++)
- {
- if(i == 0)
- curPos++; // Beginning of the line has a special caret position, primarily so we can
- // still place a caret on an empty line
- const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
- UINT32 numChars = lineDesc.endChar - lineDesc.startChar;
- if(charIdx > (curCharIdx + numChars))
- {
- curCharIdx += numChars;
- curPos += numChars;
- continue;
- }
- UINT32 diff = charIdx - curCharIdx;
- if(caretPos == CARET_BEFORE)
- curPos += diff - 1;
- else
- curPos += diff;
- break;
- }
- mCaretPos = curPos;
- }
- UINT32 GUIInputCaret::getCharIdxAtCaretPos() const
- {
- if(mTextDesc.text.size() == 0)
- return 0;
- UINT32 numLines = mTextSprite->getNumLines();
- UINT32 curPos = 0;
- UINT32 curCharIdx = 0;
- for(UINT32 i = 0; i < numLines; i++)
- {
- const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
- if(curPos == mCaretPos)
- {
- return lineDesc.startChar;
- }
- if(i == 0)
- curPos++; // Beginning of the line has a special caret position, primarily so we can
- // still place a caret on an empty line
- UINT32 numChars = lineDesc.endChar - lineDesc.startChar;
- if(mCaretPos > (curPos + numChars))
- {
- curCharIdx += numChars;
- curPos += numChars;
- continue;
- }
- UINT32 diff = mCaretPos - curPos;
- curCharIdx += diff + 1; // +1 because we want the caret to reference the char in front of it on most cases
- return curCharIdx;
- }
- return 0;
- }
- Int2 GUIInputCaret::getCaretPosition(const CM::Int2& offset) const
- {
- if(mTextDesc.text.size() > 0)
- {
- UINT32 curPos = 0;
- UINT32 numLines = mTextSprite->getNumLines();
- for(UINT32 i = 0; i < numLines; i++)
- {
- if(mCaretPos == curPos)
- {
- // Caret is on line start
- return Int2(offset.x, mTextSprite->getLineDesc(i).lineYStart);
- }
- const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
- UINT32 numChars = lineDesc.endChar - lineDesc.startChar;
- curPos += numChars;
- }
- UINT32 charIdx = getCharIdxAtCaretPos();
- if(charIdx > 0)
- charIdx -= 1;
- charIdx = std::min((UINT32)(mTextDesc.text.size() - 1), charIdx);
- Rect charRect = mTextSprite->getCharRect(charIdx);
- UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
- UINT32 yOffset = mTextSprite->getLineDesc(lineIdx).lineYStart;
- return Int2(charRect.x + charRect.width + 1, yOffset);
- }
- else
- {
- return offset;
- }
- }
- UINT32 GUIInputCaret::getCaretHeight() const
- {
- UINT32 charIdx = getCharIdxAtCaretPos();
- if(charIdx > 0)
- charIdx -= 1;
- if(charIdx < (UINT32)mTextDesc.text.size())
- {
- UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
- return mTextSprite->getLineDesc(lineIdx).lineHeight;
- }
- else
- {
- if(mTextDesc.font != nullptr)
- {
- UINT32 nearestSize = mTextDesc.font->getClosestAvailableSize(mTextDesc.fontSize);
- const FontData* fontData = mTextDesc.font->getFontDataForSize(nearestSize);
- if(fontData != nullptr)
- return fontData->fontDesc.lineHeight;
- }
- }
- return 0;
- }
- bool GUIInputCaret::isCaretAtNewline() const
- {
- if(mTextDesc.text.size() == 0)
- return true;
- UINT32 numLines = mTextSprite->getNumLines();
- UINT32 curPos = 0;
- for(UINT32 i = 0; i < numLines; i++)
- {
- const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
- if(curPos == mCaretPos)
- return true;
- if(i == 0)
- curPos++; // Beginning of the line has a special caret position, primarily so we can
- // still place a caret on an empty line
- UINT32 numChars = lineDesc.endChar - lineDesc.startChar - 1;
- curPos += numChars;
- }
- return false;
- }
- UINT32 GUIInputCaret::getMaxCaretPos() const
- {
- if(mTextDesc.text.size() == 0)
- return 0;
- UINT32 numLines = mTextSprite->getNumLines();
- UINT32 maxPos = 0;
- for(UINT32 i = 0; i < numLines; i++)
- {
- const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
- if(i == 0)
- maxPos++; // Beginning of the line has a special caret position, primarily so we can
- // still place a caret on an empty line
- UINT32 numChars = lineDesc.endChar - lineDesc.startChar;
- maxPos += numChars;
- }
- return maxPos - 1;
- }
- }
|