FTXUI 6.1.9
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 <utility> // for pair
10
11#include "ftxui/screen/cell.hpp" // for Cell
13#include "ftxui/screen/string.hpp" // for string_width
14#include "ftxui/screen/surface.hpp" // for Surface
15#include "ftxui/screen/terminal.hpp" // for Dimensions, Size
16
17#if defined(_WIN32)
18#define WIN32_LEAN_AND_MEAN
19#ifndef NOMINMAX
20#define NOMINMAX
21#endif
22#include <windows.h>
23#endif
24
25// Macro for hinting that an expression is likely to be false.
26#if !defined(FTXUI_UNLIKELY)
27#if defined(COMPILER_GCC) || defined(__clang__)
28#define FTXUI_UNLIKELY(x) __builtin_expect(!!(x), 0)
29#else
30#define FTXUI_UNLIKELY(x) (x)
31#endif // defined(COMPILER_GCC)
32#endif // !defined(FTXUI_UNLIKELY)
33
34#if !defined(FTXUI_LIKELY)
35#if defined(COMPILER_GCC) || defined(__clang__)
36#define FTXUI_LIKELY(x) __builtin_expect(!!(x), 1)
37#else
38#define FTXUI_LIKELY(x) (x)
39#endif // defined(COMPILER_GCC)
40#endif // !defined(FTXUI_LIKELY)
41
42namespace ftxui {
43
44namespace {
45
46#if defined(_WIN32)
47void WindowsEmulateVT100Terminal() {
48 static bool done = false;
49 if (done) {
50 return;
51 }
52 done = true;
53
54 // Enable VT processing on stdout and stdin
55 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
56
57 DWORD out_mode = 0;
58 GetConsoleMode(stdout_handle, &out_mode);
59
60 // https://docs.microsoft.com/en-us/windows/console/setconsolemode
61 const int enable_virtual_terminal_processing = 0x0004;
62 const int disable_newline_auto_return = 0x0008;
63 out_mode |= enable_virtual_terminal_processing;
64 out_mode |= disable_newline_auto_return;
65
66 SetConsoleMode(stdout_handle, out_mode);
67}
68#endif
69
70// NOLINTNEXTLINE(readability-function-cognitive-complexity)
71void UpdateCellStyle(const Screen* screen,
72 std::string& ss,
73 const Cell& prev,
74 const Cell& next) {
75 // See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
76 if (FTXUI_UNLIKELY(next.hyperlink != prev.hyperlink)) {
77 ss += "\x1B]8;;";
78 ss += screen->Hyperlink(next.hyperlink);
79 ss += "\x1B\\";
80 }
81
82 // Bold
83 if (FTXUI_UNLIKELY((next.bold ^ prev.bold) | (next.dim ^ prev.dim))) {
84 // BOLD_AND_DIM_RESET:
85 if ((prev.bold && !next.bold) || (prev.dim && !next.dim)) {
86 ss += "\x1B[22m";
87 }
88 if (next.bold) {
89 ss += "\x1B[1m"; // BOLD_SET
90 }
91 if (next.dim) {
92 ss += "\x1B[2m"; // DIM_SET
93 }
94 }
95
96 // Underline
97 if (FTXUI_UNLIKELY(next.underlined != prev.underlined ||
98 next.underlined_double != prev.underlined_double)) {
99 ss += (next.underlined ? "\x1B[4m" // UNDERLINE
100 : next.underlined_double ? "\x1B[21m" // UNDERLINE_DOUBLE
101 : "\x1B[24m"); // UNDERLINE_RESET
102 }
103
104 // Blink
105 if (FTXUI_UNLIKELY(next.blink != prev.blink)) {
106 ss += (next.blink ? "\x1B[5m" // BLINK_SET
107 : "\x1B[25m"); // BLINK_RESET
108 }
109
110 // Inverted
111 if (FTXUI_UNLIKELY(next.inverted != prev.inverted)) {
112 ss += (next.inverted ? "\x1B[7m" // INVERTED_SET
113 : "\x1B[27m"); // INVERTED_RESET
114 }
115
116 // Italics
117 if (FTXUI_UNLIKELY(next.italic != prev.italic)) {
118 ss += (next.italic ? "\x1B[3m" // ITALIC_SET
119 : "\x1B[23m"); // ITALIC_RESET
120 }
121
122 // StrikeThrough
123 if (FTXUI_UNLIKELY(next.strikethrough != prev.strikethrough)) {
124 ss += (next.strikethrough ? "\x1B[9m" // CROSSED_OUT
125 : "\x1B[29m"); // CROSSED_OUT_RESET
126 }
127
128 if (FTXUI_UNLIKELY(next.foreground_color != prev.foreground_color ||
129 next.background_color != prev.background_color)) {
130 ss += "\x1B[";
131 next.foreground_color.PrintTo(ss, false);
132 ss += 'm';
133 ss += "\x1B[";
134 next.background_color.PrintTo(ss, true);
135 ss += 'm';
136 }
137}
138
139struct TileEncoding {
140 std::uint8_t left : 2;
141 std::uint8_t top : 2;
142 std::uint8_t right : 2;
143 std::uint8_t down : 2;
144 std::uint8_t round : 1;
145
146 // clang-format off
147 bool operator<(const TileEncoding& other) const {
148 if (left < other.left) { return true; }
149 if (left > other.left) { return false; }
150 if (top < other.top) { return true; }
151 if (top > other.top) { return false; }
152 if (right < other.right) { return true; }
153 if (right > other.right) { return false; }
154 if (down < other.down) { return true; }
155 if (down > other.down) { return false; }
156 if (round < other.round) { return true; }
157 if (round > other.round) { return false; }
158 return false;
159 }
160 // clang-format on
161};
162
163// clang-format off
164const std::map<std::string, TileEncoding> tile_encoding = { // NOLINT
165 {"─", {1, 0, 1, 0, 0}},
166 {"━", {2, 0, 2, 0, 0}},
167 {"╍", {2, 0, 2, 0, 0}},
168
169 {"│", {0, 1, 0, 1, 0}},
170 {"┃", {0, 2, 0, 2, 0}},
171 {"╏", {0, 2, 0, 2, 0}},
172
173 {"┌", {0, 0, 1, 1, 0}},
174 {"┍", {0, 0, 2, 1, 0}},
175 {"┎", {0, 0, 1, 2, 0}},
176 {"┏", {0, 0, 2, 2, 0}},
177
178 {"┐", {1, 0, 0, 1, 0}},
179 {"┑", {2, 0, 0, 1, 0}},
180 {"┒", {1, 0, 0, 2, 0}},
181 {"┓", {2, 0, 0, 2, 0}},
182
183 {"└", {0, 1, 1, 0, 0}},
184 {"┕", {0, 1, 2, 0, 0}},
185 {"┖", {0, 2, 1, 0, 0}},
186 {"┗", {0, 2, 2, 0, 0}},
187
188 {"┘", {1, 1, 0, 0, 0}},
189 {"┙", {2, 1, 0, 0, 0}},
190 {"┚", {1, 2, 0, 0, 0}},
191 {"┛", {2, 2, 0, 0, 0}},
192
193 {"├", {0, 1, 1, 1, 0}},
194 {"┝", {0, 1, 2, 1, 0}},
195 {"┞", {0, 2, 1, 1, 0}},
196 {"┟", {0, 1, 1, 2, 0}},
197 {"┠", {0, 2, 1, 2, 0}},
198 {"┡", {0, 2, 2, 1, 0}},
199 {"┢", {0, 1, 2, 2, 0}},
200 {"┣", {0, 2, 2, 2, 0}},
201
202 {"┤", {1, 1, 0, 1, 0}},
203 {"┥", {2, 1, 0, 1, 0}},
204 {"┦", {1, 2, 0, 1, 0}},
205 {"┧", {1, 1, 0, 2, 0}},
206 {"┨", {1, 2, 0, 2, 0}},
207 {"┩", {2, 2, 0, 1, 0}},
208 {"┪", {2, 1, 0, 2, 0}},
209 {"┫", {2, 2, 0, 2, 0}},
210
211 {"┬", {1, 0, 1, 1, 0}},
212 {"┭", {2, 0, 1, 1, 0}},
213 {"┮", {1, 0, 2, 1, 0}},
214 {"┯", {2, 0, 2, 1, 0}},
215 {"┰", {1, 0, 1, 2, 0}},
216 {"┱", {2, 0, 1, 2, 0}},
217 {"┲", {1, 0, 2, 2, 0}},
218 {"┳", {2, 0, 2, 2, 0}},
219
220 {"┴", {1, 1, 1, 0, 0}},
221 {"┵", {2, 1, 1, 0, 0}},
222 {"┶", {1, 1, 2, 0, 0}},
223 {"┷", {2, 1, 2, 0, 0}},
224 {"┸", {1, 2, 1, 0, 0}},
225 {"┹", {2, 2, 1, 0, 0}},
226 {"┺", {1, 2, 2, 0, 0}},
227 {"┻", {2, 2, 2, 0, 0}},
228
229 {"┼", {1, 1, 1, 1, 0}},
230 {"┽", {2, 1, 1, 1, 0}},
231 {"┾", {1, 1, 2, 1, 0}},
232 {"┿", {2, 1, 2, 1, 0}},
233 {"╀", {1, 2, 1, 1, 0}},
234 {"╁", {1, 1, 1, 2, 0}},
235 {"╂", {1, 2, 1, 2, 0}},
236 {"╃", {2, 2, 1, 1, 0}},
237 {"╄", {1, 2, 2, 1, 0}},
238 {"╅", {2, 1, 1, 2, 0}},
239 {"╆", {1, 1, 2, 2, 0}},
240 {"╇", {2, 2, 2, 1, 0}},
241 {"╈", {2, 1, 2, 2, 0}},
242 {"╉", {2, 2, 1, 2, 0}},
243 {"╊", {1, 2, 2, 2, 0}},
244 {"╋", {2, 2, 2, 2, 0}},
245
246 {"═", {3, 0, 3, 0, 0}},
247 {"║", {0, 3, 0, 3, 0}},
248
249 {"╒", {0, 0, 3, 1, 0}},
250 {"╓", {0, 0, 1, 3, 0}},
251 {"╔", {0, 0, 3, 3, 0}},
252
253 {"╕", {3, 0, 0, 1, 0}},
254 {"╖", {1, 0, 0, 3, 0}},
255 {"╗", {3, 0, 0, 3, 0}},
256
257 {"╘", {0, 1, 3, 0, 0}},
258 {"╙", {0, 3, 1, 0, 0}},
259 {"╚", {0, 3, 3, 0, 0}},
260
261 {"╛", {3, 1, 0, 0, 0}},
262 {"╜", {1, 3, 0, 0, 0}},
263 {"╝", {3, 3, 0, 0, 0}},
264
265 {"╞", {0, 1, 3, 1, 0}},
266 {"╟", {0, 3, 1, 3, 0}},
267 {"╠", {0, 3, 3, 3, 0}},
268
269 {"╡", {3, 1, 0, 1, 0}},
270 {"╢", {1, 3, 0, 3, 0}},
271 {"╣", {3, 3, 0, 3, 0}},
272
273 {"╤", {3, 0, 3, 1, 0}},
274 {"╥", {1, 0, 1, 3, 0}},
275 {"╦", {3, 0, 3, 3, 0}},
276
277 {"╧", {3, 1, 3, 0, 0}},
278 {"╨", {1, 3, 1, 0, 0}},
279 {"╩", {3, 3, 3, 0, 0}},
280
281 {"╪", {3, 1, 3, 1, 0}},
282 {"╫", {1, 3, 1, 3, 0}},
283 {"╬", {3, 3, 3, 3, 0}},
284
285 {"╭", {0, 0, 1, 1, 1}},
286 {"╮", {1, 0, 0, 1, 1}},
287 {"╯", {1, 1, 0, 0, 1}},
288 {"╰", {0, 1, 1, 0, 1}},
289
290 {"╴", {1, 0, 0, 0, 0}},
291 {"╵", {0, 1, 0, 0, 0}},
292 {"╶", {0, 0, 1, 0, 0}},
293 {"╷", {0, 0, 0, 1, 0}},
294
295 {"╸", {2, 0, 0, 0, 0}},
296 {"╹", {0, 2, 0, 0, 0}},
297 {"╺", {0, 0, 2, 0, 0}},
298 {"╻", {0, 0, 0, 2, 0}},
299
300 {"╼", {1, 0, 2, 0, 0}},
301 {"╽", {0, 1, 0, 2, 0}},
302 {"╾", {2, 0, 1, 0, 0}},
303 {"╿", {0, 2, 0, 1, 0}},
304};
305// clang-format on
306
307template <class A, class B>
308std::map<B, A> InvertMap(const std::map<A, B> input) {
309 std::map<B, A> output;
310 for (const auto& it : input) {
311 output[it.second] = it.first;
312 }
313 return output;
314}
315
316const std::map<TileEncoding, std::string> tile_encoding_inverse = // NOLINT
317 InvertMap(tile_encoding);
318
319void UpgradeLeftRight(std::string& left, std::string& right) {
320 const auto it_left = tile_encoding.find(left);
321 if (it_left == tile_encoding.end()) {
322 return;
323 }
324 const auto it_right = tile_encoding.find(right);
325 if (it_right == tile_encoding.end()) {
326 return;
327 }
328
329 if (it_left->second.right == 0 && it_right->second.left != 0) {
330 TileEncoding encoding_left = it_left->second;
331 encoding_left.right = it_right->second.left;
332 const auto it_left_upgrade = tile_encoding_inverse.find(encoding_left);
333 if (it_left_upgrade != tile_encoding_inverse.end()) {
334 left = it_left_upgrade->second;
335 }
336 }
337
338 if (it_right->second.left == 0 && it_left->second.right != 0) {
339 TileEncoding encoding_right = it_right->second;
340 encoding_right.left = it_left->second.right;
341 const auto it_right_upgrade = tile_encoding_inverse.find(encoding_right);
342 if (it_right_upgrade != tile_encoding_inverse.end()) {
343 right = it_right_upgrade->second;
344 }
345 }
346}
347
348void UpgradeTopDown(std::string& top, std::string& down) {
349 const auto it_top = tile_encoding.find(top);
350 if (it_top == tile_encoding.end()) {
351 return;
352 }
353 const auto it_down = tile_encoding.find(down);
354 if (it_down == tile_encoding.end()) {
355 return;
356 }
357
358 if (it_top->second.down == 0 && it_down->second.top != 0) {
359 TileEncoding encoding_top = it_top->second;
360 encoding_top.down = it_down->second.top;
361 const auto it_top_down = tile_encoding_inverse.find(encoding_top);
362 if (it_top_down != tile_encoding_inverse.end()) {
363 top = it_top_down->second;
364 }
365 }
366
367 if (it_down->second.top == 0 && it_top->second.down != 0) {
368 TileEncoding encoding_down = it_down->second;
369 encoding_down.top = it_top->second.down;
370 const auto it_down_top = tile_encoding_inverse.find(encoding_down);
371 if (it_down_top != tile_encoding_inverse.end()) {
372 down = it_down_top->second;
373 }
374 }
375}
376
377bool ShouldAttemptAutoMerge(Cell& cell) {
378 return cell.automerge && cell.character.size() == 3;
379}
380
381} // namespace
382
383/// A fixed dimension.
384/// @see Fit
385/// @see Full
386Dimensions Dimension::Fixed(int v) {
387 return {v, v};
388}
389
390/// Use the terminal dimensions.
391/// @see Fixed
392/// @see Fit
393Dimensions Dimension::Full() {
394 return Terminal::Size();
395}
396
397// static
398/// Create a screen with the given dimension along the x-axis and y-axis.
400 return {width.dimx, height.dimy};
401}
402
403// static
404/// Create a screen with the given dimension.
406 return {dimension.dimx, dimension.dimy};
407}
408
409Screen::Screen(int dimx, int dimy) : Surface{dimx, dimy} {
410#if defined(_WIN32)
411 // The placement of this call is a bit weird, however we can assume that
412 // anybody who instantiates a Screen object eventually wants to output
413 // something to the console. If that is not the case, use an instance of
414 // Surface instead. As we require UTF8 for all input/output operations we will
415 // just switch to UTF8 encoding here
416 SetConsoleOutputCP(CP_UTF8);
417 SetConsoleCP(CP_UTF8);
418 WindowsEmulateVT100Terminal();
419#endif
420}
421
422/// Produce a std::string that can be used to print the Screen on the
423/// terminal.
424/// @note Don't forget to flush stdout. Alternatively, you can use
425/// Screen::Print();
426std::string Screen::ToString() const {
427 // Pre-allocate: ~30 bytes per cell for character + escape codes.
428 std::string ss;
429 ss.reserve(static_cast<size_t>(dimx_) * static_cast<size_t>(dimy_) * 30);
430 ToString(ss);
431 return ss;
432}
433
434/// Produce a std::string that can be used to print the Screen on the
435/// terminal.
436/// @param ss The string to append to.
437void Screen::ToString(std::string& ss) const {
438 const Cell default_cell;
439 const Cell* previous_cell_ref = &default_cell;
440
441 for (int y = 0; y < dimy_; ++y) {
442 // New line in between two lines.
443 if (y != 0) {
444 UpdateCellStyle(this, ss, *previous_cell_ref, default_cell);
445 previous_cell_ref = &default_cell;
446 ss += "\r\n";
447 }
448
449 // After printing a fullwith character, we need to skip the next cell.
450 bool previous_fullwidth = false;
451 for (const auto& cell : cells_[y]) {
452 if (!previous_fullwidth) {
453 UpdateCellStyle(this, ss, *previous_cell_ref, cell);
454 previous_cell_ref = &cell;
455 if (cell.character.empty()) {
456 ss += ' ';
457 } else {
458 ss += cell.character;
459 }
460 }
461 if (cell.character.size() <= 1) {
462 previous_fullwidth = false;
463 } else {
464 previous_fullwidth = (string_width(cell.character) == 2);
465 }
466 }
467 }
468
469 // Reset the style to default:
470 UpdateCellStyle(this, ss, *previous_cell_ref, default_cell);
471}
472
473// Print the Screen to the terminal.
474void Screen::Print() const {
475 std::cout << ToString() << '\0' << std::flush;
476}
477
478/// @brief Return a string to be printed in order to reset the cursor position
479/// to the beginning of the screen.
480///
481/// ```cpp
482/// std::string reset_position;
483/// while(true) {
484/// auto document = render();
485/// auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
486/// Render(screen, document);
487/// std::cout << reset_position << screen.ToString() << std::flush;
488/// reset_position = screen.ResetPosition();
489///
490/// using namespace std::chrono_literals;
491/// std::this_thread::sleep_for(0.01s);
492/// }
493/// ```
494///
495/// @return The string to print in order to reset the cursor position to the
496/// beginning.
497std::string Screen::ResetPosition(bool clear) const {
498 std::string ss;
499 ss.reserve(static_cast<size_t>(dimy_) * 12);
500 ResetPosition(ss, clear);
501 return ss;
502}
503
504/// @brief Append to a string in order to reset the cursor position to the
505/// beginning of the screen.
506/// @param ss The string to append to.
507/// @param clear Whether to clear the screen or not.
508void Screen::ResetPosition(std::string& ss, bool clear) const {
509 if (clear) {
510 ss += '\r'; // MOVE_LEFT;
511 ss += "\x1b[2K"; // CLEAR_SCREEN;
512 for (int y = 1; y < dimy_; ++y) {
513 ss += "\x1B[1A"; // MOVE_UP;
514 ss += "\x1B[2K"; // CLEAR_LINE;
515 }
516 } else {
517 ss += '\r'; // MOVE_LEFT;
518 for (int y = 1; y < dimy_; ++y) {
519 ss += "\x1B[1A"; // MOVE_UP;
520 }
521 }
522}
523
524/// @brief Clear all the cells from the screen.
527
528 cursor_.x = dimx_ - 1;
529 cursor_.y = dimy_ - 1;
530
531 hyperlinks_ = {
532 "",
533 };
534}
535
536// clang-format off
538 // Merge box characters togethers.
539 for (int y = 0; y < dimy_; ++y) {
540 for (int x = 0; x < dimx_; ++x) {
541 // Box drawing character uses exactly 3 byte.
542 Cell& cur = cells_[y][x];
543 if (!ShouldAttemptAutoMerge(cur)) {
544 continue;
545 }
546
547 if (x > 0) {
548 Cell& left = cells_[y][x-1];
549 if (ShouldAttemptAutoMerge(left)) {
550 UpgradeLeftRight(left.character, cur.character);
551 }
552 }
553 if (y > 0) {
554 Cell& top = cells_[y-1][x];
555 if (ShouldAttemptAutoMerge(top)) {
556 UpgradeTopDown(top.character, cur.character);
557 }
558 }
559 }
560 }
561}
562// clang-format on
563
564std::uint8_t Screen::RegisterHyperlink(std::string_view link) {
565 for (std::size_t i = 0; i < hyperlinks_.size(); ++i) {
566 if (hyperlinks_[i] == link) {
567 return i;
568 }
569 }
570 if (hyperlinks_.size() == std::numeric_limits<std::uint8_t>::max()) {
571 return 0;
572 }
573 hyperlinks_.push_back(std::string(link));
574 return hyperlinks_.size() - 1;
575}
576
577const std::string& Screen::Hyperlink(std::uint8_t id) const {
578 if (id >= hyperlinks_.size()) {
579 return hyperlinks_[0];
580 }
581 return hyperlinks_[id];
582}
583
584/// @brief Return the current selection style.
585/// @see SetSelectionStyle
589
590/// @brief Set the current selection style.
591/// @see GetSelectionStyle
593 selection_style_ = std::move(decorator);
594}
595
596} // namespace ftxui
void ApplyShader()
Definition screen.cpp:537
const SelectionStyle & GetSelectionStyle() const
Return the current selection style.
Definition screen.cpp:586
const std::string & Hyperlink(uint8_t id) const
Definition screen.cpp:577
std::string ToString() const
Definition screen.cpp:426
uint8_t RegisterHyperlink(std::string_view link)
Definition screen.cpp:564
static Screen Create(Dimensions dimension)
Create a screen with the given dimension.
Definition screen.cpp:405
std::string character
Definition cell.hpp:45
std::function< void(Cell &)> SelectionStyle
Definition screen.hpp:76
Screen(int dimx, int dimy)
Definition screen.cpp:409
std::string ResetPosition(bool clear=false) const
Return a string to be printed in order to reset the cursor position to the beginning of the screen.
Definition screen.cpp:497
Cursor cursor_
Definition screen.hpp:81
void Clear()
Clear all the cells from the screen.
Definition screen.cpp:525
std::vector< std::vector< Cell > > cells_
Definition surface.hpp:50
SelectionStyle selection_style_
Definition screen.hpp:85
void SetSelectionStyle(SelectionStyle decorator)
Set the current selection style.
Definition screen.cpp:592
std::vector< std::string > hyperlinks_
Definition screen.hpp:82
void Print() const
Definition screen.cpp:474
A rectangular grid of Cell.
Definition screen.hpp:26
A rectangular grid of Cell.
Definition surface.hpp:17
Dimensions Size()
Get the terminal size.
Definition terminal.cpp:94
A Unicode character and its associated style.
Definition cell.hpp:15
Dimensions is a structure that represents the size of the terminal.
Definition terminal.hpp:11
Dimensions Fixed(int)
Dimensions Full()
The FTXUI ftxui:: namespace.
Definition animation.hpp:10
int string_width(std::string_view)
Definition string.cpp:1336
std::uint8_t top
Definition screen.cpp:141
#define FTXUI_UNLIKELY(x)
Definition screen.cpp:30
std::uint8_t left
Definition screen.cpp:140
std::uint8_t down
Definition screen.cpp:143
std::uint8_t right
Definition screen.cpp:142
std::uint8_t round
Definition screen.cpp:144