Component decorators (#354)

Add decorator variants for decorator components

Add the "pipe" operator for components, similar to what was done for Elements.
We are able to put something like:
```
Button(...) | Maybe(&show_button)
```

Add decorators for:
- `Maybe`
- `CatchEvent`
- `Renderer`

Signed-off-by: Kefu Chai <tchaikov@gmail.com>
Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
This commit is contained in:
Kefu Chai 2022-03-12 22:18:36 +08:00 committed by GitHub
parent 3e28fd6520
commit 95c766e9e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 210 additions and 32 deletions

View File

@ -23,6 +23,7 @@ Element gaugeDirection(float ratio, GaugeDirection);
with others nearby. with others nearby.
- Fix the `Table` rendering function, to allow automerging characters. - Fix the `Table` rendering function, to allow automerging characters.
- Bugfix: The `vscroll_indicator` now computes its offset and size correctly. - Bugfix: The `vscroll_indicator` now computes its offset and size correctly.
- Add the `operator|=(Element, Decorator)`
### Component ### Component
- Support SIGTSTP. (ctrl+z). - Support SIGTSTP. (ctrl+z).
@ -33,6 +34,14 @@ Element gaugeDirection(float ratio, GaugeDirection);
- **bugfix** Automatically convert '\r' keys into '\n' for Linux programs that - **bugfix** Automatically convert '\r' keys into '\n' for Linux programs that
do not send the correct code for the return key, like the 'bind'. do not send the correct code for the return key, like the 'bind'.
https://github.com/ArthurSonzogni/FTXUI/issues/337 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 2.0.0
----- -----

View File

@ -112,6 +112,7 @@ add_library(component
src/ftxui/component/terminal_input_parser.cpp src/ftxui/component/terminal_input_parser.cpp
src/ftxui/component/terminal_input_parser.hpp src/ftxui/component/terminal_input_parser.hpp
src/ftxui/component/toggle.cpp src/ftxui/component/toggle.cpp
src/ftxui/component/util.cpp
) )
target_link_libraries(dom target_link_libraries(dom

View File

@ -1,17 +1,15 @@
#include <memory> // for shared_ptr, __shared_ptr_access #include <memory> // for allocator, shared_ptr
#include <string> // for string, basic_string, allocator #include <string> // for string, basic_string
#include <vector> // for vector #include <vector> // for vector
#include "ftxui/component/captured_mouse.hpp" // for ftxui #include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Checkbox, Maybe, Radiobox, Renderer, Vertical #include "ftxui/component/component.hpp" // for operator|, Maybe, Checkbox, Radiobox, Renderer, Vertical
#include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/component_base.hpp" // for Component
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive #include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
#include "ftxui/dom/elements.hpp" // for Element, operator|, border #include "ftxui/dom/elements.hpp" // for border, color, operator|, text, Element
#include "ftxui/screen/color.hpp" // for Color, Color::Red
using namespace ftxui; using namespace ftxui;
Component Border(Component child) {
return Renderer(child, [child] { return child->Render() | border; });
}
int main(int argc, const char* argv[]) { int main(int argc, const char* argv[]) {
std::vector<std::string> entries = { std::vector<std::string> entries = {
@ -21,20 +19,19 @@ int main(int argc, const char* argv[]) {
}; };
int menu_1_selected = 0; int menu_1_selected = 0;
int menu_2_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_1_show = false;
bool menu_2_show = false; bool menu_2_show = false;
auto layout = Container::Vertical({ auto layout = Container::Vertical({
Checkbox("Show menu_1", &menu_1_show), 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), 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(); auto screen = ScreenInteractive::TerminalOutput();

View File

@ -84,7 +84,7 @@ int main(int argc, const char* argv[]) {
return window(text("keys"), vbox(std::move(children))); return window(text("keys"), vbox(std::move(children)));
}); });
component = CatchEvent(component, [&](Event event) { component |= CatchEvent([&](Event event) {
keys.push_back(event); keys.push_back(event);
return true; return true;
}); });

View File

@ -1,4 +1,3 @@
#include <stddef.h> // for size_t
#include <stdio.h> // for getchar #include <stdio.h> // for getchar
#include <ftxui/dom/elements.hpp> // for operator|, size, Element, text, hcenter, Decorator, Fit, WIDTH, hflow, window, EQUAL, GREATER_THAN, HEIGHT, bold, border, dim, LESS_THAN #include <ftxui/dom/elements.hpp> // for operator|, size, Element, text, hcenter, Decorator, Fit, WIDTH, hflow, window, EQUAL, GREATER_THAN, HEIGHT, bold, border, dim, LESS_THAN
#include <ftxui/screen/screen.hpp> // for Full, Screen #include <ftxui/screen/screen.hpp> // for Full, Screen

View File

@ -1,4 +1,3 @@
#include <stddef.h> // for size_t
#include <stdio.h> // for getchar #include <stdio.h> // for getchar
#include <ftxui/dom/elements.hpp> // for operator|, Element, size, text, hcenter, Fit, vflow, window, EQUAL, bold, border, dim, HEIGHT, WIDTH #include <ftxui/dom/elements.hpp> // for operator|, Element, size, text, hcenter, Fit, vflow, window, EQUAL, bold, border, dim, HEIGHT, WIDTH
#include <ftxui/screen/screen.hpp> // for Full, Screen #include <ftxui/screen/screen.hpp> // for Full, Screen

View File

@ -4,6 +4,7 @@
#include <functional> // for function #include <functional> // for function
#include <memory> // for make_shared, shared_ptr #include <memory> // for make_shared, shared_ptr
#include <string> // for wstring #include <string> // for wstring
#include <utility> // for forward
#include <vector> // for vector #include <vector> // for vector
#include "ftxui/component/component_base.hpp" // for Component, Components #include "ftxui/component/component_base.hpp" // for Component, Components
@ -26,6 +27,14 @@ std::shared_ptr<T> Make(Args&&... args) {
return std::make_shared<T>(std::forward<Args>(args)...); return std::make_shared<T>(std::forward<Args>(args)...);
} }
// Pipe operator to decorate components.
using ComponentDecorator = std::function<Component(Component)>;
using ElementDecorator = std::function<Element(Element)>;
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 { namespace Container {
Component Vertical(Components children); Component Vertical(Components children);
Component Vertical(Components children, int* selector); Component Vertical(Components children, int* selector);
@ -38,38 +47,55 @@ Component Tab(Components children, int* selector);
Component Button(ConstStringRef label, Component Button(ConstStringRef label,
std::function<void()> on_click, std::function<void()> on_click,
Ref<ButtonOption> = {}); Ref<ButtonOption> = {});
Component Checkbox(ConstStringRef label, Component Checkbox(ConstStringRef label,
bool* checked, bool* checked,
Ref<CheckboxOption> option = {}); Ref<CheckboxOption> option = {});
Component Input(StringRef content, Component Input(StringRef content,
ConstStringRef placeholder, ConstStringRef placeholder,
Ref<InputOption> option = {}); Ref<InputOption> option = {});
Component Menu(ConstStringListRef entries, Component Menu(ConstStringListRef entries,
int* selected_, int* selected_,
Ref<MenuOption> = {}); Ref<MenuOption> = {});
Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> = {}); Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> = {});
Component Dropdown(ConstStringListRef entries, int* selected); Component Dropdown(ConstStringListRef entries, int* selected);
Component Radiobox(ConstStringListRef entries, Component Radiobox(ConstStringListRef entries,
int* selected_, int* selected_,
Ref<RadioboxOption> option = {}); Ref<RadioboxOption> option = {});
Component Toggle(ConstStringListRef entries, Component Toggle(ConstStringListRef entries,
int* selected, int* selected,
Ref<ToggleOption> option = {}); Ref<ToggleOption> option = {});
template <class T> // T = {int, float, long} template <class T> // T = {int, float, long}
Component Slider(ConstStringRef label, T* value, T min, T max, T increment); Component Slider(ConstStringRef label, T* value, T min, T max, T increment);
Component ResizableSplitLeft(Component main, Component back, int* main_size); Component ResizableSplitLeft(Component main, Component back, int* main_size);
Component ResizableSplitRight(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 ResizableSplitTop(Component main, Component back, int* main_size);
Component ResizableSplitBottom(Component main, Component back, int* main_size); Component ResizableSplitBottom(Component main, Component back, int* main_size);
Component Renderer(Component child, std::function<Element()>); Component Renderer(Component child, std::function<Element()>);
Component Renderer(std::function<Element()>); Component Renderer(std::function<Element()>);
Component Renderer(std::function<Element(bool /* focused */)>); Component Renderer(std::function<Element(bool /* focused */)>);
ComponentDecorator Renderer(ElementDecorator);
Component CatchEvent(Component child, std::function<bool(Event)>); Component CatchEvent(Component child, std::function<bool(Event)>);
ComponentDecorator CatchEvent(std::function<bool(Event)> on_event);
Component Maybe(Component, const bool* show); Component Maybe(Component, const bool* show);
Component Maybe(Component, std::function<bool()>);
ComponentDecorator Maybe(const bool* show);
ComponentDecorator Maybe(std::function<bool()>);
Component Collapsible(ConstStringRef label, Component Collapsible(ConstStringRef label,
Component child, Component child,
Ref<bool> show = false); Ref<bool> show = false);
} // namespace ftxui } // namespace ftxui
// Include component using the old deprecated wstring. // Include component using the old deprecated wstring.

View File

@ -5,9 +5,10 @@
namespace ftxui { namespace ftxui {
Component Input(WideStringRef content, [[deprecated("use Input with normal std::string instead.")]] Component Input(
ConstStringRef placeholder, WideStringRef content,
Ref<InputOption> option = {}); ConstStringRef placeholder,
Ref<InputOption> option = {});
} // namespace ftxui } // namespace ftxui
#endif /* FTXUI_COMPONENT_DEPRECATED_HPP */ #endif /* FTXUI_COMPONENT_DEPRECATED_HPP */

View File

@ -28,6 +28,7 @@ enum class GaugeDirection { Left, Up, Right, Down };
// -> text("ftxui") | bold | underlined // -> text("ftxui") | bold | underlined
// -> underlined(bold(text("FTXUI"))) // -> underlined(bold(text("FTXUI")))
Element operator|(Element, Decorator); Element operator|(Element, Decorator);
Element& operator|=(Element&, Decorator);
Elements operator|(Elements, Decorator); Elements operator|(Elements, Decorator);
Decorator operator|(Decorator, Decorator); Decorator operator|(Decorator, Decorator);

View File

@ -55,6 +55,33 @@ Component CatchEvent(Component child,
return out; 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<bool(Event)> 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 } // namespace ftxui
// Copyright 2021 Arthur Sonzogni. All rights reserved. // Copyright 2021 Arthur Sonzogni. All rights reserved.

View File

@ -1,38 +1,86 @@
#include <functional> // for function
#include <memory> // for make_unique, __shared_ptr_access, __shared_ptr_access<>::element_type, shared_ptr #include <memory> // for make_unique, __shared_ptr_access, __shared_ptr_access<>::element_type, shared_ptr
#include <utility> // for move #include <utility> // for move
#include "ftxui/component/component.hpp" // for Make, Maybe #include "ftxui/component/component.hpp" // for ComponentDecorator, Maybe, Make
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component #include "ftxui/component/component_base.hpp" // for Component, ComponentBase
#include "ftxui/component/event.hpp" // for Event #include "ftxui/component/event.hpp" // for Event
#include "ftxui/dom/elements.hpp" // for Element #include "ftxui/dom/elements.hpp" // for Element
#include "ftxui/dom/node.hpp" // for Node #include "ftxui/dom/node.hpp" // for Node
namespace ftxui { namespace ftxui {
Component Maybe(Component child, const bool* show) { Component Maybe(Component child, std::function<bool()> show) {
class Impl : public ComponentBase { class Impl : public ComponentBase {
public: public:
Impl(const bool* show) : show_(show) {} Impl(std::function<bool()> show) : show_(std::move(show)) {}
private: private:
Element Render() override { Element Render() override {
return *show_ ? ComponentBase::Render() : std::make_unique<Node>(); return show_() ? ComponentBase::Render() : std::make_unique<Node>();
} }
bool Focusable() const override { bool Focusable() const override {
return *show_ && ComponentBase::Focusable(); return show_() && ComponentBase::Focusable();
} }
bool OnEvent(Event event) override { bool OnEvent(Event event) override {
return *show_ && ComponentBase::OnEvent(event); return show_() && ComponentBase::OnEvent(event);
} }
const bool* show_; std::function<bool()> show_;
}; };
auto maybe = Make<Impl>(show); auto maybe = Make<Impl>(std::move(show));
maybe->Add(std::move(child)); maybe->Add(std::move(child));
return maybe; 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<bool()> 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 } // namespace ftxui
// Copyright 2021 Arthur Sonzogni. All rights reserved. // Copyright 2021 Arthur Sonzogni. All rights reserved.

View File

@ -104,6 +104,28 @@ Component Renderer(std::function<Element(bool)> render) {
return Make<Impl>(std::move(render)); return Make<Impl>(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 } // namespace ftxui
// Copyright 2021 Arthur Sonzogni. All rights reserved. // Copyright 2021 Arthur Sonzogni. All rights reserved.

View File

@ -0,0 +1,32 @@
#include <functional> // for function
#include <memory> // 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.

View File

@ -66,6 +66,22 @@ Element operator|(Element element, Decorator decorator) {
return decorator(std::move(element)); 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. /// The minimal dimension that will fit the given element.
/// @see Fixed /// @see Fixed
/// @see Full /// @see Full