HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
EditableText.hpp
1// Copyright 2019, 2020 Pokitec
2// All rights reserved.
3
4#pragma once
5
6#include "TTauri/Text/AttributedGrapheme.hpp"
7#include "TTauri/Text/ShapedText.hpp"
8#include "TTauri/Text/Font.hpp"
9#include <string>
10#include <vector>
11
12namespace tt {
13
16 ShapedText _shapedText;
17
21 float width = 0.0f;
22
25 bool insertMode = true;
26
29 ssize_t cursorIndex = 0;
30
34 ssize_t selectionIndex = 0;
35
36 TextStyle currentStyle;
37
40 bool hasPartialGrapheme = false;
41
42public:
43 EditableText(TextStyle style) :
44 text(), _shapedText(), currentStyle(style)
45 {
46 }
47
50 void updateShapedText() noexcept {
51 auto text_ = text;
52
53 // Make sure there is an end-paragraph marker in the text.
54 // This allows the shapedText to figure out the style of the text of an empty paragraph.
55 if (ssize(text) == 0) {
56 text_.emplace_back(Grapheme('\n'), currentStyle, 0);
57 } else {
58 text_.emplace_back(Grapheme('\n'), text_.back().style, 0);
59 }
60
61 _shapedText = ShapedText(text_, width, Alignment::TopLeft, false);
62 }
63
64 [[nodiscard]] ShapedText shapedText() const noexcept {
65 return _shapedText;
66 }
67
68 void setWidth(float _width) noexcept {
69 width = _width;
71 }
72
73 void setCurrentStyle(TextStyle style) noexcept {
74 this->currentStyle = style;
75 }
76
79 void setStyleOfAll(TextStyle style) noexcept {
80 setCurrentStyle(style);
81 for (auto &c: text) {
82 c.style = style;
83 }
85 }
86
87 size_t size() const noexcept {
88 return text.size();
89 }
90
93 decltype(auto) it(ssize_t index) noexcept {
94 tt_assume(index >= 0);
95 // Index should never be at text.cend();
96 tt_assume(index < ssize(text));
97
98 return text.begin() + index;
99 }
100
103 decltype(auto) cit(ssize_t index) const noexcept {
104 tt_assume(index >= 0);
105 // Index should never be beyond text.cend();
106 tt_assume(index <= ssize(text));
107
108 return text.cbegin() + index;
109 }
110
111 decltype(auto) it(ssize_t index) const noexcept {
112 return cit(index);
113 }
114
117 aarect partialGraphemeCaret() const noexcept {
118 if (hasPartialGrapheme) {
119 tt_assume(cursorIndex != 0);
120 return _shapedText.leftToRightCaret(cursorIndex - 1, false);
121 } else {
122 return {};
123 }
124 }
125
128 aarect leftToRightCaret() const noexcept {
129 return _shapedText.leftToRightCaret(cursorIndex, insertMode);
130 }
131
135 auto r = std::vector<aarect>{};
136 if (selectionIndex < cursorIndex) {
137 r = _shapedText.selectionRectangles(selectionIndex, cursorIndex);
138 } else if (selectionIndex > cursorIndex) {
139 r = _shapedText.selectionRectangles(cursorIndex, selectionIndex);
140 }
141 return r;
142 }
143
148 void deleteSelection() noexcept {
149 if (selectionIndex < cursorIndex) {
150 text.erase(cit(selectionIndex), cit(cursorIndex));
151 cursorIndex = selectionIndex;
153 } else if (selectionIndex > cursorIndex) {
154 text.erase(cit(cursorIndex), cit(selectionIndex));
155 selectionIndex = cursorIndex;
157 }
158 }
159
162 ssize_t characterIndexAtPosition(vec position) const noexcept;
163
164 void setCursorAtCoordinate(vec coordinate) noexcept {
165 if (ttlet newCursorPosition = _shapedText.indexOfCharAtCoordinate(coordinate)) {
166 selectionIndex = cursorIndex = *newCursorPosition;
167 tt_assume(selectionIndex >= 0);
168 tt_assume(selectionIndex <= ssize(text));
169 }
170 }
171
172 void selectWordAtCoordinate(vec coordinate) noexcept {
173 if (ttlet newCursorPosition = _shapedText.indexOfCharAtCoordinate(coordinate)) {
174 std::tie(selectionIndex, cursorIndex) = _shapedText.indicesOfWord(*newCursorPosition);
175 tt_assume(selectionIndex >= 0);
176 tt_assume(selectionIndex <= ssize(text));
177 tt_assume(cursorIndex >= 0);
178 tt_assume(cursorIndex <= ssize(text));
179 }
180 }
181
182 void selectParagraphAtCoordinate(vec coordinate) noexcept {
183 if (ttlet newCursorPosition = _shapedText.indexOfCharAtCoordinate(coordinate)) {
184 std::tie(selectionIndex, cursorIndex) = _shapedText.indicesOfParagraph(*newCursorPosition);
185 tt_assume(selectionIndex >= 0);
186 tt_assume(selectionIndex <= ssize(text));
187 tt_assume(cursorIndex >= 0);
188 tt_assume(cursorIndex <= ssize(text));
189 }
190 }
191
192 void dragCursorAtCoordinate(vec coordinate) noexcept {
193 if (ttlet newCursorPosition = _shapedText.indexOfCharAtCoordinate(coordinate)) {
194 cursorIndex = *newCursorPosition;
195 tt_assume(cursorIndex >= 0);
196 tt_assume(cursorIndex <= ssize(text));
197 }
198 }
199
200 void dragWordAtCoordinate(vec coordinate) noexcept {
201 if (ttlet newCursorPosition = _shapedText.indexOfCharAtCoordinate(coordinate)) {
202 ttlet [a, b] = _shapedText.indicesOfWord(*newCursorPosition);
203
204 if (selectionIndex <= cursorIndex) {
205 if (a < selectionIndex) {
206 // Reverse selection
207 selectionIndex = cursorIndex;
208 cursorIndex = a;
209 } else {
210 cursorIndex = b;
211 }
212 } else {
213 if (b > selectionIndex) {
214 // Reverse selection
215 selectionIndex = cursorIndex;
216 cursorIndex = b;
217 } else {
218 cursorIndex = a;
219 }
220 }
221
222 tt_assume(selectionIndex >= 0);
223 tt_assume(selectionIndex <= ssize(text));
224 tt_assume(cursorIndex >= 0);
225 tt_assume(cursorIndex <= ssize(text));
226 }
227 }
228
229 void dragParagraphAtCoordinate(vec coordinate) noexcept {
230 if (ttlet newCursorPosition = _shapedText.indexOfCharAtCoordinate(coordinate)) {
231 ttlet [a, b] = _shapedText.indicesOfParagraph(*newCursorPosition);
232
233 if (selectionIndex <= cursorIndex) {
234 if (a < selectionIndex) {
235 // Reverse selection
236 selectionIndex = cursorIndex;
237 cursorIndex = a;
238 } else {
239 cursorIndex = b;
240 }
241 } else {
242 if (b > selectionIndex) {
243 // Reverse selection
244 selectionIndex = cursorIndex;
245 cursorIndex = b;
246 } else {
247 cursorIndex = a;
248 }
249 }
250
251 tt_assume(selectionIndex >= 0);
252 tt_assume(selectionIndex <= ssize(text));
253 tt_assume(cursorIndex >= 0);
254 tt_assume(cursorIndex <= ssize(text));
255 }
256 }
257
258 void cancelPartialGrapheme() noexcept {
259 if (hasPartialGrapheme) {
260 tt_assume(cursorIndex >= 1);
261
262 selectionIndex = --cursorIndex;
263 tt_assume(selectionIndex >= 0);
264 tt_assume(selectionIndex <= ssize(text));
265 tt_assume(cursorIndex >= 0);
266 tt_assume(cursorIndex <= ssize(text));
267
268 text.erase(cit(cursorIndex));
269 hasPartialGrapheme = false;
270
272 }
273 }
274
280 void insertPartialGrapheme(Grapheme character) noexcept {
281 cancelPartialGrapheme();
283
284 text.emplace(cit(cursorIndex), character, currentStyle);
285 selectionIndex = ++cursorIndex;
286 tt_assume(selectionIndex >= 0);
287 tt_assume(selectionIndex <= ssize(text));
288 tt_assume(cursorIndex >= 0);
289 tt_assume(cursorIndex <= ssize(text));
290
291 hasPartialGrapheme = true;
293 }
294
298 void insertGrapheme(Grapheme character) noexcept {
299 cancelPartialGrapheme();
301
302 if (!insertMode) {
303 handleCommand("text.delete.char.next"_ltag);
304 }
305 text.emplace(cit(cursorIndex), character, currentStyle);
306 selectionIndex = ++cursorIndex;
307 tt_assume(selectionIndex >= 0);
308 tt_assume(selectionIndex <= ssize(text));
309 tt_assume(cursorIndex >= 0);
310 tt_assume(cursorIndex <= ssize(text));
311
313 }
314
315 void handlePaste(std::string str) noexcept {
316 cancelPartialGrapheme();
318
319 gstring gstr = to_gstring(str);
320
321 auto str_attr = std::vector<AttributedGrapheme>{};
322 str_attr.reserve(ssize(gstr));
323 for (ttlet &g: gstr) {
324 str_attr.emplace_back(g, currentStyle);
325 }
326
327 text.insert(cit(cursorIndex), str_attr.cbegin(), str_attr.cend());
328 selectionIndex = cursorIndex += ssize(str_attr);
329 tt_assume(selectionIndex >= 0);
330 tt_assume(selectionIndex <= ssize(text));
331 tt_assume(cursorIndex >= 0);
332 tt_assume(cursorIndex <= ssize(text));
333
335 }
336
337 std::string handleCopy() noexcept {
338 auto r = std::string{};
339
340 if (selectionIndex < cursorIndex) {
341 r.reserve(cursorIndex - selectionIndex);
342 for (auto i = cit(selectionIndex); i != cit(cursorIndex); ++i) {
343 r += to_string(i->grapheme);
344 }
345 } else if (selectionIndex > cursorIndex) {
346 r.reserve(selectionIndex - cursorIndex);
347 for (auto i = cit(cursorIndex); i != cit(selectionIndex); ++i) {
348 r += to_string(i->grapheme);
349 }
350 }
351 return r;
352 }
353
354 std::string handleCut() noexcept {
355 auto r = handleCopy();
356 cancelPartialGrapheme();
358 return r;
359 }
360
361 void handleCommand(string_ltag command) noexcept {
362 tt_assume(cursorIndex <= ssize(text));
363 cancelPartialGrapheme();
364
365 if (command == "text.cursor.char.left"_ltag) {
366 if (ttlet newCursorPosition = _shapedText.indexOfCharOnTheLeft(cursorIndex)) {
367 // XXX Change currentStyle based on the grapheme at the new cursor position.
368 selectionIndex = cursorIndex = *newCursorPosition;
369 }
370 } else if (command == "text.cursor.char.right"_ltag) {
371 if (ttlet newCursorPosition = _shapedText.indexOfCharOnTheRight(cursorIndex)) {
372 selectionIndex = cursorIndex = *newCursorPosition;
373 }
374 } else if (command == "text.cursor.word.left"_ltag) {
375 if (ttlet newCursorPosition = _shapedText.indexOfWordOnTheLeft(cursorIndex)) {
376 selectionIndex = cursorIndex = *newCursorPosition;
377 }
378 } else if (command == "text.cursor.word.right"_ltag) {
379 if (ttlet newCursorPosition = _shapedText.indexOfWordOnTheRight(cursorIndex)) {
380 selectionIndex = cursorIndex = *newCursorPosition;
381 }
382 } else if (command == "text.cursor.word.right"_ltag) {
383 if (ttlet newCursorPosition = _shapedText.indexOfWordOnTheRight(cursorIndex)) {
384 selectionIndex = cursorIndex = *newCursorPosition;
385 }
386 } else if (command == "text.cursor.line.end"_ltag) {
387 selectionIndex = cursorIndex = size() - 1;
388 } else if (command == "text.cursor.line.begin"_ltag) {
389 selectionIndex = cursorIndex = 0;
390 } else if (command == "text.select.char.left"_ltag) {
391 if (ttlet newCursorPosition = _shapedText.indexOfCharOnTheLeft(cursorIndex)) {
392 cursorIndex = *newCursorPosition;
393 }
394 } else if (command == "text.select.char.right"_ltag) {
395 if (ttlet newCursorPosition = _shapedText.indexOfCharOnTheRight(cursorIndex)) {
396 cursorIndex = *newCursorPosition;
397 }
398 } else if (command == "text.select.word.left"_ltag) {
399 if (ttlet newCursorPosition = _shapedText.indexOfWordOnTheLeft(cursorIndex)) {
400 cursorIndex = *newCursorPosition;
401 }
402 } else if (command == "text.select.word.right"_ltag) {
403 if (ttlet newCursorPosition = _shapedText.indexOfWordOnTheRight(cursorIndex)) {
404 cursorIndex = *newCursorPosition;
405 }
406 } else if (command == "text.select.word"_ltag) {
407 std::tie(selectionIndex, cursorIndex) = _shapedText.indicesOfWord(cursorIndex);
408 } else if (command == "text.select.line.end"_ltag) {
409 cursorIndex = size() - 1;
410 } else if (command == "text.select.line.begin"_ltag) {
411 cursorIndex = 0;
412 } else if (command == "text.select.document"_ltag) {
413 selectionIndex = 0;
414 cursorIndex = size() - 1; // Upto end-of-paragraph marker.
415 } else if (command == "text.mode.insert"_ltag) {
416 insertMode = !insertMode;
417 } else if (command == "text.delete.char.prev"_ltag) {
418 if (cursorIndex != selectionIndex) {
420
421 } else if (cursorIndex >= 1) {
422 selectionIndex = --cursorIndex;
423 text.erase(cit(cursorIndex));
425 }
426 } else if (command == "text.delete.char.next"_ltag) {
427 if (cursorIndex != selectionIndex) {
429
430 } else if (cursorIndex < (ssize(text) - 1)) {
431 // Don't delete the trailing paragraph separator.
432 text.erase(cit(cursorIndex));
434 }
435 }
436
437 tt_assume(selectionIndex >= 0);
438 tt_assume(selectionIndex <= ssize(text));
439 tt_assume(cursorIndex >= 0);
440 tt_assume(cursorIndex <= ssize(text));
441 }
442};
443
444
445}
446
Class which represents an axis-aligned rectangle.
Definition aarect.hpp:13
A 4D vector.
Definition vec.hpp:37
Definition EditableText.hpp:14
std::vector< aarect > selectionRectangles() const noexcept
Get a set of rectangles for which text is selected.
Definition EditableText.hpp:134
decltype(auto) cit(ssize_t index) const noexcept
Return the text iterator at index.
Definition EditableText.hpp:103
void deleteSelection() noexcept
Delete a selection.
Definition EditableText.hpp:148
aarect leftToRightCaret() const noexcept
Get carets at the cursor position.
Definition EditableText.hpp:128
void setStyleOfAll(TextStyle style) noexcept
Change the text style of all graphemes.
Definition EditableText.hpp:79
void insertGrapheme(Grapheme character) noexcept
Definition EditableText.hpp:298
void insertPartialGrapheme(Grapheme character) noexcept
Definition EditableText.hpp:280
aarect partialGraphemeCaret() const noexcept
Get carets at the cursor position.
Definition EditableText.hpp:117
decltype(auto) it(ssize_t index) noexcept
Return the text iterator at index.
Definition EditableText.hpp:93
void updateShapedText() noexcept
Update the shaped text after changed to text.
Definition EditableText.hpp:50
ssize_t characterIndexAtPosition(vec position) const noexcept
Definition Grapheme.hpp:20
Definition gstring.hpp:12
ShapedText represent a piece of text shaped to be displayed.
Definition ShapedText.hpp:21
std::pair< ssize_t, ssize_t > indicesOfWord(ssize_t logicalIndex) const noexcept
Return the index at the left side and right side of a word.
std::optional< ssize_t > indexOfCharOnTheRight(ssize_t logicalIndex) const noexcept
Return the index of the character to the right.
aarect leftToRightCaret(ssize_t index, bool overwrite) const noexcept
Return the cursor-carets.
std::pair< ssize_t, ssize_t > indicesOfParagraph(ssize_t logicalIndex) const noexcept
Return the index at the left side and right side of a paragraph.
std::vector< aarect > selectionRectangles(ssize_t first, ssize_t last) const noexcept
Return a list of merged rectangles to display for the selection.
std::optional< ssize_t > indexOfCharOnTheLeft(ssize_t logicalIndex) const noexcept
Return the index of the character to the left.
std::optional< ssize_t > indexOfWordOnTheRight(ssize_t logicalIndex) const noexcept
Return the index of the word to the right.
std::optional< ssize_t > indexOfCharAtCoordinate(vec coordinate) const noexcept
Return the index of the character .
std::optional< ssize_t > indexOfWordOnTheLeft(ssize_t logicalIndex) const noexcept
Return the index of the word to the left.
Definition TextStyle.hpp:15
T begin(T... args)
T emplace_back(T... args)
T emplace(T... args)
T erase(T... args)
T insert(T... args)
T reserve(T... args)
T size(T... args)
T tie(T... args)
T to_string(T... args)