diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a5f55c..7ac5a71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Element gaugeDirection(float ratio, GaugeDirection); with others nearby. - Fix the `Table` rendering function, to allow automerging characters. - Bugfix: The `vscroll_indicator` now computes its offset and size correctly. +- Add the `operator|=(Element, Decorator)` ### Component - Support SIGTSTP. (ctrl+z). @@ -33,6 +34,14 @@ Element gaugeDirection(float ratio, GaugeDirection); - **bugfix** Automatically convert '\r' keys into '\n' for Linux programs that do not send the correct code for the return key, like the 'bind'. https://github.com/ArthurSonzogni/FTXUI/issues/337 +- Add decorator for components: + - `operator|(Component, ComponentDecorator)` + - `operator|=(Component, ComponentDecorator)` + - `operator|(Component, ElementDecorator)` + - `operator|=(Component, ElementDecorator)` + - Add the `Maybe` decorator. + - Add the `CatchEvent` decorator. + - Add the `Renderer` decorator. 2.0.0 ----- diff --git a/CMakeLists.txt b/CMakeLists.txt index 8222352..930ff87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,7 @@ add_library(component src/ftxui/component/terminal_input_parser.cpp src/ftxui/component/terminal_input_parser.hpp src/ftxui/component/toggle.cpp + src/ftxui/component/util.cpp ) target_link_libraries(dom diff --git a/examples/component/maybe.cpp b/examples/component/maybe.cpp index 1d4d891..4926627 100644 --- a/examples/component/maybe.cpp +++ b/examples/component/maybe.cpp @@ -1,17 +1,15 @@ -#include // for shared_ptr, __shared_ptr_access -#include // for string, basic_string, allocator +#include // for allocator, shared_ptr +#include // for string, basic_string #include // for vector #include "ftxui/component/captured_mouse.hpp" // for ftxui -#include "ftxui/component/component.hpp" // for Checkbox, Maybe, Radiobox, Renderer, Vertical -#include "ftxui/component/component_base.hpp" // for ComponentBase -#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive -#include "ftxui/dom/elements.hpp" // for Element, operator|, border +#include "ftxui/component/component.hpp" // for operator|, Maybe, Checkbox, Radiobox, Renderer, Vertical +#include "ftxui/component/component_base.hpp" // for Component +#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive +#include "ftxui/dom/elements.hpp" // for border, color, operator|, text, Element +#include "ftxui/screen/color.hpp" // for Color, Color::Red using namespace ftxui; -Component Border(Component child) { - return Renderer(child, [child] { return child->Render() | border; }); -} int main(int argc, const char* argv[]) { std::vector entries = { @@ -21,20 +19,19 @@ int main(int argc, const char* argv[]) { }; int menu_1_selected = 0; int menu_2_selected = 0; - auto menu_1 = Radiobox(&entries, &menu_1_selected); - auto menu_2 = Radiobox(&entries, &menu_2_selected); - - menu_1 = Border(menu_1); - menu_2 = Border(menu_2); bool menu_1_show = false; bool menu_2_show = false; auto layout = Container::Vertical({ Checkbox("Show menu_1", &menu_1_show), - Maybe(menu_1, &menu_1_show), + Radiobox(&entries, &menu_1_selected) | border | Maybe(&menu_1_show), Checkbox("Show menu_2", &menu_2_show), - Maybe(menu_2, &menu_2_show), + Radiobox(&entries, &menu_2_selected) | border | Maybe(&menu_2_show), + + Renderer([] { + return text("You found the secret combinaison!") | color(Color::Red); + }) | Maybe([&] { return menu_1_selected == 1 && menu_2_selected == 2; }), }); auto screen = ScreenInteractive::TerminalOutput(); diff --git a/examples/component/print_key_press.cpp b/examples/component/print_key_press.cpp index 8ded61f..8bcb119 100644 --- a/examples/component/print_key_press.cpp +++ b/examples/component/print_key_press.cpp @@ -84,7 +84,7 @@ int main(int argc, const char* argv[]) { return window(text("keys"), vbox(std::move(children))); }); - component = CatchEvent(component, [&](Event event) { + component |= CatchEvent([&](Event event) { keys.push_back(event); return true; }); diff --git a/examples/dom/hflow.cpp b/examples/dom/hflow.cpp index a197f68..c2a9f88 100644 --- a/examples/dom/hflow.cpp +++ b/examples/dom/hflow.cpp @@ -1,4 +1,3 @@ -#include // for size_t #include // for getchar #include // for operator|, size, Element, text, hcenter, Decorator, Fit, WIDTH, hflow, window, EQUAL, GREATER_THAN, HEIGHT, bold, border, dim, LESS_THAN #include // for Full, Screen diff --git a/examples/dom/vflow.cpp b/examples/dom/vflow.cpp index e66a261..e67167a 100644 --- a/examples/dom/vflow.cpp +++ b/examples/dom/vflow.cpp @@ -1,4 +1,3 @@ -#include // for size_t #include // for getchar #include // for operator|, Element, size, text, hcenter, Fit, vflow, window, EQUAL, bold, border, dim, HEIGHT, WIDTH #include // for Full, Screen diff --git a/include/ftxui/component/component.hpp b/include/ftxui/component/component.hpp index 78d34e8..9572b06 100644 --- a/include/ftxui/component/component.hpp +++ b/include/ftxui/component/component.hpp @@ -4,6 +4,7 @@ #include // for function #include // for make_shared, shared_ptr #include // for wstring +#include // for forward #include // for vector #include "ftxui/component/component_base.hpp" // for Component, Components @@ -26,6 +27,14 @@ std::shared_ptr Make(Args&&... args) { return std::make_shared(std::forward(args)...); } +// Pipe operator to decorate components. +using ComponentDecorator = std::function; +using ElementDecorator = std::function; +Component operator|(Component component, ComponentDecorator decorator); +Component operator|(Component component, ElementDecorator decorator); +Component& operator|=(Component& component, ComponentDecorator decorator); +Component& operator|=(Component& component, ElementDecorator decorator); + namespace Container { Component Vertical(Components children); Component Vertical(Components children, int* selector); @@ -38,38 +47,55 @@ Component Tab(Components children, int* selector); Component Button(ConstStringRef label, std::function on_click, Ref = {}); + Component Checkbox(ConstStringRef label, bool* checked, Ref option = {}); + Component Input(StringRef content, ConstStringRef placeholder, Ref option = {}); + Component Menu(ConstStringListRef entries, int* selected_, Ref = {}); + Component MenuEntry(ConstStringRef label, Ref = {}); + Component Dropdown(ConstStringListRef entries, int* selected); + Component Radiobox(ConstStringListRef entries, int* selected_, Ref option = {}); + Component Toggle(ConstStringListRef entries, int* selected, Ref option = {}); + template // T = {int, float, long} Component Slider(ConstStringRef label, T* value, T min, T max, T increment); + Component ResizableSplitLeft(Component main, Component back, int* main_size); Component ResizableSplitRight(Component main, Component back, int* main_size); Component ResizableSplitTop(Component main, Component back, int* main_size); Component ResizableSplitBottom(Component main, Component back, int* main_size); + Component Renderer(Component child, std::function); Component Renderer(std::function); Component Renderer(std::function); +ComponentDecorator Renderer(ElementDecorator); + Component CatchEvent(Component child, std::function); +ComponentDecorator CatchEvent(std::function on_event); + Component Maybe(Component, const bool* show); +Component Maybe(Component, std::function); +ComponentDecorator Maybe(const bool* show); +ComponentDecorator Maybe(std::function); + Component Collapsible(ConstStringRef label, Component child, Ref show = false); - } // namespace ftxui // Include component using the old deprecated wstring. diff --git a/include/ftxui/component/deprecated.hpp b/include/ftxui/component/deprecated.hpp index 7a3178b..1f3b956 100644 --- a/include/ftxui/component/deprecated.hpp +++ b/include/ftxui/component/deprecated.hpp @@ -5,9 +5,10 @@ namespace ftxui { -Component Input(WideStringRef content, - ConstStringRef placeholder, - Ref option = {}); +[[deprecated("use Input with normal std::string instead.")]] Component Input( + WideStringRef content, + ConstStringRef placeholder, + Ref option = {}); } // namespace ftxui #endif /* FTXUI_COMPONENT_DEPRECATED_HPP */ diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp index dc3961c..e2f9cfa 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -28,6 +28,7 @@ enum class GaugeDirection { Left, Up, Right, Down }; // -> text("ftxui") | bold | underlined // -> underlined(bold(text("FTXUI"))) Element operator|(Element, Decorator); +Element& operator|=(Element&, Decorator); Elements operator|(Elements, Decorator); Decorator operator|(Decorator, Decorator); diff --git a/src/ftxui/component/catch_event.cpp b/src/ftxui/component/catch_event.cpp index fc51a1d..0ea0ec6 100644 --- a/src/ftxui/component/catch_event.cpp +++ b/src/ftxui/component/catch_event.cpp @@ -55,6 +55,33 @@ Component CatchEvent(Component child, return out; } +/// @brief Decorate a component, using |on_event| to catch events. This function +/// must returns true when the event has been handled, false otherwise. +/// @param on_event The function drawing the interface. +/// @ingroup component +/// +/// ### Example +/// +/// ```cpp +/// auto screen = ScreenInteractive::TerminalOutput(); +/// auto renderer = Renderer([] { return text("Hello world"); }); +/// renderer |= CatchEvent([&](Event event) { +/// if (event == Event::Character('q')) { +/// screen.ExitLoopClosure()(); +/// return true; +/// } +/// return false; +/// }); +/// screen.Loop(renderer); +/// ``` +ComponentDecorator CatchEvent(std::function on_event) { + return [on_event = std::move(on_event)](Component child) { + return CatchEvent(child, [on_event = std::move(on_event)](Event event) { + return on_event(event); + }); + }; +} + } // namespace ftxui // Copyright 2021 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/component/maybe.cpp b/src/ftxui/component/maybe.cpp index 838e3ca..1644bfa 100644 --- a/src/ftxui/component/maybe.cpp +++ b/src/ftxui/component/maybe.cpp @@ -1,38 +1,86 @@ +#include // for function #include // for make_unique, __shared_ptr_access, __shared_ptr_access<>::element_type, shared_ptr #include // for move -#include "ftxui/component/component.hpp" // for Make, Maybe -#include "ftxui/component/component_base.hpp" // for ComponentBase, Component +#include "ftxui/component/component.hpp" // for ComponentDecorator, Maybe, Make +#include "ftxui/component/component_base.hpp" // for Component, ComponentBase #include "ftxui/component/event.hpp" // for Event #include "ftxui/dom/elements.hpp" // for Element #include "ftxui/dom/node.hpp" // for Node namespace ftxui { -Component Maybe(Component child, const bool* show) { +Component Maybe(Component child, std::function show) { class Impl : public ComponentBase { public: - Impl(const bool* show) : show_(show) {} + Impl(std::function show) : show_(std::move(show)) {} private: Element Render() override { - return *show_ ? ComponentBase::Render() : std::make_unique(); + return show_() ? ComponentBase::Render() : std::make_unique(); } bool Focusable() const override { - return *show_ && ComponentBase::Focusable(); + return show_() && ComponentBase::Focusable(); } bool OnEvent(Event event) override { - return *show_ && ComponentBase::OnEvent(event); + return show_() && ComponentBase::OnEvent(event); } - const bool* show_; + std::function show_; }; - auto maybe = Make(show); + auto maybe = Make(std::move(show)); maybe->Add(std::move(child)); return maybe; } +/// @brief Decorate a component. It is shown only when the |show| function +/// returns true. +/// @params show a function returning whether the decoratorated component should +/// be shown. +/// @ingroup component +/// +/// ### Example +/// +/// ```cpp +/// auto component = Renderer([]{ return "Hello World!"; }); +/// auto maybe_component = component | Maybe([&]{ return counter == 42; }); +/// ``` +ComponentDecorator Maybe(std::function show) { + return [show = std::move(show)](Component child) mutable { + return Maybe(child, std::move(show)); + }; +} + +/// @brief Decorate a component |child|. It is shown only when |show| is true. +/// @params child the compoennt to decorate. +/// @params show a boolean. |child| is shown when |show| is true. +/// @ingroup component +/// +/// ### Example +/// +/// ```cpp +/// auto component = Renderer([]{ return "Hello World!"; }); +/// auto maybe_component = Maybe(component, &show); +/// ``` +Component Maybe(Component child, const bool* show) { + return Maybe(child, [show] { return *show; }); +} + +/// @brief Decorate a component. It is shown only when |show| is true. +/// @params show a boolean. |child| is shown when |show| is true. +/// @ingroup component +/// +/// ### Example +/// +/// ```cpp +/// auto component = Renderer([]{ return "Hello World!"; }); +/// auto maybe_component = component | Maybe(&show); +/// ``` +ComponentDecorator Maybe(const bool* show) { + return [show](Component child) { return Maybe(child, show); }; +} + } // namespace ftxui // Copyright 2021 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/component/renderer.cpp b/src/ftxui/component/renderer.cpp index f1d0c18..d7c0c1c 100644 --- a/src/ftxui/component/renderer.cpp +++ b/src/ftxui/component/renderer.cpp @@ -104,6 +104,28 @@ Component Renderer(std::function render) { return Make(std::move(render)); } +/// @brief Decorate a component, by decorating what it renders. +/// @param decorator the function modifying the element it renders. +/// @ingroup component +/// +/// ### Example +/// +/// ```cpp +/// auto screen = ScreenInteractive::TerminalOutput(); +/// auto renderer = +// Renderer([] { return text("Hello");) +/// | Renderer(bold) +/// | Renderer(inverted); +/// screen.Loop(renderer); +/// ``` +ComponentDecorator Renderer(ElementDecorator decorator) { + return [decorator](Component component) { + return Renderer(component, [component, decorator] { + return component->Render() | decorator; + }); + }; +} + } // namespace ftxui // Copyright 2021 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/component/util.cpp b/src/ftxui/component/util.cpp new file mode 100644 index 0000000..1337cea --- /dev/null +++ b/src/ftxui/component/util.cpp @@ -0,0 +1,32 @@ +#include // for function +#include // for __shared_ptr_access + +#include "ftxui/component/component.hpp" // for ElementDecorator, Renderer, ComponentDecorator, operator|, operator|= +#include "ftxui/component/component_base.hpp" // for Component, ComponentBase +#include "ftxui/dom/elements.hpp" // for Element + +namespace ftxui { + +Component operator|(Component component, ComponentDecorator decorator) { + return decorator(component); +} + +Component operator|(Component component, ElementDecorator decorator) { + return component | Renderer(decorator); +} + +Component& operator|=(Component& component, ComponentDecorator decorator) { + component = component | decorator; + return component; +} + +Component& operator|=(Component& component, ElementDecorator decorator) { + component = component | decorator; + return component; +} + +} // namespace ftxui + +// Copyright 2022 Arthur Sonzogni. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. diff --git a/src/ftxui/dom/util.cpp b/src/ftxui/dom/util.cpp index 2b63c51..39df1fd 100644 --- a/src/ftxui/dom/util.cpp +++ b/src/ftxui/dom/util.cpp @@ -66,6 +66,22 @@ Element operator|(Element element, Decorator decorator) { return decorator(std::move(element)); } +/// @brief Apply a decorator to an element. +/// @return the decorated element. +/// @ingroup dom +/// +/// ### Example +/// +/// Both of these are equivalent: +/// ```cpp +/// auto element = text("Hello"); +/// element |= bold; +/// ``` +Element& operator|=(Element& e, Decorator d) { + e = e | d; + return e; +} + /// The minimal dimension that will fit the given element. /// @see Fixed /// @see Full