瀏覽代碼

A lot more work on caret/selection

Marko Pintera 12 年之前
父節點
當前提交
cbe2a084e2

+ 1 - 6
BansheeEngine/Include/BsGUIInputBox.h

@@ -3,6 +3,7 @@
 #include "BsPrerequisites.h"
 #include "BsGUIElement.h"
 #include "BsImageSprite.h"
+#include "BsTextSprite.h"
 
 namespace BansheeEngine
 {
@@ -67,11 +68,6 @@ namespace BansheeEngine
 		bool mCaretShown;
 		bool mSelectionShown;
 
-		// Used for caret/selection and in general hit detection with specific chars
-		CM::Vector2* mTextVertices;
-		UINT32 mNumTextVertices;
-		UINT32 mTextLineHeight;
-
 		GUIInputBox(GUIWidget& parent, const GUIElementStyle* style, const GUILayoutOptions& layoutOptions);
 
 		virtual bool mouseEvent(const GUIMouseEvent& ev);
@@ -89,7 +85,6 @@ namespace BansheeEngine
 		void clearSelection();
 		CM::Vector<CM::Rect>::type getSelectionRects() const;
 
-		CM::UINT32 getCharAtPosition(const CM::Int2& pos) const;
 		CM::Rect getTextBounds() const;
 		TEXT_SPRITE_DESC getTextDesc() const;
 	};

+ 16 - 15
BansheeEngine/Include/BsTextSprite.h

@@ -36,6 +36,14 @@ namespace BansheeEngine
 		bool wordWrap;
 	};
 
+	struct SpriteLineDesc
+	{
+		CM::UINT32 startChar;
+		CM::UINT32 endChar;
+		CM::UINT32 lineHeight;
+		CM::INT32 lineYStart;
+	};
+
 	class BS_EXPORT TextSprite : public Sprite
 	{
 	public:
@@ -43,20 +51,13 @@ namespace BansheeEngine
 
 		void update(const TEXT_SPRITE_DESC& desc);
 
-		/**
-		 * @brief	Populates the provided buffer with vertices for the individual characters described
-		 * 			by the descriptor structure. Characters will be in the same position as if they were being drawn on
-		 * 			the screen using TextSprite directly.
-		 *
-		 * @param	desc				What text to get vertices for, what font, what size, etc.
-		 * @param   [out]	vertices	Pre-allocated array with enough space to hold all vertices.						
-		 *
-		 * @return	Number of text quads. (1 quad per character)
-		 * 			
-		 * @note	This method should be called twice. Once with "vertices" as nullptr to receive number
-		 * 			of quads you will need, then you should allocate the vertex array of enough size and
-		 * 			send it to the second call. (4 * numQuads)
-		 */
-		static UINT32 getTextVertices(const TEXT_SPRITE_DESC& desc, CM::Vector2* vertices);
+		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::Rect getCharRect(CM::UINT32 charIdx) const;
+		CM::UINT32 getCharIdxAtPos(const CM::Int2& pos) const;
+
+	private:
+		CM::Vector<SpriteLineDesc>::type mLineDescs;
 	};
 }

+ 101 - 38
BansheeEngine/Source/BsGUIInputBox.cpp

@@ -9,6 +9,7 @@
 #include "BsGUIButtonEvent.h"
 #include "BsGUIMouseEvent.h"
 #include "BsGUICommandEvent.h"
+#include "CmFont.h"
 #include "CmTextUtility.h"
 #include "CmTexture.h"
 #include "CmCursor.h"
@@ -26,7 +27,7 @@ namespace BansheeEngine
 	GUIInputBox::GUIInputBox(GUIWidget& parent, const GUIElementStyle* style, const GUILayoutOptions& layoutOptions)
 		:GUIElement(parent, style, layoutOptions), mInputCursorSet(false), mDragInProgress(false),
 		mSelectionStart(0), mSelectionEnd(0), mCaretSprite(nullptr), mCaretShown(false), mSelectionShown(false), mCaretPos(0),
