diff --git a/doc/mainpage.md b/doc/mainpage.md index 94fe5d8..38434b9 100644 --- a/doc/mainpage.md +++ b/doc/mainpage.md @@ -44,28 +44,71 @@ int main(void) { └────┘└─────────────────────────────────────────────────────────────────┘└─────┘ ``` -**cmake** -```c +# Build + +## Using CMake + +CMakeLists.txt +~~~cmake cmake_minimum_required (VERSION 3.11) +# --- 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() -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::dom 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. @@ -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 own components. -## screen +# screen 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...). @@ -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. @@ -130,30 +173,6 @@ You only need one header: 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 The most simple widget. It displays a text. @@ -226,6 +245,7 @@ border(gauge(0.5)) ~~~ ## graph + @htmlonly @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 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 -The component: \ref ftxui::Input +Produced by: ftxui::Input() from "ftxui/component/component.hpp" @htmlonly @@ -385,7 +423,7 @@ The component: \ref ftxui::Input ## Menu -The component: \ref ftxui::Menu +Produced by: ftxui::Menu() from "ftxui/component/component.hpp" @htmlonly @@ -393,7 +431,7 @@ The component: \ref ftxui::Menu ## Toggle. -The component: \ref ftxui::Toggle +Produced by: ftxui::Toggle() from "ftxui/component/component.hpp" @htmlonly @@ -401,7 +439,7 @@ The component: \ref ftxui::Toggle ## CheckBox -The component: \ref ftxui::CheckBox +Produced by: ftxui::Checkbox() from "ftxui/component/component.hpp" @htmlonly @@ -409,99 +447,32 @@ The component: \ref ftxui::CheckBox ## RadioBox -The component: \ref ftxui::RadioBox +Produced by: ftxui::Radiobox() from "ftxui/component/component.hpp" @htmlonly @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** -~~~cpp -#include "ftxui/screen/screen.c -#include "ftxui/dom/elements.c -#include +## Container::Horizontal -int main(int argc, const char *argv[]) { - using namespace ftxui; - auto document = - 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); +Produced by: ftxui::Container::Horizontal() from +"ftxui/component/component.hpp". It displays a list of components horizontally +and handle keyboard/mouse navigation. - 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 -~~~cmake -cmake_minimum_required (VERSION 3.11) - -# --- 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 -~~~ +Produced by: ftxui::Container::Tab() from +"ftxui/component/component.hpp". It take a list of component and display only +one of them. This is useful for implementing a tab bar. diff --git a/include/ftxui/component/component.hpp b/include/ftxui/component/component.hpp index b32459d..47e942c 100644 --- a/include/ftxui/component/component.hpp +++ b/include/ftxui/component/component.hpp @@ -32,11 +32,11 @@ Component Renderer(std::function); template // T = {int, float} Component Slider(std::wstring label, T* value, T min, T max, T increment); -// namespace Component { -// Component Vertical(Components children); -// Component Horizontal(Components children); -// Component Tab(int* selector, Components children); -//} // namespace Component +namespace Container { +Component Vertical(Components children); +Component Horizontal(Components children); +Component Tab(int* selector, Components children); +} // namespace Container }; // namespace ftxui diff --git a/include/ftxui/component/container.hpp b/include/ftxui/component/container.hpp index 19277c7..f15b65f 100644 --- a/include/ftxui/component/container.hpp +++ b/include/ftxui/component/container.hpp @@ -9,7 +9,7 @@ namespace ftxui { /// @brief A component where focus and events are automatically handled for you. -class Container : public ComponentBase { +class ContainerBase : public ComponentBase { public: static Component Vertical(); static Component Vertical(Components children); @@ -20,7 +20,7 @@ class Container : public ComponentBase { static Component Tab(int* selector); static Component Tab(int* selector, Components children); - ~Container() override = default; + ~ContainerBase() override = default; // Component override. bool OnEvent(Event event) override; @@ -30,13 +30,13 @@ class Container : public ComponentBase { protected: // Handlers - using EventHandler = bool (Container::*)(Event); + using EventHandler = bool (ContainerBase::*)(Event); bool VerticalEvent(Event event); bool HorizontalEvent(Event event); bool TabEvent(Event) { return false; } EventHandler event_handler_; - using RenderHandler = Element (Container::*)(); + using RenderHandler = Element (ContainerBase::*)(); Element VerticalRender(); Element HorizontalRender(); Element TabRender(); diff --git a/src/ftxui/component/container.cpp b/src/ftxui/component/container.cpp index 7efc0dc..3dc9fdf 100644 --- a/src/ftxui/component/container.cpp +++ b/src/ftxui/component/container.cpp @@ -9,65 +9,120 @@ namespace ftxui { -Component ContainerVertical(Components children) { - return Container::Vertical(std::move(children)); +namespace Container { + +/// @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) { - return Container::Horizontal(std::move(children)); +/// @brief A list of components, drawn one by one horizontally and navigated +/// 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) { - return Container::Tab(selector, std::move(children)); +/// @brief A list of components, where only one is drawn and interacted with at +/// 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 -Component Container::Vertical() { +Component ContainerBase::Vertical() { return Vertical({}); } // static -Component Container::Vertical(Components children) { +Component ContainerBase::Vertical(Components children) { auto container = std::make_shared(); - container->event_handler_ = &Container::VerticalEvent; - container->render_handler_ = &Container::VerticalRender; + container->event_handler_ = &ContainerBase::VerticalEvent; + container->render_handler_ = &ContainerBase::VerticalRender; for (Component& child : children) container->Add(std::move(child)); return container; } // static -Component Container::Horizontal() { +Component ContainerBase::Horizontal() { return Horizontal({}); } // static -Component Container::Horizontal(Components children) { +Component ContainerBase::Horizontal(Components children) { auto container = std::make_shared(); - container->event_handler_ = &Container::HorizontalEvent; - container->render_handler_ = &Container::HorizontalRender; + container->event_handler_ = &ContainerBase::HorizontalEvent; + container->render_handler_ = &ContainerBase::HorizontalRender; for (Component& child : children) container->Add(std::move(child)); return container; } // static -Component Container::Tab(int* selector) { +Component ContainerBase::Tab(int* selector) { return Tab(selector, {}); } // static -Component Container::Tab(int* selector, Components children) { +Component ContainerBase::Tab(int* selector, Components children) { auto container = std::make_shared(); container->selector_ = selector; - container->event_handler_ = &Container::TabEvent; - container->render_handler_ = &Container::TabRender; + container->event_handler_ = &ContainerBase::TabEvent; + container->render_handler_ = &ContainerBase::TabRender; for (Component& child : children) container->Add(std::move(child)); return container; } -bool Container::OnEvent(Event event) { +bool ContainerBase::OnEvent(Event event) { if (event.is_mouse()) return OnMouseEvent(event); @@ -80,7 +135,7 @@ bool Container::OnEvent(Event event) { return (this->*event_handler_)(event); } -Component Container::ActiveChild() { +Component ContainerBase::ActiveChild() { if (children_.size() == 0) return nullptr; @@ -88,7 +143,7 @@ Component Container::ActiveChild() { return children_[selected % children_.size()]; } -void Container::SetActiveChild(ComponentBase* child) { +void ContainerBase::SetActiveChild(ComponentBase* child) { for (size_t i = 0; i < children_.size(); ++i) { if (children_[i].get() == child) { (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_; if (event == Event::ArrowUp || event == Event::Character('k')) selected_--; @@ -112,7 +167,7 @@ bool Container::VerticalEvent(Event event) { return old_selected != selected_; } -bool Container::HorizontalEvent(Event event) { +bool ContainerBase::HorizontalEvent(Event event) { int old_selected = selected_; if (event == Event::ArrowLeft || event == Event::Character('h')) selected_--; @@ -127,11 +182,11 @@ bool Container::HorizontalEvent(Event event) { return old_selected != selected_; } -Element Container::Render() { +Element ContainerBase::Render() { return (this->*render_handler_)(); } -Element Container::VerticalRender() { +Element ContainerBase::VerticalRender() { Elements elements; for (auto& it : children_) elements.push_back(it->Render()); @@ -140,7 +195,7 @@ Element Container::VerticalRender() { return vbox(std::move(elements)); } -Element Container::HorizontalRender() { +Element ContainerBase::HorizontalRender() { Elements elements; for (auto& it : children_) elements.push_back(it->Render()); @@ -149,14 +204,14 @@ Element Container::HorizontalRender() { return hbox(std::move(elements)); } -Element Container::TabRender() { +Element ContainerBase::TabRender() { Component active_child = ActiveChild(); if (active_child) return active_child->Render(); return text(L"Empty container"); } -bool Container::OnMouseEvent(Event event) { +bool ContainerBase::OnMouseEvent(Event event) { if (selector_) return ActiveChild()->OnEvent(event); diff --git a/src/ftxui/component/renderer.cpp b/src/ftxui/component/renderer.cpp index 27a0c9e..95fdd9a 100644 --- a/src/ftxui/component/renderer.cpp +++ b/src/ftxui/component/renderer.cpp @@ -27,10 +27,43 @@ class RendererBase : public ComponentBase { std::function 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 render) { return Make(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 render) { Component renderer = Renderer(std::move(render)); renderer->Add(std::move(child));