Просмотр исходного кода

Fixing caret/selection so it work with word wrap properly

Marko Pintera 12 лет назад
Родитель
Сommit
d68636c4da

+ 18 - 6
BansheeEngine/Include/BsTextSprite.h

@@ -37,12 +37,24 @@ namespace BansheeEngine
 		bool wordWrap;
 	};
 
-	struct SpriteLineDesc
+	class BS_EXPORT SpriteLineDesc
 	{
-		CM::UINT32 startChar;
-		CM::UINT32 endChar;
-		CM::UINT32 lineHeight;
-		CM::INT32 lineYStart;
+	public:
+		SpriteLineDesc(CM::UINT32 startChar, CM::UINT32 endChar, CM::UINT32 lineHeight, CM::INT32 lineYStart, bool includesNewline);
+
+		CM::UINT32 getEndChar(bool includeNewline = true) const;
+		CM::UINT32 getStartChar() const { return mStartChar; }
+		CM::UINT32 getLineHeight() const { return mLineHeight; }
+		CM::INT32 getLineYStart() const { return mLineYStart; }
+		bool isNewline(CM::UINT32 charIdx) const;
+		bool hasNewlineChar() const { return mIncludesNewline; }
+
+	private:
+		CM::UINT32 mStartChar;
+		CM::UINT32 mEndChar;
+		CM::UINT32 mLineHeight;
+		CM::INT32 mLineYStart;
+		bool mIncludesNewline;
 	};
 
 	class BS_EXPORT TextSprite : public Sprite
@@ -54,7 +66,7 @@ namespace BansheeEngine
 
 		CM::UINT32 getNumLines() const { return (CM::UINT32)mLineDescs.size(); }
 		const SpriteLineDesc& getLineDesc(CM::UINT32 lineIdx) const { return mLineDescs.at(lineIdx); }
-		CM::UINT32 getLineForChar(CM::UINT32 charIdx) const;
+		CM::UINT32 getLineForChar(CM::UINT32 charIdx, bool newlineCountsOnNextLine = false) const;
 		CM::Rect getCharRect(CM::UINT32 charIdx) const;
 		CM::INT32 getCharIdxAtPos(const CM::Int2& pos) const;
 

+ 19 - 41
BansheeEngine/Source/BsGUIInputBox.cpp

@@ -818,13 +818,7 @@ namespace BansheeEngine
 		if(charIdx > 0)
 			charIdx--;
 
