Skip to content

Commit 8e2ec4a

Browse files
authored
feat: Selection, copy, cut and placeholder added to TextBox (#3)
1 parent 8b0ad5e commit 8e2ec4a

File tree

4 files changed

+189
-1
lines changed

4 files changed

+189
-1
lines changed

demo/demo.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ int main()
5555
gui::Theme::input.textColor = hex2color("#000");
5656
gui::Theme::input.textColorHover = hex2color("#000");
5757
gui::Theme::input.textColorFocus = hex2color("#000");
58+
gui::Theme::input.textSelectionColor = hex2color("#8791AD");
59+
gui::Theme::input.textPlaceholderColor = hex2color("#8791AD");
5860
gui::Theme::PADDING = 2.f;
5961
gui::Theme::windowBgColor = defaultTheme.backgroundColor;
6062

@@ -72,6 +74,7 @@ int main()
7274
text.setString(textbox->getText());
7375
text.setOrigin(text.getLocalBounds().width / 2, text.getLocalBounds().height / 2);
7476
});
77+
textbox->setPlaceholder("Type something!");
7578
form->addRow("Text", textbox);
7679

7780
gui::TextBox* textbox2 = new gui::TextBox();

src/Gui/TextBox.cpp

+153-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ TextBox::TextBox(float width):
2121
m_text.setFillColor(Theme::input.textColor);
2222
m_text.setCharacterSize(Theme::textSize);
2323

24+
m_placeholder.setFont(Theme::getFont());
25+
m_placeholder.setPosition(offset, offset);
26+
m_placeholder.setFillColor(Theme::input.textPlaceholderColor);
27+
m_placeholder.setCharacterSize(Theme::textSize);
28+
2429
// Build cursor
2530
m_cursor.setPosition(offset, offset);
2631
m_cursor.setSize(sf::Vector2f(1.f, Theme::getLineSpacing()));
@@ -127,6 +132,11 @@ void TextBox::onKeyPressed(const sf::Event::KeyEvent& key)
127132
break;
128133

129134
case sf::Keyboard::BackSpace:
135+
if(!m_selectedText.isEmpty())
136+
{
137+
deleteSelectedText();
138+
break;
139+
}
130140
// Erase character before cursor
131141
if (m_cursorPos > 0)
132142
{
@@ -139,6 +149,11 @@ void TextBox::onKeyPressed(const sf::Event::KeyEvent& key)
139149
break;
140150

141151
case sf::Keyboard::Delete:
152+
if(!m_selectedText.isEmpty())
153+
{
154+
deleteSelectedText();
155+
break;
156+
}
142157
// Erase character after cursor
143158
if (m_cursorPos < m_text.getString().getSize())
144159
{
@@ -162,10 +177,19 @@ void TextBox::onKeyPressed(const sf::Event::KeyEvent& key)
162177
triggerCallback();
163178
break;
164179

180+
case sf::Keyboard::A:
181+
if(key.control)
182+
{
183+
setSelectedText(0, m_text.getString().getSize());
184+
}
185+
break;
186+
165187
// Ctrl+V: paste clipboard
166188
case sf::Keyboard::V:
167189
if (key.control)
168190
{
191+
// Delete selected text and write clipboard string over it.
192+
deleteSelectedText();
169193
sf::String string = m_text.getString();
170194
sf::String clipboardString = sf::Clipboard::getString();
171195
// Trim clipboard content if needed
@@ -180,6 +204,27 @@ void TextBox::onKeyPressed(const sf::Event::KeyEvent& key)
180204
}
181205
break;
182206

207+
case sf::Keyboard::C:
208+
if(key.control)
209+
{
210+
if(!m_selectedText.isEmpty())
211+
{
212+
sf::Clipboard::setString(m_selectedText);
213+
}
214+
}
215+
break;
216+
217+
case sf::Keyboard::X:
218+
if(key.control)
219+
{
220+
if(!m_selectedText.isEmpty())
221+
{
222+
sf::Clipboard::setString(m_selectedText);
223+
deleteSelectedText();
224+
}
225+
}
226+
break;
227+
183228
default:
184229
break;
185230
}
@@ -201,10 +246,46 @@ void TextBox::onMousePressed(float x, float)
201246
}
202247

203248

