Procházet zdrojové kódy

More input selection refactoring

Marko Pintera před 12 roky
rodič
revize
df3532e71c

+ 2 - 0
BansheeEngine/BansheeEngine.vcxproj

@@ -155,6 +155,7 @@
     <ClInclude Include="Include\BsGUIInputBox.h" />
     <ClInclude Include="Include\BsGUIInputBox.h" />
     <ClInclude Include="Include\BsGUIButtonEvent.h" />
     <ClInclude Include="Include\BsGUIButtonEvent.h" />
     <ClInclude Include="Include\BsGUIInputCaret.h" />
     <ClInclude Include="Include\BsGUIInputCaret.h" />
+    <ClInclude Include="Include\BsGUIInputSelection.h" />
     <ClInclude Include="Include\BsGUILayoutOptions.h" />
     <ClInclude Include="Include\BsGUILayoutOptions.h" />
     <ClInclude Include="Include\BsGUILayoutX.h" />
     <ClInclude Include="Include\BsGUILayoutX.h" />
     <ClInclude Include="Include\BsGUILayout.h" />
     <ClInclude Include="Include\BsGUILayout.h" />
@@ -200,6 +201,7 @@
     <ClCompile Include="Source\BsGUIInputBox.cpp" />
     <ClCompile Include="Source\BsGUIInputBox.cpp" />
     <ClCompile Include="Source\BsGUIButtonEvent.cpp" />
     <ClCompile Include="Source\BsGUIButtonEvent.cpp" />
     <ClCompile Include="Source\BsGUIInputCaret.cpp" />
     <ClCompile Include="Source\BsGUIInputCaret.cpp" />
+    <ClCompile Include="Source\BsGUIInputSelection.cpp" />
     <ClCompile Include="Source\BsGUILabel.cpp" />
     <ClCompile Include="Source\BsGUILabel.cpp" />
     <ClCompile Include="Source\BsGUILayout.cpp" />
     <ClCompile Include="Source\BsGUILayout.cpp" />
     <ClCompile Include="Source\BsGUILayoutY.cpp" />
     <ClCompile Include="Source\BsGUILayoutY.cpp" />

+ 6 - 0
BansheeEngine/BansheeEngine.vcxproj.filters

@@ -165,6 +165,9 @@
     <ClInclude Include="Include\BsGUIInputCaret.h">
     <ClInclude Include="Include\BsGUIInputCaret.h">
       <Filter>Header Files\GUI</Filter>
       <Filter>Header Files\GUI</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Include\BsGUIInputSelection.h">
+      <Filter>Header Files\GUI</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsGUIElement.cpp">
     <ClCompile Include="Source\BsGUIElement.cpp">
@@ -275,5 +278,8 @@
     <ClCompile Include="Source\BsGUIInputCaret.cpp">
     <ClCompile Include="Source\BsGUIInputCaret.cpp">
       <Filter>Source Files\GUI</Filter>
       <Filter>Source Files\GUI</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="Source\BsGUIInputSelection.cpp">
+      <Filter>Source Files\GUI</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 11 - 2
BansheeEngine/Include/BsGUIInputBox.h

@@ -90,15 +90,24 @@ namespace BansheeEngine
 		void hideCaret();
 		void hideCaret();
 		void scrollTextToCaret();
 		void scrollTextToCaret();
 
 
-		void showSelection(CM::UINT32 startChar);
+		void showSelection(CM::UINT32 caretPos, SelectionDir dir);
 		void clearSelection();
 		void clearSelection();
 
 
 		void moveSelectionLeft(bool skipNewline);
 		void moveSelectionLeft(bool skipNewline);
 		void moveSelectionRight(bool skipnewLine);
 		void moveSelectionRight(bool skipnewLine);
 		void moveSelectionUp();
 		void moveSelectionUp();
 		void moveSelectionDown();
 		void moveSelectionDown();
