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

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;
 		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
 	class BS_EXPORT TextSprite : public Sprite
@@ -54,7 +66,7 @@ namespace BansheeEngine
 
 
 		CM::UINT32 getNumLines() const { return (CM::UINT32)mLineDescs.size(); }
 		CM::UINT32 getNumLines() const { return (CM::UINT32)mLineDescs.size(); }
 		const SpriteLineDesc& getLineDesc(CM::UINT32 lineIdx) const { return mLineDescs.at(lineIdx); }
 		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::Rect getCharRect(CM::UINT32 charIdx) const;
 		CM::INT32 getCharIdxAtPos(const CM::Int2& pos) const;
 		CM::INT32 getCharIdxAtPos(const CM::Int2& pos) const;
 
 

+ 19 - 41
BansheeEngine/Source/BsGUIInputBox.cpp

@@ -818,13 +818,7 @@ namespace BansheeEngine
 		if(charIdx > 0)
 		if(charIdx > 0)
 			charIdx--;
 			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)
 		if(lineIdx == 0)
 		{
 		{
@@ -859,13 +853,7 @@ namespace BansheeEngine
 		if(charIdx > 0)
 		if(charIdx > 0)
 			charIdx--;
 			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))
 		if(lineIdx == (mTextSprite->getNumLines() - 1))
 		{
 		{
@@ -905,16 +893,7 @@ namespace BansheeEngine
 
 
 		UINT32 endLine = startLine;
 		UINT32 endLine = startLine;
 		if(mSelectionEnd > 0)
 		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);
 			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(startLine);
@@ -924,10 +903,9 @@ namespace BansheeEngine
 			UINT32 endCharIdx = mSelectionEnd - 1;
 			UINT32 endCharIdx = mSelectionEnd - 1;
 			if(startLine != endLine)
 			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))
 			if(!isNewlineChar(startCharIdx) && !isNewlineChar(endCharIdx))
@@ -937,8 +915,8 @@ namespace BansheeEngine
 
 
 				Rect selectionRect;
 				Rect selectionRect;
 				selectionRect.x = startChar.x;
 				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;
 				selectionRect.width = (endChar.x + endChar.width) - startChar.x;
 
 
 				selectionRects.push_back(selectionRect);
 				selectionRects.push_back(selectionRect);