-		mIsMultiline(false), mTextVertices(nullptr), mNumTextVertices(0), mTextLineHeight(0)
+		mIsMultiline(false)
 	{
 		mImageSprite = cm_new<ImageSprite, PoolAlloc>();
 		mCaretSprite = cm_new<ImageSprite, PoolAlloc>();
@@ -299,7 +300,12 @@ namespace BansheeEngine
 		else if(ev.getType() == GUIMouseEventType::MouseDown)
 		{
 			mImageDesc.texture = mStyle->active.texture;
-			showCaret(getCharAtPosition(ev.getPosition()));
+
+			if(mText.size() > 0)
+				showCaret(mTextSprite->getCharIdxAtPos(ev.getPosition()) +  1);
+			else
+				showCaret(0);
+
 			clearSelection();
 			markAsDirty();
 
@@ -352,12 +358,13 @@ namespace BansheeEngine
 					if(mSelectionShown)
 					{
 						mText.erase(mSelectionStart, mSelectionEnd);
-						mCaretPos = mSelectionStart;
+						mCaretPos = mSelectionStart + 1;
 						clearSelection();
 					}
 					else
 					{
-						mText.erase(mCaretPos);
+						if(mCaretPos > 0)
+							mText.erase(mCaretPos - 1);
 					}
 
 					markAsDirty();
@@ -377,7 +384,7 @@ namespace BansheeEngine
 			if(mSelectionShown)
 			{
 				mText.erase(mSelectionStart, mSelectionEnd);
-				mCaretPos = mSelectionStart;
+				mCaretPos = mSelectionStart + 1;
 				clearSelection();
 			}
 
@@ -403,9 +410,9 @@ namespace BansheeEngine
 		return false;
 	}
 
-	void GUIInputBox::showCaret(CM::UINT32 charIdx)
+	void GUIInputBox::showCaret(CM::UINT32 caretPos)
 	{
-		mCaretPos = charIdx;
+		mCaretPos = caretPos;
 		mCaretShown = true;
 		markAsDirty();
 	}
@@ -418,14 +425,41 @@ namespace BansheeEngine
 
 	Int2 GUIInputBox::getCaretPosition() const
 	{
-		// TODO
-		return Int2(0, 0);
+		if(mCaretPos > 0)
+		{
+			Rect charRect = mTextSprite->getCharRect(mCaretPos - 1);
+			UINT32 lineIdx = mTextSprite->getLineForChar(mCaretPos - 1);
+			UINT32 yOffset = mTextSprite->getLineDesc(lineIdx).lineYStart;
+
+			return Int2(charRect.x + charRect.width + 1, yOffset);
+		}
+		else
+		{
+			Rect contentBounds = getContentBounds();
+			return Int2(contentBounds.x, contentBounds.y);
+		}		
 	}
 
 	UINT32 GUIInputBox::getCaretHeight() const
 	{
-		// TODO
-		return 8;
+		if(mCaretPos > 0)
+		{
+			UINT32 lineIdx = mTextSprite->getLineForChar(mCaretPos - 1);
+			return mTextSprite->getLineDesc(lineIdx).lineHeight;
+		}
+		else
+		{
+			if(mStyle->font != nullptr)
+			{
+				UINT32 nearestSize = mStyle->font->getClosestAvailableSize(mStyle->fontSize);
+				const FontData* fontData = mStyle->font->getFontDataForSize(nearestSize);
+
+				if(fontData != nullptr)
+					return fontData->fontDesc.lineHeight;
+			}
+		}
+
+		return 0;
 	}
 
 	void GUIInputBox::showSelection(UINT32 startChar, UINT32 endChar)
@@ -448,43 +482,70 @@ namespace BansheeEngine
 
 	Vector<Rect>::type GUIInputBox::getSelectionRects() const
 	{
-		// TODO
-		return Vector<Rect>::type();
-	}
+		Vector<Rect>::type selectionRects;
 
-	UINT32 GUIInputBox::getCharAtPosition(const Int2& pos) const
-	{
-		TEXT_SPRITE_DESC textDesc = getTextDesc();
-		UINT32 numQuads = TextSprite::getTextVertices(textDesc, nullptr);
+		if(mSelectionStart == mSelectionEnd)
+			return selectionRects;
 
-		if(numQuads == 0)
-			return 0;
+		UINT32 startLine = mTextSprite->getLineForChar(mSelectionStart);
+		UINT32 endLine = mTextSprite->getLineForChar(mSelectionEnd);
 
-		Vector2* vertices = (Vector2*)cm_alloc(numQuads * 4 * sizeof(Vector2));
-		TextSprite::getTextVertices(textDesc, vertices);
-
-		Vector2 vecPos((float)pos.x, (float)pos.y);
+		{
+			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(startLine);
+			Rect startChar = mTextSprite->getCharRect(mSelectionStart);
+			UINT32 endCharIdx = mSelectionEnd;
+			if(endCharIdx >= lineDesc.endChar)
+				endCharIdx = lineDesc.endChar - 1;
+			
+			Rect endChar = mTextSprite->getCharRect(endCharIdx);
+
+			Rect selectionRect;
+			selectionRect.x = startChar.x;
+			selectionRect.y = lineDesc.lineYStart;
+			selectionRect.height = lineDesc.lineHeight;
+			selectionRect.width = (endChar.x + endChar.width) - startChar.x;
+
+			selectionRects.push_back(selectionRect);
+		}
 
-		float nearestDist = std::numeric_limits<float>::max();
-		UINT32 nearestChar = 0;
-		for(UINT32 i = 0; i < numQuads; i++)
+		for(UINT32 i = startLine + 1; i < endLine; i++)
 		{
-			UINT32 curVert = i * 4;
+			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
+			if(lineDesc.startChar == lineDesc.endChar)
+				continue;
 
-			Vector2 center = vertices[curVert + 0] + vertices[curVert + 1] + vertices[curVert + 2] + vertices[curVert + 3];
-			center /= 4;
+			Rect startChar = mTextSprite->getCharRect(lineDesc.startChar);
+			Rect endChar = mTextSprite->getCharRect(lineDesc.endChar);
 
-			float dist = center.squaredDistance(vecPos);
-			if(dist < nearestDist)
-			{
-				nearestChar = i;
-				nearestDist = dist;
-			}
+			Rect selectionRect;
+			selectionRect.x = startChar.x;
+			selectionRect.y = lineDesc.lineYStart;
+			selectionRect.height = lineDesc.lineHeight;
+			selectionRect.width = (endChar.x + endChar.width) - startChar.x;
+
+			selectionRects.push_back(selectionRect);
 		}
 
-		cm_free(vertices);
+		if(startLine != endLine)
+		{
+			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(endLine);
+
+			if(lineDesc.startChar != lineDesc.endChar)
+			{
+				Rect startChar = mTextSprite->getCharRect(lineDesc.startChar);
+				Rect endChar = mTextSprite->getCharRect(mSelectionEnd);
+
+				Rect selectionRect;
+				selectionRect.x = startChar.x;
+				selectionRect.y = lineDesc.lineYStart;
+				selectionRect.height = lineDesc.lineHeight;
+				selectionRect.width = (endChar.x + endChar.width) - startChar.x;
 
-		return nearestChar;
+				selectionRects.push_back(selectionRect);
+			}
+		}
+		
+		return selectionRects;
 	}
 
 	CM::Rect GUIInputBox::getTextBounds() const
@@ -515,6 +576,8 @@ namespace BansheeEngine
 		textDesc.clipRect = Rect(0, 0, textDesc.width, textDesc.height);
 		textDesc.horzAlign = mStyle->textHorzAlign;
 		textDesc.vertAlign = mStyle->textVertAlign;
+
+		return textDesc;
 	}
 
 	void GUIInputBox::_setFocus(bool focus)