+		bool isSelectionEmpty() const;
+		void selectAll();
 
 
-		CM::UINT32 getCaretSelectionCharIdx(SelectionDir dir) const;
+		void selectionDragStart(CM::UINT32 caretPos);
+		void selectionDragUpdate(CM::UINT32 caretPos);
+		void selectionDragEnd();
+
+		CM::UINT32 getSelectionStart() const { return mSelectionStart; }
+		CM::UINT32 getSelectionEnd() const { return mSelectionEnd; }
+
+		CM::UINT32 caretPosToSelectionChar(CM::UINT32 caretPos, SelectionDir dir) const;
 		bool isNewlineChar(CM::UINT32 charIdx) const;
 		bool isNewlineChar(CM::UINT32 charIdx) const;
 		CM::Vector<CM::Rect>::type getSelectionRects() const;
 		CM::Vector<CM::Rect>::type getSelectionRects() const;
 
 

+ 31 - 0
BansheeEngine/Include/BsGUIInputSelection.h

@@ -0,0 +1,31 @@
+#pragma once
+
+#include "BsPrerequisites.h"
+#include "BsTextSprite.h"
+
+namespace BansheeEngine
+{
+	class BS_EXPORT GUIInputSelection
+	{
+	public:
+		GUIInputSelection(const TEXT_SPRITE_DESC& textDesc);
+		~GUIInputSelection();
+
+		CM::Vector<ImageSprite*>::type getSprites() const { return mSprites; }
+		void updateText(const TEXT_SPRITE_DESC& textDesc);
+		void updateSprite(const CM::Int2& offset);
+
+	private:
+		CM::UINT32 mSelectionStart;
+		CM::UINT32 mSelectionEnd;
+		CM::UINT32 mSelectionAnchor;
+		CM::UINT32 mSelectionDragAnchor;
+		TextSprite* mTextSprite; // TODO - Try to get rid of this and implement its methods internally?
+		CM::Vector<ImageSprite*>::type mSprites;
+
+		TEXT_SPRITE_DESC mTextDesc;
+
+		CM::Vector<CM::Rect>::type getSelectionRects() const;
+		bool isNewlineChar(CM::UINT32 charIdx) const;
+	};
+}

+ 99 - 68
BansheeEngine/Source/BsGUIInputBox.cpp

@@ -317,9 +317,6 @@ namespace BansheeEngine
 		{
 		{
 			mImageDesc.texture = mStyle->hover.texture;
 			mImageDesc.texture = mStyle->hover.texture;
 
 
-			if(mSelectionStart == mSelectionEnd)
-				clearSelection();
-
 			markAsDirty();
 			markAsDirty();
 
 
 			return true;
 			return true;
@@ -328,6 +325,8 @@ namespace BansheeEngine
 		{
 		{
 			mDragInProgress = true;
 			mDragInProgress = true;
 
 
+			selectionDragStart(mInputCaret->getCaretPos());
+
 			return true;
 			return true;
 		}
 		}
 		else if(ev.getType() == GUIMouseEventType::MouseDragEnd)
 		else if(ev.getType() == GUIMouseEventType::MouseDragEnd)
@@ -340,6 +339,8 @@ namespace BansheeEngine
 				mInputCursorSet = false;
 				mInputCursorSet = false;
 			}
 			}
 
 
+			selectionDragEnd();
+
 			return true;
 			return true;
 		}
 		}
 		else if(ev.getType() == GUIMouseEventType::MouseDrag)
 		else if(ev.getType() == GUIMouseEventType::MouseDrag)
@@ -350,42 +351,54 @@ namespace BansheeEngine
 			else
 			else
 				mInputCaret->moveCaretToStart();
 				mInputCaret->moveCaretToStart();
 
 
