FTXUI  5.0.0
C++ functional terminal UI.
border.cpp
Go to the documentation of this file.
1 // Copyright 2020 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.
4 #include <algorithm> // for max
5 #include <array> // for array
6 #include <ftxui/screen/color.hpp> // for Color
7 #include <memory> // for allocator, make_shared, __shared_ptr_access
8 #include <optional> // for optional, nullopt
9 #include <string> // for basic_string, string
10 #include <utility> // for move
11 
12 #include "ftxui/dom/elements.hpp" // for unpack, Element, Decorator, BorderStyle, ROUNDED, borderStyled, Elements, DASHED, DOUBLE, EMPTY, HEAVY, LIGHT, border, borderDashed, borderDouble, borderEmpty, borderHeavy, borderLight, borderRounded, borderWith, window
13 #include "ftxui/dom/node.hpp" // for Node, Elements
14 #include "ftxui/dom/requirement.hpp" // for Requirement
15 #include "ftxui/screen/box.hpp" // for Box
16 #include "ftxui/screen/pixel.hpp" // for Pixel
17 #include "ftxui/screen/screen.hpp" // for Pixel, Screen
18 
19 namespace ftxui {
20 
21 namespace {
22 using Charset = std::array<std::string, 6>; // NOLINT
23 using Charsets = std::array<Charset, 6>; // NOLINT
24 // NOLINTNEXTLINE
25 static Charsets simple_border_charset = {
26  Charset{"┌", "┐", "└", "┘", "─", "│"}, // LIGHT
27  Charset{"┏", "┓", "┗", "┛", "╍", "╏"}, // DASHED
28  Charset{"┏", "┓", "┗", "┛", "━", "┃"}, // HEAVY
29  Charset{"╔", "╗", "╚", "╝", "═", "║"}, // DOUBLE
30  Charset{"╭", "╮", "╰", "╯", "─", "│"}, // ROUNDED
31  Charset{" ", " ", " ", " ", " ", " "}, // EMPTY
32 };
33 
34 // For reference, here is the charset for normal border:
35 class Border : public Node {
36  public:
37  Border(Elements children,
38  BorderStyle style,
39  std::optional<Color> foreground_color = std::nullopt)
40  : Node(std::move(children)),
41  charset_(simple_border_charset[style]) // NOLINT
42  ,
43  foreground_color_(foreground_color) {} // NOLINT
44 
45  const Charset& charset_; // NOLINT
46  std::optional<Color> foreground_color_;
47 
48  void ComputeRequirement() override {
50  requirement_ = children_[0]->requirement();
51  requirement_.min_x += 2;
52  requirement_.min_y += 2;
53  if (children_.size() == 2) {
54  requirement_.min_x =
55  std::max(requirement_.min_x, children_[1]->requirement().min_x + 2);
56  }
57  requirement_.selected_box.x_min++;
58  requirement_.selected_box.x_max++;
59  requirement_.selected_box.y_min++;
60  requirement_.selected_box.y_max++;
61  }
62 
63  void SetBox(Box box) override {
64  Node::SetBox(box);
65  if (children_.size() == 2) {
66  Box title_box;
67  title_box.x_min = box.x_min + 1;
68  title_box.x_max = box.x_max - 1;
69  title_box.y_min = box.y_min;
70  title_box.y_max = box.y_min;
71  children_[1]->SetBox(title_box);
72  }
73  box.x_min++;
74  box.x_max--;
75  box.y_min++;
76  box.y_max--;
77  children_[0]->SetBox(box);
78  }
79 
80  void Render(Screen& screen) override {
81  // Draw content.
82  children_[0]->Render(screen);
83 
84  // Draw the border.
85  if (box_.x_min >= box_.x_max || box_.y_min >= box_.y_max) {
86  return;
87  }
88 
89  screen.at(box_.x_min, box_.y_min) = charset_[0]; // NOLINT
90  screen.at(box_.x_max, box_.y_min) = charset_[1]; // NOLINT
91  screen.at(box_.x_min, box_.y_max) = charset_[2]; // NOLINT
92  screen.at(box_.x_max, box_.y_max) = charset_[3]; // NOLINT
93 
94  for (int x = box_.x_min + 1; x < box_.x_max; ++x) {
95  Pixel& p1 = screen.PixelAt(x, box_.y_min);
96  Pixel& p2 = screen.PixelAt(x, box_.y_max);
97  p1.character = charset_[4]; // NOLINT
98  p2.character = charset_[4]; // NOLINT
99  p1.automerge = true;
100  p2.automerge = true;
101  }
102  for (int y = box_.y_min + 1; y < box_.y_max; ++y) {
103  Pixel& p3 = screen.PixelAt(box_.x_min, y);
104  Pixel& p4 = screen.PixelAt(box_.x_max, y);
105  p3.character = charset_[5]; // NOLINT
106  p4.character = charset_[5]; // NOLINT
107  p3.automerge = true;
108  p4.automerge = true;
109  }
110 
111  // Draw title.
112  if (children_.size() == 2) {
113  children_[1]->Render(screen);
114  }
115 
116  // Draw the border color.
117  if (foreground_color_) {
118  for (int x = box_.x_min; x <= box_.x_max; ++x) {
119  screen.PixelAt(x, box_.y_min).foreground_color = *foreground_color_;
120  screen.PixelAt(x, box_.y_max).foreground_color = *foreground_color_;
121  }
122  for (int y = box_.y_min; y <= box_.y_max; ++y) {
123  screen.PixelAt(box_.x_min, y).foreground_color = *foreground_color_;
124  screen.PixelAt(box_.x_max, y).foreground_color = *foreground_color_;
125  }
126  }
127  }
128 };
129 
130 // For reference, here is the charset for normal border:
131 class BorderPixel : public Node {
132  public:
133  BorderPixel(Elements children, Pixel pixel)
134  : Node(std::move(children)), pixel_(std::move(pixel)) {}
135 
136  private:
137  Pixel pixel_;
138 
139  void ComputeRequirement() override {
141  requirement_ = children_[0]->requirement();
142  requirement_.min_x += 2;
143  requirement_.min_y += 2;
144  if (children_.size() == 2) {
145  requirement_.min_x =
146  std::max(requirement_.min_x, children_[1]->requirement().min_x + 2);
147  }
148  requirement_.selected_box.x_min++;
149  requirement_.selected_box.x_max++;
150  requirement_.selected_box.y_min++;
151  requirement_.selected_box.y_max++;
152  }
153 
154  void SetBox(Box box) override {
155  Node::SetBox(box);
156  if (children_.size() == 2) {
157  Box title_box;
158  title_box.x_min = box.x_min + 1;
159  title_box.x_max = box.x_max - 1;
160  title_box.y_min = box.y_min;
161  title_box.y_max = box.y_min;
162  children_[1]->SetBox(title_box);
163  }
164  box.x_min++;
165  box.x_max--;
166  box.y_min++;
167  box.y_max--;
168  children_[0]->SetBox(box);
169  }
170 
171  void Render(Screen& screen) override {
172  // Draw content.
173  children_[0]->Render(screen);
174 
175  // Draw the border.
176  if (box_.x_min >= box_.x_max || box_.y_min >= box_.y_max) {
177  return;
178  }
179 
180  screen.PixelAt(box_.x_min, box_.y_min) = pixel_;
181  screen.PixelAt(box_.x_max, box_.y_min) = pixel_;
182  screen.PixelAt(box_.x_min, box_.y_max) = pixel_;
183  screen.PixelAt(box_.x_max, box_.y_max) = pixel_;
184 
185  for (int x = box_.x_min + 1; x < box_.x_max; ++x) {
186  screen.PixelAt(x, box_.y_min) = pixel_;
187  screen.PixelAt(x, box_.y_max) = pixel_;
188  }
189  for (int y = box_.y_min + 1; y < box_.y_max; ++y) {
190  screen.PixelAt(box_.x_min, y) = pixel_;
191  screen.PixelAt(box_.x_max, y) = pixel_;
192  }
193  }
194 };
195 } // namespace
196 
197 /// @brief Draw a border around the element.
198 /// @ingroup dom
199 /// @see border
200 /// @see borderLight
201 /// @see borderDashed
202 /// @see borderDouble
203 /// @see borderHeavy
204 /// @see borderEmpty
205 /// @see borderRounded
206 /// @see borderStyled
207 /// @see borderWith
208 ///
209 /// Add a border around an element
210 ///
211 /// ### Example
212 ///
213 /// ```cpp
214 /// // Use 'border' as a function...
215 /// Element document = border(text("The element"));
216 ///
217 /// // ...Or as a 'pipe'.
218 /// Element document = text("The element") | border;
219 /// ```
220 ///
221 /// ### Output
222 ///
223 /// ```bash
224 /// ┌───────────┐
225 /// │The element│
226 /// └───────────┘
227 /// ```
229  return std::make_shared<Border>(unpack(std::move(child)), ROUNDED);
230 }
231 
232 /// @brief Same as border but with a constant Pixel around the element.
233 /// @ingroup dom
234 /// @see border
235 Decorator borderWith(const Pixel& pixel) {
236  return [pixel](Element child) {
237  return std::make_shared<BorderPixel>(unpack(std::move(child)), pixel);
238  };
239 }
240 
241 /// @brief Same as border but with different styles.
242 /// @ingroup dom
243 /// @see border
245  return [style](Element child) {
246  return std::make_shared<Border>(unpack(std::move(child)), style);
247  };
248 }
249 
250 /// @brief Same as border but with a foreground color.
251 /// @ingroup dom
252 /// @see border
253 Decorator borderStyled(Color foreground_color) {
254  return [foreground_color](Element child) {
255  return std::make_shared<Border>(unpack(std::move(child)), ROUNDED,
256  foreground_color);
257  };
258 }
259 
260 /// @brief Same as border but with a foreground color and a different style
261 /// @ingroup dom
262 /// @see border
263 Decorator borderStyled(BorderStyle style, Color foreground_color) {
264  return [style, foreground_color](Element child) {
265  return std::make_shared<Border>(unpack(std::move(child)), style,
266  foreground_color);
267  };
268 }
269 
270 /// @brief Draw a dashed border around the element.
271 /// @ingroup dom
272 /// @see border
273 /// @see borderLight
274 /// @see borderDashed
275 /// @see borderDouble
276 /// @see borderHeavy
277 /// @see borderRounded
278 /// @see borderEmpty
279 /// @see borderStyled
280 /// @see borderWith
281 ///
282 /// Add a border around an element
283 ///
284 /// ### Example
285 ///
286 /// ```cpp
287 /// // Use 'borderDash' as a function...
288 /// Element document = borderDash(text("The element"));
289 ///
290 /// // ...Or as a 'pipe'.
291 /// Element document = text("The element") | borderDAsh;
292 /// ```
293 ///
294 /// ### Output
295 ///
296 /// ```bash
297 /// ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓
298 /// ╏The element ╏
299 /// ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛
300 /// ```
302  return std::make_shared<Border>(unpack(std::move(child)), DASHED);
303 }
304 
305 /// @brief Draw a light border around the element.
306 /// @ingroup dom
307 /// @see border
308 /// @see borderLight
309 /// @see borderDashed
310 /// @see borderDouble
311 /// @see borderHeavy
312 /// @see borderRounded
313 /// @see borderEmpty
314 /// @see borderStyled
315 /// @see borderWith
316 ///
317 /// Add a border around an element
318 ///
319 /// ### Example
320 ///
321 /// ```cpp
322 /// // Use 'borderLight' as a function...
323 /// Element document = borderLight(text("The element"));
324 ///
325 /// // ...Or as a 'pipe'.
326 /// Element document = text("The element") | borderLight;
327 /// ```
328 ///
329 /// ### Output
330 ///
331 /// ```bash
332 /// ┌──────────────┐
333 /// │The element │
334 /// └──────────────┘
335 /// ```
337  return std::make_shared<Border>(unpack(std::move(child)), LIGHT);
338 }
339 
340 /// @brief Draw a heavy border around the element.
341 /// @ingroup dom
342 /// @see border
343 /// @see borderLight
344 /// @see borderDashed
345 /// @see borderDouble
346 /// @see borderHeavy
347 /// @see borderRounded
348 /// @see borderEmpty
349 /// @see borderStyled
350 /// @see borderWith
351 ///
352 /// Add a border around an element
353 ///
354 /// ### Example
355 ///
356 /// ```cpp
357 /// // Use 'borderHeavy' as a function...
358 /// Element document = borderHeavy(text("The element"));
359 ///
360 /// // ...Or as a 'pipe'.
361 /// Element document = text("The element") | borderHeavy;
362 /// ```
363 ///
364 /// ### Output
365 ///
366 /// ```bash
367 /// ┏━━━━━━━━━━━━━━┓
368 /// ┃The element ┃
369 /// ┗━━━━━━━━━━━━━━┛
370 /// ```
372  return std::make_shared<Border>(unpack(std::move(child)), HEAVY);
373 }
374 
375 /// @brief Draw a double border around the element.
376 /// @ingroup dom
377 /// @see border
378 /// @see borderLight
379 /// @see borderDashed
380 /// @see borderDouble
381 /// @see borderHeavy
382 /// @see borderRounded
383 /// @see borderEmpty
384 /// @see borderStyled
385 /// @see borderWith
386 ///
387 /// Add a border around an element
388 ///
389 /// ### Example
390 ///
391 /// ```cpp
392 /// // Use 'borderDouble' as a function...
393 /// Element document = borderDouble(text("The element"));
394 ///
395 /// // ...Or as a 'pipe'.
396 /// Element document = text("The element") | borderDouble;
397 /// ```
398 ///
399 /// ### Output
400 ///
401 /// ```bash
402 /// ╔══════════════╗
403 /// ║The element ║
404 /// ╚══════════════╝
405 /// ```
407  return std::make_shared<Border>(unpack(std::move(child)), DOUBLE);
408 }
409 
410 /// @brief Draw a rounded border around the element.
411 /// @ingroup dom
412 /// @see border
413 /// @see borderLight
414 /// @see borderDashed
415 /// @see borderDouble
416 /// @see borderHeavy
417 /// @see borderRounded
418 /// @see borderEmpty
419 /// @see borderStyled
420 /// @see borderWith
421 ///
422 /// Add a border around an element
423 ///
424 /// ### Example
425 ///
426 /// ```cpp
427 /// // Use 'borderRounded' as a function...
428 /// Element document = borderRounded(text("The element"));
429 ///
430 /// // ...Or as a 'pipe'.
431 /// Element document = text("The element") | borderRounded;
432 /// ```
433 ///
434 /// ### Output
435 ///
436 /// ```bash
437 /// ╭──────────────╮
438 /// │The element │
439 /// ╰──────────────╯
440 /// ```
442  return std::make_shared<Border>(unpack(std::move(child)), ROUNDED);
443 }
444 
445 /// @brief Draw an empty border around the element.
446 /// @ingroup dom
447 /// @see border
448 /// @see borderLight
449 /// @see borderDashed
450 /// @see borderDouble
451 /// @see borderHeavy
452 /// @see borderRounded
453 /// @see borderEmpty
454 /// @see borderStyled
455 /// @see borderWith
456 ///
457 /// Add a border around an element
458 ///
459 /// ### Example
460 ///
461 /// ```cpp
462 /// // Use 'borderRounded' as a function...
463 /// Element document = borderRounded(text("The element"));
464 ///
465 /// // ...Or as a 'pipe'.
466 /// Element document = text("The element") | borderRounded;
467 /// ```
468 ///
469 /// ### Output
470 ///
471 /// ```bash
472 ///
473 /// The element
474 ///
475 /// ```
477  return std::make_shared<Border>(unpack(std::move(child)), EMPTY);
478 }
479 
480 /// @brief Draw window with a title and a border around the element.
481 /// @param title The title of the window.
482 /// @param content The element to be wrapped.
483 /// @param border The style of the border. Default is ROUNDED.
484 /// @ingroup dom
485 /// @see border
486 ///
487 /// ### Example
488 ///
489 /// ```cpp
490 /// Element document = window(text("Title"),
491 /// text("content")
492 /// );
493 ///
494 /// // With specifying border
495 /// Element document = window(text("Title"),
496 /// text("content"),
497 /// ROUNDED
498 /// );
499 /// ```
500 ///
501 /// ### Output
502 ///
503 /// ```bash
504 /// ┌Title──┐
505 /// │content│
506 /// └───────┘
507 /// ```
509  return std::make_shared<Border>(unpack(std::move(content), std::move(title)),
510  border);
511 }
512 } // namespace ftxui
A class representing terminal colors.
Definition: color.hpp:20
virtual void SetBox(Box box)
Assign a position and a dimension to an element for drawing.
Definition: node.cpp:26
virtual void ComputeRequirement()
Compute how much space an elements needs.
Definition: node.cpp:18
Element window(Element title, Element content, BorderStyle border=ROUNDED)
Draw window with a title and a border around the element.
Definition: border.cpp:508
Element borderDouble(Element)
Draw a double border around the element.
Definition: border.cpp:406
std::function< Element(Element)> Decorator
Definition: elements.hpp:24
Element borderDashed(Element)
Draw a dashed border around the element.
Definition: border.cpp:301
std::shared_ptr< Node > Element
Definition: elements.hpp:22
Element borderRounded(Element)
Draw a rounded border around the element.
Definition: border.cpp:441
Element borderHeavy(Element)
Draw a heavy border around the element.
Definition: border.cpp:371
std::vector< Element > Elements
Definition: elements.hpp:23
Element borderLight(Element)
Draw a light border around the element.
Definition: border.cpp:336
Decorator borderWith(const Pixel &)
Same as border but with a constant Pixel around the element.
Definition: border.cpp:235
Decorator borderStyled(BorderStyle)
Same as border but with different styles.
Definition: border.cpp:244
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
Definition: node.cpp:47
Element border(Element)
Draw a border around the element.
Definition: border.cpp:228
Element borderEmpty(Element)
Draw an empty border around the element.
Definition: border.cpp:476
BorderStyle
Definition: elements.hpp:27
@ EMPTY
Definition: elements.hpp:33
@ DOUBLE
Definition: elements.hpp:31
@ HEAVY
Definition: elements.hpp:30
@ ROUNDED
Definition: elements.hpp:32
@ DASHED
Definition: elements.hpp:29
@ LIGHT
Definition: elements.hpp:28
A Unicode character and its associated style.
Definition: pixel.hpp:15