-		UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
-		// Newline chars should count on the second line, but that not how the sprite reports them, so fix that
-		if(lineIdx < (mTextSprite->getNumLines() - 1))
-		{
-			if(charIdx == (mTextSprite->getLineDesc(lineIdx).endChar - 1)) 
-				lineIdx++;
-		}
+		UINT32 lineIdx = mTextSprite->getLineForChar(charIdx, true);
 
 		if(lineIdx == 0)
 		{
@@ -859,13 +853,7 @@ namespace BansheeEngine
 		if(charIdx > 0)
 			charIdx--;
 
-		UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
-		// Newline chars should count on the second line, but that not how the sprite reports them, so fix that
-		if(lineIdx < (mTextSprite->getNumLines() - 1))
-		{
-			if(charIdx == (mTextSprite->getLineDesc(lineIdx).endChar - 1)) 
-				lineIdx++;
-		}
+		UINT32 lineIdx = mTextSprite->getLineForChar(charIdx, true);
 
 		if(lineIdx == (mTextSprite->getNumLines() - 1))
 		{
@@ -905,16 +893,7 @@ namespace BansheeEngine
 
 		UINT32 endLine = startLine;
 		if(mSelectionEnd > 0)
-		{
-			endLine = mTextSprite->getLineForChar(mSelectionEnd - 1);
-
-			// Newline chars should count on the second line, but that not how the sprite reports them, so fix that
-			if(endLine < (mTextSprite->getNumLines() - 1))
-			{
-				if((mSelectionEnd - 1) == (mTextSprite->getLineDesc(endLine).endChar - 1)) 
-					endLine++;
-			}
-		}
+			endLine = mTextSprite->getLineForChar(mSelectionEnd - 1, true);
 
 		{
 			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(startLine);
@@ -924,10 +903,9 @@ namespace BansheeEngine
 			UINT32 endCharIdx = mSelectionEnd - 1;
 			if(startLine != endLine)
 			{
-				endCharIdx = lineDesc.endChar - 1;
-
-				if(startLine != (mTextSprite->getNumLines() - 1) && endCharIdx > 0) // Ignore newline char
-					endCharIdx -= 1;
+				endCharIdx = lineDesc.getEndChar(false);
+				if(endCharIdx > 0)
+					endCharIdx = endCharIdx - 1;
 			}
 
 			if(!isNewlineChar(startCharIdx) && !isNewlineChar(endCharIdx))
@@ -937,8 +915,8 @@ namespace BansheeEngine
 
 				Rect selectionRect;
 				selectionRect.x = startChar.x;
-				selectionRect.y = lineDesc.lineYStart;
-				selectionRect.height = lineDesc.lineHeight;
+				selectionRect.y = lineDesc.getLineYStart();
+				selectionRect.height = lineDesc.getLineHeight();
 				selectionRect.width = (endChar.x + endChar.width) - startChar.x;
 
 				selectionRects.push_back(selectionRect);
@@ -948,20 +926,20 @@ namespace BansheeEngine
 		for(UINT32 i = startLine + 1; i < endLine; i++)
 		{
 			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
-			if(lineDesc.startChar == lineDesc.endChar || isNewlineChar(lineDesc.startChar))
+			if(lineDesc.getStartChar() == lineDesc.getEndChar() || isNewlineChar(lineDesc.getStartChar()))
 				continue;
 
-			UINT32 endCharIdx = lineDesc.endChar - 1;
-			if(endCharIdx > 0) // Ignore newline char
-				endCharIdx -= 1;
+			UINT32 endCharIdx = lineDesc.getEndChar(true);
+			if(endCharIdx > 0)
+				endCharIdx = endCharIdx - 1;
 
-			Rect startChar = mTextSprite->getCharRect(lineDesc.startChar);
+			Rect startChar = mTextSprite->getCharRect(lineDesc.getStartChar());
 			Rect endChar = mTextSprite->getCharRect(endCharIdx);
 
 			Rect selectionRect;
 			selectionRect.x = startChar.x;
-			selectionRect.y = lineDesc.lineYStart;
-			selectionRect.height = lineDesc.lineHeight;
+			selectionRect.y = lineDesc.getLineYStart();
+			selectionRect.height = lineDesc.getLineHeight();
 			selectionRect.width = (endChar.x + endChar.width) - startChar.x;
 
 			selectionRects.push_back(selectionRect);
@@ -971,19 +949,19 @@ namespace BansheeEngine
 		{
 			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(endLine);
 
-			if(lineDesc.startChar != lineDesc.endChar && !isNewlineChar(lineDesc.startChar))
+			if(lineDesc.getStartChar() != lineDesc.getEndChar() && !isNewlineChar(lineDesc.getStartChar()))
 			{
 				UINT32 endCharIdx = mSelectionEnd - 1;
 
 				if(!isNewlineChar(endCharIdx))
 				{
-					Rect startChar = mTextSprite->getCharRect(lineDesc.startChar);
+					Rect startChar = mTextSprite->getCharRect(lineDesc.getStartChar());
 					Rect endChar = mTextSprite->getCharRect(endCharIdx);
 
 					Rect selectionRect;
 					selectionRect.x = startChar.x;
-					selectionRect.y = lineDesc.lineYStart;
-					selectionRect.height = lineDesc.lineHeight;
+					selectionRect.y = lineDesc.getLineYStart();
+					selectionRect.height = lineDesc.getLineHeight();
 					selectionRect.width = (endChar.x + endChar.width) - startChar.x;
 
 					selectionRects.push_back(selectionRect);

+ 32 - 22
BansheeEngine/Source/BsGUIInputCaret.cpp

@@ -74,11 +74,10 @@ namespace BansheeEngine
 
 		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 char is a newline, I want that to count as being on the next line because that's
+		// how user sees it
+		if(desc.isNewline(charIdx))
+			lineIdx++;	
 
 		if(lineIdx == 0)
 			return;
@@ -97,11 +96,10 @@ namespace BansheeEngine
 
 		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 char is a newline, I want that to count as being on the next line because that's
+		// how user sees it
+		if(desc.isNewline(charIdx))
+			lineIdx++;					  
 
 		if(lineIdx == (mTextSprite->getNumLines() - 1))
 			return;
@@ -124,7 +122,15 @@ namespace BansheeEngine
 			if(pos.x <= xCenter)
 				moveCaretToChar(charIdx, CARET_BEFORE);
 			else
-				moveCaretToChar(charIdx, CARET_AFTER);
+			{
+				//UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
+				//const SpriteLineDesc& line = mTextSprite->getLineDesc(lineIdx);
+
+				//if(charIdx == (line.getEndChar(false) - 1)) // If last char on the line don't move beyond it
+				//	moveCaretToChar(charIdx, CARET_BEFORE);
+				//else
+					moveCaretToChar(charIdx, CARET_AFTER);
+			}
 		}
 		else
 		{
@@ -135,13 +141,13 @@ namespace BansheeEngine
 			{
 				const SpriteLineDesc& line = mTextSprite->getLineDesc(i);
 
-				if(pos.y >= line.lineYStart && pos.y < (line.lineYStart + (INT32)line.lineHeight))
+				if(pos.y >= line.getLineYStart() && pos.y < (line.getLineYStart() + (INT32)line.getLineHeight()))
 				{
 					mCaretPos = curPos;
 					return;
 				}
 
-				UINT32 numChars = line.endChar - line.startChar;
+				UINT32 numChars = line.getEndChar() - line.getStartChar();
 				curPos += numChars;
 			}
 
@@ -167,7 +173,7 @@ namespace BansheeEngine
 			// still place a caret on an empty line
 
 			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
-			UINT32 numChars = lineDesc.endChar - lineDesc.startChar;
+			UINT32 numChars = lineDesc.getEndChar() - lineDesc.getStartChar();
 			if(charIdx > (curCharIdx + numChars))
 			{
 				curCharIdx += numChars;
@@ -202,14 +208,14 @@ namespace BansheeEngine
 
 			if(curPos == mCaretPos)
 			{
-				return lineDesc.startChar;
+				return lineDesc.getStartChar();
 			}
 
 			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;
+			UINT32 numChars = lineDesc.getEndChar() - lineDesc.getStartChar();
 			if(mCaretPos > (curPos + numChars))
 			{
 				curCharIdx += numChars;
@@ -237,11 +243,15 @@ namespace BansheeEngine
 				if(mCaretPos == curPos)
 				{
 					// Caret is on line start
-					return Int2(offset.x, mTextSprite->getLineDesc(i).lineYStart);
+					return Int2(offset.x, mTextSprite->getLineDesc(i).getLineYStart());
 				}
 
+				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;
+				UINT32 numChars = lineDesc.getEndChar() - lineDesc.getStartChar();
 				curPos += numChars;
 			}
 
@@ -253,7 +263,7 @@ namespace BansheeEngine
 
 			Rect charRect = mTextSprite->getCharRect(charIdx);
 			UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
-			UINT32 yOffset = mTextSprite->getLineDesc(lineIdx).lineYStart;
+			UINT32 yOffset = mTextSprite->getLineDesc(lineIdx).getLineYStart();
 
 			return Int2(charRect.x + charRect.width + 1, yOffset);
 		}
@@ -272,7 +282,7 @@ namespace BansheeEngine
 		if(charIdx < (UINT32)mTextDesc.text.size())
 		{
 			UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
-			return mTextSprite->getLineDesc(lineIdx).lineHeight;
+			return mTextSprite->getLineDesc(lineIdx).getLineHeight();
 		}
 		else
 		{
@@ -307,7 +317,7 @@ namespace BansheeEngine
 				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;
+			UINT32 numChars = lineDesc.getEndChar() - lineDesc.getStartChar() - 1;
 			curPos += numChars;
 		}
 
@@ -329,7 +339,7 @@ namespace BansheeEngine
 				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;
+			UINT32 numChars = lineDesc.getEndChar() - lineDesc.getStartChar();
 			maxPos += numChars;
 		}
 

+ 63 - 22
BansheeEngine/Source/BsTextSprite.cpp

@@ -135,18 +135,19 @@ namespace BansheeEngine
 		UINT32 curLineIdx = 0;
 		for(auto& line : lines)
 		{
-			UINT32 newlineChar = (curLineIdx == ((UINT32)lines.size() - 1)) ? 0 : 1;
+			// Line has a newline char only if it wasn't created by word wrap and it isn't the last line
+			bool hasNewline = line.hasNewlineChar() && (curLineIdx != ((UINT32)lines.size() - 1));
 
-			SpriteLineDesc lineDesc;
-			lineDesc.startChar = curCharIdx;
-			lineDesc.endChar = curCharIdx + line.getNumChars() + newlineChar;
-			lineDesc.lineHeight = line.getYOffset();
-			lineDesc.lineYStart = alignmentOffsets[curLineIdx].y + desc.offset.y;
+			UINT32 startChar = curCharIdx;
+			UINT32 endChar = curCharIdx + line.getNumChars() + (hasNewline ? 1 : 0);
+			UINT32 lineHeight = line.getYOffset();
+			INT32 lineYStart = alignmentOffsets[curLineIdx].y + desc.offset.y;
 
+			SpriteLineDesc lineDesc(startChar, endChar, lineHeight, lineYStart, hasNewline);
 			mLineDescs.push_back(lineDesc);
 
-			curCharIdx = lineDesc.endChar;
-			cachedLineY += lineDesc.lineHeight;
+			curCharIdx = lineDesc.getEndChar();
+			cachedLineY += lineDesc.getLineHeight();
 			curLineIdx++;
 		}
 
@@ -159,10 +160,13 @@ namespace BansheeEngine
 
 		// If char is newline we don't have any geometry to return
 		const SpriteLineDesc& lineDesc = getLineDesc(lineIdx);
-		if(lineIdx != (getNumLines() - 1) && (lineDesc.endChar - 1) == charIdx)
+		if(lineDesc.isNewline(charIdx))
 			return Rect();
 
-		UINT32 numNewlineChars = lineIdx;
+		UINT32 numNewlineChars = 0;
+		for(UINT32 i = 0; i < lineIdx; i++)
+			numNewlineChars += (getLineDesc(i).hasNewlineChar() ? 1 : 0);
+
 		UINT32 quadIdx = charIdx - numNewlineChars;
 
 		UINT32 curQuadIdx = 0;
@@ -193,26 +197,26 @@ namespace BansheeEngine
 
 		UINT32 lineStartChar = 0;
 		UINT32 lineEndChar = 0;
-		UINT32 newlineChars = 0;
+		UINT32 numNewlineChars = 0;
 		UINT32 lineIdx = 0;
 		for(auto& line : mLineDescs)
 		{
-			if(pos.y >= line.lineYStart && pos.y < (line.lineYStart + (INT32)line.lineHeight))
+			if(pos.y >= line.getLineYStart() && pos.y < (line.getLineYStart() + (INT32)line.getLineHeight()))
 			{
-				lineStartChar = line.startChar;
-				lineEndChar = line.endChar;
+				lineStartChar = line.getStartChar();
+				lineEndChar = line.getEndChar(false);
 				break;
 			}
 
-			newlineChars++; // Newline chars count in the startChar/endChar variables, but don't actually exist in the buffers
+			// Newline chars count in the startChar/endChar variables, but don't actually exist in the buffers
 			// so we need to filter them out
+			numNewlineChars += (line.hasNewlineChar() ? 1 : 0); 
+
 			lineIdx++;
 		}
 
-		bool hasNewlineChar = lineIdx < (mLineDescs.size() - 1);
-
-		UINT32 lineStartQuad = lineStartChar - newlineChars;
-		UINT32 lineEndQuad = lineEndChar - newlineChars - (hasNewlineChar ? 1 : 0);
+		UINT32 lineStartQuad = lineStartChar - numNewlineChars;
+		UINT32 lineEndQuad = lineEndChar - numNewlineChars;
 
 		float nearestDist = std::numeric_limits<float>::max();
 		UINT32 nearestChar = 0;
@@ -239,7 +243,7 @@ namespace BansheeEngine
 				float dist = Math::Abs(centerX - vecPos.x);
 				if(dist < nearestDist)
 				{
-					nearestChar = quadIdx + newlineChars;
+					nearestChar = quadIdx + numNewlineChars;
 					nearestDist = dist;
 					foundChar = true;
 				}
@@ -254,13 +258,16 @@ namespace BansheeEngine
 		return nearestChar;
 	}
 
-	CM::UINT32 TextSprite::getLineForChar(CM::UINT32 charIdx) const
+	CM::UINT32 TextSprite::getLineForChar(CM::UINT32 charIdx, bool newlineCountsOnNextLine) const
 	{
 		UINT32 idx = 0;
 		for(auto& line : mLineDescs)
 		{
-			if(charIdx >= line.startChar && charIdx < line.endChar)
+			if(charIdx >= line.getStartChar() && charIdx < line.getEndChar())
 			{
+				if(line.isNewline(charIdx) && newlineCountsOnNextLine)
+					return idx + 1; // Incrementing is safe because next line must exist, since we just found a newline char
+
 				return idx;
 			}
 
@@ -318,4 +325,38 @@ namespace BansheeEngine
 
 		return lineOffsets;
 	}
+
+	SpriteLineDesc::SpriteLineDesc(CM::UINT32 startChar, CM::UINT32 endChar, CM::UINT32 lineHeight, CM::INT32 lineYStart, bool includesNewline)
+		:mStartChar(startChar), mEndChar(endChar), mLineHeight(lineHeight), mLineYStart(lineYStart), mIncludesNewline(includesNewline)
+	{
+
+	}
+
+	UINT32 SpriteLineDesc::getEndChar(bool includeNewline) const
+	{
+		if(mIncludesNewline)
+		{
+			if(includeNewline)
+				return mEndChar;
+			else
+			{
+				if(mEndChar > 0)
+					return mEndChar - 1;
+				else
+					return mStartChar;
+			}
+		}
+		else
+			return mEndChar;
+	}
+
+	bool SpriteLineDesc::isNewline(UINT32 charIdx) const
+	{
+		if(mIncludesNewline)
+		{
+			return (mEndChar - 1) == charIdx;
+		}
+		else
+			return false;
+	}
 }

+ 9 - 0
CamelotCore/Include/CmTextUtility.h

@@ -79,6 +79,12 @@ namespace CamelotFramework
 			 * @brief	Returns the total number of characters on this line.
 			 */
 			UINT32 getNumChars() const;
+
+			/**
+			 * @brief	Query if this line was created explicitly due to a newline character.
+			 * 			As opposed to a line that was created because a word couldn't fit on the previous line.
+			 */
+			bool hasNewlineChar() const { return mHasNewline; }
 		private:
 			friend class TextUtility;
 
@@ -89,11 +95,14 @@ namespace CamelotFramework
 			UINT32 mSpaceWidth;
 			Vector<TextWord>::type mWords;
 			TextWord* mLastWord;
+			bool mHasNewline;
 
 			void add(const CHAR_DESC& charDesc);
 			void addSpace();
 			void addWord(const TextWord& word);
 
+			void finalize(bool hasNewlineChar);
+
 			TextWord removeLastWord();
 
 			void calculateBounds();

+ 13 - 15
CamelotCore/Source/CmTextUtility.cpp

@@ -73,7 +73,8 @@ namespace CamelotFramework
 	}
 
 	TextUtility::TextLine::TextLine(UINT32 baselineOffset, UINT32 lineHeight, UINT32 spaceWidth)
-		:mWidth(0), mHeight(0), mLastWord(nullptr), mBaselineOffset(baselineOffset), mLineHeight(lineHeight), mSpaceWidth(spaceWidth)
+		:mWidth(0), mHeight(0), mLastWord(nullptr), mBaselineOffset(baselineOffset), 
+		mLineHeight(lineHeight), mSpaceWidth(spaceWidth)
 	{
 
 	}
@@ -82,6 +83,11 @@ namespace CamelotFramework
 	{
 	}
 
+	void TextUtility::TextLine::finalize(bool hasNewlineChar)
+	{
+		mHasNewline = hasNewlineChar;
+	}
+
 	void TextUtility::TextLine::add(const CHAR_DESC& charDesc)
 	{
 		if(mLastWord == nullptr)
@@ -358,6 +364,7 @@ namespace CamelotFramework
 
 			if(text[charIdx] == '\n')
 			{
+				curLine->finalize(true);
 				textData->mLines.push_back(TextLine(fontData->fontDesc.baselineOffset, fontData->fontDesc.lineHeight, fontData->fontDesc.spaceWidth));
 				curLine = &textData->mLines.back();
 
@@ -383,34 +390,25 @@ namespace CamelotFramework
 				if(wordWrap)
 				{
 					TextWord lastWord = curLine->removeLastWord();
-					bool moveLastWord = true;
-					if(lastWord.isSpacer())
-					{
-						curLine->addWord(lastWord); // Spaces can stay on previous line even if they don't technically fit
-						moveLastWord = false;
-					}
 
-					if(lastWord.getWidth() > width) // If the word doesn't fit on the next line, don't bother moving it
-					{
-						curLine->addWord(lastWord);
-						moveLastWord = false;
-					}
-					else
+					if(lastWord.getWidth() <= width) // If the world fits, attempt to add it to a new line
 					{
+						curLine->finalize(false);
 						textData->mLines.push_back(TextLine(fontData->fontDesc.baselineOffset, fontData->fontDesc.lineHeight, fontData->fontDesc.spaceWidth));
 						curLine = &textData->mLines.back();
 
 						curHeight += fontData->fontDesc.lineHeight;
 					}
 
-					if(moveLastWord)
-						curLine->addWord(lastWord);
+					curLine->addWord(lastWord);
 				}
 			}
 
 			charIdx++;
 		}
 
+		curLine->finalize(true);
+
 		return textData;
 	}
 

+ 7 - 1
TODO.txt

@@ -23,7 +23,9 @@ IMMEDIATE:
 	- SpriteTexture keeps a static reference to DUmmyTexture which I need to release before shutdown
 
 TextBox needed elements:
- - Key-repeat? Pressing left/right/up/down arrows doesn't repeat the keys
+ - IMPORTANT: Word wrap won't work with my current approach. In a lot of places I assume lines 
+    end with a newline char but in word wrap case a lot of them wont.
+ - Key-repeat? Pressing left/right/up/down arrows doesn't repeat the keys (also delete/backspace)
  - Drag mouse to update selection
  - Input caret positioning ignores kerning which means the caret is sometimes 
    in the middle of another char. Try typing "fa" and moving caret in front of "f".
@@ -36,6 +38,10 @@ TextBox needed elements:
  - LATER
   - TAB between input elements
   - Context menu with copy/cut/paste
+  - Sprites. Perform clipping when fillbuffer is called? 
+    This way I can change clip rect without recreating the entire sprite. 
+	Which is important for text, especially when I'll be scrolling it. 
+	Plus its important for all other elements that will be inside scroll areas.
 
 GUIDragManager
  - GUI system sends startdrag/enddrag/drag events to all elements