Improve the documentation.

This commit is contained in:
ArthurSonzogni 2021-05-14 20:56:37 +02:00
parent 58287c147a
commit 9fdf235836
No known key found for this signature in database
GPG Key ID: 41D98248C074CD6C
5 changed files with 217 additions and 158 deletions

View File

@ -44,28 +44,71 @@ int main(void) {
└────┘└─────────────────────────────────────────────────────────────────┘└─────┘ └────┘└─────────────────────────────────────────────────────────────────┘└─────┘
``` ```
**cmake** # Build
```c
## Using CMake
CMakeLists.txt
~~~cmake
cmake_minimum_required (VERSION 3.11) cmake_minimum_required (VERSION 3.11)
# --- Fetch FTXUI --------------------------------------------------------------
include(FetchContent) include(FetchContent)
set(FETCHCONTENT_UPDATES_DISCONNECTED TRUE)
FetchContent_Declare(ftxui FetchContent_Declare(ftxui
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
# Specify a GIT_TAG here.
) )
FetchContent_GetProperties(ftxui) FetchContent_GetProperties(ftxui)
if(NOT ftxui_POPULATED) if(NOT ftxui_POPULATED)
FetchContent_Populate(ftxui) FetchContent_Populate(ftxui)
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL) add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
endif() endif()
add_executable(main src/main.cpp) # ------------------------------------------------------------------------------
target_link_libraries(main
project(ftxui-starter
LANGUAGES CXX
VERSION 1.0.0
)
add_executable(ftxui-starter src/main.cpp)
target_include_directories(ftxui-starter PRIVATE src)
target_link_libraries(ftxui-starter
PRIVATE ftxui::screen PRIVATE ftxui::screen
PRIVATE ftxui::dom PRIVATE ftxui::dom
PRIVATE ftxui::component # Not needed for this example. PRIVATE ftxui::component # Not needed for this example.
) )
set_target_properties(main PROPERTIES CXX_STANDARD 17)
``` # C++17 is used. We requires fold expressions at least.
set_target_properties(ftxui-starter PROPERTIES CXX_STANDARD 17)
~~~
Build
~~~
mkdir build && cd build
cmake ..
make
./main
~~~
## Using NXXM
**.nxxm/deps**
~~~json
{
"ArthurSonzogni/FTXUI": {}
}
~~~
Build:
~~~
nxxm . -t clang-cxx17
~~~
# List of modules. # List of modules.
@ -82,7 +125,7 @@ input. It defines a set of ftxui::Component. The use can navigates using the
arrow keys and interact with widgets like checkbox/inputbox/... You can make you arrow keys and interact with widgets like checkbox/inputbox/... You can make you
own components. own components.
## screen # screen
It defines a ftxui::Screen. This is a grid of ftxui::Pixel. A Pixel represent a It defines a ftxui::Screen. This is a grid of ftxui::Pixel. A Pixel represent a
unicode character and its associated style (bold, colors, etc...). unicode character and its associated style (bold, colors, etc...).
@ -106,7 +149,7 @@ The screen can be printed as a string using ftxui::Screen::ToString().
} }
~~~ ~~~
## dom # dom
This module defines a hierachical set of Element. An element manages layout and can be responsive to the terminal dimensions. This module defines a hierachical set of Element. An element manages layout and can be responsive to the terminal dimensions.
@ -130,30 +173,6 @@ You only need one header: ftxui/dom/elements.hpp
\include ftxui/dom/elements.hpp \include ftxui/dom/elements.hpp
## component
Finally, the ftxui/component directory defines the logic to get interactivity.
Please take a look at ./examples/component
This provides:
1. A main loop.
2. Get events and respond to them.
3. A predefined implementation of "keyboard navigation".
4. A set of predefined widget: CheckBox, RadioBox, Input, Menu, Toggle.
**List of Component**
You only need one header: ftxui/dom/component.hpp
\include ftxui/component/component.hpp
# ftxui/dom
Every elements of the dom are declared from:
\ref ftxui/dom/elements.hpp
## text ## text
The most simple widget. It displays a text. The most simple widget. It displays a text.
@ -226,6 +245,7 @@ border(gauge(0.5))
~~~ ~~~
## graph ## graph
@htmlonly @htmlonly
<script id="asciicast-223726" src="https://asciinema.org/a/223726.js" async></script> <script id="asciicast-223726" src="https://asciinema.org/a/223726.js" async></script>
@endhtmlonly @endhtmlonly
@ -369,7 +389,25 @@ An horizontal flow layout is implemented by:
└────┘└───────────────────────────────────┘└───────────────────────────────────┘ └────┘└───────────────────────────────────┘└───────────────────────────────────┘
~~~ ~~~
# ftxui/component
# component
The `ftxui/component` directory defines the logic to get produce
interactive component responding to user's events (keyboard, mouse, etc...)
A ftxui::ScreenInteractive defines a main loop to render a component.
A ftxui::Component is a shared pointer to a ftxui::ComponentBase. The later
defines
- ftxui::ComponentBase::Render(): How to render the interface.
- ftxui::ComponentBase::OnEvent(): How to react to events.
- ftxui::ComponentBase::Add(): Give a parent/child relation ship in between
two component. This defines a tree a components, which help properly define
how keyboard navigation works.
Predefined components are available in `ftxui/dom/component.hpp`:
\include ftxui/component/component.hpp
Element are stateless object. On the other side, components are used when an Element are stateless object. On the other side, components are used when an
internal state is needed. Components are used to interact with the user with internal state is needed. Components are used to interact with the user with
@ -377,7 +415,7 @@ its keyboard. They handle keyboard navigation, including component focus.
## Input ## Input
The component: \ref ftxui::Input Produced by: ftxui::Input() from "ftxui/component/component.hpp"
@htmlonly @htmlonly
<script id="asciicast-223719" src="https://asciinema.org/a/223719.js" async></script> <script id="asciicast-223719" src="https://asciinema.org/a/223719.js" async></script>
@ -385,7 +423,7 @@ The component: \ref ftxui::Input
## Menu ## Menu
The component: \ref ftxui::Menu Produced by: ftxui::Menu() from "ftxui/component/component.hpp"
@htmlonly @htmlonly
<script id="asciicast-223720" src="https://asciinema.org/a/223720.js" async></script> <script id="asciicast-223720" src="https://asciinema.org/a/223720.js" async></script>
@ -393,7 +431,7 @@ The component: \ref ftxui::Menu
## Toggle. ## Toggle.
The component: \ref ftxui::Toggle Produced by: ftxui::Toggle() from "ftxui/component/component.hpp"
@htmlonly @htmlonly
<script id="asciicast-223722" src="https://asciinema.org/a/223722.js" async></script> <script id="asciicast-223722" src="https://asciinema.org/a/223722.js" async></script>
@ -401,7 +439,7 @@ The component: \ref ftxui::Toggle
## CheckBox ## CheckBox
The component: \ref ftxui::CheckBox Produced by: ftxui::Checkbox() from "ftxui/component/component.hpp"
@htmlonly @htmlonly
<script id="asciicast-223724" src="https://asciinema.org/a/223724.js" async></script> <script id="asciicast-223724" src="https://asciinema.org/a/223724.js" async></script>
@ -409,99 +447,32 @@ The component: \ref ftxui::CheckBox
## RadioBox ## RadioBox
The component: \ref ftxui::RadioBox Produced by: ftxui::Radiobox() from "ftxui/component/component.hpp"
@htmlonly @htmlonly
<script id="asciicast-223725" src="https://asciinema.org/a/223725.js" async></script> <script id="asciicast-223725" src="https://asciinema.org/a/223725.js" async></script>
@endhtmlonly @endhtmlonly
# Build ## Renderer
Assuming this example example.cpp file. Produced by: ftxui::Renderer() from \ref "ftxui/component/component.hpp". This
component decorate another one by using a different function to render an
interface.
**main.cpp** ## Container::Horizontal
~~~cpp
#include "ftxui/screen/screen.c
#include "ftxui/dom/elements.c
#include <iostream>
int main(int argc, const char *argv[]) { Produced by: ftxui::Container::Horizontal() from
using namespace ftxui; "ftxui/component/component.hpp". It displays a list of components horizontally
auto document = and handle keyboard/mouse navigation.
hbox({
text(L"left") | bold | border,
text(L"middle") | flex | border,
text(L"right") | border,
});
auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
Render(screen, document);
std::cout << screen.ToString(); ## Container::Vertial
return 0; Produced by: ftxui::Container::Vertical() from
} "ftxui/component/component.hpp". It displays a list of components vertically
~~~ and handles keyboard/mouse navigation.
## Using CMake ## Container::Tab
CMakeLists.txt Produced by: ftxui::Container::Tab() from
~~~cmake "ftxui/component/component.hpp". It take a list of component and display only
cmake_minimum_required (VERSION 3.11) one of them. This is useful for implementing a tab bar.
# --- Fetch FTXUI --------------------------------------------------------------
include(FetchContent)
set(FETCHCONTENT_UPDATES_DISCONNECTED TRUE)
FetchContent_Declare(ftxui
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
# Specify a GIT TAG here.
)
FetchContent_GetProperties(ftxui)
if(NOT ftxui_POPULATED)
FetchContent_Populate(ftxui)
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
# ------------------------------------------------------------------------------
project(ftxui-starter
LANGUAGES CXX
VERSION 1.0.0
)
add_executable(ftxui-starter src/main.cpp)
target_include_directories(ftxui-starter PRIVATE src)
target_link_libraries(ftxui-starter
PRIVATE ftxui::screen
PRIVATE ftxui::dom
PRIVATE ftxui::component # Not needed for this example.
)
# C++17 is used. We requires fold expressions at least.
set_target_properties(ftxui-starter PROPERTIES CXX_STANDARD 17)
~~~
Build
~~~
mkdir build && cd build
cmake ..
make
./main
~~~
## Using NXXM
**.nxxm/deps**
~~~json
{
"ArthurSonzogni/FTXUI": {}
}
~~~
Build:
~~~
nxxm . -t clang-cxx17
~~~

