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// 本原始碼的使用受 MIT 授權約束,詳情請參閱 LICENSE 檔案。
4
5#include <algorithm> // for max, min
6#include <cmath> // for abs
7#include <cstdint> // for uint8_t
8#include <cstdlib> // for abs
9#include <ftxui/screen/color.hpp> // for Color
10#include <functional> // for function
11#include <map> // for map
12#include <memory> // for make_shared
13#include <utility> // for move, pair
14#include <vector> // for vector
15
16#include "ftxui/dom/elements.hpp" // for Element, canvas
17#include "ftxui/dom/node.hpp" // for Node
18#include "ftxui/dom/requirement.hpp" // for Requirement
19#include "ftxui/screen/box.hpp" // for Box
20#include "ftxui/screen/image.hpp" // for Image
21#include "ftxui/screen/pixel.hpp" // for Pixel
22#include "ftxui/screen/screen.hpp" // for Pixel, Screen
23#include "ftxui/screen/string.hpp" // for Utf8ToGlyphs
24#include "ftxui/util/ref.hpp" // for ConstRef
25
26namespace ftxui {
27
28namespace {
29
30// Base UTF8 pattern:
31// 11100010 10100000 10000000 // empty
32
33// Pattern for the individual dots:
34// ┌──────┬───────┐
35// │dot1 │ dot4 │
36// ├──────┼───────┤
37// │dot2 │ dot5 │
38// ├──────┼───────┤
39// │dot3 │ dot6 │
40// ├──────┼───────┤
41// │dot0-1│ dot0-2│
42// └──────┴───────┘
43// 11100010 10100000 10000001 // dot1
44// 11100010 10100000 10000010 // dot2
45// 11100010 10100000 10000100 // dot3
46// 11100010 10100001 10000000 // dot0-1
47// 11100010 10100000 10001000 // dot4
48// 11100010 10100000 10010000 // dot5
49// 11100010 10100000 10100000 // dot6
50// 11100010 10100010 10000000 // dot0-2
51
52// NOLINTNEXTLINE
53uint8_t g_map_braille[2][4][2] = {
54 {
55 {0b00000000, 0b00000001}, // NOLINT | dot1
56 {0b00000000, 0b00000010}, // NOLINT | dot2
57 {0b00000000, 0b00000100}, // NOLINT | dot3
58 {0b00000001, 0b00000000}, // NOLINT | dot0-1
59 },
60 {
61 {0b00000000, 0b00001000}, // NOLINT | dot4
62 {0b00000000, 0b00010000}, // NOLINT | dot5
63 {0b00000000, 0b00100000}, // NOLINT | dot6
64 {0b00000010, 0b00000000}, // NOLINT | dot0-2
65 },
66};
67
68// NOLINTNEXTLINE
69std::vector<std::string> g_map_block = {
70 " ", "▘", "▖", "▌", "▝", "▀", "▞", "▛",
71 "▗", "▚", "▄", "▙", "▐", "▜", "▟", "█",
72};
73
74// NOLINTNEXTLINE
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},
80};
81
82constexpr auto nostyle = [](Pixel& /*pixel*/) {};
83
84} // namespace
85
86/// @brief 建構函式。
87/// @param width 畫布的寬度。一個單元格是 2x4 的盲文點。
88/// @param height 畫布的高度。一個單元格是 2x4 的盲文點。
89Canvas::Canvas(int width, int height)
90 : width_(width),
91 height_(height),
92 storage_(width_ * height_ / 8 /* NOLINT */) {}
93
94/// @brief 取得單元格的內容。
95/// @param x 單元格的 x 座標。
96/// @param y 單元格的 y 座標。
97Pixel Canvas::GetPixel(int x, int y) const {
98 auto it = storage_.find(XY{x, y});
99 return (it == storage_.end()) ? Pixel() : it->second.content;
100}
101
102/// @brief 繪製一個盲文點。
103/// @param x 點的 x 座標。
104/// @param y 點的 y 座標。
105/// @param value 點是否填滿。
106void Canvas::DrawPoint(int x, int y, bool value) {
107 DrawPoint(x, y, value, [](Pixel& /*pixel*/) {});
108}
109
110/// @brief 繪製一個盲文點。
111/// @param x 點的 x 座標。
112/// @param y 點的 y 座標。
113/// @param value 點是否填滿。
114/// @param color 點的顏色。
115void Canvas::DrawPoint(int x, int y, bool value, const Color& color) {
116 DrawPoint(x, y, value, [color](Pixel& p) { p.foreground_color = color; });
117}
118
119/// @brief 繪製一個盲文點。
120/// @param x 點的 x 座標。
121/// @param y 點的 y 座標。
122/// @param value 點是否填滿。
123/// @param style 單元格的樣式。
124void Canvas::DrawPoint(int x, int y, bool value, const Stylizer& style) {
125 Style(x, y, style);
126 if (value) {
127 DrawPointOn(x, y);
128 } else {
129 DrawPointOff(x, y);
130 }
131}
132
133/// @brief 繪製一個盲文點。
134/// @param x 點的 x 座標。
135/// @param y 點的 y 座標。
136void Canvas::DrawPointOn(int x, int y) {
137 if (!IsIn(x, y)) {
138 return;
139 }
140 Cell& cell = storage_[XY{x / 2, y / 4}];
141 if (cell.type != CellType::kBraille) {
142 cell.content.character = "⠀"; // 3 bytes.
143 cell.type = CellType::kBraille;
144 }
145
146 cell.content.character[1] |= g_map_braille[x % 2][y % 4][0]; // NOLINT
147 cell.content.character[2] |= g_map_braille[x % 2][y % 4][1]; // NOLINT
148}
149
150/// @brief 擦除一個盲文點。
151/// @param x 點的 x 座標。
152/// @param y 點的 y 座標。
153void Canvas::DrawPointOff(int x, int y) {
154 if (!IsIn(x, y)) {
155 return;
156 }
157 Cell& cell = storage_[XY{x / 2, y / 4}];
158 if (cell.type != CellType::kBraille) {
159 cell.content.character = "⠀"; // 3 byt
160 cell.type = CellType::kBraille;
161 }
162
163 cell.content.character[1] &= ~(g_map_braille[x % 2][y % 4][0]); // NOLINT
164 cell.content.character[2] &= ~(g_map_braille[x % 2][y % 4][1]); // NOLINT
165}
166
167/// @brief 切換一個盲文點。如果它已填滿,將被擦除;如果為空,則繪製它。
168/// @param x 點的 x 座標。
169/// @param y 點的 y 座標。
170void Canvas::DrawPointToggle(int x, int y) {
171 if (!IsIn(x, y)) {
172 return;
173 }
174 Cell& cell = storage_[XY{x / 2, y / 4}];
175 if (cell.type != CellType::kBraille) {
176 cell.content.character = "⠀"; // 3 byt
177 cell.type = CellType::kBraille;
178 }
179
180 cell.content.character[1] ^= g_map_braille[x % 2][y % 4][0]; // NOLINT
181 cell.content.character[2] ^= g_map_braille[x % 2][y % 4][1]; // NOLINT
182}
183
184/// @brief 繪製一條由盲文點組成的線條。
185/// @param x1 第一個點的 x 座標。
186/// @param y1 第一個點的 y 座標。
187/// @param x2 第二個點的 x 座標。
188/// @param y2 第二個點的 y 座標。
189void Canvas::DrawPointLine(int x1, int y1, int x2, int y2) {
190 DrawPointLine(x1, y1, x2, y2, [](Pixel& /*pixel*/) {});
191}
192
193/// @brief 繪製一條由盲文點組成的線條。
194/// @param x1 第一個點的 x 座標。
195/// @param y1 第一個點的 y 座標。
196/// @param x2 第二個點的 x 座標。
197/// @param y2 第二個點的 y 座標。
198/// @param color 線條的顏色。
199void Canvas::DrawPointLine(int x1, int y1, int x2, int y2, const Color& color) {
200 DrawPointLine(x1, y1, x2, y2,
201 [color](Pixel& p) { p.foreground_color = color; });
202}
203
204/// @brief 繪製一條由盲文點組成的線條。
205/// @param x1 第一個點的 x 座標。
206/// @param y1 第一個點的 y 座標。
207/// @param x2 第二個點的 x 座標。
208/// @param y2 第二個點的 y 座標。
209/// @param style 線條的樣式。
211 int y1,
212 int x2,
213 int y2,
214 const Stylizer& style) {
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);
220
221 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
222 return;
223 }
224 if (dx + dx > width_ * height_) {
225 return;
226 }
227
228 int error = dx - dy;
229 for (int i = 0; i < length; ++i) {
230 DrawPoint(x1, y1, true, style);
231 if (2 * error >= -dy) {
232 error -= dy;
233 x1 += sx;
234 }
235 if (2 * error <= dx) {
236 error += dx;
237 y1 += sy;
238 }
239 }
240 DrawPoint(x2, y2, true, style);
241}
242
243/// @brief 繪製一個由盲文點組成的圓形。
244/// @param x 圓心點的 x 座標。
245/// @param y 圓心點的 y 座標。
246/// @param radius 圓形的半徑。
247void Canvas::DrawPointCircle(int x, int y, int radius) {
248 DrawPointCircle(x, y, radius, [](Pixel& /*pixel*/) {});
249}
250
251/// @brief 繪製一個由盲文點組成的圓形。
252/// @param x 圓心點的 x 座標。
253/// @param y 圓心點的 y 座標。
254/// @param radius 圓形的半徑。
255/// @param color 圓形的顏色。
256void Canvas::DrawPointCircle(int x, int y, int radius, const Color& color) {
257 DrawPointCircle(x, y, radius,
258 [color](Pixel& p) { p.foreground_color = color; });
259}
260
261/// @brief 繪製一個由盲文點組成的圓形。
262/// @param x 圓心點的 x 座標。
263/// @param y 圓心點的 y 座標。
264/// @param radius 圓形的半徑。
265/// @param style 圓形的樣式。
266void Canvas::DrawPointCircle(int x, int y, int radius, const Stylizer& style) {
267 DrawPointEllipse(x, y, radius, radius, style);
268}
269
270/// @brief 繪製一個由盲文點組成的實心圓形。
271/// @param x 圓心點的 x 座標。
272/// @param y 圓心點的 y 座標。
273/// @param radius 圓形的半徑。 {
274 DrawPointCircleFilled(x, y, radius, [](Pixel& /*pixel*/) {});
275}
276
277/// @brief 繪製一個由盲文點組成的實心圓形。
278/// @param x 圓心點的 x 座標。
279/// @param y 圓心點的 y 座標。
280/// @param radius 圓形的半徑。
281/// @param color 圓形的顏色。
282void Canvas::DrawPointCircleFilled(int x,
283 int y,
284 int radius,
285 const Color& color) {
286 DrawPointCircleFilled(x, y, radius,
287 [color](Pixel& p) { p.foreground_color = color; });
288}
289
290/// @brief 繪製一個由盲文點組成的實心圓形。
291/// @param x 圓心點的 x 座標。
292/// @param y 圓心點的 y 座標。
293/// @param radius 圓形的半徑。
294/// @param style 圓形的樣式。
295void Canvas::DrawPointCircleFilled(int x,
296 int y,
297 int radius,
298 const Stylizer& style) {
299 DrawPointEllipseFilled(x, y, radius, radius, style);
300}
301
302/// @brief 繪製一個由盲文點組成的橢圓。
303/// @param x 橢圓中心的 x 座標。
304/// @param y 橢圓中心的 y 座標。
305/// @param r1 沿 x 軸的橢圓半徑。
306/// @param r2 沿 y 軸的橢圓半徑。
307void Canvas::DrawPointEllipse(int x, int y, int r1, int r2) {
308 DrawPointEllipse(x, y, r1, r2, [](Pixel& /*pixel*/) {});
309}
310
311/// @brief 繪製一個由盲文點組成的橢圓。
312/// @param x 橢圓中心的 x 座標。
313/// @param y 橢圓中心的 y 座標。
314/// @param r1 沿 x 軸的橢圓半徑。
315/// @param r2 沿 y 軸的橢圓半徑。
316/// @param color 橢圓的顏色。
317void Canvas::DrawPointEllipse(int x,
318 int y,
319 int r1,
320 int r2,
321 const Color& color) {
322 DrawPointEllipse(x, y, r1, r2,
323 [color](Pixel& p) { p.foreground_color = color; });
324}
325
326/// @brief 繪製一個由盲文點組成的橢圓。
327/// @param x1 橢圓中心的 x 座標。
328/// @param y1 橢圓中心的 y 座標。
329/// @param r1 沿 x 軸的橢圓半徑。
330/// @param r2 沿 y 軸的橢圓半徑。
331/// @param s 橢圓的樣式。
332void Canvas::DrawPointEllipse(int x1,
333 int y1,
334 int r1,
335 int r2,
336 const Stylizer& s) {
337 int x = -r1;
338 int y = 0;
339 int e2 = r2;
340 int dx = (1 + 2 * x) * e2 * e2;
341 int dy = x * x;
342 int err = dx + dy;
343
344 do { // NOLINT
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);
349 e2 = 2 * err;
350 if (e2 >= dx) {
351 x++;
352 err += dx += 2 * r2 * r2;
353 }
354 if (e2 <= dy) {
355 y++;
356 err += dy += 2 * r1 * r1;
357 }
358 } while (x <= 0);
359
360 while (y++ < r2) {
361 DrawPoint(x1, y1 + y, true, s);
362 DrawPoint(x1, y1 - y, true, s);
363 }
364}
365
366/// @brief 繪製一個由盲文點組成的實心橢圓。
367/// @param x1 橢圓中心的 x 座標。
368/// @param y1 橢圓中心的 y 座標。
369/// @param r1 沿 x 軸的橢圓半徑。
370/// @param r2 沿 y 軸的橢圓半徑。
371void Canvas::DrawPointEllipseFilled(int x1, int y1, int r1, int r2) {
372 DrawPointEllipseFilled(x1, y1, r1, r2, [](Pixel& /*pixel*/) {});
373}
374
375/// @brief 繪製一個由盲文點組成的實心橢圓。
376/// @param x1 橢圓中心的 x 座標。
377/// @param y1 橢圓中心的 y 座標。
378/// @param r1 沿 x 軸的橢圓半徑。
379/// @param r2 沿 y 軸的橢圓半徑。
380/// @param color 橢圓的顏色。
381void Canvas::DrawPointEllipseFilled(int x1,
382 int y1,
383 int r1,
384 int r2,
385 const Color& color) {
386 DrawPointEllipseFilled(x1, y1, r1, r2,
387 [color](Pixel& p) { p.foreground_color = color; });
388}
389
390/// @brief 繪製一個由盲文點組成的實心橢圓。
391/// @param x1 橢圓中心的 x 座標。
392/// @param y1 橢圓中心的 y 座標。
393/// @param r1 沿 x 軸的橢圓半徑。
394/// @param r2 沿 y 軸的橢圓半徑。
395/// @param s 橢圓的樣式。
396void Canvas::DrawPointEllipseFilled(int x1,
397 int y1,
398 int r1,
399 int r2,
400 const Stylizer& s) {
401 int x = -r1;
402 int y = 0;
403 int e2 = r2;
404 int dx = (1 + 2 * x) * e2 * e2;
405 int dy = x * x;
406 int err = dx + dy;
407
408 do { // NOLINT
409 for (int xx = x1 + x; xx <= x1 - x; ++xx) {
410 DrawPoint(xx, y1 + y, true, s);
411 DrawPoint(xx, y1 - y, true, s);
412 }
413 e2 = 2 * err;
414 if (e2 >= dx) {
415 x++;
416 err += dx += 2 * r2 * r2;
417 }
418 if (e2 <= dy) {
419 y++;
420 err += dy += 2 * r1 * r1;
421 }
422 } while (x <= 0);
423
424 while (y++ < r2) {
425 for (int yy = y1 - y; yy <= y1 + y; ++yy) {
426 DrawPoint(x1, yy, true, s);
427 }
428 }
429}
430
431/// @brief 繪製一個區塊。
432/// @param x 區塊的 x 座標。
433/// @param y 區塊的 y 座標。
434/// @param value 區塊是否填滿。
435void Canvas::DrawBlock(int x, int y, bool value) {
436 DrawBlock(x, y, value, [](Pixel& /*pixel*/) {});
437}
438
439/// @brief 繪製一個區塊。
440/// @param x 區塊的 x 座標。
441/// @param y 區塊的 y 座標。
442/// @param value 區塊是否填滿。
443/// @param color 區塊的顏色。
444void Canvas::DrawBlock(int x, int y, bool value, const Color& color) {
445 DrawBlock(x, y, value, [color](Pixel& p) { p.foreground_color = color; });
446}
447
448/// @brief 繪製一個區塊。
449/// @param x 區塊的 x 座標。
450/// @param y 區塊的 y 座標。
451/// @param value 區塊是否填滿。
452/// @param style 區塊的樣式。
453void Canvas::DrawBlock(int x, int y, bool value, const Stylizer& style) {
454 Style(x, y, style);
455 if (value) {
456 DrawBlockOn(x, y);
457 } else {
458 DrawBlockOff(x, y);
459 }
460}
461
462/// @brief 繪製一個區塊。
463/// @param x 區塊的 x 座標。
464/// @param y 區塊的 y 座標。
465void Canvas::DrawBlockOn(int x, int y) {
466 if (!IsIn(x, y)) {
467 return;
468 }
469 y /= 2;
470 Cell& cell = storage_[XY{x / 2, y / 2}];
471 if (cell.type != CellType::kBlock) {
472 cell.content.character = " ";
473 cell.type = CellType::kBlock;
474 }
475
476 const uint8_t bit = (x % 2) * 2 + y % 2;
477 uint8_t value = g_map_block_inversed.at(cell.content.character);
478 value |= 1U << bit;
479 cell.content.character = g_map_block[value];
480}
481
482/// @brief 擦除一個區塊。
483/// @param x 區塊的 x 座標。
484/// @param y 區塊的 y 座標。
485void Canvas::DrawBlockOff(int x, int y) {
486 if (!IsIn(x, y)) {
487 return;
488 }
489 Cell& cell = storage_[XY{x / 2, y / 4}];
490 if (cell.type != CellType::kBlock) {
491 cell.content.character = " ";
492 cell.type = CellType::kBlock;
493 }
494 y /= 2;
495
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];
500}
501
502/// @brief 切換一個區塊。如果它已填滿,將被擦除;如果為空,將被填滿。
503/// @param x 區塊的 x 座標。
504/// @param y 區塊的 y 座標。
505void Canvas::DrawBlockToggle(int x, int y) {
506 if (!IsIn(x, y)) {
507 return;
508 }
509 Cell& cell = storage_[XY{x / 2, y / 4}];
510 if (cell.type != CellType::kBlock) {
511 cell.content.character = " ";
512 cell.type = CellType::kBlock;
513 }
514 y /= 2;
515
516 const uint8_t bit = (y % 2) * 2 + x % 2;
517 uint8_t value = g_map_block_inversed.at(cell.content.character);
518 value ^= 1U << bit;
519 cell.content.character = g_map_block[value];
520}
521
522/// @brief 繪製一條由區塊字元組成的線條。
523/// @param x1 線條第一個點的 x 座標。
524/// @param y1 線條第一個點的 y 座標。
525/// @param x2 線條第二個點的 x 座標。
526/// @param y2 線條第二個點的 y 座標。
527void Canvas::DrawBlockLine(int x1, int y1, int x2, int y2) {
528 DrawBlockLine(x1, y1, x2, y2, [](Pixel& /*pixel*/) {});
529}
530
531/// @brief 繪製一條由區塊字元組成的線條。
532/// @param x1 線條第一個點的 x 座標。
533/// @param y1 線條第一個點的 y 座標。
534/// @param x2 線條第二個點的 x 座標。
535/// @param y2 線條第二個點的 y 座標。
536/// @param color 線條的顏色。
537void Canvas::DrawBlockLine(int x1, int y1, int x2, int y2, const Color& color) {
538 DrawBlockLine(x1, y1, x2, y2,
539 [color](Pixel& p) { p.foreground_color = color; });
540}
541
542/// @brief 繪製一條由區塊字元組成的線條。
543/// @param x1 線條第一個點的 x 座標。
544/// @param y1 線條第一個點的 y 座標。
545/// @param x2 線條第二個點的 x 座標。
546/// @param y2 線條第二個點的 y 座標。
547/// @param style 線條的樣式。
548void Canvas::DrawBlockLine(int x1,
549 int y1,
550 int x2,
551 int y2,
552 const Stylizer& style) {
553 y1 /= 2;
554 y2 /= 2;
555
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);
561
562 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
563 return;
564 }
565 if (dx + dx > width_ * height_) {
566 return;
567 }
568
569 int error = dx - dy;
570 for (int i = 0; i < length; ++i) {
571 DrawBlock(x1, y1 * 2, true, style);
572 if (2 * error >= -dy) {
573 error -= dy;
574 x1 += sx;
575 }
576 if (2 * error <= dx) {
577 error += dx;
578 y1 += sy;
579 }
580 }
581 DrawBlock(x2, y2 * 2, true, style);
582}
583
584/// @brief 繪製一個由區塊字元組成的圓形。
585/// @param x 圓心點的 x 座標。
586/// @param y 圓心點的 y 座標。
587/// @param radius 圓形的半徑。
588void Canvas::DrawBlockCircle(int x, int y, int radius) {
589 DrawBlockCircle(x, y, radius, nostyle);
590}
591
592/// @brief 繪製一個由區塊字元組成的圓形。
593/// @param x 圓心點的 x 座標。
594/// @param y 圓心點的 y 座標。
595/// @param radius 圓形的半徑。
596/// @param color 圓形的顏色。
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; });
600}
601
602/// @brief 繪製一個由區塊字元組成的圓形。
603/// @param x 圓心點的 x 座標。
604/// @param y 圓心點的 y 座標。
605/// @param radius 圓形的半徑。
606/// @param style 圓形的樣式。
607void Canvas::DrawBlockCircle(int x, int y, int radius, const Stylizer& style) {
608 DrawBlockEllipse(x, y, radius, radius, style);
609}
610
611/// @brief 繪製一個由區塊字元組成的實心圓形。
612/// @param x 圓心點的 x 座標。
613/// @param y 圓心點的 y 座標。
614/// @param radius 圓形的半徑。
615void Canvas::DrawBlockCircleFilled(int x, int y, int radius) {
616 DrawBlockCircleFilled(x, y, radius, nostyle);
617}
618
619/// @brief 繪製一個由區塊字元組成的實心圓形。
620/// @param x 圓心點的 x 座標。
621/// @param y 圓心點的 y 座標。
622/// @param radius 圓形的半徑。
623/// @param color 圓形的顏色。
624void Canvas::DrawBlockCircleFilled(int x,
625 int y,
626 int radius,
627 const Color& color) {
628 DrawBlockCircleFilled(x, y, radius,
629 [color](Pixel& p) { p.foreground_color = color; });
630}
631
632/// @brief 繪製一個由區塊字元組成的實心圓形。
633/// @param x 圓心點的 x 座標。
634/// @param y 圓心點的 y 座標。
635/// @param radius 圓形的半徑。
636/// @param s 圓形的樣式。
637void Canvas::DrawBlockCircleFilled(int x,
638 int y,
639 int radius,
640 const Stylizer& s) {
641 DrawBlockEllipseFilled(x, y, radius, radius, s);
642}
643
644/// @brief 繪製一個由區塊字元組成的橢圓。
645/// @param x 橢圓中心的 x 座標。
646/// @param y 橢圓中心的 y 座標。
647/// @param r1 沿 x 軸的橢圓半徑。
648/// @param r2 沿 y 軸的橢圓半徑。
649void Canvas::DrawBlockEllipse(int x, int y, int r1, int r2) {
650 DrawBlockEllipse(x, y, r1, r2, nostyle);
651}
652
653/// @brief 繪製一個由區塊字元組成的橢圓。
654/// @param x 橢圓中心的 x 座標。
655/// @param y 橢圓中心的 y 座標。
656/// @param r1 沿 x 軸的橢圓半徑。
657/// @param r2 沿 y 軸的橢圓半徑。
658/// @param color 橢圓的顏色。
659void Canvas::DrawBlockEllipse(int x,
660 int y,
661 int r1,
662 int r2,
663 const Color& color) {
664 DrawBlockEllipse(x, y, r1, r2,
665 [color](Pixel& p) { p.foreground_color = color; });
666}
667
668/// @brief 繪製一個由區塊字元組成的橢圓。
669/// @param x1 橢圓中心的 x 座標。
670/// @param y1 橢圓中心的 y 座標。
671/// @param r1 沿 x 軸的橢圓半徑。
672/// @param r2 沿 y 軸的橢圓半徑。
673/// @param s 橢圓的樣式。
674void Canvas::DrawBlockEllipse(int x1,
675 int y1,
676 int r1,
677 int r2,
678 const Stylizer& s) {
679 y1 /= 2;
680 r2 /= 2;
681 int x = -r1;
682 int y = 0;
683 int e2 = r2;
684 int dx = (1 + 2 * x) * e2 * e2;
685 int dy = x * x;
686 int err = dx + dy;
687
688 do { // NOLINT
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);
693 e2 = 2 * err;
694 if (e2 >= dx) {
695 x++;
696 err += dx += 2 * r2 * r2;
697 }
698 if (e2 <= dy) {
699 y++;
700 err += dy += 2 * r1 * r1;
701 }
702 } while (x <= 0);
703
704 while (y++ < r2) {
705 DrawBlock(x1, 2 * (y1 + y), true, s);
706 DrawBlock(x1, 2 * (y1 - y), true, s);
707 }
708}
709
710/// @brief 繪製一個由區塊字元組成的實心橢圓。
711/// @param x 橢圓中心的 x 座標。
712/// @param y 橢圓中心的 y 座標。
713/// @param r1 沿 x 軸的橢圓半徑。
714/// @param r2 沿 y 軸的橢圓半徑。
715void Canvas::DrawBlockEllipseFilled(int x, int y, int r1, int r2) {
716 DrawBlockEllipseFilled(x, y, r1, r2, nostyle);
717}
718
719/// @brief 繪製一個由區塊字元組成的實心橢圓。
720/// @param x 橢圓中心的 x 座標。
721/// @param y 橢圓中心的 y 座標。
722/// @param r1 沿 x 軸的橢圓半徑。
723/// @param r2 沿 y 軸的橢圓半徑。
724/// @param color 橢圓的顏色。
725void Canvas::DrawBlockEllipseFilled(int x,
726 int y,
727 int r1,
728 int r2,
729 const Color& color) {
730 DrawBlockEllipseFilled(x, y, r1, r2,
731 [color](Pixel& p) { p.foreground_color = color; });
732}
733
734/// @brief 繪製一個由區塊字元組成的實心橢圓。
735/// @param x1 橢圓中心的 x 座標。
736/// @param y1 橢圓中心的 y 座標。
737/// @param r1 沿 x 軸的橢圓半徑。
738/// @param r2 沿 y 軸的橢圓半徑。
739/// @param s 橢圓的樣式。
740void Canvas::DrawBlockEllipseFilled(int x1,
741 int y1,
742 int r1,
743 int r2,
744 const Stylizer& s) {
745 y1 /= 2;
746 r2 /= 2;
747 int x = -r1;
748 int y = 0;
749 int e2 = r2;
750 int dx = (1 + 2 * x) * e2 * e2;
751 int dy = x * x;
752 int err = dx + dy;
753
754 do { // NOLINT
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);
758 }
759 e2 = 2 * err;
760 if (e2 >= dx) {
761 x++;
762 err += dx += 2 * r2 * r2;
763 }
764 if (e2 <= dy) {
765 y++;
766 err += dy += 2 * r1 * r1;
767 }
768 } while (x <= 0);
769
770 while (y++ < r2) {
771 for (int yy = y1 + y; yy <= y1 - y; ++yy) {
772 DrawBlock(x1, 2 * yy, true, s);
773 }
774 }
775}
776
777/// @brief 繪製一段文字。
778/// @param x 文字的 x 座標。
779/// @param y 文字的 y 座標。
780/// @param value 要繪製的文字。
781void Canvas::DrawText(int x, int y, const std::string& value) {
782 DrawText(x, y, value, nostyle);
783}
784
785/// @brief 繪製一段文字。
786/// @param x 文字的 x 座標。
787/// @param y 文字的 y 座標。
788/// @param value 要繪製的文字。
789/// @param color 文字的顏色。
790void Canvas::DrawText(int x,
791 int y,
792 const std::string& value,
793 const Color& color) {
794 DrawText(x, y, value, [color](Pixel& p) { p.foreground_color = color; });
795}
796
797/// @brief 繪製一段文字。
798/// @param x 文字的 x 座標。
799/// @param y 文字的 y 座標。
800/// @param value 要繪製的文字。
801/// @param style 文字的樣式。
802void Canvas::DrawText(int x,
803 int y,
804 const std::string& value,
805 const Stylizer& style) {
806 for (const auto& it : Utf8ToGlyphs(value)) {
807 if (!IsIn(x, y)) {
808 x += 2;
809 continue;
810 }
811 Cell& cell = storage_[XY{x / 2, y / 4}];
812 cell.type = CellType::kCell;
813 cell.content.character = it;
814 style(cell.content);
815 x += 2;
816 }
817}
818
819/// @brief 直接在給定座標繪製一個預定義的像素。
820/// @param x 像素的 x 座標。
821/// @param y 像素的 y 座標。
822/// @param p 要繪製的像素。
823void Canvas::DrawPixel(int x, int y, const Pixel& p) {
824 Cell& cell = storage_[XY{x / 2, y / 4}];
825 cell.type = CellType::kCell;
826 cell.content = p;
827}
828
829/// @brief 繪製一個預定義的圖像,左上角位於給定座標。
830/// 您可以提供負座標來隨意對齊圖像 - 只會繪製「可見」部分。
831/// @param x 圖像左上角對應的 x 座標。
832/// @param y 圖像左上角對應的 y 座標。
833/// @param image 要繪製的圖像。
834void Canvas::DrawImage(int x, int y, const Image& image) {
835 x /= 2;
836 y /= 4;
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);
841
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{
845 x + dx,
846 y + dy,
847 }];
848 cell.type = CellType::kCell;
849 cell.content = image.PixelAt(dx, dy);
850 }
851 }
852}
853
854/// @brief 修改給定位置的像素。
855/// @param style 修改像素的函式。
856void Canvas::Style(int x, int y, const Stylizer& style) {
857 if (IsIn(x, y)) {
858 style(storage_[XY{x / 2, y / 4}].content);
859 }
860}
861
862namespace {
863
864class CanvasNodeBase : public Node {
865 public:
866 CanvasNodeBase() = default;
867
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);
875 }
876 }
877 }
878
879 virtual const Canvas& canvas() = 0;
880};
881
882} // namespace
883
884/// @brief 從 Canvas 或對 Canvas 的參考中產生一個元素。
885// NOLINTNEXTLINE
886Element canvas(ConstRef<Canvas> canvas) {
887 class Impl : public CanvasNodeBase {
888 public:
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;
892 }
893 const Canvas& canvas() final { return *canvas_; }
894 ConstRef<Canvas> canvas_;
895 };
896 return std::make_shared<Impl>(canvas);
897}
898
899/// @brief 從指定大小的畫布中產生一個繪製畫布的元素。
900/// @param width 畫布的寬度。
901/// @param height 畫布的高度。
902/// @param fn 繪製畫布的函式。
903Element canvas(int width, int height, std::function<void(Canvas&)> fn) {
904 class Impl : public CanvasNodeBase {
905 public:
906 Impl(int width, int height, std::function<void(Canvas&)> fn)
907 : width_(width), height_(height), fn_(std::move(fn)) {}
908
909 void ComputeRequirement() final {
910 requirement_.min_x = (width_ + 1) / 2;
911 requirement_.min_y = (height_ + 3) / 4;
912 }
913
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);
918 fn_(canvas_);
919 CanvasNodeBase::Render(screen);
920 }
921
922 const Canvas& canvas() final { return canvas_; }
923 Canvas canvas_;
924 int width_;
925 int height_;
926 std::function<void(Canvas&)> fn_;
927 };
928 return std::make_shared<Impl>(width, height, std::move(fn));
929}
930
931/// @brief 從繪製畫布的函式中產生一個元素。
932/// @param fn 繪製畫布的函式。
933Element canvas(std::function<void(Canvas&)> fn) {
934 const int default_dim = 12;
935 return canvas(default_dim, default_dim, std::move(fn));
936}
937
938} // namespace ftxui
ButtonOption Style()
void DrawPointLine(int x1, int y1, int x2, int y2)
繪製一條由盲文點組成的線條。
Canvas()=default
std::function< void(Pixel &)> Stylizer
Definition canvas.hpp:45
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 上顯示元素。
Definition node.cpp:82
Decorator color(Color)
使用前景顏色進行裝飾。
int dimy() const
Definition image.hpp:36
Color foreground_color
Definition pixel.hpp:48
std::string character
Definition pixel.hpp:44
Pixel & PixelAt(int x, int y)
存取給定位置的單元格 (Pixel)。
Definition image.cpp:43
int dimx() const
Definition image.hpp:35
Color 是一個在終端使用者介面中表示顏色的類別。
Definition color.hpp:20
像素的矩形網格。
Definition image.hpp:17
一個 Unicode 字元及其相關樣式。
Definition pixel.hpp:14
FTXUI 的 ftxui:: 命名空間
Definition animation.hpp:10
DrawPointCircleFilled(x, y, radius, [](Pixel &) {})
繪製一個由盲文點組成的實心圓形。
Element canvas(ConstRef< Canvas > canvas)
從 Canvas 或對 Canvas 的參考中產生一個元素。