diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ec4cb1b..7637c65 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,8 @@ add_subdirectory(frame) add_subdirectory(gauge) +add_subdirectory(menu) +add_subdirectory(menu2) +add_subdirectory(print_key_press) add_subdirectory(separator) add_subdirectory(vbox_hbox) +add_subdirectory(toggle) diff --git a/examples/frame/main.cpp b/examples/frame/main.cpp index 7b155cc..329d004 100644 --- a/examples/frame/main.cpp +++ b/examples/frame/main.cpp @@ -2,33 +2,47 @@ #include #include -#include "ftxui/core/screen.hpp" -#include "ftxui/core/dom/elements.hpp" +#include "ftxui/screen.hpp" +#include "ftxui/dom/elements.hpp" int main(int argc, const char *argv[]) { using namespace ftxui::dom; - auto document = - hbox( - frame( - vbox( - text(L"Line 1"), - text(L"Line 2"), - text(L"Line 3"), - frame( - vbox( - text(L"Line 4"), - text(L"Line 5"), - text(L"Line 6") - ) - ), - text(L"Line 7"), - text(L"Line 8"), - text(L"Line 9") - ) - ), - flex() - ); + auto document = + hbox( + frame(hcenter(text(L" main frame ")), + vbox( + text(L"Line 1"), + text(L"Line 2"), + text(L"Line 3"), + frame( + vbox( + text(L"Line 4"), + text(L"Line 5"), + text(L"Line 6") + ) + ), + hbox( + frame(text(L"frame 2"), + vbox( + text(L"Line 4"), + text(L"Line 5"), + text(L"Line 6") + ) + ), + frame(text(L"frame 3"), + vbox( + text(L"Line 7"), + text(L"Line 8"), + text(L"Line 9") + ) + ) + ), + text(L"footer footer footer footer footer") + ) + ), + flex() + ); auto screen = ftxui::Screen::TerminalOutput(document); Render(screen, document.get()); std::cout << screen.ToString() << std::endl; diff --git a/examples/gauge/main.cpp b/examples/gauge/main.cpp index ab45ec2..64ec77b 100644 --- a/examples/gauge/main.cpp +++ b/examples/gauge/main.cpp @@ -2,15 +2,21 @@ #include #include -#include "ftxui/core/screen.hpp" -#include "ftxui/core/dom/elements.hpp" +#include "ftxui/screen.hpp" +#include "ftxui/dom/elements.hpp" int main(int argc, const char *argv[]) { for(float percentage = 0; percentage <= 1.0; percentage+=0.001) { + std::wstring data_downloaded = + std::to_wstring(int(percentage * 44100)) + L"/44100"; using namespace ftxui::dom; auto document = - hbox(text(L"gauge = -"), flex(gauge(percentage)), text(L"-")); + hbox( + text(L"downloading:"), + flex(gauge(percentage)), + text(L" " + data_downloaded) + ); auto screen = ftxui::Screen(100, 1); Render(screen, document.get()); std::cout << '\r' << screen.ToString() << std::flush; diff --git a/examples/menu/CMakeLists.txt b/examples/menu/CMakeLists.txt new file mode 100644 index 0000000..3b5e420 --- /dev/null +++ b/examples/menu/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(menu_main + main.cpp +) +target_link_libraries(menu_main PRIVATE ftxui) diff --git a/examples/menu/main.cpp b/examples/menu/main.cpp new file mode 100644 index 0000000..2dd6234 --- /dev/null +++ b/examples/menu/main.cpp @@ -0,0 +1,21 @@ +#include +#include +#include + +#include "ftxui/screen_interactive.hpp" +#include "ftxui/component/menu.hpp" + +int main(int argc, const char *argv[]) +{ + ftxui::ScreenInteractive screen(30,3); + ftxui::component::Menu menu(screen.delegate()); + menu.entries = { + L"entry 1", + L"entry 2", + L"entry 3" + }; + menu.selected = 0; + menu.on_enter = screen.ExitLoopClosure(); + + screen.Loop(); +} diff --git a/examples/menu2/CMakeLists.txt b/examples/menu2/CMakeLists.txt new file mode 100644 index 0000000..719d9bc --- /dev/null +++ b/examples/menu2/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(menu2_main + main.cpp +) +target_link_libraries(menu2_main PRIVATE ftxui) diff --git a/examples/menu2/main.cpp b/examples/menu2/main.cpp new file mode 100644 index 0000000..80accaf --- /dev/null +++ b/examples/menu2/main.cpp @@ -0,0 +1,75 @@ +#include +#include +#include + +#include "ftxui/screen_interactive.hpp" +#include "ftxui/component/menu.hpp" +#include "ftxui/component/component_horizontal.hpp" +#include "ftxui/component/component_vertical.hpp" +#include "ftxui/util/string.hpp" + +using namespace ftxui::component; +using namespace ftxui::dom; + +class MyComponent : ComponentHorizontal { + public: + MyComponent(ftxui::component::Delegate* delegate) + : ComponentHorizontal(delegate), + left_menu(delegate->NewChild()), + right_menu(delegate->NewChild()) { + left_menu.entries = {L"0%", L"10%", L"20%", L"30%", L"40%", L"50%", + L"60%", L"70%", L"80%", L"90%"}; + right_menu.entries = {L"0%", L"1%", L"2%", L"3%", L"4%", L"5%", + L"6%", L"7%", L"8%", L"9%", L"10%"}; + + left_menu.on_enter = [this]() { on_enter(); }; + right_menu.on_enter = [this]() { on_enter(); }; + Focus(&left_menu); + } + + std::function on_enter = [](){}; + private: + Menu left_menu; + Menu right_menu; + + Element Render() override { + int sum = left_menu.selected * 10 + right_menu.selected; + return + frame( + vbox( + // -------- Top panel -------------- + hbox( + // -------- Left Menu -------------- + flex( + vbox( + hcenter(bold(text(L"Percentage by 10%"))), + left_menu.Render() + ) + ), + // -------- Right Menu -------------- + flex( + vbox( + hcenter(bold(text(L"Percentage by 1%"))), + right_menu.Render() + ) + ), + flex() + ), + separator(), + // -------- Bottom panel -------------- + flex(vbox( + hbox(text(L" gauge : "), gauge(sum/100.0)), + hbox(text(L" text : "), text(to_wstring(std::to_string(sum) + " %"))) + )) + ) + ); + } +}; + +int main(int argc, const char *argv[]) +{ + ftxui::ScreenInteractive screen(60,17); + MyComponent component(screen.delegate()); + component.on_enter = screen.ExitLoopClosure(); + screen.Loop(); +} diff --git a/examples/print_key_press/CMakeLists.txt b/examples/print_key_press/CMakeLists.txt new file mode 100644 index 0000000..8245f94 --- /dev/null +++ b/examples/print_key_press/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(print_key_press + main.cpp +) +target_link_libraries(print_key_press PRIVATE ftxui) diff --git a/examples/print_key_press/main.cpp b/examples/print_key_press/main.cpp new file mode 100644 index 0000000..b36d281 --- /dev/null +++ b/examples/print_key_press/main.cpp @@ -0,0 +1,44 @@ +#include +#include +#include + +#include "ftxui/screen_interactive.hpp" +#include "ftxui/component/component.hpp" +#include "ftxui/util/string.hpp" + +class DrawKey : public ftxui::component::Component { + public: + DrawKey(ftxui::component::Delegate* delegate) + : ftxui::component::Component(delegate) {} + + ftxui::dom::Element Render() override { + using namespace ftxui::dom; + Children children; + for (size_t i = std::max(0, (int)keys.size() - 10); i < keys.size(); ++i) { + try { + std::string line = std::to_string(i) + " -> " + std::to_string(keys[i]) + + " (" + char(keys[i]) + ")"; + children.push_back(text(to_wstring(line))); + } catch (...) { + std::string line = std::to_string(i) + " -> " + std::to_string(keys[i]) + + " (undefined)"; + children.push_back(text(to_wstring(line))); + } + } + return vbox(std::move(children)); + } + + bool Event(int key) override { + keys.push_back(key); + return true; + } + + private: + std::vector keys; +}; + +int main(int argc, const char* argv[]) { + ftxui::ScreenInteractive screen(80,10); + DrawKey draw_key(screen.delegate()); + screen.Loop(); +} diff --git a/examples/separator/main.cpp b/examples/separator/main.cpp index 0c23f32..e3426d8 100644 --- a/examples/separator/main.cpp +++ b/examples/separator/main.cpp @@ -1,5 +1,5 @@ -#include "ftxui/core/screen.hpp" -#include "ftxui/core/dom/elements.hpp" +#include "ftxui/screen.hpp" +#include "ftxui/dom/elements.hpp" #include int main(int argc, const char *argv[]) @@ -15,7 +15,7 @@ int main(int argc, const char *argv[]) center(text(L"bottom-column")) )) ); - auto screen = ftxui::Screen::WholeTerminal(); + auto screen = ftxui::Screen::TerminalFullscreen(); Render(screen, document.get()); std::cout << screen.ToString(); diff --git a/examples/toggle/CMakeLists.txt b/examples/toggle/CMakeLists.txt new file mode 100644 index 0000000..a1784e2 --- /dev/null +++ b/examples/toggle/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(toogle_main + main.cpp +) +target_link_libraries(toogle_main PRIVATE ftxui) diff --git a/examples/toggle/main.cpp b/examples/toggle/main.cpp new file mode 100644 index 0000000..959cc70 --- /dev/null +++ b/examples/toggle/main.cpp @@ -0,0 +1,69 @@ +#include +#include +#include + +#include "ftxui/screen_interactive.hpp" +#include "ftxui/component/toggle.hpp" +#include "ftxui/component/component_horizontal.hpp" +#include "ftxui/component/component_vertical.hpp" +#include "ftxui/util/string.hpp" + +using namespace ftxui::component; +using namespace ftxui::dom; + +class MyComponent : ComponentVertical { + public: + MyComponent(ftxui::component::Delegate* delegate) + : ComponentVertical(delegate), + toggle_1(delegate->NewChild()), + toggle_2(delegate->NewChild()), + toggle_3(delegate->NewChild()) { + toggle_1.on = L"On"; + toggle_1.off = L"Off"; + + toggle_2.on = L"Enabled"; + toggle_2.off = L"Disabled"; + + toggle_3.on = L"10€"; + toggle_3.off = L"0€"; + + Focus(&toggle_1); + } + + std::function on_enter = []() {}; + + private: + Toggle toggle_1; + Toggle toggle_2; + Toggle toggle_3; + + Element Render() override { + return + vbox( + text(L"Choose your options:"), + text(L""), + hbox(text(L" * Poweroff on startup : "), toggle_1.Render()), + hbox(text(L" * Out of process : "), toggle_2.Render()), + hbox(text(L" * Price of the information : "), toggle_3.Render()) + ); + } + + bool Event(int key) override { + if (ComponentVertical::Event(key)) + return true; + + if (key == 10) { + on_enter(); + return true; + } + + return false; + } +}; + +int main(int argc, const char* argv[]) { + ftxui::ScreenInteractive screen(50,5); + MyComponent component(screen.delegate()); + component.on_enter = screen.ExitLoopClosure(); + screen.Loop(); +} diff --git a/examples/vbox_hbox/main.cpp b/examples/vbox_hbox/main.cpp index bae8090..f4efb41 100644 --- a/examples/vbox_hbox/main.cpp +++ b/examples/vbox_hbox/main.cpp @@ -1,5 +1,5 @@ -#include "ftxui/core/screen.hpp" -#include "ftxui/core/dom/elements.hpp" +#include "ftxui/screen.hpp" +#include "ftxui/dom/elements.hpp" #include int main(int argc, const char *argv[]) @@ -27,7 +27,7 @@ int main(int argc, const char *argv[]) text(L"south-east") ) ); - auto screen = ftxui::Screen::WholeTerminal(); + auto screen = ftxui::Screen::TerminalFullscreen(); Render(screen, document.get()); std::cout << screen.ToString(); diff --git a/ftxui/CMakeLists.txt b/ftxui/CMakeLists.txt index 46a91b5..dc5d1ff 100644 --- a/ftxui/CMakeLists.txt +++ b/ftxui/CMakeLists.txt @@ -2,23 +2,32 @@ cmake_minimum_required(VERSION 3.0) project(ftxui) add_library(ftxui - src/ftxui/core/component.cpp - src/ftxui/core/dom/frame.cpp - src/ftxui/core/dom/centered.cpp - src/ftxui/core/dom/flex.cpp - src/ftxui/core/dom/frame.cpp - src/ftxui/core/dom/gauge.cpp - src/ftxui/core/dom/hbox.cpp - src/ftxui/core/dom/node.cpp - src/ftxui/core/dom/separator.cpp - src/ftxui/core/dom/text.cpp - src/ftxui/core/dom/vbox.cpp - src/ftxui/core/screen.cpp - src/ftxui/core/terminal.cpp + src/ftxui/component/component.cpp + src/ftxui/component/component_direction.cpp + src/ftxui/component/component_horizontal.cpp + src/ftxui/component/component_vertical.cpp + src/ftxui/component/toggle.cpp + src/ftxui/component/menu.cpp + src/ftxui/dom/bold.cpp + src/ftxui/dom/dim.cpp + src/ftxui/dom/underlined.cpp + src/ftxui/dom/inverted.cpp + src/ftxui/dom/composite_decorator.cpp + src/ftxui/dom/flex.cpp + src/ftxui/dom/frame.cpp + src/ftxui/dom/frame.cpp + src/ftxui/dom/gauge.cpp + src/ftxui/dom/hbox.cpp + src/ftxui/dom/node.cpp + src/ftxui/dom/separator.cpp + src/ftxui/dom/text.cpp + src/ftxui/dom/vbox.cpp + src/ftxui/screen.cpp + src/ftxui/screen_interactive.cpp + src/ftxui/terminal.cpp src/ftxui/util/string.cpp ) - target_include_directories(ftxui PUBLIC include PRIVATE src @@ -50,8 +59,9 @@ if (GTEST_FOUND AND THREADS_FOUND) endfunction(add_new_test) add_new_test(dom_tests - src/ftxui/core/dom/text_test.cpp - src/ftxui/core/dom/hbox_test.cpp - src/ftxui/core/dom/vbox_test.cpp + src/ftxui/dom/gauge_test.cpp + src/ftxui/dom/hbox_test.cpp + src/ftxui/dom/text_test.cpp + src/ftxui/dom/vbox_test.cpp ) endif() diff --git a/ftxui/include/ftxui/README.md b/ftxui/include/ftxui/README.md index 5b0dc90..74e2a21 100644 --- a/ftxui/include/ftxui/README.md +++ b/ftxui/include/ftxui/README.md @@ -1 +1,31 @@ -State => Components => Document => Text. +# +* Level 0: terminal output. +* Level 1: ftxui::Screen +* Level 2: ftxui::dom::Node +* Level 3: ftxui::component::Component + +## Level 0: terminal output. + The terminal you know, you can append text on it. It is represented by + std::cout. + +## Level 1: ftxui::Screen + A rectangular grid of characters. + Use Terminal::ToString() to append its content into the console. + +## Level 2: ftxui::dom::Node + A hierarchical set of element. + They handle layout and Render themself on the screen. + See ftxui/dom/elements.hpp + + You can make implement your own. + +## Level 3: ftxui::component::Component + A hierarchical set of component. A component render itself by producing + ftxui::dom::Node in Component::Render(). + + Some component can handle events: + * keyboard + * mouse + * terminal event + + You can make implement your own. diff --git a/ftxui/include/ftxui/core/box.hpp b/ftxui/include/ftxui/box.hpp similarity index 100% rename from ftxui/include/ftxui/core/box.hpp rename to ftxui/include/ftxui/box.hpp diff --git a/ftxui/include/ftxui/component/Event.hpp b/ftxui/include/ftxui/component/Event.hpp new file mode 100644 index 0000000..4ce8a77 --- /dev/null +++ b/ftxui/include/ftxui/component/Event.hpp @@ -0,0 +1,34 @@ +#ifndef FTXUI_COMPONENT_EVENT +#define FTXUI_COMPONENT_EVENT + +namespace ftxui { +namespace component { + +struct Event{ + // --- Character --- + static Event Character(char); + + // --- Arrow --- + static Event Arrow_Left; + static Event Arrow_Right; + static Event Arrow_Up; + static Event Arrow_Down; + + // --- Other --- + static Event Backspace; + static Event Delete; + static Event Escape; + static Event F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12; + + // Internal representation. + int values [3]; + Event(int values[3]) : values(values); + +}; + + +} // namespace component +} // namespace ftxui + + +#endif /* end of include guard: FTXUI_COMPONENT_EVENT */ diff --git a/ftxui/include/ftxui/component/component.hpp b/ftxui/include/ftxui/component/component.hpp new file mode 100644 index 0000000..80e77b7 --- /dev/null +++ b/ftxui/include/ftxui/component/component.hpp @@ -0,0 +1,44 @@ +#ifndef FTXUI_COMPONENT_COMPONENT_HPP +#define FTXUI_COMPONENT_COMPONENT_HPP + +#include "ftxui/dom/elements.hpp" +#include "ftxui/component/delegate.hpp" + +namespace ftxui { +namespace component { + +class Delegate; +class Focus; + +class Component { + public: + // Constructor/Destructor. + Component(Delegate* delegate); + virtual ~Component(); + + // Render the component. + virtual dom::Element Render(); + + // Handle an event. By default, it calls this function on each children. + virtual bool Event(int key); + + // If this component contains children, this indicates which one is active. It + // can be none of them. + // We say an element has the focus if the chain of GetActiveChild() from the + // root component contains this object. + virtual Component* GetActiveChild() { return nullptr; } + bool Active(); // True is this component is an active child. + bool Focused(); // True if all the ancestors are active childs. + + Component* Parent(); + Component* PreviousSibling(); + Component* NextSibling(); + + private: + Delegate* delegate_; +}; + +} // namespace component +} // namespace ftxui + +#endif /* end of include guard: FTXUI_COMPONENT_COMPONENT_HPP */ diff --git a/ftxui/include/ftxui/component/component_direction.hpp b/ftxui/include/ftxui/component/component_direction.hpp new file mode 100644 index 0000000..d71f2ac --- /dev/null +++ b/ftxui/include/ftxui/component/component_direction.hpp @@ -0,0 +1,25 @@ +#ifndef FTXUI_COMPONENT_COMPONENT_DIRECTION_H_ +#define FTXUI_COMPONENT_COMPONENT_DIRECTION_H_ + +#include "ftxui/component/component.hpp" + +namespace ftxui { +namespace component { + +// A component where focus and events are automatically handled for you. +class ComponentDirection : public Component { + public: + ComponentDirection(Delegate* delegate); + bool Event(int key) override; + Component* GetActiveChild() override; + + protected: + void Focus(Component* child); + virtual bool HandleDirection(int key) = 0; + Component* active_child_; +}; + +} // namespace component +} // namespace ftxui + +#endif /* end of include guard: FTXUI_COMPONENT_COMPONENT_DIRECTION_H_ */ diff --git a/ftxui/include/ftxui/component/component_horizontal.hpp b/ftxui/include/ftxui/component/component_horizontal.hpp new file mode 100644 index 0000000..f518c32 --- /dev/null +++ b/ftxui/include/ftxui/component/component_horizontal.hpp @@ -0,0 +1,20 @@ +#ifndef FTXUI_COMPONENT_COMPONENT_HORIZONTAL_H_ +#define FTXUI_COMPONENT_COMPONENT_HORIZONTAL_H_ + +#include "ftxui/component/component_direction.hpp" + +namespace ftxui { +namespace component { + +// A component where focus and events are automatically handled for you. +// It assumes its children are put in the horizontal direction. +class ComponentHorizontal : public ComponentDirection { + public: + ComponentHorizontal(Delegate* delegate); + bool HandleDirection(int key) override; +}; + +} // namespace component +} // namespace ftxui + +#endif /* end of include guard: FTXUI_COMPONENT_COMPONENT_HORIZONTAL_H_ */ diff --git a/ftxui/include/ftxui/component/component_vertical.hpp b/ftxui/include/ftxui/component/component_vertical.hpp new file mode 100644 index 0000000..a840858 --- /dev/null +++ b/ftxui/include/ftxui/component/component_vertical.hpp @@ -0,0 +1,20 @@ +#ifndef FTXUI_COMPONENT_COMPONENT_VERTICAL_H_ +#define FTXUI_COMPONENT_COMPONENT_VERTICAL_H_ + +#include "ftxui/component/component_direction.hpp" + +namespace ftxui { +namespace component { + +// A component where focus and events are automatically handled for you. +// It assumes its children are put in the vertical direction. +class ComponentVertical : public ComponentDirection { + public: + ComponentVertical(Delegate* delegate); + bool HandleDirection(int key) override; +}; + +} // namespace component +} // namespace ftxui + +#endif /* end of include guard: FTXUI_COMPONENT_COMPONENT_VERTICAL_H_ */ diff --git a/ftxui/include/ftxui/component/delegate.hpp b/ftxui/include/ftxui/component/delegate.hpp new file mode 100644 index 0000000..d4bc236 --- /dev/null +++ b/ftxui/include/ftxui/component/delegate.hpp @@ -0,0 +1,34 @@ +#ifndef FTXUI_COMPONENT_DELEGATE_HPP +#define FTXUI_COMPONENT_DELEGATE_HPP + +#include "ftxui/dom/elements.hpp" + +namespace ftxui { +namespace component { + +class Component; + +class Delegate { + public: + Delegate() {} + virtual ~Delegate() {} + + // A Delegate shadows a component. + virtual void Register(Component* component) = 0; + virtual Component* component() = 0; + + // Create new children. + virtual Delegate* NewChild() = 0; + virtual std::vector children() = 0; + + // Navigate in the tree. + virtual Delegate* PreviousSibling() = 0; + virtual Delegate* NextSibling() = 0; + virtual Delegate* Parent() = 0; + virtual Delegate* Root() = 0; +}; + +} // namespace component +} // namespace ftxui + +#endif /* end of include guard: FTXUI_COMPONENT_DELEGATE_HPP */ diff --git a/ftxui/include/ftxui/component/menu.hpp b/ftxui/include/ftxui/component/menu.hpp new file mode 100644 index 0000000..79dd19f --- /dev/null +++ b/ftxui/include/ftxui/component/menu.hpp @@ -0,0 +1,31 @@ +#ifndef FTXUI_COMPONENT_MENU +#define FTXUI_COMPONENT_MENU + +#include "ftxui/component/component.hpp" +#include + +namespace ftxui { +namespace component { + +class Menu : public Component { + public: + // Constructor. + Menu(Delegate*); + + // State. + std::vector entries = {}; + int selected = 0; + + // State update callback. + std::function on_change = [](){}; + std::function on_enter = [](){}; + + // Component implementation. + dom::Element Render() override; + bool Event(int key) override; +}; + +} // namespace component +} // namespace ftxui + +#endif /* end of include guard: FTXUI_COMPONENT_MENU */ diff --git a/ftxui/include/ftxui/component/toggle.hpp b/ftxui/include/ftxui/component/toggle.hpp new file mode 100644 index 0000000..daa18e7 --- /dev/null +++ b/ftxui/include/ftxui/component/toggle.hpp @@ -0,0 +1,31 @@ +#ifndef FTXUI_COMPONENT_TOGGLE_H_ +#define FTXUI_COMPONENT_TOGGLE_H_ + +#include "ftxui/component/component.hpp" +#include + +namespace ftxui { +namespace component { + +class Toggle : public Component { + public: + // Constructor. + Toggle(Delegate*); + + // State. + bool activated = true; + std::wstring on = L"On"; + std::wstring off = L"Off"; + + // Callback. + std::function on_change = [](){}; + + // Component implementation. + dom::Element Render() override; + bool Event(int key) override; +}; + +} // namespace component +} // namespace ftxui + +#endif /* end of include guard: FTXUI_COMPONENT_TOGGLE_H_ */ diff --git a/ftxui/include/ftxui/core/component.hpp b/ftxui/include/ftxui/core/component.hpp deleted file mode 100644 index fb0c4f0..0000000 --- a/ftxui/include/ftxui/core/component.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef FTXUI_STATE_HPP -#define FTXUI_STATE_HPP - -#include "ftxui/core/requirement.hpp" -#include "ftxui/core/document.hpp" - -namespace ftxui { - -class Component { - public: - virtual Document Render() = 0; - - // Requirement ------------------------------------------------------------- - virtual void ComputeRequirement(); - Requirement requirement() { return requirement_; } - - private: - Requirement requirement_; -}; - -}; // namespace ftxui - -#endif /* end of include guard: FTXUI_STATE_HPP */ diff --git a/ftxui/include/ftxui/core/document.hpp b/ftxui/include/ftxui/core/document.hpp deleted file mode 100644 index 51034f9..0000000 --- a/ftxui/include/ftxui/core/document.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef FTXUI_DOCUMENT_HPP -#define FTXUI_DOCUMENT_HPP - -namespace ftxui { - -class Document { - -}; - -}; - -#endif /* end of include guard: FTXUI_DOCUMENT_HPP */ diff --git a/ftxui/include/ftxui/core/screen.hpp b/ftxui/include/ftxui/core/screen.hpp deleted file mode 100644 index 9d67601..0000000 --- a/ftxui/include/ftxui/core/screen.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef FTXUI_CORE_SCREEN -#define FTXUI_CORE_SCREEN - -#include -#include -#include - -namespace ftxui { -namespace dom { - class Node; -} - -class Screen { - public: - Screen(size_t dimx, size_t dimy); - wchar_t& at(size_t x, size_t y); - std::string ToString(); - - size_t dimx() { return dimx_;} - size_t dimy() { return dimy_;} - - static Screen WholeTerminal(); - static Screen TerminalOutput(std::unique_ptr& element); - - private: - size_t dimx_; - size_t dimy_; - std::vector lines_; -}; - -}; // namespace ftxui - -#endif /* end of include guard: FTXUI_CORE_SCREEN */ diff --git a/ftxui/include/ftxui/core/dom/elements.hpp b/ftxui/include/ftxui/dom/elements.hpp similarity index 61% rename from ftxui/include/ftxui/core/dom/elements.hpp rename to ftxui/include/ftxui/dom/elements.hpp index df2f7cc..40c2dfe 100644 --- a/ftxui/include/ftxui/core/dom/elements.hpp +++ b/ftxui/include/ftxui/dom/elements.hpp @@ -1,16 +1,14 @@ -#ifndef FTXUI_CORE_DOM_ELEMENTS_HPP -#define FTXUI_CORE_DOM_ELEMENTS_HPP +#ifndef FTXUI_DOM_ELEMENTS_HPP +#define FTXUI_DOM_ELEMENTS_HPP -#include "ftxui/core/dom/node.hpp" -#include +#include "ftxui/dom/node.hpp" namespace ftxui { namespace dom { using Element = std::unique_ptr; using Child = std::unique_ptr; -using Children = std::vector>; - +using Children = std::vector; // --- Layout ---- Element vbox(Children); @@ -22,7 +20,13 @@ Element text(std::wstring text); Element separator(); Element gauge(float ratio); Element frame(Child); -Element frame(std::wstring title, Child); +Element frame(Child title, Child content); + +// -- Decorator (Style) --- +Element bold(Element); +Element dim(Element); +Element inverted(Element); +Element underlined(Element); // --- Decorator --- Element hcenter(Element); @@ -31,23 +35,23 @@ Element center(Element); Element flex(Element); template -std::vector unpack(Args... args) { - std::vector vec; +Children unpack(Args... args) { + Children vec; (vec.push_back(std::forward(args)), ...); return vec; } template -std::unique_ptr vbox(Args... children) { +Element vbox(Args... children) { return vbox(unpack(std::forward(children)...)); } template -std::unique_ptr hbox(Args... children) { +Element hbox(Args... children) { return hbox(unpack(std::forward(children)...)); } }; // namespace dom }; // namespace ftxui -#endif /* end of include guard: FTXUI_CORE_DOM_ELEMENTS_HPP */ +#endif /* end of include guard: FTXUI_DOM_ELEMENTS_HPP */ diff --git a/ftxui/include/ftxui/core/dom/node.hpp b/ftxui/include/ftxui/dom/node.hpp similarity index 79% rename from ftxui/include/ftxui/core/dom/node.hpp rename to ftxui/include/ftxui/dom/node.hpp index f843228..6a5f7be 100644 --- a/ftxui/include/ftxui/core/dom/node.hpp +++ b/ftxui/include/ftxui/dom/node.hpp @@ -1,12 +1,12 @@ -#ifndef CORE_DOM_NODE_HPP -#define CORE_DOM_NODE_HPP +#ifndef DOM_NODE_HPP +#define DOM_NODE_HPP #include #include -#include "ftxui/core/requirement.hpp" -#include "ftxui/core/screen.hpp" -#include "ftxui/core/box.hpp" +#include "ftxui/requirement.hpp" +#include "ftxui/screen.hpp" +#include "ftxui/box.hpp" namespace ftxui { namespace dom { @@ -41,4 +41,4 @@ void Render(Screen& screen, Node* node); }; // namespace dom }; // namespace ftxui -#endif /* end of include guard: CORE_DOM_NODE_HPP */ +#endif /* end of include guard: DOM_NODE_HPP */ diff --git a/ftxui/include/ftxui/core/requirement.hpp b/ftxui/include/ftxui/requirement.hpp similarity index 67% rename from ftxui/include/ftxui/core/requirement.hpp rename to ftxui/include/ftxui/requirement.hpp index 74089b4..2b4e714 100644 --- a/ftxui/include/ftxui/core/requirement.hpp +++ b/ftxui/include/ftxui/requirement.hpp @@ -1,5 +1,5 @@ -#ifndef FTXUI_LAYOUT_REQUIREMENT_HPP -#define FTXUI_LAYOUT_REQUIREMENT_HPP +#ifndef FTXUI_REQUIREMENT_HPP +#define FTXUI_REQUIREMENT_HPP namespace ftxui { @@ -25,4 +25,4 @@ struct Requirement { }; // namespace ftxui -#endif /* end of include guard: FTXUI_LAYOUT_REQUIREMENT_HPP */ +#endif /* end of include guard: FTXUI_REQUIREMENT_HPP */ diff --git a/ftxui/include/ftxui/screen.hpp b/ftxui/include/ftxui/screen.hpp new file mode 100644 index 0000000..2782271 --- /dev/null +++ b/ftxui/include/ftxui/screen.hpp @@ -0,0 +1,55 @@ +#ifndef FTXUI_SCREEN +#define FTXUI_SCREEN + +#include +#include +#include + +namespace ftxui { +namespace dom { + class Node; +} + +struct Pixel { + wchar_t character = U' '; + bool bold = false; + bool inverted = false; + bool underlined = false; + bool dim = false; +}; + +class Screen { + public: + // Constructor. + Screen(size_t dimx, size_t dimy); + + // Constructor using the terminal. + static Screen TerminalFullscreen(); + static Screen TerminalOutput(std::unique_ptr& element); + + // dom::Node write into the screen using Screen::at. + wchar_t& at(size_t x, size_t y); + Pixel& PixelAt(size_t x, size_t y); + + // Convert the screen into a printable string in the terminal. + std::string ToString(); + + // Get screen dimensions. + size_t dimx() { return dimx_;} + size_t dimy() { return dimy_;} + + // Move the terminal cursor n-lines up with n = dimy(). + std::string ResetPosition(); + + // Fill with space. + void Clear(); + + private: + size_t dimx_; + size_t dimy_; + std::vector> pixels_; +}; + +}; // namespace ftxui + +#endif /* end of include guard: FTXUI_SCREEN */ diff --git a/ftxui/include/ftxui/screen_interactive.hpp b/ftxui/include/ftxui/screen_interactive.hpp new file mode 100644 index 0000000..1acf9e1 --- /dev/null +++ b/ftxui/include/ftxui/screen_interactive.hpp @@ -0,0 +1,34 @@ +#ifndef FTXUI_SCREEN_INTERACTIVE +#define FTXUI_SCREEN_INTERACTIVE + +#include "ftxui/screen.hpp" +#include +#include + +namespace ftxui { + +namespace component { + class Delegate; + class Component; +} // namespace component + +class ScreenInteractive : public Screen { + public: + ScreenInteractive(size_t dimx, size_t dimy); + ~ScreenInteractive(); + component::Delegate* delegate(); + void Loop(); + std::function ExitLoopClosure(); + + private: + class Delegate; + std::unique_ptr delegate_; + + void Clear(); + void Draw(); + bool quit_ = false; +}; + +} // namespace ftxui + +#endif /* end of include guard: FTXUI_SCREEN_INTERACTIVE */ diff --git a/ftxui/src/ftxui/component/component.cpp b/ftxui/src/ftxui/component/component.cpp new file mode 100644 index 0000000..369e9c3 --- /dev/null +++ b/ftxui/src/ftxui/component/component.cpp @@ -0,0 +1,59 @@ +#include "ftxui/component/component.hpp" +#include "ftxui/component/delegate.hpp" +#include + +namespace ftxui { +namespace component { + +Component::Component(Delegate* delegate) { + delegate_ = delegate; + delegate_->Register(this); +} + +Component::~Component() {} + +dom::Element Component::Render() { + using namespace ftxui::dom; + return text(L"Not implemented component"); +} + +bool Component::Event(int key) { + return false; +} + +bool Component::Focused() { + Delegate* current = delegate_->Root(); + while (current) { + if (current == delegate_) + return true; + + Component* active_child = current->component()->GetActiveChild(); + current = active_child ? active_child->delegate_ : nullptr; + } + return false; +} + +bool Component::Active() { + Delegate* parent = delegate_->Parent(); + return parent && parent->component()->GetActiveChild() == this; +} + +Component* Component::PreviousSibling() { + Delegate* sibling = delegate_->PreviousSibling(); + return sibling ? sibling->component() : nullptr; +} + +Component* Component::NextSibling() { + Delegate* sibling = delegate_->NextSibling(); + return sibling ? sibling->component() : nullptr; +} + +Component* Component::Parent() { + Delegate* parent_delegate = delegate_->Parent(); + if (!parent_delegate) + return nullptr; + return parent_delegate->component(); +} + +} // namespace component +} // namespace ftxui diff --git a/ftxui/src/ftxui/component/component_direction.cpp b/ftxui/src/ftxui/component/component_direction.cpp new file mode 100644 index 0000000..fa77b81 --- /dev/null +++ b/ftxui/src/ftxui/component/component_direction.cpp @@ -0,0 +1,31 @@ +#include "ftxui/component/component_direction.hpp" + +namespace ftxui { +namespace component { + +ComponentDirection::ComponentDirection(Delegate* delegate) + : Component(delegate), active_child_(nullptr) {} + +bool ComponentDirection::Event(int key) { + if (!Focused()) + return false; + + if (!active_child_) + return false; + + if (active_child_->Event(key)) + return true; + + return HandleDirection(key); +} + +Component* ComponentDirection::GetActiveChild() { + return active_child_; +} + +void ComponentDirection::Focus(Component* child) { + active_child_ = child; +} + +} // namespace component +} // namespace ftxui diff --git a/ftxui/src/ftxui/component/component_horizontal.cpp b/ftxui/src/ftxui/component/component_horizontal.cpp new file mode 100644 index 0000000..012098a --- /dev/null +++ b/ftxui/src/ftxui/component/component_horizontal.cpp @@ -0,0 +1,32 @@ +#include "ftxui/component/component_horizontal.hpp" + +namespace ftxui { +namespace component { + +ComponentHorizontal::ComponentHorizontal(Delegate* delegate) + : ComponentDirection(delegate) {} + +bool ComponentHorizontal::HandleDirection(int key) { + // Left pressed ? + if (key == 68 || key == 'h') { + Component* previous_sibling = active_child_->PreviousSibling(); + if (previous_sibling) { + active_child_ = previous_sibling; + return true; + } + } + + // Left pressed ? + if (key == 67 || key == 'l') { + Component* next_sibling = active_child_->NextSibling(); + if (next_sibling) { + active_child_ = next_sibling; + return true; + } + } + + return false; +} + +} // namespace component +} // namespace ftxui diff --git a/ftxui/src/ftxui/component/component_vertical.cpp b/ftxui/src/ftxui/component/component_vertical.cpp new file mode 100644 index 0000000..70d516e --- /dev/null +++ b/ftxui/src/ftxui/component/component_vertical.cpp @@ -0,0 +1,32 @@ +#include "ftxui/component/component_vertical.hpp" + +namespace ftxui { +namespace component { + +ComponentVertical::ComponentVertical(Delegate* delegate) + : ComponentDirection(delegate) {} + +bool ComponentVertical::HandleDirection(int key) { + // Up pressed ? + if (key == 65 || key == 'k') { + Component* previous_sibling = active_child_->PreviousSibling(); + if (previous_sibling) { + active_child_ = previous_sibling; + return true; + } + } + + // Down pressed ? + if (key == 66 || key == 'j') { + Component* next_sibling = active_child_->NextSibling(); + if (next_sibling) { + active_child_ = next_sibling; + return true; + } + } + + return false; +} + +} // namespace component +} // namespace ftxui diff --git a/ftxui/src/ftxui/component/menu.cpp b/ftxui/src/ftxui/component/menu.cpp new file mode 100644 index 0000000..3d5f259 --- /dev/null +++ b/ftxui/src/ftxui/component/menu.cpp @@ -0,0 +1,53 @@ +#include "ftxui/component/menu.hpp" +#include + +namespace ftxui { +namespace component { + +Menu::Menu(Delegate* delegate) : Component(delegate) {} + +dom::Element Menu::Render() { + using namespace dom; + std::vector elements; + bool focused = Focused(); + for (size_t i = 0; i < entries.size(); ++i) { + if (size_t(selected) == i) { + if (focused) + elements.push_back(inverted(text(L"> " + entries[i]))); + else + elements.push_back(bold(text(L"> " + entries[i]))); + } + else { + elements.push_back(text(L" " + entries[i])); + } + } + return vbox(std::move(elements)); +} + +bool Menu::Event(int key) { + if (!Focused()) + return false; + + int new_selected = selected; + if (key == 65 || key == 'k') + new_selected--; + if (key == 66 || key == 'j') + new_selected++; + new_selected = std::max(0, std::min(int(entries.size())-1, new_selected)); + + if (selected != new_selected) { + selected = new_selected; + on_change(); + return true; + } + + if (key == 10) { + on_enter(); + return true; + } + + return false; +} + +} // namespace component +} // namespace ftxui diff --git a/ftxui/src/ftxui/component/toggle.cpp b/ftxui/src/ftxui/component/toggle.cpp new file mode 100644 index 0000000..023ca0e --- /dev/null +++ b/ftxui/src/ftxui/component/toggle.cpp @@ -0,0 +1,47 @@ +#include "ftxui/component/toggle.hpp" + +namespace ftxui { +namespace component { + +Toggle::Toggle(Delegate* delegate) : Component(delegate) {} + +dom::Element Toggle::Render() { + using namespace dom; + auto highlight = Focused() ? inverted : bold; + + Children children; + children.push_back(text(L"[")); + if (activated) { + children.push_back(highlight(text(on))); + children.push_back(text(L"|")); + children.push_back(dim(text(off))); + } else { + children.push_back(dim(text(on))); + children.push_back(text(L"|")); + children.push_back(highlight(text(off))); + } + children.push_back(text(L"]")); + return hbox(std::move(children)); +} + +bool Toggle::Event(int key) { + + if (activated) { + if (key == 67 || key == 'l') { + activated = false; + on_change(); + return true; + } + } else { + if (key == 68 || key == 'h') { + activated = true; + on_change(); + return true; + } + } + + return false; +} + +} // namespace component +} // namespace ftxui diff --git a/ftxui/src/ftxui/core/component.cpp b/ftxui/src/ftxui/core/component.cpp deleted file mode 100644 index cd28bc8..0000000 --- a/ftxui/src/ftxui/core/component.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "ftxui/core/component.hpp" - -namespace ftxui { - void Component::ComputeRequirement() {} -} // namespace ftxui. diff --git a/ftxui/src/ftxui/core/dom/frame.cpp b/ftxui/src/ftxui/core/dom/frame.cpp deleted file mode 100644 index 4b43f58..0000000 --- a/ftxui/src/ftxui/core/dom/frame.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "ftxui/core/dom/node.hpp" -#include "ftxui/core/dom/elements.hpp" - -namespace ftxui { -namespace dom { - -static wchar_t charset[] = L"┌┐└┘─│"; - -class Frame : public Node { - public: - Frame(Child child) : Node(unpack(std::move(child))) {} - ~Frame() override {} - - void ComputeRequirement() override { - children[0]->ComputeRequirement(); - requirement_ = children[0]->requirement(); - requirement_.min.x += 2; - requirement_.min.y += 2; - } - - void SetBox(Box box) override { - Node::SetBox(box); - box.left++; - box.right--; - box.top++; - box.bottom--; - children[0]->SetBox(box); - } - - void Render(Screen& screen) override { - if (box_.left >= box_.right || box_.top >= box_.bottom) - return; - - screen.at(box_.left, box_.top) = charset[0]; - screen.at(box_.right, box_.top) = charset[1]; - screen.at(box_.left, box_.bottom) = charset[2]; - screen.at(box_.right, box_.bottom) = charset[3]; - for(float x = box_.left + 1; xRender(screen); - } - private: - float progress_; -}; - -std::unique_ptr frame(Child child) { - return std::make_unique(std::move(child)); -} - -}; // namespace dom -}; // namespace ftxui diff --git a/ftxui/src/ftxui/core/screen.cpp b/ftxui/src/ftxui/core/screen.cpp deleted file mode 100644 index 768e17e..0000000 --- a/ftxui/src/ftxui/core/screen.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "ftxui/core/screen.hpp" -#include "ftxui/core/terminal.hpp" -#include "ftxui/util/string.hpp" -#include "ftxui/core/dom/node.hpp" - -#include - -namespace ftxui { - -Screen::Screen(size_t dimx, size_t dimy) - : dimx_(dimx), dimy_(dimy), lines_(dimy, std::wstring(dimx, U' ')) {} - -std::string Screen::ToString() { - std::stringstream ss; - for (size_t y = 0; y < dimy_; ++y) { - ss << to_string(lines_[y]); - if (y + 1 < dimy_) - ss << '\n'; - } - return ss.str(); -} - -wchar_t& Screen::at(size_t x, size_t y) { - return lines_[y][x]; -} - -Screen Screen::WholeTerminal() { - Terminal::Dimensions size = Terminal::Size(); - return Screen(size.dimx, size.dimy); -} - -Screen Screen::TerminalOutput(std::unique_ptr& element) { - element->ComputeRequirement(); - Terminal::Dimensions size = Terminal::Size(); - return Screen(size.dimx, element->requirement().min.y); -} - -}; // namespace ftxui diff --git a/ftxui/src/ftxui/dom/bold.cpp b/ftxui/src/ftxui/dom/bold.cpp new file mode 100644 index 0000000..cee8d97 --- /dev/null +++ b/ftxui/src/ftxui/dom/bold.cpp @@ -0,0 +1,37 @@ +#include "ftxui/dom/node.hpp" +#include "ftxui/dom/elements.hpp" + +namespace ftxui { +namespace dom { + +class Bold : public Node { + public: + Bold(Children children) : Node(std::move(children)) {} + ~Bold() override {} + + void ComputeRequirement() override { + Node::ComputeRequirement(); + requirement_ = children[0]->requirement(); + } + + void SetBox(Box box) override { + Node::SetBox(box); + children[0]->SetBox(box); + } + + void Render(Screen& screen) override { + Node::Render(screen); + for (int y = box_.top; y <= box_.bottom; ++y) { + for (int x = box_.left; x <= box_.right; ++x) { + screen.PixelAt(x,y).bold = true; + } + } + } +}; + +std::unique_ptr bold(Child child) { + return std::make_unique(unpack(std::move(child))); +} + +}; // namespace dom +}; // namespace ftxui diff --git a/ftxui/src/ftxui/core/dom/centered.cpp b/ftxui/src/ftxui/dom/composite_decorator.cpp similarity index 83% rename from ftxui/src/ftxui/core/dom/centered.cpp rename to ftxui/src/ftxui/dom/composite_decorator.cpp index 254c573..cedc328 100644 --- a/ftxui/src/ftxui/core/dom/centered.cpp +++ b/ftxui/src/ftxui/dom/composite_decorator.cpp @@ -1,5 +1,5 @@ -#include "ftxui/core/dom/node.hpp" -#include "ftxui/core/dom/elements.hpp" +#include "ftxui/dom/node.hpp" +#include "ftxui/dom/elements.hpp" namespace ftxui { namespace dom { diff --git a/ftxui/src/ftxui/dom/dim.cpp b/ftxui/src/ftxui/dom/dim.cpp new file mode 100644 index 0000000..08283f9 --- /dev/null +++ b/ftxui/src/ftxui/dom/dim.cpp @@ -0,0 +1,37 @@ +#include "ftxui/dom/node.hpp" +#include "ftxui/dom/elements.hpp" + +namespace ftxui { +namespace dom { + +class Dim : public Node { + public: + Dim(Children children) : Node(std::move(children)) {} + ~Dim() override {} + + void ComputeRequirement() override { + Node::ComputeRequirement(); + requirement_ = children[0]->requirement(); + } + + void SetBox(Box box) override { + Node::SetBox(box); + children[0]->SetBox(box); + } + + void Render(Screen& screen) override { + Node::Render(screen); + for (int y = box_.top; y <= box_.bottom; ++y) { + for (int x = box_.left; x <= box_.right; ++x) { + screen.PixelAt(x,y).dim = true; + } + } + } +}; + +std::unique_ptr dim(Child child) { + return std::make_unique(unpack(std::move(child))); +} + +}; // namespace dom +}; // namespace ftxui diff --git a/ftxui/src/ftxui/core/dom/flex.cpp b/ftxui/src/ftxui/dom/flex.cpp similarity index 91% rename from ftxui/src/ftxui/core/dom/flex.cpp rename to ftxui/src/ftxui/dom/flex.cpp index 99afa61..9684dff 100644 --- a/ftxui/src/ftxui/core/dom/flex.cpp +++ b/ftxui/src/ftxui/dom/flex.cpp @@ -1,5 +1,5 @@ -#include "ftxui/core/dom/node.hpp" -#include "ftxui/core/dom/elements.hpp" +#include "ftxui/dom/node.hpp" +#include "ftxui/dom/elements.hpp" namespace ftxui { namespace dom { diff --git a/ftxui/src/ftxui/dom/frame.cpp b/ftxui/src/ftxui/dom/frame.cpp new file mode 100644 index 0000000..6112b70 --- /dev/null +++ b/ftxui/src/ftxui/dom/frame.cpp @@ -0,0 +1,92 @@ +#include "ftxui/dom/node.hpp" +#include "ftxui/dom/elements.hpp" + +namespace ftxui { +namespace dom { + +static wchar_t charset[] = L"┌┐└┘─│┬┴┤├"; + +class Frame : public Node { + public: + Frame(Children children) : Node(std::move(children)) {} + ~Frame() override {} + + void ComputeRequirement() override { + Node::ComputeRequirement(); + requirement_ = children[0]->requirement(); + requirement_.min.x += 2; + requirement_.min.y += 2; + if (children.size() == 2) { + requirement_.min.x = + std::max(requirement_.min.x, children[1]->requirement().min.x + 2); + } + } + + void SetBox(Box box) override { + Node::SetBox(box); + if (children.size() == 2) { + Box title_box; + title_box.left = box.left + 1; + title_box.right = box.right - 1; + title_box.top = box.top; + title_box.bottom = box.top; + children[1]->SetBox(title_box); + } + box.left++; + box.right--; + box.top++; + box.bottom--; + children[0]->SetBox(box); + } + + void Render(Screen& screen) override { + // Draw content. + children[0]->Render(screen); + + // Draw the frame. + if (box_.left >= box_.right || box_.top >= box_.bottom) + return; + + screen.at(box_.left, box_.top) = charset[0]; + screen.at(box_.right, box_.top) = charset[1]; + screen.at(box_.left, box_.bottom) = charset[2]; + screen.at(box_.right, box_.bottom) = charset[3]; + for(float x = box_.left + 1; xRender(screen); + } +}; + +std::unique_ptr frame(Child child) { + return std::make_unique(unpack(std::move(child))); +} + +std::unique_ptr frame(Child title, Child content) { + return std::make_unique(unpack(std::move(content), std::move(title))); +} + +}; // namespace dom +}; // namespace ftxui diff --git a/ftxui/src/ftxui/core/dom/gauge.cpp b/ftxui/src/ftxui/dom/gauge.cpp similarity index 92% rename from ftxui/src/ftxui/core/dom/gauge.cpp rename to ftxui/src/ftxui/dom/gauge.cpp index 8b6756f..603d5e2 100644 --- a/ftxui/src/ftxui/core/dom/gauge.cpp +++ b/ftxui/src/ftxui/dom/gauge.cpp @@ -1,5 +1,5 @@ -#include "ftxui/core/dom/node.hpp" -#include "ftxui/core/dom/elements.hpp" +#include "ftxui/dom/node.hpp" +#include "ftxui/dom/elements.hpp" namespace ftxui { namespace dom { diff --git a/ftxui/src/ftxui/dom/gauge_test.cpp b/ftxui/src/ftxui/dom/gauge_test.cpp new file mode 100644 index 0000000..c3f8881 --- /dev/null +++ b/ftxui/src/ftxui/dom/gauge_test.cpp @@ -0,0 +1,34 @@ +#include "ftxui/dom/elements.hpp" +#include "ftxui/screen.hpp" +#include "gtest/gtest.h" + +namespace ftxui { +namespace dom { + +TEST(GaugeTest, zero) { + auto root = gauge(0); + Screen screen(11,1); + Render(screen, root.get()); + + EXPECT_EQ(" ", screen.ToString()); +} + +TEST(GaugeTest, half) { + auto root = gauge(0.5); + Screen screen(11,1); + Render(screen, root.get()); + + EXPECT_EQ("█████▏▋ ", screen.ToString()); +//" ▏▎▍▌▊▉█"; +} + +TEST(GaugeTest, one) { + auto root = gauge(1.0); + Screen screen(11,1); + Render(screen, root.get()); + + EXPECT_EQ("███████████", screen.ToString()); +} + +} // namespace dom +} // namespace ftxui diff --git a/ftxui/src/ftxui/core/dom/hbox.cpp b/ftxui/src/ftxui/dom/hbox.cpp similarity index 95% rename from ftxui/src/ftxui/core/dom/hbox.cpp rename to ftxui/src/ftxui/dom/hbox.cpp index dfd66d7..a96049e 100644 --- a/ftxui/src/ftxui/core/dom/hbox.cpp +++ b/ftxui/src/ftxui/dom/hbox.cpp @@ -1,5 +1,5 @@ -#include "ftxui/core/dom/node.hpp" -#include "ftxui/core/dom/elements.hpp" +#include "ftxui/dom/node.hpp" +#include "ftxui/dom/elements.hpp" namespace ftxui { namespace dom { diff --git a/ftxui/src/ftxui/core/dom/hbox_test.cpp b/ftxui/src/ftxui/dom/hbox_test.cpp similarity index 96% rename from ftxui/src/ftxui/core/dom/hbox_test.cpp rename to ftxui/src/ftxui/dom/hbox_test.cpp index fb05538..83f775b 100644 --- a/ftxui/src/ftxui/core/dom/hbox_test.cpp +++ b/ftxui/src/ftxui/dom/hbox_test.cpp @@ -1,5 +1,5 @@ -#include "ftxui/core/dom/elements.hpp" -#include "ftxui/core/screen.hpp" +#include "ftxui/dom/elements.hpp" +#include "ftxui/screen.hpp" #include "gtest/gtest.h" namespace ftxui { diff --git a/ftxui/src/ftxui/dom/inverted.cpp b/ftxui/src/ftxui/dom/inverted.cpp new file mode 100644 index 0000000..2f4afdb --- /dev/null +++ b/ftxui/src/ftxui/dom/inverted.cpp @@ -0,0 +1,37 @@ +#include "ftxui/dom/node.hpp" +#include "ftxui/dom/elements.hpp" + +namespace ftxui { +namespace dom { + +class Inverted : public Node { + public: + Inverted(Children children) : Node(std::move(children)) {} + ~Inverted() override {} + + void ComputeRequirement() override { + Node::ComputeRequirement(); + requirement_ = children[0]->requirement(); + } + + void SetBox(Box box) override { + Node::SetBox(box); + children[0]->SetBox(box); + } + + void Render(Screen& screen) override { + Node::Render(screen); + for (int y = box_.top; y <= box_.bottom; ++y) { + for (int x = box_.left; x <= box_.right; ++x) { + screen.PixelAt(x,y).inverted = true; + } + } + } +}; + +std::unique_ptr inverted(Child child) { + return std::make_unique(unpack(std::move(child))); +} + +}; // namespace dom +}; // namespace ftxui diff --git a/ftxui/src/ftxui/core/dom/node.cpp b/ftxui/src/ftxui/dom/node.cpp similarity index 84% rename from ftxui/src/ftxui/core/dom/node.cpp rename to ftxui/src/ftxui/dom/node.cpp index db6b266..648c121 100644 --- a/ftxui/src/ftxui/core/dom/node.cpp +++ b/ftxui/src/ftxui/dom/node.cpp @@ -1,4 +1,4 @@ -#include "ftxui/core/dom/node.hpp" +#include "ftxui/dom/node.hpp" namespace ftxui { namespace dom { @@ -8,10 +8,15 @@ Node::Node(std::vector> children) : children(std::move(children)) {} Node::~Node() {} -void Node::ComputeRequirement() {} +void Node::ComputeRequirement() { + for(auto& child : children) + child->ComputeRequirement(); +} + void Node::SetBox(Box box) { box_ = box; } + void Node::Render(Screen& screen) { for(auto& child : children) child->Render(screen); diff --git a/ftxui/src/ftxui/core/dom/separator.cpp b/ftxui/src/ftxui/dom/separator.cpp similarity index 95% rename from ftxui/src/ftxui/core/dom/separator.cpp rename to ftxui/src/ftxui/dom/separator.cpp index cf9a158..db22aeb 100644 --- a/ftxui/src/ftxui/core/dom/separator.cpp +++ b/ftxui/src/ftxui/dom/separator.cpp @@ -1,4 +1,4 @@ -#include "ftxui/core/dom/node.hpp" +#include "ftxui/dom/node.hpp" namespace ftxui { namespace dom { diff --git a/ftxui/src/ftxui/core/dom/text.cpp b/ftxui/src/ftxui/dom/text.cpp similarity index 94% rename from ftxui/src/ftxui/core/dom/text.cpp rename to ftxui/src/ftxui/dom/text.cpp index 7b604fb..218ed06 100644 --- a/ftxui/src/ftxui/core/dom/text.cpp +++ b/ftxui/src/ftxui/dom/text.cpp @@ -1,4 +1,4 @@ -#include "ftxui/core/dom/node.hpp" +#include "ftxui/dom/node.hpp" namespace ftxui { namespace dom { diff --git a/ftxui/src/ftxui/core/dom/text_test.cpp b/ftxui/src/ftxui/dom/text_test.cpp similarity index 92% rename from ftxui/src/ftxui/core/dom/text_test.cpp rename to ftxui/src/ftxui/dom/text_test.cpp index 72775ac..f18f99d 100644 --- a/ftxui/src/ftxui/core/dom/text_test.cpp +++ b/ftxui/src/ftxui/dom/text_test.cpp @@ -1,5 +1,5 @@ -#include "ftxui/core/dom/elements.hpp" -#include "ftxui/core/screen.hpp" +#include "ftxui/dom/elements.hpp" +#include "ftxui/screen.hpp" #include "gtest/gtest.h" namespace ftxui { diff --git a/ftxui/src/ftxui/dom/underlined.cpp b/ftxui/src/ftxui/dom/underlined.cpp new file mode 100644 index 0000000..07f714f --- /dev/null +++ b/ftxui/src/ftxui/dom/underlined.cpp @@ -0,0 +1,37 @@ +#include "ftxui/dom/node.hpp" +#include "ftxui/dom/elements.hpp" + +namespace ftxui { +namespace dom { + +class Underlined : public Node { + public: + Underlined(Children children) : Node(std::move(children)) {} + ~Underlined() override {} + + void ComputeRequirement() override { + Node::ComputeRequirement(); + requirement_ = children[0]->requirement(); + } + + void SetBox(Box box) override { + Node::SetBox(box); + children[0]->SetBox(box); + } + + void Render(Screen& screen) override { + Node::Render(screen); + for (int y = box_.top; y <= box_.bottom; ++y) { + for (int x = box_.left; x <= box_.right; ++x) { + screen.PixelAt(x,y).underlined = true; + } + } + } +}; + +std::unique_ptr underlined(Child child) { + return std::make_unique(unpack(std::move(child))); +} + +}; // namespace dom +}; // namespace ftxui diff --git a/ftxui/src/ftxui/core/dom/vbox.cpp b/ftxui/src/ftxui/dom/vbox.cpp similarity index 95% rename from ftxui/src/ftxui/core/dom/vbox.cpp rename to ftxui/src/ftxui/dom/vbox.cpp index 1556621..3f504e6 100644 --- a/ftxui/src/ftxui/core/dom/vbox.cpp +++ b/ftxui/src/ftxui/dom/vbox.cpp @@ -1,5 +1,5 @@ -#include "ftxui/core/dom/node.hpp" -#include "ftxui/core/dom/elements.hpp" +#include "ftxui/dom/node.hpp" +#include "ftxui/dom/elements.hpp" namespace ftxui { namespace dom { diff --git a/ftxui/src/ftxui/core/dom/vbox_test.cpp b/ftxui/src/ftxui/dom/vbox_test.cpp similarity index 95% rename from ftxui/src/ftxui/core/dom/vbox_test.cpp rename to ftxui/src/ftxui/dom/vbox_test.cpp index f9005a6..912c349 100644 --- a/ftxui/src/ftxui/core/dom/vbox_test.cpp +++ b/ftxui/src/ftxui/dom/vbox_test.cpp @@ -1,5 +1,5 @@ -#include "ftxui/core/dom/elements.hpp" -#include "ftxui/core/screen.hpp" +#include "ftxui/dom/elements.hpp" +#include "ftxui/screen.hpp" #include "gtest/gtest.h" namespace ftxui { diff --git a/ftxui/src/ftxui/screen.cpp b/ftxui/src/ftxui/screen.cpp new file mode 100644 index 0000000..cbb5497 --- /dev/null +++ b/ftxui/src/ftxui/screen.cpp @@ -0,0 +1,91 @@ +#include "ftxui/dom/node.hpp" +#include "ftxui/screen.hpp" +#include "ftxui/terminal.hpp" +#include "ftxui/util/string.hpp" + +#include + +namespace ftxui { + +Screen::Screen(size_t dimx, size_t dimy) + : dimx_(dimx), dimy_(dimy), pixels_(dimy, std::vector(dimx)) {} + +std::string Screen::ToString() { + std::wstringstream ss; + + Pixel previous_pixel; + + for (size_t y = 0; y < dimy_; ++y) { + for (size_t x = 0; x < dimx_; ++x) { + if (pixels_[y][x].bold != previous_pixel.bold) { + if (pixels_[y][x].bold) { + ss << L"\e[1m"; + } else { + ss << L"\e[0m"; + } + } + if (pixels_[y][x].inverted != previous_pixel.inverted) { + if (pixels_[y][x].inverted) { + ss << L"\e[7m"; + } else { + ss << L"\e[27m"; + } + } + if (pixels_[y][x].underlined != previous_pixel.underlined) { + if (pixels_[y][x].underlined) { + ss << L"\e[4m"; + } else { + ss << L"\e[24m"; + } + } + if (pixels_[y][x].dim != previous_pixel.dim) { + if (pixels_[y][x].dim) { + ss << L"\e[2m"; + } else { + ss << L"\e[22m"; + } + } + ss << pixels_[y][x].character; + previous_pixel = pixels_[y][x]; + } + if (y + 1 < dimy_) + ss << '\n'; + } + return to_string(ss.str()); +} + +wchar_t& Screen::at(size_t x, size_t y) { + return pixels_[y][x].character; +} + +Pixel& Screen::PixelAt(size_t x, size_t y) { + return pixels_[y][x]; +} + +// static +Screen Screen::TerminalFullscreen() { + Terminal::Dimensions size = Terminal::Size(); + return Screen(size.dimx, size.dimy); +} + +// static +Screen Screen::TerminalOutput(std::unique_ptr& element) { + element->ComputeRequirement(); + Terminal::Dimensions size = Terminal::Size(); + return Screen(size.dimx, element->requirement().min.y); +} + +std::string Screen::ResetPosition() { + std::stringstream ss; + for(size_t y = 1; y>(dimy_, + std::vector(dimx_, Pixel())); +} + +}; // namespace ftxui diff --git a/ftxui/src/ftxui/screen_interactive.cpp b/ftxui/src/ftxui/screen_interactive.cpp new file mode 100644 index 0000000..7d35943 --- /dev/null +++ b/ftxui/src/ftxui/screen_interactive.cpp @@ -0,0 +1,112 @@ +#include "ftxui/screen_interactive.hpp" +#include "ftxui/component/component.hpp" +#include "ftxui/component/delegate.hpp" +#include +#include +#include +#include + +namespace ftxui { + +class ScreenInteractive::Delegate : public component::Delegate { + public: + Delegate() : root_(this) {} + + void Register(component::Component* c) override { component_ = c; } + + std::vector> child_; + Delegate* NewChild() override { + Delegate* child = new Delegate; + child->root_ = root_; + child->parent_ = this; + + if (!child_.empty()) { + child_.back()->next_sibling_ = child; + child->previous_sibling_ = child_.back().get(); + } + + child_.emplace_back(child); + return child; + } + + void Event(int key) { component_->Event(key); } + + + std::vector children() override { + std::vector ret; + for (auto& it : child_) + ret.push_back(it.get()); + return ret; + } + + Delegate* root_; + Delegate* parent_ = nullptr; + Delegate* previous_sibling_ = nullptr; + Delegate* next_sibling_ = nullptr; + component::Component* component_; + + Delegate* Root() override { return root_; } + Delegate* Parent() override { return parent_; } + Delegate* PreviousSibling() override { return previous_sibling_; } + Delegate* NextSibling() override { return next_sibling_; } + component::Component* component() override { return component_; } +}; + +ScreenInteractive::ScreenInteractive(size_t dimx, size_t dimy) + : Screen(dimx, dimy), delegate_(new Delegate) {} +ScreenInteractive::~ScreenInteractive() {} + +void ScreenInteractive::Loop() { + std::cout << "\033[?9h"; /* Send Mouse Row & Column on Button Press */ + std::cout << "\033[?1000h"; /* Send Mouse X & Y on button press and release */ + std::cout << std::flush; + + // Save the old terminal configuration. + struct termios terminal_configuration_old; + tcgetattr(STDIN_FILENO, &terminal_configuration_old); + + // Set the new terminal configuration + struct termios terminal_configuration_new; + terminal_configuration_new = terminal_configuration_old; + + // Non canonique terminal. + terminal_configuration_new.c_lflag &= ~ICANON; + // Do not print after a key press. + terminal_configuration_new.c_lflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_new); + + Draw(); + while (!quit_) { + int key = getchar(); + delegate_->Event(key); + + Clear(); + Draw(); + } + std::cout << std::endl; + //Clear(); + + // Restore the old terminal configuration. + tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_old); +} + +void ScreenInteractive::Draw() { + auto document = delegate_->component()->Render(); + Render(*this, document.get()); + std::cout << ToString() << std::flush; +} + +void ScreenInteractive::Clear() { + std::cout << ResetPosition(); + Screen::Clear(); +} + +component::Delegate* ScreenInteractive::delegate() { + return delegate_.get(); +} + +std::function ScreenInteractive::ExitLoopClosure() { + return [this]() { quit_ = true; }; +} + +} // namespace ftxui diff --git a/ftxui/src/ftxui/core/terminal.cpp b/ftxui/src/ftxui/terminal.cpp similarity index 87% rename from ftxui/src/ftxui/core/terminal.cpp rename to ftxui/src/ftxui/terminal.cpp index f8bf7c8..b8e1347 100644 --- a/ftxui/src/ftxui/core/terminal.cpp +++ b/ftxui/src/ftxui/terminal.cpp @@ -2,7 +2,7 @@ #include #include -#include "ftxui/core/terminal.hpp" +#include "ftxui/terminal.hpp" namespace ftxui { diff --git a/ftxui/src/ftxui/core/terminal.hpp b/ftxui/src/ftxui/terminal.hpp similarity index 100% rename from ftxui/src/ftxui/core/terminal.hpp rename to ftxui/src/ftxui/terminal.hpp diff --git a/tutorials/dom_elements.md b/tutorials/dom_elements.md new file mode 100644 index 0000000..ca41158 --- /dev/null +++ b/tutorials/dom_elements.md @@ -0,0 +1,141 @@ +All the dom element are declared in one header: +"""c++ +#include +""" + +It declares the following set of elements: + +"""C++ +// --- Layout ---- +Element vbox(Children); +Element hbox(Children); +Element flex(); + +// --- Widget -- +Element text(std::wstring text); +Element separator(); +Element gauge(float ratio); +Element frame(Child); +Element frame(Child title, Child content); + +// -- Decorator (Style) --- +Element bold(Element); +Element dim(Element); +Element inverted(Element); +Element underlined(Element); + +// --- Decorator --- +Element hcenter(Element); +Element vcenter(Element); +Element center(Element); +Element flex(Element); +""" + +# Layout elements. + +vbox (Vertical-box) and hbox (Horizontal-box) are containers. They are used to +compose all the elements together. They will display their children one by one in one direction. +Each elements will occupy the space it required plus a fraction of the remaining +space dispatched to all the flexible elements. + +flex() is used to make an element flexible. + +## Examples +"""C++ + hbox( + frame(text(L"left")), + flex(frame(text(L"middle"))), + frame(text(L"right")) + ); +""" +"""bash +┌────┐┌─────────────────────────────────────────────────────────────────┐┌─────┐ +│left││middle ││right│ +└────┘└─────────────────────────────────────────────────────────────────┘└─────┘ +""" + +"""C++ + hbox( + frame(text(L"left")), + flex(frame(text(L"middle"))), + flex(frame(text(L"right"))) + ); +""" +"""bash +┌────┐┌───────────────────────────────────┐┌───────────────────────────────────┐ +│left││middle ││right │ +└────┘└───────────────────────────────────┘└───────────────────────────────────┘ +""" + +# Widget elements. + +## text + +The more simple widget. It display a text. +"""C++ + text(L"I am a piece of text"); +""" +"""bash +I am a piece of text. +""" + +## frame +Add a border arround an element +"""c+ +frame(text(L"The element")) +""" + +"""bash +┌───────────┐ +│The element│ +└───────────┘ +""" + +## separator + +Display a vertical or horizontal line to visually split the content of a +container in two. + +"""c++ +frame(hbox( + vbox( + text(L"left top"), + text(L"left bottom") + ), + separator(), + vbox( + text(L"right top"), + text(L"right bottom") + ) +)); +""" + +"""bash +┌───────────┬────────────┐ +│left top │right top │ +│left bottom│right bottom│ +└───────────┴────────────┘ +""" + +## gauge + + +A gauge. It can be used to represent a progress bar. +"""c+ +frame(gauge(0.5)) +""" + +"""bash +┌────────────────────────────────────────────────────────────────────────────┐ +│██████████████████████████████████████ │ +└────────────────────────────────────────────────────────────────────────────┘ +""" + +# Decorator (style) +A terminal console can usually display colored text and colored background. +The text can also have different effects: bold, dim, underlined, inverted. + +Element bold(Element); +Element dim(Element); +Element inverted(Element); +Element underlined(Element);