249+
void TextBox::onMouseReleased(float x, float y)
250+
{
251+
for (int i = m_text.getString().getSize(); i >= 0; --i)
252+
{
253+
// Place cursor after the character under the mouse
254+
sf::Vector2f glyph_pos = m_text.findCharacterPos(i);
255+
if (glyph_pos.x <= x)
256+
{
257+
setSelectedText(m_cursorPos, i);
258+
setCursor(i);
259+
break;
260+
}
261+
}
262+
}
263+
264+
265+
void TextBox::onMouseMoved(float x, float y)
266+
{
267+
if(sf::Mouse::isButtonPressed(sf::Mouse::Left))
268+
{
269+
for (int i = m_text.getString().getSize(); i >= 0; --i)
270+
{
271+
// Place cursor after the character under the mouse
272+
sf::Vector2f glyph_pos = m_text.findCharacterPos(i);
273+
if (glyph_pos.x <= x)
274+
{
275+
setSelectedText(m_cursorPos, i);
276+
break;
277+
}
278+
}
279+
}
280+
}
281+
282+
204283
void TextBox::onTextEntered(sf::Uint32 unicode)
205284
{
206285
if (unicode > 30 && (unicode < 127 || unicode > 159))
207286
{
287+
// Delete selected text when a new input is received
288+
deleteSelectedText();
208289
sf::String string = m_text.getString();
209290
if (string.getSize() < m_maxLength)
210291
{
@@ -220,6 +301,12 @@ void TextBox::onTextEntered(sf::Uint32 unicode)
220301
void TextBox::onStateChanged(State state)
221302
{
222303
m_box.applyState(state);
304+
305+
// Discard selection when focus is lost
306+
if(state != State::StateFocused)
307+
{
308+
setSelectedText(0,0);
309+
}
223310
}
224311

225312

@@ -230,9 +317,27 @@ void TextBox::draw(sf::RenderTarget& target, sf::RenderStates states) const
230317

231318
// Crop the text with GL Scissor
232319
glEnable(GL_SCISSOR_TEST);
320+
233321
sf::Vector2f pos = getAbsolutePosition();
234322
glScissor(pos.x + Theme::borderSize, target.getSize().y - (pos.y + getSize().y), getSize().x, getSize().y);
235-
target.draw(m_text, states);
323+
324+
if(m_text.getString().isEmpty())
325+
{
326+
target.draw(m_placeholder, states);
327+
}
328+
else
329+
{
330+
// Draw selection indicator
331+
if(!m_selectedText.isEmpty())
332+
{
333+
sf::RectangleShape selRect;
334+
selRect.setPosition(m_text.findCharacterPos(m_selectionFirst));
335+
selRect.setSize({m_text.findCharacterPos(m_selectionLast).x - m_text.findCharacterPos(m_selectionFirst).x, m_cursor.getSize().y});
336+
selRect.setFillColor(Theme::input.textSelectionColor);
337+
target.draw(selRect, states);
338+
}
339+
target.draw(m_text, states);
340+
}
236341

237342
glDisable(GL_SCISSOR_TEST);
238343

@@ -253,4 +358,51 @@ void TextBox::draw(sf::RenderTarget& target, sf::RenderStates states) const
253358
}
254359
}
255360

361+
362+
void TextBox::setSelectedText(size_t from, size_t to)
363+
{
364+
if(from != to)
365+
{
366+
m_selectionLast = std::max(from, to);
367+
m_selectionFirst = std::min(from, to);
368+
m_selectedText = m_text.getString().substring(m_selectionFirst, m_selectionLast - m_selectionFirst);
369+
}
370+
else
371+
{
372+
m_selectionFirst = m_selectionLast = 0;
373+
m_selectedText.clear();
374+
}
375+
}
376+
377+
const sf::String& TextBox::getSelectedText() const
378+
{
379+
return m_selectedText;
380+
}
381+
382+
383+
void TextBox::deleteSelectedText()
384+
{
385+
// Delete if any selected text
386+
if(!m_selectedText.isEmpty())
387+
{
388+
sf::String str = m_text.getString();
389+
str.erase(m_selectionFirst, m_selectionLast - m_selectionFirst);
390+
setCursor(m_selectionFirst);
391+
setSelectedText(0,0);
392+
m_text.setString(str);
393+
}
394+
}
395+
396+
397+
void TextBox::setPlaceholder(const sf::String& placeholder)
398+
{
399+
m_placeholder.setString(placeholder);
400+
}
401+
402+
403+
const sf::String& TextBox::getPlaceholder() const
404+
{
405+
return m_placeholder.getString();
406+
}
407+
256408
}

src/Gui/TextBox.hpp

+31
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,53 @@ class TextBox: public Widget
4141
*/
4242
size_t getCursor() const;
4343

44+
/**
45+
* Set selected text
46+
*/
47+
void setSelectedText(size_t from, size_t to);
48+
49+
/**
50+
* Get selected text
51+
*/
52+
const sf::String& getSelectedText() const;
53+
54+
/**
55+
* Set placeholder text
56+
*/
57+
void setPlaceholder(const sf::String& placeholder);
58+
59+
/**
60+
* Get placeholder text
61+
*/
62+
const sf::String& getPlaceholder() const;
63+
4464
protected:
4565
// Callbacks
4666
void onKeyPressed(const sf::Event::KeyEvent& key) override;
4767
void onMousePressed(float x, float y) override;
68+
void onMouseReleased(float x, float y) override;
69+
void onMouseMoved(float x, float y) override;
4870
void onTextEntered(sf::Uint32 unicode) override;
4971
void onStateChanged(State state) override;
5072

5173
private:
5274
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
5375

76+
/**
77+
* Delete selected text if any
78+
*/
79+
void deleteSelectedText();
80+
5481
sf::Text m_text;
82+
sf::Text m_placeholder;
5583
Box m_box;
5684
mutable sf::RectangleShape m_cursor;
5785
mutable sf::Clock m_cursorTimer;
5886
size_t m_cursorPos;
5987
size_t m_maxLength;
88+
size_t m_selectionFirst;
89+
size_t m_selectionLast;
90+
sf::String m_selectedText;
6091
};
6192

6293
}

src/Gui/Theme.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class Theme
5151
sf::Color textColor;
5252
sf::Color textColorHover;
5353
sf::Color textColorFocus;
54+
sf::Color textSelectionColor;
55+
sf::Color textPlaceholderColor;
5456
};
5557

5658
static Style click;

0 commit comments

Comments
 (0)