FifeGUI 0.2.0
A C++ GUI library designed for games.
text.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#include "fifechan/text.hpp"
6
7#include <algorithm>
8#include <numeric>
9#include <string>
10#include <utility>
11
12#include "fifechan/exception.hpp"
13#include "fifechan/font.hpp"
14#include "fifechan/rectangle.hpp"
15
16namespace fcn
17{
18 Text::Text() = default;
19
20 Text::Text(std::string const & content)
21 {
22 std::string::size_type lastPos = 0;
23 std::string::size_type pos = content.find('\n', lastPos);
24
25 for (; pos != std::string::npos; pos = content.find('\n', lastPos)) {
26 int const length = pos - lastPos;
27 std::string const sub = content.substr(lastPos, length);
28 mRows.push_back(sub);
29 lastPos = pos + 1;
30 }
31
32 // Add the last part after the last newline or the entire string if no newline is found
33 std::string const sub = content.substr(lastPos);
34 if (!sub.empty()) {
35 mRows.push_back(sub);
36 }
37 }
38
39 Text::~Text() = default;
40
41 void Text::setContent(std::string const & content)
42 {
43 // Reset caret
45 mCaretRow = 0;
46 mCaretColumn = 0;
47
48 mRows.clear();
49 std::string::size_type lastPos = 0;
50 std::string::size_type pos = 0;
51
52 for (; (pos = content.find('\n', lastPos)) != std::string::npos; lastPos = pos + 1) {
53 int const length = pos - lastPos;
54 std::string const sub = content.substr(lastPos, length);
55 mRows.push_back(sub);
56 }
57
58 // Add the last part after the last newline or the entire string if no newline is found
59 std::string const sub = content.substr(lastPos);
60 if (!sub.empty()) {
61 mRows.push_back(sub);
62 }
63 }
64
65 std::string Text::getContent() const
66 {
67 if (mRows.empty()) {
68 return {};
69 }
70
71 std::string result;
72 for (size_t i = 0; i < mRows.size() - 1; ++i) {
73 result.append(mRows[i]).append("\n");
74 }
75
76 result.append(mRows.back());
77
78 return result;
79 }
80
81 void Text::setRow(unsigned int row, std::string const & content)
82 {
83 if (row >= mRows.size()) {
84 throwException("Row out of bounds!");
85 }
86
87 mRows[row] = content;
88 }
89
90 void Text::addRow(std::string const & row)
91 {
92 unsigned int i = 0;
93 for (i = 0; i < row.size(); i++) {
94 if (row[i] == '\n') {
95 throwException("Line feed not allowed in the row to be added!");
96 }
97 }
98
99 mRows.push_back(row);
100 }
101
102 void Text::insertRow(std::string const & row, unsigned int position)
103 {
104 unsigned int const totalRows = mRows.size();
105
106 if (position >= totalRows) {
107 if (position == totalRows) {
108 addRow(row);
109 return;
110 }
111 throwException("Position out of bounds!");
112 }
113
114 unsigned int i = 0;
115 for (i = 0; i < row.size(); i++) {
116 if (row[i] == '\n') {
117 throwException("Line feed not allowed in the row to be inserted!");
118 }
119 }
120
121 mRows.insert(mRows.begin() + position, row);
122 }
123
124 void Text::eraseRow(unsigned int row)
125 {
126 if (row >= mRows.size()) {
127 throwException("Row to be erased out of bounds!");
128 }
129
130 mRows.erase(mRows.begin() + row);
131 }
132
133 std::string& Text::getRow(unsigned int row)
134 {
135 if (row >= mRows.size()) {
136 throwException("Row out of bounds!");
137 }
138
139 return mRows[row];
140 }
141
142 void Text::insert(int character)
143 {
144 char const c = static_cast<char>(character);
145
146 if (mRows.empty()) {
147 if (c == '\n') {
148 mRows.emplace_back("");
149 } else {
150 mRows.emplace_back(1, c);
151 }
152 } else {
153 if (c == '\n') {
154 mRows.insert(
155 mRows.begin() + mCaretRow + 1,
158 } else {
159 mRows[mCaretRow].insert(mCaretColumn, std::string(1, c));
160 }
161 }
162
164 }
165
166 void Text::remove(int numberOfCharacters)
167 {
168 if (mRows.empty() || numberOfCharacters == 0) {
169 return;
170 }
171
172 // We should remove characters left of the caret position.
173 if (numberOfCharacters < 0) {
174 while (numberOfCharacters != 0) {
175 // If the caret position is zero there is nothing
176 // more to do.
177 if (mCaretPosition == 0) {
178 break;
179 }
180
181 // If we are at the end of the row
182 // and the row is not the first row we
183 // need to merge two rows.
184 if (mCaretColumn == 0 && mCaretRow != 0) {
186 mRows.erase(mRows.begin() + mCaretRow);
189 } else {
190 mRows[mCaretRow].erase(mCaretColumn - 1, 1);
192 }
193
194 numberOfCharacters++;
195 }
196 } else if (numberOfCharacters > 0) {
197 // We should remove characters right of the caret position.
198 while (numberOfCharacters != 0) {
199 // If all rows have been removed there is nothing
200 // more to do.
201 if (mRows.empty()) {
202 break;
203 }
204
205 // If we are at the end of row and the row
206 // is not the last row we need to merge two
207 // rows.
208 if (mCaretColumn == mRows[mCaretRow].size() && mCaretRow < (mRows.size() - 1)) {
210 mRows.erase(mRows.begin() + mCaretRow + 1);
211 } else {
212 mRows[mCaretRow].erase(mCaretColumn, 1);
213 }
214
215 numberOfCharacters--;
216 }
217 }
218 }
219
221 {
222 return mCaretPosition;
223 }
224
225 void Text::setCaretPosition(int position)
226 {
227 if (mRows.empty() || position < 0) {
228 mCaretPosition = 0;
229 mCaretRow = 0;
230 mCaretColumn = 0;
231 return;
232 }
233
234 // Loop through all rows until we find the
235 // position of the caret.
236 unsigned int i = 0;
237 unsigned int total = 0;
238 for (i = 0; i < mRows.size(); i++) {
239 if (std::cmp_less_equal(position, total + mRows[i].size())) {
240 mCaretRow = i;
241 mCaretColumn = position - total;
242 mCaretPosition = position;
243 return;
244 }
245
246 // Add one for the line feed.
247 total += mRows[i].size() + 1;
248 }
249
250 // The position is beyond the content.
251
252 // Remove one as the last line doesn't have a line feed.
253 mCaretPosition = total - 1;
254 mCaretRow = mRows.size() - 1;
255 mCaretColumn = mRows[mCaretRow].size();
256 }
257
258 void Text::setCaretPosition(int x, int y, Font* font)
259 {
260 if (mRows.empty()) {
261 return;
262 }
263
264 setCaretRow(y / font->getHeight());
266 }
267
269 {
270 return mCaretColumn;
271 }
272
274 {
275 return mCaretRow;
276 }
277
278 void Text::setCaretColumn(int column)
279 {
280 if (mRows.empty() || column < 0) {
281 mCaretColumn = 0;
282 } else if (std::cmp_greater(column, mRows[mCaretRow].size())) {
283 mCaretColumn = mRows[mCaretRow].size();
284 } else {
285 mCaretColumn = column;
286 }
287
289 }
290
291 void Text::setCaretRow(int row)
292 {
293 if (mRows.empty() || row < 0) {
294 mCaretRow = 0;
295 } else if (std::cmp_greater_equal(row, mRows.size())) {
296 mCaretRow = mRows.size() - 1;
297 } else {
298 mCaretRow = row;
299 }
300
302 }
303
304 int Text::getCaretX(Font* font) const
305 {
306 if (mRows.empty()) {
307 return 0;
308 }
309
310 return font->getWidth(mRows[mCaretRow].substr(0, mCaretColumn));
311 }
312
313 int Text::getCaretY(Font* font) const
314 {
315 return mCaretRow * font->getHeight();
316 ;
317 }
318
320 {
321 if (mRows.empty()) {
322 return {0, 0, font->getWidth(" "), font->getHeight()};
323 }
324
325 int width = 0;
326 for (auto const & mRow : mRows) {
327 int const w = font->getWidth(mRow);
328 width = std::max(width, w);
329 }
330
331 auto h = static_cast<int>(font->getHeight() * mRows.size());
332 auto w = width + font->getWidth(" ");
333
334 return {0, 0, w, h};
335 }
336
338 {
339 Rectangle dim;
340 dim.x = !mRows.empty() ? font->getWidth(mRows[mCaretRow].substr(0, mCaretColumn)) : 0;
341 dim.y = font->getHeight() * mCaretRow;
342 dim.width = font->getWidth(" ");
343 // We add two for some extra spacing to be sure the whole caret is visible.
344 dim.height = font->getHeight() + 2;
345 return dim;
346 }
347
348 int Text::getWidth(int /*row*/, Font* /*font*/) const
349 {
350 return 0;
351 }
352
353 unsigned int Text::getMaximumCaretRow() const
354 {
355 return 0;
356 }
357
358 unsigned int Text::getMaximumCaretRow(unsigned int /*row*/) const
359 {
360 return 0;
361 }
362
363 unsigned int Text::getNumberOfCharacters() const
364 {
365 return std::accumulate(mRows.begin(), mRows.end(), 0U, [](unsigned int sum, auto const & row) {
366 return sum + row.size() + 1;
367 });
368 }
369
370 unsigned int Text::getNumberOfRows() const
371 {
372 return mRows.size();
373 }
374
375 unsigned int Text::getNumberOfCharacters(unsigned int row) const
376 {
377 if (row >= mRows.size()) {
378 return 0;
379 }
380
381 return mRows[row].size();
382 }
383
385 {
386 unsigned int total = 0;
387 for (auto i = 0; std::cmp_less(i, mCaretRow); i++) {
388 // Add one for the line feed.
389 total += mRows[i].size() + 1;
390 }
391
393 }
394} // namespace fcn
Abstract interface for font rendering.
Definition font.hpp:24
virtual int getWidth(std::string const &text) const =0
Gets the width of a string.
virtual int getHeight() const =0
Gets the height of the glyphs in the font.
virtual int getStringIndexAt(std::string const &text, int x) const
Gets a string index in a string providing an x coordinate.
Definition font.cpp:11
Represents a rectangular area (X, Y, Width, Height).
Definition rectangle.hpp:20
int width
Holds the width of the rectangle.
int y
Holds the x coordinate of the rectangle.
int x
Holds the x coordinate of the rectangle.
int height
Holds the height of the rectangle.
unsigned int mCaretColumn
Holds the column the caret is in.
Definition text.hpp:297
virtual void setCaretColumn(int column)
Sets the column the caret should be in.
Definition text.cpp:278
virtual unsigned int getNumberOfCharacters() const
Gets the number of characters in the text.
Definition text.cpp:363
virtual unsigned int getMaximumCaretRow() const
Gets the maximum row the caret can be in.
Definition text.cpp:353
virtual void setContent(std::string const &content)
Sets the content of the text.
Definition text.cpp:41
virtual unsigned int getNumberOfRows() const
Gets the number of rows in the text.
Definition text.cpp:370
virtual int getWidth(int row, Font *font) const
Gets the width in pixels of a row.
Definition text.cpp:348
virtual std::string & getRow(unsigned int row)
Gets a reference to a row.
Definition text.cpp:133
virtual void setCaretRow(int row)
Sets the row the caret should be in.
Definition text.cpp:291
virtual int getCaretPosition() const
Gets the caret position.
Definition text.cpp:220
virtual void addRow(std::string const &row)
Adds a row to the content.
Definition text.cpp:90
virtual void setRow(unsigned int row, std::string const &content)
Sets the content of a row.
Definition text.cpp:81
virtual void remove(int numberOfCharacters)
Removes a given number of characters at starting at the current caret position.
Definition text.cpp:166
unsigned int mCaretRow
Holds the row the caret is in.
Definition text.hpp:291
virtual int getCaretColumn() const
Gets the column the caret is currently in.
Definition text.cpp:268
virtual void insertRow(std::string const &row, unsigned int position)
Inserts a row before the specified row position.
Definition text.cpp:102
virtual void setCaretPosition(int position)
Sets the caret position.
Definition text.cpp:225
virtual int getCaretX(Font *font) const
Gets the x coordinate of the caret in pixels given a font.
Definition text.cpp:304
unsigned int mCaretPosition
Holds the position of the caret.
Definition text.hpp:285
virtual int getCaretRow() const
Gets the row the caret is currently in.
Definition text.cpp:273
virtual void eraseRow(unsigned int row)
Erases the given row.
Definition text.cpp:124
virtual int getCaretY(Font *font) const
Gets the y coordinate of the caret in pixels given a font.
Definition text.cpp:313
std::vector< std::string > mRows
Holds the text row by row.
Definition text.hpp:279
virtual std::string getContent() const
Gets the content of the text.
Definition text.cpp:65
virtual void insert(int character)
Inserts a character at the current caret position.
Definition text.cpp:142
void calculateCaretPositionFromRowAndColumn()
Calculates the caret position from the caret row and caret column.
Definition text.cpp:384
virtual Rectangle getDimension(Font *font) const
Gets the dimension in pixels of the text given a font.
Definition text.cpp:319
virtual Rectangle getCaretDimension(Font *font) const
Gets the caret dimension relative to this text.
Definition text.cpp:337