+ 12 - 5
BansheeEngine/Source/BsSprite.cpp

@@ -48,17 +48,24 @@ namespace BansheeEngine
 		assert((startIndex + mNumIndices) <= maxIndexIdx);
 
 		UINT8* vertDst = vertices + startVert * vertexStride;
-		UINT8* uvDst = uv + startVert * vertexStride;
 		for(UINT32 i = 0; i < mNumVertices; i++)
 		{
 			memcpy(vertDst, &renderElem.vertices[i], sizeof(Vector2));
-			memcpy(uvDst, &renderElem.uvs[i], sizeof(Vector2));
-
 			vertDst += vertexStride;
-			uvDst += vertexStride;
 		}
 
-		memcpy(&indices[startIndex], renderElem.indexes, mNumIndices * sizeof(UINT32));
+		if(uv != nullptr)
+		{
+			UINT8* uvDst = uv + startVert * vertexStride;
+			for(UINT32 i = 0; i < mNumVertices; i++)
+			{
+				memcpy(uvDst, &renderElem.uvs[i], sizeof(Vector2));
+				uvDst += vertexStride;
+			}
+		}
+
+		if(indices != nullptr)
+			memcpy(&indices[startIndex], renderElem.indexes, mNumIndices * sizeof(UINT32));
 
 		return renderElem.numQuads;
 	}

