From b5c3b17b3f6c2e5cd27dd209b76d7a2f66e39769 Mon Sep 17 00:00:00 2001 From: Arthur Sonzogni Date: Sun, 12 Sep 2021 00:36:59 +0200 Subject: [PATCH] feat: Multiple border style. (#202) --- examples/dom/CMakeLists.txt | 2 + examples/dom/border_style.cpp | 28 ++++ examples/dom/separator.cpp | 4 +- examples/dom/separator_style.cpp | 41 ++++++ include/ftxui/dom/elements.hpp | 11 ++ src/ftxui/dom/border.cpp | 186 +++++++++++++++++++++--- src/ftxui/dom/separator.cpp | 35 +++-- src/ftxui/screen/screen.cpp | 239 +++++++++++++++++++++++++++++-- 8 files changed, 506 insertions(+), 40 deletions(-) create mode 100644 examples/dom/border_style.cpp create mode 100644 examples/dom/separator_style.cpp diff --git a/examples/dom/CMakeLists.txt b/examples/dom/CMakeLists.txt index 1bac5ae..758e35b 100644 --- a/examples/dom/CMakeLists.txt +++ b/examples/dom/CMakeLists.txt @@ -1,6 +1,7 @@ set(DIRECTORY_LIB dom) example(border) +example(border_style) example(color_gallery) example(dbox) example(gauge) @@ -10,6 +11,7 @@ example(html_like) example(package_manager) example(paragraph) example(separator) +example(separator_style) example(size) example(spinner) example(style_blink) diff --git a/examples/dom/border_style.cpp b/examples/dom/border_style.cpp new file mode 100644 index 0000000..c837a11 --- /dev/null +++ b/examples/dom/border_style.cpp @@ -0,0 +1,28 @@ +#include // for text, operator|, vbox, border, Element, Fit, hbox +#include // for Full, Screen +#include +#include // for allocator + +#include "ftxui/dom/node.hpp" // for Render +#include "ftxui/screen/box.hpp" // for ftxui + +int main(int argc, const char* argv[]) { + using namespace ftxui; + + auto document = vbox({ + text("borderLight") | borderLight, + text("borderHeavy") | borderHeavy, + text("borderDouble") | borderDouble, + text("borderRounded") | borderRounded, + }); + + auto screen = + Screen::Create(Dimension::Fit(document), Dimension::Fit(document)); + Render(screen, document); + screen.Print(); + std::cout << std::endl; +} + +// Copyright 2020 Arthur Sonzogni. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. diff --git a/examples/dom/separator.cpp b/examples/dom/separator.cpp index ed61299..3013a5f 100644 --- a/examples/dom/separator.cpp +++ b/examples/dom/separator.cpp @@ -11,9 +11,9 @@ int main(int argc, const char* argv[]) { text("left-column"), separator(), vbox({ - center(text("right-top")) | flex, + center(text("top")) | flex, separator(), - center(text("bottom-bottom")), + center(text("bottom")), }) | flex, separator(), text("right-column"), diff --git a/examples/dom/separator_style.cpp b/examples/dom/separator_style.cpp new file mode 100644 index 0000000..aaf2d22 --- /dev/null +++ b/examples/dom/separator_style.cpp @@ -0,0 +1,41 @@ +#include // for text, operator|, vbox, border, Element, Fit, hbox +#include // for Full, Screen +#include +#include // for allocator + +#include "ftxui/dom/node.hpp" // for Render +#include "ftxui/screen/box.hpp" // for ftxui + +int main(int argc, const char* argv[]) { + using namespace ftxui; + + auto document = vbox({ + vbox({ + text("separatorLight"), + separatorLight(), + hbox(text("left"), separatorLight(), text("right")), + }) | borderLight, + + vbox({ + text("separatorHeavy"), + separatorHeavy(), + hbox(text("left"), separatorHeavy(), text("right")), + }) | borderHeavy, + + vbox({ + text("separatorDouble"), + separatorDouble(), + hbox(text("left"), separatorDouble(), text("right")), + }) | borderDouble, + }); + + auto screen = + Screen::Create(Dimension::Fit(document), Dimension::Fit(document)); + Render(screen, document); + screen.Print(); + std::cout << std::endl; +} + +// Copyright 2020 Arthur Sonzogni. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp index 5b54dbe..c3dbc3f 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -17,6 +17,8 @@ using Elements = std::vector; using Decorator = std::function; using GraphFunction = std::function(int, int)>; +enum BorderStyle { LIGHT, HEAVY, DOUBLE, ROUNDED }; + // Pipe elements into decorator togethers. // For instance the next lines are equivalents: // -> text("ftxui") | bold | underlined @@ -29,9 +31,18 @@ Decorator operator|(Decorator, Decorator); Element text(std::string text); Element vtext(std::string text); Element separator(void); +Element separatorLight(); +Element separatorHeavy(); +Element separatorDouble(); +Element separatorStyled(BorderStyle); Element separator(Pixel); Element gauge(float ratio); Element border(Element); +Element borderLight(Element); +Element borderHeavy(Element); +Element borderDouble(Element); +Element borderRounded(Element); +Decorator borderStyled(BorderStyle); Decorator borderWith(Pixel); Element window(Element title, Element content); Element spinner(int charset_index, size_t image_index); diff --git a/src/ftxui/dom/border.cpp b/src/ftxui/dom/border.cpp index 13b6d47..4ad97ae 100644 --- a/src/ftxui/dom/border.cpp +++ b/src/ftxui/dom/border.cpp @@ -13,8 +13,12 @@ namespace ftxui { -static std::string simple_border_charset[] = {"╭", "╮", "╰", "╯", "─", - "│", "┬", "┴", "┤", "├"}; +static std::string simple_border_charset[6][6] = { + {"┌", "┐", "└", "┘", "─", "│"}, + {"┏", "┓", "┗", "┛", "━", "┃"}, + {"╔", "╗", "╚", "╝", "═", "║"}, + {"╭", "╮", "╰", "╯", "─", "│"}, +}; // For reference, here is the charset for normal border: // {"┌", "┐", "└", "┘", "─", "│", "┬", "┴", "┤", "├"}; @@ -23,10 +27,10 @@ static std::string simple_border_charset[] = {"╭", "╮", "╰", "╯", "─", class Border : public Node { public: - Border(Elements children) + Border(Elements children, BorderStyle style) : Node(std::move(children)), - charset(std::begin(simple_border_charset), - std::end(simple_border_charset)) {} + charset(std::begin(simple_border_charset[style]), + std::end(simple_border_charset[style])) {} Border(Elements children, Pixel pixel) : Node(std::move(children)), charset_pixel(10, pixel) {} @@ -113,8 +117,14 @@ class Border : public Node { } } }; + /// @brief Draw a border around the element. /// @ingroup dom +/// @see border +/// @see borderLight +/// @see borderDouble +/// @see borderHeavy +/// @see borderRounded /// /// Add a border around an element /// @@ -136,7 +146,158 @@ class Border : public Node { /// └───────────┘ /// ``` Element border(Element child) { - return std::make_shared(unpack(std::move(child))); + return std::make_shared(unpack(std::move(child)), ROUNDED); +} + + +/// @brief Same as border but with a constant Pixel around the element. +/// @ingroup dom +/// @see border +Decorator borderWith(Pixel pixel) { + return [pixel](Element child) { + return std::make_shared(unpack(std::move(child)), pixel); + }; +} + +/// @brief Same as border but with different styles. +/// @ingroup dom +/// @see border +Decorator borderStyled(BorderStyle style) { + return [style](Element child) { + return std::make_shared(unpack(std::move(child)), style); + }; +} + +/// @brief Draw a light border around the element. +/// @ingroup dom +/// @see border +/// @see borderLight +/// @see borderDouble +/// @see borderHeavy +/// @see borderRounded +/// @see borderStyled +/// @see borderWith +/// +/// Add a border around an element +/// +/// ### Example +/// +/// ```cpp +/// // Use 'borderLight' as a function... +/// Element document = borderLight(text("The element")); +/// +/// // ...Or as a 'pipe'. +/// Element document = text("The element") | borderLight; +/// ``` +/// +/// ### Output +/// +/// ```bash +/// ┌──────────────┐ +/// │The element │ +/// └──────────────┘ +/// ``` +Element borderLight(Element child) { + return std::make_shared(unpack(std::move(child)), LIGHT); +} + +/// @brief Draw a heavy border around the element. +/// @ingroup dom +/// @see border +/// @see borderLight +/// @see borderDouble +/// @see borderHeavy +/// @see borderRounded +/// @see borderStyled +/// @see borderWith +/// +/// Add a border around an element +/// +/// ### Example +/// +/// ```cpp +/// // Use 'borderHeavy' as a function... +/// Element document = borderHeavy(text("The element")); +/// +/// // ...Or as a 'pipe'. +/// Element document = text("The element") | borderHeavy; +/// ``` +/// +/// ### Output +/// +/// ```bash +/// ┏━━━━━━━━━━━━━━┓ +/// ┃The element ┃ +/// ┗━━━━━━━━━━━━━━┛ +/// ``` +Element borderHeavy(Element child) { + return std::make_shared(unpack(std::move(child)), HEAVY); +} + +/// @brief Draw a double border around the element. +/// @ingroup dom +/// @see border +/// @see borderLight +/// @see borderDouble +/// @see borderHeavy +/// @see borderRounded +/// @see borderStyled +/// @see borderWith +/// +/// Add a border around an element +/// +/// ### Example +/// +/// ```cpp +/// // Use 'borderDouble' as a function... +/// Element document = borderDouble(text("The element")); +/// +/// // ...Or as a 'pipe'. +/// Element document = text("The element") | borderDouble; +/// ``` +/// +/// ### Output +/// +/// ```bash +/// ╔══════════════╗ +/// ║The element ║ +/// ╚══════════════╝ +/// ``` +Element borderDouble(Element child) { + return std::make_shared(unpack(std::move(child)), DOUBLE); +} + +/// @brief Draw a rounded border around the element. +/// @ingroup dom +/// @see border +/// @see borderLight +/// @see borderDouble +/// @see borderHeavy +/// @see borderRounded +/// @see borderStyled +/// @see borderWith +/// +/// Add a border around an element +/// +/// ### Example +/// +/// ```cpp +/// // Use 'borderRounded' as a function... +/// Element document = borderRounded(text("The element")); +/// +/// // ...Or as a 'pipe'. +/// Element document = text("The element") | borderRounded; +/// ``` +/// +/// ### Output +/// +/// ```bash +/// ╭──────────────╮ +/// │The element │ +/// ╰──────────────╯ +/// ``` +Element borderRounded(Element child) { + return std::make_shared(unpack(std::move(child)), ROUNDED); } /// @brief Draw window with a title and a border around the element. @@ -161,18 +322,9 @@ Element border(Element child) { /// └───────┘ /// ``` Element window(Element title, Element content) { - return std::make_shared(unpack(std::move(content), std::move(title))); + return std::make_shared(unpack(std::move(content), std::move(title)), + ROUNDED); } - -/// @brief Same as border but with a constant Pixel around the element. -/// @ingroup dom -/// @see border -Decorator borderWith(Pixel pixel) { - return [pixel](Element child) { - return std::make_shared(unpack(std::move(child)), pixel); - }; -} - } // namespace ftxui // Copyright 2020 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/dom/separator.cpp b/src/ftxui/dom/separator.cpp index fa3d894..6d8a757 100644 --- a/src/ftxui/dom/separator.cpp +++ b/src/ftxui/dom/separator.cpp @@ -11,8 +11,17 @@ namespace ftxui { using ftxui::Screen; +const std::string charset[][2] = { + {"│", "─"}, + {"┃", "━"}, + {"║", "═"}, + {"│", "─"}, +}; + class Separator : public Node { public: + Separator(BorderStyle style) : style_(style) {} + void ComputeRequirement() override { requirement_.min_x = 1; requirement_.min_y = 1; @@ -22,11 +31,7 @@ class Separator : public Node { bool is_column = (box_.x_max == box_.x_min); bool is_line = (box_.y_min == box_.y_max); - std::string c = "+"; - if (is_line && !is_column) - c = "─"; - else - c = "│"; + const std::string c = charset[style_][is_line && !is_column]; for (int y = box_.y_min; y <= box_.y_max; ++y) { for (int x = box_.x_min; x <= box_.x_max; ++x) { @@ -34,11 +39,13 @@ class Separator : public Node { } } } + + BorderStyle style_; }; class SeparatorWithPixel : public Separator { public: - SeparatorWithPixel(Pixel pixel) : pixel_(pixel) {} + SeparatorWithPixel(Pixel pixel) : Separator(LIGHT), pixel_(pixel) {} void Render(Screen& screen) override { for (int y = box_.y_min; y <= box_.y_max; ++y) { for (int x = box_.x_min; x <= box_.x_max; ++x) { @@ -52,11 +59,21 @@ class SeparatorWithPixel : public Separator { }; Element separator() { - return std::make_shared(); + return std::make_shared(LIGHT); } -Element separator(Pixel pixel) { - return std::make_shared(pixel); +Element separatorStyled(BorderStyle style) { + return std::make_shared(style); +} + +Element separatorLight() { + return std::make_shared(LIGHT); +} +Element separatorHeavy() { + return std::make_shared(HEAVY); +} +Element separatorDouble() { + return std::make_shared(DOUBLE); } } // namespace ftxui diff --git a/src/ftxui/screen/screen.cpp b/src/ftxui/screen/screen.cpp index 6da31a1..e2b333b 100644 --- a/src/ftxui/screen/screen.cpp +++ b/src/ftxui/screen/screen.cpp @@ -1,4 +1,5 @@ #include // for operator<<, stringstream, basic_ostream, flush, cout, ostream +#include #include // for allocator #include // IWYU pragma: keep @@ -88,6 +89,228 @@ void UpdatePixelStyle(std::stringstream& ss, previous = next; } +struct TileEncoding { + unsigned int left : 2; + unsigned int top : 2; + unsigned int right : 2; + unsigned int down : 2; + unsigned int round : 1; + + bool operator<(const TileEncoding& other) const { + union Converter { + TileEncoding input; + uint16_t output = 0; + }; + Converter a, b; + a.input = *this; + b.input = other; + return a.output < b.output; + } +}; + +// clang-format off +const std::map tile_encoding = { + {"─", {1, 0, 1, 0, 0}}, + {"━", {2, 0, 2, 0, 0}}, + + {"│", {0, 1, 0, 1, 0}}, + {"┃", {0, 2, 0, 2, 0}}, + + {"┌", {0, 0, 1, 1, 0}}, + {"┍", {0, 0, 2, 1, 0}}, + {"┎", {0, 0, 1, 2, 0}}, + {"┏", {0, 0, 2, 2, 0}}, + + {"┐", {1, 0, 0, 1, 0}}, + {"┑", {2, 0, 0, 1, 0}}, + {"┒", {1, 0, 0, 2, 0}}, + {"┓", {2, 0, 0, 2, 0}}, + + {"└", {0, 1, 1, 0, 0}}, + {"┕", {0, 1, 2, 0, 0}}, + {"┖", {0, 2, 1, 0, 0}}, + {"┗", {0, 2, 2, 0, 0}}, + + {"┘", {1, 1, 0, 0, 0}}, + {"┙", {2, 1, 0, 0, 0}}, + {"┚", {1, 2, 0, 0, 0}}, + {"┛", {2, 2, 0, 0, 0}}, + + {"├", {0, 1, 1, 1, 0}}, + {"┝", {0, 1, 2, 1, 0}}, + {"┞", {0, 2, 1, 1, 0}}, + {"┟", {0, 1, 1, 2, 0}}, + {"┠", {0, 2, 1, 2, 0}}, + {"┡", {0, 2, 2, 1, 0}}, + {"┢", {0, 1, 2, 2, 0}}, + {"┣", {0, 2, 2, 2, 0}}, + + {"┤", {1, 1, 0, 1, 0}}, + {"┥", {2, 1, 0, 1, 0}}, + {"┦", {1, 2, 0, 1, 0}}, + {"┧", {1, 1, 0, 2, 0}}, + {"┨", {1, 2, 0, 2, 0}}, + {"┩", {2, 2, 0, 1, 0}}, + {"┪", {2, 1, 0, 2, 0}}, + {"┫", {2, 2, 0, 2, 0}}, + + {"┬", {1, 0, 1, 1, 0}}, + {"┭", {2, 0, 1, 1, 0}}, + {"┮", {1, 0, 2, 1, 0}}, + {"┯", {2, 0, 2, 1, 0}}, + {"┰", {1, 0, 1, 2, 0}}, + {"┱", {2, 0, 1, 2, 0}}, + {"┲", {1, 0, 2, 2, 0}}, + {"┳", {2, 0, 2, 2, 0}}, + + {"┴", {1, 1, 1, 0, 0}}, + {"┵", {2, 1, 1, 0, 0}}, + {"┶", {1, 1, 2, 0, 0}}, + {"┷", {2, 1, 2, 0, 0}}, + {"┸", {1, 2, 1, 0, 0}}, + {"┹", {2, 2, 1, 0, 0}}, + {"┺", {1, 2, 2, 0, 0}}, + {"┻", {2, 2, 2, 0, 0}}, + + {"┼", {1, 1, 1, 1, 0}}, + {"┽", {2, 1, 1, 1, 0}}, + {"┾", {1, 1, 2, 1, 0}}, + {"┿", {2, 1, 2, 1, 0}}, + {"╀", {1, 2, 1, 1, 0}}, + {"╁", {1, 1, 1, 2, 0}}, + {"╂", {1, 2, 1, 2, 0}}, + {"╃", {2, 2, 1, 1, 0}}, + {"╄", {1, 2, 2, 1, 0}}, + {"╅", {2, 1, 1, 2, 0}}, + {"╆", {1, 1, 2, 2, 0}}, + {"╇", {2, 2, 2, 1, 0}}, + {"╈", {2, 1, 2, 2, 0}}, + {"╉", {2, 2, 1, 2, 0}}, + {"╊", {1, 2, 2, 2, 0}}, + {"╋", {2, 2, 2, 2, 0}}, + + {"═", {3, 0, 3, 0, 0}}, + {"║", {0, 3, 0, 3, 0}}, + + {"╒", {0, 0, 3, 1, 0}}, + {"╓", {0, 0, 1, 3, 0}}, + {"╔", {0, 0, 3, 3, 0}}, + + {"╕", {3, 0, 0, 1, 0}}, + {"╖", {1, 0, 0, 3, 0}}, + {"╗", {3, 0, 0, 3, 0}}, + + {"╘", {0, 1, 3, 0, 0}}, + {"╙", {0, 3, 1, 0, 0}}, + {"╚", {0, 3, 3, 0, 0}}, + + {"╛", {3, 1, 0, 0, 0}}, + {"╜", {1, 3, 0, 0, 0}}, + {"╝", {3, 3, 0, 0, 0}}, + + {"╞", {0, 1, 3, 1, 0}}, + {"╟", {0, 3, 1, 3, 0}}, + {"╠", {0, 3, 3, 3, 0}}, + + {"╡", {3, 1, 0, 1, 0}}, + {"╢", {1, 3, 0, 3, 0}}, + {"╣", {3, 3, 0, 3, 0}}, + + {"╤", {3, 0, 3, 1, 0}}, + {"╥", {1, 0, 1, 3, 0}}, + {"╦", {3, 0, 3, 3, 0}}, + + {"╧", {3, 1, 3, 0, 0}}, + {"╨", {1, 3, 1, 0, 0}}, + {"╩", {3, 3, 3, 0, 0}}, + + {"╪", {3, 1, 3, 1, 0}}, + {"╫", {1, 3, 1, 3, 0}}, + {"╬", {3, 3, 3, 3, 0}}, + + {"╭", {0, 0, 1, 1, 1}}, + {"╮", {1, 0, 0, 1, 1}}, + {"╯", {1, 1, 0, 0, 1}}, + {"╰", {0, 1, 1, 0, 1}}, + + {"╴", {1, 0, 0, 0, 0}}, + {"╵", {0, 1, 0, 0, 0}}, + {"╶", {0, 0, 1, 0, 0}}, + {"╷", {0, 0, 0, 1, 0}}, + + {"╸", {2, 0, 0, 0, 0}}, + {"╹", {0, 2, 0, 0, 0}}, + {"╺", {0, 0, 2, 0, 0}}, + {"╻", {0, 0, 0, 2, 0}}, + + {"╼", {1, 0, 2, 0, 0}}, + {"╽", {0, 1, 0, 2, 0}}, + {"╾", {2, 0, 1, 0, 0}}, + {"╿", {0, 2, 0, 1, 0}}, +}; +// clang-format on + +template +const std::map InvertMap(const std::map input) { + std::map output; + for (const auto& it : input) + output[it.second] = it.first; + return output; +} + +const std::map tile_encoding_inverse = + InvertMap(tile_encoding); + +void UpgradeLeftRight(std::string& left, std::string& right) { + const auto it_left = tile_encoding.find(left); + if (it_left == tile_encoding.end()) + return; + const auto it_right = tile_encoding.find(right); + if (it_right == tile_encoding.end()) + return; + + if (it_left->second.right == 0 && it_right->second.left != 0) { + TileEncoding encoding_left = it_left->second; + encoding_left.right = it_right->second.left; + const auto it_left_upgrade = tile_encoding_inverse.find(encoding_left); + if (it_left_upgrade != tile_encoding_inverse.end()) + left = it_left_upgrade->second; + } + + if (it_right->second.left == 0 && it_left->second.right != 0) { + TileEncoding encoding_right = it_right->second; + encoding_right.left = it_left->second.right; + const auto it_right_upgrade = tile_encoding_inverse.find(encoding_right); + if (it_right_upgrade != tile_encoding_inverse.end()) + right = it_right_upgrade->second; + } +} + +void UpgradeTopDown(std::string& top, std::string& down) { + const auto it_top = tile_encoding.find(top); + if (it_top == tile_encoding.end()) + return; + const auto it_down = tile_encoding.find(down); + if (it_down == tile_encoding.end()) + return; + + if (it_top->second.down == 0 && it_down->second.top != 0) { + TileEncoding encoding_top = it_top->second; + encoding_top.down = it_down->second.top; + const auto it_top_down = tile_encoding_inverse.find(encoding_top); + if (it_top_down != tile_encoding_inverse.end()) + top = it_top_down->second; + } + + if (it_down->second.top == 0 && it_top->second.down != 0) { + TileEncoding encoding_down = it_down->second; + encoding_down.top = it_top->second.down; + const auto it_down_top = tile_encoding_inverse.find(encoding_down); + if (it_down_top != tile_encoding_inverse.end()) + down = it_down_top->second; + } +} + } // namespace /// A fixed dimension. @@ -234,21 +457,13 @@ void Screen::ApplyShader() { // Left vs current. std::string& left = pixels_[y][x-1].character; - if (left.size() == 3u) { - if (cur == "│" && left == "─") cur = "┤"; - if (cur == "├" && left == "─") cur = "┼"; - if (cur == "─" && left == "│") left = "├"; - if (cur == "─" && left == "┤") left = "┼"; - } + if (left.size() == 3u) + UpgradeLeftRight(left, cur); // Top vs current. std::string& top = pixels_[y-1][x].character; - if (top.size() == 3u) { - if (cur == "─" && top == "│") cur = "┴"; - if (cur == "┬" && top == "│") cur = "┼"; - if (cur == "│" && top == "─") top = "┬"; - if (cur == "│" && top == "┴") top = "┼"; - } + if (top.size() == 3u) + UpgradeTopDown(top, cur); } } }