-			if(!mSelectionShown)
-			{
-				showSelection(getCaretSelectionCharIdx(SelectionDir::Left));
-				mSelectionDragAnchor = mInputCaret->getCaretPos();
-			}
+			selectionDragUpdate(mInputCaret->getCaretPos());
 
 
-			UINT32 curCaretPos = mInputCaret->getCaretPos();
-			if(curCaretPos < mSelectionDragAnchor)
-			{
-				mSelectionStart = mInputCaret->getCharIdxAtCaretPos(curCaretPos);
-				mSelectionEnd = mInputCaret->getCharIdxAtCaretPos(mSelectionDragAnchor);
+			scrollTextToCaret();
 
 
-				mSelectionAnchor = mSelectionStart;
-			}
+			markAsDirty();
+			return true;
+		}
 
 
-			if(curCaretPos > mSelectionDragAnchor)
-			{
-				mSelectionStart = mInputCaret->getCharIdxAtCaretPos(mSelectionDragAnchor);
-				mSelectionEnd = mInputCaret->getCharIdxAtCaretPos(curCaretPos);
+		return false;
+	}
 
 
-				mSelectionAnchor = mSelectionEnd;
-			}
+	void GUIInputBox::selectionDragStart(UINT32 caretPos)
+	{
+		clearSelection();
 
 
-			if(curCaretPos == mSelectionDragAnchor)
-			{
-				mSelectionStart = mSelectionAnchor;
-				mSelectionEnd = mSelectionAnchor;
-			}
+		showSelection(caretPos, SelectionDir::Left); 
+		mSelectionDragAnchor = caretPos;
+	}
 
 
-			scrollTextToCaret();
+	void GUIInputBox::selectionDragUpdate(UINT32 caretPos)
+	{
+		if(caretPos < mSelectionDragAnchor)
+		{
+			mSelectionStart = mInputCaret->getCharIdxAtCaretPos(caretPos);
+			mSelectionEnd = mInputCaret->getCharIdxAtCaretPos(mSelectionDragAnchor);
 
 
-			markAsDirty();
-			return true;
+			mSelectionAnchor = mSelectionStart;
 		}
 		}
 
 
-		return false;
+		if(caretPos > mSelectionDragAnchor)
+		{
+			mSelectionStart = mInputCaret->getCharIdxAtCaretPos(mSelectionDragAnchor);
+			mSelectionEnd = mInputCaret->getCharIdxAtCaretPos(caretPos);
+
+			mSelectionAnchor = mSelectionEnd;
+		}
+
+		if(caretPos == mSelectionDragAnchor)
+		{
+			mSelectionStart = mSelectionAnchor;
+			mSelectionEnd = mSelectionAnchor;
+		}
+	}
+
+	void GUIInputBox::selectionDragEnd()
+	{
+		if(isSelectionEmpty())
+			clearSelection();
 	}
 	}
 
 
 	bool GUIInputBox::keyEvent(const GUIKeyEvent& ev)
 	bool GUIInputBox::keyEvent(const GUIKeyEvent& ev)