+ 65 - 71
BansheeEngine/Source/BsTextSprite.cpp

@@ -161,101 +161,95 @@ namespace BansheeEngine
 			}
 		}
 
+		// Store cached line data
+		mLineDescs.clear();
+		UINT32 curCharIdx = 0;
+		UINT32 cachedLineY = 0;
+		for(auto& line : lines)
+		{
+			SpriteLineDesc lineDesc;
+			lineDesc.startChar = curCharIdx;
+			lineDesc.endChar = curCharIdx + line.getNumChars();
+			lineDesc.lineHeight = line.getYOffset();
+			lineDesc.lineYStart = vertOffset + curY + desc.offset.y;
+
+			mLineDescs.push_back(lineDesc);
+
+			curCharIdx = lineDesc.endChar;
+			cachedLineY += lineDesc.lineHeight;
+		}
+
 		updateBounds();
 	}
 
-	UINT32 TextSprite::getTextVertices(const TEXT_SPRITE_DESC& desc, CM::Vector2* vertices)
+	CM::Rect TextSprite::getCharRect(UINT32 charIdx) const
 	{
-		std::shared_ptr<TextUtility::TextData> textData = TextUtility::getTextData(desc.text, desc.font, desc.fontSize, desc.width, desc.height, desc.wordWrap);
-
-		if(textData == nullptr)
-			return 0;
+		UINT32 idx = 0;
+		for(auto& renderElem : mCachedRenderElements)
+		{
+			if(charIdx >= idx && charIdx < renderElem.numQuads)
+			{
+				UINT32 localIdx = charIdx - idx;
 
-		const CM::Vector<TextUtility::TextLine>::type& lines = textData->getLines();
-		const CM::Vector<UINT32>::type& quadsPerPage = textData->getNumQuadsPerPage();
+				Rect charRect;
+				charRect.x = Math::RoundToInt(renderElem.vertices[localIdx + 0].x);
+				charRect.y = Math::RoundToInt(renderElem.vertices[localIdx + 0].y);
+				charRect.width = Math::RoundToInt(renderElem.vertices[localIdx + 3].x - charRect.x);
+				charRect.height = Math::RoundToInt(renderElem.vertices[localIdx + 3].y - charRect.y);
 
-		UINT32 numQuads = 0;
-		for(auto& quads : quadsPerPage)
-			numQuads += quads;
+				return charRect;
+			}
 
-		if(vertices == nullptr)
-			return numQuads;
+			idx += renderElem.numQuads;
+		}
 
-		UINT32 curHeight = 0;
-		for(auto& line : lines)
-			curHeight += line.getYOffset();
+		CM_EXCEPT(InternalErrorException, "Invalid character index: " + toString(charIdx));
+	}
 
