From 11519ef1c6941a8e4a5c949f81bbdccafb2accaa Mon Sep 17 00:00:00 2001 From: Arthur Sonzogni Date: Sun, 22 May 2022 21:41:29 +0200 Subject: [PATCH] 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. --- CHANGELOG.md | 7 +++++ include/ftxui/screen/box.hpp | 1 + src/ftxui/component/screen_interactive.cpp | 3 +- src/ftxui/dom/dbox.cpp | 1 + src/ftxui/dom/flexbox.cpp | 32 ++++++++++++++++++---- src/ftxui/dom/flexbox_test.cpp | 23 ++++++++++++++++ src/ftxui/dom/hbox.cpp | 1 + src/ftxui/dom/vbox.cpp | 1 + src/ftxui/screen/box.cpp | 12 ++++++++ src/ftxui/screen/terminal.cpp | 4 +-- 10 files changed, 77 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 817f5b8..55a255e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ Changelog 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 ----- diff --git a/include/ftxui/screen/box.hpp b/include/ftxui/screen/box.hpp index 9edcd9e..7e0dcdc 100644 --- a/include/ftxui/screen/box.hpp +++ b/include/ftxui/screen/box.hpp @@ -10,6 +10,7 @@ struct Box { int y_max = 0; static auto Intersection(Box a, Box b) -> Box; + static auto Union(Box a, Box b) -> Box; bool Contain(int x, int y) const; bool operator==(const Box& other) const; bool operator!=(const Box& other) const; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 674276f..88609b2 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -297,8 +297,9 @@ ScreenInteractive ScreenInteractive::FitComponent() { void ScreenInteractive::Post(Task task) { // Task/Events sent toward inactive screen or screen waiting to become // inactive are dropped. - if (!task_sender_) + if (!task_sender_) { return; + } task_sender_->Send(std::move(task)); } diff --git a/src/ftxui/dom/dbox.cpp b/src/ftxui/dom/dbox.cpp index bf1c244..2c4ec26 100644 --- a/src/ftxui/dom/dbox.cpp +++ b/src/ftxui/dom/dbox.cpp @@ -21,6 +21,7 @@ class DBox : public Node { requirement_.flex_grow_y = 0; requirement_.flex_shrink_x = 0; requirement_.flex_shrink_y = 0; + requirement_.selection = Requirement::NORMAL; for (auto& child : children_) { child->ComputeRequirement(); requirement_.min_x = diff --git a/src/ftxui/dom/flexbox.cpp b/src/ftxui/dom/flexbox.cpp index 0c4bb3a..b2ebb9b 100644 --- a/src/ftxui/dom/flexbox.cpp +++ b/src/ftxui/dom/flexbox.cpp @@ -99,41 +99,63 @@ class Flexbox : public Node { } 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()) { - requirement_.min_x = 0; - requirement_.min_y = 0; return; } + // Compute the union of all the blocks: Box box; box.x_min = global.blocks[0].x; box.y_min = global.blocks[0].y; box.x_max = global.blocks[0].x + global.blocks[0].dim_x; box.y_max = global.blocks[0].y + global.blocks[0].dim_y; - for (auto& b : global.blocks) { box.x_min = std::min(box.x_min, b.x); box.y_min = std::min(box.y_min, b.y); 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); } - requirement_.min_x = box.x_max - box.x_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 { Node::SetBox(box); + int asked_previous = asked_; asked_ = std::min(asked_, IsColumnOriented() ? box.y_max - box.y_min + 1 : box.x_max - box.x_min + 1); + need_iteration_ = (asked_ != asked_previous); + flexbox_helper::Global global; global.config = config_; global.size_x = box.x_max - box.x_min + 1; global.size_y = box.y_max - box.y_min + 1; Layout(global); - need_iteration_ = false; for (size_t i = 0; i < children_.size(); ++i) { auto& child = children_[i]; auto& b = global.blocks[i]; diff --git a/src/ftxui/dom/flexbox_test.cpp b/src/ftxui/dom/flexbox_test.cpp index 78fff80..b5175f6 100644 --- a/src/ftxui/dom/flexbox_test.cpp +++ b/src/ftxui/dom/flexbox_test.cpp @@ -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 // Copyright 2021 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/dom/hbox.cpp b/src/ftxui/dom/hbox.cpp index 75bba74..d268439 100644 --- a/src/ftxui/dom/hbox.cpp +++ b/src/ftxui/dom/hbox.cpp @@ -23,6 +23,7 @@ class HBox : public Node { requirement_.flex_grow_y = 0; requirement_.flex_shrink_x = 0; requirement_.flex_shrink_y = 0; + requirement_.selection = Requirement::NORMAL; for (auto& child : children_) { child->ComputeRequirement(); if (requirement_.selection < child->requirement().selection) { diff --git a/src/ftxui/dom/vbox.cpp b/src/ftxui/dom/vbox.cpp index ecd63da..5b4c331 100644 --- a/src/ftxui/dom/vbox.cpp +++ b/src/ftxui/dom/vbox.cpp @@ -23,6 +23,7 @@ class VBox : public Node { requirement_.flex_grow_y = 0; requirement_.flex_shrink_x = 0; requirement_.flex_shrink_y = 0; + requirement_.selection = Requirement::NORMAL; for (auto& child : children_) { child->ComputeRequirement(); if (requirement_.selection < child->requirement().selection) { diff --git a/src/ftxui/screen/box.cpp b/src/ftxui/screen/box.cpp index 4b96ff7..15a60ba 100644 --- a/src/ftxui/screen/box.cpp +++ b/src/ftxui/screen/box.cpp @@ -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. /// @ingroup screen bool Box::Contain(int x, int y) const { diff --git a/src/ftxui/screen/terminal.cpp b/src/ftxui/screen/terminal.cpp index 971db34..4ea48a6 100644 --- a/src/ftxui/screen/terminal.cpp +++ b/src/ftxui/screen/terminal.cpp @@ -20,8 +20,8 @@ namespace ftxui { namespace { -bool g_cached = false; -Terminal::Color g_cached_supported_color; +bool g_cached = false; // NOLINT +Terminal::Color g_cached_supported_color; // NOLINT Dimensions& FallbackSize() { #if defined(__EMSCRIPTEN__)