FTXUI  3.0.0
C++ functional terminal UI.
Loading...
Searching...
No Matches
canvas.cpp
Go to the documentation of this file.
2
3#include <algorithm> // for max, min
4#include <cstdint> // for uint8_t
5#include <cstdlib> // for abs
6#include <map> // for allocator, map
7#include <memory> // for make_shared
8#include <utility> // for move, pair
9#include <vector> // for vector
10
11#include "ftxui/dom/elements.hpp" // for Element, canvas
12#include "ftxui/dom/node.hpp" // for Node
13#include "ftxui/dom/requirement.hpp" // for Requirement
14#include "ftxui/screen/box.hpp" // for Box
15#include "ftxui/screen/screen.hpp" // for Pixel, Screen
16#include "ftxui/screen/string.hpp" // for Utf8ToGlyphs
17#include "ftxui/util/ref.hpp" // for ConstRef
18
19namespace ftxui {
20
21namespace {
22
23// Base UTF8 pattern:
24// 11100010 10100000 10000000 // empty
25
26// Pattern for the individuel dots:
27// ┌──────┬───────┐
28// │dot1 │ dot4 │
29// ├──────┼───────┤
30// │dot2 │ dot5 │
31// ├──────┼───────┤
32// │dot3 │ dot6 │
33// ├──────┼───────┤
34// │dot0-1│ dot0-2│
35// └──────┴───────┘
36// 11100010 10100000 10000001 // dot1
37// 11100010 10100000 10000010 // dot2
38// 11100010 10100000 10000100 // dot3
39// 11100010 10100001 10000000 // dot0-1
40// 11100010 10100000 10001000 // dot4
41// 11100010 10100000 10010000 // dot5
42// 11100010 10100000 10100000 // dot6
43// 11100010 10100010 10000000 // dot0-2
44
45// NOLINTNEXTLINE
46uint8_t g_map_braille[2][4][2] = {
47 {
48 {0b00000000, 0b00000001}, // NOLINT | dot1
49 {0b00000000, 0b00000010}, // NOLINT | dot2
50 {0b00000000, 0b00000100}, // NOLINT | dot3
51 {0b00000001, 0b00000000}, // NOLINT | dot0-1
52 },
53 {
54 {0b00000000, 0b00001000}, // NOLINT | dot4
55 {0b00000000, 0b00010000}, // NOLINT | dot5
56 {0b00000000, 0b00100000}, // NOLINT | dot6
57 {0b00000010, 0b00000000}, // NOLINT | dot0-2
58 },
59};
60
61// NOLINTNEXTLINE
62std::vector<std::string> g_map_block = {
63 " ", "▘", "▖", "▌", "▝", "▀", "▞", "▛",
64 "▗", "▚", "▄", "▙", "▐", "▜", "▟", "█",
65};
66
67// NOLINTNEXTLINE
68const std::map<std::string, uint8_t> g_map_block_inversed = {
69 {" ", 0b0000}, {"▘", 0b0001}, {"▖", 0b0010}, {"▌", 0b0011},
70 {"▝", 0b0100}, {"▀", 0b0101}, {"▞", 0b0110}, {"▛", 0b0111},
71 {"▗", 0b1000}, {"▚", 0b1001}, {"▄", 0b1010}, {"▙", 0b1011},
72 {"▐", 0b1100}, {"▜", 0b1101}, {"▟", 0b1110}, {"█", 0b1111},
73};
74
75constexpr auto nostyle = [](Pixel& /*pixel*/) {};
76
77} // namespace
78
79/// @brief Constructor.
80/// @param width the width of the canvas. A cell is a 2x8 braille dot.
81/// @param height the height of the canvas. A cell is a 2x8 braille dot.
82Canvas::Canvas(int width, int height)
83 : width_(width),
84 height_(height),
85 storage_(width_ * height_ / 8 /* NOLINT */) {}
86
87/// @brief Get the content of a cell.
88/// @param x the x coordinate of the cell.
89/// @param y the y coordinate of the cell.
90Pixel Canvas::GetPixel(int x, int y) const {
91 auto it = storage_.find(XY{x, y});
92 return (it == storage_.end()) ? Pixel{} : it->second.content;
93}
94
95/// @brief Draw a braille dot.
96/// @param x the x coordinate of the dot.
97/// @param y the y coordinate of the dot.
98/// @param value whether the dot is filled or not.
99void Canvas::DrawPoint(int x, int y, bool value) {
100 DrawPoint(x, y, value, [](Pixel& /*pixel*/) {});
101}
102
103/// @brief Draw a braille dot.
104/// @param x the x coordinate of the dot.
105/// @param y the y coordinate of the dot.
106/// @param value whether the dot is filled or not.
107/// @param color the color of the dot.
108void Canvas::DrawPoint(int x, int y, bool value, const Color& color) {
109 DrawPoint(x, y, value, [color](Pixel& p) { p.foreground_color = color; });
110}
111
112/// @brief Draw a braille dot.
113/// @param x the x coordinate of the dot.
114/// @param y the y coordinate of the dot.
115/// @param value whether the dot is filled or not.
116/// @param style the style of the cell.
117void Canvas::DrawPoint(int x, int y, bool value, const Stylizer& style) {
118 Style(x, y, style);
119 if (value) {
120 DrawPointOn(x, y);
121 } else {
122 DrawPointOff(x, y);
123 }
124}
125
126/// @brief Draw a braille dot.
127/// @param x the x coordinate of the dot.
128/// @param y the y coordinate of the dot.
129void Canvas::DrawPointOn(int x, int y) {
130 if (!IsIn(x, y)) {
131 return;
132 }
133 Cell& cell = storage_[XY{x / 2, y / 4}];
134 if (cell.type != CellType::kBraille) {
135 cell.content.character = "⠀"; // 3 bytes.
136 cell.type = CellType::kBraille;
137 }
138
139 cell.content.character[1] |= g_map_braille[x % 2][y % 4][0]; // NOLINT
140 cell.content.character[2] |= g_map_braille[x % 2][y % 4][1]; // NOLINT
141}
142
143/// @brief Erase a braille dot.
144/// @param x the x coordinate of the dot.
145/// @param y the y coordinate of the dot.
146void Canvas::DrawPointOff(int x, int y) {
147 if (!IsIn(x, y)) {
148 return;
149 }
150 Cell& cell = storage_[XY{x / 2, y / 4}];
151 if (cell.type != CellType::kBraille) {
152 cell.content.character = "⠀"; // 3 byt
153 cell.type = CellType::kBraille;
154 }
155
156 cell.content.character[1] &= ~(g_map_braille[x % 2][y % 4][0]); // NOLINT
157 cell.content.character[2] &= ~(g_map_braille[x % 2][y % 4][1]); // NOLINT
158}
159
160/// @brief Toggle a braille dot. A filled one will be erased, and the other will
161/// be drawn.
162/// @param x the x coordinate of the dot.
163/// @param y the y coordinate of the dot.
164void Canvas::DrawPointToggle(int x, int y) {
165 if (!IsIn(x, y)) {
166 return;
167 }
168 Cell& cell = storage_[XY{x / 2, y / 4}];
169 if (cell.type != CellType::kBraille) {
170 cell.content.character = "⠀"; // 3 byt
171 cell.type = CellType::kBraille;
172 }
173
174 cell.content.character[1] ^= g_map_braille[x % 2][y % 4][0]; // NOLINT
175 cell.content.character[2] ^= g_map_braille[x % 2][y % 4][1]; // NOLINT
176}
177
178/// @brief Draw a line made of braille dots.
179/// @param x1 the x coordinate of the first dot.
180/// @param y1 the y coordinate of the first dot.
181/// @param x2 the x coordinate of the second dot.
182/// @param y2 the y coordinate of the second dot.
183void Canvas::DrawPointLine(int x1, int y1, int x2, int y2) {
184 DrawPointLine(x1, y1, x2, y2, [](Pixel& /*pixel*/) {});
185}
186
187/// @brief Draw a line made of braille dots.
188/// @param x1 the x coordinate of the first dot.
189/// @param y1 the y coordinate of the first dot.
190/// @param x2 the x coordinate of the second dot.
191/// @param y2 the y coordinate of the second dot.
192/// @param color the color of the line.
193void Canvas::DrawPointLine(int x1, int y1, int x2, int y2, const Color& color) {
194 DrawPointLine(x1, y1, x2, y2,
195 [color](Pixel& p) { p.foreground_color = color; });
196}
197
198/// @brief Draw a line made of braille dots.
199/// @param x1 the x coordinate of the first dot.
200/// @param y1 the y coordinate of the first dot.o
201/// @param x2 the x coordinate of the second dot.
202/// @param y2 the y coordinate of the second dot.
203/// @param style the style of the line.
205 int y1,
206 int x2,
207 int y2,
208 const Stylizer& style) {
209 const int dx = std::abs(x2 - x1);
210 const int dy = std::abs(y2 - y1);
211 const int sx = x1 < x2 ? 1 : -1;
212 const int sy = y1 < y2 ? 1 : -1;
213 const int length = std::max(dx, dy);
214
215 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
216 return;
217 }
218 if (dx + dx > width_ * height_) {
219 return;
220 }
221
222 int error = dx - dy;
223 for (int i = 0; i < length; ++i) {
224 DrawPoint(x1, y1, true, style);
225 if (2 * error >= -dy) {
226 error -= dy;
227 x1 += sx;
228 }
229 if (2 * error <= dx) {
230 error += dx;
231 y1 += sy;
232 }
233 }
234 DrawPoint(x2, y2, true, style);
235}
236
237/// @brief Draw a circle made of braille dots.
238/// @param x the x coordinate of the center of the circle.
239/// @param y the y coordinate of the center of the circle.
240/// @param radius the radius of the circle.
241void Canvas::DrawPointCircle(int x, int y, int radius) {
242 DrawPointCircle(x, y, radius, [](Pixel& /*pixel*/) {});
243}
244
245/// @brief Draw a circle made of braille dots.
246/// @param x the x coordinate of the center of the circle.
247/// @param y the y coordinate of the center of the circle.
248/// @param radius the radius of the circle.
249/// @param color the color of the circle.
250void Canvas::DrawPointCircle(int x, int y, int radius, const Color& color) {
251 DrawPointCircle(x, y, radius,
252 [color](Pixel& p) { p.foreground_color = color; });
253}
254
255/// @brief Draw a circle made of braille dots.
256/// @param x the x coordinate of the center of the circle.
257/// @param y the y coordinate of the center of the circle.
258/// @param radius the radius of the circle.
259/// @param style the style of the circle.
260void Canvas::DrawPointCircle(int x, int y, int radius, const Stylizer& style) {
261 DrawPointEllipse(x, y, radius, radius, style);
262}
263
264/// @brief Draw a filled circle made of braille dots.
265/// @param x the x coordinate of the center of the circle.
266/// @param y the y coordinate of the center of the circle.
267/// @param radius the radius of the circle.
268void Canvas::DrawPointCircleFilled(int x, int y, int radius) {
269 DrawPointCircleFilled(x, y, radius, [](Pixel& /*pixel*/) {});
270}
271
272/// @brief Draw a filled circle made of braille dots.
273/// @param x the x coordinate of the center of the circle.
274/// @param y the y coordinate of the center of the circle.
275/// @param radius the radius of the circle.
276/// @param color the color of the circle.
278 int y,
279 int radius,
280 const Color& color) {
281 DrawPointCircleFilled(x, y, radius,
282 [color](Pixel& p) { p.foreground_color = color; });
283}
284
285/// @brief Draw a filled circle made of braille dots.
286/// @param x the x coordinate of the center of the circle.
287/// @param y the y coordinate of the center of the circle.
288/// @param radius the radius of the circle.
289/// @param style the style of the circle.
291 int y,
292 int radius,
293 const Stylizer& style) {
294 DrawPointEllipseFilled(x, y, radius, radius, style);
295}
296
297/// @brief Draw an ellipse made of braille dots.
298/// @param x the x coordinate of the center of the ellipse.
299/// @param y the y coordinate of the center of the ellipse.
300/// @param r1 the radius of the ellipse along the x axis.
301/// @param r2 the radius of the ellipse along the y axis.
302void Canvas::DrawPointEllipse(int x, int y, int r1, int r2) {
303 DrawPointEllipse(x, y, r1, r2, [](Pixel& /*pixel*/) {});
304}
305
306/// @brief Draw an ellipse made of braille dots.
307/// @param x the x coordinate of the center of the ellipse.
308/// @param y the y coordinate of the center of the ellipse.
309/// @param r1 the radius of the ellipse along the x axis.
310/// @param r2 the radius of the ellipse along the y axis.
311/// @param color the color of the ellipse.
313 int y,
314 int r1,
315 int r2,
316 const Color& color) {
317 DrawPointEllipse(x, y, r1, r2,
318 [color](Pixel& p) { p.foreground_color = color; });
319}
320
321/// @brief Draw an ellipse made of braille dots.
322/// @param x the x coordinate of the center of the ellipse.
323/// @param y the y coordinate of the center of the ellipse.
324/// @param r1 the radius of the ellipse along the x axis.
325/// @param r2 the radius of the ellipse along the y axis.
326/// @param style the style of the ellipse.
328 int y1,
329 int r1,
330 int r2,
331 const Stylizer& s) {
332 int x = -r1;
333 int y = 0;
334 int e2 = r2;
335 int dx = (1 + 2 * x) * e2 * e2;
336 int dy = x * x;
337 int err = dx + dy;
338
339 do {
340 DrawPoint(x1 - x, y1 + y, true, s);
341 DrawPoint(x1 + x, y1 + y, true, s);
342 DrawPoint(x1 + x, y1 - y, true, s);
343 DrawPoint(x1 - x, y1 - y, true, s);
344 e2 = 2 * err;
345 if (e2 >= dx) {
346 x++;
347 err += dx += 2 * r2 * r2;
348 }
349 if (e2 <= dy) {
350 y++;
351 err += dy += 2 * r1 * r1;
352 }
353 } while (x <= 0);
354
355 while (y++ < r2) {
356 DrawPoint(x1, y1 + y, true, s);
357 DrawPoint(x1, y1 - y, true, s);
358 }
359}
360
361/// @brief Draw a filled ellipse made of braille dots.
362/// @param x the x coordinate of the center of the ellipse.
363/// @param y the y coordinate of the center of the ellipse.
364/// @param r1 the radius of the ellipse along the x axis.
365/// @param r2 the radius of the ellipse along the y axis.
366void Canvas::DrawPointEllipseFilled(int x1, int y1, int r1, int r2) {
367 DrawPointEllipseFilled(x1, y1, r1, r2, [](Pixel& /*pixel*/) {});
368}
369
370/// @brief Draw a filled ellipse made of braille dots.
371/// @param x the x coordinate of the center of the ellipse.
372/// @param y the y coordinate of the center of the ellipse.
373/// @param r1 the radius of the ellipse along the x axis.
374/// @param r2 the radius of the ellipse along the y axis.
375/// @param color the color of the ellipse.
377 int y1,
378 int r1,
379 int r2,
380 const Color& color) {
381 DrawPointEllipseFilled(x1, y1, r1, r2,
382 [color](Pixel& p) { p.foreground_color = color; });
383}
384
385/// @brief Draw a filled ellipse made of braille dots.
386/// @param x the x coordinate of the center of the ellipse.
387/// @param y the y coordinate of the center of the ellipse.
388/// @param r1 the radius of the ellipse along the x axis.
389/// @param r2 the radius of the ellipse along the y axis.
390/// @param style the style of the ellipse.
392 int y1,
393 int r1,
394 int r2,
395 const Stylizer& s) {
396 int x = -r1;
397 int y = 0;
398 int e2 = r2;
399 int dx = (1 + 2 * x) * e2 * e2;
400 int dy = x * x;
401 int err = dx + dy;
402
403 do {
404 for (int xx = x1 + x; xx <= x1 - x; ++xx) {
405 DrawPoint(xx, y1 + y, true, s);
406 DrawPoint(xx, y1 - y, true, s);
407 }
408 e2 = 2 * err;
409 if (e2 >= dx) {
410 x++;
411 err += dx += 2 * (long)r2 * r2; // NOLINT
412 }
413 if (e2 <= dy) {
414 y++;
415 err += dy += 2 * (long)r1 * r1; // NOLINT
416 }
417 } while (x <= 0);
418
419 while (y++ < r2) {
420 for (int yy = y1 - y; yy <= y1 + y; ++yy) {
421 DrawPoint(x1, yy, true, s);
422 }
423 }
424}
425
426/// @brief Draw a block.
427/// @param x the x coordinate of the block.
428/// @param y the y coordinate of the block.
429/// @param value whether the block is filled or not.
430void Canvas::DrawBlock(int x, int y, bool value) {
431 DrawBlock(x, y, value, [](Pixel& /*pixel*/) {});
432}
433
434/// @brief Draw a block.
435/// @param x the x coordinate of the block.
436/// @param y the y coordinate of the block.
437/// @param value whether the block is filled or not.
438/// @param color the color of the block.
439void Canvas::DrawBlock(int x, int y, bool value, const Color& color) {
440 DrawBlock(x, y, value, [color](Pixel& p) { p.foreground_color = color; });
441}
442
443/// @brief Draw a block.
444/// @param x the x coordinate of the block.
445/// @param y the y coordinate of the block.
446/// @param value whether the block is filled or not.
447/// @param style the style of the block.
448void Canvas::DrawBlock(int x, int y, bool value, const Stylizer& style) {
449 Style(x, y, style);
450 if (value) {
451 DrawBlockOn(x, y);
452 } else {
453 DrawBlockOff(x, y);
454 }
455}
456
457/// @brief Draw a block.
458/// @param x the x coordinate of the block.
459/// @param y the y coordinate of the block.
460void Canvas::DrawBlockOn(int x, int y) {
461 if (!IsIn(x, y)) {
462 return;
463 }
464 y /= 2;
465 Cell& cell = storage_[XY{x / 2, y / 2}];
466 if (cell.type != CellType::kBlock) {
467 cell.content.character = " ";
468 cell.type = CellType::kBlock;
469 }
470
471 uint8_t bit = (x % 2) * 2 + y % 2;
472 uint8_t value = g_map_block_inversed.at(cell.content.character);
473 value |= 1U << bit;
474 cell.content.character = g_map_block[value];
475}
476
477/// @brief Erase a block.
478/// @param x the x coordinate of the block.
479/// @param y the y coordinate of the block.
480void Canvas::DrawBlockOff(int x, int y) {
481 if (!IsIn(x, y)) {
482 return;
483 }
484 Cell& cell = storage_[XY{x / 2, y / 4}];
485 if (cell.type != CellType::kBlock) {
486 cell.content.character = " ";
487 cell.type = CellType::kBlock;
488 }
489 y /= 2;
490
491 uint8_t bit = (y % 2) * 2 + x % 2;
492 uint8_t value = g_map_block_inversed.at(cell.content.character);
493 value &= ~(1U << bit);
494 cell.content.character = g_map_block[value];
495}
496
497/// @brief Toggle a block. If it is filled, it will be erased. If it is empty,
498/// it will be filled.
499/// @param x the x coordinate of the block.
500/// @param y the y coordinate of the block.
501void Canvas::DrawBlockToggle(int x, int y) {
502 if (!IsIn(x, y)) {
503 return;
504 }
505 Cell& cell = storage_[XY{x / 2, y / 4}];
506 if (cell.type != CellType::kBlock) {
507 cell.content.character = " ";
508 cell.type = CellType::kBlock;
509 }
510 y /= 2;
511
512 uint8_t bit = (y % 2) * 2 + x % 2;
513 uint8_t value = g_map_block_inversed.at(cell.content.character);
514 value ^= 1U << bit;
515 cell.content.character = g_map_block[value];
516}
517
518/// @brief Draw a line made of block characters.
519/// @param x1 the x coordinate of the first point of the line.
520/// @param y1 the y coordinate of the first point of the line.
521/// @param x2 the x coordinate of the second point of the line.
522/// @param y2 the y coordinate of the second point of the line.
523void Canvas::DrawBlockLine(int x1, int y1, int x2, int y2) {
524 DrawBlockLine(x1, y1, x2, y2, [](Pixel& /*pixel*/) {});
525}
526
527/// @brief Draw a line made of block characters.
528/// @param x1 the x coordinate of the first point of the line.
529/// @param y1 the y coordinate of the first point of the line.
530/// @param x2 the x coordinate of the second point of the line.
531/// @param y2 the y coordinate of the second point of the line.
532/// @param color the color of the line.
533void Canvas::DrawBlockLine(int x1, int y1, int x2, int y2, const Color& color) {
534 DrawBlockLine(x1, y1, x2, y2,
535 [color](Pixel& p) { p.foreground_color = color; });
536}
537
538/// @brief Draw a line made of block characters.
539/// @param x1 the x coordinate of the first point of the line.
540/// @param y1 the y coordinate of the first point of the line.
541/// @param x2 the x coordinate of the second point of the line.
542/// @param y2 the y coordinate of the second point of the line.
543/// @param style the style of the line.
545 int y1,
546 int x2,
547 int y2,
548 const Stylizer& style) {
549 y1 /= 2;
550 y2 /= 2;
551
552 const int dx = std::abs(x2 - x1);
553 const int dy = std::abs(y2 - y1);
554 const int sx = x1 < x2 ? 1 : -1;
555 const int sy = y1 < y2 ? 1 : -1;
556 const int length = std::max(dx, dy);
557
558 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
559 return;
560 }
561 if (dx + dx > width_ * height_) {
562 return;
563 }
564
565 int error = dx - dy;
566 for (int i = 0; i < length; ++i) {
567 DrawBlock(x1, y1 * 2, true, style);
568 if (2 * error >= -dy) {
569 error -= dy;
570 x1 += sx;
571 }
572 if (2 * error <= dx) {
573 error += dx;
574 y1 += sy;
575 }
576 }
577 DrawBlock(x2, y2 * 2, true, style);
578}
579
580/// @brief Draw a circle made of block characters.
581/// @param x the x coordinate of the center of the circle.
582/// @param y the y coordinate of the center of the circle.
583/// @param radius the radius of the circle.
584void Canvas::DrawBlockCircle(int x, int y, int radius) {
585 DrawBlockCircle(x, y, radius, nostyle);
586}
587
588/// @brief Draw a circle made of block characters.
589/// @param x the x coordinate of the center of the circle.
590/// @param y the y coordinate of the center of the circle.
591/// @param radius the radius of the circle.
592/// @param color the color of the circle.
593void Canvas::DrawBlockCircle(int x, int y, int radius, const Color& color) {
594 DrawBlockCircle(x, y, radius,
595 [color](Pixel& p) { p.foreground_color = color; });
596}
597
598/// @brief Draw a circle made of block characters.
599/// @param x the x coordinate of the center of the circle.
600/// @param y the y coordinate of the center of the circle.
601/// @param radius the radius of the circle.
602/// @param style the style of the circle.
603void Canvas::DrawBlockCircle(int x, int y, int radius, const Stylizer& style) {
604 DrawBlockEllipse(x, y, radius, radius, style);
605}
606
607/// @brief Draw a filled circle made of block characters.
608/// @param x the x coordinate of the center of the circle.
609/// @param y the y coordinate of the center of the circle.
610/// @param radius the radius of the circle.
611void Canvas::DrawBlockCircleFilled(int x, int y, int radius) {
612 DrawBlockCircleFilled(x, y, radius, nostyle);
613}
614
615/// @brief Draw a filled circle made of block characters.
616/// @param x the x coordinate of the center of the circle.
617/// @param y the y coordinate of the center of the circle.
618/// @param radius the radius of the circle.
619/// @param color the color of the circle.
621 int y,
622 int radius,
623 const Color& color) {
624 DrawBlockCircleFilled(x, y, radius,
625 [color](Pixel& p) { p.foreground_color = color; });
626}
627
628/// @brief Draw a filled circle made of block characters.
629/// @param x the x coordinate of the center of the circle.
630/// @param y the y coordinate of the center of the circle.
631/// @param radius the radius of the circle.
632/// @param style the style of the circle.
634 int y,
635 int radius,
636 const Stylizer& s) {
637 DrawBlockEllipseFilled(x, y, radius, radius, s);
638}
639
640/// @brief Draw an ellipse made of block characters.
641/// @param x the x coordinate of the center of the ellipse.
642/// @param y the y coordinate of the center of the ellipse.
643/// @param r1 the radius of the ellipse along the x axis.
644/// @param r2 the radius of the ellipse along the y axis.
645void Canvas::DrawBlockEllipse(int x, int y, int r1, int r2) {
646 DrawBlockEllipse(x, y, r1, r2, nostyle);
647}
648
649/// @brief Draw an ellipse made of block characters.
650/// @param x the x coordinate of the center of the ellipse.
651/// @param y the y coordinate of the center of the ellipse.
652/// @param r1 the radius of the ellipse along the x axis.
653/// @param r2 the radius of the ellipse along the y axis.
654/// @param color the color of the ellipse.
656 int y,
657 int r1,
658 int r2,
659 const Color& color) {
660 DrawBlockEllipse(x, y, r1, r2,
661 [color](Pixel& p) { p.foreground_color = color; });
662}
663
664/// @brief Draw an ellipse made of block characters.
665/// @param x the x coordinate of the center of the ellipse.
666/// @param y the y coordinate of the center of the ellipse.
667/// @param r1 the radius of the ellipse along the x axis.
668/// @param r2 the radius of the ellipse along the y axis.
669/// @param style the style of the ellipse.
671 int y1,
672 int r1,
673 int r2,
674 const Stylizer& s) {
675 y1 /= 2;
676 r2 /= 2;
677 int x = -r1;
678 int y = 0;
679 int e2 = r2;
680 int dx = (1 + 2 * x) * e2 * e2;
681 int dy = x * x;
682 int err = dx + dy;
683
684 do {
685 DrawBlock(x1 - x, 2 * (y1 + y), true, s);
686 DrawBlock(x1 + x, 2 * (y1 + y), true, s);
687 DrawBlock(x1 + x, 2 * (y1 - y), true, s);
688 DrawBlock(x1 - x, 2 * (y1 - y), true, s);
689 e2 = 2 * err;
690 if (e2 >= dx) {
691 x++;
692 err += dx += 2 * r2 * r2;
693 }
694 if (e2 <= dy) {
695 y++;
696 err += dy += 2 * r1 * r1;
697 }
698 } while (x <= 0);
699
700 while (y++ < r2) {
701 DrawBlock(x1, 2 * (y1 + y), true, s);
702 DrawBlock(x1, 2 * (y1 - y), true, s);
703 }
704}
705
706/// @brief Draw a filled ellipse made of block characters.
707/// @param x the x coordinate of the center of the ellipse.
708/// @param y the y coordinate of the center of the ellipse.
709/// @param r1 the radius of the ellipse along the x axis.
710/// @param r2 the radius of the ellipse along the y axis.
711void Canvas::DrawBlockEllipseFilled(int x, int y, int r1, int r2) {
712 DrawBlockEllipseFilled(x, y, r1, r2, nostyle);
713}
714
715/// @brief Draw a filled ellipse made of block characters.
716/// @param x the x coordinate of the center of the ellipse.
717/// @param y the y coordinate of the center of the ellipse.
718/// @param r1 the radius of the ellipse along the x axis.
719/// @param r2 the radius of the ellipse along the y axis.
720/// @param color the color of the ellipse.
722 int y,
723 int r1,
724 int r2,
725 const Color& color) {
726 DrawBlockEllipseFilled(x, y, r1, r2,
727 [color](Pixel& p) { p.foreground_color = color; });
728}
729
730/// @brief Draw a filled ellipse made of block characters.
731/// @param x the x coordinate of the center of the ellipse.
732/// @param y the y coordinate of the center of the ellipse.
733/// @param r1 the radius of the ellipse along the x axis.
734/// @param r2 the radius of the ellipse along the y axis.
735/// @param style the style of the ellipse.
737 int y1,
738 int r1,
739 int r2,
740 const Stylizer& s) {
741 y1 /= 2;
742 r2 /= 2;
743 int x = -r1;
744 int y = 0;
745 int e2 = r2;
746 int dx = (1 + 2 * x) * e2 * e2;
747 int dy = x * x;
748 int err = dx + dy;
749
750 do {
751 for (int xx = x1 + x; xx <= x1 - x; ++xx) {
752 DrawBlock(xx, 2 * (y1 + y), true, s);
753 DrawBlock(xx, 2 * (y1 - y), true, s);
754 }
755 e2 = 2 * err;
756 if (e2 >= dx) {
757 x++;
758 err += dx += 2 * r2 * r2;
759 }
760 if (e2 <= dy) {
761 y++;
762 err += dy += 2 * r1 * r1;
763 }
764 } while (x <= 0);
765
766 while (y++ < r2) {
767 for (int yy = y1 + y; yy <= y1 - y; ++yy) {
768 DrawBlock(x1, 2 * yy, true, s);
769 }
770 }
771}
772
773/// @brief Draw a piece of text.
774/// @param x the x coordinate of the text.
775/// @param y the y coordinate of the text.
776/// @param value the text to draw.
777void Canvas::DrawText(int x, int y, const std::string& value) {
778 DrawText(x, y, value, nostyle);
779}
780
781/// @brief Draw a piece of text.
782/// @param x the x coordinate of the text.
783/// @param y the y coordinate of the text.
784/// @param value the text to draw.
785/// @param color the color of the text.
787 int y,
788 const std::string& value,
789 const Color& color) {
790 DrawText(x, y, value, [color](Pixel& p) { p.foreground_color = color; });
791}
792
793/// @brief Draw a piece of text.
794/// @param x the x coordinate of the text.
795/// @param y the y coordinate of the text.
796/// @param value the text to draw.
797/// @param style the style of the text.
799 int y,
800 const std::string& value,
801 const Stylizer& style) {
802 for (const auto& it : Utf8ToGlyphs(value)) {
803 if (!IsIn(x, y)) {
804 continue;
805 }
806 Cell& cell = storage_[XY{x / 2, y / 4}];
807 cell.type = CellType::kText;
808 cell.content.character = it;
809 style(cell.content);
810 x += 2;
811 }
812}
813
814/// @brief Modify a pixel at a given location.
815/// @param style a function that modifies the pixel.
816void Canvas::Style(int x, int y, const Stylizer& style) {
817 if (IsIn(x, y)) {
818 style(storage_[XY{x / 2, y / 4}].content);
819 }
820}
821
822namespace {
823
824class CanvasNodeBase : public Node {
825 public:
826 CanvasNodeBase() = default;
827
828 void Render(Screen& screen) override {
829 const Canvas& c = canvas();
830 int y_max = std::min(c.height() / 4, box_.y_max - box_.y_min + 1);
831 int x_max = std::min(c.width() / 2, box_.x_max - box_.x_min + 1);
832 for (int y = 0; y < y_max; ++y) {
833 for (int x = 0; x < x_max; ++x) {
834 screen.PixelAt(box_.x_min + x, box_.y_min + y) = c.GetPixel(x, y);
835 }
836 }
837 }
838
839 virtual const Canvas& canvas() = 0;
840};
841
842} // namespace
843
844/// @brief Produce an element from a Canvas, or a reference to a Canvas.
846 class Impl : public CanvasNodeBase {
847 public:
848 explicit Impl(ConstRef<Canvas> canvas) : canvas_(std::move(canvas)) {
849 requirement_.min_x = (canvas_->width() + 1) / 2;
850 requirement_.min_y = (canvas_->height() + 3) / 4;
851 }
852 const Canvas& canvas() final { return *canvas_; }
853 ConstRef<Canvas> canvas_;
854 };
855 return std::make_shared<Impl>(std::move(canvas));
856}
857
858/// @brief Produce an element drawing a canvas of requested size.
859/// @param width the width of the canvas.
860/// @param height the height of the canvas.
861/// @param fn a function drawing the canvas.
862Element canvas(int width, int height, std::function<void(Canvas&)> fn) {
863 class Impl : public CanvasNodeBase {
864 public:
865 Impl(int width, int height, std::function<void(Canvas&)> fn)
866 : width_(width), height_(height), fn_(std::move(fn)) {}
867
868 void ComputeRequirement() final {
869 requirement_.min_x = (width_ + 1) / 2;
870 requirement_.min_y = (height_ + 3) / 4;
871 }
872
873 void Render(Screen& screen) final {
874 int width = (box_.y_max - box_.y_min + 1) * 2;
875 int height = (box_.x_max - box_.x_min + 1) * 4;
876 canvas_ = Canvas(width, height);
877 fn_(canvas_);
878 CanvasNodeBase::Render(screen);
879 }
880
881 const Canvas& canvas() final { return canvas_; }
882 Canvas canvas_;
883 int width_;
884 int height_;
885 std::function<void(Canvas&)> fn_;
886 };
887 return std::make_shared<Impl>(width, height, std::move(fn));
888}
889
890/// @brief Produce an element drawing a canvas.
891/// @param fn a function drawing the canvas.
892Element canvas(std::function<void(Canvas&)> fn) {
893 const int default_dim = 12;
894 return canvas(default_dim, default_dim, std::move(fn));
895}
896
897} // namespace ftxui
898
899// Copyright 2021 Arthur Sonzogni. All rights reserved.
900// Use of this source code is governed by the MIT license that can be found in
901// the LICENSE file.
A class representing terminal colors.
Definition color.hpp:17
An adapter. Own or reference an immutable object.
Definition ref.hpp:11
A rectangular grid of Pixel.
Definition screen.hpp:53
Pixel & PixelAt(int x, int y)
Access a Pixel at a given position.
Definition screen.cpp:447
std::shared_ptr< Node > Element
Definition elements.hpp:18
std::vector< std::string > Utf8ToGlyphs(const std::string &input)
Definition string.cpp:291
Element canvas(ConstRef< Canvas >)
Produce an element from a Canvas, or a reference to a Canvas.
Definition canvas.cpp:845
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
Definition node.cpp:43
Decorator color(Color)
Decorate using a foreground color.
Definition color.cpp:86
void DrawBlockLine(int x1, int y1, int x2, int y2)
Draw a line made of block characters.
Definition canvas.cpp:523
void DrawPointEllipseFilled(int x, int y, int r1, int r2)
Draw a filled ellipse made of braille dots.
Definition canvas.cpp:366
void DrawPointLine(int x1, int y1, int x2, int y2)
Draw a line made of braille dots.
Definition canvas.cpp:183
void DrawText(int x, int y, const std::string &value)
Draw a piece of text.
Definition canvas.cpp:777
Canvas()=default
std::function< void(Pixel &)> Stylizer
Definition canvas.hpp:30
void DrawBlockOn(int x, int y)
Draw a block.
Definition canvas.cpp:460
void DrawPointCircleFilled(int x, int y, int radius)
Draw a filled circle made of braille dots.
Definition canvas.cpp:268
void DrawPointOn(int x, int y)
Draw a braille dot.
Definition canvas.cpp:129
void DrawPointOff(int x, int y)
Erase a braille dot.
Definition canvas.cpp:146
Pixel GetPixel(int x, int y) const
Get the content of a cell.
Definition canvas.cpp:90
void DrawBlockEllipseFilled(int x1, int y1, int r1, int r2)
Draw a filled ellipse made of block characters.
Definition canvas.cpp:711
void DrawPointEllipse(int x, int y, int r1, int r2)
Draw an ellipse made of braille dots.
Definition canvas.cpp:302
void DrawPoint(int x, int y, bool value)
Draw a braille dot.
Definition canvas.cpp:99
void DrawBlockEllipse(int x1, int y1, int r1, int r2)
Draw an ellipse made of block characters.
Definition canvas.cpp:645
void DrawBlockToggle(int x, int y)
Toggle a block. If it is filled, it will be erased. If it is empty, it will be filled.
Definition canvas.cpp:501
void DrawBlockCircle(int x1, int y1, int radius)
Draw a circle made of block characters.
Definition canvas.cpp:584
void DrawBlockCircleFilled(int x1, int y1, int radius)
Draw a filled circle made of block characters.
Definition canvas.cpp:611
void DrawPointCircle(int x, int y, int radius)
Draw a circle made of braille dots.
Definition canvas.cpp:241
int height() const
Definition canvas.hpp:27
void DrawBlockOff(int x, int y)
Erase a block.
Definition canvas.cpp:480
int width() const
Definition canvas.hpp:26
void DrawBlock(int x, int y, bool value)
Draw a block.
Definition canvas.cpp:430
void Style(int x, int y, const Stylizer &style)
Modify a pixel at a given location.
Definition canvas.cpp:816
void DrawPointToggle(int x, int y)
Toggle a braille dot. A filled one will be erased, and the other will be drawn.
Definition canvas.cpp:164
A unicode character and its associated style.
Definition screen.hpp:16
Color foreground_color
Definition screen.hpp:25
std::string character
Definition screen.hpp:21