diff --git a/README.md b/README.md index 6bd1aa0..5167ac0 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,10 @@ A simple C++ library for terminal based user interface. hbox({ text(L"left") | border, text(L"middle") | border | flex, - text(L"right") | border + text(L"right") | border, }), - gauge(0.5) | border - }) + gauge(0.5) | border, + }); ~~~ ~~~bash diff --git a/examples/component/homescreen.cpp b/examples/component/homescreen.cpp index 6635842..834faaf 100644 --- a/examples/component/homescreen.cpp +++ b/examples/component/homescreen.cpp @@ -118,7 +118,7 @@ class CompilerComponent : public Component { L"gcc", L"clang", L"emcc", - L"game_maker" + L"game_maker", L"Ada compilers", L"ALGOL 60 compilers", L"ALGOL 68 compilers", @@ -212,9 +212,9 @@ class CompilerComponent : public Component { }), filler(), }), - hflow(RenderCommandLine()), + hflow(RenderCommandLine()) | flex_grow, }) | - border; + flex_grow | border; } Elements RenderCommandLine() { diff --git a/examples/component/toggle.cpp b/examples/component/toggle.cpp index c21289d..38fe434 100644 --- a/examples/component/toggle.cpp +++ b/examples/component/toggle.cpp @@ -49,7 +49,7 @@ class MyComponent : public Component { }); } - bool OnEvent(Event event) { + bool OnEvent(Event event) override { if (event == Event::Return) { on_enter(); return true; diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp index ca2c663..1ba984e 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -21,6 +21,7 @@ using GraphFunction = std::function(int, int)>; // --- Widget --- Element text(std::wstring text); +Element vtext(std::wstring text); Element separator(void); Element separator(Pixel); Element gauge(float ratio); @@ -52,9 +53,11 @@ Element hflow(Elements); // -- Flexibility --- // Define how to share the remaining space when not all of it is used inside a // container. -Element filler(); -Element flex(Element); -Element notflex(Element); +Element flex(Element); // Expand/Minimize if possible/needed. +Element flex_grow(Element); // Expand element if possible. +Element flex_shrink(Element); // Minimize element if needed. +Element notflex(Element); // Reset the flex attribute. +Element filler(); // A blank expandable element. // -- Size override; enum Direction { WIDTH, HEIGHT }; diff --git a/include/ftxui/dom/requirement.hpp b/include/ftxui/dom/requirement.hpp index 84d4419..c781c80 100644 --- a/include/ftxui/dom/requirement.hpp +++ b/include/ftxui/dom/requirement.hpp @@ -15,8 +15,10 @@ struct Requirement { int min_y = 0; // How much flexibility is given to the component. - int flex_x = 0; - int flex_y = 0; + int flex_grow_x = 0; + int flex_grow_y = 0; + int flex_shrink_x = 0; + int flex_shrink_y = 0; // Focus management to support the frame/focus/select element. enum Selection { diff --git a/src/ftxui/dom/composite_decorator.cpp b/src/ftxui/dom/composite_decorator.cpp index 3088bb5..23deb58 100644 --- a/src/ftxui/dom/composite_decorator.cpp +++ b/src/ftxui/dom/composite_decorator.cpp @@ -8,19 +8,19 @@ namespace ftxui { Element hcenter(Element child) { - return hbox(filler(), std::move(child), filler()); + return hbox(filler(), std::move(child), filler()) | flex_grow; } Element vcenter(Element child) { - return vbox(filler(), std::move(child), filler()); + return vbox(filler(), std::move(child), filler()) | flex_grow; } Element center(Element child) { - return hcenter(vcenter(std::move(child))); + return hcenter(vcenter(std::move(child))) | flex_grow; } Element align_right(Element child) { - return hbox(filler(), std::move(child)); + return hbox(filler(), std::move(child)) | flex_grow; } } // namespace ftxui diff --git a/src/ftxui/dom/dbox.cpp b/src/ftxui/dom/dbox.cpp index 4a1f91b..a0fbc3f 100644 --- a/src/ftxui/dom/dbox.cpp +++ b/src/ftxui/dom/dbox.cpp @@ -17,8 +17,10 @@ class DBox : public Node { void ComputeRequirement() override { requirement_.min_x = 0; requirement_.min_y = 0; - requirement_.flex_x = 1; - requirement_.flex_y = 0; + requirement_.flex_grow_x = 0; + requirement_.flex_grow_y = 0; + requirement_.flex_shrink_x = 0; + requirement_.flex_shrink_y = 0; for (auto& child : children) { child->ComputeRequirement(); requirement_.min_x = diff --git a/src/ftxui/dom/flex.cpp b/src/ftxui/dom/flex.cpp index 10d6bed..48e1cec 100644 --- a/src/ftxui/dom/flex.cpp +++ b/src/ftxui/dom/flex.cpp @@ -7,10 +7,36 @@ namespace ftxui { +using FlexFunction = void (*)(Requirement&); + +void function_flex_grow(Requirement& r) { + r.flex_grow_x = 1; + r.flex_grow_y = 1; +} + +void function_flex_shrink(Requirement& r) { + r.flex_shrink_x = 1; + r.flex_shrink_y = 1; +} + +void function_flex(Requirement& r) { + r.flex_grow_x = 1; + r.flex_grow_y = 1; + r.flex_shrink_x = 1; + r.flex_shrink_y = 1; +} + +void function_not_flex(Requirement& r) { + r.flex_grow_x = 0; + r.flex_grow_y = 0; + r.flex_shrink_x = 0; + r.flex_shrink_y = 0; +} + class Flex : public Node { public: - Flex() {} - Flex(Element child) : Node(unpack(std::move(child))) {} + Flex(FlexFunction f) { f_ = f; } + Flex(FlexFunction f, Element child) : Node(unpack(std::move(child))), f_(f) {} ~Flex() override {} void ComputeRequirement() override { requirement_.min_x = 0; @@ -19,8 +45,7 @@ class Flex : public Node { children[0]->ComputeRequirement(); requirement_ = children[0]->requirement(); } - requirement_.flex_x = 1; - requirement_.flex_y = 1; + f_(requirement_); } void SetBox(Box box) override { @@ -28,35 +53,28 @@ class Flex : public Node { return; children[0]->SetBox(box); } -}; -class NotFlex : public Flex { - public: - NotFlex() {} - NotFlex(Element child) : Flex(std::move(child)) {} - ~NotFlex() override {} - void ComputeRequirement() override { - requirement_.min_x = 0; - requirement_.min_y = 0; - if (!children.empty()) { - children[0]->ComputeRequirement(); - requirement_ = children[0]->requirement(); - } - requirement_.flex_x = 0; - requirement_.flex_y = 0; - } + FlexFunction f_; }; Element filler() { - return std::make_shared(); + return std::make_shared(function_flex); } Element flex(Element child) { - return std::make_shared(std::move(child)); + return std::make_shared(function_flex, std::move(child)); +} + +Element flex_grow(Element child) { + return std::make_shared(function_flex_grow, std::move(child)); +} + +Element flex_shrink(Element child) { + return std::make_shared(function_flex_shrink, std::move(child)); } Element notflex(Element child) { - return std::make_shared(std::move(child)); + return std::make_shared(function_not_flex, std::move(child)); } } // namespace ftxui diff --git a/src/ftxui/dom/gauge.cpp b/src/ftxui/dom/gauge.cpp index f7c174b..be4fe67 100644 --- a/src/ftxui/dom/gauge.cpp +++ b/src/ftxui/dom/gauge.cpp @@ -15,7 +15,11 @@ class Gauge : public Node { ~Gauge() override {} void ComputeRequirement() override { - requirement_.flex_x = 1; + requirement_.flex_grow_x = 1; + requirement_.flex_grow_y = 0; + requirement_.flex_shrink_x = 1; + requirement_.flex_shrink_y = 0; + requirement_.min_x = 1; requirement_.min_y = 1; } diff --git a/src/ftxui/dom/graph.cpp b/src/ftxui/dom/graph.cpp index 81ff081..966939f 100644 --- a/src/ftxui/dom/graph.cpp +++ b/src/ftxui/dom/graph.cpp @@ -14,10 +14,12 @@ class Graph : public Node { ~Graph() override {} void ComputeRequirement() override { - requirement_.flex_x = 1; - requirement_.flex_y = 1; - requirement_.min_x = 1; - requirement_.min_y = 1; + requirement_.flex_grow_x = 1; + requirement_.flex_grow_y = 1; + requirement_.flex_shrink_x = 1; + requirement_.flex_shrink_y = 1; + requirement_.min_x = 3; + requirement_.min_y = 3; } void Render(Screen& screen) override { diff --git a/src/ftxui/dom/hbox.cpp b/src/ftxui/dom/hbox.cpp index 9b702ed..707e12e 100644 --- a/src/ftxui/dom/hbox.cpp +++ b/src/ftxui/dom/hbox.cpp @@ -17,8 +17,10 @@ class HBox : public Node { void ComputeRequirement() override { requirement_.min_x = 0; requirement_.min_y = 0; - requirement_.flex_x = 1; - requirement_.flex_y = 0; + requirement_.flex_grow_x = 0; + requirement_.flex_grow_y = 0; + requirement_.flex_shrink_x = 0; + requirement_.flex_shrink_y = 0; for (auto& child : children) { child->ComputeRequirement(); if (requirement_.selection < child->requirement().selection) { @@ -36,31 +38,89 @@ class HBox : public Node { void SetBox(Box box) override { Node::SetBox(box); - int flex_sum = 0; - for (auto& child : children) - flex_sum += child->requirement().flex_x; - int space = box.x_max - box.x_min + 1; int extra_space = space - requirement_.min_x; - int remaining_flex = flex_sum; - int remaining_extra_space = extra_space; + int size = 0; + int flex_grow_sum = 0; + int flex_shrink_sum = 0; + int flex_shrink_size = 0; + for (auto& child : children) { + const Requirement& r = child->requirement(); + flex_grow_sum += r.flex_grow_x; + flex_shrink_sum += r.min_x * r.flex_shrink_x; + if (r.flex_shrink_x) { + flex_shrink_size += r.min_x; + } + size += r.min_x; + } + if (extra_space >= 0) + SetBoxGrow(box, extra_space, flex_grow_sum); + else if (flex_shrink_size + extra_space >= 0) + SetBoxShrinkEasy(box, extra_space, flex_shrink_sum); + else + SetBoxShrinkHard(box, extra_space + flex_shrink_size, + size - flex_shrink_size); + } + + void SetBoxGrow(Box box, int extra_space, int flex_grow_sum) { int x = box.x_min; for (auto& child : children) { Box child_box = box; + const Requirement& r = child->requirement(); + + int added_space = + extra_space * r.flex_grow_x / std::max(flex_grow_sum, 1); + extra_space -= added_space; + flex_grow_sum -= r.flex_grow_x; + child_box.x_min = x; + child_box.x_max = x + r.min_x + added_space - 1; - child_box.x_max = x + child->requirement().min_x - 1; + child->SetBox(child_box); + x = child_box.x_max + 1; + } + } - if (child->requirement().flex_x) { - int added_space = remaining_extra_space * child->requirement().flex_x / - remaining_flex; - remaining_extra_space -= added_space; - remaining_flex -= child->requirement().flex_x; - child_box.x_max += added_space; + void SetBoxShrinkEasy(Box box, int extra_space, int flex_shrink_sum) { + int x = box.x_min; + for (auto& child : children) { + Box child_box = box; + const Requirement& r = child->requirement(); + + int added_space = extra_space * r.min_x * r.flex_shrink_x / + std::max(flex_shrink_sum, 1); + extra_space -= added_space; + flex_shrink_sum -= r.flex_shrink_x * r.min_x; + + child_box.x_min = x; + child_box.x_max = x + r.min_x + added_space - 1; + + child->SetBox(child_box); + x = child_box.x_max + 1; + } + } + + void SetBoxShrinkHard(Box box, int extra_space, int size) { + int x = box.x_min; + for (auto& child : children) { + Box child_box = box; + const Requirement& r = child->requirement(); + + if (r.flex_shrink_x) { + child_box.x_min = x; + child_box.x_max = x - 1; + child->SetBox(child_box); + continue; } - child_box.x_max = std::min(child_box.x_max, box.x_max); + + int added_space = extra_space * r.min_x / std::max(1, size); + extra_space -= added_space; + size -= r.min_x; + + child_box.x_min = x; + child_box.x_max = x + r.min_x + added_space - 1; child->SetBox(child_box); x = child_box.x_max + 1; diff --git a/src/ftxui/dom/hbox_test.cpp b/src/ftxui/dom/hbox_test.cpp index f7b55f9..63ffec0 100644 --- a/src/ftxui/dom/hbox_test.cpp +++ b/src/ftxui/dom/hbox_test.cpp @@ -9,81 +9,347 @@ using namespace ftxui; using namespace ftxui; -TEST(HBoxTest, ScreenSmaller1) { - auto root = hbox(text(L"text_1"), text(L"text_2")); - Screen screen(11, 1); - Render(screen, root); +TEST(HBoxTest, NoFlex_NoFlex_NoFlex) { + auto root = hbox({ + text(L"012"), + text(L"abc"), + text(L"ABC"), + }); - EXPECT_EQ("text_1text_", screen.ToString()); + 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(HBoxTest, ScreenSmaller2) { - auto root = hbox(text(L"text_1"), text(L"text_2")); - Screen screen(10, 1); - Render(screen, root); +TEST(HBoxTest, FlexGrow_NoFlex_NoFlex) { + auto root = hbox({ + text(L"012") | flex_grow, + text(L"abc"), + text(L"ABC"), + }); - EXPECT_EQ("text_1text", screen.ToString()); + 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(HBoxTest, ScreenFit) { - auto root = hbox(text(L"text_1"), text(L"text_2")); - Screen screen(12, 1); - Render(screen, root); +TEST(HBoxTest, NoFlex_FlexGrow_NoFlex) { + auto root = hbox({ + text(L"012"), + text(L"abc") | flex_grow, + text(L"ABC"), + }); - EXPECT_EQ("text_1text_2", screen.ToString()); + 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(HBoxTest, ScreenBigger1) { - auto root = hbox(text(L"text_1"), text(L"text_2")); - Screen screen(13, 1); - Render(screen, root); +TEST(HBoxTest, NoFlex_NoFlex_FlexGrow) { + auto root = hbox({ + text(L"012"), + text(L"abc"), + text(L"ABC") | flex_grow, + }); - EXPECT_EQ("text_1text_2 ", screen.ToString()); -} -TEST(HBoxTest, ScreenBigger2) { - auto root = hbox(text(L"text_1"), text(L"text_2")); - Screen screen(14, 1); - Render(screen, root); - - EXPECT_EQ("text_1text_2 ", screen.ToString()); + 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(HBoxTest, ScreenSmaller1Flex) { - auto root = hbox(text(L"text_1"), filler(), text(L"text_2")); - Screen screen(11, 1); - Render(screen, root); +TEST(HBoxTest, FlexGrow_NoFlex_FlexGrow) { + auto root = hbox({ + text(L"012") | flex_grow, + text(L"abc"), + text(L"ABC") | flex_grow, + }); - EXPECT_EQ("text_text_2", screen.ToString()); + 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(HBoxTest, ScreenSmaller2Flex) { - auto root = hbox(text(L"text_1"), filler(), text(L"text_2")); - Screen screen(10, 1); - Render(screen, root); +TEST(HBoxTest, FlexGrow_FlexGrow_FlexGrow) { + auto root = hbox({ + text(L"012") | flex_grow, + text(L"abc") | flex_grow, + text(L"ABC") | flex_grow, + }); - EXPECT_EQ("texttext_2", screen.ToString()); + 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(HBoxTest, ScreenFitFlex) { - auto root = hbox(text(L"text_1"), filler(), text(L"text_2")); - Screen screen(12, 1); - Render(screen, root); +// ------ - EXPECT_EQ("text_1text_2", screen.ToString()); +TEST(HBoxTest, FlexShrink_NoFlex_NoFlex) { + auto root = hbox({ + text(L"012") | flex_shrink, + text(L"abc"), + text(L"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(HBoxTest, ScreenBigger1Flex) { - auto root = hbox(text(L"text_1"), filler(), text(L"text_2")); - Screen screen(13, 1); - Render(screen, root); +TEST(HBoxTest, NoFlex_FlexShrink_NoFlex) { + auto root = hbox({ + text(L"012"), + text(L"abc") | flex_shrink, + text(L"ABC"), + }); - EXPECT_EQ("text_1 text_2", screen.ToString()); + 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(HBoxTest, ScreenBigger2Flex) { - auto root = hbox(text(L"text_1"), filler(), text(L"text_2")); - Screen screen(14, 1); - Render(screen, root); +TEST(HBoxTest, NoFlex_NoFlex_FlexShrink) { + auto root = hbox({ + text(L"012"), + text(L"abc"), + text(L"ABC") | flex_shrink, + }); - EXPECT_EQ("text_1 text_2", screen.ToString()); + 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(HBoxTest, FlexShrink_NoFlex_FlexShrink) { + auto root = hbox({ + text(L"012") | flex_shrink, + text(L"abc"), + text(L"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(HBoxTest, FlexShrink_FlexShrink_FlexShrink) { + auto root = hbox({ + text(L"012") | flex_shrink, + text(L"abc") | flex_shrink, + text(L"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(HBoxTest, FlexGrow_NoFlex_FlewShrink) { + auto root = hbox({ + text(L"012") | flex_grow, + text(L"abc"), + text(L"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()); + } } diff --git a/src/ftxui/dom/hflow.cpp b/src/ftxui/dom/hflow.cpp index fb0d3ca..0b9e54e 100644 --- a/src/ftxui/dom/hflow.cpp +++ b/src/ftxui/dom/hflow.cpp @@ -15,10 +15,12 @@ class HFlow : public Node { ~HFlow() {} void ComputeRequirement() override { - requirement_.min_x = 0; - requirement_.min_y = 0; - requirement_.flex_x = 1; - requirement_.flex_y = 1; + requirement_.min_x = 1; + requirement_.min_y = 1; + requirement_.flex_grow_x = 1; + requirement_.flex_grow_y = 1; + requirement_.flex_shrink_x = 0; + requirement_.flex_shrink_y = 0; for (auto& child : children) child->ComputeRequirement(); } diff --git a/src/ftxui/dom/size.cpp b/src/ftxui/dom/size.cpp index 1a59ca0..e21bbe1 100644 --- a/src/ftxui/dom/size.cpp +++ b/src/ftxui/dom/size.cpp @@ -37,10 +37,13 @@ class Size : public Node { break; } - if (direction_ == WIDTH) - requirement_.flex_x = 0; - else - requirement_.flex_y = 0; + if (direction_ == WIDTH) { + requirement_.flex_grow_x = 0; + requirement_.flex_shrink_x = 0; + } else { + requirement_.flex_grow_y = 0; + requirement_.flex_shrink_y = 0; + } } void SetBox(Box box) override { diff --git a/src/ftxui/dom/text.cpp b/src/ftxui/dom/text.cpp index ec38a24..2ed6735 100644 --- a/src/ftxui/dom/text.cpp +++ b/src/ftxui/dom/text.cpp @@ -4,6 +4,7 @@ #include "ftxui/dom/node.hpp" #include "ftxui/screen/string.hpp" +#include namespace ftxui { @@ -36,8 +37,43 @@ class Text : public Node { std::wstring text_; }; +class VText : public Node { + public: + VText(std::wstring text) : Node(), text_(text) { + for (auto& c : text_) + width_ = std::max(width_, wchar_width(c)); + } + ~VText() {} + + void ComputeRequirement() override { + requirement_.min_x = width_; + requirement_.min_y = text_.size(); + } + + void Render(Screen& screen) override { + int x = box_.x_min; + int y = box_.y_min; + if (x + width_ - 1 > box_.x_max) + return; + for (wchar_t c : text_) { + if (y > box_.y_max) + return; + screen.at(x, y) = c; + y += 1; + } + } + + private: + std::wstring text_; + int width_ = 1; +}; + Element text(std::wstring text) { return std::make_shared(text); } +Element vtext(std::wstring text) { + return std::make_shared(text); +} + } // namespace ftxui diff --git a/src/ftxui/dom/vbox.cpp b/src/ftxui/dom/vbox.cpp index 8f67189..cfd2d5d 100644 --- a/src/ftxui/dom/vbox.cpp +++ b/src/ftxui/dom/vbox.cpp @@ -15,11 +15,13 @@ class VBox : public Node { VBox(Elements children) : Node(std::move(children)) {} ~VBox() {} - void ComputeRequirement() { + void ComputeRequirement() override { requirement_.min_x = 0; requirement_.min_y = 0; - requirement_.flex_x = 0; - requirement_.flex_y = 1; + requirement_.flex_grow_x = 0; + requirement_.flex_grow_y = 0; + requirement_.flex_shrink_x = 0; + requirement_.flex_shrink_y = 0; for (auto& child : children) { child->ComputeRequirement(); if (requirement_.selection < child->requirement().selection) { @@ -34,34 +36,92 @@ class VBox : public Node { } } - void SetBox(Box box) { + void SetBox(Box box) override { Node::SetBox(box); - int flex_sum = 0; - for (auto& child : children) - flex_sum += child->requirement().flex_y; - int space = box.y_max - box.y_min + 1; int extra_space = space - requirement_.min_y; - int remaining_flex = flex_sum; - int remaining_extra_space = extra_space; + int size = 0; + int flex_grow_sum = 0; + int flex_shrink_sum = 0; + int flex_shrink_size = 0; + for (auto& child : children) { + const Requirement& r = child->requirement(); + flex_grow_sum += r.flex_grow_y; + flex_shrink_sum += r.min_y * r.flex_shrink_y; + if (r.flex_shrink_y) { + flex_shrink_size += r.min_y; + } + size += r.min_y; + } + if (extra_space >= 0) + SetBoxGrow(box, extra_space, flex_grow_sum); + else if (flex_shrink_size + extra_space >= 0) + SetBoxShrinkEasy(box, extra_space, flex_shrink_sum); + else + SetBoxShrinkHard(box, extra_space + flex_shrink_size, + size - flex_shrink_size); + } + + void SetBoxGrow(Box box, int extra_space, int flex_grow_sum) { int y = box.y_min; for (auto& child : children) { Box child_box = box; + const Requirement& r = child->requirement(); + + int added_space = + extra_space * r.flex_grow_y / std::max(flex_grow_sum, 1); + extra_space -= added_space; + flex_grow_sum -= r.flex_grow_y; + child_box.y_min = y; + child_box.y_max = y + r.min_y + added_space - 1; - child_box.y_max = y + child->requirement().min_y - 1; + child->SetBox(child_box); + y = child_box.y_max + 1; + } + } - if (child->requirement().flex_y) { - int added_space = remaining_extra_space * child->requirement().flex_y / - remaining_flex; - remaining_extra_space -= added_space; - remaining_flex -= child->requirement().flex_y; - child_box.y_max += added_space; + void SetBoxShrinkEasy(Box box, int extra_space, int flex_shrink_sum) { + int y = box.y_min; + for (auto& child : children) { + Box child_box = box; + const Requirement& r = child->requirement(); + + int added_space = extra_space * r.min_y * r.flex_shrink_y / + std::max(flex_shrink_sum, 1); + extra_space -= added_space; + flex_shrink_sum -= r.flex_shrink_y * r.min_y; + + child_box.y_min = y; + child_box.y_max = y + r.min_y + added_space - 1; + + child->SetBox(child_box); + y = child_box.y_max + 1; + } + } + + void SetBoxShrinkHard(Box box, int extra_space, int size) { + int y = box.y_min; + for (auto& child : children) { + Box child_box = box; + const Requirement& r = child->requirement(); + + if (r.flex_shrink_y) { + child_box.y_min = y; + child_box.y_max = y - 1; + child->SetBox(child_box); + continue; } - child_box.y_max = std::min(child_box.y_max, box.y_max); + + int added_space = extra_space * r.min_y / std::max(1, size); + extra_space -= added_space; + size -= r.min_y; + + child_box.y_min = y; + child_box.y_max = y + r.min_y + added_space - 1; child->SetBox(child_box); y = child_box.y_max + 1; diff --git a/src/ftxui/dom/vbox_test.cpp b/src/ftxui/dom/vbox_test.cpp index dd2abaa..3afad30 100644 --- a/src/ftxui/dom/vbox_test.cpp +++ b/src/ftxui/dom/vbox_test.cpp @@ -9,70 +9,352 @@ using namespace ftxui; using namespace ftxui; -TEST(VBoxTest, ScreenSmaller1) { - auto root = vbox(text(L"text_1"), text(L"text_2")); - Screen screen(6, 1); - Render(screen, root); - - EXPECT_EQ("text_1", screen.ToString()); +std::string rotate(std::string str) { + str.erase(std::remove(str.begin(), str.end(), '\n'), str.end()); + return str; } -TEST(VBoxTest, ScreenFit) { - auto root = vbox(text(L"text_1"), text(L"text_2")); - Screen screen(6, 2); - Render(screen, root); +TEST(VBoxText, NoFlex_NoFlex_NoFlex) { + auto root = vbox({ + vtext(L"012"), + vtext(L"abc"), + vtext(L"ABC"), + }); - EXPECT_EQ("text_1\ntext_2", screen.ToString()); + 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(VBoxTest, ScreenBigger1) { - auto root = vbox(text(L"text_1"), text(L"text_2")); - Screen screen(6, 3); - Render(screen, root); +TEST(VBoxText, FlexGrow_NoFlex_NoFlex) { + auto root = vbox({ + vtext(L"012") | flex_grow, + vtext(L"abc"), + vtext(L"ABC"), + }); - EXPECT_EQ("text_1\ntext_2\n ", screen.ToString()); -} -TEST(VBoxTest, ScreenBigger2) { - auto root = vbox(text(L"text_1"), text(L"text_2")); - Screen screen(6, 4); - Render(screen, root); - - EXPECT_EQ("text_1\ntext_2\n \n ", screen.ToString()); + 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(VBoxTest, ScreenSmaller1Flex) { - auto root = vbox(text(L"text_1"), filler(), text(L"text_2")); - Screen screen(6, 1); - Render(screen, root); +TEST(VBoxText, NoFlex_FlexGrow_NoFlex) { + auto root = vbox({ + vtext(L"012"), + vtext(L"abc") | flex_grow, + vtext(L"ABC"), + }); - EXPECT_EQ("text_2", screen.ToString()); + 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(1, i); + Render(screen, root); + EXPECT_EQ(expectations[i], rotate(screen.ToString())); + } } -TEST(VBoxTest, ScreenFitFlex) { - auto root = vbox(text(L"text_1"), filler(), text(L"text_2")); - Screen screen(7, 5); - Render(screen, root); +TEST(VBoxText, NoFlex_NoFlex_FlexGrow) { + auto root = vbox({ + vtext(L"012"), + vtext(L"abc"), + vtext(L"ABC") | flex_grow, + }); - EXPECT_EQ( - "text_1 \n" - " \n" - " \n" - " \n" - "text_2 ", - screen.ToString()); + 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(VBoxTest, ScreenBigger1Flex) { - auto root = vbox(text(L"text_1"), filler(), text(L"text_2")); - Screen screen(6, 3); - Render(screen, root); +TEST(VBoxText, FlexGrow_NoFlex_FlexGrow) { + auto root = vbox({ + vtext(L"012") | flex_grow, + vtext(L"abc"), + vtext(L"ABC") | flex_grow, + }); - EXPECT_EQ("text_1\n \ntext_2", screen.ToString()); + 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(1, i); + Render(screen, root); + EXPECT_EQ(expectations[i], rotate(screen.ToString())); + } } -TEST(VBoxTest, ScreenBigger2Flex) { - auto root = vbox(text(L"text_1"), filler(), text(L"text_2")); - Screen screen(6, 4); - Render(screen, root); - EXPECT_EQ("text_1\n \n \ntext_2", screen.ToString()); +TEST(VBoxText, FlexGrow_FlexGrow_FlexGrow) { + auto root = vbox({ + vtext(L"012") | flex_grow, + vtext(L"abc") | flex_grow, + vtext(L"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(1, i); + Render(screen, root); + EXPECT_EQ(expectations[i], rotate(screen.ToString())); + } +} + +// ------ + +TEST(VBoxText, FlexShrink_NoFlex_NoFlex) { + auto root = vbox({ + vtext(L"012") | flex_shrink, + vtext(L"abc"), + vtext(L"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(1, i); + Render(screen, root); + EXPECT_EQ(expectations[i], rotate(screen.ToString())); + } +} + +TEST(VBoxText, NoFlex_FlexShrink_NoFlex) { + auto root = vbox({ + vtext(L"012"), + vtext(L"abc") | flex_shrink, + vtext(L"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(1, i); + Render(screen, root); + EXPECT_EQ(expectations[i], rotate(screen.ToString())); + } +} + +TEST(VBoxText, NoFlex_NoFlex_FlexShrink) { + auto root = vbox({ + vtext(L"012"), + vtext(L"abc"), + vtext(L"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(1, i); + Render(screen, root); + EXPECT_EQ(expectations[i], rotate(screen.ToString())); + } +} + +TEST(VBoxText, FlexShrink_NoFlex_FlexShrink) { + auto root = vbox({ + vtext(L"012") | flex_shrink, + vtext(L"abc"), + vtext(L"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(1, i); + Render(screen, root); + EXPECT_EQ(expectations[i], rotate(screen.ToString())); + } +} + +TEST(VBoxText, FlexShrink_FlexShrink_FlexShrink) { + auto root = vbox({ + vtext(L"012") | flex_shrink, + vtext(L"abc") | flex_shrink, + vtext(L"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(1, i); + Render(screen, root); + EXPECT_EQ(expectations[i], rotate(screen.ToString())); + } +} + +TEST(VBoxText, FlexGrow_NoFlex_FlewShrink) { + auto root = vbox({ + vtext(L"012") | flex_grow, + vtext(L"abc"), + vtext(L"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(1, i); + Render(screen, root); + EXPECT_EQ(expectations[i], rotate(screen.ToString())); + } }