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.
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
24namespace ftxui {
25
26namespace {
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
51uint8_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
67std::vector<std::string> g_map_block = {
68 " ", "▘", "▖", "▌", "▝", "▀", "▞", "▛",
69 "▗", "▚", "▄", "▙", "▐", "▜", "▟", "█",
70};
71
72// NOLINTNEXTLINE
73const 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
80constexpr 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.
87Canvas::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.
95Pixel 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.
104void 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.
113void 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.
122void 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.
134void 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.
151void 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.
169void 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.
188void 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.
198void 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.
246void 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.
255void 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.
265void 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.
273void 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.
307void 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.
371void 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.
435void 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.
444void 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.
453void 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.
465void 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.
485void 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.
506void 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.
528void 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.
538void 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.
589void 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.
598void 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.
608void 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.
616void 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.
650void 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.
716void 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.
782void 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.
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.
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.
822void 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
828namespace {
829
830class 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.
869Element 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_);
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.
899Element 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:110
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