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