From 09e690f8ab059dcbe91d7b122fb26660752dbf4e Mon Sep 17 00:00:00 2001 From: Nicolas Busser Date: Sun, 19 Oct 2025 23:53:33 +0900 Subject: [PATCH] Add `Merge()` specializations to support more `Element` containers (#1117) `Merge()` was previously only supporting `Elements` as a `Element` container. This PR adds specialization for: - all the containers that matches the concept `std::ranges::range` - `std::queue` - `std::stack` Co-authored-by: ArthurSonzogni Bug:https://github.com/ArthurSonzogni/FTXUI/issues/1108 Fixed:https://github.com/ArthurSonzogni/FTXUI/issues/1108 --- CHANGELOG.md | 2 ++ include/ftxui/dom/take_any_args.hpp | 54 ++++++++++++++++++++++------- src/ftxui/component/input.cpp | 2 +- src/ftxui/component/menu.cpp | 4 +-- src/ftxui/component/radiobox.cpp | 2 +- src/ftxui/dom/hbox_test.cpp | 44 +++++++++++++++++++++-- 6 files changed, 88 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bab7f60..0e8427d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ Next ### Dom - Fix integer overflow in `ComputeShrinkHard`. Thanks @its-pablo in #1137 for reporting and fixing the issue. +- Add specialization for `vbox/hbox/dbox` to allow a container of Element as + as input. Thanks @nbusser in #1117. 6.1.9 (2025-05-07) ------------ diff --git a/include/ftxui/dom/take_any_args.hpp b/include/ftxui/dom/take_any_args.hpp index 52c83248..99fbd7ee 100644 --- a/include/ftxui/dom/take_any_args.hpp +++ b/include/ftxui/dom/take_any_args.hpp @@ -5,25 +5,18 @@ #define FTXUI_DOM_TAKE_ANY_ARGS_HPP // IWYU pragma: private, include "ftxui/dom/elements.hpp" +#include #include +#include +#include +#include namespace ftxui { -template -void Merge(Elements& /*container*/, T /*element*/) {} - -template <> inline void Merge(Elements& container, Element element) { container.push_back(std::move(element)); } -template <> -inline void Merge(Elements& container, Elements elements) { - for (auto& element : elements) { - container.push_back(std::move(element)); - } -} - // Turn a set of arguments into a vector. template Elements unpack(Args... args) { @@ -32,11 +25,46 @@ Elements unpack(Args... args) { return vec; } -// Make |container| able to take any number of argments. +// Make |container| able to take any number of arguments. #define TAKE_ANY_ARGS(container) \ template \ - Element container(Args... children) { \ + inline Element container(Args... children) { \ return container(unpack(std::forward(children)...)); \ + } \ + \ + template \ + inline Element container(Container&& children) { \ + Elements elements; \ + for (auto& child : children) { \ + elements.push_back(std::move(child)); \ + } \ + return container(std::move(elements)); \ + } \ + template <> \ + inline Element container(std::stack&& children) { \ + Elements elements; \ + while (!children.empty()) { \ + elements.push_back(std::move(children.top())); \ + children.pop(); \ + } \ + return container(std::move(elements)); \ + } \ + template <> \ + inline Element container(std::queue&& children) { \ + Elements elements; \ + while (!children.empty()) { \ + elements.push_back(std::move(children.front())); \ + children.pop(); \ + } \ + return container(std::move(elements)); \ + } \ + template <> \ + inline Element container(std::deque&& children) { \ + Elements elements; \ + for (auto& child : children) { \ + elements.push_back(std::move(child)); \ + } \ + return container(std::move(elements)); \ } TAKE_ANY_ARGS(vbox) diff --git a/src/ftxui/component/input.cpp b/src/ftxui/component/input.cpp index 6ef53883..4a860bf0 100644 --- a/src/ftxui/component/input.cpp +++ b/src/ftxui/component/input.cpp @@ -173,7 +173,7 @@ class InputBase : public ComponentBase, public InputOption { elements.push_back(element); } - auto element = vbox(std::move(elements), cursor_line) | frame; + auto element = vbox(std::move(elements)) | frame; return transform_func({ std::move(element), hovered_, is_focused, false // placeholder diff --git a/src/ftxui/component/menu.cpp b/src/ftxui/component/menu.cpp index ae9d2483..413aa090 100644 --- a/src/ftxui/component/menu.cpp +++ b/src/ftxui/component/menu.cpp @@ -145,8 +145,8 @@ class MenuBase : public ComponentBase, public MenuOption { } const Element bar = IsHorizontal() - ? hbox(std::move(elements), selected_focus_) - : vbox(std::move(elements), selected_focus_); + ? hbox(std::move(elements)) + : vbox(std::move(elements)); if (!underline.enabled) { return bar | reflect(box_); diff --git a/src/ftxui/component/radiobox.cpp b/src/ftxui/component/radiobox.cpp index f1b1d477..452bc972 100644 --- a/src/ftxui/component/radiobox.cpp +++ b/src/ftxui/component/radiobox.cpp @@ -46,7 +46,7 @@ class RadioboxBase : public ComponentBase, public RadioboxOption { } elements.push_back(element | reflect(boxes_[i])); } - return vbox(std::move(elements), hovered_) | reflect(box_); + return vbox(std::move(elements)) | reflect(box_); } // NOLINTNEXTLINE(readability-function-cognitive-complexity) diff --git a/src/ftxui/dom/hbox_test.cpp b/src/ftxui/dom/hbox_test.cpp index 982bfde4..36132786 100644 --- a/src/ftxui/dom/hbox_test.cpp +++ b/src/ftxui/dom/hbox_test.cpp @@ -2,9 +2,13 @@ // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. #include // for Test, TestInfo (ptr only), EXPECT_EQ, Message, TEST, TestPartResult -#include // for size_t -#include // for allocator, basic_string, string -#include // for vector +#include // for array +#include // for size_t +#include +#include // for stack +#include // for allocator, basic_string, string +#include // for unordered_set +#include // for vector #include "ftxui/dom/elements.hpp" // for text, operator|, Element, flex_grow, flex_shrink, hbox #include "ftxui/dom/node.hpp" // for Render @@ -358,5 +362,39 @@ TEST(HBoxTest, FlexGrow_NoFlex_FlewShrink) { } } +TEST(HBoxTest, FromElementsContainer) { + Elements elements_vector{text("0"), text("1")}; + + std::array elements_array{text("0"), text("1")}; + + std::deque elements_deque{text("0"), text("1")}; + + std::stack elements_stack; + elements_stack.push(text("1")); + elements_stack.push(text("0")); + + std::queue elements_queue; + elements_queue.emplace(text("0")); + elements_queue.emplace(text("1")); + + const std::vector collection_hboxes{ + hbox(std::move(elements_vector)), hbox(std::move(elements_array)), + hbox(std::move(elements_stack)), hbox(std::move(elements_deque)), + hbox(std::move(elements_queue)), + }; + + for (const Element& collection_hbox : collection_hboxes) { + Screen screen(2, 1); + Render(screen, collection_hbox); + EXPECT_EQ("01", screen.ToString()); + } + + // Exception: unordered set, which has no guaranteed order. + std::unordered_set elements_set{text("0"), text("0")}; + Screen screen(2, 1); + Render(screen, hbox(elements_set)); + EXPECT_EQ("00", screen.ToString()); +}; + } // namespace ftxui // NOLINTEND