@@ -398,12 +411,13 @@ namespace BansheeEngine
 				{
 				{
 					if(mSelectionShown)
 					if(mSelectionShown)
 					{
 					{
-						mText.erase(mText.begin() + mSelectionStart, mText.begin() + mSelectionEnd);
+						UINT32 selStart = getSelectionStart();
+						mText.erase(mText.begin() + selStart, mText.begin() + getSelectionEnd());
 						mInputCaret->updateText(getTextDesc());
 						mInputCaret->updateText(getTextDesc());
 
 
-						if(mSelectionStart > 0)
+						if(selStart > 0)
 						{
 						{
-							UINT32 newCaretPos = mSelectionStart - 1;
+							UINT32 newCaretPos = selStart - 1;
 							mInputCaret->moveCaretToChar(newCaretPos, CARET_AFTER);
 							mInputCaret->moveCaretToChar(newCaretPos, CARET_AFTER);
 						}
 						}
 						else
 						else
@@ -444,12 +458,13 @@ namespace BansheeEngine
 				{
 				{
 					if(mSelectionShown)
 					if(mSelectionShown)
 					{
 					{
-						mText.erase(mText.begin() + mSelectionStart, mText.begin() + mSelectionEnd);
+						UINT32 selStart = getSelectionStart();
+						mText.erase(mText.begin() + selStart, mText.begin() + getSelectionEnd());
 						mInputCaret->updateText(getTextDesc());
 						mInputCaret->updateText(getTextDesc());
 
 
-						if(mSelectionStart > 0)
+						if(selStart > 0)
 						{
 						{
-							UINT32 newCaretPos = mSelectionStart - 1;
+							UINT32 newCaretPos = selStart - 1;
 							mInputCaret->moveCaretToChar(newCaretPos, CARET_AFTER);
 							mInputCaret->moveCaretToChar(newCaretPos, CARET_AFTER);
 						}
 						}
 						else
 						else
@@ -490,13 +505,13 @@ namespace BansheeEngine
 					bool caretMovedDueToNewline = false;
 					bool caretMovedDueToNewline = false;
 					if(!mSelectionShown)
 					if(!mSelectionShown)
 					{
 					{
-						if(isNewlineChar(getCaretSelectionCharIdx(SelectionDir::Right)))
+						if(isNewlineChar(caretPosToSelectionChar(mInputCaret->getCaretPos(), SelectionDir::Right)))
 						{
 						{
 							mInputCaret->moveCaretLeft();
 							mInputCaret->moveCaretLeft();
 							caretMovedDueToNewline = true;
 							caretMovedDueToNewline = true;
 						}
 						}
 
 
-						showSelection(getCaretSelectionCharIdx(SelectionDir::Left));
+						showSelection(mInputCaret->getCaretPos(), SelectionDir::Left);
 					}
 					}
 
 
 					moveSelectionLeft(caretMovedDueToNewline);
 					moveSelectionLeft(caretMovedDueToNewline);
@@ -523,13 +538,13 @@ namespace BansheeEngine
 					bool caretMovedDueToNewline = false;
 					bool caretMovedDueToNewline = false;
 					if(!mSelectionShown)
 					if(!mSelectionShown)
 					{
 					{
-						if(isNewlineChar(getCaretSelectionCharIdx(SelectionDir::Left)))
+						if(isNewlineChar(caretPosToSelectionChar(mInputCaret->getCaretPos(), SelectionDir::Left)))
 						{
 						{
 							mInputCaret->moveCaretRight();
 							mInputCaret->moveCaretRight();
 							caretMovedDueToNewline = true;
 							caretMovedDueToNewline = true;
 						}
 						}
 
 
-						showSelection(getCaretSelectionCharIdx(SelectionDir::Left));
+						showSelection(mInputCaret->getCaretPos(), SelectionDir::Left);
 					}
 					}
 						
 						
 					moveSelectionRight(caretMovedDueToNewline);
 					moveSelectionRight(caretMovedDueToNewline);
@@ -555,12 +570,12 @@ namespace BansheeEngine
 				{
 				{
 					if(!mSelectionShown)
 					if(!mSelectionShown)
 					{
 					{
-						if(isNewlineChar(getCaretSelectionCharIdx(SelectionDir::Right)))
+						if(isNewlineChar(caretPosToSelectionChar(mInputCaret->getCaretPos(), SelectionDir::Right)))
 						{
 						{
 							mInputCaret->moveCaretLeft();
 							mInputCaret->moveCaretLeft();
 						}
 						}
 
 
-						showSelection(getCaretSelectionCharIdx(SelectionDir::Left));
+						showSelection(mInputCaret->getCaretPos(), SelectionDir::Left);
 					}
 					}
 
 
 					moveSelectionUp();
 					moveSelectionUp();
@@ -586,12 +601,12 @@ namespace BansheeEngine
 				{
 				{
 					if(!mSelectionShown)
 					if(!mSelectionShown)
 					{
 					{
-						if(isNewlineChar(getCaretSelectionCharIdx(SelectionDir::Left)))
+						if(isNewlineChar(caretPosToSelectionChar(mInputCaret->getCaretPos(), SelectionDir::Left)))
 						{
 						{
 							mInputCaret->moveCaretRight();
 							mInputCaret->moveCaretRight();
 						}
 						}
 
 
-						showSelection(getCaretSelectionCharIdx(SelectionDir::Left));
+						showSelection(mInputCaret->getCaretPos(), SelectionDir::Left);
 					}
 					}
 
 
 					moveSelectionDown();
 					moveSelectionDown();
@@ -617,10 +632,11 @@ namespace BansheeEngine
 				{
 				{
 					if(mSelectionShown)
 					if(mSelectionShown)
 					{
 					{
-						mText.erase(mText.begin() + mSelectionStart, mText.begin() + mSelectionEnd);
+						UINT32 selStart = getSelectionStart();
+						mText.erase(mText.begin() + selStart, mText.begin() + getSelectionEnd());
 						mInputCaret->updateText(getTextDesc());
 						mInputCaret->updateText(getTextDesc());
 
 
-						mInputCaret->moveCaretToChar(mSelectionStart, CARET_BEFORE);
+						mInputCaret->moveCaretToChar(selStart, CARET_BEFORE);
 						scrollTextToCaret();
 						scrollTextToCaret();
 						clearSelection();
 						clearSelection();
 					}
 					}
@@ -639,10 +655,8 @@ namespace BansheeEngine
 
 
 			if(ev.getKey() == BC_A && ev.isCtrlDown())
 			if(ev.getKey() == BC_A && ev.isCtrlDown())
 			{
 			{
-				showSelection(0);
-
-				mSelectionStart = 0;
-				mSelectionEnd = (UINT32)mText.size();
+				showSelection(0, SelectionDir::Left);
+				selectAll();
 
 
 				markAsDirty();
 				markAsDirty();
 				return true;
 				return true;
@@ -652,10 +666,11 @@ namespace BansheeEngine
 		{
 		{
 			if(mSelectionShown)
 			if(mSelectionShown)
 			{
 			{
-				mText.erase(mText.begin() + mSelectionStart, mText.begin() + mSelectionEnd);
+				UINT32 selStart = getSelectionStart();
+				mText.erase(mText.begin() + selStart, mText.begin() + getSelectionEnd());
 				mInputCaret->updateText(getTextDesc());
 				mInputCaret->updateText(getTextDesc());
 
 
-				mInputCaret->moveCaretToChar(mSelectionStart, CARET_BEFORE);
+				mInputCaret->moveCaretToChar(selStart, CARET_BEFORE);
 				clearSelection();
 				clearSelection();
 			}
 			}
 
 
@@ -740,11 +755,16 @@ namespace BansheeEngine
 		markAsDirty();
 		markAsDirty();
 	}
 	}
 
 
-	void GUIInputBox::showSelection(UINT32 startChar)
+	void GUIInputBox::showSelection(CM::UINT32 caretPos, SelectionDir dir)
 	{
 	{
-		mSelectionStart = startChar;
-		mSelectionEnd = startChar;
-		mSelectionAnchor = startChar;
+		UINT32 charIdx = mInputCaret->getCharIdxAtCaretPos(caretPos);
+
+		if(dir == SelectionDir::Right)
+			charIdx = (UINT32)std::max(0, (INT32)(charIdx - 1));
+
+		mSelectionStart = charIdx;
+		mSelectionEnd = charIdx;
+		mSelectionAnchor = charIdx;
 		mSelectionShown = true;
 		mSelectionShown = true;
 		markAsDirty();
 		markAsDirty();
 	}
 	}
@@ -759,9 +779,9 @@ namespace BansheeEngine
 		markAsDirty();
 		markAsDirty();
 	}
 	}
 
 
-	UINT32 GUIInputBox::getCaretSelectionCharIdx(SelectionDir dir) const
+	UINT32 GUIInputBox::caretPosToSelectionChar(UINT32 caretPos, SelectionDir dir) const
 	{
 	{
-		UINT32 charIdx = mInputCaret->getCharIdxAtCaretPos();
+		UINT32 charIdx = mInputCaret->getCharIdxAtCaretPos(caretPos);
 
 
 		if(dir == SelectionDir::Right)
 		if(dir == SelectionDir::Right)
 			charIdx = (UINT32)std::max(0, (INT32)(charIdx - 1));
 			charIdx = (UINT32)std::max(0, (INT32)(charIdx - 1));
@@ -791,13 +811,13 @@ namespace BansheeEngine
 
 
 			if(!skipNewline) // Move one more if we moved to a new line (we can't select newline char so we skip it)
 			if(!skipNewline) // Move one more if we moved to a new line (we can't select newline char so we skip it)
 			{
 			{
-				if (isNewlineChar(getCaretSelectionCharIdx(newlineTestSelectionDir)) && mInputCaret->getCaretPos() > 0)
+				if (isNewlineChar(caretPosToSelectionChar(mInputCaret->getCaretPos(), newlineTestSelectionDir)) && mInputCaret->getCaretPos() > 0)
 				{
 				{
 					mInputCaret->moveCaretLeft();
 					mInputCaret->moveCaretLeft();
 
 
 					// Reverse caret movement if previous char was a newline, and this one is as well.
 					// Reverse caret movement if previous char was a newline, and this one is as well.
 					// Otherwise we skip an entire line which is not what we want.
 					// Otherwise we skip an entire line which is not what we want.
-					if (isNewlineChar(getCaretSelectionCharIdx(newlineTestSelectionDir))) 
+					if (isNewlineChar(caretPosToSelectionChar(mInputCaret->getCaretPos(), newlineTestSelectionDir))) 
 						mInputCaret->moveCaretRight();
 						mInputCaret->moveCaretRight();
 				} 
 				} 
 			}
 			}
@@ -805,12 +825,12 @@ namespace BansheeEngine
 			{
 			{
 				// Reverse caret movement if previous char was a newline, and this one is as well
 				// Reverse caret movement if previous char was a newline, and this one is as well
 				// so we don't skip a line
 				// so we don't skip a line
-				if (isNewlineChar(getCaretSelectionCharIdx(newlineTestSelectionDir))) 
+				if (isNewlineChar(caretPosToSelectionChar(mInputCaret->getCaretPos(), newlineTestSelectionDir))) 
 					mInputCaret->moveCaretRight();
 					mInputCaret->moveCaretRight();
 			}
 			}
 		}
 		}
 
 
-		UINT32 charIdx = getCaretSelectionCharIdx(SelectionDir::Left);
+		UINT32 charIdx = caretPosToSelectionChar(mInputCaret->getCaretPos(), SelectionDir::Left);
 		
 		
 		if(mSelectionAnchor == mSelectionEnd)
 		if(mSelectionAnchor == mSelectionEnd)
 			mSelectionStart = std::min(mSelectionEnd, charIdx); 
 			mSelectionStart = std::min(mSelectionEnd, charIdx); 
@@ -836,13 +856,13 @@ namespace BansheeEngine
 
 
 			if(!skipNewline) // Move one more if we moved to a new line (we can't select newline char so we skip it)
 			if(!skipNewline) // Move one more if we moved to a new line (we can't select newline char so we skip it)
 			{
 			{
-				if (isNewlineChar(getCaretSelectionCharIdx(newlineTestSelectionDir)) && mInputCaret->getCaretPos() < maxCaretPos)
+				if (isNewlineChar(caretPosToSelectionChar(mInputCaret->getCaretPos(), newlineTestSelectionDir)) && mInputCaret->getCaretPos() < maxCaretPos)
 				{
 				{
 					mInputCaret->moveCaretRight();
 					mInputCaret->moveCaretRight();
 
 
 					// Reverse caret movement if previous char was a newline, and this one is as well.
 					// Reverse caret movement if previous char was a newline, and this one is as well.
 					// Otherwise we skip an entire line which is not what we want.
 					// Otherwise we skip an entire line which is not what we want.
-					if (isNewlineChar(getCaretSelectionCharIdx(newlineTestSelectionDir))) 
+					if (isNewlineChar(caretPosToSelectionChar(mInputCaret->getCaretPos(), newlineTestSelectionDir))) 
 						mInputCaret->moveCaretLeft();
 						mInputCaret->moveCaretLeft();
 				} 
 				} 
 			}
 			}
@@ -850,12 +870,12 @@ namespace BansheeEngine
 			{
 			{
 				// Reverse caret movement if previous char was a newline, and this one is as well.
 				// Reverse caret movement if previous char was a newline, and this one is as well.
 				// Otherwise we skip an entire line which is not what we want.
 				// Otherwise we skip an entire line which is not what we want.
-				if (isNewlineChar(getCaretSelectionCharIdx(newlineTestSelectionDir))) 
+				if (isNewlineChar(caretPosToSelectionChar(mInputCaret->getCaretPos(), newlineTestSelectionDir))) 
 					mInputCaret->moveCaretLeft();
 					mInputCaret->moveCaretLeft();
 			}
 			}
 		}
 		}
 
 
-		UINT32 charIdx = getCaretSelectionCharIdx(SelectionDir::Left);
+		UINT32 charIdx = caretPosToSelectionChar(mInputCaret->getCaretPos(), SelectionDir::Left);
 
 
 		if(mSelectionAnchor == mSelectionStart)
 		if(mSelectionAnchor == mSelectionStart)
 			mSelectionEnd = std::max(mSelectionStart, charIdx);
 			mSelectionEnd = std::max(mSelectionStart, charIdx);
@@ -883,7 +903,7 @@ namespace BansheeEngine
 		else
 		else
 		{
 		{
 			mInputCaret->moveCaretUp();
 			mInputCaret->moveCaretUp();
-			UINT32 charIdx = getCaretSelectionCharIdx(SelectionDir::Left);
+			UINT32 charIdx = caretPosToSelectionChar(mInputCaret->getCaretPos(), SelectionDir::Left);
 
 
 			if(charIdx > mSelectionAnchor)
 			if(charIdx > mSelectionAnchor)
 			{
 			{
@@ -918,7 +938,7 @@ namespace BansheeEngine
 		else
 		else
 		{
 		{
 			mInputCaret->moveCaretDown();
 			mInputCaret->moveCaretDown();
-			UINT32 charIdx = getCaretSelectionCharIdx(SelectionDir::Left);
+			UINT32 charIdx = caretPosToSelectionChar(mInputCaret->getCaretPos(), SelectionDir::Left);
 
 
 			if(charIdx > mSelectionAnchor)
 			if(charIdx > mSelectionAnchor)
 			{
 			{
@@ -936,6 +956,17 @@ namespace BansheeEngine
 			clearSelection();
 			clearSelection();
 	}
 	}
 
 
+	void GUIInputBox::selectAll()
+	{
+		mSelectionStart = 0;
+		mSelectionEnd = (UINT32)mText.size();
+	}
+
+	bool GUIInputBox::isSelectionEmpty() const
+	{
+		return mSelectionStart == mSelectionEnd;
+	}
+
 	Vector<Rect>::type GUIInputBox::getSelectionRects() const
 	Vector<Rect>::type GUIInputBox::getSelectionRects() const
 	{
 	{
 		Vector<Rect>::type selectionRects;
 		Vector<Rect>::type selectionRects;

+ 170 - 0
BansheeEngine/Source/BsGUIInputSelection.cpp

@@ -0,0 +1,170 @@
+#include "BsGUIInputSelection.h"
+#include "BsImageSprite.h"
+#include "BsGUIManager.h"
+
+using namespace CamelotFramework;
+
+namespace BansheeEngine
+{
+	GUIInputSelection::GUIInputSelection(const TEXT_SPRITE_DESC& textDesc)
+		:mSelectionStart(0), mSelectionEnd(0), mSelectionAnchor(0), mSelectionDragAnchor(0),
+		mTextSprite(nullptr)
+	{
+		mTextSprite = cm_new<TextSprite, PoolAlloc>();
+
+		mTextSprite->update(mTextDesc);
+	}
+
+	GUIInputSelection::~GUIInputSelection()
+	{
+		for(auto& sprite : mSprites)
+			cm_delete<PoolAlloc>(sprite);
+
+		cm_delete<PoolAlloc>(mTextSprite);
+	}
+
+	void GUIInputSelection::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 GUIInputSelection::updateSprite(const CM::Int2& offset)
+	{
+		Vector<Rect>::type selectionRects = getSelectionRects();
+
+		INT32 diff = (INT32)(mSprites.size() - selectionRects.size());
+
+		if(diff > 0)
+		{
+			for(UINT32 i = (UINT32)selectionRects.size(); i < (UINT32)mSprites.size(); i++)
+				cm_delete(mSprites[i]);
+
+			mSprites.erase(mSprites.begin() + selectionRects.size(), mSprites.end());
+		}
+		else if(diff < 0)
+		{
+			for(INT32 i = diff; i < 0; i++)
+			{
+				ImageSprite* newSprite = cm_new<ImageSprite>();
+				mSprites.push_back(newSprite);
+			}
+		}
+
+		UINT32 idx = 0;
+		for(auto& sprite : mSprites)
+		{
+			IMAGE_SPRITE_DESC desc;
+			desc.offset = Int2(selectionRects[idx].x, selectionRects[idx].y);
+			desc.width = selectionRects[idx].width;
+			desc.height = selectionRects[idx].height;
+			desc.clipRect = Rect(mTextDesc.offset.x - selectionRects[idx].x, 
+				mTextDesc.offset.y - selectionRects[idx].y, mTextDesc.width, mTextDesc.height);
+			desc.texture = GUIManager::instance().getTextSelectionTexture();
+
+			sprite->update(desc);
+			idx++;
+		}
+	}
+
+	Vector<Rect>::type GUIInputSelection::getSelectionRects() const
+	{
+		Vector<Rect>::type selectionRects;
+
+		if(mSelectionStart == mSelectionEnd)
+			return selectionRects;
+
+		UINT32 startLine = mTextSprite->getLineForChar(mSelectionStart);
+
+		UINT32 endLine = startLine;
+		if(mSelectionEnd > 0)
+			endLine = mTextSprite->getLineForChar(mSelectionEnd - 1, true);
+
+		{
+			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(startLine);
+
+			UINT32 startCharIdx = mSelectionStart;
+
+			UINT32 endCharIdx = mSelectionEnd - 1;
+			if(startLine != endLine)
+			{
+				endCharIdx = lineDesc.getEndChar(false);
+				if(endCharIdx > 0)
+					endCharIdx = endCharIdx - 1;
+			}
+
+			if(!isNewlineChar(startCharIdx) && !isNewlineChar(endCharIdx))
+			{
+				Rect startChar = mTextSprite->getCharRect(startCharIdx);
+				Rect endChar = mTextSprite->getCharRect(endCharIdx);
+
+				Rect selectionRect;
+				selectionRect.x = startChar.x;
+				selectionRect.y = lineDesc.getLineYStart();
+				selectionRect.height = lineDesc.getLineHeight();
+				selectionRect.width = (endChar.x + endChar.width) - startChar.x;
+
+				selectionRects.push_back(selectionRect);
+			}
+		}
+
+		for(UINT32 i = startLine + 1; i < endLine; i++)
+		{
+			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(i);
+			if(lineDesc.getStartChar() == lineDesc.getEndChar() || isNewlineChar(lineDesc.getStartChar()))
+				continue;
+
+			UINT32 endCharIdx = lineDesc.getEndChar(false);
+			if(endCharIdx > 0)
+				endCharIdx = endCharIdx - 1;
+
+			Rect startChar = mTextSprite->getCharRect(lineDesc.getStartChar());
+			Rect endChar = mTextSprite->getCharRect(endCharIdx);
+
+			Rect selectionRect;
+			selectionRect.x = startChar.x;
+			selectionRect.y = lineDesc.getLineYStart();
+			selectionRect.height = lineDesc.getLineHeight();
+			selectionRect.width = (endChar.x + endChar.width) - startChar.x;
+
+			selectionRects.push_back(selectionRect);
+		}
+
+		if(startLine != endLine)
+		{
+			const SpriteLineDesc& lineDesc = mTextSprite->getLineDesc(endLine);
+
+			if(lineDesc.getStartChar() != lineDesc.getEndChar() && !isNewlineChar(lineDesc.getStartChar()))
+			{
+				UINT32 endCharIdx = mSelectionEnd - 1;
+
+				if(!isNewlineChar(endCharIdx))
+				{
+					Rect startChar = mTextSprite->getCharRect(lineDesc.getStartChar());
+					Rect endChar = mTextSprite->getCharRect(endCharIdx);
+
+					Rect selectionRect;
+					selectionRect.x = startChar.x;
+					selectionRect.y = lineDesc.getLineYStart();
+					selectionRect.height = lineDesc.getLineHeight();
+					selectionRect.width = (endChar.x + endChar.width) - startChar.x;
+
+					selectionRects.push_back(selectionRect);
+				}
+			}
+		}
+
+		return selectionRects;
+	}
+
+	bool GUIInputSelection::isNewlineChar(CM::UINT32 charIdx) const
+	{
+		if(mTextDesc.text[charIdx] == '\n')
+			return true;
+
+		return false;
+	}
+}