60uint8_t g_map_braille[2][4][2] = {
62 {0b00000000, 0b00000001},
63 {0b00000000, 0b00000010},
64 {0b00000000, 0b00000100},
65 {0b00000001, 0b00000000},
68 {0b00000000, 0b00001000},
69 {0b00000000, 0b00010000},
70 {0b00000000, 0b00100000},
71 {0b00000010, 0b00000000},
76std::vector<std::string> g_map_block = {
77 " ",
"▘",
"▖",
"▌",
"▝",
"▀",
"▞",
"▛",
78 "▗",
"▚",
"▄",
"▙",
"▐",
"▜",
"▟",
"█",
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},
89constexpr auto nostyle = [](Cell& ) {};
96Canvas::Canvas(
int width,
int height)
99 storage_(width_ * height_ / 8 ) {}
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;
113void Canvas::DrawPoint(
int x,
int y,
bool value) {
114 DrawPoint(x, y, value, [](Cell& ) {});
122void Canvas::DrawPoint(
int x,
int y,
bool value,
const Color& color) {
123 DrawPoint(x, y, value, [color](Cell& p) { p.foreground_color = color; });
131void Canvas::DrawPoint(
int x,
int y,
bool value,
const Stylizer& style) {
143void Canvas::DrawPointOn(
int x,
int y) {
147 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
148 if (cell.type != CellType::kBraille) {
149 cell.content.character =
"⠀";
150 cell.type = CellType::kBraille;
153 cell.content.character[1] |= g_map_braille[x % 2][
y % 4][0];
154 cell.content.character[2] |= g_map_braille[x % 2][
y % 4][1];
160void Canvas::DrawPointOff(
int x,
int y) {
164 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
165 if (cell.type != CellType::kBraille) {
166 cell.content.character =
"⠀";
167 cell.type = CellType::kBraille;
170 cell.content.character[1] &= ~(g_map_braille[x % 2][
y % 4][0]);
171 cell.content.character[2] &= ~(g_map_braille[x % 2][
y % 4][1]);
178void Canvas::DrawPointToggle(
int x,
int y) {
182 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
183 if (cell.type != CellType::kBraille) {
184 cell.content.character =
"⠀";
185 cell.type = CellType::kBraille;
188 cell.content.character[1] ^= g_map_braille[x % 2][
y % 4][0];
189 cell.content.character[2] ^= g_map_braille[x % 2][
y % 4][1];
197void Canvas::DrawPointLine(
int x1,
int y1,
int x2,
int y2) {
198 DrawPointLine(x1, y1, x2, y2, [](Cell& ) {});
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; });
218void Canvas::DrawPointLine(
int x1,
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);
229 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
232 if (dx + dx > width_ * height_) {
237 for (
int i = 0; i < length; ++i) {
238 DrawPoint(x1, y1,
true, style);
239 if (2 * error >= -dy) {
243 if (2 * error <= dx) {
248 DrawPoint(x2, y2,
true, style);
255void Canvas::DrawPointCircle(
int x,
int y,
int radius) {
256 DrawPointCircle(x, y, radius, [](Cell& ) {});
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; });
274void Canvas::DrawPointCircle(
int x,
int y,
int radius,
const Stylizer& style) {
275 DrawPointEllipse(x, y, radius, radius, style);
282void Canvas::DrawPointCircleFilled(
int x,
int y,
int radius) {
283 DrawPointCircleFilled(x, y, radius, [](Cell& ) {});
291void Canvas::DrawPointCircleFilled(
int x,
294 const Color& color) {
295 DrawPointCircleFilled(x, y, radius,
296 [color](Cell& p) { p.foreground_color = color; });
304void Canvas::DrawPointCircleFilled(
int x,
307 const Stylizer& style) {
308 DrawPointEllipseFilled(x, y, radius, radius, style);
316void Canvas::DrawPointEllipse(
int x,
int y,
int r1,
int r2) {
317 DrawPointEllipse(x, y, r1, r2, [](Cell& ) {});
326void Canvas::DrawPointEllipse(
int x,
330 const Color& color) {
331 DrawPointEllipse(x, y, r1, r2,
332 [color](Cell& p) { p.foreground_color = color; });
341void Canvas::DrawPointEllipse(
int x1,
349 int dx = (1 + 2 * x) * e2 * e2;
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);
361 err += dx += 2 * r2 * r2;
365 err += dy += 2 * r1 * r1;
370 DrawPoint(x1, y1 + y,
true, s);
371 DrawPoint(x1, y1 - y,
true, s);
380void Canvas::DrawPointEllipseFilled(
int x1,
int y1,
int r1,
int r2) {
381 DrawPointEllipseFilled(x1, y1, r1, r2, [](Cell& ) {});
390void Canvas::DrawPointEllipseFilled(
int x1,
394 const Color& color) {
395 DrawPointEllipseFilled(x1, y1, r1, r2,
396 [color](Cell& p) { p.foreground_color = color; });
405void Canvas::DrawPointEllipseFilled(
int x1,
413 int dx = (1 + 2 * x) * e2 * e2;
418 for (
int xx = x1 + x; xx <= x1 - x; ++xx) {
419 DrawPoint(xx, y1 + y,
true, s);
420 DrawPoint(xx, y1 - y,
true, s);
425 err += dx += 2 * r2 * r2;
429 err += dy += 2 * r1 * r1;
434 for (
int yy = y1 - y; yy <= y1 +
y; ++yy) {
435 DrawPoint(x1, yy,
true, s);
444void Canvas::DrawBlock(
int x,
int y,
bool value) {
445 DrawBlock(x, y, value, [](Cell& ) {});
453void Canvas::DrawBlock(
int x,
int y,
bool value,
const Color& color) {
454 DrawBlock(x, y, value, [color](Cell& p) { p.foreground_color = color; });
462void Canvas::DrawBlock(
int x,
int y,
bool value,
const Stylizer& style) {
474void Canvas::DrawBlockOn(
int x,
int y) {
479 CanvasCell& cell = storage_[XY{x / 2,
y / 2}];
480 if (cell.type != CellType::kBlock) {
481 cell.content.character =
" ";
482 cell.type = CellType::kBlock;
485 const uint8_t bit = (x % 2) * 2 + y % 2;
486 uint8_t
value = g_map_block_inversed.at(cell.content.character);
488 cell.content.character = g_map_block[
value];
494void Canvas::DrawBlockOff(
int x,
int y) {
498 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
499 if (cell.type != CellType::kBlock) {
500 cell.content.character =
" ";
501 cell.type = CellType::kBlock;
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];
515void Canvas::DrawBlockToggle(
int x,
int y) {
519 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
520 if (cell.type != CellType::kBlock) {
521 cell.content.character =
" ";
522 cell.type = CellType::kBlock;
526 const uint8_t bit = (
y % 2) * 2 + x % 2;
527 uint8_t
value = g_map_block_inversed.at(cell.content.character);
529 cell.content.character = g_map_block[
value];
537void Canvas::DrawBlockLine(
int x1,
int y1,
int x2,
int y2) {
538 DrawBlockLine(x1, y1, x2, y2, [](Cell& ) {});
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; });
558void Canvas::DrawBlockLine(
int x1,
562 const Stylizer& style) {
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);
572 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
575 if (dx + dx > width_ * height_) {
580 for (
int i = 0; i < length; ++i) {
581 DrawBlock(x1, y1 * 2,
true, style);
582 if (2 * error >= -dy) {
586 if (2 * error <= dx) {
591 DrawBlock(x2, y2 * 2,
true, style);
598void Canvas::DrawBlockCircle(
int x,
int y,
int radius) {
599 DrawBlockCircle(x, y, radius, nostyle);
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; });
617void Canvas::DrawBlockCircle(
int x,
int y,
int radius,
const Stylizer& style) {
618 DrawBlockEllipse(x, y, radius, radius, style);
625void Canvas::DrawBlockCircleFilled(
int x,
int y,
int radius) {
626 DrawBlockCircleFilled(x, y, radius, nostyle);
634void Canvas::DrawBlockCircleFilled(
int x,
637 const Color& color) {
638 DrawBlockCircleFilled(x, y, radius,
639 [color](Cell& p) { p.foreground_color = color; });
647void Canvas::DrawBlockCircleFilled(
int x,
651 DrawBlockEllipseFilled(x, y, radius, radius, s);
659void Canvas::DrawBlockEllipse(
int x,
int y,
int r1,
int r2) {
660 DrawBlockEllipse(x, y, r1, r2, nostyle);
669void Canvas::DrawBlockEllipse(
int x,
673 const Color& color) {
674 DrawBlockEllipse(x, y, r1, r2,
675 [color](Cell& p) { p.foreground_color = color; });
684void Canvas::DrawBlockEllipse(
int x1,
694 int dx = (1 + 2 * x) * e2 * e2;
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);
706 err += dx += 2 * r2 * r2;
710 err += dy += 2 * r1 * r1;
715 DrawBlock(x1, 2 * (y1 + y),
true, s);
716 DrawBlock(x1, 2 * (y1 - y),
true, s);
725void Canvas::DrawBlockEllipseFilled(
int x,
int y,
int r1,
int r2) {
726 DrawBlockEllipseFilled(x, y, r1, r2, nostyle);
735void Canvas::DrawBlockEllipseFilled(
int x,
739 const Color& color) {
740 DrawBlockEllipseFilled(x, y, r1, r2,
741 [color](Cell& p) { p.foreground_color = color; });
750void Canvas::DrawBlockEllipseFilled(
int x1,
760 int dx = (1 + 2 * x) * e2 * e2;
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);
772 err += dx += 2 * r2 * r2;
776 err += dy += 2 * r1 * r1;
781 for (
int yy = y1 + y; yy <= y1 -
y; ++yy) {
782 DrawBlock(x1, 2 * yy,
true, s);
791void Canvas::DrawText(
int x,
int y, std::string_view
value) {
792 DrawText(x, y, value, nostyle);
800void Canvas::DrawText(
int x,
802 std::string_view
value,
803 const Color& color) {
804 DrawText(x, y, value, [color](Cell& p) { p.foreground_color = color; });
812void Canvas::DrawText(
int x,
814 std::string_view
value,
815 const Stylizer& style) {
816 for (
const auto& it : Utf8ToGlyphs(value)) {
821 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
822 cell.type = CellType::kCell;
823 cell.content.character = it;
833void Canvas::DrawCell(
int x,
int y,
const Cell& p) {
834 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
835 cell.type = CellType::kCell;
845void Canvas::DrawSurface(
int x,
int y,
const Surface& image) {
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);
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{
859 cell.type = CellType::kCell;
860 cell.content = image.CellAt(dx, dy);
869void Canvas::Style(
int x,
int y,
const Stylizer& style) {
871 style(storage_[XY{x / 2,
y / 4}].content);
877class CanvasNodeBase :
public Node {
879 CanvasNodeBase() =
default;
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);
892 virtual const Canvas&
canvas() = 0;
900#if defined(__GNUC__) && !defined(__clang__)
901#pragma GCC diagnostic push
902#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
904 class Impl :
public CanvasNodeBase {
907 requirement_.min_x = (canvas_->width() + 1) / 2;
908 requirement_.min_y = (canvas_->height() + 3) / 4;
910 const Canvas& canvas()
final {
return *canvas_; }
913 return std::make_shared<Impl>(canvas);
914#if defined(__GNUC__) && !defined(__clang__)
915#pragma GCC diagnostic pop
924 class Impl :
public CanvasNodeBase {
926 Impl(
int width,
int height, std::function<
void(Canvas&)> fn)
927 : width_(width), height_(height), fn_(std::move(fn)) {}
929 void ComputeRequirement()
final {
930 requirement_.min_x = (width_ + 1) / 2;
931 requirement_.min_y = (height_ + 3) / 4;
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);
939 CanvasNodeBase::Render(screen);
942 const Canvas& canvas()
final {
return canvas_; }
946 std::function<void(Canvas&)> fn_;
948 return std::make_shared<Impl>(width, height, std::move(fn));
954 const int default_dim = 12;
955 return canvas(default_dim, default_dim, std::move(fn));
An adapter. Own or reference an immutable object.
The FTXUI ftxui:: namespace.
std::shared_ptr< Node > Element
void Render(Screen &screen, Node *node, Selection &selection)
Element canvas(int width, int height, std::function< void(Canvas &)>)
Produce an element drawing a canvas of requested size.