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.
- 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
-----

View File

@ -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

View File

@ -1,17 +1,15 @@
#include <memory> // for shared_ptr, __shared_ptr_access
#include <string> // for string, basic_string, allocator
#include <memory> // for allocator, shared_ptr
#include <string> // for string, basic_string
#include <vector> // 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<std::string> 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();

View File

@ -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;
});

View File

@ -1,4 +1,3 @@
#include <stddef.h> // for size_t
#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/screen/screen.hpp> // for Full, Screen

View File

@ -1,4 +1,3 @@
#include <stddef.h> // for size_t
#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/screen/screen.hpp> // for Full, Screen

View File

@ -4,6 +4,7 @@
#include <functional> // for function
#include <memory> // for make_shared, shared_ptr
#include <string> // for wstring
#include <utility> // for forward
#include <vector> // for vector
#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)...);
}
// 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 {
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<void()> on_click,
Ref<ButtonOption> = {});
Component Checkbox(ConstStringRef label,
bool* checked,
Ref<CheckboxOption> option = {});
Component Input(StringRef content,
ConstStringRef placeholder,
Ref<InputOption> option = {});
Component Menu(ConstStringListRef entries,
int* selected_,
Ref<MenuOption> = {});
Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> = {});
Component Dropdown(ConstStringListRef entries, int* selected);
Component Radiobox(ConstStringListRef entries,
int* selected_,
Ref<RadioboxOption> option = {});
Component Toggle(ConstStringListRef entries,
int* selected,
Ref<ToggleOption> option = {});
template <class T> // 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<Element()>);
Component Renderer(std::function<Element()>);
Component Renderer(std::function<Element(bool /* focused */)>);
ComponentDecorator Renderer(ElementDecorator);
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, std::function<bool()>);
ComponentDecorator Maybe(const bool* show);
ComponentDecorator Maybe(std::function<bool()>);
Component Collapsible(ConstStringRef label,
Component child,
Ref<bool> show = false);
} // namespace ftxui
// Include component using the old deprecated wstring.

View File

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

View File

@ -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);

View File

@ -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<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
// 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 <utility> // 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<bool()> show) {
class Impl : public ComponentBase {
public:
Impl(const bool* show) : show_(show) {}
Impl(std::function<bool()> show) : show_(std::move(show)) {}
private:
Element Render() override {
return *show_ ? ComponentBase::Render() : std::make_unique<Node>();
return show_() ? ComponentBase::Render() : std::make_unique<Node>();
}
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<bool()> show_;
};
auto maybe = Make<Impl>(show);
auto maybe = Make<Impl>(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<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
// 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));
}
/// @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.

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));
}
/// @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