Add flex_grow and flex_shrink.

Two new elements:
- flex_grow  : Expand the element to occupy free space.
- flex_shrink: Minimize the element leave away missing space.

flex = flex_grow | flex_shrink.

Other changes:
- hbox and vbox are now non flexible by default.
- the vtext element has been added to help writting tests.
- Many new tests.
This commit is contained in:
ArthurSonzogni 2020-06-01 23:40:32 +02:00 committed by Arthur Sonzogni
parent 7f7775ba62
commit 08ee49f3e6
17 changed files with 929 additions and 189 deletions

View File

@ -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

View File

@ -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() {

View File

@ -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;

View File

@ -21,6 +21,7 @@ using GraphFunction = std::function<std::vector<int>(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 };

View File

@ -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 {

View File

@ -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

View File

@ -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 =

View File

@ -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<Flex>();
return std::make_shared<Flex>(function_flex);
}
Element flex(Element child) {
return std::make_shared<Flex>(std::move(child));
return std::make_shared<Flex>(function_flex, std::move(child));
}
Element flex_grow(Element child) {
return std::make_shared<Flex>(function_flex_grow, std::move(child));
}
Element flex_shrink(Element child) {
return std::make_shared<Flex>(function_flex_shrink, std::move(child));
}
Element notflex(Element child) {
return std::make_shared<NotFlex>(std::move(child));
return std::make_shared<Flex>(function_not_flex, std::move(child));
}
} // namespace ftxui

View File

@ -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;
}

View File

@ -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 {

View File

@ -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;

View File

@ -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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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());
}
}

View File

@ -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();
}

View File

@ -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 {

View File

@ -4,6 +4,7 @@
#include "ftxui/dom/node.hpp"
#include "ftxui/screen/string.hpp"
#include <algorithm>
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>(text);
}
Element vtext(std::wstring text) {
return std::make_shared<VText>(text);
}
} // namespace ftxui

View File

@ -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;

View File

@ -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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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()));
}
}