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