19#define WIN32_LEAN_AND_MEAN
27#if !defined(FTXUI_UNLIKELY)
28#if defined(COMPILER_GCC) || defined(__clang__)
29#define FTXUI_UNLIKELY(x) __builtin_expect(!!(x), 0)
31#define FTXUI_UNLIKELY(x) (x)
35#if !defined(FTXUI_LIKELY)
36#if defined(COMPILER_GCC) || defined(__clang__)
37#define FTXUI_LIKELY(x) __builtin_expect(!!(x), 1)
39#define FTXUI_LIKELY(x) (x)
48void WindowsEmulateVT100Terminal() {
49 static bool done =
false;
56 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
59 GetConsoleMode(stdout_handle, &out_mode);
62 const int enable_virtual_terminal_processing = 0x0004;
63 const int disable_newline_auto_return = 0x0008;
64 out_mode |= enable_virtual_terminal_processing;
65 out_mode |= disable_newline_auto_return;
67 SetConsoleMode(stdout_handle, out_mode);
72void UpdatePixelStyle(
const Screen*
screen,
73 std::stringstream& ss,
78 ss <<
"\x1B]8;;" <<
screen->Hyperlink(next.hyperlink) <<
"\x1B\";
82 if (FTXUI_UNLIKELY((next.bold ^ prev.bold) || (next.dim ^ prev.dim))) {
83 // BOLD_AND_DIM_RESET:
84 ss << ((prev.bold && !next.bold) || (prev.dim && !next.dim) ? "\x1B[22m
"
86 ss << (next.bold ? "\x1B[1m
" : ""); // BOLD_SET
87 ss << (next.dim ? "\x1B[2m
" : ""); // DIM_SET
91 if (FTXUI_UNLIKELY(next.underlined != prev.underlined ||
92 next.underlined_double != prev.underlined_double)) {
93 ss << (next.underlined ? "\x1B[4m
" // UNDERLINE
94 : next.underlined_double ? "\x1B[21m
" // UNDERLINE_DOUBLE
95 : "\x1B[24m
"); // UNDERLINE_RESET
99 if (FTXUI_UNLIKELY(next.blink != prev.blink)) {
100 ss << (next.blink ? "\x1B[5m
" // BLINK_SET
101 : "\x1B[25m
"); // BLINK_RESET
105 if (FTXUI_UNLIKELY(next.inverted != prev.inverted)) {
106 ss << (next.inverted ? "\x1B[7m
" // INVERTED_SET
107 : "\x1B[27m
"); // INVERTED_RESET
111 if (FTXUI_UNLIKELY(next.italic != prev.italic)) {
112 ss << (next.italic ? "\x1B[3m
" // ITALIC_SET
113 : "\x1B[23m
"); // ITALIC_RESET
117 if (FTXUI_UNLIKELY(next.strikethrough != prev.strikethrough)) {
118 ss << (next.strikethrough ? "\x1B[9m
" // CROSSED_OUT
119 : "\x1B[29m
"); // CROSSED_OUT_RESET
122 if (FTXUI_UNLIKELY(next.foreground_color != prev.foreground_color ||
123 next.background_color != prev.background_color)) {
124 ss << "\x1B[
" + next.foreground_color.Print(false) + "m
";
125 ss << "\x1B[
" + next.background_color.Print(true) + "m
";
130 std::uint8_t left : 2;
131 std::uint8_t top : 2;
132 std::uint8_t right : 2;
133 std::uint8_t down : 2;
134 std::uint8_t round : 1;
137 bool operator<(const TileEncoding& other) const {
138 if (left < other.left) { return true; }
139 if (left > other.left) { return false; }
140 if (top < other.top) { return true; }
141 if (top > other.top) { return false; }
142 if (right < other.right) { return true; }
143 if (right > other.right) { return false; }
144 if (down < other.down) { return true; }
145 if (down > other.down) { return false; }
146 if (round < other.round) { return true; }
147 if (round > other.round) { return false; }
154const std::map<std::string, TileEncoding> tile_encoding = { // NOLINT
155 {"─
", {1, 0, 1, 0, 0}},
156 {"━
", {2, 0, 2, 0, 0}},
157 {"╍
", {2, 0, 2, 0, 0}},
159 {"│
", {0, 1, 0, 1, 0}},
160 {"┃
", {0, 2, 0, 2, 0}},
161 {"╏
", {0, 2, 0, 2, 0}},
163 {"┌
", {0, 0, 1, 1, 0}},
164 {"┍
", {0, 0, 2, 1, 0}},
165 {"┎
", {0, 0, 1, 2, 0}},
166 {"┏
", {0, 0, 2, 2, 0}},
168 {"┐
", {1, 0, 0, 1, 0}},
169 {"┑
", {2, 0, 0, 1, 0}},
170 {"┒
", {1, 0, 0, 2, 0}},
171 {"┓
", {2, 0, 0, 2, 0}},
173 {"└
", {0, 1, 1, 0, 0}},
174 {"┕
", {0, 1, 2, 0, 0}},
175 {"┖
", {0, 2, 1, 0, 0}},
176 {"┗
", {0, 2, 2, 0, 0}},
178 {"┘
", {1, 1, 0, 0, 0}},
179 {"┙
", {2, 1, 0, 0, 0}},
180 {"┚
", {1, 2, 0, 0, 0}},
181 {"┛
", {2, 2, 0, 0, 0}},
183 {"├
", {0, 1, 1, 1, 0}},
184 {"┝
", {0, 1, 2, 1, 0}},
185 {"┞
", {0, 2, 1, 1, 0}},
186 {"┟
", {0, 1, 1, 2, 0}},
187 {"┠
", {0, 2, 1, 2, 0}},
188 {"┡
", {0, 2, 2, 1, 0}},
189 {"┢
", {0, 1, 2, 2, 0}},
190 {"┣
", {0, 2, 2, 2, 0}},
192 {"┤
", {1, 1, 0, 1, 0}},
193 {"┥
", {2, 1, 0, 1, 0}},
194 {"┦
", {1, 2, 0, 1, 0}},
195 {"┧
", {1, 1, 0, 2, 0}},
196 {"┨
", {1, 2, 0, 2, 0}},
197 {"┩
", {2, 2, 0, 1, 0}},
198 {"┪
", {2, 1, 0, 2, 0}},
199 {"┫
", {2, 2, 0, 2, 0}},
201 {"┬
", {1, 0, 1, 1, 0}},
202 {"┭
", {2, 0, 1, 1, 0}},
203 {"┮
", {1, 0, 2, 1, 0}},
204 {"┯
", {2, 0, 2, 1, 0}},
205 {"┰
", {1, 0, 1, 2, 0}},
206 {"┱
", {2, 0, 1, 2, 0}},
207 {"┲
", {1, 0, 2, 2, 0}},
208 {"┳
", {2, 0, 2, 2, 0}},
210 {"┴
", {1, 1, 1, 0, 0}},
211 {"┵
", {2, 1, 1, 0, 0}},
212 {"┶
", {1, 1, 2, 0, 0}},
213 {"┷
", {2, 1, 2, 0, 0}},
214 {"┸
", {1, 2, 1, 0, 0}},
215 {"┹
", {2, 2, 1, 0, 0}},
216 {"┺
", {1, 2, 2, 0, 0}},
217 {"┻
", {2, 2, 2, 0, 0}},
219 {"┼
", {1, 1, 1, 1, 0}},
220 {"┽
", {2, 1, 1, 1, 0}},
221 {"┾
", {1, 1, 2, 1, 0}},
222 {"┿
", {2, 1, 2, 1, 0}},
223 {"╀
", {1, 2, 1, 1, 0}},
224 {"╁
", {1, 1, 1, 2, 0}},
225 {"╂
", {1, 2, 1, 2, 0}},
226 {"╃
", {2, 2, 1, 1, 0}},
227 {"╄
", {1, 2, 2, 1, 0}},
228 {"╅
", {2, 1, 1, 2, 0}},
229 {"╆
", {1, 1, 2, 2, 0}},
230 {"╇
", {2, 2, 2, 1, 0}},
231 {"╈
", {2, 1, 2, 2, 0}},
232 {"╉
", {2, 2, 1, 2, 0}},
233 {"╊
", {1, 2, 2, 2, 0}},
234 {"╋
", {2, 2, 2, 2, 0}},
236 {"═
", {3, 0, 3, 0, 0}},
237 {"║
", {0, 3, 0, 3, 0}},
239 {"╒
", {0, 0, 3, 1, 0}},
240 {"╓
", {0, 0, 1, 3, 0}},
241 {"╔
", {0, 0, 3, 3, 0}},
243 {"╕
", {3, 0, 0, 1, 0}},
244 {"╖
", {1, 0, 0, 3, 0}},
245 {"╗
", {3, 0, 0, 3, 0}},
247 {"╘
", {0, 1, 3, 0, 0}},
248 {"╙
", {0, 3, 1, 0, 0}},
249 {"╚
", {0, 3, 3, 0, 0}},
251 {"╛
", {3, 1, 0, 0, 0}},
252 {"╜
", {1, 3, 0, 0, 0}},
253 {"╝
", {3, 3, 0, 0, 0}},
255 {"╞
", {0, 1, 3, 1, 0}},
256 {"╟
", {0, 3, 1, 3, 0}},
257 {"╠
", {0, 3, 3, 3, 0}},
259 {"╡
", {3, 1, 0, 1, 0}},
260 {"╢
", {1, 3, 0, 3, 0}},
261 {"╣
", {3, 3, 0, 3, 0}},
263 {"╤
", {3, 0, 3, 1, 0}},
264 {"╥
", {1, 0, 1, 3, 0}},
265 {"╦
", {3, 0, 3, 3, 0}},
267 {"╧
", {3, 1, 3, 0, 0}},
268 {"╨
", {1, 3, 1, 0, 0}},
269 {"╩
", {3, 3, 3, 0, 0}},
271 {"╪
", {3, 1, 3, 1, 0}},
272 {"╫
", {1, 3, 1, 3, 0}},
273 {"╬
", {3, 3, 3, 3, 0}},
275 {"╭
", {0, 0, 1, 1, 1}},
276 {"╮
", {1, 0, 0, 1, 1}},
277 {"╯
", {1, 1, 0, 0, 1}},
278 {"╰
", {0, 1, 1, 0, 1}},
280 {"╴
", {1, 0, 0, 0, 0}},
281 {"╵
", {0, 1, 0, 0, 0}},
282 {"╶
", {0, 0, 1, 0, 0}},
283 {"╷
", {0, 0, 0, 1, 0}},
285 {"╸
", {2, 0, 0, 0, 0}},
286 {"╹
", {0, 2, 0, 0, 0}},
287 {"╺
", {0, 0, 2, 0, 0}},
288 {"╻
", {0, 0, 0, 2, 0}},
290 {"╼
", {1, 0, 2, 0, 0}},
291 {"╽
", {0, 1, 0, 2, 0}},
292 {"╾
", {2, 0, 1, 0, 0}},
293 {"╿
", {0, 2, 0, 1, 0}},
297template <class A, class B>
298std::map<B, A> InvertMap(const std::map<A, B> input) {
299 std::map<B, A> output;
300 for (const auto& it : input) {
301 output[it.second] = it.first;
306const std::map<TileEncoding, std::string> tile_encoding_inverse = // NOLINT
307 InvertMap(tile_encoding);
309void UpgradeLeftRight(std::string& left, std::string& right) {
310 const auto it_left = tile_encoding.find(left);
311 if (it_left == tile_encoding.end()) {
314 const auto it_right = tile_encoding.find(right);
315 if (it_right == tile_encoding.end()) {
319 if (it_left->second.right == 0 && it_right->second.left != 0) {
320 TileEncoding encoding_left = it_left->second;
321 encoding_left.right = it_right->second.left;
322 const auto it_left_upgrade = tile_encoding_inverse.find(encoding_left);
323 if (it_left_upgrade != tile_encoding_inverse.end()) {
324 left = it_left_upgrade->second;
328 if (it_right->second.left == 0 && it_left->second.right != 0) {
329 TileEncoding encoding_right = it_right->second;
330 encoding_right.left = it_left->second.right;
331 const auto it_right_upgrade = tile_encoding_inverse.find(encoding_right);
332 if (it_right_upgrade != tile_encoding_inverse.end()) {
333 right = it_right_upgrade->second;
338void UpgradeTopDown(std::string& top, std::string& down) {
339 const auto it_top = tile_encoding.find(top);
340 if (it_top == tile_encoding.end()) {
343 const auto it_down = tile_encoding.find(down);
344 if (it_down == tile_encoding.end()) {
348 if (it_top->second.down == 0 && it_down->second.top != 0) {
349 TileEncoding encoding_top = it_top->second;
350 encoding_top.down = it_down->second.top;
351 const auto it_top_down = tile_encoding_inverse.find(encoding_top);
352 if (it_top_down != tile_encoding_inverse.end()) {
353 top = it_top_down->second;
357 if (it_down->second.top == 0 && it_top->second.down != 0) {
358 TileEncoding encoding_down = it_down->second;
359 encoding_down.top = it_top->second.down;
360 const auto it_down_top = tile_encoding_inverse.find(encoding_down);
361 if (it_down_top != tile_encoding_inverse.end()) {
362 down = it_down_top->second;
367bool ShouldAttemptAutoMerge(Pixel& pixel) {
368 return pixel.automerge && pixel.character.size() == 3;
376Dimensions Dimension::Fixed(int v) {
383Dimensions Dimension::Full() {
384 return Terminal::Size();
389Screen Screen::Create(Dimensions width, Dimensions height) {
390 return {width.dimx, height.dimy};
395Screen Screen::Create(Dimensions dimension) {
396 return {dimension.dimx, dimension.dimy};
399Screen::Screen(int dimx, int dimy) : Image{dimx, dimy} {
401 // Le placement de cet appel est un peu étrange, cependant nous pouvons supposer que
402 // toute personne qui instancie un objet Screen souhaite éventuellement afficher
403 // quelque chose sur la console. Si ce n'est pas le cas, utilisez une instance de Image
404 // à la place. Comme nous exigeons l'UTF8 pour toutes les opérations d'entrée/sortie, nous allons simplement
405 // passer à l'encodage UTF8 ici.
406 SetConsoleOutputCP(CP_UTF8);
407 SetConsoleCP(CP_UTF8);
408 WindowsEmulateVT100Terminal();
416std::string Screen::ToString() const {
417 std::stringstream ss;
419 const Pixel default_pixel;
420 const Pixel* previous_pixel_ref = &default_pixel;
422 for (int y = 0; y < dimy_; ++y) {
423 // Nouvelle ligne entre deux lignes.
425 UpdatePixelStyle(this, ss, *previous_pixel_ref, default_pixel);
426 previous_pixel_ref = &default_pixel;
430 // Après avoir affiché un caractère pleine largeur, nous devons sauter la cellule suivante.
431 bool previous_fullwidth = false;
432 for (const auto& pixel : pixels_[y]) {
433 if (!previous_fullwidth) {
434 UpdatePixelStyle(this, ss, *previous_pixel_ref, pixel);
435 previous_pixel_ref = &pixel;
436 if (pixel.character.empty()) {
439 ss << pixel.character;
442 previous_fullwidth = (string_width(pixel.character) == 2);
446 // Réinitialise le style par défaut:
447 UpdatePixelStyle(this, ss, *previous_pixel_ref, default_pixel);
452// Affiche l'écran sur le terminal.
453void Screen::Print() const {
454 std::cout << ToString() << '\0' << std::flush;
476std::string Screen::ResetPosition(bool clear) const {
477 std::stringstream ss;
479 ss << "\r
"; // MOVE_LEFT;
480 ss << "\x1b[2K
"; // CLEAR_SCREEN;
481 for (int y = 1; y < dimy_; ++y) {
482 ss << "\x1B[1A
"; // MOVE_UP;
483 ss << "\x1B[2K
"; // CLEAR_LINE;
486 ss << "\r
"; // MOVE_LEFT;
487 for (int y = 1; y < dimy_; ++y) {
488 ss << "\x1B[1A
"; // MOVE_UP;
495void Screen::Clear() {
498 cursor_.x = dimx_ - 1;
499 cursor_.y = dimy_ - 1;
507void Screen::ApplyShader() {
508 // Fusionne les caractères de boîte ensemble.
509 for (int y = 0; y < dimy_; ++y) {
510 for (int x = 0; x < dimx_; ++x) {
511 // Les caractères de dessin de boîte utilisent exactement 3 octets.
512 Pixel& cur = pixels_[y][x];
513 if (!ShouldAttemptAutoMerge(cur)) {
518 Pixel& left = pixels_[y][x-1];
519 if (ShouldAttemptAutoMerge(left)) {
520 UpgradeLeftRight(left.character, cur.character);
524 Pixel& top = pixels_[y-1][x];
525 if (ShouldAttemptAutoMerge(top)) {
526 UpgradeTopDown(top.character, cur.character);
534std::uint8_t Screen::RegisterHyperlink(const std::string& link) {
535 for (std::size_t i = 0; i < hyperlinks_.size(); ++i) {
536 if (hyperlinks_[i] == link) {
540 if (hyperlinks_.size() == std::numeric_limits<std::uint8_t>::max()) {
543 hyperlinks_.push_back(link);
544 return hyperlinks_.size() - 1;
547const std::string& Screen::Hyperlink(std::uint8_t id) const {
548 if (id >= hyperlinks_.size()) {
549 return hyperlinks_[0];
551 return hyperlinks_[id];
556const Screen::SelectionStyle& Screen::GetSelectionStyle() const {
557 return selection_style_;
562void Screen::SetSelectionStyle(SelectionStyle decorator) {
563 selection_style_ = std::move(decorator);
L'espace de noms FTXUI ftxui::
#define FTXUI_UNLIKELY(x)