HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
editable_text.hpp
1// Copyright Take Vos 2019-2021.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "attributed_grapheme.hpp"
8#include "shaped_text.hpp"
9#include "font.hpp"
10#include <string>
11#include <vector>
12
13namespace tt {
14
17 shaped_text _shapedText;
18
22 float width = 0.0f;
23
26 bool insertMode = true;
27
30 ssize_t cursorIndex = 0;
31
35 ssize_t selectionIndex = 0;
36
37 text_style currentStyle;
38
41 bool hasPartialgrapheme = false;
42
43public:
45 text(), _shapedText(), currentStyle(style)
46 {
47 }
48
49 [[nodiscard]] operator std::string() const noexcept
50 {
51 auto r = std::string{};
52
53 for (ttlet &c : text) {
54 r += to_string(c.grapheme.NFC());
55 }
56
57 return r;
58 }
59
60 editable_text &operator=(std::string_view str) noexcept
61 {
62 cancelPartialgrapheme();
63
64 gstring gstr = to_gstring(str);
65
66 text.clear();
67 text.reserve(std::ssize(gstr));
68 for (ttlet &g : gstr) {
69 text.emplace_back(g, currentStyle);
70 }
71
72 selectionIndex = cursorIndex = 0;
73 tt_axiom(selectionIndex >= 0);
74 tt_axiom(selectionIndex <= std::ssize(text));
75 tt_axiom(cursorIndex >= 0);
76 tt_axiom(cursorIndex <= std::ssize(text));
77
79 return *this;
80 }
81
84 void updateshaped_text() noexcept {
85 auto text_ = text;
86
87 // Make sure there is an end-paragraph marker in the text.
88 // This allows the shapedText to figure out the style of the text of an empty paragraph.
89 if (std::ssize(text) == 0) {
90 text_.emplace_back(grapheme::PS(), currentStyle, 0);
91 } else {
92 text_.emplace_back(grapheme::PS(), text_.back().style, 0);
93 }
94
95 _shapedText = shaped_text(text_, width, alignment::top_left, false);
96 }
97
98 [[nodiscard]] shaped_text shapedText() const noexcept {
99 return _shapedText;
100 }
101
102 void setWidth(float _width) noexcept {
103 width = _width;
105 }
106
107 void setCurrentStyle(text_style style) noexcept {
108 this->currentStyle = style;
109 }
110
113 void setStyleOfAll(text_style style) noexcept {
114 setCurrentStyle(style);
115 for (auto &c: text) {
116 c.style = style;
117 }
119 }
120
121 size_t size() const noexcept {
122 return text.size();
123 }
124
127 decltype(auto) it(ssize_t index) noexcept {
128 tt_axiom(index >= 0);
129 // Index should never be at text.cend();
130 tt_axiom(index < std::ssize(text));
131
132 return text.begin() + index;
133 }
134
137 decltype(auto) cit(ssize_t index) const noexcept {
138 tt_axiom(index >= 0);
139 // Index should never be beyond text.cend();
140 tt_axiom(index <= std::ssize(text));
141
142 return text.cbegin() + index;
143 }
144
145 decltype(auto) it(ssize_t index) const noexcept {
146 return cit(index);
147 }
148
151 aarect partialgraphemeCaret() const noexcept {
152 if (hasPartialgrapheme) {
153 tt_axiom(cursorIndex != 0);
154 return _shapedText.leftToRightCaret(cursorIndex - 1, false);
155 } else {
156 return {};
157 }
158 }
159
162 aarect leftToRightCaret() const noexcept {
163 return _shapedText.leftToRightCaret(cursorIndex, insertMode);
164 }
165
169 auto r = std::vector<aarect>{};
170 if (selectionIndex < cursorIndex) {
171 r = _shapedText.selectionRectangles(selectionIndex, cursorIndex);
172 } else if (selectionIndex > cursorIndex) {
173 r = _shapedText.selectionRectangles(cursorIndex, selectionIndex);
174 }
175 return r;
176 }
177
182 void deleteSelection() noexcept {
183 if (selectionIndex < cursorIndex) {
184 text.erase(cit(selectionIndex), cit(cursorIndex));
185 cursorIndex = selectionIndex;
187 } else if (selectionIndex > cursorIndex) {
188 text.erase(cit(cursorIndex), cit(selectionIndex));
189 selectionIndex = cursorIndex;
191 }
192 }
193
196 ssize_t characterIndexAtPosition(f32x4 position) const noexcept;
197
198 void setmouse_cursorAtCoordinate(f32x4 coordinate) noexcept {
199 if (ttlet newmouse_cursorPosition = _shapedText.indexOfCharAtCoordinate(coordinate)) {
200 selectionIndex = cursorIndex = *newmouse_cursorPosition;
201 tt_axiom(selectionIndex >= 0);
202 tt_axiom(selectionIndex <= std::ssize(text));
203 }
204 }
205
206 void selectWordAtCoordinate(f32x4 coordinate) noexcept {
207 if (ttlet newmouse_cursorPosition = _shapedText.indexOfCharAtCoordinate(coordinate)) {
208 std::tie(selectionIndex, cursorIndex) = _shapedText.indicesOfWord(*newmouse_cursorPosition);
209 tt_axiom(selectionIndex >= 0);
210 tt_axiom(selectionIndex <= std::ssize(text));
211 tt_axiom(cursorIndex >= 0);
212 tt_axiom(cursorIndex <= std::ssize(text));
213 }
214 }
215
216 void selectParagraphAtCoordinate(f32x4 coordinate) noexcept {
217 if (ttlet newmouse_cursorPosition = _shapedText.indexOfCharAtCoordinate(coordinate)) {
218 std::tie(selectionIndex, cursorIndex) = _shapedText.indicesOfParagraph(*newmouse_cursorPosition);
219 tt_axiom(selectionIndex >= 0);
220 tt_axiom(selectionIndex <= std::ssize(text));
221 tt_axiom(cursorIndex >= 0);
222 tt_axiom(cursorIndex <= std::ssize(text));
223 }
224 }
225
226 void dragmouse_cursorAtCoordinate(f32x4 coordinate) noexcept {
227 if (ttlet newmouse_cursorPosition = _shapedText.indexOfCharAtCoordinate(coordinate)) {
228 cursorIndex = *newmouse_cursorPosition;
229 tt_axiom(cursorIndex >= 0);
230 tt_axiom(cursorIndex <= std::ssize(text));
231 }
232 }
233
234 void dragWordAtCoordinate(f32x4 coordinate) noexcept {
235 if (ttlet newmouse_cursorPosition = _shapedText.indexOfCharAtCoordinate(coordinate)) {
236 ttlet [a, b] = _shapedText.indicesOfWord(*newmouse_cursorPosition);
237
238 if (selectionIndex <= cursorIndex) {
239 if (a < selectionIndex) {
240 // Reverse selection
241 selectionIndex = cursorIndex;
242 cursorIndex = a;
243 } else {
244 cursorIndex = b;
245 }
246 } else {
247 if (b > selectionIndex) {
248 // Reverse selection
249 selectionIndex = cursorIndex;
250 cursorIndex = b;
251 } else {
252 cursorIndex = a;
253 }
254 }
255
256 tt_axiom(selectionIndex >= 0);
257 tt_axiom(selectionIndex <= std::ssize(text));
258 tt_axiom(cursorIndex >= 0);
259 tt_axiom(cursorIndex <= std::ssize(text));
260 }
261 }
262
263 void dragParagraphAtCoordinate(f32x4 coordinate) noexcept {
264 if (ttlet newmouse_cursorPosition = _shapedText.indexOfCharAtCoordinate(coordinate)) {
265 ttlet [a, b] = _shapedText.indicesOfParagraph(*newmouse_cursorPosition);
266
267 if (selectionIndex <= cursorIndex) {
268 if (a < selectionIndex) {
269 // Reverse selection
270 selectionIndex = cursorIndex;
271 cursorIndex = a;
272 } else {
273 cursorIndex = b;
274 }
275 } else {
276 if (b > selectionIndex) {
277 // Reverse selection
278 selectionIndex = cursorIndex;
279 cursorIndex = b;
280 } else {
281 cursorIndex = a;
282 }
283 }
284
285 tt_axiom(selectionIndex >= 0);
286 tt_axiom(selectionIndex <= std::ssize(text));
287 tt_axiom(cursorIndex >= 0);
288 tt_axiom(cursorIndex <= std::ssize(text));
289 }
290 }
291
292 void cancelPartialgrapheme() noexcept {
293 if (hasPartialgrapheme) {
294 tt_axiom(cursorIndex >= 1);
295
296 selectionIndex = --cursorIndex;
297 tt_axiom(selectionIndex >= 0);
298 tt_axiom(selectionIndex <= std::ssize(text));
299 tt_axiom(cursorIndex >= 0);
300 tt_axiom(cursorIndex <= std::ssize(text));
301
302 text.erase(cit(cursorIndex));
303 hasPartialgrapheme = false;
304
306 }
307 }
308
314 void insertPartialgrapheme(grapheme character) noexcept {
315 cancelPartialgrapheme();
317
318 text.emplace(cit(cursorIndex), character, currentStyle);
319 selectionIndex = ++cursorIndex;
320 tt_axiom(selectionIndex >= 0);
321 tt_axiom(selectionIndex <= std::ssize(text));
322 tt_axiom(cursorIndex >= 0);
323 tt_axiom(cursorIndex <= std::ssize(text));
324
325 hasPartialgrapheme = true;
327 }
328
332 void insertgrapheme(grapheme character) noexcept {
333 cancelPartialgrapheme();
335
336 if (!insertMode) {
337 handle_event(command::text_delete_char_next);
338 }
339 text.emplace(cit(cursorIndex), character, currentStyle);
340 selectionIndex = ++cursorIndex;
341 tt_axiom(selectionIndex >= 0);
342 tt_axiom(selectionIndex <= std::ssize(text));
343 tt_axiom(cursorIndex >= 0);
344 tt_axiom(cursorIndex <= std::ssize(text));
345
347 }
348
349 void handlePaste(std::string str) noexcept {
350 cancelPartialgrapheme();
352
353 gstring gstr = to_gstring(str);
354
355 auto str_attr = std::vector<attributed_grapheme>{};
356 str_attr.reserve(std::ssize(gstr));
357 for (ttlet &g: gstr) {
358 str_attr.emplace_back(g, currentStyle);
359 }
360
361 text.insert(cit(cursorIndex), str_attr.cbegin(), str_attr.cend());
362 selectionIndex = cursorIndex += std::ssize(str_attr);
363 tt_axiom(selectionIndex >= 0);
364 tt_axiom(selectionIndex <= std::ssize(text));
365 tt_axiom(cursorIndex >= 0);
366 tt_axiom(cursorIndex <= std::ssize(text));
367
369 }
370
371 std::string handleCopy() noexcept {
372 auto r = std::string{};
373
374 if (selectionIndex < cursorIndex) {
375 r.reserve(cursorIndex - selectionIndex);
376 for (auto i = cit(selectionIndex); i != cit(cursorIndex); ++i) {
377 r += to_string(i->grapheme);
378 }
379 } else if (selectionIndex > cursorIndex) {
380 r.reserve(selectionIndex - cursorIndex);
381 for (auto i = cit(cursorIndex); i != cit(selectionIndex); ++i) {
382 r += to_string(i->grapheme);
383 }
384 }
385 return r;
386 }
387
388 std::string handleCut() noexcept {
389 auto r = handleCopy();
390 cancelPartialgrapheme();
392 return r;
393 }
394
395 bool handle_event(command command) noexcept {
396 auto handled = false;
397
398 tt_axiom(cursorIndex <= std::ssize(text));
399 cancelPartialgrapheme();
400
401 switch (command) {
402 case command::text_cursor_char_left:
403 handled = true;
404 if (ttlet newmouse_cursorPosition = _shapedText.indexOfCharOnTheLeft(cursorIndex)) {
405 // XXX Change currentStyle based on the grapheme at the new cursor position.
406 selectionIndex = cursorIndex = *newmouse_cursorPosition;
407 }
408 break;
409
410 case command::text_cursor_char_right:
411 handled = true;
412 if (ttlet newmouse_cursorPosition = _shapedText.indexOfCharOnTheRight(cursorIndex)) {
413 selectionIndex = cursorIndex = *newmouse_cursorPosition;
414 }
415 break;
416
417 case command::text_cursor_word_left:
418 handled = true;
419 if (ttlet newmouse_cursorPosition = _shapedText.indexOfWordOnTheLeft(cursorIndex)) {
420 selectionIndex = cursorIndex = *newmouse_cursorPosition;
421 }
422 break;
423
424 case command::text_cursor_word_right:
425 handled = true;
426 if (ttlet newmouse_cursorPosition = _shapedText.indexOfWordOnTheRight(cursorIndex)) {
427 selectionIndex = cursorIndex = *newmouse_cursorPosition;
428 }
429 break;
430
431 case command::text_cursor_line_end:
432 handled = true;
433 selectionIndex = cursorIndex = size() - 1;
434 break;
435
436 case command::text_cursor_line_begin:
437 handled = true;
438 selectionIndex = cursorIndex = 0;
439 break;
440
441 case command::text_select_char_left:
442 handled = true;
443 if (ttlet newmouse_cursorPosition = _shapedText.indexOfCharOnTheLeft(cursorIndex)) {
444 cursorIndex = *newmouse_cursorPosition;
445 }
446 break;
447
448 case command::text_select_char_right:
449 handled = true;
450 if (ttlet newmouse_cursorPosition = _shapedText.indexOfCharOnTheRight(cursorIndex)) {
451 cursorIndex = *newmouse_cursorPosition;
452 }
453 break;
454
455 case command::text_select_word_left:
456 handled = true;
457 if (ttlet newmouse_cursorPosition = _shapedText.indexOfWordOnTheLeft(cursorIndex)) {
458 cursorIndex = *newmouse_cursorPosition;
459 }
460 break;
461
462 case command::text_select_word_right:
463 handled = true;
464 if (ttlet newmouse_cursorPosition = _shapedText.indexOfWordOnTheRight(cursorIndex)) {
465 cursorIndex = *newmouse_cursorPosition;
466 }
467 break;
468
469 case command::text_select_word:
470 handled = true;
471 std::tie(selectionIndex, cursorIndex) = _shapedText.indicesOfWord(cursorIndex);
472 break;
473
474 case command::text_select_line_end:
475 handled = true;
476 cursorIndex = size() - 1;
477 break;
478
479 case command::text_select_line_begin:
480 handled = true;
481 cursorIndex = 0;
482 break;
483
484 case command::text_select_document:
485 handled = true;
486 selectionIndex = 0;
487 cursorIndex = size() - 1; // Upto end-of-paragraph marker.
488 break;
489
490 case command::text_mode_insert:
491 handled = true;
492 insertMode = !insertMode;
493 break;
494
495 case command::text_delete_char_prev:
496 handled = true;
497 if (cursorIndex != selectionIndex) {
499
500 } else if (cursorIndex >= 1) {
501 selectionIndex = --cursorIndex;
502 text.erase(cit(cursorIndex));
504 }
505 break;
506
507 case command::text_delete_char_next:
508 handled = true;
509 if (cursorIndex != selectionIndex) {
511
512 } else if (cursorIndex < (std::ssize(text) - 1)) {
513 // Don't delete the trailing paragraph separator.
514 text.erase(cit(cursorIndex));
516 }
517 default:;
518 }
519
520 tt_axiom(selectionIndex >= 0);
521 tt_axiom(selectionIndex <= std::ssize(text));
522 tt_axiom(cursorIndex >= 0);
523 tt_axiom(cursorIndex <= std::ssize(text));
524 return handled;
525 }
526};
527
528
529}
530
Definition editable_text.hpp:15
decltype(auto) cit(ssize_t index) const noexcept
Return the text iterator at index.
Definition editable_text.hpp:137
void setStyleOfAll(text_style style) noexcept
Change the text style of all graphemes.
Definition editable_text.hpp:113
aarect partialgraphemeCaret() const noexcept
Get carets at the cursor position.
Definition editable_text.hpp:151
void updateshaped_text() noexcept
Update the shaped text after changed to text.
Definition editable_text.hpp:84
void deleteSelection() noexcept
Delete a selection.
Definition editable_text.hpp:182
decltype(auto) it(ssize_t index) noexcept
Return the text iterator at index.
Definition editable_text.hpp:127
ssize_t characterIndexAtPosition(f32x4 position) const noexcept
aarect leftToRightCaret() const noexcept
Get carets at the cursor position.
Definition editable_text.hpp:162
void insertgrapheme(grapheme character) noexcept
Definition editable_text.hpp:332
void insertPartialgrapheme(grapheme character) noexcept
Definition editable_text.hpp:314
std::vector< aarect > selectionRectangles() const noexcept
Get a set of rectangles for which text is selected.
Definition editable_text.hpp:168
Definition grapheme.hpp:21
static grapheme PS() noexcept
Paragraph separator.
Definition grapheme.hpp:196
Definition gstring.hpp:13
shaped_text represent a piece of text shaped to be displayed.
Definition shaped_text.hpp:22
std::optional< ssize_t > indexOfWordOnTheLeft(ssize_t logical_index) const noexcept
Get the first character of the word on the left.
std::optional< ssize_t > indexOfCharOnTheRight(ssize_t logical_index) const noexcept
Get the character right of the given character.
std::optional< ssize_t > indexOfWordOnTheRight(ssize_t logical_index) const noexcept
Get the last character of the word on the right.
std::vector< aarect > selectionRectangles(ssize_t first, ssize_t last) const noexcept
Return a list of merged rectangles to display for the selection.
std::pair< ssize_t, ssize_t > indicesOfWord(ssize_t logical_index) const noexcept
Get the word with the given character.
std::pair< ssize_t, ssize_t > indicesOfParagraph(ssize_t logical_index) const noexcept
Get the character right of the given character.
std::optional< ssize_t > indexOfCharAtCoordinate(f32x4 coordinate) const noexcept
Get the character close to a coordinate.
aarect leftToRightCaret(ssize_t index, bool overwrite) const noexcept
Return the cursor-carets.
std::optional< ssize_t > indexOfCharOnTheLeft(ssize_t logical_index) const noexcept
Get the character left of the given character.
Definition text_style.hpp:16
T begin(T... args)
T clear(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)