@@ -948,20 +926,20 @@ namespace BansheeEngine
 		for(UINT32 i = startLine + 1; i < endLine; i++)
 		for(UINT32 i = startLine + 1; i < endLine; i++)
 		{
 		{
 			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
 			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
-			if(lineDesc.startChar == lineDesc.endChar || isNewlineChar(lineDesc.startChar))
+			if(lineDesc.getStartChar() == lineDesc.getEndChar() || isNewlineChar(lineDesc.getStartChar()))
 				continue;
 				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 endChar = mTextSprite->getCharRect(endCharIdx);
 
 
 			Rect selectionRect;
 			Rect selectionRect;
 			selectionRect.x = startChar.x;
 			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;
 			selectionRect.width = (endChar.x + endChar.width) - startChar.x;
 
 
 			selectionRects.push_back(selectionRect);
 			selectionRects.push_back(selectionRect);
@@ -971,19 +949,19 @@ namespace BansheeEngine
 		{
 		{
 			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(endLine);
 			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;
 				UINT32 endCharIdx = mSelectionEnd - 1;
 
 
 				if(!isNewlineChar(endCharIdx))
 				if(!isNewlineChar(endCharIdx))
 				{
 				{
-					Rect startChar = mTextSprite->getCharRect(lineDesc.startChar);
+					Rect startChar = mTextSprite->getCharRect(lineDesc.getStartChar());
 					Rect endChar = mTextSprite->getCharRect(endCharIdx);
 					Rect endChar = mTextSprite->getCharRect(endCharIdx);
 
 
 					Rect selectionRect;
 					Rect selectionRect;
 					selectionRect.x = startChar.x;
 					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;
 					selectionRect.width = (endChar.x + endChar.width) - startChar.x;
 
 
 					selectionRects.push_back(selectionRect);
 					selectionRects.push_back(selectionRect);

+ 32 - 22
BansheeEngine/Source/BsGUIInputCaret.cpp

@@ -74,11 +74,10 @@ namespace BansheeEngine
 
 
 		UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
 		UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
 		const SpriteLineDesc& desc = mTextSprite->getLineDesc(lineIdx);
 		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)
 		if(lineIdx == 0)
 			return;
 			return;
@@ -97,11 +96,10 @@ namespace BansheeEngine
 
 
 		UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
 		UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
 		const SpriteLineDesc& desc = mTextSprite->getLineDesc(lineIdx);
 		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))
 		if(lineIdx == (mTextSprite->getNumLines() - 1))
 			return;
 			return;
@@ -124,7 +122,15 @@ namespace BansheeEngine
 			if(pos.x <= xCenter)
 			if(pos.x <= xCenter)
 				moveCaretToChar(charIdx, CARET_BEFORE);
 				moveCaretToChar(charIdx, CARET_BEFORE);
 			else
 			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
 		else
 		{
 		{
@@ -135,13 +141,13 @@ namespace BansheeEngine
 			{
 			{
 				const SpriteLineDesc& line = mTextSprite->getLineDesc(i);
 				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;
 					mCaretPos = curPos;
 					return;
 					return;
 				}
 				}
 
 
-				UINT32 numChars = line.endChar - line.startChar;
+				UINT32 numChars = line.getEndChar() - line.getStartChar();
 				curPos += numChars;
 				curPos += numChars;
 			}
 			}
 
 
@@ -167,7 +173,7 @@ namespace BansheeEngine
 			// still place a caret on an empty line
 			// still place a caret on an empty line
 
 
 			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
 			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
-			UINT32 numChars = lineDesc.endChar - lineDesc.startChar;
+			UINT32 numChars = lineDesc.getEndChar() - lineDesc.getStartChar();
 			if(charIdx > (curCharIdx + numChars))
 			if(charIdx > (curCharIdx + numChars))
 			{
 			{
 				curCharIdx += numChars;
 				curCharIdx += numChars;
@@ -202,14 +208,14 @@ namespace BansheeEngine
 
 
 			if(curPos == mCaretPos)
 			if(curPos == mCaretPos)
 			{
 			{
-				return lineDesc.startChar;
+				return lineDesc.getStartChar();
 			}
 			}
 
 
 			if(i == 0)
 			if(i == 0)
 				curPos++; // Beginning of the line has a special caret position, primarily so we can
 				curPos++; // Beginning of the line has a special caret position, primarily so we can
 			// still place a caret on an empty line
 			// still place a caret on an empty line
 
 
-			UINT32 numChars = lineDesc.endChar - lineDesc.startChar;
+			UINT32 numChars = lineDesc.getEndChar() - lineDesc.getStartChar();
 			if(mCaretPos > (curPos + numChars))
 			if(mCaretPos > (curPos + numChars))
 			{
 			{
 				curCharIdx += numChars;
 				curCharIdx += numChars;
@@ -237,11 +243,15 @@ namespace BansheeEngine
 				if(mCaretPos == curPos)
 				if(mCaretPos == curPos)
 				{
 				{
 					// Caret is on line start
 					// 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);
 				const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
-				UINT32 numChars = lineDesc.endChar - lineDesc.startChar;
+				UINT32 numChars = lineDesc.getEndChar() - lineDesc.getStartChar();
 				curPos += numChars;
 				curPos += numChars;
 			}
 			}
 
 
@@ -253,7 +263,7 @@ namespace BansheeEngine
 
 
 			Rect charRect = mTextSprite->getCharRect(charIdx);
 			Rect charRect = mTextSprite->getCharRect(charIdx);
 			UINT32 lineIdx = mTextSprite->getLineForChar(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);
 			return Int2(charRect.x + charRect.width + 1, yOffset);
 		}
 		}
@@ -272,7 +282,7 @@ namespace BansheeEngine
 		if(charIdx < (UINT32)mTextDesc.text.size())
 		if(charIdx < (UINT32)mTextDesc.text.size())
 		{
 		{
 			UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
 			UINT32 lineIdx = mTextSprite->getLineForChar(charIdx);
-			return mTextSprite->getLineDesc(lineIdx).lineHeight;
+			return mTextSprite->getLineDesc(lineIdx).getLineHeight();
 		}
 		}
 		else
 		else
 		{
 		{
@@ -307,7 +317,7 @@ namespace BansheeEngine
 				curPos++; // Beginning of the line has a special caret position, primarily so we can
 				curPos++; // Beginning of the line has a special caret position, primarily so we can
 						  // still place a caret on an empty line
 						  // still place a caret on an empty line
 
 
-			UINT32 numChars = lineDesc.endChar - lineDesc.startChar - 1;
+			UINT32 numChars = lineDesc.getEndChar() - lineDesc.getStartChar() - 1;
 			curPos += numChars;
 			curPos += numChars;
 		}
 		}
 
 
@@ -329,7 +339,7 @@ namespace BansheeEngine
 				maxPos++; // Beginning of the line has a special caret position, primarily so we can
 				maxPos++; // Beginning of the line has a special caret position, primarily so we can
 						  // still place a caret on an empty line
 						  // still place a caret on an empty line
 
 
-			UINT32 numChars = lineDesc.endChar - lineDesc.startChar;
+			UINT32 numChars = lineDesc.getEndChar() - lineDesc.getStartChar();
 			maxPos += numChars;
 			maxPos += numChars;
 		}
 		}
 
 

+ 63 - 22
BansheeEngine/Source/BsTextSprite.cpp

@@ -135,18 +135,19 @@ namespace BansheeEngine
 		UINT32 curLineIdx = 0;
 		UINT32 curLineIdx = 0;
 		for(auto& line : lines)
 		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);
 			mLineDescs.push_back(lineDesc);
 
 
-			curCharIdx = lineDesc.endChar;
-			cachedLineY += lineDesc.lineHeight;
+			curCharIdx = lineDesc.getEndChar();
+			cachedLineY += lineDesc.getLineHeight();
 			curLineIdx++;
 			curLineIdx++;
 		}
 		}
 
 