-		// Calc vertical alignment offset
-		UINT32 vertDiff = std::max(0U, desc.height - curHeight);
-		UINT32 vertOffset = 0;
-		switch(desc.vertAlign)
-		{
-		case TVA_Top:
-			vertOffset = 0;
-			break;
-		case TVA_Bottom:
-			vertOffset = std::max(0, (INT32)vertDiff);
-			break;
-		case TVA_Center:
-			vertOffset = std::max(0, (INT32)vertDiff) / 2;
-			break;
-		}
+	UINT32 TextSprite::getCharIdxAtPos(const Int2& pos) const
+	{
+		Vector2 vecPos((float)pos.x, (float)pos.y);
 
-		// Calc horizontal alignment offset and set final line positions
-		Int2 offset = getAnchorOffset(desc.anchor, desc.width, desc.height);
-		UINT32 curY = 0;
-		UINT32 numPages = (UINT32)quadsPerPage.size();
-		UINT32 quadOffset = 0;
-		for(size_t i = 0; i < lines.size(); i++)
+		float nearestDist = std::numeric_limits<float>::max();
+		UINT32 nearestChar = 0;
+		UINT32 idx = 0;
+		for(auto& renderElem : mCachedRenderElements)
 		{
-			UINT32 horzOffset = 0;
-			switch(desc.horzAlign)
+			for(UINT32 i = 0; i < renderElem.numQuads; i++)
 			{
-			case THA_Left:
-				horzOffset = 0;
-				break;
-			case THA_Right:
-				horzOffset = std::max(0, (INT32)(desc.width - lines[i].getWidth()));
-				break;
-			case THA_Center:
-				horzOffset = std::max(0, (INT32)(desc.width - lines[i].getWidth())) / 2;
-				break;
-			}
+				UINT32 curVert = idx * 4;
 
-			Int2 position = offset + Int2(horzOffset, vertOffset + curY);
-			curY += lines[i].getYOffset();
+				Vector2 center = renderElem.vertices[curVert + 0] + renderElem.vertices[curVert + 1] + 
+					renderElem.vertices[curVert + 2] + renderElem.vertices[curVert + 3];
+				center /= 4;
 
-			for(size_t j = 0; j < numPages; j++)
-			{
-				// TODO - fillBuffer won't accept null uv or indexes
-				UINT32 writtenQuads = lines[i].fillBuffer((UINT32)j, vertices, nullptr, nullptr, quadOffset, numQuads);
-
-				UINT32 numVertices = writtenQuads * 4;
-				for(size_t i = 0; i < numVertices; i++)
+				float dist = center.squaredDistance(vecPos);
+				if(dist < nearestDist)
 				{
-					vertices[quadOffset * 4 + i].x += (float)position.x;
-					vertices[quadOffset * 4 + i].y += (float)position.y;
+					nearestChar = idx;
+					nearestDist = dist;
 				}
 
-				quadOffset += writtenQuads;
+				idx++;
 			}
 		}
 
-		if(desc.clipRect.width > 0 && desc.clipRect.height > 0)
-		{
-			clipToRect(vertices, nullptr, numQuads, desc.clipRect);
-		}
+		return nearestChar;
+	}
 
-		// Apply offset
+	CM::UINT32 TextSprite::getLineForChar(CM::UINT32 charIdx) const
+	{
+		UINT32 idx = 0;
+		for(auto& line : mLineDescs)
 		{
-			UINT32 numVertices = numQuads * 4;
-			for(size_t i = 0; i < numVertices; i++)
+			if(charIdx >= line.startChar && charIdx < line.endChar)
 			{
-				vertices[i].x += (float)desc.offset.x;
-				vertices[i].y += (float)desc.offset.y;
+				return idx;
 			}
-		}
 
-		return numQuads;
+			idx++;
+		}
+		
+		CM_EXCEPT(InternalErrorException, "Invalid character index: " + toString(charIdx));
 	}
 }

+ 4 - 0
CamelotCore/Include/CmTextUtility.h

@@ -75,6 +75,10 @@ namespace CamelotFramework
 			 */
 			UINT32 fillBuffer(UINT32 page, Vector2* vertices, Vector2* uvs, UINT32* indexes, UINT32 offset, UINT32 size) const;
 
+			/**
+			 * @brief	Returns the total number of characters on this line.
+			 */
+			UINT32 getNumChars() const;
 		private:
 			friend class TextUtility;
 

+ 14 - 0
CamelotCore/Source/CmTextUtility.cpp

@@ -249,6 +249,20 @@ namespace CamelotFramework
 		return numQuads;
 	}
 
+	UINT32 TextUtility::TextLine::getNumChars() const
+	{
+		UINT32 numChars = 0;
+		for(auto& word : mWords)
+		{
+			if(word.isSpacer())
+				continue;
+
+			numChars += (UINT32)word.getChars().size();
+		}
+
+		return numChars;
+	}
+
 	void TextUtility::TextLine::calculateBounds()
 	{
 		mWidth = 0;