FTXUI 7.0.0
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_(std::max(0, width)),
98 height_(std::max(0, height)),
99 storage_(static_cast<size_t>(width_) * static_cast<size_t>(height_) /
100 8 /* NOLINT */) {}
101
102/// @brief Get the content of a cell.
103/// @param x the x coordinate of the cell.
104/// @param y the y coordinate of the cell.
105Cell Canvas::GetCell(int x, int y) const {
106 auto it = storage_.find(XY{x, y});
107 return (it == storage_.end()) ? Cell() : it->second.content;
108}
109
110/// @brief Draw a braille dot.
111/// @param x the x coordinate of the dot.
112/// @param y the y coordinate of the dot.
113/// @param value whether the dot is filled or not.
114void Canvas::DrawPoint(int x, int y, bool value) {
115 DrawPoint(x, y, value, [](Cell& /*pixel*/) {});
116}
117
118/// @brief Draw a braille dot.
119/// @param x the x coordinate of the dot.
120/// @param y the y coordinate of the dot.
121/// @param value whether the dot is filled or not.
122/// @param color the color of the dot.
123void Canvas::DrawPoint(int x, int y, bool value, const Color& color) {
124 DrawPoint(x, y, value, [color](Cell& p) { p.foreground_color = color; });
125}
126
127/// @brief Draw a braille dot.
128/// @param x the x coordinate of the dot.
129/// @param y the y coordinate of the dot.
130/// @param value whether the dot is filled or not.
131/// @param style the style of the cell.
132void Canvas::DrawPoint(int x, int y, bool value, const Stylizer& style) {
133 Style(x, y, style);
134 if (value) {
135 DrawPointOn(x, y);
136 } else {
137 DrawPointOff(x, y);
138 }
139}
140
141/// @brief Draw a braille dot.
142/// @param x the x coordinate of the dot.
143/// @param y the y coordinate of the dot.
144void Canvas::DrawPointOn(int x, int y) {
145 if (!IsIn(x, y)) {
146 return;
147 }
148 CanvasCell& cell = storage_[XY{x / 2, y / 4}];
149 if (cell.type != CellType::kBraille) {
150 cell.content.character = "⠀"; // 3 bytes.
151 cell.type = CellType::kBraille;
152 }
153
154 cell.content.character[1] |= g_map_braille[x % 2][y % 4][0]; // NOLINT
155 cell.content.character[2] |= g_map_braille[x % 2][y % 4][1]; // NOLINT
156}
157
158/// @brief Erase a braille dot.
159/// @param x the x coordinate of the dot.
160/// @param y the y coordinate of the dot.
161void Canvas::DrawPointOff(int x, int y) {
162 if (!IsIn(x, y)) {
163 return;
164 }
165 CanvasCell& cell = storage_[XY{x / 2, y / 4}];
166 if (cell.type != CellType::kBraille) {
167 cell.content.character = "⠀"; // 3 byt
168 cell.type = CellType::kBraille;
169 }
170
171 cell.content.character[1] &= ~(g_map_braille[x % 2][y % 4][0]); // NOLINT
172 cell.content.character[2] &= ~(g_map_braille[x % 2][y % 4][1]); // NOLINT
173}
174
175/// @brief Toggle a braille dot. A filled one will be erased, and the other will
176/// be drawn.
177/// @param x the x coordinate of the dot.
178/// @param y the y coordinate of the dot.
179void Canvas::DrawPointToggle(int x, int y) {
180 if (!IsIn(x, y)) {
181 return;
182 }
183 CanvasCell& cell = storage_[XY{x / 2, y / 4}];
184 if (cell.type != CellType::kBraille) {
185 cell.content.character = "⠀"; // 3 byt
186 cell.type = CellType::kBraille;
187 }
188
189 cell.content.character[1] ^= g_map_braille[x % 2][y % 4][0]; // NOLINT
190 cell.content.character[2] ^= g_map_braille[x % 2][y % 4][1]; // NOLINT
191}
192
193/// @brief Draw a line made of braille dots.
194/// @param x1 the x coordinate of the first dot.
195/// @param y1 the y coordinate of the first dot.
196/// @param x2 the x coordinate of the second dot.
197/// @param y2 the y coordinate of the second dot.
198void Canvas::DrawPointLine(int x1, int y1, int x2, int y2) {
199 DrawPointLine(x1, y1, x2, y2, [](Cell& /*pixel*/) {});
200}
201
202/// @brief Draw a line made of braille dots.
203/// @param x1 the x coordinate of the first dot.
204/// @param y1 the y coordinate of the first dot.
205/// @param x2 the x coordinate of the second dot.
206/// @param y2 the y coordinate of the second dot.
207/// @param color the color of the line.
208void Canvas::DrawPointLine(int x1, int y1, int x2, int y2, const Color& color) {
209 DrawPointLine(x1, y1, x2, y2,
210 [color](Cell& p) { p.foreground_color = color; });
211}
212
213/// @brief Draw a line made of braille dots.
214/// @param x1 the x coordinate of the first dot.
215/// @param y1 the y coordinate of the first dot.o
216/// @param x2 the x coordinate of the second dot.
217/// @param y2 the y coordinate of the second dot.
218/// @param style the style of the line.
219void Canvas::DrawPointLine(int x1,
220 int y1,
221 int x2,
222 int y2,
223 const Stylizer& style) {
224 const int dx = std::abs(x2 - x1);
225 const int dy = std::abs(y2 - y1);
226 const int sx = x1 < x2 ? 1 : -1;
227 const int sy = y1 < y2 ? 1 : -1;
228 const int length = std::max(dx, dy);
229
230 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
231 return;
232 }
233 if (dx + dx > width_ * height_) {
234 return;
235 }
236
237 int error = dx - dy;
238 for (int i = 0; i < length; ++i) {
239 DrawPoint(x1, y1, true, style);
240 if (2 * error >= -dy) {
241 error -= dy;
242 x1 += sx;
243 }
244 if (2 * error <= dx) {
245 error += dx;
246 y1 += sy;
247 }
248 }
249 DrawPoint(x2, y2, true, style);
250}
251
252/// @brief Draw a circle made of braille dots.
253/// @param x the x coordinate of the center of the circle.
254/// @param y the y coordinate of the center of the circle.
255/// @param radius the radius of the circle.
256void Canvas::DrawPointCircle(int x, int y, int radius) {
257 DrawPointCircle(x, y, radius, [](Cell& /*pixel*/) {});
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 color the color of the circle.
265void Canvas::DrawPointCircle(int x, int y, int radius, const Color& color) {
266 DrawPointCircle(x, y, radius,
267 [color](Cell& p) { p.foreground_color = color; });
268}
269
270/// @brief Draw a circle made of braille dots.
271/// @param x the x coordinate of the center of the circle.
272/// @param y the y coordinate of the center of the circle.
273/// @param radius the radius of the circle.
274/// @param style the style of the circle.
275void Canvas::DrawPointCircle(int x, int y, int radius, const Stylizer& style) {
276 DrawPointEllipse(x, y, radius, radius, style);
277}
278
279/// @brief Draw a filled circle made of braille dots.
280/// @param x the x coordinate of the center of the circle.
281/// @param y the y coordinate of the center of the circle.
282/// @param radius the radius of the circle.
283void Canvas::DrawPointCircleFilled(int x, int y, int radius) {
284 DrawPointCircleFilled(x, y, radius, [](Cell& /*pixel*/) {});
285}
286
287/// @brief Draw a filled circle made of braille dots.
288/// @param x the x coordinate of the center of the circle.
289/// @param y the y coordinate of the center of the circle.
290/// @param radius the radius of the circle.
291/// @param color the color of the circle.
292void Canvas::DrawPointCircleFilled(int x,
293 int y,
294 int radius,
295 const Color& color) {
296 DrawPointCircleFilled(x, y, radius,
297 [color](Cell& p) { p.foreground_color = color; });
298}
299
300/// @brief Draw a filled circle made of braille dots.
301/// @param x the x coordinate of the center of the circle.
302/// @param y the y coordinate of the center of the circle.
303/// @param radius the radius of the circle.
304/// @param style the style of the circle.
305void Canvas::DrawPointCircleFilled(int x,
306 int y,
307 int radius,
308 const Stylizer& style) {
309 DrawPointEllipseFilled(x, y, radius, radius, style);
310}
311
312/// @brief Draw an ellipse made of braille dots.
313/// @param x the x coordinate of the center of the ellipse.
314/// @param y the y coordinate of the center of the ellipse.
315/// @param r1 the radius of the ellipse along the x axis.
316/// @param r2 the radius of the ellipse along the y axis.
317void Canvas::DrawPointEllipse(int x, int y, int r1, int r2) {
318 DrawPointEllipse(x, y, r1, r2, [](Cell& /*pixel*/) {});
319}
320
321/// @brief Draw an ellipse made of braille dots.
322/// @param x the x coordinate of the center of the ellipse.
323/// @param y the y coordinate of the center of the ellipse.
324/// @param r1 the radius of the ellipse along the x axis.
325/// @param r2 the radius of the ellipse along the y axis.
326/// @param color the color of the ellipse.
327void Canvas::DrawPointEllipse(int x,
328 int y,
329 int r1,
330 int r2,
331 const Color& color) {
332 DrawPointEllipse(x, y, r1, r2,
333 [color](Cell& p) { p.foreground_color = color; });
334}
335
336/// @brief Draw an ellipse made of braille dots.
337/// @param x1 the x coordinate of the center of the ellipse.
338/// @param y1 the y coordinate of the center of the ellipse.
339/// @param r1 the radius of the ellipse along the x axis.
340/// @param r2 the radius of the ellipse along the y axis.
341/// @param s the style of the ellipse.
342void Canvas::DrawPointEllipse(int x1,
343 int y1,
344 int r1,
345 int r2,
346 const Stylizer& s) {
347 int x = -r1;
348 int y = 0;
349 int e2 = r2;
350 int dx = (1 + 2 * x) * e2 * e2;
351 int dy = x * x;
352 int err = dx + dy;
353
354 do { // NOLINT
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 DrawPoint(x1 - x, y1 - y, true, s);
359 e2 = 2 * err;
360 if (e2 >= dx) {
361 x++;
362 err += dx += 2 * r2 * r2;
363 }
364 if (e2 <= dy) {
365 y++;
366 err += dy += 2 * r1 * r1;
367 }
368 } while (x <= 0);
369
370 while (y++ < r2) {
371 DrawPoint(x1, y1 + y, true, s);
372 DrawPoint(x1, y1 - y, true, s);
373 }
374}
375
376/// @brief Draw a filled ellipse made of braille dots.
377/// @param x1 the x coordinate of the center of the ellipse.
378/// @param y1 the y coordinate of the center of the ellipse.
379/// @param r1 the radius of the ellipse along the x axis.
380/// @param r2 the radius of the ellipse along the y axis.
381void Canvas::DrawPointEllipseFilled(int x1, int y1, int r1, int r2) {
382 DrawPointEllipseFilled(x1, y1, r1, r2, [](Cell& /*pixel*/) {});
383}
384
385/// @brief Draw a filled ellipse made of braille dots.
386/// @param x1 the x coordinate of the center of the ellipse.
387/// @param y1 the y coordinate of the center of the ellipse.
388/// @param r1 the radius of the ellipse along the x axis.
389/// @param r2 the radius of the ellipse along the y axis.
390/// @param color the color of the ellipse.
391void Canvas::DrawPointEllipseFilled(int x1,
392 int y1,
393 int r1,
394 int r2,
395 const Color& color) {
396 DrawPointEllipseFilled(x1, y1, r1, r2,
397 [color](Cell& p) { p.foreground_color = color; });
398}
399
400/// @brief Draw a filled ellipse made of braille dots.
401/// @param x1 the x coordinate of the center of the ellipse.
402/// @param y1 the y coordinate of the center of the ellipse.
403/// @param r1 the radius of the ellipse along the x axis.
404/// @param r2 the radius of the ellipse along the y axis.
405/// @param s the style of the ellipse.
406void Canvas::DrawPointEllipseFilled(int x1,
407 int y1,
408 int r1,
409 int r2,
410 const Stylizer& s) {
411 int x = -r1;
412 int y = 0;
413 int e2 = r2;
414 int dx = (1 + 2 * x) * e2 * e2;
415 int dy = x * x;
416 int err = dx + dy;
417
418 do { // NOLINT
419 for (int xx = x1 + x; xx <= x1 - x; ++xx) {
420 DrawPoint(xx, y1 + y, true, s);
421 DrawPoint(xx, y1 - y, true, s);
422 }
423 e2 = 2 * err;
424 if (e2 >= dx) {
425 x++;
426 err += dx += 2 * r2 * r2;
427 }
428 if (e2 <= dy) {
429 y++;
430 err += dy += 2 * r1 * r1;
431 }
432 } while (x <= 0);
433
434 while (y++ < r2) {
435 for (int yy = y1 - y; yy <= y1 + y; ++yy) {
436 DrawPoint(x1, yy, true, s);
437 }
438 }
439}
440
441/// @brief Draw a block.
442/// @param x the x coordinate of the block.
443/// @param y the y coordinate of the block.
444/// @param value whether the block is filled or not.
445void Canvas::DrawBlock(int x, int y, bool value) {
446 DrawBlock(x, y, value, [](Cell& /*pixel*/) {});
447}
448
449/// @brief Draw a block.
450/// @param x the x coordinate of the block.
451/// @param y the y coordinate of the block.
452/// @param value whether the block is filled or not.
453/// @param color the color of the block.
454void Canvas::DrawBlock(int x, int y, bool value, const Color& color) {
455 DrawBlock(x, y, value, [color](Cell& p) { p.foreground_color = color; });
456}
457
458/// @brief Draw a block.
459/// @param x the x coordinate of the block.
460/// @param y the y coordinate of the block.
461/// @param value whether the block is filled or not.
462/// @param style the style of the block.
463void Canvas::DrawBlock(int x, int y, bool value, const Stylizer& style) {
464 Style(x, y, style);
465 if (value) {
466 DrawBlockOn(x, y);
467 } else {
468 DrawBlockOff(x, y);
469 }
470}
471
472/// @brief Draw a block.
473/// @param x the x coordinate of the block.
474/// @param y the y coordinate of the block.
475void Canvas::DrawBlockOn(int x, int y) {
476 if (!IsIn(x, y)) {
477 return;
478 }
479 y /= 2;
480 CanvasCell& cell = storage_[XY{x / 2, y / 2}];
481 if (cell.type != CellType::kBlock) {
482 cell.content.character = " ";
483 cell.type = CellType::kBlock;
484 }
485
486 const uint8_t bit = (x % 2) * 2 + y % 2;
487 uint8_t value = g_map_block_inversed.at(cell.content.character);
488 value |= 1U << bit;
489 cell.content.character = g_map_block[value];
490}
491
492/// @brief Erase a block.
493/// @param x the x coordinate of the block.
494/// @param y the y coordinate of the block.
495void Canvas::DrawBlockOff(int x, int y) {
496 if (!IsIn(x, y)) {
497 return;
498 }
499 CanvasCell& cell = storage_[XY{x / 2, y / 4}];
500 if (cell.type != CellType::kBlock) {
501 cell.content.character = " ";
502 cell.type = CellType::kBlock;
503 }
504 y /= 2;
505
506 const uint8_t bit = (y % 2) * 2 + x % 2;
507 uint8_t value = g_map_block_inversed.at(cell.content.character);
508 value &= ~(1U << bit);
509 cell.content.character = g_map_block[value];
510}
511
512/// @brief Toggle a block. If it is filled, it will be erased. If it is empty,
513/// it will be filled.
514/// @param x the x coordinate of the block.
515/// @param y the y coordinate of the block.
516void Canvas::DrawBlockToggle(int x, int y) {
517 if (!IsIn(x, y)) {
518 return;
519 }
520 CanvasCell& cell = storage_[XY{x / 2, y / 4}];
521 if (cell.type != CellType::kBlock) {
522 cell.content.character = " ";
523 cell.type = CellType::kBlock;
524 }
525 y /= 2;
526
527 const uint8_t bit = (y % 2) * 2 + x % 2;
528 uint8_t value = g_map_block_inversed.at(cell.content.character);
529 value ^= 1U << bit;
530 cell.content.character = g_map_block[value];
531}
532
533/// @brief Draw a line made of block characters.
534/// @param x1 the x coordinate of the first point of the line.
535/// @param y1 the y coordinate of the first point of the line.
536/// @param x2 the x coordinate of the second point of the line.
537/// @param y2 the y coordinate of the second point of the line.
538void Canvas::DrawBlockLine(int x1, int y1, int x2, int y2) {
539 DrawBlockLine(x1, y1, x2, y2, [](Cell& /*pixel*/) {});
540}
541
542/// @brief Draw a line made of block characters.
543/// @param x1 the x coordinate of the first point of the line.
544/// @param y1 the y coordinate of the first point of the line.
545/// @param x2 the x coordinate of the second point of the line.
546/// @param y2 the y coordinate of the second point of the line.
547/// @param color the color of the line.
548void Canvas::DrawBlockLine(int x1, int y1, int x2, int y2, const Color& color) {
549 DrawBlockLine(x1, y1, x2, y2,
550 [color](Cell& p) { p.foreground_color = color; });
551}
552
553/// @brief Draw a line made of block characters.
554/// @param x1 the x coordinate of the first point of the line.
555/// @param y1 the y coordinate of the first point of the line.
556/// @param x2 the x coordinate of the second point of the line.
557/// @param y2 the y coordinate of the second point of the line.
558/// @param style the style of the line.
559void Canvas::DrawBlockLine(int x1,
560 int y1,
561 int x2,
562 int y2,
563 const Stylizer& style) {
564 y1 /= 2;
565 y2 /= 2;
566
567 const int dx = std::abs(x2 - x1);
568 const int dy = std::abs(y2 - y1);
569 const int sx = x1 < x2 ? 1 : -1;
570 const int sy = y1 < y2 ? 1 : -1;
571 const int length = std::max(dx, dy);
572
573 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
574 return;
575 }
576 if (dx + dx > width_ * height_) {
577 return;
578 }
579
580 int error = dx - dy;
581 for (int i = 0; i < length; ++i) {
582 DrawBlock(x1, y1 * 2, true, style);
583 if (2 * error >= -dy) {
584 error -= dy;
585 x1 += sx;
586 }
587 if (2 * error <= dx) {
588 error += dx;
589 y1 += sy;
590 }
591 }
592 DrawBlock(x2, y2 * 2, true, style);
593}
594
595/// @brief Draw a circle made of block characters.
596/// @param x the x coordinate of the center of the circle.
597/// @param y the y coordinate of the center of the circle.
598/// @param radius the radius of the circle.
599void Canvas::DrawBlockCircle(int x, int y, int radius) {
600 DrawBlockCircle(x, y, radius, nostyle);
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 color the color of the circle.
608void Canvas::DrawBlockCircle(int x, int y, int radius, const Color& color) {
609 DrawBlockCircle(x, y, radius,
610 [color](Cell& p) { p.foreground_color = color; });
611}
612
613/// @brief Draw a circle made of block characters.
614/// @param x the x coordinate of the center of the circle.
615/// @param y the y coordinate of the center of the circle.
616/// @param radius the radius of the circle.
617/// @param style the style of the circle.
618void Canvas::DrawBlockCircle(int x, int y, int radius, const Stylizer& style) {
619 DrawBlockEllipse(x, y, radius, radius, style);
620}
621
622/// @brief Draw a filled circle made of block characters.
623/// @param x the x coordinate of the center of the circle.
624/// @param y the y coordinate of the center of the circle.
625/// @param radius the radius of the circle.
626void Canvas::DrawBlockCircleFilled(int x, int y, int radius) {
627 DrawBlockCircleFilled(x, y, radius, nostyle);
628}
629
630/// @brief Draw a filled circle made of block characters.
631/// @param x the x coordinate of the center of the circle.
632/// @param y the y coordinate of the center of the circle.
633/// @param radius the radius of the circle.
634/// @param color the color of the circle.
635void Canvas::DrawBlockCircleFilled(int x,
636 int y,
637 int radius,
638 const Color& color) {
639 DrawBlockCircleFilled(x, y, radius,
640 [color](Cell& p) { p.foreground_color = color; });
641}
642
643/// @brief Draw a filled circle made of block characters.
644/// @param x the x coordinate of the center of the circle.
645/// @param y the y coordinate of the center of the circle.
646/// @param radius the radius of the circle.
647/// @param s the style of the circle.
648void Canvas::DrawBlockCircleFilled(int x,
649 int y,
650 int radius,
651 const Stylizer& s) {
652 DrawBlockEllipseFilled(x, y, radius, radius, s);
653}
654
655/// @brief Draw an ellipse made of block characters.
656/// @param x the x coordinate of the center of the ellipse.
657/// @param y the y coordinate of the center of the ellipse.
658/// @param r1 the radius of the ellipse along the x axis.
659/// @param r2 the radius of the ellipse along the y axis.
660void Canvas::DrawBlockEllipse(int x, int y, int r1, int r2) {
661 DrawBlockEllipse(x, y, r1, r2, nostyle);
662}
663
664/// @brief Draw an ellipse made of block characters.
665/// @param x the x coordinate of the center of the ellipse.
666/// @param y the y coordinate of the center of the ellipse.
667/// @param r1 the radius of the ellipse along the x axis.
668/// @param r2 the radius of the ellipse along the y axis.
669/// @param color the color of the ellipse.
670void Canvas::DrawBlockEllipse(int x,
671 int y,
672 int r1,
673 int r2,
674 const Color& color) {
675 DrawBlockEllipse(x, y, r1, r2,
676 [color](Cell& p) { p.foreground_color = color; });
677}
678
679/// @brief Draw an ellipse made of block characters.
680/// @param x1 the x coordinate of the center of the ellipse.
681/// @param y1 the y coordinate of the center of the ellipse.
682/// @param r1 the radius of the ellipse along the x axis.
683/// @param r2 the radius of the ellipse along the y axis.
684/// @param s the style of the ellipse.
685void Canvas::DrawBlockEllipse(int x1,
686 int y1,
687 int r1,
688 int r2,
689 const Stylizer& s) {
690 y1 /= 2;
691 r2 /= 2;
692 int x = -r1;
693 int y = 0;
694 int e2 = r2;
695 int dx = (1 + 2 * x) * e2 * e2;
696 int dy = x * x;
697 int err = dx + dy;
698
699 do { // NOLINT
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 DrawBlock(x1 - x, 2 * (y1 - y), true, s);
704 e2 = 2 * err;
705 if (e2 >= dx) {
706 x++;
707 err += dx += 2 * r2 * r2;
708 }
709 if (e2 <= dy) {
710 y++;
711 err += dy += 2 * r1 * r1;
712 }
713 } while (x <= 0);
714
715 while (y++ < r2) {
716 DrawBlock(x1, 2 * (y1 + y), true, s);
717 DrawBlock(x1, 2 * (y1 - y), true, s);
718 }
719}
720
721/// @brief Draw a filled ellipse made of block characters.
722/// @param x the x coordinate of the center of the ellipse.
723/// @param y the y coordinate of the center of the ellipse.
724/// @param r1 the radius of the ellipse along the x axis.
725/// @param r2 the radius of the ellipse along the y axis.
726void Canvas::DrawBlockEllipseFilled(int x, int y, int r1, int r2) {
727 DrawBlockEllipseFilled(x, y, r1, r2, nostyle);
728}
729
730/// @brief Draw a filled ellipse made of block characters.
731/// @param x the x coordinate of the center of the ellipse.
732/// @param y the y coordinate of the center of the ellipse.
733/// @param r1 the radius of the ellipse along the x axis.
734/// @param r2 the radius of the ellipse along the y axis.
735/// @param color the color of the ellipse.
736void Canvas::DrawBlockEllipseFilled(int x,
737 int y,
738 int r1,
739 int r2,
740 const Color& color) {
741 DrawBlockEllipseFilled(x, y, r1, r2,
742 [color](Cell& p) { p.foreground_color = color; });
743}
744
745/// @brief Draw a filled ellipse made of block characters.
746/// @param x1 the x coordinate of the center of the ellipse.
747/// @param y1 the y coordinate of the center of the ellipse.
748/// @param r1 the radius of the ellipse along the x axis.
749/// @param r2 the radius of the ellipse along the y axis.
750/// @param s the style of the ellipse.
751void Canvas::DrawBlockEllipseFilled(int x1,
752 int y1,
753 int r1,
754 int r2,
755 const Stylizer& s) {
756 y1 /= 2;
757 r2 /= 2;
758 int x = -r1;
759 int y = 0;
760 int e2 = r2;
761 int dx = (1 + 2 * x) * e2 * e2;
762 int dy = x * x;
763 int err = dx + dy;
764
765 do { // NOLINT
766 for (int xx = x1 + x; xx <= x1 - x; ++xx) {
767 DrawBlock(xx, 2 * (y1 + y), true, s);
768 DrawBlock(xx, 2 * (y1 - y), true, s);
769 }
770 e2 = 2 * err;
771 if (e2 >= dx) {
772 x++;
773 err += dx += 2 * r2 * r2;
774 }
775 if (e2 <= dy) {
776 y++;
777 err += dy += 2 * r1 * r1;
778 }
779 } while (x <= 0);
780
781 while (y++ < r2) {
782 for (int yy = y1 + y; yy <= y1 - y; ++yy) {
783 DrawBlock(x1, 2 * yy, true, s);
784 }
785 }
786}
787
788/// @brief Draw a piece of text.
789/// @param x the x coordinate of the text.
790/// @param y the y coordinate of the text.
791/// @param value the text to draw.
792void Canvas::DrawText(int x, int y, std::string_view value) {
793 DrawText(x, y, value, nostyle);
794}
795
796/// @brief Draw a piece of text.
797/// @param x the x coordinate of the text.
798/// @param y the y coordinate of the text.
799/// @param value the text to draw.
800/// @param color the color of the text.
801void Canvas::DrawText(int x,
802 int y,
803 std::string_view value,
804 const Color& color) {
805 DrawText(x, y, value, [color](Cell& p) { p.foreground_color = color; });
806}
807
808/// @brief Draw a piece of text.
809/// @param x the x coordinate of the text.
810/// @param y the y coordinate of the text.
811/// @param value the text to draw.
812/// @param style the style of the text.
813void Canvas::DrawText(int x,
814 int y,
815 std::string_view value,
816 const Stylizer& style) {
817 for (const auto& it : Utf8ToGlyphs(value)) {
818 if (!IsIn(x, y)) {
819 x += 2;
820 continue;
821 }
822 CanvasCell& cell = storage_[XY{x / 2, y / 4}];
823 cell.type = CellType::kCell;
824 cell.content.character = it;
825 style(cell.content);
826 x += 2;
827 }
828}
829
830/// @brief Directly draw a predefined pixel at the given coordinate
831/// @param x the x coordinate of the pixel.
832/// @param y the y coordinate of the pixel.
833/// @param p the pixel to draw.
834void Canvas::DrawCell(int x, int y, const Cell& p) {
835 CanvasCell& cell = storage_[XY{x / 2, y / 4}];
836 cell.type = CellType::kCell;
837 cell.content = p;
838}
839
840/// @brief Draw a predefined image, with top-left corner at the given coordinate
841/// You can supply negative coordinates to align the image however you like -
842/// only the 'visible' portion will be drawn
843/// @param x the x coordinate corresponding to the top-left corner of the image.
844/// @param y the y coordinate corresponding to the top-left corner of the image.
845/// @param image the image to draw.
846void Canvas::DrawSurface(int x, int y, const Surface& image) {
847 x /= 2;
848 y /= 4;
849 const int dx_begin = std::max(0, -x);
850 const int dy_begin = std::max(0, -y);
851 const int dx_end = std::min(image.dimx(), width_ - x);
852 const int dy_end = std::min(image.dimy(), height_ - y);
853
854 for (int dy = dy_begin; dy < dy_end; ++dy) {
855 for (int dx = dx_begin; dx < dx_end; ++dx) {
856 CanvasCell& cell = storage_[XY{
857 x + dx,
858 y + dy,
859 }];
860 cell.type = CellType::kCell;
861 cell.content = image.CellAt(dx, dy);
862 }
863 }
864}
865
866/// @brief Modify a pixel at a given location.
867/// @param x The x-coordinate of the pixel.
868/// @param y The y-coordinate of the pixel.
869/// @param style a function that modifies the pixel.
870void Canvas::Style(int x, int y, const Stylizer& style) {
871 if (IsIn(x, y)) {
872 style(storage_[XY{x / 2, y / 4}].content);
873 }
874}
875
876namespace {
877
878class CanvasNodeBase : public Node {
879 public:
880 CanvasNodeBase() = default;
881
882 void Render(Screen& screen) override {
883 const Canvas& c = canvas();
884 const int y_max = std::min(c.height() / 4, box_.y_max - box_.y_min + 1);
885 const int x_max = std::min(c.width() / 2, box_.x_max - box_.x_min + 1);
886 for (int y = 0; y < y_max; ++y) {
887 for (int x = 0; x < x_max; ++x) {
888 screen.CellAt(box_.x_min + x, box_.y_min + y) = c.GetCell(x, y);
889 }
890 }
891 }
892
893 virtual const Canvas& canvas() = 0;
894};
895
896} // namespace
897
898/// @brief Produce an element from a Canvas, or a reference to a Canvas.
899// NOLINTNEXTLINE
901#if defined(__GNUC__) && !defined(__clang__)
902#pragma GCC diagnostic push
903#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
904#endif
905 class Impl : public CanvasNodeBase {
906 public:
907 explicit Impl(ConstRef<Canvas> canvas) : canvas_(std::move(canvas)) {
908 requirement_.min_x = (canvas_->width() + 1) / 2;
909 requirement_.min_y = (canvas_->height() + 3) / 4;
910 }
911 const Canvas& canvas() final { return *canvas_; }
912 ConstRef<Canvas> canvas_;
913 };
914 return std::make_shared<Impl>(canvas);
915#if defined(__GNUC__) && !defined(__clang__)
916#pragma GCC diagnostic pop
917#endif
918}
919
920/// @brief Produce an element drawing a canvas of requested size.
921/// @param width the width of the canvas.
922/// @param height the height of the canvas.
923/// @param fn a function drawing the canvas.
924Element canvas(int width, int height, std::function<void(Canvas&)> fn) {
925 class Impl : public CanvasNodeBase {
926 public:
927 Impl(int width, int height, std::function<void(Canvas&)> fn)
928 : width_(width), height_(height), fn_(std::move(fn)) {}
929
930 void ComputeRequirement() final {
931 requirement_.min_x = (width_ + 1) / 2;
932 requirement_.min_y = (height_ + 3) / 4;
933 }
934
935 void Render(Screen& screen) final {
936 const int width = (box_.x_max - box_.x_min + 1) * 2;
937 const int height = (box_.y_max - box_.y_min + 1) * 4;
938 canvas_ = Canvas(width, height);
939 fn_(canvas_);
940 CanvasNodeBase::Render(screen);
941 }
942
943 const Canvas& canvas() final { return canvas_; }
944 Canvas canvas_;
945 int width_;
946 int height_;
947 std::function<void(Canvas&)> fn_;
948 };
949 return std::make_shared<Impl>(width, height, std::move(fn));
950}
951
952/// @brief Produce an element drawing a canvas.
953/// @param fn a function drawing the canvas.
954Element canvas(std::function<void(Canvas&)> fn) {
955 const int default_dim = 12;
956 return canvas(default_dim, default_dim, std::move(fn));
957}
958
959} // 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:25
int y
Definition elements.hpp:127
void Render(Screen &screen, Node *node, Selection &selection)
Definition node.cpp:105
int value
Definition elements.hpp:188
Element canvas(int width, int height, std::function< void(Canvas &)>)
Produce an element drawing a canvas of requested size.