FTXUI  0.11.1
C++ functional terminal UI.
screen.cpp
Go to the documentation of this file.
1 #include <iostream> // for operator<<, stringstream, basic_ostream, flush, cout, ostream
2 #include <map> // for _Rb_tree_const_iterator, map, operator!=, operator==
3 #include <memory> // for allocator, allocator_traits<>::value_type
4 #include <sstream> // IWYU pragma: keep
5 #include <utility> // for pair
6 
8 #include "ftxui/screen/string.hpp" // for string_width
9 #include "ftxui/screen/terminal.hpp" // for Dimensions, Size
10 
11 #if defined(_WIN32)
12 #define WIN32_LEAN_AND_MEAN
13 #ifndef NOMINMAX
14 #define NOMINMAX
15 #endif
16 #include <Windows.h>
17 #endif
18 
19 namespace ftxui {
20 
21 namespace {
22 static const char BOLD_SET[] = "\x1B[1m";
23 static const char BOLD_RESET[] = "\x1B[22m"; // Can't use 21 here.
24 
25 static const char DIM_SET[] = "\x1B[2m";
26 static const char DIM_RESET[] = "\x1B[22m";
27 
28 static const char UNDERLINED_SET[] = "\x1B[4m";
29 static const char UNDERLINED_RESET[] = "\x1B[24m";
30 
31 static const char BLINK_SET[] = "\x1B[5m";
32 static const char BLINK_RESET[] = "\x1B[25m";
33 
34 static const char INVERTED_SET[] = "\x1B[7m";
35 static const char INVERTED_RESET[] = "\x1B[27m";
36 
37 static const char MOVE_LEFT[] = "\r";
38 static const char MOVE_UP[] = "\x1B[1A";
39 static const char CLEAR_LINE[] = "\x1B[2K";
40 
41 Pixel dev_null_pixel;
42 
43 #if defined(_WIN32)
44 void WindowsEmulateVT100Terminal() {
45  static bool done = false;
46  if (done)
47  return;
48  done = true;
49 
50  // Enable VT processing on stdout and stdin
51  auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
52 
53  DWORD out_mode = 0;
54  GetConsoleMode(stdout_handle, &out_mode);
55 
56  // https://docs.microsoft.com/en-us/windows/console/setconsolemode
57  const int enable_virtual_terminal_processing = 0x0004;
58  const int disable_newline_auto_return = 0x0008;
59  out_mode |= enable_virtual_terminal_processing;
60  out_mode |= disable_newline_auto_return;
61 
62  SetConsoleMode(stdout_handle, out_mode);
63 }
64 #endif
65 
66 void UpdatePixelStyle(std::stringstream& ss,
67  Pixel& previous,
68  const Pixel& next) {
69  if (next.bold != previous.bold)
70  ss << (next.bold ? BOLD_SET : BOLD_RESET);
71 
72  if (next.dim != previous.dim)
73  ss << (next.dim ? DIM_SET : DIM_RESET);
74 
75  if (next.underlined != previous.underlined)
76  ss << (next.underlined ? UNDERLINED_SET : UNDERLINED_RESET);
77 
78  if (next.blink != previous.blink)
79  ss << (next.blink ? BLINK_SET : BLINK_RESET);
80 
81  if (next.inverted != previous.inverted)
82  ss << (next.inverted ? INVERTED_SET : INVERTED_RESET);
83 
84  if (next.foreground_color != previous.foreground_color ||
85  next.background_color != previous.background_color) {
86  ss << "\x1B[" + next.foreground_color.Print(false) + "m";
87  ss << "\x1B[" + next.background_color.Print(true) + "m";
88  }
89 
90  previous = next;
91 }
92 
93 struct TileEncoding {
94  unsigned int left : 2;
95  unsigned int top : 2;
96  unsigned int right : 2;
97  unsigned int down : 2;
98  unsigned int round : 1;
99 
100  // clang-format off
101  bool operator<(const TileEncoding& other) const {
102  if (left < other.left) return true;
103  if (left > other.left) return false;
104  if (top < other.top) return true;
105  if (top > other.top) return false;
106  if (right < other.right) return true;
107  if (right > other.right) return false;
108  if (down < other.down) return true;
109  if (down > other.down) return false;
110  if (round < other.round) return true;
111  if (round > other.round) return false;
112  return false;
113  }
114  // clang-format on
115 };
116 
117 // clang-format off
118 const std::map<std::string, TileEncoding> tile_encoding = {
119  {"─", {1, 0, 1, 0, 0}},
120  {"━", {2, 0, 2, 0, 0}},
121 
122  {"│", {0, 1, 0, 1, 0}},
123  {"┃", {0, 2, 0, 2, 0}},
124 
125  {"┌", {0, 0, 1, 1, 0}},
126  {"┍", {0, 0, 2, 1, 0}},
127  {"┎", {0, 0, 1, 2, 0}},
128  {"┏", {0, 0, 2, 2, 0}},
129 
130  {"┐", {1, 0, 0, 1, 0}},
131  {"┑", {2, 0, 0, 1, 0}},
132  {"┒", {1, 0, 0, 2, 0}},
133  {"┓", {2, 0, 0, 2, 0}},
134 
135  {"└", {0, 1, 1, 0, 0}},
136  {"┕", {0, 1, 2, 0, 0}},
137  {"┖", {0, 2, 1, 0, 0}},
138  {"┗", {0, 2, 2, 0, 0}},
139 
140  {"┘", {1, 1, 0, 0, 0}},
141  {"┙", {2, 1, 0, 0, 0}},
142  {"┚", {1, 2, 0, 0, 0}},
143  {"┛", {2, 2, 0, 0, 0}},
144 
145  {"├", {0, 1, 1, 1, 0}},
146  {"┝", {0, 1, 2, 1, 0}},
147  {"┞", {0, 2, 1, 1, 0}},
148  {"┟", {0, 1, 1, 2, 0}},
149  {"┠", {0, 2, 1, 2, 0}},
150  {"┡", {0, 2, 2, 1, 0}},
151  {"┢", {0, 1, 2, 2, 0}},
152  {"┣", {0, 2, 2, 2, 0}},
153 
154  {"┤", {1, 1, 0, 1, 0}},
155  {"┥", {2, 1, 0, 1, 0}},
156  {"┦", {1, 2, 0, 1, 0}},
157  {"┧", {1, 1, 0, 2, 0}},
158  {"┨", {1, 2, 0, 2, 0}},
159  {"┩", {2, 2, 0, 1, 0}},
160  {"┪", {2, 1, 0, 2, 0}},
161  {"┫", {2, 2, 0, 2, 0}},
162 
163  {"┬", {1, 0, 1, 1, 0}},
164  {"┭", {2, 0, 1, 1, 0}},
165  {"┮", {1, 0, 2, 1, 0}},
166  {"┯", {2, 0, 2, 1, 0}},
167  {"┰", {1, 0, 1, 2, 0}},
168  {"┱", {2, 0, 1, 2, 0}},
169  {"┲", {1, 0, 2, 2, 0}},
170  {"┳", {2, 0, 2, 2, 0}},
171 
172  {"┴", {1, 1, 1, 0, 0}},
173  {"┵", {2, 1, 1, 0, 0}},
174  {"┶", {1, 1, 2, 0, 0}},
175  {"┷", {2, 1, 2, 0, 0}},
176  {"┸", {1, 2, 1, 0, 0}},
177  {"┹", {2, 2, 1, 0, 0}},
178  {"┺", {1, 2, 2, 0, 0}},
179  {"┻", {2, 2, 2, 0, 0}},
180 
181  {"┼", {1, 1, 1, 1, 0}},
182  {"┽", {2, 1, 1, 1, 0}},
183  {"┾", {1, 1, 2, 1, 0}},
184  {"┿", {2, 1, 2, 1, 0}},
185  {"╀", {1, 2, 1, 1, 0}},
186  {"╁", {1, 1, 1, 2, 0}},
187  {"╂", {1, 2, 1, 2, 0}},
188  {"╃", {2, 2, 1, 1, 0}},
189  {"╄", {1, 2, 2, 1, 0}},
190  {"╅", {2, 1, 1, 2, 0}},
191  {"╆", {1, 1, 2, 2, 0}},
192  {"╇", {2, 2, 2, 1, 0}},
193  {"╈", {2, 1, 2, 2, 0}},
194  {"╉", {2, 2, 1, 2, 0}},
195  {"╊", {1, 2, 2, 2, 0}},
196  {"╋", {2, 2, 2, 2, 0}},
197 
198  {"═", {3, 0, 3, 0, 0}},
199  {"║", {0, 3, 0, 3, 0}},
200 
201  {"╒", {0, 0, 3, 1, 0}},
202  {"╓", {0, 0, 1, 3, 0}},
203  {"╔", {0, 0, 3, 3, 0}},
204 
205  {"╕", {3, 0, 0, 1, 0}},
206  {"╖", {1, 0, 0, 3, 0}},
207  {"╗", {3, 0, 0, 3, 0}},
208 
209  {"╘", {0, 1, 3, 0, 0}},
210  {"╙", {0, 3, 1, 0, 0}},
211  {"╚", {0, 3, 3, 0, 0}},
212 
213  {"╛", {3, 1, 0, 0, 0}},
214  {"╜", {1, 3, 0, 0, 0}},
215  {"╝", {3, 3, 0, 0, 0}},
216 
217  {"╞", {0, 1, 3, 1, 0}},
218  {"╟", {0, 3, 1, 3, 0}},
219  {"╠", {0, 3, 3, 3, 0}},
220 
221  {"╡", {3, 1, 0, 1, 0}},
222  {"╢", {1, 3, 0, 3, 0}},
223  {"╣", {3, 3, 0, 3, 0}},
224 
225  {"╤", {3, 0, 3, 1, 0}},
226  {"╥", {1, 0, 1, 3, 0}},
227  {"╦", {3, 0, 3, 3, 0}},
228 
229  {"╧", {3, 1, 3, 0, 0}},
230  {"╨", {1, 3, 1, 0, 0}},
231  {"╩", {3, 3, 3, 0, 0}},
232 
233  {"╪", {3, 1, 3, 1, 0}},
234  {"╫", {1, 3, 1, 3, 0}},
235  {"╬", {3, 3, 3, 3, 0}},
236 
237  {"╭", {0, 0, 1, 1, 1}},
238  {"╮", {1, 0, 0, 1, 1}},
239  {"╯", {1, 1, 0, 0, 1}},
240  {"╰", {0, 1, 1, 0, 1}},
241 
242  {"╴", {1, 0, 0, 0, 0}},
243  {"╵", {0, 1, 0, 0, 0}},
244  {"╶", {0, 0, 1, 0, 0}},
245  {"╷", {0, 0, 0, 1, 0}},
246 
247  {"╸", {2, 0, 0, 0, 0}},
248  {"╹", {0, 2, 0, 0, 0}},
249  {"╺", {0, 0, 2, 0, 0}},
250  {"╻", {0, 0, 0, 2, 0}},
251 
252  {"╼", {1, 0, 2, 0, 0}},
253  {"╽", {0, 1, 0, 2, 0}},
254  {"╾", {2, 0, 1, 0, 0}},
255  {"╿", {0, 2, 0, 1, 0}},
256 };
257 // clang-format on
258 
259 template <class A, class B>
260 const std::map<B, A> InvertMap(const std::map<A, B> input) {
261  std::map<B, A> output;
262  for (const auto& it : input)
263  output[it.second] = it.first;
264  return output;
265 }
266 
267 const std::map<TileEncoding, std::string> tile_encoding_inverse =
268  InvertMap(tile_encoding);
269 
270 void UpgradeLeftRight(std::string& left, std::string& right) {
271  const auto it_left = tile_encoding.find(left);
272  if (it_left == tile_encoding.end())
273  return;
274  const auto it_right = tile_encoding.find(right);
275  if (it_right == tile_encoding.end())
276  return;
277 
278  if (it_left->second.right == 0 && it_right->second.left != 0) {
279  TileEncoding encoding_left = it_left->second;
280  encoding_left.right = it_right->second.left;
281  const auto it_left_upgrade = tile_encoding_inverse.find(encoding_left);
282  if (it_left_upgrade != tile_encoding_inverse.end())
283  left = it_left_upgrade->second;
284  }
285 
286  if (it_right->second.left == 0 && it_left->second.right != 0) {
287  TileEncoding encoding_right = it_right->second;
288  encoding_right.left = it_left->second.right;
289  const auto it_right_upgrade = tile_encoding_inverse.find(encoding_right);
290  if (it_right_upgrade != tile_encoding_inverse.end())
291  right = it_right_upgrade->second;
292  }
293 }
294 
295 void UpgradeTopDown(std::string& top, std::string& down) {
296  const auto it_top = tile_encoding.find(top);
297  if (it_top == tile_encoding.end())
298  return;
299  const auto it_down = tile_encoding.find(down);
300  if (it_down == tile_encoding.end())
301  return;
302 
303  if (it_top->second.down == 0 && it_down->second.top != 0) {
304  TileEncoding encoding_top = it_top->second;
305  encoding_top.down = it_down->second.top;
306  const auto it_top_down = tile_encoding_inverse.find(encoding_top);
307  if (it_top_down != tile_encoding_inverse.end())
308  top = it_top_down->second;
309  }
310 
311  if (it_down->second.top == 0 && it_top->second.down != 0) {
312  TileEncoding encoding_down = it_down->second;
313  encoding_down.top = it_top->second.down;
314  const auto it_down_top = tile_encoding_inverse.find(encoding_down);
315  if (it_down_top != tile_encoding_inverse.end())
316  down = it_down_top->second;
317  }
318 }
319 
320 } // namespace
321 
322 /// A fixed dimension.
323 /// @see Fit
324 /// @see Full
326  return {v, v};
327 }
328 
329 /// Use the terminal dimensions.
330 /// @see Fixed
331 /// @see Fit
333  return Terminal::Size();
334 }
335 
336 // static
337 /// Create a screen with the given dimension along the x-axis and y-axis.
339  return Screen(width.dimx, height.dimy);
340 }
341 
342 // static
343 /// Create a screen with the given dimension.
345  return Screen(dimension.dimx, dimension.dimy);
346 }
347 
348 Screen::Screen(int dimx, int dimy)
349  : stencil{0, dimx - 1, 0, dimy - 1},
350  dimx_(dimx),
351  dimy_(dimy),
352  pixels_(dimy, std::vector<Pixel>(dimx)) {
353 #if defined(_WIN32)
354  // The placement of this call is a bit weird, however we can assume that
355  // anybody who instantiates a Screen object eventually wants to output
356  // something to the console.
357  // As we require UTF8 for all input/output operations we will just switch to
358  // UTF8 encoding here
359  SetConsoleOutputCP(CP_UTF8);
360  SetConsoleCP(CP_UTF8);
361  WindowsEmulateVT100Terminal();
362 #endif
363 }
364 
365 /// Produce a std::string that can be used to print the Screen on the terminal.
366 /// Don't forget to flush stdout. Alternatively, you can use Screen::Print();
367 std::string Screen::ToString() {
368  std::stringstream ss;
369 
370  Pixel previous_pixel;
371  Pixel final_pixel;
372 
373  for (int y = 0; y < dimy_; ++y) {
374  if (y != 0) {
375  UpdatePixelStyle(ss, previous_pixel, final_pixel);
376  ss << "\r\n";
377  }
378  bool previous_fullwidth = false;
379  for (const auto& pixel : pixels_[y]) {
380  if (!previous_fullwidth) {
381  UpdatePixelStyle(ss, previous_pixel, pixel);
382  ss << pixel.character;
383  }
384  previous_fullwidth = (string_width(pixel.character) == 2);
385  }
386  }
387 
388  UpdatePixelStyle(ss, previous_pixel, final_pixel);
389 
390  return ss.str();
391 }
392 
394  std::cout << ToString() << '\0' << std::flush;
395 }
396 
397 /// @brief Access a character a given position.
398 /// @param x The character position along the x-axis.
399 /// @param y The character position along the y-axis.
400 std::string& Screen::at(int x, int y) {
401  return PixelAt(x, y).character;
402 }
403 
404 /// @brief Access a Pixel at a given position.
405 /// @param x The pixel position along the x-axis.
406 /// @param y The pixel position along the y-axis.
407 Pixel& Screen::PixelAt(int x, int y) {
408  return stencil.Contain(x, y) ? pixels_[y][x] : dev_null_pixel;
409 }
410 
411 /// @brief Return a string to be printed in order to reset the cursor position
412 /// to the beginning of the screen.
413 ///
414 /// ```cpp
415 /// std::string reset_position;
416 /// while(true) {
417 /// auto document = render();
418 /// auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
419 /// Render(screen, document);
420 /// std::cout << reset_position << screen.ToString() << std::flush;
421 /// reset_position = screen.ResetPosition();
422 ///
423 /// using namespace std::chrono_literals;
424 /// std::this_thread::sleep_for(0.01s);
425 /// }
426 /// ```
427 ///
428 /// @return The string to print in order to reset the cursor position to the
429 /// beginning.
430 std::string Screen::ResetPosition(bool clear) {
431  std::stringstream ss;
432  if (clear) {
433  ss << MOVE_LEFT << CLEAR_LINE;
434  for (int y = 1; y < dimy_; ++y) {
435  ss << MOVE_UP << CLEAR_LINE;
436  }
437  } else {
438  ss << MOVE_LEFT;
439  for (int y = 1; y < dimy_; ++y) {
440  ss << MOVE_UP;
441  }
442  }
443  return ss.str();
444 }
445 
446 /// @brief Clear all the pixel from the screen.
448  pixels_ = std::vector<std::vector<Pixel>>(dimy_,
449  std::vector<Pixel>(dimx_, Pixel()));
450  cursor_.x = dimx_ - 1;
451  cursor_.y = dimy_ - 1;
452 }
453 
454 // clang-format off
456  // Merge box characters togethers.
457  for (int y = 1; y < dimy_; ++y) {
458  for (int x = 1; x < dimx_; ++x) {
459  // Box drawing character uses exactly 3 byte.
460  std::string& cur = pixels_[y][x].character;
461  if (cur.size() != 3u)
462  continue;
463 
464  // Left vs current.
465  std::string& left = pixels_[y][x-1].character;
466  if (left.size() == 3u)
467  UpgradeLeftRight(left, cur);
468 
469  // Top vs current.
470  std::string& top = pixels_[y-1][x].character;
471  if (top.size() == 3u)
472  UpgradeTopDown(top, cur);
473  }
474  }
475 }
476 
477 // clang-format on
478 
479 } // namespace ftxui
480 
481 // Copyright 2020 Arthur Sonzogni. All rights reserved.
482 // Use of this source code is governed by the MIT license that can be found in
483 // the LICENSE file.
A rectangular grid of Pixel.
Definition: screen.hpp:49
void ApplyShader()
Definition: screen.cpp:455
std::string ResetPosition(bool clear=false)
Return a string to be printed in order to reset the cursor position to the beginning of the screen.
Definition: screen.cpp:430
static Screen Create(Dimensions dimension)
Create a screen with the given dimension.
Definition: screen.cpp:344
Pixel & PixelAt(int x, int y)
Access a Pixel at a given position.
Definition: screen.cpp:407
std::string & at(int x, int y)
Access a character a given position.
Definition: screen.cpp:400
Screen(int dimx, int dimy)
Definition: screen.cpp:348
std::string ToString()
Definition: screen.cpp:367
void Print()
Definition: screen.cpp:393
Cursor cursor_
Definition: screen.hpp:88
void Clear()
Clear all the pixel from the screen.
Definition: screen.cpp:447
std::vector< std::vector< Pixel > > pixels_
Definition: screen.hpp:87
Dimensions Fixed(int)
Definition: screen.cpp:325
Dimensions Full()
Definition: screen.cpp:332
Dimensions Size()
Definition: terminal.cpp:30
int string_width(const std::string &)
Definition: string.cpp:226
bool Contain(int x, int y)
Definition: box.cpp:20
A unicode character and its associated style.
Definition: screen.hpp:16
std::string character
Definition: screen.hpp:19