53uint8_t g_map_braille[2][4][2] = {
55 {0b00000000, 0b00000001},
56 {0b00000000, 0b00000010},
57 {0b00000000, 0b00000100},
58 {0b00000001, 0b00000000},
61 {0b00000000, 0b00001000},
62 {0b00000000, 0b00010000},
63 {0b00000000, 0b00100000},
64 {0b00000010, 0b00000000},
69std::vector<std::string> g_map_block = {
70 " ",
"▘",
"▖",
"▌",
"▝",
"▀",
"▞",
"▛",
71 "▗",
"▚",
"▄",
"▙",
"▐",
"▜",
"▟",
"█",
75const std::map<std::string, uint8_t> g_map_block_inversed = {
76 {
" ", 0b0000}, {
"▘", 0b0001}, {
"▖", 0b0010}, {
"▌", 0b0011},
77 {
"▝", 0b0100}, {
"▀", 0b0101}, {
"▞", 0b0110}, {
"▛", 0b0111},
78 {
"▗", 0b1000}, {
"▚", 0b1001}, {
"▄", 0b1010}, {
"▙", 0b1011},
79 {
"▐", 0b1100}, {
"▜", 0b1101}, {
"▟", 0b1110}, {
"█", 0b1111},
82constexpr auto nostyle = [](Pixel& ) {};
92 storage_(width_ * height_ / 8 ) {}
98 auto it = storage_.find(XY{x, y});
99 return (it == storage_.end()) ?
Pixel() : it->second.content;
140 Cell& cell = storage_[XY{x / 2, y / 4}];
141 if (cell.type != CellType::kBraille) {
143 cell.type = CellType::kBraille;
146 cell.content.
character[1] |= g_map_braille[x % 2][y % 4][0];
147 cell.content.
character[2] |= g_map_braille[x % 2][y % 4][1];
157 Cell& cell = storage_[XY{x / 2, y / 4}];
158 if (cell.type != CellType::kBraille) {
160 cell.type = CellType::kBraille;
163 cell.content.
character[1] &= ~(g_map_braille[x % 2][y % 4][0]);
164 cell.content.
character[2] &= ~(g_map_braille[x % 2][y % 4][1]);
174 Cell& cell = storage_[XY{x / 2, y / 4}];
175 if (cell.type != CellType::kBraille) {
177 cell.type = CellType::kBraille;
180 cell.content.
character[1] ^= g_map_braille[x % 2][y % 4][0];
181 cell.content.
character[2] ^= g_map_braille[x % 2][y % 4][1];
215 const int dx = std::abs(x2 - x1);
216 const int dy = std::abs(y2 - y1);
217 const int sx = x1 < x2 ? 1 : -1;
218 const int sy = y1 < y2 ? 1 : -1;
219 const int length = std::max(dx, dy);
221 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
224 if (dx + dx > width_ * height_) {
229 for (
int i = 0; i < length; ++i) {
231 if (2 * error >= -dy) {
235 if (2 * error <= dx) {
282void Canvas::DrawPointCircleFilled(
int x,
285 const Color& color) {
295void Canvas::DrawPointCircleFilled(
int x,
298 const Stylizer& style) {
299 DrawPointEllipseFilled(x, y, radius, radius, style);
307void Canvas::DrawPointEllipse(
int x,
int y,
int r1,
int r2) {
308 DrawPointEllipse(x, y, r1, r2, [](
Pixel& ) {});
317void Canvas::DrawPointEllipse(
int x,
321 const Color& color) {
322 DrawPointEllipse(x, y, r1, r2,
332void Canvas::DrawPointEllipse(
int x1,
340 int dx = (1 + 2 * x) * e2 * e2;
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);
352 err += dx += 2 * r2 * r2;
356 err += dy += 2 * r1 * r1;
361 DrawPoint(x1, y1 + y,
true, s);
362 DrawPoint(x1, y1 - y,
true, s);
371void Canvas::DrawPointEllipseFilled(
int x1,
int y1,
int r1,
int r2) {
372 DrawPointEllipseFilled(x1, y1, r1, r2, [](Pixel& ) {});
381void Canvas::DrawPointEllipseFilled(
int x1,
385 const Color& color) {
386 DrawPointEllipseFilled(x1, y1, r1, r2,
387 [color](Pixel& p) { p.foreground_color = color; });
396void Canvas::DrawPointEllipseFilled(
int x1,
404 int dx = (1 + 2 * x) * e2 * e2;
409 for (
int xx = x1 + x; xx <= x1 - x; ++xx) {
410 DrawPoint(xx, y1 + y,
true, s);
411 DrawPoint(xx, y1 - y,
true, s);
416 err += dx += 2 * r2 * r2;
420 err += dy += 2 * r1 * r1;
425 for (
int yy = y1 - y; yy <= y1 + y; ++yy) {
426 DrawPoint(x1, yy,
true, s);
435void Canvas::DrawBlock(
int x,
int y,
bool value) {
436 DrawBlock(x, y, value, [](
Pixel& ) {});
444void Canvas::DrawBlock(
int x,
int y,
bool value,
const Color& color) {
453void Canvas::DrawBlock(
int x,
int y,
bool value,
const Stylizer& style) {
465void Canvas::DrawBlockOn(
int x,
int y) {
470 Cell& cell = storage_[XY{x / 2, y / 2}];
471 if (cell.type != CellType::kBlock) {
472 cell.content.character =
" ";
473 cell.type = CellType::kBlock;
476 const uint8_t bit = (x % 2) * 2 + y % 2;
477 uint8_t value = g_map_block_inversed.at(cell.content.character);
479 cell.content.character = g_map_block[value];
485void Canvas::DrawBlockOff(
int x,
int y) {
489 Cell& cell = storage_[XY{x / 2, y / 4}];
490 if (cell.type != CellType::kBlock) {
492 cell.type = CellType::kBlock;
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];
505void Canvas::DrawBlockToggle(
int x,
int y) {
509 Cell& cell = storage_[XY{x / 2, y / 4}];
510 if (cell.type != CellType::kBlock) {
512 cell.type = CellType::kBlock;
516 const uint8_t bit = (y % 2) * 2 + x % 2;
517 uint8_t value = g_map_block_inversed.at(cell.content.
character);
519 cell.content.
character = g_map_block[value];
527void Canvas::DrawBlockLine(
int x1,
int y1,
int x2,
int y2) {
528 DrawBlockLine(x1, y1, x2, y2, [](
Pixel& ) {});
537void Canvas::DrawBlockLine(
int x1,
int y1,
int x2,
int y2,
const Color& color) {
538 DrawBlockLine(x1, y1, x2, y2,
548void Canvas::DrawBlockLine(
int x1,
552 const Stylizer& style) {
556 const int dx = std::abs(x2 - x1);
557 const int dy = std::abs(y2 - y1);
558 const int sx = x1 < x2 ? 1 : -1;
559 const int sy = y1 < y2 ? 1 : -1;
560 const int length = std::max(dx, dy);
562 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
565 if (dx + dx > width_ * height_) {
570 for (
int i = 0; i < length; ++i) {
571 DrawBlock(x1, y1 * 2,
true, style);
572 if (2 * error >= -dy) {
576 if (2 * error <= dx) {
581 DrawBlock(x2, y2 * 2,
true, style);
588void Canvas::DrawBlockCircle(
int x,
int y,
int radius) {
589 DrawBlockCircle(x, y, radius, nostyle);
597void Canvas::DrawBlockCircle(
int x,
int y,
int radius,
const Color& color) {
598 DrawBlockCircle(x, y, radius,
599 [color](Pixel& p) { p.foreground_color = color; });
607void Canvas::DrawBlockCircle(
int x,
int y,
int radius,
const Stylizer& style) {
608 DrawBlockEllipse(x, y, radius, radius, style);
615void Canvas::DrawBlockCircleFilled(
int x,
int y,
int radius) {
616 DrawBlockCircleFilled(x, y, radius, nostyle);
624void Canvas::DrawBlockCircleFilled(
int x,
627 const Color& color) {
628 DrawBlockCircleFilled(x, y, radius,
629 [color](Pixel& p) { p.foreground_color = color; });
637void Canvas::DrawBlockCircleFilled(
int x,
641 DrawBlockEllipseFilled(x, y, radius, radius, s);
649void Canvas::DrawBlockEllipse(
int x,
int y,
int r1,
int r2) {
650 DrawBlockEllipse(x, y, r1, r2, nostyle);
659void Canvas::DrawBlockEllipse(
int x,
663 const Color& color) {
664 DrawBlockEllipse(x, y, r1, r2,
665 [color](Pixel& p) { p.foreground_color = color; });
674void Canvas::DrawBlockEllipse(
int x1,
684 int dx = (1 + 2 * x) * e2 * e2;
689 DrawBlock(x1 - x, 2 * (y1 + y),
true, s);
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);
696 err += dx += 2 * r2 * r2;
700 err += dy += 2 * r1 * r1;
705 DrawBlock(x1, 2 * (y1 + y),
true, s);
706 DrawBlock(x1, 2 * (y1 - y),
true, s);
715void Canvas::DrawBlockEllipseFilled(
int x,
int y,
int r1,
int r2) {
716 DrawBlockEllipseFilled(x, y, r1, r2, nostyle);
725void Canvas::DrawBlockEllipseFilled(
int x,
729 const Color& color) {
730 DrawBlockEllipseFilled(x, y, r1, r2,
731 [color](Pixel& p) { p.foreground_color = color; });
740void Canvas::DrawBlockEllipseFilled(
int x1,
750 int dx = (1 + 2 * x) * e2 * e2;
755 for (
int xx = x1 + x; xx <= x1 - x; ++xx) {
756 DrawBlock(xx, 2 * (y1 + y),
true, s);
757 DrawBlock(xx, 2 * (y1 - y),
true, s);
762 err += dx += 2 * r2 * r2;
766 err += dy += 2 * r1 * r1;
771 for (
int yy = y1 + y; yy <= y1 - y; ++yy) {
772 DrawBlock(x1, 2 * yy,
true, s);
781void Canvas::DrawText(
int x,
int y,
const std::string& value) {
782 DrawText(x, y, value, nostyle);
790void Canvas::DrawText(
int x,
792 const std::string& value,
793 const Color& color) {
802void Canvas::DrawText(
int x,
804 const std::string& value,
806 for (
const auto& it : Utf8ToGlyphs(value)) {
811 Cell& cell = storage_[XY{x / 2, y / 4}];
812 cell.type = CellType::kCell;
823void Canvas::DrawPixel(
int x,
int y,
const Pixel& p) {
824 Cell& cell = storage_[XY{x / 2, y / 4}];
825 cell.type = CellType::kCell;
834void Canvas::DrawImage(
int x,
int y,
const Image& image) {
837 const int dx_begin = std::max(0, -x);
838 const int dy_begin = std::max(0, -y);
839 const int dx_end = std::min(image.
dimx(), width_ - x);
840 const int dy_end = std::min(image.
dimy(), height_ - y);
842 for (
int dy = dy_begin; dy < dy_end; ++dy) {
843 for (
int dx = dx_begin; dx < dx_end; ++dx) {
844 Cell& cell = storage_[XY{
848 cell.type = CellType::kCell;
849 cell.content = image.
PixelAt(dx, dy);
856void Canvas::Style(
int x,
int y,
const Stylizer& style) {
858 style(storage_[XY{x / 2, y / 4}].content);
864class CanvasNodeBase :
public Node {
866 CanvasNodeBase() =
default;
868 void Render(Screen& screen)
override {
869 const Canvas& c =
canvas();
870 const int y_max = std::min(c.height() / 4, box_.y_max - box_.y_min + 1);
871 const int x_max = std::min(c.width() / 2, box_.x_max - box_.x_min + 1);
872 for (
int y = 0; y < y_max; ++y) {
873 for (
int x = 0; x < x_max; ++x) {
874 screen.PixelAt(box_.x_min + x, box_.y_min + y) = c.GetPixel(x, y);
879 virtual const Canvas&
canvas() = 0;
887 class Impl :
public CanvasNodeBase {
889 explicit Impl(ConstRef<Canvas> canvas) : canvas_(std::move(canvas)) {
890 requirement_.min_x = (canvas_->width() + 1) / 2;
891 requirement_.min_y = (canvas_->height() + 3) / 4;
893 const Canvas&
canvas()
final {
return *canvas_; }
894 ConstRef<Canvas> canvas_;
896 return std::make_shared<Impl>(canvas);
903Element
canvas(
int width,
int height, std::function<
void(Canvas&)> fn) {
904 class Impl :
public CanvasNodeBase {
906 Impl(
int width,
int height, std::function<
void(Canvas&)> fn)
907 : width_(width), height_(height), fn_(std::move(fn)) {}
909 void ComputeRequirement()
final {
910 requirement_.min_x = (width_ + 1) / 2;
911 requirement_.min_y = (height_ + 3) / 4;
914 void Render(Screen& screen)
final {
915 const int width = (box_.x_max - box_.x_min + 1) * 2;
916 const int height = (box_.y_max - box_.y_min + 1) * 4;
917 canvas_ = Canvas(width, height);
919 CanvasNodeBase::Render(screen);
922 const Canvas&
canvas()
final {
return canvas_; }
926 std::function<void(Canvas&)> fn_;
928 return std::make_shared<Impl>(width, height, std::move(fn));
933Element
canvas(std::function<
void(Canvas&)> fn) {
934 const int default_dim = 12;
935 return canvas(default_dim, default_dim, std::move(fn));
void DrawPointLine(int x1, int y1, int x2, int y2)
繪製一條由盲文點組成的線條。
std::function< void(Pixel &)> Stylizer
void DrawPointOn(int x, int y)
繪製一個盲文點。
void DrawPointOff(int x, int y)
擦除一個盲文點。
Pixel GetPixel(int x, int y) const
取得單元格的內容。
void DrawPointEllipse(int x, int y, int r1, int r2)
繪製一個由盲文點組成的橢圓。
void DrawPoint(int x, int y, bool value)
繪製一個盲文點。
void DrawPointCircle(int x, int y, int radius)
繪製一個由盲文點組成的圓形。
void DrawPointToggle(int x, int y)
切換一個盲文點。如果它已填滿,將被擦除;如果為空,則繪製它。
void Render(Screen &screen, const Element &element)
在 ftxui::Screen 上顯示元素。
Decorator color(Color)
使用前景顏色進行裝飾。
Pixel & PixelAt(int x, int y)
存取給定位置的單元格 (Pixel)。
Color 是一個在終端使用者介面中表示顏色的類別。
DrawPointCircleFilled(x, y, radius, [](Pixel &) {})
繪製一個由盲文點組成的實心圓形。
Element canvas(ConstRef< Canvas > canvas)
從 Canvas 或對 Canvas 的參考中產生一個元素。