View File

@ -32,11 +32,11 @@ Component Renderer(std::function<Element()>);
template <class T> // T = {int, float} template <class T> // T = {int, float}
Component Slider(std::wstring label, T* value, T min, T max, T increment); Component Slider(std::wstring label, T* value, T min, T max, T increment);
// namespace Component { namespace Container {
// Component Vertical(Components children); Component Vertical(Components children);
// Component Horizontal(Components children); Component Horizontal(Components children);
// Component Tab(int* selector, Components children); Component Tab(int* selector, Components children);
//} // namespace Component } // namespace Container
}; // namespace ftxui }; // namespace ftxui

View File

@ -9,7 +9,7 @@
namespace ftxui { namespace ftxui {
/// @brief A component where focus and events are automatically handled for you. /// @brief A component where focus and events are automatically handled for you.
class Container : public ComponentBase { class ContainerBase : public ComponentBase {
public: public:
static Component Vertical(); static Component Vertical();
static Component Vertical(Components children); static Component Vertical(Components children);
@ -20,7 +20,7 @@ class Container : public ComponentBase {
static Component Tab(int* selector); static Component Tab(int* selector);
static Component Tab(int* selector, Components children); static Component Tab(int* selector, Components children);
~Container() override = default; ~ContainerBase() override = default;
// Component override. // Component override.
bool OnEvent(Event event) override; bool OnEvent(Event event) override;
@ -30,13 +30,13 @@ class Container : public ComponentBase {
protected: protected:
// Handlers // Handlers
using EventHandler = bool (Container::*)(Event); using EventHandler = bool (ContainerBase::*)(Event);
bool VerticalEvent(Event event); bool VerticalEvent(Event event);
bool HorizontalEvent(Event event); bool HorizontalEvent(Event event);
bool TabEvent(Event) { return false; } bool TabEvent(Event) { return false; }
EventHandler event_handler_; EventHandler event_handler_;
using RenderHandler = Element (Container::*)(); using RenderHandler = Element (ContainerBase::*)();
Element VerticalRender(); Element VerticalRender();
Element HorizontalRender(); Element HorizontalRender();
Element TabRender(); Element TabRender();

View File

@ -9,65 +9,120 @@
namespace ftxui { namespace ftxui {
Component ContainerVertical(Components children) { namespace Container {
return Container::Vertical(std::move(children));
/// @brief A list of components, drawn one by one vertically and navigated
/// vertically using up/down arrow key or 'j'/'k' keys.
/// @param children the list of components.
/// @ingroup component
/// @see ContainerBase
///
/// ### Example
///
/// ```cpp
/// auto container = Container::Vertical({
/// children_1,
/// children_2,
/// children_3,
/// children_4,
/// });
/// ```
Component Vertical(Components children) {
return ContainerBase::Vertical(std::move(children));
} }
Component ContainerHorizontal(Components children) { /// @brief A list of components, drawn one by one horizontally and navigated
return Container::Horizontal(std::move(children)); /// horizontally using left/right arrow key or 'h'/'l' keys.
/// @param children the list of components.
/// @ingroup component
/// @see ContainerBase
///
/// ### Example
///
/// ```cpp
/// auto container = Container::Horizontal({
/// children_1,
/// children_2,
/// children_3,
/// children_4,
/// });
/// ```
Component Horizontal(Components children) {
return ContainerBase::Horizontal(std::move(children));
} }
Component ContainerTab(int* selector, Components children) { /// @brief A list of components, where only one is drawn and interacted with at
return Container::Tab(selector, std::move(children)); /// a time. The |selector| gives the index of the selected component. This is
/// useful to implement tabs.
/// @param selector The index of the drawn children.
/// @param children the list of components.
/// @ingroup component
/// @see ContainerBase
///
/// ### Example
///
/// ```cpp
/// int tab_drawn = 0;
/// auto container = Container::Tab(&tab_drawn, {
/// children_1,
/// children_2,
/// children_3,
/// children_4,
/// });
/// ```
Component Tab(int* selector, Components children) {
return ContainerBase::Tab(selector, std::move(children));
} }
} // namespace Container
// static // static
Component Container::Vertical() { Component ContainerBase::Vertical() {
return Vertical({}); return Vertical({});
} }
// static // static
Component Container::Vertical(Components children) { Component ContainerBase::Vertical(Components children) {
auto container = std::make_shared<Container>(); auto container = std::make_shared<Container>();
container->event_handler_ = &Container::VerticalEvent; container->event_handler_ = &ContainerBase::VerticalEvent;
container->render_handler_ = &Container::VerticalRender; container->render_handler_ = &ContainerBase::VerticalRender;
for (Component& child : children) for (Component& child : children)
container->Add(std::move(child)); container->Add(std::move(child));
return container; return container;
} }
// static // static
Component Container::Horizontal() { Component ContainerBase::Horizontal() {
return Horizontal({}); return Horizontal({});
} }
// static // static
Component Container::Horizontal(Components children) { Component ContainerBase::Horizontal(Components children) {
auto container = std::make_shared<Container>(); auto container = std::make_shared<Container>();
container->event_handler_ = &Container::HorizontalEvent; container->event_handler_ = &ContainerBase::HorizontalEvent;
container->render_handler_ = &Container::HorizontalRender; container->render_handler_ = &ContainerBase::HorizontalRender;
for (Component& child : children) for (Component& child : children)
container->Add(std::move(child)); container->Add(std::move(child));
return container; return container;
} }
// static // static
Component Container::Tab(int* selector) { Component ContainerBase::Tab(int* selector) {
return Tab(selector, {}); return Tab(selector, {});
} }
// static // static
Component Container::Tab(int* selector, Components children) { Component ContainerBase::Tab(int* selector, Components children) {
auto container = std::make_shared<Container>(); auto container = std::make_shared<Container>();
container->selector_ = selector; container->selector_ = selector;
container->event_handler_ = &Container::TabEvent; container->event_handler_ = &ContainerBase::TabEvent;
container->render_handler_ = &Container::TabRender; container->render_handler_ = &ContainerBase::TabRender;
for (Component& child : children) for (Component& child : children)
container->Add(std::move(child)); container->Add(std::move(child));
return container; return container;
} }
bool Container::OnEvent(Event event) { bool ContainerBase::OnEvent(Event event) {
if (event.is_mouse()) if (event.is_mouse())
return OnMouseEvent(event); return OnMouseEvent(event);
@ -80,7 +135,7 @@ bool Container::OnEvent(Event event) {
return (this->*event_handler_)(event); return (this->*event_handler_)(event);
} }
Component Container::ActiveChild() { Component ContainerBase::ActiveChild() {
if (children_.size() == 0) if (children_.size() == 0)
return nullptr; return nullptr;
@ -88,7 +143,7 @@ Component Container::ActiveChild() {
return children_[selected % children_.size()]; return children_[selected % children_.size()];
} }
void Container::SetActiveChild(ComponentBase* child) { void ContainerBase::SetActiveChild(ComponentBase* child) {
for (size_t i = 0; i < children_.size(); ++i) { for (size_t i = 0; i < children_.size(); ++i) {
if (children_[i].get() == child) { if (children_[i].get() == child) {
(selector_ ? *selector_ : selected_) = i; (selector_ ? *selector_ : selected_) = i;
@ -97,7 +152,7 @@ void Container::SetActiveChild(ComponentBase* child) {
} }
} }
bool Container::VerticalEvent(Event event) { bool ContainerBase::VerticalEvent(Event event) {
int old_selected = selected_; int old_selected = selected_;
if (event == Event::ArrowUp || event == Event::Character('k')) if (event == Event::ArrowUp || event == Event::Character('k'))
selected_--; selected_--;
@ -112,7 +167,7 @@ bool Container::VerticalEvent(Event event) {
return old_selected != selected_; return old_selected != selected_;
} }
bool Container::HorizontalEvent(Event event) { bool ContainerBase::HorizontalEvent(Event event) {
int old_selected = selected_; int old_selected = selected_;
if (event == Event::ArrowLeft || event == Event::Character('h')) if (event == Event::ArrowLeft || event == Event::Character('h'))
selected_--; selected_--;
@ -127,11 +182,11 @@ bool Container::HorizontalEvent(Event event) {
return old_selected != selected_; return old_selected != selected_;
} }
Element Container::Render() { Element ContainerBase::Render() {
return (this->*render_handler_)(); return (this->*render_handler_)();
} }
Element Container::VerticalRender() { Element ContainerBase::VerticalRender() {
Elements elements; Elements elements;
for (auto& it : children_) for (auto& it : children_)
elements.push_back(it->Render()); elements.push_back(it->Render());
@ -140,7 +195,7 @@ Element Container::VerticalRender() {
return vbox(std::move(elements)); return vbox(std::move(elements));
} }
Element Container::HorizontalRender() { Element ContainerBase::HorizontalRender() {
Elements elements; Elements elements;
for (auto& it : children_) for (auto& it : children_)
elements.push_back(it->Render()); elements.push_back(it->Render());
@ -149,14 +204,14 @@ Element Container::HorizontalRender() {
return hbox(std::move(elements)); return hbox(std::move(elements));
} }
Element Container::TabRender() { Element ContainerBase::TabRender() {
Component active_child = ActiveChild(); Component active_child = ActiveChild();
if (active_child) if (active_child)
return active_child->Render(); return active_child->Render();
return text(L"Empty container"); return text(L"Empty container");
} }
bool Container::OnMouseEvent(Event event) { bool ContainerBase::OnMouseEvent(Event event) {
if (selector_) if (selector_)
return ActiveChild()->OnEvent(event); return ActiveChild()->OnEvent(event);

View File

@ -27,10 +27,43 @@ class RendererBase : public ComponentBase {
std::function<Element()> render_; std::function<Element()> render_;
}; };
/// @brief Return a component, using |render| to render its interface.
/// @param render The function drawing the interface.
/// @ingroup component
///
/// ### Example
///
/// ```cpp
/// auto screen = ScreenInteractive::TerminalOutput();
/// auto renderer = Renderer([] {
/// return text(L"My interface");
/// });
/// screen.Loop(renderer);
/// ```
Component Renderer(std::function<Element()> render) { Component Renderer(std::function<Element()> render) {
return Make<RendererBase>(std::move(render)); return Make<RendererBase>(std::move(render));
} }
/// @brief Return a new Component, similar to |child|, but using |render| as the
/// Component::Render() event.
/// @param child The component to forward events to.
/// @param render The function drawing the interface.
/// @ingroup component
///
/// ### Example
///
/// ```cpp
/// auto screen = ScreenInteractive::TerminalOutput();
/// std::wstring label = "Click to quit";
/// auto button = Button(&label, screen.ExitLoopClosure());
/// auto renderer = Renderer(button, [&] {
/// return hbox({
/// text("A button:"),
/// button->Render(),
/// });
/// });
/// screen.Loop(renderer);
/// ```
Component Renderer(Component child, std::function<Element()> render) { Component Renderer(Component child, std::function<Element()> render) {
Component renderer = Renderer(std::move(render)); Component renderer = Renderer(std::move(render));
renderer->Add(std::move(child)); renderer->Add(std::move(child));