Fix focus vs flexbox interaction. (#405)

- Fix focus in flexbox. This required resetting the focus state at the
  beginning of the ComputeRequirement(), because it can now run several
  times.

  This resolves:https://github.com/ArthurSonzogni/FTXUI/issues/399

- Add Box::Union.

- Add a preliminary implementation of forwarding selected_box from
  within the flexbox.
This commit is contained in:
Arthur Sonzogni 2022-05-22 21:41:29 +02:00 committed by GitHub
parent f9256fa132
commit 11519ef1c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 77 additions and 8 deletions

View File

@ -4,6 +4,13 @@ Changelog
current (development) current (development)
--------------------- ---------------------
### DOM
- Bugfix: Fix `focus`/`select` when the `vbox`/`hbox`/`dbox` contains a
`flexbox`
### Screen
- Feature: add `Box::Union(a,b) -> Box`
3.0.0 3.0.0
----- -----

View File

@ -10,6 +10,7 @@ struct Box {
int y_max = 0; int y_max = 0;
static auto Intersection(Box a, Box b) -> Box; static auto Intersection(Box a, Box b) -> Box;
static auto Union(Box a, Box b) -> Box;
bool Contain(int x, int y) const; bool Contain(int x, int y) const;
bool operator==(const Box& other) const; bool operator==(const Box& other) const;
bool operator!=(const Box& other) const; bool operator!=(const Box& other) const;

View File

@ -297,8 +297,9 @@ ScreenInteractive ScreenInteractive::FitComponent() {
void ScreenInteractive::Post(Task task) { void ScreenInteractive::Post(Task task) {
// Task/Events sent toward inactive screen or screen waiting to become // Task/Events sent toward inactive screen or screen waiting to become
// inactive are dropped. // inactive are dropped.
if (!task_sender_) if (!task_sender_) {
return; return;
}
task_sender_->Send(std::move(task)); task_sender_->Send(std::move(task));
} }

View File

@ -21,6 +21,7 @@ class DBox : public Node {
requirement_.flex_grow_y = 0; requirement_.flex_grow_y = 0;
requirement_.flex_shrink_x = 0; requirement_.flex_shrink_x = 0;
requirement_.flex_shrink_y = 0; requirement_.flex_shrink_y = 0;
requirement_.selection = Requirement::NORMAL;
for (auto& child : children_) { for (auto& child : children_) {
child->ComputeRequirement(); child->ComputeRequirement();
requirement_.min_x = requirement_.min_x =

View File

@ -99,41 +99,63 @@ class Flexbox : public Node {
} }
Layout(global, true); Layout(global, true);
// Reset:
requirement_.selection = Requirement::Selection::NORMAL;
requirement_.selected_box = Box();
requirement_.min_x = 0;
requirement_.min_y = 0;
if (global.blocks.empty()) { if (global.blocks.empty()) {
requirement_.min_x = 0;
requirement_.min_y = 0;
return; return;
} }
// Compute the union of all the blocks:
Box box; Box box;
box.x_min = global.blocks[0].x; box.x_min = global.blocks[0].x;
box.y_min = global.blocks[0].y; box.y_min = global.blocks[0].y;
box.x_max = global.blocks[0].x + global.blocks[0].dim_x; box.x_max = global.blocks[0].x + global.blocks[0].dim_x;
box.y_max = global.blocks[0].y + global.blocks[0].dim_y; box.y_max = global.blocks[0].y + global.blocks[0].dim_y;
for (auto& b : global.blocks) { for (auto& b : global.blocks) {
box.x_min = std::min(box.x_min, b.x); box.x_min = std::min(box.x_min, b.x);
box.y_min = std::min(box.y_min, b.y); box.y_min = std::min(box.y_min, b.y);
box.x_max = std::max(box.x_max, b.x + b.dim_x); box.x_max = std::max(box.x_max, b.x + b.dim_x);
box.y_max = std::max(box.y_max, b.y + b.dim_y); box.y_max = std::max(box.y_max, b.y + b.dim_y);
} }
requirement_.min_x = box.x_max - box.x_min; requirement_.min_x = box.x_max - box.x_min;
requirement_.min_y = box.y_max - box.y_min; requirement_.min_y = box.y_max - box.y_min;
// Find the selection:
for (size_t i = 0; i < children_.size(); ++i) {
if (requirement_.selection >= children_[i]->requirement().selection) {
continue;
}
requirement_.selection = children_[i]->requirement().selection;
Box selected_box = children_[i]->requirement().selected_box;
// Shift |selected_box| according to its position inside this component:
auto& b = global.blocks[i];
selected_box.x_min += b.x;
selected_box.y_min += b.y;
selected_box.x_max += b.x;
selected_box.y_max += b.y;
requirement_.selected_box = Box::Intersection(selected_box, box);
}
} }
void SetBox(Box box) override { void SetBox(Box box) override {
Node::SetBox(box); Node::SetBox(box);
int asked_previous = asked_;
asked_ = std::min(asked_, IsColumnOriented() ? box.y_max - box.y_min + 1 asked_ = std::min(asked_, IsColumnOriented() ? box.y_max - box.y_min + 1
: box.x_max - box.x_min + 1); : box.x_max - box.x_min + 1);
need_iteration_ = (asked_ != asked_previous);
flexbox_helper::Global global; flexbox_helper::Global global;
global.config = config_; global.config = config_;
global.size_x = box.x_max - box.x_min + 1; global.size_x = box.x_max - box.x_min + 1;
global.size_y = box.y_max - box.y_min + 1; global.size_y = box.y_max - box.y_min + 1;
Layout(global); Layout(global);
need_iteration_ = false;
for (size_t i = 0; i < children_.size(); ++i) { for (size_t i = 0; i < children_.size(); ++i) {
auto& child = children_[i]; auto& child = children_[i];
auto& b = global.blocks[i]; auto& b = global.blocks[i];

View File

@ -432,6 +432,29 @@ TEST(FlexboxTest, GapY) {
" "); " ");
} }
TEST(FlexboxTest, Focus) {
auto document = vbox({
paragraph("0 -"),
paragraph("1 -"),
paragraph("2 -"),
paragraph("3 -"),
paragraph("4 -"),
paragraph("5 -"),
paragraph("6 -"),
paragraph("7 -") | focus,
paragraph("8 -"),
paragraph("9 -"),
}) | yframe | flex;
Screen screen(1, 3);
Render(screen, document);
EXPECT_EQ(screen.ToString(),
"7\r\n"
"-\r\n"
"8"
);
}
} // namespace ftxui } // namespace ftxui
// Copyright 2021 Arthur Sonzogni. All rights reserved. // Copyright 2021 Arthur Sonzogni. All rights reserved.

View File

@ -23,6 +23,7 @@ class HBox : public Node {
requirement_.flex_grow_y = 0; requirement_.flex_grow_y = 0;
requirement_.flex_shrink_x = 0; requirement_.flex_shrink_x = 0;
requirement_.flex_shrink_y = 0; requirement_.flex_shrink_y = 0;
requirement_.selection = Requirement::NORMAL;
for (auto& child : children_) { for (auto& child : children_) {
child->ComputeRequirement(); child->ComputeRequirement();
if (requirement_.selection < child->requirement().selection) { if (requirement_.selection < child->requirement().selection) {

View File

@ -23,6 +23,7 @@ class VBox : public Node {
requirement_.flex_grow_y = 0; requirement_.flex_grow_y = 0;
requirement_.flex_shrink_x = 0; requirement_.flex_shrink_x = 0;
requirement_.flex_shrink_y = 0; requirement_.flex_shrink_y = 0;
requirement_.selection = Requirement::NORMAL;
for (auto& child : children_) { for (auto& child : children_) {
child->ComputeRequirement(); child->ComputeRequirement();
if (requirement_.selection < child->requirement().selection) { if (requirement_.selection < child->requirement().selection) {

View File

@ -15,6 +15,18 @@ Box Box::Intersection(Box a, Box b) {
}; };
} }
/// @return the smallest Box containing both |a| and |b|.
/// @ingroup screen
// static
Box Box::Union(Box a, Box b) {
return Box{
std::min(a.x_min, b.x_min),
std::max(a.x_max, b.x_max),
std::min(a.y_min, b.y_min),
std::max(a.y_max, b.y_max),
};
}
/// @return whether (x,y) is contained inside the box. /// @return whether (x,y) is contained inside the box.
/// @ingroup screen /// @ingroup screen
bool Box::Contain(int x, int y) const { bool Box::Contain(int x, int y) const {

View File

@ -20,8 +20,8 @@ namespace ftxui {
namespace { namespace {
bool g_cached = false; bool g_cached = false; // NOLINT
Terminal::Color g_cached_supported_color; Terminal::Color g_cached_supported_color; // NOLINT
Dimensions& FallbackSize() { Dimensions& FallbackSize() {
#if defined(__EMSCRIPTEN__) #if defined(__EMSCRIPTEN__)