@@ -159,10 +160,13 @@ namespace BansheeEngine
 
 
 		// If char is newline we don't have any geometry to return
 		// If char is newline we don't have any geometry to return
 		const SpriteLineDesc& lineDesc = getLineDesc(lineIdx);
 		const SpriteLineDesc& lineDesc = getLineDesc(lineIdx);
-		if(lineIdx != (getNumLines() - 1) && (lineDesc.endChar - 1) == charIdx)
+		if(lineDesc.isNewline(charIdx))
 			return Rect();
 			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 quadIdx = charIdx - numNewlineChars;
 
 
 		UINT32 curQuadIdx = 0;
 		UINT32 curQuadIdx = 0;
@@ -193,26 +197,26 @@ namespace BansheeEngine
 
 
 		UINT32 lineStartChar = 0;
 		UINT32 lineStartChar = 0;
 		UINT32 lineEndChar = 0;
 		UINT32 lineEndChar = 0;
-		UINT32 newlineChars = 0;
+		UINT32 numNewlineChars = 0;
 		UINT32 lineIdx = 0;
 		UINT32 lineIdx = 0;
 		for(auto& line : mLineDescs)
 		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;
 				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
 			// so we need to filter them out
+			numNewlineChars += (line.hasNewlineChar() ? 1 : 0); 
+
 			lineIdx++;
 			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();
 		float nearestDist = std::numeric_limits<float>::max();
 		UINT32 nearestChar = 0;
 		UINT32 nearestChar = 0;
@@ -239,7 +243,7 @@ namespace BansheeEngine
 				float dist = Math::Abs(centerX - vecPos.x);
 				float dist = Math::Abs(centerX - vecPos.x);
 				if(dist < nearestDist)
 				if(dist < nearestDist)
 				{
 				{
-					nearestChar = quadIdx + newlineChars;
+					nearestChar = quadIdx + numNewlineChars;
 					nearestDist = dist;
 					nearestDist = dist;
 					foundChar = true;
 					foundChar = true;
 				}
 				}
@@ -254,13 +258,16 @@ namespace BansheeEngine
 		return nearestChar;
 		return nearestChar;
 	}
 	}
 
 
