1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "GUI/BsGUIInputSelection.h"
4#include "Image/BsSpriteTexture.h"
5#include "2D/BsImageSprite.h"
6#include "GUI/BsGUIElement.h"
7#include "GUI/BsGUIManager.h"
8
9namespace bs
10{
11 GUIInputSelection::~GUIInputSelection()
12 {
13 for(auto& sprite : mSprites)
14 bs_delete(sprite);
15 }
16
17 void GUIInputSelection::updateSprite()
18 {
19 mSelectionRects = getSelectionRects();
20
21 INT32 diff = (INT32)(mSprites.size() - mSelectionRects.size());
22
23 if(diff > 0)
24 {
25 for(UINT32 i = (UINT32)mSelectionRects.size(); i < (UINT32)mSprites.size(); i++)
26 bs_delete(mSprites[i]);
27
28 mSprites.erase(mSprites.begin() + mSelectionRects.size(), mSprites.end());
29 }
30 else if(diff < 0)
31 {
32 for(INT32 i = diff; i < 0; i++)
33 {
34 ImageSprite* newSprite = bs_new<ImageSprite>();
35 mSprites.push_back(newSprite);
36 }
37 }
38
39 const GUIWidget* widget = nullptr;
40 if (mElement != nullptr)
41 widget = mElement->_getParentWidget();
42
43 UINT32 idx = 0;
44 for(auto& sprite : mSprites)
45 {
46 IMAGE_SPRITE_DESC desc;
47 desc.width = mSelectionRects[idx].width;
48 desc.height = mSelectionRects[idx].height;
49 desc.texture = GUIManager::instance().getTextSelectionTexture();
50
51 sprite->update(desc, (UINT64)widget);
52 idx++;
53 }
54 }
55
56 Vector2I GUIInputSelection::getSelectionSpriteOffset(UINT32 spriteIdx) const
57 {
58 return Vector2I(mSelectionRects[spriteIdx].x, mSelectionRects[spriteIdx].y) + getTextOffset();
59 }
60
61 Rect2I GUIInputSelection::getSelectionSpriteClipRect(UINT32 spriteIdx, const Rect2I& parentClipRect) const
62 {
63 Vector2I selectionOffset(mSelectionRects[spriteIdx].x, mSelectionRects[spriteIdx].y);
64 Vector2I clipOffset = selectionOffset + mElement->_getTextInputOffset();
65
66 Rect2I clipRect(-clipOffset.x, -clipOffset.y, mTextDesc.width, mTextDesc.height);
67
68 Rect2I localParentCliprect = parentClipRect;
69
70 // Move parent rect to our space
71 localParentCliprect.x += mElement->_getTextInputOffset().x + clipRect.x;
72 localParentCliprect.y += mElement->_getTextInputOffset().y + clipRect.y;
73
74 // Clip our rectangle so its not larger then the parent
75 clipRect.clip(localParentCliprect);
76
77 // Increase clip size by 1, so we can fit the caret in case it is fully at the end of the text
78 clipRect.width += 1;
79
80 return clipRect;
81 }
82
83 Vector<Rect2I> GUIInputSelection::getSelectionRects() const
84 {
85 Vector<Rect2I> selectionRects;
86
87 if(mSelectionStart == mSelectionEnd)
88 return selectionRects;
89
90 UINT32 startLine = getLineForChar(mSelectionStart);
91
92 UINT32 endLine = startLine;
93 if(mSelectionEnd > 0)
94 endLine = getLineForChar(mSelectionEnd - 1, true);
95
96 {
97 const GUIInputLineDesc& lineDesc = getLineDesc(startLine);
98
99 UINT32 startCharIdx = mSelectionStart;
100
101 UINT32 endCharIdx = mSelectionEnd - 1;
102 if(startLine != endLine)
103 {
104 endCharIdx = lineDesc.getEndChar(false);
105 if(endCharIdx > 0)
106 endCharIdx = endCharIdx - 1;
107 }
108
109 if(!isNewlineChar(startCharIdx) && !isNewlineChar(endCharIdx))
110 {
111 Rect2I startChar = getLocalCharRect(startCharIdx);
112 Rect2I endChar = getLocalCharRect(endCharIdx);
113
114 Rect2I selectionRect;
115 selectionRect.x = startChar.x;
116 selectionRect.y = lineDesc.getLineYStart();
117 selectionRect.height = lineDesc.getLineHeight();
118 selectionRect.width = (endChar.x + endChar.width) - startChar.x;
119
120 selectionRects.push_back(selectionRect);
121 }
122 }
123
124 for(UINT32 i = startLine + 1; i < endLine; i++)
125 {
126 const GUIInputLineDesc& lineDesc = getLineDesc(i);
127 if(lineDesc.getStartChar() == lineDesc.getEndChar() || isNewlineChar(lineDesc.getStartChar()))
128 continue;
129
130 UINT32 endCharIdx = lineDesc.getEndChar(false);
131 if(endCharIdx > 0)
132 endCharIdx = endCharIdx - 1;
133
134 Rect2I startChar = getLocalCharRect(lineDesc.getStartChar());
135 Rect2I endChar = getLocalCharRect(endCharIdx);
136
137 Rect2I selectionRect;
138 selectionRect.x = startChar.x;
139 selectionRect.y = lineDesc.getLineYStart();
140 selectionRect.height = lineDesc.getLineHeight();
141 selectionRect.width = (endChar.x + endChar.width) - startChar.x;
142
143 selectionRects.push_back(selectionRect);
144 }
145
146 if(startLine != endLine)
147 {
148 const GUIInputLineDesc& lineDesc = getLineDesc(endLine);
149
150 if(lineDesc.getStartChar() != lineDesc.getEndChar() && !isNewlineChar(lineDesc.getStartChar()))
151 {
152 UINT32 endCharIdx = mSelectionEnd - 1;
153
154 if(!isNewlineChar(endCharIdx))
155 {
156 Rect2I startChar = getLocalCharRect(lineDesc.getStartChar());
157 Rect2I endChar = getLocalCharRect(endCharIdx);
158
159 Rect2I selectionRect;
160 selectionRect.x = startChar.x;
161 selectionRect.y = lineDesc.getLineYStart();
162 selectionRect.height = lineDesc.getLineHeight();
163 selectionRect.width = (endChar.x + endChar.width) - startChar.x;
164
165 selectionRects.push_back(selectionRect);
166 }
167 }
168 }
169
170 return selectionRects;
171 }
172
173 void GUIInputSelection::showSelection(UINT32 anchorCaretPos)
174 {
175 UINT32 charIdx = getCharIdxAtInputIdx(anchorCaretPos);
176
177 mSelectionStart = charIdx;
178 mSelectionEnd = charIdx;
179 mSelectionAnchor = charIdx;
180 }
181
182 void GUIInputSelection::clearSelectionVisuals()
183 {
184 for(auto& sprite : mSprites)
185 bs_delete(sprite);
186
187 mSprites.clear();
188 }
189
190 void GUIInputSelection::selectionDragStart(UINT32 caretPos)
191 {
192 clearSelectionVisuals();
193
194 showSelection(caretPos);
195 mSelectionDragAnchor = caretPos;
196 }
197
198 void GUIInputSelection::selectionDragUpdate(UINT32 caretPos)
199 {
200 if(caretPos < mSelectionDragAnchor)
201 {
202 mSelectionStart = getCharIdxAtInputIdx(caretPos);
203 mSelectionEnd = getCharIdxAtInputIdx(mSelectionDragAnchor);
204
205 mSelectionAnchor = mSelectionStart;
206 }
207
208 if(caretPos > mSelectionDragAnchor)
209 {
210 mSelectionStart = getCharIdxAtInputIdx(mSelectionDragAnchor);
211 mSelectionEnd = getCharIdxAtInputIdx(caretPos);
212
213 mSelectionAnchor = mSelectionEnd;
214 }
215
216 if(caretPos == mSelectionDragAnchor)
217 {
218 mSelectionStart = mSelectionAnchor;
219 mSelectionEnd = mSelectionAnchor;
220 }
221 }
222
223 void GUIInputSelection::selectionDragEnd()
224 {
225 if(isSelectionEmpty())
226 clearSelectionVisuals();
227 }
228
229 void GUIInputSelection::moveSelectionToCaret(UINT32 caretPos)
230 {
231 UINT32 charIdx = getCharIdxAtInputIdx(caretPos);
232
233 if(charIdx > mSelectionAnchor)
234 {
235 mSelectionStart = mSelectionAnchor;
236 mSelectionEnd = charIdx;
237 }
238 else
239 {
240 mSelectionStart = charIdx;
241 mSelectionEnd = mSelectionAnchor;
242 }
243
244 if(mSelectionStart == mSelectionEnd)
245 clearSelectionVisuals();
246 }
247
248 void GUIInputSelection::selectAll()
249 {
250 mSelectionStart = 0;
251 mSelectionEnd = mNumChars;
252 }
253
254 bool GUIInputSelection::isSelectionEmpty() const
255 {
256 return mSelectionStart == mSelectionEnd;
257 }
258}