diff --git a/CMakeLists.txt b/CMakeLists.txt index d2ad4619..9456c7d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ add_library(dom STATIC src/ftxui/dom/gauge.cpp src/ftxui/dom/graph.cpp src/ftxui/dom/hbox.cpp + src/ftxui/dom/gridbox.cpp src/ftxui/dom/hflow.cpp src/ftxui/dom/inverted.cpp src/ftxui/dom/node.cpp diff --git a/cmake/ftxui_test.cmake b/cmake/ftxui_test.cmake index 33fc0d89..cab0b97c 100644 --- a/cmake/ftxui_test.cmake +++ b/cmake/ftxui_test.cmake @@ -20,6 +20,7 @@ add_executable(tests src/ftxui/component/terminal_input_parser_test.cpp src/ftxui/component/toggle_test.cpp src/ftxui/dom/gauge_test.cpp + src/ftxui/dom/gridbox_test.cpp src/ftxui/dom/hbox_test.cpp src/ftxui/dom/text_test.cpp src/ftxui/dom/vbox_test.cpp diff --git a/examples/dom/CMakeLists.txt b/examples/dom/CMakeLists.txt index c1297ea3..1bac5aef 100644 --- a/examples/dom/CMakeLists.txt +++ b/examples/dom/CMakeLists.txt @@ -19,6 +19,7 @@ example(color_truecolor_RGB) example(color_truecolor_HSV) example(color_info_palette256) example(style_dim) +example(gridbox) example(style_gallery) example(style_inverted) example(style_underlined) diff --git a/examples/dom/gridbox.cpp b/examples/dom/gridbox.cpp new file mode 100644 index 00000000..21396266 --- /dev/null +++ b/examples/dom/gridbox.cpp @@ -0,0 +1,49 @@ +#include // for getchar +#include // for filler, text, hbox, vbox +#include // for Full, Screen +#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 cell = [](const char* t) { return text(t) | border; }; + auto document = // + gridbox({ + { + cell("north-west"), + cell("north"), + cell("north-east"), + }, + { + cell("center-west"), + gridbox({ + { + cell("center-north-west"), + cell("center-north-east"), + }, + { + cell("center-south-west"), + cell("center-south-east"), + }, + }), + cell("center-east"), + }, + { + cell("south-west"), + cell("south"), + cell("south-east"), + }, + }); + auto screen = Screen::Create(Dimension::Fit(document)); + Render(screen, document); + screen.Print(); + getchar(); + + return 0; +} + +// Copyright 2021 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 60cff2c1..5b54dbe8 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -54,6 +54,7 @@ Element bgcolor(Color, Element); Element hbox(Elements); Element vbox(Elements); Element dbox(Elements); +Element gridbox(std::vector lines); Element hflow(Elements); // -- Flexibility --- diff --git a/src/ftxui/dom/gridbox.cpp b/src/ftxui/dom/gridbox.cpp new file mode 100644 index 00000000..be06ec16 --- /dev/null +++ b/src/ftxui/dom/gridbox.cpp @@ -0,0 +1,161 @@ +#include +#include // for max +#include // for __shared_ptr_access, shared_ptr, make_shared +#include // for move +#include // for vector + +#include "ftxui/dom/box_helper.hpp" // for Requirement +#include "ftxui/dom/elements.hpp" // for Element, Elements, hbox +#include "ftxui/dom/node.hpp" // for Node +#include "ftxui/dom/requirement.hpp" // for Requirement +#include "ftxui/screen/box.hpp" // for Box + +namespace ftxui { + +class GridBox : public Node { + public: + GridBox(std::vector lines) : Node(), lines_(std::move(lines)) { + y_size = lines_.size(); + for (const auto& line : lines_) + x_size = std::max(x_size, (int)line.size()); + for (auto& line : lines_) { + while (line.size() < (size_t)y_size) { + line.push_back(filler()); + } + } + } + + void ComputeRequirement() override { + requirement_.min_x = 0; + requirement_.min_y = 0; + requirement_.flex_grow_x = 0; + requirement_.flex_grow_y = 0; + requirement_.flex_shrink_x = 0; + requirement_.flex_shrink_y = 0; + + for(auto& line : lines_) { + for(auto& cell : line) { + cell->ComputeRequirement(); + + // Determine focus based on the focused child. + if (requirement_.selection >= cell->requirement().selection) + continue; + requirement_.selection = cell->requirement().selection; + requirement_.selected_box = cell->requirement().selected_box; + requirement_.selected_box.x_min += requirement_.min_x; + requirement_.selected_box.x_max += requirement_.min_x; + } + } + + // Work on the x-axis. + for (int x = 0; x < x_size; ++x) { + int min_x = 0; + for (int y = 0; y < y_size; ++y) + min_x = std::max(min_x, lines_[y][x]->requirement().min_x); + requirement_.min_x += min_x; + } + + // Work on the y-axis. + for (int y = 0; y < y_size; ++y) { + int min_y = 0; + for (int x = 0; x < x_size; ++x) + min_y = std::max(min_y, lines_[y][x]->requirement().min_y); + requirement_.min_y += min_y; + } + } + + void SetBox(Box box) override { + Node::SetBox(box); + + box_helper::Element init; + init.min_size = 0; + init.flex_grow = 1024; + init.flex_shrink = 1024; + std::vector elements_x(x_size, init); + std::vector elements_y(y_size, init); + + for (int y = 0; y < y_size; ++y) { + for (int x = 0; x < x_size; ++x) { + const auto& cell = lines_[y][x]; + const auto& requirement = cell->requirement(); + auto& e_x = elements_x[x]; + auto& e_y = elements_y[y]; + e_x.min_size = std::max(e_x.min_size, requirement.min_x); + e_y.min_size = std::max(e_y.min_size, requirement.min_y); + e_x.flex_grow = std::min(e_x.flex_grow, requirement.flex_grow_x); + e_y.flex_grow = std::min(e_y.flex_grow, requirement.flex_grow_y); + e_x.flex_shrink = std::min(e_x.flex_shrink, requirement.flex_shrink_x); + e_y.flex_shrink = std::min(e_y.flex_shrink, requirement.flex_shrink_y); + } + } + + int target_size_x = box.x_max - box.x_min + 1; + int target_size_y = box.y_max - box.y_min + 1; + box_helper::Compute(&elements_x, target_size_x); + box_helper::Compute(&elements_y, target_size_y); + + Box box_y = box; + int y = box_y.y_min; + for (int iy = 0; iy < y_size; ++iy) { + box_y.y_min = y; + y += elements_y[iy].size; + box_y.y_max = y - 1; + + Box box_x = box_y; + int x = box_x.x_min; + for (int ix = 0; ix < x_size; ++ix) { + box_x.x_min = x; + x += elements_x[ix].size; + box_x.x_max = x - 1; + lines_[iy][ix]->SetBox(box_x); + } + } + } + + void Render(Screen& screen) override { + for (auto& line : lines_) { + for (auto& cell : line) + cell->Render(screen); + } + } + + int x_size = 0; + int y_size = 0; + std::vector lines_; +}; + +/// @brief A container displaying a grid of elements. +/// @param lines A list of lines, each line being a list of elements. +/// @return The container. +/// +/// #### Example +/// +/// ```cpp +/// auto cell = [](const char* t) { return text(t) | border; }; +/// auto document = gridbox({ +/// {cell("north-west") , cell("north") , cell("north-east")} , +/// {cell("west") , cell("center") , cell("east")} , +/// {cell("south-west") , cell("south") , cell("south-east")} , +/// }); +/// ``` +/// Output: +/// ``` +///╭──────────╮╭──────╮╭──────────╮ +///│north-west││north ││north-east│ +///╰──────────╯╰──────╯╰──────────╯ +///╭──────────╮╭──────╮╭──────────╮ +///│west ││center││east │ +///╰──────────╯╰──────╯╰──────────╯ +///╭──────────╮╭──────╮╭──────────╮ +///│south-west││south ││south-east│ +///╰──────────╯╰──────╯╰──────────╯ +/// ``` +Element gridbox(std::vector lines) { + return std::make_shared(std::move(lines)); +} + +} // namespace ftxui + +// 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/src/ftxui/dom/gridbox_test.cpp b/src/ftxui/dom/gridbox_test.cpp new file mode 100644 index 00000000..a7506d74 --- /dev/null +++ b/src/ftxui/dom/gridbox_test.cpp @@ -0,0 +1,573 @@ +#include // for Message +#include // for SuiteApiResolver, TestFactoryImpl, TestPartResult +#include // for allocator, basic_string, string +#include // for vector + +#include "ftxui/dom/elements.hpp" // for text, operator|, Element, flex_grow, flex_shrink, gridbox +#include "ftxui/dom/node.hpp" // for Render +#include "ftxui/screen/box.hpp" // for ftxui +#include "ftxui/screen/screen.hpp" // for Screen +#include "gtest/gtest_pred_impl.h" // for Test, EXPECT_EQ, TEST + +using namespace ftxui; + +namespace { +std::string rotate(std::string str) { + str.erase(std::remove(str.begin(), str.end(), '\r'), str.end()); + str.erase(std::remove(str.begin(), str.end(), '\n'), str.end()); + return str; +} + +Element cell(const char* t) { + return text(t) | border; +} +} // namespace + +TEST(GridboxTest, DifferentSize) { + auto root = gridbox({ + {cell("1"), cell("22"), cell("333")}, + {cell("4444"), cell("5") | flex, cell("66666")}, + {cell("7"), cell("8"), vbox({cell("9"), cell("10")})}, + }); + + Screen screen(20, 12); + Render(screen, root); + EXPECT_EQ(screen.ToString(), + "╭────╮╭──╮╭─────╮ \r\n" + "│1 ││22││333 │ \r\n" + "╰────╯╰──╯╰─────╯ \r\n" + "╭────╮╭──╮╭─────╮ \r\n" + "│4444││5 ││66666│ \r\n" + "╰────╯╰──╯╰─────╯ \r\n" + "╭────╮╭──╮╭─────╮ \r\n" + "│7 ││8 ││9 │ \r\n" + "│ ││ │╰─────╯ \r\n" + "│ ││ │╭─────╮ \r\n" + "│ ││ ││10 │ \r\n" + "╰────╯╰──╯╰─────╯ "); +} + +TEST(GridboxTest, CenterFlex) { + auto root = gridbox({ + {cell("1"), cell("2"), cell("3")}, + {cell("4"), cell("5") | flex, cell("6")}, + {cell("7"), cell("8"), cell("9")}, + }); + + Screen screen(12, 12); + Render(screen, root); + EXPECT_EQ(screen.ToString(), + "╭─╮╭─╮╭─╮ \r\n" + "│1││2││3│ \r\n" + "╰─╯╰─╯╰─╯ \r\n" + "╭─╮╭─╮╭─╮ \r\n" + "│4││5││6│ \r\n" + "╰─╯╰─╯╰─╯ \r\n" + "╭─╮╭─╮╭─╮ \r\n" + "│7││8││9│ \r\n" + "╰─╯╰─╯╰─╯ \r\n" + " \r\n" + " \r\n" + " "); +} + +TEST(GridboxTest, CenterFlexCross) { + auto root = gridbox({ + {cell("1"), cell("2") | flex, cell("3")}, + {cell("4") | flex, cell("5") | flex, cell("6") | flex}, + {cell("7"), cell("8") | flex, cell("9")}, + }); + + Screen screen(12, 12); + Render(screen, root); + EXPECT_EQ(screen.ToString(), + "╭─╮╭────╮╭─╮\r\n" + "│1││2 ││3│\r\n" + "╰─╯╰────╯╰─╯\r\n" + "╭─╮╭────╮╭─╮\r\n" + "│4││5 ││6│\r\n" + "│ ││ ││ │\r\n" + "│ ││ ││ │\r\n" + "│ ││ ││ │\r\n" + "╰─╯╰────╯╰─╯\r\n" + "╭─╮╭────╮╭─╮\r\n" + "│7││8 ││9│\r\n" + "╰─╯╰────╯╰─╯"); +} + +TEST(GridboxTest, CenterShrink) { + auto root = gridbox({ + {cell("111"), cell("222"), cell("333")}, + {cell("444"), cell("555") | flex, cell("444")}, + {cell("777"), cell("888"), cell("999")}, + }); + + Screen screen(13, 12); + Render(screen, root); + EXPECT_EQ(screen.ToString(), + "╭───╮╭──╮╭──╮\r\n" + "│111││22││33│\r\n" + "╰───╯╰──╯╰──╯\r\n" + "╭───╮╭──╮╭──╮\r\n" + "│444││55││44│\r\n" + "╰───╯╰──╯╰──╯\r\n" + "╭───╮╭──╮╭──╮\r\n" + "│777││88││99│\r\n" + "╰───╯╰──╯╰──╯\r\n" + " \r\n" + " \r\n" + " "); +} + +TEST(GridboxTest, CenterShrinkColumn) { + auto root = gridbox({ + {cell("111"), cell("222") | flex, cell("333")}, + {cell("444"), cell("555") | flex, cell("666")}, + {cell("777"), cell("888") | flex, cell("999")}, + }); + + Screen screen(13, 12); + Render(screen, root); + EXPECT_EQ(screen.ToString(), + "╭───╮╭─╮╭───╮\r\n" + "│111││2││333│\r\n" + "╰───╯╰─╯╰───╯\r\n" + "╭───╮╭─╮╭───╮\r\n" + "│444││5││666│\r\n" + "╰───╯╰─╯╰───╯\r\n" + "╭───╮╭─╮╭───╮\r\n" + "│777││8││999│\r\n" + "╰───╯╰─╯╰───╯\r\n" + " \r\n" + " \r\n" + " "); +} + +TEST(GridboxTest, Horizontal_NoFlex_NoFlex_NoFlex) { + auto root = gridbox({ + { + text("012"), + text("abc"), + text("ABC"), + }, + }); + + std::vector expectations = { + "", // + "0", // + "0a", // + "0aA", // + "01aA", // + "01abA", // + "01abAB", // + "012abAB", // + "012abcAB", // + "012abcABC", // + "012abcABC ", // + "012abcABC ", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(i, 1); + Render(screen, root); + EXPECT_EQ(expectations[i], screen.ToString()); + } +} + +TEST(GridboxTest, Vertical_NoFlex_NoFlex_NoFlex) { + auto root = gridbox({ + {vtext("012")}, + {vtext("abc")}, + {vtext("ABC")}, + }); + + std::vector expectations = { + "", // + "0", // + "0a", // + "0aA", // + "01aA", // + "01abA", // + "01abAB", // + "012abAB", // + "012abcAB", // + "012abcABC", // + "012abcABC ", // + "012abcABC ", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(1, i); + Render(screen, root); + EXPECT_EQ(expectations[i], rotate(screen.ToString())); + } +} + +TEST(GridboxTest, Horizontal_FlexGrow_NoFlex_NoFlex) { + auto root = gridbox({ + { + text("012") | flex_grow, + text("abc"), + text("ABC"), + }, + }); + + std::vector expectations = { + "", // + "0", // + "0a", // + "0aA", // + "01aA", // + "01abA", // + "01abAB", // + "012abAB", // + "012abcAB", // + "012abcABC", // + "012 abcABC", // + "012 abcABC", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(i, 1); + Render(screen, root); + EXPECT_EQ(expectations[i], screen.ToString()); + } +} + +TEST(GridboxTest, Vertical_FlexGrow_NoFlex_NoFlex) { + auto root = gridbox({ + {vtext("012") | flex_grow}, + {vtext("abc")}, + {vtext("ABC")}, + }); + + std::vector expectations = { + "", // + "0", // + "0a", // + "0aA", // + "01aA", // + "01abA", // + "01abAB", // + "012abAB", // + "012abcAB", // + "012abcABC", // + "012 abcABC", // + "012 abcABC", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(1, i); + Render(screen, root); + EXPECT_EQ(expectations[i], rotate(screen.ToString())); + } +} + +TEST(GridboxTest, Horizontal_NoFlex_FlexGrow_NoFlex) { + auto root = gridbox({ + { + text("012"), + text("abc") | flex_grow, + text("ABC"), + }, + }); + + std::vector expectations = { + "", // + "0", // + "0a", // + "0aA", // + "01aA", // + "01abA", // + "01abAB", // + "012abAB", // + "012abcAB", // + "012abcABC", // + "012abc ABC", // + "012abc ABC", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(i, 1); + Render(screen, root); + EXPECT_EQ(expectations[i], screen.ToString()); + } +} + +TEST(GridboxTest, Horizontal_NoFlex_NoFlex_FlexGrow) { + auto root = gridbox({ + { + text("012"), + text("abc"), + text("ABC") | flex_grow, + }, + }); + + std::vector expectations = { + "", // + "0", // + "0a", // + "0aA", // + "01aA", // + "01abA", // + "01abAB", // + "012abAB", // + "012abcAB", // + "012abcABC", // + "012abcABC ", // + "012abcABC ", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(i, 1); + Render(screen, root); + EXPECT_EQ(expectations[i], screen.ToString()); + } +} + +TEST(GridboxTest, Horizontal_FlexGrow_NoFlex_FlexGrow) { + auto root = gridbox({ + { + text("012") | flex_grow, + text("abc"), + text("ABC") | flex_grow, + }, + }); + + std::vector expectations = { + "", // + "0", // + "0a", // + "0aA", // + "01aA", // + "01abA", // + "01abAB", // + "012abAB", // + "012abcAB", // + "012abcABC", // + "012abcABC ", // + "012 abcABC ", // + "012 abcABC ", // + "012 abcABC ", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(i, 1); + Render(screen, root); + EXPECT_EQ(expectations[i], screen.ToString()); + } +} + +TEST(GridboxTest, Horizontal_FlexGrow_FlexGrow_FlexGrow) { + auto root = gridbox({ + { + text("012") | flex_grow, + text("abc") | flex_grow, + text("ABC") | flex_grow, + }, + }); + + std::vector expectations = { + "", // + "0", // + "0a", // + "0aA", // + "01aA", // + "01abA", // + "01abAB", // + "012abAB", // + "012abcAB", // + "012abcABC", // + "012abcABC ", // + "012abc ABC ", // + "012 abc ABC ", // + "012 abc ABC ", // + "012 abc ABC ", // + "012 abc ABC ", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(i, 1); + Render(screen, root); + EXPECT_EQ(expectations[i], screen.ToString()); + } +} + +// ------ + +TEST(GridboxTest, Horizontal_FlexShrink_NoFlex_NoFlex) { + auto root = gridbox({ + { + text("012") | flex_shrink, + text("abc"), + text("ABC"), + }, + }); + + std::vector expectations = { + "", // + "a", // + "aA", // + "abA", // + "abAB", // + "abcAB", // + "abcABC", // + "0abcABC", // + "01abcABC", // + "012abcABC", // + "012abcABC ", // + "012abcABC ", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(i, 1); + Render(screen, root); + EXPECT_EQ(expectations[i], screen.ToString()); + } +} + +TEST(GridboxTest, Horizontal_NoFlex_FlexShrink_NoFlex) { + auto root = gridbox({ + { + text("012"), + text("abc") | flex_shrink, + text("ABC"), + }, + }); + + std::vector expectations = { + "", // + "0", // + "0A", // + "01A", // + "01AB", // + "012AB", // + "012ABC", // + "012aABC", // + "012abABC", // + "012abcABC", // + "012abcABC ", // + "012abcABC ", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(i, 1); + Render(screen, root); + EXPECT_EQ(expectations[i], screen.ToString()); + } +} + +TEST(GridboxTest, Horizontal_NoFlex_NoFlex_FlexShrink) { + auto root = gridbox({ + { + text("012"), + text("abc"), + text("ABC") | flex_shrink, + }, + }); + + std::vector expectations = { + "", // + "0", // + "0a", // + "01a", // + "01ab", // + "012ab", // + "012abc", // + "012abcA", // + "012abcAB", // + "012abcABC", // + "012abcABC ", // + "012abcABC ", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(i, 1); + Render(screen, root); + EXPECT_EQ(expectations[i], screen.ToString()); + } +} + +TEST(GridboxTest, Horizontal_FlexShrink_NoFlex_FlexShrink) { + auto root = gridbox({ + { + text("012") | flex_shrink, + text("abc"), + text("ABC") | flex_shrink, + }, + }); + + std::vector expectations = { + "", // + "a", // + "ab", // + "abc", // + "0abc", // + "0abcA", // + "01abcA", // + "01abcAB", // + "012abcAB", // + "012abcABC", // + "012abcABC ", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(i, 1); + Render(screen, root); + EXPECT_EQ(expectations[i], screen.ToString()); + } +} + +TEST(GridboxTest, Horizontal_FlexShrink_FlexShrink_FlexShrink) { + auto root = gridbox({ + { + text("012") | flex_shrink, + text("abc") | flex_shrink, + text("ABC") | flex_shrink, + }, + }); + + std::vector expectations = { + "", // + "0", // + "0a", // + "0aA", // + "01aA", // + "01abA", // + "01abAB", // + "012abAB", // + "012abcAB", // + "012abcABC", // + "012abcABC ", // + "012abcABC ", // + "012abcABC ", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(i, 1); + Render(screen, root); + EXPECT_EQ(expectations[i], screen.ToString()); + } +} + +TEST(GridboxTest, Horizontal_FlexGrow_NoFlex_FlewShrink) { + auto root = gridbox({ + { + text("012") | flex_grow, + text("abc"), + text("ABC") | flex_shrink, + }, + }); + + std::vector expectations = { + "", // + "0", // + "0a", // + "01a", // + "01ab", // + "012ab", // + "012abc", // + "012abcA", // + "012abcAB", // + "012abcABC", // + "012 abcABC", // + "012 abcABC", // + "012 abcABC", // + }; + for (int i = 0; i < expectations.size(); ++i) { + Screen screen(i, 1); + Render(screen, root); + EXPECT_EQ(expectations[i], screen.ToString()); + } +} + +// 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/src/ftxui/dom/vbox_test.cpp b/src/ftxui/dom/vbox_test.cpp index 497b3888..a16543bf 100644 --- a/src/ftxui/dom/vbox_test.cpp +++ b/src/ftxui/dom/vbox_test.cpp @@ -10,7 +10,6 @@ #include "ftxui/screen/screen.hpp" // for Screen #include "gtest/gtest_pred_impl.h" // for Test, EXPECT_EQ, TEST -using namespace ftxui; using namespace ftxui; std::string rotate(std::string str) {