-	CM::UINT32 TextSprite::getLineForChar(CM::UINT32 charIdx) const
+	CM::UINT32 TextSprite::getLineForChar(CM::UINT32 charIdx, bool newlineCountsOnNextLine) const
 	{
 	{
 		UINT32 idx = 0;
 		UINT32 idx = 0;
 		for(auto& line : mLineDescs)
 		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;
 				return idx;
 			}
 			}
 
 
@@ -318,4 +325,38 @@ namespace BansheeEngine
 
 
 		return lineOffsets;
 		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.
 			 * @brief	Returns the total number of characters on this line.
 			 */
 			 */
 			UINT32 getNumChars() const;
 			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:
 		private:
 			friend class TextUtility;
 			friend class TextUtility;
 
 
@@ -89,11 +95,14 @@ namespace CamelotFramework
 			UINT32 mSpaceWidth;
 			UINT32 mSpaceWidth;
 			Vector<TextWord>::type mWords;
 			Vector<TextWord>::type mWords;
 			TextWord* mLastWord;
 			TextWord* mLastWord;
+			bool mHasNewline;
 
 
 			void add(const CHAR_DESC& charDesc);
 			void add(const CHAR_DESC& charDesc);
 			void addSpace();
 			void addSpace();
 			void addWord(const TextWord& word);
 			void addWord(const TextWord& word);
 
 
+			void finalize(bool hasNewlineChar);
+
 			TextWord removeLastWord();
 			TextWord removeLastWord();
 
 
 			void calculateBounds();
 			void calculateBounds();

+ 13 - 15
CamelotCore/Source/CmTextUtility.cpp

@@ -73,7 +73,8 @@ namespace CamelotFramework
 	}
 	}
 
 
 	TextUtility::TextLine::TextLine(UINT32 baselineOffset, UINT32 lineHeight, UINT32 spaceWidth)
 	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)
 	void TextUtility::TextLine::add(const CHAR_DESC& charDesc)
 	{
 	{
 		if(mLastWord == nullptr)
 		if(mLastWord == nullptr)
@@ -358,6 +364,7 @@ namespace CamelotFramework
 
 
 			if(text[charIdx] == '\n')
 			if(text[charIdx] == '\n')
 			{
 			{
+				curLine->finalize(true);
 				textData->mLines.push_back(TextLine(fontData->fontDesc.baselineOffset, fontData->fontDesc.lineHeight, fontData->fontDesc.spaceWidth));
 				textData->mLines.push_back(TextLine(fontData->fontDesc.baselineOffset, fontData->fontDesc.lineHeight, fontData->fontDesc.spaceWidth));
 				curLine = &textData->mLines.back();
 				curLine = &textData->mLines.back();
 
 
@@ -383,34 +390,25 @@ namespace CamelotFramework
 				if(wordWrap)
 				if(wordWrap)
 				{
 				{
 					TextWord lastWord = curLine->removeLastWord();
 					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));
 						textData->mLines.push_back(TextLine(fontData->fontDesc.baselineOffset, fontData->fontDesc.lineHeight, fontData->fontDesc.spaceWidth));
 						curLine = &textData->mLines.back();
 						curLine = &textData->mLines.back();
 
 
 						curHeight += fontData->fontDesc.lineHeight;
 						curHeight += fontData->fontDesc.lineHeight;
 					}
 					}
 
 
-					if(moveLastWord)
-						curLine->addWord(lastWord);
+					curLine->addWord(lastWord);
 				}
 				}
 			}
 			}
 
 
 			charIdx++;
 			charIdx++;
 		}
 		}
 
 
+		curLine->finalize(true);
+
 		return textData;
 		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
 	- SpriteTexture keeps a static reference to DUmmyTexture which I need to release before shutdown
 
 
 TextBox needed elements:
 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
  - Drag mouse to update selection
  - Input caret positioning ignores kerning which means the caret is sometimes 
  - 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".
    in the middle of another char. Try typing "fa" and moving caret in front of "f".
@@ -36,6 +38,10 @@ TextBox needed elements:
  - LATER
  - LATER
   - TAB between input elements
   - TAB between input elements
   - Context menu with copy/cut/paste
   - 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
 GUIDragManager
  - GUI system sends startdrag/enddrag/drag events to all elements
  - GUI system sends startdrag/enddrag/drag events to all elements