FTXUI 7.1.0
C++ functional terminal UI.
Loading...
Searching...
No Matches
screen.cpp
Go to the documentation of this file.
1// Copyright 2020 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
4#include <cstddef> // for size_t
5#include <cstdint>
6#include <iostream> // for cout, flush
7#include <limits>
8#include <map> // for _Rb_tree_const_iterator, map, operator!=, operator==
9#include <string> // for string
10#include <string_view> // for string_view
11#include <utility> // for pair
12#include <vector> // for vector
13
14#include "ftxui/screen/cell.hpp" // for Cell
16#include "ftxui/screen/string.hpp" // for string_width
17#include "ftxui/screen/surface.hpp" // for Surface
18#include "ftxui/screen/terminal.hpp" // for Dimensions, Size
19
20#if defined(_WIN32)
21#define WIN32_LEAN_AND_MEAN
22#ifndef NOMINMAX
23#define NOMINMAX
24#endif
25#include <windows.h>
26#endif
27
28// Macro for hinting that an expression is likely to be false.
29#if !defined(FTXUI_UNLIKELY)
30#if defined(COMPILER_GCC) || defined(__clang__)
31#define FTXUI_UNLIKELY(x) __builtin_expect(!!(x), 0)
32#else
33#define FTXUI_UNLIKELY(x) (x)
34#endif // defined(COMPILER_GCC)
35#endif // !defined(FTXUI_UNLIKELY)
36
37#if !defined(FTXUI_LIKELY)
38#if defined(COMPILER_GCC) || defined(__clang__)
39#define FTXUI_LIKELY(x) __builtin_expect(!!(x), 1)
40#else
41#define FTXUI_LIKELY(x) (x)
42#endif // defined(COMPILER_GCC)
43#endif // !defined(FTXUI_LIKELY)
44
45namespace ftxui {
46
47namespace {
48
49#if defined(_WIN32)
50void WindowsEmulateVT100Terminal() {
51 static bool done = false;
52 if (done) {
53 return;
54 }
55 done = true;
56
57 // Enable VT processing on stdout and stdin
58 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
59
60 DWORD out_mode = 0;
61 if (!GetConsoleMode(stdout_handle, &out_mode)) {
62 // The output is not a console (e.g. redirected to a file or a pipe). Keep
63 // the detected color support and let the consumer of the stream interpret
64 // the escape sequences.
65 return;
66 }
67
68 // https://docs.microsoft.com/en-us/windows/console/setconsolemode
69 const int enable_virtual_terminal_processing = 0x0004;
70 const int disable_newline_auto_return = 0x0008;
71 out_mode |= enable_virtual_terminal_processing;
72 out_mode |= disable_newline_auto_return;
73
74 SetConsoleMode(stdout_handle, out_mode);
75}
76#endif
77
78// Position of a color sequence inside the output string. It refers to the
79// output instead of copying it, so tracking it costs no allocation.
80struct ColorSequence {
81 size_t begin = 0;
82 size_t size = 0;
83};
84
85// Append to `ss` the escape sequences switching the style from `prev` to
86// `next`.
87// - `color_support`: the terminal color support, read once per frame.
88// - `last_colors`: the last color sequence written to `ss`.
89// NOLINTNEXTLINE(readability-function-cognitive-complexity)
90void UpdateCellStyle(const Screen* screen,
91 std::string& ss,
92 Terminal::Color color_support,
93 ColorSequence& last_colors,
94 const Cell& prev,
95 const Cell& next) {
96 // See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
97 if (FTXUI_UNLIKELY(next.hyperlink != prev.hyperlink)) {
98 ss += "\x1B]8;;";
99 ss += screen->Hyperlink(next.hyperlink);
100 ss += "\x1B\\";
101 }
102
103 // Bold
104 if (FTXUI_UNLIKELY((next.bold ^ prev.bold) | (next.dim ^ prev.dim))) {
105 // BOLD_AND_DIM_RESET:
106 if ((prev.bold && !next.bold) || (prev.dim && !next.dim)) {
107 ss += "\x1B[22m";
108 }
109 if (next.bold) {
110 ss += "\x1B[1m"; // BOLD_SET
111 }
112 if (next.dim) {
113 ss += "\x1B[2m"; // DIM_SET
114 }
115 }
116
117 // Underline
118 if (FTXUI_UNLIKELY(next.underlined != prev.underlined ||
119 next.underlined_double != prev.underlined_double)) {
120 ss += (next.underlined ? "\x1B[4m" // UNDERLINE
121 : next.underlined_double ? "\x1B[21m" // UNDERLINE_DOUBLE
122 : "\x1B[24m"); // UNDERLINE_RESET
123 }
124
125 // Blink
126 if (FTXUI_UNLIKELY(next.blink != prev.blink)) {
127 ss += (next.blink ? "\x1B[5m" // BLINK_SET
128 : "\x1B[25m"); // BLINK_RESET
129 }
130
131 // Inverted
132 if (FTXUI_UNLIKELY(next.inverted != prev.inverted)) {
133 ss += (next.inverted ? "\x1B[7m" // INVERTED_SET
134 : "\x1B[27m"); // INVERTED_RESET
135 }
136
137 // Italics
138 if (FTXUI_UNLIKELY(next.italic != prev.italic)) {
139 ss += (next.italic ? "\x1B[3m" // ITALIC_SET
140 : "\x1B[23m"); // ITALIC_RESET
141 }
142
143 // StrikeThrough
144 if (FTXUI_UNLIKELY(next.strikethrough != prev.strikethrough)) {
145 ss += (next.strikethrough ? "\x1B[9m" // CROSSED_OUT
146 : "\x1B[29m"); // CROSSED_OUT_RESET
147 }
148
149 if (FTXUI_UNLIKELY(next.foreground_color != prev.foreground_color ||
150 next.background_color != prev.background_color)) {
151 const size_t begin = ss.size();
152 ss += "\x1B[";
153 next.foreground_color.PrintTo(ss, false, color_support);
154 ss += 'm';
155 ss += "\x1B[";
156 next.background_color.PrintTo(ss, true, color_support);
157 ss += 'm';
158
159 // Colors are degraded to the terminal color support when printed, so
160 // distinct colors can print the same sequence. e.g. RGB(1,2,3) and
161 // RGB(2,3,4) both print "38;5;16" on a 256-color terminal. Drop the new
162 // sequence when it repeats the last one. This can't happen with
163 // TrueColor, so skip the comparison there.
164 if (color_support != Terminal::Color::TrueColor) {
165 const size_t size = ss.size() - begin;
166 if (size == last_colors.size &&
167 ss.compare(begin, size, ss, last_colors.begin, size) == 0) {
168 ss.resize(begin);
169 } else {
170 last_colors.begin = begin;
171 last_colors.size = size;
172 }
173 }
174 }
175}
176
177struct TileEncoding {
178 std::uint8_t left : 2;
179 std::uint8_t top : 2;
180 std::uint8_t right : 2;
181 std::uint8_t down : 2;
182 std::uint8_t round : 1;
183
184 // clang-format off
185 bool operator<(const TileEncoding& other) const {
186 if (left < other.left) { return true; }
187 if (left > other.left) { return false; }
188 if (top < other.top) { return true; }
189 if (top > other.top) { return false; }
190 if (right < other.right) { return true; }
191 if (right > other.right) { return false; }
192 if (down < other.down) { return true; }
193 if (down > other.down) { return false; }
194 if (round < other.round) { return true; }
195 if (round > other.round) { return false; }
196 return false;
197 }
198 // clang-format on
199};
200
201// clang-format off
202const std::map<std::string, TileEncoding> tile_encoding = { // NOLINT
203 {"─", {1, 0, 1, 0, 0}},
204 {"━", {2, 0, 2, 0, 0}},
205 {"╍", {2, 0, 2, 0, 0}},
206
207 {"│", {0, 1, 0, 1, 0}},
208 {"┃", {0, 2, 0, 2, 0}},
209 {"╏", {0, 2, 0, 2, 0}},
210
211 {"┌", {0, 0, 1, 1, 0}},
212 {"┍", {0, 0, 2, 1, 0}},
213 {"┎", {0, 0, 1, 2, 0}},
214 {"┏", {0, 0, 2, 2, 0}},
215
216 {"┐", {1, 0, 0, 1, 0}},
217 {"┑", {2, 0, 0, 1, 0}},
218 {"┒", {1, 0, 0, 2, 0}},
219 {"┓", {2, 0, 0, 2, 0}},
220
221 {"└", {0, 1, 1, 0, 0}},
222 {"┕", {0, 1, 2, 0, 0}},
223 {"┖", {0, 2, 1, 0, 0}},
224 {"┗", {0, 2, 2, 0, 0}},
225
226 {"┘", {1, 1, 0, 0, 0}},
227 {"┙", {2, 1, 0, 0, 0}},
228 {"┚", {1, 2, 0, 0, 0}},
229 {"┛", {2, 2, 0, 0, 0}},
230
231 {"├", {0, 1, 1, 1, 0}},
232 {"┝", {0, 1, 2, 1, 0}},
233 {"┞", {0, 2, 1, 1, 0}},
234 {"┟", {0, 1, 1, 2, 0}},
235 {"┠", {0, 2, 1, 2, 0}},
236 {"┡", {0, 2, 2, 1, 0}},
237 {"┢", {0, 1, 2, 2, 0}},
238 {"┣", {0, 2, 2, 2, 0}},
239
240 {"┤", {1, 1, 0, 1, 0}},
241 {"┥", {2, 1, 0, 1, 0}},
242 {"┦", {1, 2, 0, 1, 0}},
243 {"┧", {1, 1, 0, 2, 0}},
244 {"┨", {1, 2, 0, 2, 0}},
245 {"┩", {2, 2, 0, 1, 0}},
246 {"┪", {2, 1, 0, 2, 0}},
247 {"┫", {2, 2, 0, 2, 0}},
248
249 {"┬", {1, 0, 1, 1, 0}},
250 {"┭", {2, 0, 1, 1, 0}},
251 {"┮", {1, 0, 2, 1, 0}},
252 {"┯", {2, 0, 2, 1, 0}},
253 {"┰", {1, 0, 1, 2, 0}},
254 {"┱", {2, 0, 1, 2, 0}},
255 {"┲", {1, 0, 2, 2, 0}},
256 {"┳", {2, 0, 2, 2, 0}},
257
258 {"┴", {1, 1, 1, 0, 0}},
259 {"┵", {2, 1, 1, 0, 0}},
260 {"┶", {1, 1, 2, 0, 0}},
261 {"┷", {2, 1, 2, 0, 0}},
262 {"┸", {1, 2, 1, 0, 0}},
263 {"┹", {2, 2, 1, 0, 0}},
264 {"┺", {1, 2, 2, 0, 0}},
265 {"┻", {2, 2, 2, 0, 0}},
266
267 {"┼", {1, 1, 1, 1, 0}},
268 {"┽", {2, 1, 1, 1, 0}},
269 {"┾", {1, 1, 2, 1, 0}},
270 {"┿", {2, 1, 2, 1, 0}},
271 {"╀", {1, 2, 1, 1, 0}},
272 {"╁", {1, 1, 1, 2, 0}},
273 {"╂", {1, 2, 1, 2, 0}},
274 {"╃", {2, 2, 1, 1, 0}},
275 {"╄", {1, 2, 2, 1, 0}},
276 {"╅", {2, 1, 1, 2, 0}},
277 {"╆", {1, 1, 2, 2, 0}},
278 {"╇", {2, 2, 2, 1, 0}},
279 {"╈", {2, 1, 2, 2, 0}},
280 {"╉", {2, 2, 1, 2, 0}},
281 {"╊", {1, 2, 2, 2, 0}},
282 {"╋", {2, 2, 2, 2, 0}},
283
284 {"═", {3, 0, 3, 0, 0}},
285 {"║", {0, 3, 0, 3, 0}},
286
287 {"╒", {0, 0, 3, 1, 0}},
288 {"╓", {0, 0, 1, 3, 0}},
289 {"╔", {0, 0, 3, 3, 0}},
290
291 {"╕", {3, 0, 0, 1, 0}},
292 {"╖", {1, 0, 0, 3, 0}},
293 {"╗", {3, 0, 0, 3, 0}},
294
295 {"╘", {0, 1, 3, 0, 0}},
296 {"╙", {0, 3, 1, 0, 0}},
297 {"╚", {0, 3, 3, 0, 0}},
298
299 {"╛", {3, 1, 0, 0, 0}},
300 {"╜", {1, 3, 0, 0, 0}},
301 {"╝", {3, 3, 0, 0, 0}},
302
303 {"╞", {0, 1, 3, 1, 0}},
304 {"╟", {0, 3, 1, 3, 0}},
305 {"╠", {0, 3, 3, 3, 0}},
306
307 {"╡", {3, 1, 0, 1, 0}},
308 {"╢", {1, 3, 0, 3, 0}},
309 {"╣", {3, 3, 0, 3, 0}},
310
311 {"╤", {3, 0, 3, 1, 0}},
312 {"╥", {1, 0, 1, 3, 0}},
313 {"╦", {3, 0, 3, 3, 0}},
314
315 {"╧", {3, 1, 3, 0, 0}},
316 {"╨", {1, 3, 1, 0, 0}},
317 {"╩", {3, 3, 3, 0, 0}},
318
319 {"╪", {3, 1, 3, 1, 0}},
320 {"╫", {1, 3, 1, 3, 0}},
321 {"╬", {3, 3, 3, 3, 0}},
322
323 {"╭", {0, 0, 1, 1, 1}},
324 {"╮", {1, 0, 0, 1, 1}},
325 {"╯", {1, 1, 0, 0, 1}},
326 {"╰", {0, 1, 1, 0, 1}},
327
328 {"╴", {1, 0, 0, 0, 0}},
329 {"╵", {0, 1, 0, 0, 0}},
330 {"╶", {0, 0, 1, 0, 0}},
331 {"╷", {0, 0, 0, 1, 0}},
332
333 {"╸", {2, 0, 0, 0, 0}},
334 {"╹", {0, 2, 0, 0, 0}},
335 {"╺", {0, 0, 2, 0, 0}},
336 {"╻", {0, 0, 0, 2, 0}},
337
338 {"╼", {1, 0, 2, 0, 0}},
339 {"╽", {0, 1, 0, 2, 0}},
340 {"╾", {2, 0, 1, 0, 0}},
341 {"╿", {0, 2, 0, 1, 0}},
342};
343// clang-format on
344
345template <class A, class B>
346std::map<B, A> InvertMap(const std::map<A, B>& input) {
347 std::map<B, A> output;
348 for (const auto& it : input) {
349 output[it.second] = it.first;
350 }
351 return output;
352}
353
354const std::map<TileEncoding, std::string> tile_encoding_inverse = // NOLINT
355 InvertMap(tile_encoding);
356
357void UpgradeLeftRight(std::string& left, std::string& right) {
358 const auto it_left = tile_encoding.find(left);
359 if (it_left == tile_encoding.end()) {
360 return;
361 }
362 const auto it_right = tile_encoding.find(right);
363 if (it_right == tile_encoding.end()) {
364 return;
365 }
366
367 if (it_left->second.right == 0 && it_right->second.left != 0) {
368 TileEncoding encoding_left = it_left->second;
369 encoding_left.right = it_right->second.left;
370 const auto it_left_upgrade = tile_encoding_inverse.find(encoding_left);
371 if (it_left_upgrade != tile_encoding_inverse.end()) {
372 left = it_left_upgrade->second;
373 }
374 }
375
376 if (it_right->second.left == 0 && it_left->second.right != 0) {
377 TileEncoding encoding_right = it_right->second;
378 encoding_right.left = it_left->second.right;
379 const auto it_right_upgrade = tile_encoding_inverse.find(encoding_right);
380 if (it_right_upgrade != tile_encoding_inverse.end()) {
381 right = it_right_upgrade->second;
382 }
383 }
384}
385
386void UpgradeTopDown(std::string& top, std::string& down) {
387 const auto it_top = tile_encoding.find(top);
388 if (it_top == tile_encoding.end()) {
389 return;
390 }
391 const auto it_down = tile_encoding.find(down);
392 if (it_down == tile_encoding.end()) {
393 return;
394 }
395
396 if (it_top->second.down == 0 && it_down->second.top != 0) {
397 TileEncoding encoding_top = it_top->second;
398 encoding_top.down = it_down->second.top;
399 const auto it_top_down = tile_encoding_inverse.find(encoding_top);
400 if (it_top_down != tile_encoding_inverse.end()) {
401 top = it_top_down->second;
402 }
403 }
404
405 if (it_down->second.top == 0 && it_top->second.down != 0) {
406 TileEncoding encoding_down = it_down->second;
407 encoding_down.top = it_top->second.down;
408 const auto it_down_top = tile_encoding_inverse.find(encoding_down);
409 if (it_down_top != tile_encoding_inverse.end()) {
410 down = it_down_top->second;
411 }
412 }
413}
414
415bool ShouldAttemptAutoMerge(Cell& cell) {
416 return cell.automerge && cell.character.size() == 3;
417}
418
419// Whether the two cells are printed identically.
420bool SameCell(const Screen& screen_a,
421 const Cell& a,
422 const Screen& screen_b,
423 const Cell& b) {
424 return a.character == b.character && //
425 a.foreground_color == b.foreground_color && //
426 a.background_color == b.background_color && //
427 a.blink == b.blink && //
428 a.bold == b.bold && //
429 a.dim == b.dim && //
430 a.italic == b.italic && //
431 a.inverted == b.inverted && //
432 a.underlined == b.underlined && //
433 a.underlined_double == b.underlined_double &&
434 a.strikethrough == b.strikethrough &&
435 screen_a.Hyperlink(a.hyperlink) == screen_b.Hyperlink(b.hyperlink);
436}
437
438// Mark the cells hidden behind the fullwidth character preceding them. They
439// aren't printed. This matches Screen::ToString().
440void HiddenCells(const Cell* row, int dimx, std::vector<bool>& hidden) {
441 hidden.assign(static_cast<size_t>(dimx), false);
442 bool previous_fullwidth = false;
443 for (int x = 0; x < dimx; ++x) {
444 hidden[x] = previous_fullwidth;
445 const std::string& character = row[x].character;
446 previous_fullwidth =
447 !hidden[x] && character.size() > 1 && string_width(character) == 2;
448 }
449}
450
451} // namespace
452
453/// A fixed dimension.
454/// @see Fit
455/// @see Full
456Dimensions Dimension::Fixed(int v) {
457 return {v, v};
458}
459
460/// Use the terminal dimensions.
461/// @see Fixed
462/// @see Fit
463Dimensions Dimension::Full() {
464 return Terminal::Size();
465}
466
467// static
468/// Create a screen with the given dimension along the x-axis and y-axis.
469Screen Screen::Create(Dimensions width, Dimensions height) {
470 return {width.dimx, height.dimy};
471}
472
473// static
474/// Create a screen with the given dimension.
475Screen Screen::Create(Dimensions dimension) {
476 return {dimension.dimx, dimension.dimy};
477}
478
479Screen::Screen(int dimx, int dimy) : Surface{dimx, dimy} {
480#if defined(_WIN32)
481 // The placement of this call is a bit weird, however we can assume that
482 // anybody who instantiates a Screen object eventually wants to output
483 // something to the console. If that is not the case, use an instance of
484 // Surface instead. As we require UTF8 for all input/output operations we will
485 // just switch to UTF8 encoding here
486 SetConsoleOutputCP(CP_UTF8);
487 SetConsoleCP(CP_UTF8);
488 WindowsEmulateVT100Terminal();
489#endif
490}
491
492/// Produce a std::string that can be used to print the Screen on the
493/// terminal.
494/// @note Don't forget to flush stdout. Alternatively, you can use
495/// Screen::Print();
496std::string Screen::ToString() const {
497 // Pre-allocate: ~30 bytes per cell for character + escape codes.
498 std::string ss;
499 ss.reserve(static_cast<size_t>(dimx_) * static_cast<size_t>(dimy_) * 30);
500 ToString(ss);
501 return ss;
502}
503
504/// Produce a std::string that can be used to print the Screen on the
505/// terminal.
506/// @param ss The string to append to.
507void Screen::ToString(std::string& ss) const {
508 const Cell default_cell;
509 const Cell* previous_cell_ref = &default_cell;
510 const Terminal::Color color_support = Terminal::ColorSupport();
511 ColorSequence last_colors;
512
513 for (int y = 0; y < dimy_; ++y) {
514 // New line in between two lines.
515 if (y != 0) {
516 UpdateCellStyle(this, ss, color_support, last_colors, *previous_cell_ref,
517 default_cell);
518 previous_cell_ref = &default_cell;
519 ss += "\r\n";
520 }
521
522 // After printing a fullwith character, we need to skip the next cell.
523 bool previous_fullwidth = false;
524 if (dimx_ > 0) {
525 const Cell* line_start = &FastCellAt(0, y);
526 const Cell* line_end = line_start + dimx_;
527 for (const Cell* it = line_start; it != line_end; ++it) {
528 const auto& cell = *it;
529 if (!previous_fullwidth) {
530 UpdateCellStyle(this, ss, color_support, last_colors,
531 *previous_cell_ref, cell);
532 previous_cell_ref = &cell;
533 if (cell.character.empty()) {
534 ss += ' ';
535 } else {
536 ss += cell.character;
537 }
538 }
539 // A hidden cell can't hide the next one.
540 previous_fullwidth = !previous_fullwidth && cell.character.size() > 1 &&
541 string_width(cell.character) == 2;
542 }
543 }
544 }
545
546 // Reset the style to default:
547 UpdateCellStyle(this, ss, color_support, last_colors, *previous_cell_ref,
548 default_cell);
549}
550
551/// Produce a std::string updating the terminal from |previous| to this Screen.
552/// Only the cells that changed are printed.
553/// @param ss The string to append to.
554/// @param previous The Screen currently displayed. The cursor must be at its
555/// top-left corner. It ends where ToString() would leave it.
556void Screen::ToString(std::string& ss, const Screen& previous) const {
557 if (previous.dimx_ != dimx_ || previous.dimy_ != dimy_ || dimx_ == 0 ||
558 dimy_ == 0) {
559 ToString(ss);
560 return;
561 }
562
563 const Cell default_cell;
564 const Terminal::Color color_support = Terminal::ColorSupport();
565 ColorSequence last_colors;
566 std::vector<bool> hidden;
567 std::vector<bool> previous_hidden;
568 int cursor_y = 0;
569
570 for (int y = 0; y < dimy_; ++y) {
571 const Cell* row = &FastCellAt(0, y);
572 const Cell* previous_row = &previous.FastCellAt(0, y);
573 auto same = [&](int x) {
574 return SameCell(*this, row[x], previous, previous_row[x]);
575 };
576
577 int first = 0;
578 while (first < dimx_ && same(first)) {
579 ++first;
580 }
581 int last = dimx_ - 1;
582 if (y != dimy_ - 1) {
583 if (first == dimx_) {
584 continue;
585 }
586 while (same(last)) {
587 --last;
588 }
589 } else if (first == dimx_) {
590 // Always print the last cell, so the cursor ends like ToString().
591 first = dimx_ - 1;
592 }
593
594 // Don't split fullwidth characters, from either frame. Overwriting half of
595 // one erases the other half, which must be printed again.
596 HiddenCells(row, dimx_, hidden);
597 HiddenCells(previous_row, dimx_, previous_hidden);
598 while (first > 0 && (hidden[first] || previous_hidden[first])) {
599 --first;
600 }
601 while (last + 1 < dimx_ &&
602 (hidden[last + 1] || previous_hidden[last + 1])) {
603 ++last;
604 }
605
606 // Move the cursor to (first, y).
607 if (y != cursor_y) {
608 ss += "\x1B[" + std::to_string(y - cursor_y) + "B"; // MOVE_DOWN
609 cursor_y = y;
610 }
611 ss += '\r'; // MOVE_LEFT
612 if (first != 0) {
613 ss += "\x1B[" + std::to_string(first) + "C"; // MOVE_RIGHT
614 }
615
616 const Cell* previous_cell_ref = &default_cell;
617 for (int x = first; x <= last; ++x) {
618 if (hidden[x]) {
619 continue;
620 }
621 const Cell& cell = row[x];
622 UpdateCellStyle(this, ss, color_support, last_colors, *previous_cell_ref,
623 cell);
624 previous_cell_ref = &cell;
625 if (cell.character.empty()) {
626 ss += ' ';
627 } else {
628 ss += cell.character;
629 }
630 }
631 UpdateCellStyle(this, ss, color_support, last_colors, *previous_cell_ref,
632 default_cell);
633 }
634}
635
636// Print the Screen to the terminal.
637void Screen::Print() const {
638 std::cout << ToString() << '\0' << std::flush;
639}
640
641/// @brief Return a string to be printed in order to reset the cursor position
642/// to the beginning of the screen.
643///
644/// ```cpp
645/// std::string reset_position;
646/// while(true) {
647/// auto document = render();
648/// auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
649/// Render(screen, document);
650/// std::cout << reset_position << screen.ToString() << std::flush;
651/// reset_position = screen.ResetPosition();
652///
653/// using namespace std::chrono_literals;
654/// std::this_thread::sleep_for(0.01s);
655/// }
656/// ```
657///
658/// @return The string to print in order to reset the cursor position to the
659/// beginning.
660std::string Screen::ResetPosition(bool clear) const {
661 std::string ss;
662 ss.reserve(static_cast<size_t>(dimy_) * 12);
663 ResetPosition(ss, clear);
664 return ss;
665}
666
667/// @brief Append to a string in order to reset the cursor position to the
668/// beginning of the screen.
669/// @param ss The string to append to.
670/// @param clear Whether to clear the screen or not.
671void Screen::ResetPosition(std::string& ss, bool clear) const {
672 if (clear) {
673 // The clear branch must move up one row at a time, because each row needs
674 // its own CLEAR_LINE (\x1B[2K) erase. It cannot be collapsed into a single
675 // parameterized cursor-up.
676 ss += '\r'; // MOVE_LEFT;
677 ss += "\x1b[2K"; // CLEAR_SCREEN;
678 for (int y = 1; y < dimy_; ++y) {
679 ss += "\x1B[1A"; // MOVE_UP;
680 ss += "\x1B[2K"; // CLEAR_LINE;
681 }
682 } else {
683 // The non-clear branch only needs to reposition the cursor at the top-left,
684 // so the per-row walk-up is collapsed into a single parameterized
685 // CSI cursor-up (\x1B[<n>A), emitting far fewer bytes per frame.
686 ss += '\r'; // MOVE_LEFT;
687 if (dimy_ > 1) {
688 ss += "\x1B[" + std::to_string(dimy_ - 1) + "A"; // MOVE_UP;
689 }
690 }
691}
692
693/// @brief Clear all the cells from the screen.
694void Screen::Clear() {
695 Surface::Clear();
696
697 cursor_.x = dimx_ - 1;
698 cursor_.y = dimy_ - 1;
699
700 hyperlinks_ = {
701 "",
702 };
703}
704
705// clang-format off
706void Screen::ApplyShader() {
707 // Merge box characters together.
708 for (int y = 0; y < dimy_; ++y) {
709 for (int x = 0; x < dimx_; ++x) {
710 // Box drawing character uses exactly 3 byte.
711 Cell& cur = FastCellAt(x, y);
712 if (!ShouldAttemptAutoMerge(cur)) {
713 continue;
714 }
715
716 if (x > 0) {
717 Cell& left = FastCellAt(x - 1, y);
718 if (ShouldAttemptAutoMerge(left)) {
719 UpgradeLeftRight(left.character, cur.character);
720 }
721 }
722 if (y > 0) {
723 Cell& top = FastCellAt(x, y - 1);
724 if (ShouldAttemptAutoMerge(top)) {
725 UpgradeTopDown(top.character, cur.character);
726 }
727 }
728 }
729 }
730}
731// clang-format on
732
733std::uint8_t Screen::RegisterHyperlink(std::string_view link) {
734 for (std::size_t i = 0; i < hyperlinks_.size(); ++i) {
735 if (hyperlinks_[i] == link) {
736 return i;
737 }
738 }
739 if (hyperlinks_.size() == std::numeric_limits<std::uint8_t>::max()) {
740 return 0;
741 }
742 hyperlinks_.emplace_back(link);
743 return hyperlinks_.size() - 1;
744}
745
746const std::string& Screen::Hyperlink(std::uint8_t id) const {
747 if (id >= hyperlinks_.size()) {
748 return hyperlinks_[0];
749 }
750 return hyperlinks_[id];
751}
752
753/// @brief Return the current selection style.
754/// @see SetSelectionStyle
755const Screen::SelectionStyle& Screen::GetSelectionStyle() const {
756 return selection_style_;
757}
758
759/// @brief Set the current selection style.
760/// @see GetSelectionStyle
761void Screen::SetSelectionStyle(SelectionStyle decorator) {
762 selection_style_ = std::move(decorator);
763}
764
765void Screen::Reserved1() {}
766void Screen::Reserved2() {}
767void Screen::Reserved3() {}
768void Screen::Reserved4() {}
769void Screen::Reserved5() {}
770void Screen::Reserved6() {}
771void Screen::Reserved7() {}
772void Screen::Reserved8() {}
773
774} // namespace ftxui
Color
Color is an enumeration that represents the color support of the terminal.
Definition terminal.hpp:31
Dimensions Size()
Get the terminal size.
Definition terminal.cpp:312
The FTXUI ftxui:: namespace.
Definition animation.hpp:11
int string_width(std::string_view input)
Definition string.cpp:304
int y
Definition elements.hpp:134
std::uint8_t top
Definition screen.cpp:179
#define FTXUI_UNLIKELY(x)
Definition screen.cpp:33
size_t size
Definition screen.cpp:82
std::uint8_t left
Definition screen.cpp:178
std::uint8_t down
Definition screen.cpp:181
std::uint8_t right
Definition screen.cpp:180
size_t begin
Definition screen.cpp:81
std::uint8_t round
Definition screen.cpp:182