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