FTXUI 7.0.3
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// 在 Windows 上,DrawText 是 windows.h 中定義的一個巨集。這與我們
7// 在單一編譯單元下建置時的 Canvas::DrawText 方法有衝突。
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// 基礎 UTF8 樣式:
38// 11100010 10100000 10000000 // 空白
39
40// 個別點的樣式:
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 建構函式。
94/// @param width 畫布的寬度。一個單元格是 2x4 的盲文點。
95/// @param height 畫布的高度。一個單元格是 2x4 的盲文點。
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 取得單元格的內容。
103/// @param x 單元格的 x 座標。
104/// @param y 單元格的 y 座標。
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 繪製一個盲文點。
111/// @param x 點的 x 座標。
112/// @param y 點的 y 座標。
113/// @param value 點是否填滿。
114void Canvas::DrawPoint(int x, int y, bool value) {
115 DrawPoint(x, y, value, [](Cell& /*pixel*/) {});
116}
117
118/// @brief 繪製一個盲文點。
119/// @param x 點的 x 座標。
120/// @param y 點的 y 座標。
121/// @param value 點是否填滿。
122/// @param color 點的顏色。
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 繪製一個盲文點。
128/// @param x 點的 x 座標。
129/// @param y 點的 y 座標。
130/// @param value 點是否填滿。
131/// @param style 單元格的樣式。
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 繪製一個盲文點。
142/// @param x 點的 x 座標。
143/// @param y 點的 y 座標。
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 個位元組。
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 擦除一個盲文點。
159/// @param x 點的 x 座標。
160/// @param y 點的 y 座標。
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 切換一個盲文點。如果它已填滿,將被擦除;如果為空,則繪製它。
176/// @param x 點的 x 座標。
177/// @param y 點的 y 座標。
178void Canvas::DrawPointToggle(int x, int y) {
179 if (!IsIn(x, y)) {
180 return;
181 }
182 CanvasCell& cell = storage_[XY{x / 2, y / 4}];
183 if (cell.type != CellType::kBraille) {
184 cell.content.character = "⠀"; // 3 byt
185 cell.type = CellType::kBraille;
186 }
187
188 cell.content.character[1] ^= g_map_braille[x % 2][y % 4][0]; // NOLINT
189 cell.content.character[2] ^= g_map_braille[x % 2][y % 4][1]; // NOLINT
190}
191
192/// @brief 繪製一條由盲文點組成的線條。
193/// @param x1 第一個點的 x 座標。
194/// @param y1 第一個點的 y 座標。
195/// @param x2 第二個點的 x 座標。
196/// @param y2 第二個點的 y 座標。
197void Canvas::DrawPointLine(int x1, int y1, int x2, int y2) {
198 DrawPointLine(x1, y1, x2, y2, [](Cell& /*pixel*/) {});
199}
200
201/// @brief 繪製一條由盲文點組成的線條。
202/// @param x1 第一個點的 x 座標。
203/// @param y1 第一個點的 y 座標。
204/// @param x2 第二個點的 x 座標。
205/// @param y2 第二個點的 y 座標。
206/// @param color 線條的顏色。
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; });
210}
211
212/// @brief 繪製一條由盲文點組成的線條。
213/// @param x1 第一個點的 x 座標。
214/// @param y1 第一個點的 y 座標。
215/// @param x2 第二個點的 x 座標。
216/// @param y2 第二個點的 y 座標。
217/// @param style 線條的樣式。
218void Canvas::DrawPointLine(int x1,
219 int y1,
220 int x2,
221 int y2,
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);
228
229 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
230 return;
231 }
232 if (dx + dx > width_ * height_) {
233 return;
234 }
235
236 int error = dx - dy;
237 for (int i = 0; i < length; ++i) {
238 DrawPoint(x1, y1, true, style);
239 if (2 * error >= -dy) {
240 error -= dy;
241 x1 += sx;
242 }
243 if (2 * error <= dx) {
244 error += dx;
245 y1 += sy;
246 }
247 }
248 DrawPoint(x2, y2, true, style);
249}
250
251/// @brief 繪製一個由盲文點組成的圓形。
252/// @param x 圓心點的 x 座標。
253/// @param y 圓心點的 y 座標。
254/// @param radius 圓形的半徑。
255void Canvas::DrawPointCircle(int x, int y, int radius) {
256 DrawPointCircle(x, y, radius, [](Cell& /*pixel*/) {});
257}
258
259/// @brief 繪製一個由盲文點組成的圓形。
260/// @param x 圓心點的 x 座標。
261/// @param y 圓心點的 y 座標。
262/// @param radius 圓形的半徑。
263/// @param color 圓形的顏色。
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; });
267}
268
269/// @brief 繪製一個由盲文點組成的圓形。
270/// @param x 圓心點的 x 座標。
271/// @param y 圓心點的 y 座標。
272/// @param radius 圓形的半徑。
273/// @param style 圓形的樣式。
274void Canvas::DrawPointCircle(int x, int y, int radius, const Stylizer& style) {
275 DrawPointEllipse(x, y, radius, radius, style);
276}
277
278/// @brief 繪製一個由盲文點組成的實心圓形。
279/// @param x 圓心點的 x 座標。
280/// @param y 圓心點的 y 座標。
281/// @param radius 圓形的半徑。 {
282void Canvas::DrawPointCircleFilled(int x, int y, int radius) {
283 DrawPointCircleFilled(x, y, radius, [](Cell& /*pixel*/) {});
284}
285
286/// @brief 繪製一個由盲文點組成的實心圓形。
287/// @param x 圓心點的 x 座標。
288/// @param y 圓心點的 y 座標。
289/// @param radius 圓形的半徑。
290/// @param color 圓形的顏色。
291void Canvas::DrawPointCircleFilled(int x,
292 int y,
293 int radius,
294 const Color& color) {
295 DrawPointCircleFilled(x, y, radius,
296 [color](Cell& p) { p.foreground_color = color; });
297}
298
299/// @brief 繪製一個由盲文點組成的實心圓形。
300/// @param x 圓心點的 x 座標。
301/// @param y 圓心點的 y 座標。
302/// @param radius 圓形的半徑。
303/// @param style 圓形的樣式。
304void Canvas::DrawPointCircleFilled(int x,
305 int y,
306 int radius,
307 const Stylizer& style) {
308 DrawPointEllipseFilled(x, y, radius, radius, style);
309}
310
311/// @brief 繪製一個由盲文點組成的橢圓。
312/// @param x 橢圓中心的 x 座標。
313/// @param y 橢圓中心的 y 座標。
314/// @param r1 沿 x 軸的橢圓半徑。
315/// @param r2 沿 y 軸的橢圓半徑。
316void Canvas::DrawPointEllipse(int x, int y, int r1, int r2) {
317 DrawPointEllipse(x, y, r1, r2, [](Cell& /*pixel*/) {});
318}
319
320/// @brief 繪製一個由盲文點組成的橢圓。
321/// @param x 橢圓中心的 x 座標。
322/// @param y 橢圓中心的 y 座標。
323/// @param r1 沿 x 軸的橢圓半徑。
324/// @param r2 沿 y 軸的橢圓半徑。
325/// @param color 橢圓的顏色。
326void Canvas::DrawPointEllipse(int x,
327 int y,
328 int r1,
329 int r2,
330 const Color& color) {
331 DrawPointEllipse(x, y, r1, r2,
332 [color](Cell& p) { p.foreground_color = color; });
333}
334
335/// @brief 繪製一個由盲文點組成的橢圓。
336/// @param x1 橢圓中心的 x 座標。
337/// @param y1 橢圓中心的 y 座標。
338/// @param r1 沿 x 軸的橢圓半徑。
339/// @param r2 沿 y 軸的橢圓半徑。
340/// @param s 橢圓的樣式。
341void Canvas::DrawPointEllipse(int x1,
342 int y1,
343 int r1,
344 int r2,
345 const Stylizer& s) {
346 int x = -r1;
347 int y = 0;
348 int e2 = r2;
349 int dx = (1 + 2 * x) * e2 * e2;
350 int dy = x * x;
351 int err = dx + dy;
352
353 do { // NOLINT
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);
358 e2 = 2 * err;
359 if (e2 >= dx) {
360 x++;
361 err += dx += 2 * r2 * r2;
362 }
363 if (e2 <= dy) {
364 y++;
365 err += dy += 2 * r1 * r1;
366 }
367 } while (x <= 0);
368
369 while (y++ < r2) {
370 DrawPoint(x1, y1 + y, true, s);
371 DrawPoint(x1, y1 - y, true, s);
372 }
373}
374
375/// @brief 繪製一個由盲文點組成的實心橢圓。
376/// @param x1 橢圓中心的 x 座標。
377/// @param y1 橢圓中心的 y 座標。
378/// @param r1 沿 x 軸的橢圓半徑。
379/// @param r2 沿 y 軸的橢圓半徑。
380void Canvas::DrawPointEllipseFilled(int x1, int y1, int r1, int r2) {
381 DrawPointEllipseFilled(x1, y1, r1, r2, [](Cell& /*pixel*/) {});
382}
383
384/// @brief 繪製一個由盲文點組成的實心橢圓。
385/// @param x1 橢圓中心的 x 座標。
386/// @param y1 橢圓中心的 y 座標。
387/// @param r1 沿 x 軸的橢圓半徑。
388/// @param r2 沿 y 軸的橢圓半徑。
389/// @param color 橢圓的顏色。
390void Canvas::DrawPointEllipseFilled(int x1,
391 int y1,
392 int r1,
393 int r2,
394 const Color& color) {
395 DrawPointEllipseFilled(x1, y1, r1, r2,
396 [color](Cell& p) { p.foreground_color = color; });
397}
398
399/// @brief 繪製一個由盲文點組成的實心橢圓。
400/// @param x1 橢圓中心的 x 座標。
401/// @param y1 橢圓中心的 y 座標。
402/// @param r1 沿 x 軸的橢圓半徑。
403/// @param r2 沿 y 軸的橢圓半徑。
404/// @param s 橢圓的樣式。
405void Canvas::DrawPointEllipseFilled(int x1,
406 int y1,
407 int r1,
408 int r2,
409 const Stylizer& s) {
410 int x = -r1;
411 int y = 0;
412 int e2 = r2;
413 int dx = (1 + 2 * x) * e2 * e2;
414 int dy = x * x;
415 int err = dx + dy;
416
417 do { // NOLINT
418 for (int xx = x1 + x; xx <= x1 - x; ++xx) {
419 DrawPoint(xx, y1 + y, true, s);
420 DrawPoint(xx, y1 - y, true, s);
421 }
422 e2 = 2 * err;
423 if (e2 >= dx) {
424 x++;
425 err += dx += 2 * r2 * r2;
426 }
427 if (e2 <= dy) {
428 y++;
429 err += dy += 2 * r1 * r1;
430 }
431 } while (x <= 0);
432
433 while (y++ < r2) {
434 for (int yy = y1 - y; yy <= y1 + y; ++yy) {
435 DrawPoint(x1, yy, true, s);
436 }
437 }
438}
439
440/// @brief 繪製一個區塊。
441/// @param x 區塊的 x 座標。
442/// @param y 區塊的 y 座標。
443/// @param value 區塊是否填滿。
444void Canvas::DrawBlock(int x, int y, bool value) {
445 DrawBlock(x, y, value, [](Cell& /*pixel*/) {});
446}
447
448/// @brief 繪製一個區塊。
449/// @param x 區塊的 x 座標。
450/// @param y 區塊的 y 座標。
451/// @param value 區塊是否填滿。
452/// @param color 區塊的顏色。
453void Canvas::DrawBlock(int x, int y, bool value, const Color& color) {
454 DrawBlock(x, y, value, [color](Cell& p) { p.foreground_color = color; });
455}
456
457/// @brief 繪製一個區塊。
458/// @param x 區塊的 x 座標。
459/// @param y 區塊的 y 座標。
460/// @param value 區塊是否填滿。
461/// @param style 區塊的樣式。
462void Canvas::DrawBlock(int x, int y, bool value, const Stylizer& style) {
463 Style(x, y, style);
464 if (value) {
465 DrawBlockOn(x, y);
466 } else {
467 DrawBlockOff(x, y);
468 }
469}
470
471/// @brief 繪製一個區塊。
472/// @param x 區塊的 x 座標。
473/// @param y 區塊的 y 座標。
474void Canvas::DrawBlockOn(int x, int y) {
475 if (!IsIn(x, y)) {
476 return;
477 }
478 y /= 2;
479 CanvasCell& cell = storage_[XY{x / 2, y / 2}];
480 if (cell.type != CellType::kBlock) {
481 cell.content.character = " ";
482 cell.type = CellType::kBlock;
483 }
484
485 const uint8_t bit = (x % 2) * 2 + y % 2;
486 uint8_t value = g_map_block_inversed.at(cell.content.character);
487 value |= 1U << bit;
488 cell.content.character = g_map_block[value];
489}
490
491/// @brief 擦除一個區塊。
492/// @param x 區塊的 x 座標。
493/// @param y 區塊的 y 座標。
494void Canvas::DrawBlockOff(int x, int y) {
495 if (!IsIn(x, y)) {
496 return;
497 }
498 CanvasCell& cell = storage_[XY{x / 2, y / 4}];
499 if (cell.type != CellType::kBlock) {
500 cell.content.character = " ";
501 cell.type = CellType::kBlock;
502 }
503 y /= 2;
504
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];
509}
510
511/// @brief 切換一個區塊。如果它已填滿,將被擦除;如果為空,將被填滿。
512/// @param x 區塊的 x 座標。
513/// @param y 區塊的 y 座標。
514void Canvas::DrawBlockToggle(int x, int y) {
515 if (!IsIn(x, y)) {
516 return;
517 }
518 CanvasCell& cell = storage_[XY{x / 2, y / 4}];
519 if (cell.type != CellType::kBlock) {
520 cell.content.character = " ";
521 cell.type = CellType::kBlock;
522 }
523 y /= 2;
524
525 const uint8_t bit = (y % 2) * 2 + x % 2;
526 uint8_t value = g_map_block_inversed.at(cell.content.character);
527 value ^= 1U << bit;
528 cell.content.character = g_map_block[value];
529}
530
531/// @brief 繪製一條由區塊字元組成的線條。
532/// @param x1 線條第一個點的 x 座標。
533/// @param y1 線條第一個點的 y 座標。
534/// @param x2 線條第二個點的 x 座標。
535/// @param y2 線條第二個點的 y 座標。
536void Canvas::DrawBlockLine(int x1, int y1, int x2, int y2) {
537 DrawBlockLine(x1, y1, x2, y2, [](Cell& /*pixel*/) {});
538}
539
540/// @brief 繪製一條由區塊字元組成的線條。
541/// @param x1 線條第一個點的 x 座標。
542/// @param y1 線條第一個點的 y 座標。
543/// @param x2 線條第二個點的 x 座標。
544/// @param y2 線條第二個點的 y 座標。
545/// @param color 線條的顏色。
546void Canvas::DrawBlockLine(int x1, int y1, int x2, int y2, const Color& color) {
547 DrawBlockLine(x1, y1, x2, y2,
548 [color](Cell& p) { p.foreground_color = color; });
549}
550
551/// @brief 繪製一條由區塊字元組成的線條。
552/// @param x1 線條第一個點的 x 座標。
553/// @param y1 線條第一個點的 y 座標。
554/// @param x2 線條第二個點的 x 座標。
555/// @param y2 線條第二個點的 y 座標。
556/// @param style 線條的樣式。
557void Canvas::DrawBlockLine(int x1,
558 int y1,
559 int x2,
560 int y2,
561 const Stylizer& style) {
562 y1 /= 2;
563 y2 /= 2;
564
565 const int dx = std::abs(x2 - x1);
566 const int dy = std::abs(y2 - y1);
567 const int sx = x1 < x2 ? 1 : -1;
568 const int sy = y1 < y2 ? 1 : -1;
569 const int length = std::max(dx, dy);
570
571 if (!IsIn(x1, y1) && !IsIn(x2, y2)) {
572 return;
573 }
574 if (dx + dx > width_ * height_) {
575 return;
576 }
577
578 int error = dx - dy;
579 for (int i = 0; i < length; ++i) {
580 DrawBlock(x1, y1 * 2, true, style);
581 if (2 * error >= -dy) {
582 error -= dy;
583 x1 += sx;
584 }
585 if (2 * error <= dx) {
586 error += dx;
587 y1 += sy;
588 }
589 }
590 DrawBlock(x2, y2 * 2, true, style);
591}
592
593/// @brief 繪製一個由區塊字元組成的圓形。
594/// @param x 圓心點的 x 座標。
595/// @param y 圓心點的 y 座標。
596/// @param radius 圓形的半徑。
597void Canvas::DrawBlockCircle(int x, int y, int radius) {
598 DrawBlockCircle(x, y, radius, nostyle);
599}
600
601/// @brief 繪製一個由區塊字元組成的圓形。
602/// @param x 圓心點的 x 座標。
603/// @param y 圓心點的 y 座標。
604/// @param radius 圓形的半徑。
605/// @param color 圓形的顏色。
606void Canvas::DrawBlockCircle(int x, int y, int radius, const Color& color) {
607 DrawBlockCircle(x, y, radius,
608 [color](Cell& p) { p.foreground_color = color; });
609}
610
611/// @brief 繪製一個由區塊字元組成的圓形。
612/// @param x 圓心點的 x 座標。
613/// @param y 圓心點的 y 座標。
614/// @param radius 圓形的半徑。
615/// @param style 圓形的樣式。
616void Canvas::DrawBlockCircle(int x, int y, int radius, const Stylizer& style) {
617 DrawBlockEllipse(x, y, radius, radius, style);
618}
619
620/// @brief 繪製一個由區塊字元組成的實心圓形。
621/// @param x 圓心點的 x 座標。
622/// @param y 圓心點的 y 座標。
623/// @param radius 圓形的半徑。
624void Canvas::DrawBlockCircleFilled(int x, int y, int radius) {
625 DrawBlockCircleFilled(x, y, radius, nostyle);
626}
627
628/// @brief 繪製一個由區塊字元組成的實心圓形。
629/// @param x 圓心點的 x 座標。
630/// @param y 圓心點的 y 座標。
631/// @param radius 圓形的半徑。
632/// @param color 圓形的顏色。
633void Canvas::DrawBlockCircleFilled(int x,
634 int y,
635 int radius,
636 const Color& color) {
637 DrawBlockCircleFilled(x, y, radius,
638 [color](Cell& p) { p.foreground_color = color; });
639}
640
641/// @brief 繪製一個由區塊字元組成的實心圓形。
642/// @param x 圓心點的 x 座標。
643/// @param y 圓心點的 y 座標。
644/// @param radius 圓形的半徑。
645/// @param s 圓形的樣式。
646void Canvas::DrawBlockCircleFilled(int x,
647 int y,
648 int radius,
649 const Stylizer& s) {
650 DrawBlockEllipseFilled(x, y, radius, radius, s);
651}
652
653/// @brief 繪製一個由區塊字元組成的橢圓。
654/// @param x 橢圓中心的 x 座標。
655/// @param y 橢圓中心的 y 座標。
656/// @param r1 沿 x 軸的橢圓半徑。
657/// @param r2 沿 y 軸的橢圓半徑。
658void Canvas::DrawBlockEllipse(int x, int y, int r1, int r2) {
659 DrawBlockEllipse(x, y, r1, r2, nostyle);
660}
661
662/// @brief 繪製一個由區塊字元組成的橢圓。
663/// @param x 橢圓中心的 x 座標。
664/// @param y 橢圓中心的 y 座標。
665/// @param r1 沿 x 軸的橢圓半徑。
666/// @param r2 沿 y 軸的橢圓半徑。
667/// @param color 橢圓的顏色。
668void Canvas::DrawBlockEllipse(int x,
669 int y,
670 int r1,
671 int r2,
672 const Color& color) {
673 DrawBlockEllipse(x, y, r1, r2,
674 [color](Cell& p) { p.foreground_color = color; });
675}
676
677/// @brief 繪製一個由區塊字元組成的橢圓。
678/// @param x1 橢圓中心的 x 座標。
679/// @param y1 橢圓中心的 y 座標。
680/// @param r1 沿 x 軸的橢圓半徑。
681/// @param r2 沿 y 軸的橢圓半徑。
682/// @param s 橢圓的樣式。
683void Canvas::DrawBlockEllipse(int x1,
684 int y1,
685 int r1,
686 int r2,
687 const Stylizer& s) {
688 y1 /= 2;
689 r2 /= 2;
690 int x = -r1;
691 int y = 0;
692 int e2 = r2;
693 int dx = (1 + 2 * x) * e2 * e2;
694 int dy = x * x;
695 int err = dx + dy;
696
697 do { // NOLINT
698 DrawBlock(x1 - x, 2 * (y1 + y), true, s);
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 e2 = 2 * err;
703 if (e2 >= dx) {
704 x++;
705 err += dx += 2 * r2 * r2;
706 }
707 if (e2 <= dy) {
708 y++;
709 err += dy += 2 * r1 * r1;
710 }
711 } while (x <= 0);
712
713 while (y++ < r2) {
714 DrawBlock(x1, 2 * (y1 + y), true, s);
715 DrawBlock(x1, 2 * (y1 - y), true, s);
716 }
717}
718
719/// @brief 繪製一個由區塊字元組成的實心橢圓。
720/// @param x 橢圓中心的 x 座標。
721/// @param y 橢圓中心的 y 座標。
722/// @param r1 沿 x 軸的橢圓半徑。
723/// @param r2 沿 y 軸的橢圓半徑。
724void Canvas::DrawBlockEllipseFilled(int x, int y, int r1, int r2) {
725 DrawBlockEllipseFilled(x, y, r1, r2, nostyle);
726}
727
728/// @brief 繪製一個由區塊字元組成的實心橢圓。
729/// @param x 橢圓中心的 x 座標。
730/// @param y 橢圓中心的 y 座標。
731/// @param r1 沿 x 軸的橢圓半徑。
732/// @param r2 沿 y 軸的橢圓半徑。
733/// @param color 橢圓的顏色。
734void Canvas::DrawBlockEllipseFilled(int x,
735 int y,
736 int r1,
737 int r2,
738 const Color& color) {
739 DrawBlockEllipseFilled(x, y, r1, r2,
740 [color](Cell& p) { p.foreground_color = color; });
741}
742
743/// @brief 繪製一個由區塊字元組成的實心橢圓。
744/// @param x1 橢圓中心的 x 座標。
745/// @param y1 橢圓中心的 y 座標。
746/// @param r1 沿 x 軸的橢圓半徑。
747/// @param r2 沿 y 軸的橢圓半徑。
748/// @param s 橢圓的樣式。
749void Canvas::DrawBlockEllipseFilled(int x1,
750 int y1,
751 int r1,
752 int r2,
753 const Stylizer& s) {
754 y1 /= 2;
755 r2 /= 2;
756 int x = -r1;
757 int y = 0;
758 int e2 = r2;
759 int dx = (1 + 2 * x) * e2 * e2;
760 int dy = x * x;
761 int err = dx + dy;
762
763 do { // NOLINT
764 for (int xx = x1 + x; xx <= x1 - x; ++xx) {
765 DrawBlock(xx, 2 * (y1 + y), true, s);
766 DrawBlock(xx, 2 * (y1 - y), true, s);
767 }
768 e2 = 2 * err;
769 if (e2 >= dx) {
770 x++;
771 err += dx += 2 * r2 * r2;
772 }
773 if (e2 <= dy) {
774 y++;
775 err += dy += 2 * r1 * r1;
776 }
777 } while (x <= 0);
778
779 while (y++ < r2) {
780 for (int yy = y1 + y; yy <= y1 - y; ++yy) {
781 DrawBlock(x1, 2 * yy, true, s);
782 }
783 }
784}
785
786/// @brief 繪製一段文字。
787/// @param x 文字的 x 座標。
788/// @param y 文字的 y 座標。
789/// @param value 要繪製的文字。
790void Canvas::DrawText(int x, int y, std::string_view value) {
791 DrawText(x, y, value, nostyle);
792}
793
794/// @brief 繪製一段文字。
795/// @param x 文字的 x 座標。
796/// @param y 文字的 y 座標。
797/// @param value 要繪製的文字。
798/// @param color 文字的顏色。
799void Canvas::DrawText(int x,
800 int y,
801 std::string_view value,
802 const Color& color) {
803 DrawText(x, y, value, [color](Cell& p) { p.foreground_color = color; });
804}
805
806/// @brief 繪製一段文字。
807/// @param x 文字的 x 座標。
808/// @param y 文字的 y 座標。
809/// @param value 要繪製的文字。
810/// @param style 文字的樣式。
811void Canvas::DrawText(int x,
812 int y,
813 std::string_view value,
814 const Stylizer& style) {
815 for (const auto& it : Utf8ToGlyphs(value)) {
816 if (!IsIn(x, y)) {
817 x += 2;
818 continue;
819 }
820 CanvasCell& cell = storage_[XY{x / 2, y / 4}];
821 cell.type = CellType::kCell;
822 cell.content.character = it;
823 style(cell.content);
824 x += 2;
825 }
826}
827
828/// @brief 直接在給定座標繪製一個預定義的像素。
829/// @param x 像素的 x 座標。
830/// @param y 像素的 y 座標。
831/// @param p 要繪製的像素。
832void Canvas::DrawCell(int x, int y, const Cell& p) {
833 CanvasCell& cell = storage_[XY{x / 2, y / 4}];
834 cell.type = CellType::kCell;
835 cell.content = p;
836}
837
838/// @brief 繪製一個預定義的圖像,左上角位於給定座標。
839/// 您可以提供負座標來隨意對齊圖像 - 只會繪製「可見」部分。
840/// @param x 圖像左上角對應的 x 座標。
841/// @param y 圖像左上角對應的 y 座標。
842/// @param image 要繪製的圖像。
843void Canvas::DrawSurface(int x, int y, const Surface& image) {
844 x /= 2;
845 y /= 4;
846 const int dx_begin = std::max(0, -x);
847 const int dy_begin = std::max(0, -y);
848 const int dx_end = std::min(image.dimx(), width_ - x);
849 const int dy_end = std::min(image.dimy(), height_ - y);
850
851 for (int dy = dy_begin; dy < dy_end; ++dy) {
852 for (int dx = dx_begin; dx < dx_end; ++dx) {
853 CanvasCell& cell = storage_[XY{
854 x + dx,
855 y + dy,
856 }];
857 cell.type = CellType::kCell;
858 cell.content = image.CellAt(dx, dy);
859 }
860 }
861}
862
863/// @brief 修改指定位置的像素。
864/// @param x 像素的 x 座標。
865/// @param y 像素的 y 座標。
866/// @param style 修改像素的函式。
867void Canvas::Style(int x, int y, const Stylizer& style) {
868 if (IsIn(x, y)) {
869 style(storage_[XY{x / 2, y / 4}].content);
870 }
871}
872
873namespace {
874
875class CanvasNodeBase : public Node {
876 public:
877 CanvasNodeBase() = default;
878
879 void Render(Screen& screen) override {
880 const Canvas& c = canvas();
881 const int y_max = std::min(c.height() / 4, box_.y_max - box_.y_min + 1);
882 const int x_max = std::min(c.width() / 2, box_.x_max - box_.x_min + 1);
883 for (int y = 0; y < y_max; ++y) {
884 for (int x = 0; x < x_max; ++x) {
885 screen.CellAt(box_.x_min + x, box_.y_min + y) = c.GetCell(x, y);
886 }
887 }
888 }
889
890 virtual const Canvas& canvas() = 0;
891};
892
893} // namespace
894
895/// @brief 從 Canvas 或對 Canvas 的參考中產生一個元素。
896// NOLINTNEXTLINE
898#if defined(__GNUC__) && !defined(__clang__)
899#pragma GCC diagnostic push
900#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
901#endif
902 class Impl : public CanvasNodeBase {
903 public:
904 explicit Impl(ConstRef<Canvas> canvas) : canvas_(std::move(canvas)) {
905 requirement_.min_x = (canvas_->width() + 1) / 2;
906 requirement_.min_y = (canvas_->height() + 3) / 4;
907 }
908 const Canvas& canvas() final { return *canvas_; }
909 ConstRef<Canvas> canvas_;
910 };
911 return std::make_shared<Impl>(canvas);
912#if defined(__GNUC__) && !defined(__clang__)
913#pragma GCC diagnostic pop
914#endif
915}
916
917/// @brief 從指定大小的畫布中產生一個繪製畫布的元素。
918/// @param width 畫布的寬度。
919/// @param height 畫布的高度。
920/// @param fn 繪製畫布的函式。
921Element canvas(int width, int height, std::function<void(Canvas&)> fn) {
922 class Impl : public CanvasNodeBase {
923 public:
924 Impl(int width, int height, std::function<void(Canvas&)> fn)
925 : width_(width), height_(height), fn_(std::move(fn)) {}
926
927 void ComputeRequirement() final {
928 requirement_.min_x = (width_ + 1) / 2;
929 requirement_.min_y = (height_ + 3) / 4;
930 }
931
932 void Render(Screen& screen) final {
933 const int width = (box_.x_max - box_.x_min + 1) * 2;
934 const int height = (box_.y_max - box_.y_min + 1) * 4;
935 canvas_ = Canvas(width, height);
936 fn_(canvas_);
937 CanvasNodeBase::Render(screen);
938 }
939
940 const Canvas& canvas() final { return canvas_; }
941 Canvas canvas_;
942 int width_;
943 int height_;
944 std::function<void(Canvas&)> fn_;
945 };
946 return std::make_shared<Impl>(width, height, std::move(fn));
947}
948
949/// @brief 從繪製畫布的函式中產生一個元素。
950/// @param fn 繪製畫布的函式。
951Element canvas(std::function<void(Canvas&)> fn) {
952 const int default_dim = 12;
953 return canvas(default_dim, default_dim, std::move(fn));
954}
955
956} // namespace ftxui
一個適配器。擁有或引用一個不可變的物件。
Definition ref.hpp:20
ButtonOption Style()
FTXUI ftxui:: 命名空間
Definition animation.hpp:11
std::shared_ptr< Node > Element
Definition elements.hpp:25
int y
Definition elements.hpp:131
void Render(Screen &screen, Node *node, Selection &selection)
Definition node.cpp:105
int value
Definition elements.hpp:192
Element canvas(int width, int height, std::function< void(Canvas &)>)
從指定大小的畫布中產生一個繪製畫布的元素。