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)
97 : width_(std::max(0, width)),
98 height_(std::max(0, height)),
99 storage_(static_cast<size_t>(width_) * static_cast<size_t>(height_) /
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;
114void Canvas::DrawPoint(
int x,
int y,
bool value) {
115 DrawPoint(x, y, value, [](Cell& ) {});
123void Canvas::DrawPoint(
int x,
int y,
bool value,
const Color& color) {
124 DrawPoint(x, y, value, [color](Cell& p) { p.foreground_color = color; });
132void Canvas::DrawPoint(
int x,
int y,
bool value,
const Stylizer& style) {
144void Canvas::DrawPointOn(
int x,
int y) {
148 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
149 if (cell.type != CellType::kBraille) {
150 cell.content.character =
"⠀";
151 cell.type = CellType::kBraille;
154 cell.content.character[1] |= g_map_braille[x % 2][
y % 4][0];
155 cell.content.character[2] |= g_map_braille[x % 2][
y % 4][1];
161void Canvas::DrawPointOff(
int x,
int y) {
165 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
166 if (cell.type != CellType::kBraille) {
167 cell.content.character =
"⠀";
168 cell.type = CellType::kBraille;
171 cell.content.character[1] &= ~(g_map_braille[x % 2][
y % 4][0]);
172 cell.content.character[2] &= ~(g_map_braille[x % 2][
y % 4][1]);
179void Canvas::DrawPointToggle(
int x,
int y) {
183 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
184 if (cell.type != CellType::kBraille) {
185 cell.content.character =
"⠀";
186 cell.type = CellType::kBraille;
189 cell.content.character[1] ^= g_map_braille[x % 2][
y % 4][0];
190 cell.content.character[2] ^= g_map_braille[x % 2][
y % 4][1];
198void Canvas::DrawPointLine(
int x1,
int y1,
int x2,
int y2) {
199 DrawPointLine(x1, y1, x2, y2, [](Cell& ) {});
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; });
219void Canvas::DrawPointLine(
int x1,
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);
230 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
233 if (dx + dx > width_ * height_) {
238 for (
int i = 0; i < length; ++i) {
239 DrawPoint(x1, y1,
true, style);
240 if (2 * error >= -dy) {
244 if (2 * error <= dx) {
249 DrawPoint(x2, y2,
true, style);
256void Canvas::DrawPointCircle(
int x,
int y,
int radius) {
257 DrawPointCircle(x, y, radius, [](Cell& ) {});
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; });
275void Canvas::DrawPointCircle(
int x,
int y,
int radius,
const Stylizer& style) {
276 DrawPointEllipse(x, y, radius, radius, style);
283void Canvas::DrawPointCircleFilled(
int x,
int y,
int radius) {
284 DrawPointCircleFilled(x, y, radius, [](Cell& ) {});
292void Canvas::DrawPointCircleFilled(
int x,
295 const Color& color) {
296 DrawPointCircleFilled(x, y, radius,
297 [color](Cell& p) { p.foreground_color = color; });
305void Canvas::DrawPointCircleFilled(
int x,
308 const Stylizer& style) {
309 DrawPointEllipseFilled(x, y, radius, radius, style);
317void Canvas::DrawPointEllipse(
int x,
int y,
int r1,
int r2) {
318 DrawPointEllipse(x, y, r1, r2, [](Cell& ) {});
327void Canvas::DrawPointEllipse(
int x,
331 const Color& color) {
332 DrawPointEllipse(x, y, r1, r2,
333 [color](Cell& p) { p.foreground_color = color; });
342void Canvas::DrawPointEllipse(
int x1,
350 int dx = (1 + 2 * x) * e2 * e2;
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);
362 err += dx += 2 * r2 * r2;
366 err += dy += 2 * r1 * r1;
371 DrawPoint(x1, y1 + y,
true, s);
372 DrawPoint(x1, y1 - y,
true, s);
381void Canvas::DrawPointEllipseFilled(
int x1,
int y1,
int r1,
int r2) {
382 DrawPointEllipseFilled(x1, y1, r1, r2, [](Cell& ) {});
391void Canvas::DrawPointEllipseFilled(
int x1,
395 const Color& color) {
396 DrawPointEllipseFilled(x1, y1, r1, r2,
397 [color](Cell& p) { p.foreground_color = color; });
406void Canvas::DrawPointEllipseFilled(
int x1,
414 int dx = (1 + 2 * x) * e2 * e2;
419 for (
int xx = x1 + x; xx <= x1 - x; ++xx) {
420 DrawPoint(xx, y1 + y,
true, s);
421 DrawPoint(xx, y1 - y,
true, s);
426 err += dx += 2 * r2 * r2;
430 err += dy += 2 * r1 * r1;
435 for (
int yy = y1 - y; yy <= y1 +
y; ++yy) {
436 DrawPoint(x1, yy,
true, s);
445void Canvas::DrawBlock(
int x,
int y,
bool value) {
446 DrawBlock(x, y, value, [](Cell& ) {});
454void Canvas::DrawBlock(
int x,
int y,
bool value,
const Color& color) {
455 DrawBlock(x, y, value, [color](Cell& p) { p.foreground_color = color; });
463void Canvas::DrawBlock(
int x,
int y,
bool value,
const Stylizer& style) {
475void Canvas::DrawBlockOn(
int x,
int y) {
480 CanvasCell& cell = storage_[XY{x / 2,
y / 2}];
481 if (cell.type != CellType::kBlock) {
482 cell.content.character =
" ";
483 cell.type = CellType::kBlock;
486 const uint8_t bit = (x % 2) * 2 + y % 2;
487 uint8_t
value = g_map_block_inversed.at(cell.content.character);
489 cell.content.character = g_map_block[
value];
495void Canvas::DrawBlockOff(
int x,
int y) {
499 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
500 if (cell.type != CellType::kBlock) {
501 cell.content.character =
" ";
502 cell.type = CellType::kBlock;
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];
516void Canvas::DrawBlockToggle(
int x,
int y) {
520 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
521 if (cell.type != CellType::kBlock) {
522 cell.content.character =
" ";
523 cell.type = CellType::kBlock;
527 const uint8_t bit = (
y % 2) * 2 + x % 2;
528 uint8_t
value = g_map_block_inversed.at(cell.content.character);
530 cell.content.character = g_map_block[
value];
538void Canvas::DrawBlockLine(
int x1,
int y1,
int x2,
int y2) {
539 DrawBlockLine(x1, y1, x2, y2, [](Cell& ) {});
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; });
559void Canvas::DrawBlockLine(
int x1,
563 const Stylizer& style) {
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);
573 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
576 if (dx + dx > width_ * height_) {
581 for (
int i = 0; i < length; ++i) {
582 DrawBlock(x1, y1 * 2,
true, style);
583 if (2 * error >= -dy) {
587 if (2 * error <= dx) {
592 DrawBlock(x2, y2 * 2,
true, style);
599void Canvas::DrawBlockCircle(
int x,
int y,
int radius) {
600 DrawBlockCircle(x, y, radius, nostyle);
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; });
618void Canvas::DrawBlockCircle(
int x,
int y,
int radius,
const Stylizer& style) {
619 DrawBlockEllipse(x, y, radius, radius, style);
626void Canvas::DrawBlockCircleFilled(
int x,
int y,
int radius) {
627 DrawBlockCircleFilled(x, y, radius, nostyle);
635void Canvas::DrawBlockCircleFilled(
int x,
638 const Color& color) {
639 DrawBlockCircleFilled(x, y, radius,
640 [color](Cell& p) { p.foreground_color = color; });
648void Canvas::DrawBlockCircleFilled(
int x,
652 DrawBlockEllipseFilled(x, y, radius, radius, s);
660void Canvas::DrawBlockEllipse(
int x,
int y,
int r1,
int r2) {
661 DrawBlockEllipse(x, y, r1, r2, nostyle);
670void Canvas::DrawBlockEllipse(
int x,
674 const Color& color) {
675 DrawBlockEllipse(x, y, r1, r2,
676 [color](Cell& p) { p.foreground_color = color; });
685void Canvas::DrawBlockEllipse(
int x1,
695 int dx = (1 + 2 * x) * e2 * e2;
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);
707 err += dx += 2 * r2 * r2;
711 err += dy += 2 * r1 * r1;
716 DrawBlock(x1, 2 * (y1 + y),
true, s);
717 DrawBlock(x1, 2 * (y1 - y),
true, s);
726void Canvas::DrawBlockEllipseFilled(
int x,
int y,
int r1,
int r2) {
727 DrawBlockEllipseFilled(x, y, r1, r2, nostyle);
736void Canvas::DrawBlockEllipseFilled(
int x,
740 const Color& color) {
741 DrawBlockEllipseFilled(x, y, r1, r2,
742 [color](Cell& p) { p.foreground_color = color; });
751void Canvas::DrawBlockEllipseFilled(
int x1,
761 int dx = (1 + 2 * x) * e2 * e2;
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);
773 err += dx += 2 * r2 * r2;
777 err += dy += 2 * r1 * r1;
782 for (
int yy = y1 + y; yy <= y1 -
y; ++yy) {
783 DrawBlock(x1, 2 * yy,
true, s);
792void Canvas::DrawText(
int x,
int y, std::string_view
value) {
793 DrawText(x, y, value, nostyle);
801void Canvas::DrawText(
int x,
803 std::string_view
value,
804 const Color& color) {
805 DrawText(x, y, value, [color](Cell& p) { p.foreground_color = color; });
813void Canvas::DrawText(
int x,
815 std::string_view
value,
816 const Stylizer& style) {
817 for (
const auto& it : Utf8ToGlyphs(value)) {
822 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
823 cell.type = CellType::kCell;
824 cell.content.character = it;
834void Canvas::DrawCell(
int x,
int y,
const Cell& p) {
835 CanvasCell& cell = storage_[XY{x / 2,
y / 4}];
836 cell.type = CellType::kCell;
846void Canvas::DrawSurface(
int x,
int y,
const Surface& image) {
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);
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{
860 cell.type = CellType::kCell;
861 cell.content = image.CellAt(dx, dy);
870void Canvas::Style(
int x,
int y,
const Stylizer& style) {
872 style(storage_[XY{x / 2,
y / 4}].content);
878class CanvasNodeBase :
public Node {
880 CanvasNodeBase() =
default;
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);
893 virtual const Canvas&
canvas() = 0;
901#if defined(__GNUC__) && !defined(__clang__)
902#pragma GCC diagnostic push
903#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
905 class Impl :
public CanvasNodeBase {
908 requirement_.min_x = (canvas_->width() + 1) / 2;
909 requirement_.min_y = (canvas_->height() + 3) / 4;
911 const Canvas& canvas()
final {
return *canvas_; }
914 return std::make_shared<Impl>(canvas);
915#if defined(__GNUC__) && !defined(__clang__)
916#pragma GCC diagnostic pop
925 class Impl :
public CanvasNodeBase {
927 Impl(
int width,
int height, std::function<
void(Canvas&)> fn)
928 : width_(width), height_(height), fn_(std::move(fn)) {}
930 void ComputeRequirement()
final {
931 requirement_.min_x = (width_ + 1) / 2;
932 requirement_.min_y = (height_ + 3) / 4;
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);
940 CanvasNodeBase::Render(screen);
943 const Canvas& canvas()
final {
return canvas_; }
947 std::function<void(Canvas&)> fn_;
949 return std::make_shared<Impl>(width, height, std::move(fn));
955 const int default_dim = 12;
956 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.