FifeGUI 0.3.0
A C++ GUI library designed for games.
textbox.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause
2// SPDX-FileCopyrightText: 2004 - 2008 Olof Naessén and Per Larsson
3// SPDX-FileCopyrightText: 2013 - 2026 Fifengine contributors
4
5// Corresponding header include
6#include <fifechan/widgets/textbox.hpp>
7
8// Standard library includes
9#include <cassert>
10#include <string>
11
12// Third-party library includes
13#include <utf8cpp/utf8.h>
14
15// Project headers (subdirs before local)
16#include "fifechan/events/textinputevent.hpp"
17#include "fifechan/font.hpp"
18#include "fifechan/graphics.hpp"
19#include "fifechan/key.hpp"
20#include "fifechan/mouseinput.hpp"
21#include "fifechan/text.hpp"
22#include "fifechan/utf8stringeditor.hpp"
23
24namespace fcn
25{
26 TextBox::TextBox(std::string const & text) : mText(new Text(text)), mStringEditor(new UTF8StringEditor)
27 {
28 assert("mText is not null" && mText != nullptr);
29 assert("mStringEditor is not null" && mStringEditor != nullptr);
30
31 setFocusable(true);
32
33 addMouseListener(this);
34 addKeyListener(this);
36 }
37
38 TextBox::~TextBox()
39 {
40 delete mText;
41 delete mStringEditor;
42 }
43
44 void TextBox::setText(std::string const & text)
45 {
46 assert("text is valid utf8" && utf8::is_valid(text.begin(), text.end()));
47
48 mText->setContent(text);
50 }
51
52 void TextBox::draw(Graphics* graphics)
53 {
54 assert("font is not null" && getFont() != nullptr);
55
56 if (mOpaque) {
57 graphics->setColor(getBackgroundColor());
58 graphics->fillRectangle(0, 0, getWidth(), getHeight());
59 }
60
61 if (isFocused() && isEditable()) {
62 drawCaret(graphics, mText->getCaretX(getFont()), mText->getCaretY(getFont()));
63 }
64
65 graphics->setColor(getForegroundColor());
66 graphics->setFont(getFont());
67
68 unsigned int i = 0;
69 for (i = 0; i < mText->getNumberOfRows(); i++) {
70 // Move the text one pixel so we can have a caret before a letter.
71 graphics->drawText(mText->getRow(i), 1, i * getFont()->getHeight());
72 }
73 }
74
75 void TextBox::drawCaret(Graphics* graphics, int x, int y)
76 {
77 assert("font is not null" && getFont() != nullptr);
78
79 graphics->setColor(getForegroundColor());
80 graphics->drawLine(x, y, x, y + getFont()->getHeight());
81 }
82
84 {
85 assert("font is not null" && getFont() != nullptr);
86
87 if (mouseEvent.getButton() == MouseEvent::Button::Left) {
88 mText->setCaretPosition(mouseEvent.getX(), mouseEvent.getY(), getFont());
89 mouseEvent.consume();
90 }
91 }
92
94 {
95 mouseEvent.consume();
96 }
97
99 {
100 Key const key = keyEvent.getKey();
101
102 if (key.getValue() == fcn::Key::LEFT) {
103 // Move caret left, or to end of previous row if at column 0
104 if (getCaretColumn() == 0) {
105 if (getCaretRow() > 0) {
108 }
109 } else {
111 }
112 } else if (key.getValue() == fcn::Key::RIGHT) {
113 // Move caret right, or to start of next row if at end of line
114 if (getCaretColumn() < getTextRow(getCaretRow()).size()) {
116 } else {
117 if (getCaretRow() < getNumberOfRows() - 1) {
120 }
121 }
122 } else if (key.getValue() == fcn::Key::DOWN) {
124 } else if (key.getValue() == fcn::Key::UP) {
126 } else if (key.getValue() == fcn::Key::HOME) {
128 } else if (key.getValue() == fcn::Key::END) {
130 } else if (key.getValue() == fcn::Key::KEY_RETURN && mEditable) {
131 // Split current row at caret: text after caret becomes new row
132 mText->insertRow(
134 getCaretRow() + 1);
135
136 mText->getRow(getCaretRow()).resize(getCaretColumn());
139 } else if (key.getValue() == fcn::Key::BACKSPACE && getCaretColumn() != 0 && mEditable) {
140 // Delete character before caret within current row
141 std::string& currRow = mText->getRow(getCaretRow());
144 } else if (key.getValue() == fcn::Key::BACKSPACE && getCaretColumn() == 0 && getCaretRow() != 0 && mEditable) {
145 // Merge with previous row: delete row, append to previous row
146 unsigned const newCaretColumn = getTextRow(getCaretRow() - 1).size();
147 mText->getRow(getCaretRow() - 1) += getTextRow(getCaretRow());
148 mText->eraseRow(getCaretRow());
150 setCaretColumn(newCaretColumn);
151 } else if (
152 key.getValue() == fcn::Key::KEY_DELETE &&
153 getCaretColumn() < static_cast<int>(getTextRow(getCaretRow()).size()) && mEditable) {
154 // Delete character after caret within current row
156 } else if (
157 key.getValue() == fcn::Key::KEY_DELETE &&
158 getCaretColumn() == static_cast<int>(getTextRow(getCaretRow()).size()) &&
159 getCaretRow() < (static_cast<int>(getNumberOfRows()) - 1) && mEditable) {
160 // Merge with next row: append next row to current, then delete next row
161 mText->getRow(getCaretRow()) += getTextRow((getCaretRow() + 1));
162 mText->eraseRow(getCaretRow() + 1);
163 } else if (key.getValue() == fcn::Key::PAGEUP) {
164 // Move caret up by rowsPerPage, preserving column position
165 Widget* par = getParent();
166
167 if (par != nullptr) {
168 int const rowsPerPage = par->getChildrenArea().height / getFont()->getHeight();
170 int const newCaretRow = getCaretRow() - rowsPerPage;
171 if (newCaretRow >= 0) {
172 setCaretRow(newCaretRow);
173 } else {
174 setCaretRow(0);
175 }
177 }
178 } else if (key.getValue() == fcn::Key::PAGEDOWN) {
179 // Move caret down by rowsPerPage, preserving column position
180 Widget* par = getParent();
181
182 if (par != nullptr) {
183 int const rowsPerPage = par->getChildrenArea().height / getFont()->getHeight();
185 setCaretRow(getCaretRow() + rowsPerPage);
186
187 if (getCaretRow() >= static_cast<int>(getNumberOfRows())) {
189 }
190
192 }
193 } else if (key.getValue() == fcn::Key::TAB && mEditable) {
194 // Insert spaces to align to next tab stop
195 int constexpr tabSize = 4;
196 int const spacesToInsert = tabSize - (getCaretColumn() % tabSize);
197 mText->getRow(getCaretRow()).insert(getCaretColumn(), std::string(spacesToInsert, ' '));
198 setCaretColumn(getCaretColumn() + spacesToInsert);
199 }
200 // Printable characters are handled by textInput(TextInputEvent&).
201
204
205 std::string const & row = getTextRow(getCaretRow());
206
207 assert("text row is valid utf8" && utf8::is_valid(row.begin(), row.end()));
208 assert("caret column position is valid" && utf8::is_valid(row.begin(), row.begin() + getCaretColumn()));
209
210 keyEvent.consume();
211 }
212
214 {
215 std::string const & text = event.getText();
216 if (!text.empty() && mEditable) {
217 mText->getRow(getCaretRow()).insert(getCaretColumn(), text);
218 setCaretColumn(getCaretColumn() + static_cast<int>(text.size()));
221 }
222 }
223
224 void TextBox::resizeToContent(bool recursion)
225 {
226 static_cast<void>(recursion);
228 }
229
231 {
233 }
234
236 {
237 assert("font is not null" && getFont() != nullptr);
238
239 Rectangle const & dim = mText->getDimension(getFont());
240
241 // Default to measured content size
242 int newWidth = static_cast<int>(dim.width);
243 int newHeight = static_cast<int>(dim.height);
244
245 if (isFixedSize()) {
246 return;
247 }
248
249 // Handle expand flags:
250 // if the widget is set to expand in an axis,
251 // keep the current size in that axis so parent layout can
252 // control it instead of content-driven resizing.
253 if (isHorizontalExpand()) {
254 newWidth = getWidth();
255 }
256 if (isVerticalExpand()) {
257 newHeight = getHeight();
258 }
259
260 setSize(newWidth, newHeight);
261 }
262
263 void TextBox::setCaretPosition(unsigned int position)
264 {
265 assert("position is within bounds" && position <= mText->getContent().size());
266 mText->setCaretPosition(position);
267 }
268
269 unsigned int TextBox::getCaretPosition() const
270 {
271 return mText->getCaretPosition();
272 }
273
274 void TextBox::setCaretRowColumn(int row, int column)
275 {
276 assert("row is non-negative" && row >= 0);
277 assert("column is non-negative" && column >= 0);
278 mText->setCaretRow(row);
279 mText->setCaretColumn(column);
280 }
281
283 {
284 assert("row is non-negative" && row >= 0);
285 mText->setCaretRow(row);
286 }
287
288 unsigned int TextBox::getCaretRow() const
289 {
290 return mText->getCaretRow();
291 }
292
293 void TextBox::setCaretColumn(int column)
294 {
295 assert("column is non-negative" && column >= 0);
296 mText->setCaretColumn(column);
297 }
298
299 unsigned int TextBox::getCaretColumn() const
300 {
301 return mText->getCaretColumn();
302 }
303
304 std::string TextBox::getTextRow(int row) const
305 {
306 assert("row is non-negative" && row >= 0);
307 assert("row is within bounds" && static_cast<unsigned>(row) < getNumberOfRows());
308
309 return mText->getRow(row);
310 }
311
312 void TextBox::setTextRow(int row, std::string const & text)
313 {
314 assert("row is non-negative" && row >= 0);
315 assert("row is within bounds" && static_cast<unsigned>(row) < getNumberOfRows());
316 assert("text is valid utf8" && utf8::is_valid(text.begin(), text.end()));
317
318 mText->setRow(row, text);
320 }
321
322 unsigned int TextBox::getNumberOfRows() const
323 {
324 return mText->getNumberOfRows();
325 }
326
327 std::string TextBox::getText() const
328 {
329 return mText->getContent();
330 }
331
333 {
335 }
336
338 {
339 assert("font is not null" && getFont() != nullptr);
340
341 showPart(mText->getCaretDimension(getFont()));
342 }
343
344 void TextBox::setEditable(bool editable)
345 {
346 mEditable = editable;
347 }
348
350 {
351 return mEditable;
352 }
353
354 void TextBox::addRow(std::string const & row)
355 {
356 assert("row text is valid utf8" && utf8::is_valid(row.begin(), row.end()));
357
358 mText->addRow(row);
360 }
361
362 bool TextBox::isOpaque() const
363 {
364 return mOpaque;
365 }
366
367 void TextBox::setOpaque(bool opaque)
368 {
369 mOpaque = opaque;
370 }
371
373 {
374 assert("column is non-negative" && column >= 0);
375
376 // no need to clip the column, mStringEditor handles it automatically
378 }
379
381 {
383 if (row < 0) {
384 row = 0;
385 } else if (row >= getNumberOfRows()) {
386 row = getNumberOfRows() - 1;
387 }
388 setCaretRow(row);
390 }
391
392 void TextBox::setCaretRowColumnUTF8(int row, int column)
393 {
394 assert("row is non-negative" && row >= 0);
395 assert("column is non-negative" && column >= 0);
396
397 setCaretRowUTF8(row);
398 setCaretColumnUTF8(column);
399 }
400} // namespace fcn
virtual int getHeight() const =0
Gets the height of the glyphs in the font.
Abstract interface providing primitive drawing functions (lines, rectangles, etc.).
Definition graphics.hpp:58
void drawText(std::string const &text, int x, int y)
Draws text with a default left alignment.
Definition graphics.hpp:401
virtual void setFont(Font *font)
Sets the font to use when drawing text.
Definition graphics.cpp:91
virtual void drawLine(int x1, int y1, int x2, int y2)=0
Draws a line.
virtual void setColor(Color const &color)=0
Sets the color to use when drawing.
virtual void fillRectangle(Rectangle const &rectangle)=0
Draws a filled rectangle.
void consume()
Marks this event as consumed.
Represents a key event.
Definition keyevent.hpp:26
Key const & getKey() const
Gets the key of the event.
Definition keyevent.cpp:48
Represents a mouse event.
int getX() const
Gets the x coordinate of the mouse event.
int getY() const
Gets the y coordinate of the mouse event.
MouseEvent::Button getButton() const
Gets the button of the mouse event.
Represents a rectangular area (X, Y, Width, Height).
Definition rectangle.hpp:22
int width
Holds the width of the rectangle.
int height
Holds the height of the rectangle.
void fontChanged() override
Called when the font has changed.
Definition textbox.cpp:332
virtual void scrollToCaret()
Scrolls the text to the caret if the text box is in a scroll area.
Definition textbox.cpp:337
void setCaretColumnUTF8(int column)
Sets caret column (UTF-8 aware).
Definition textbox.cpp:372
virtual void addRow(std::string const &row)
Adds a row of text to the end of the text.
Definition textbox.cpp:354
void keyPressed(KeyEvent &keyEvent) override
Called if a key is pressed when the widget has keyboard focus.
Definition textbox.cpp:98
std::string getTextRow(int row) const
Gets a certain row from the text.
Definition textbox.cpp:304
Text * mText
Holds the text of the text box.
Definition textbox.hpp:272
void setCaretRowColumn(int row, int column)
Sets the row and the column where the caret should be currently located.
Definition textbox.cpp:274
void draw(Graphics *graphics) override
Draws the widget.
Definition textbox.cpp:52
void textInput(TextInputEvent &event) override
Called when text input (IME, dead-key, paste) is received.
Definition textbox.cpp:213
void setTextRow(int row, std::string const &text)
Sets the text of a certain row of the text.
Definition textbox.cpp:312
bool mEditable
True if the text box is editable, false otherwise.
Definition textbox.hpp:277
void adjustSizeImpl()
Adjusts the size of the button to fit the caption.
Definition textbox.cpp:235
void resizeToContent(bool recursion=true) override
Resizes the widget's size to fit the content exactly, calls recursively all childs.
Definition textbox.cpp:224
unsigned int getCaretPosition() const
Gets the caret position in the text.
Definition textbox.cpp:269
virtual void drawCaret(Graphics *graphics, int x, int y)
Draws the caret.
Definition textbox.cpp:75
bool mOpaque
True if the text box is opaque, false otherwise.
Definition textbox.hpp:282
std::string getText() const
Gets the text of the text box.
Definition textbox.cpp:327
void setCaretColumn(int column)
Sets the column where the caret should be currently located.
Definition textbox.cpp:293
TextBox(std::string const &text="")
Constructor.
Definition textbox.cpp:26
unsigned int getCaretColumn() const
Gets the column where the caret is currently located.
Definition textbox.cpp:299
void setCaretRow(int row)
Sets the row where the caret should be currently located.
Definition textbox.cpp:282
void mousePressed(MouseEvent &mouseEvent) override
Called when a mouse button has been pressed down on the widget area.
Definition textbox.cpp:83
void setEditable(bool editable)
Sets the text box to be editable or not.
Definition textbox.cpp:344
void adjustSize() override
Adjusts the text box's size to fit the text.
Definition textbox.cpp:230
void setCaretRowUTF8(int row)
Sets caret row (UTF-8 aware).
Definition textbox.cpp:380
void mouseDragged(MouseEvent &mouseEvent) override
Called when the mouse has moved and the mouse has previously been pressed on the widget.
Definition textbox.cpp:93
unsigned int getCaretRow() const
Gets the row number where the caret is currently located.
Definition textbox.cpp:288
void setText(std::string const &text)
Sets the text of the text box.
Definition textbox.cpp:44
bool isOpaque() const
Checks if the text box is opaque.
Definition textbox.cpp:362
void setOpaque(bool opaque)
Sets the text box to be opaque or not.
Definition textbox.cpp:367
UTF8StringEditor * mStringEditor
UTF8StringEditor for UTF8 support.
Definition textbox.hpp:287
unsigned int getNumberOfRows() const
Gets the number of rows in the text.
Definition textbox.cpp:322
bool isEditable() const
Checks if the text box is editable.
Definition textbox.cpp:349
void setCaretPosition(unsigned int position)
Sets the position of the caret in the text.
Definition textbox.cpp:263
void setCaretRowColumnUTF8(int row, int column)
Sets the caret row and column (UTF-8 aware).
Definition textbox.cpp:392
Text input event for IME (input method editor) composition, dead keys, and pasted text.
Helper class for text manipulation within widgets.
Definition text.hpp:32
Utility for editing and handling UTF-8 encoded strings.
static int countChars(std::string const &text, int byteOffset)
Counts characters up to byteOffset.
static int eraseChar(std::string &text, int byteOffset)
Erase character at specified byte offset.
static int getOffset(std::string const &text, int charIndex)
Gets byte offset for character index.
static int nextChar(std::string const &text, int byteOffset)
Returns byte offset of the next character.
static int prevChar(std::string const &text, int byteOffset)
Returns byte offset of the previous character.
bool isHorizontalExpand() const
Gets if widget is horizontal expandable.
Definition widget.cpp:440
void setFocusable(bool focusable)
Sets the widget to be focusable, or not.
Definition widget.cpp:646
int getWidth() const
Gets the width of the widget.
Definition widget.cpp:252
virtual Widget * getParent() const
Gets the widget's parent container.
Definition widget.cpp:239
virtual Rectangle getChildrenArea()
Gets the area of the widget occupied by the widget's children.
Definition widget.cpp:1166
virtual bool isFocused() const
Checks if the widget is focused.
Definition widget.cpp:624
virtual void showPart(Rectangle rectangle)
Shows a certain part of a widget in the widget's parent.
Definition widget.cpp:1309
Widget()
Constructor.
Definition widget.cpp:52
virtual void setSize(int width, int height)
Sets the size of the widget.
Definition widget.cpp:1065
Color const & getBackgroundColor() const
Gets the background color.
Definition widget.cpp:762
void addMouseListener(MouseListener *mouseListener)
Adds a mouse listener to the widget.
Definition widget.cpp:907
void addKeyListener(KeyListener *keyListener)
Adds a key listener to the widget.
Definition widget.cpp:885
Color const & getForegroundColor() const
Gets the foreground color.
Definition widget.cpp:752
Font * getFont() const
Gets the font set for the widget.
Definition widget.cpp:1000
int getHeight() const
Gets the height of the widget.
Definition widget.cpp:265
bool isFixedSize() const
Gets if the widget use a fixed size.
Definition widget.cpp:401
bool isVerticalExpand() const
Gets if widget is vertical expandable.
Definition widget.cpp:430
Used replacement tokens by configure_file():