diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt index 5c05f26..91f3e0e 100644 --- a/examples/component/CMakeLists.txt +++ b/examples/component/CMakeLists.txt @@ -16,3 +16,4 @@ example(radiobox_in_frame) example(tab_horizontal) example(tab_vertical) example(toggle) +example(homescreen) diff --git a/examples/component/homescreen.cpp b/examples/component/homescreen.cpp new file mode 100644 index 0000000..af0f23e --- /dev/null +++ b/examples/component/homescreen.cpp @@ -0,0 +1,244 @@ +#include +#include +#include "ftxui/component/checkbox.hpp" +#include "ftxui/component/container.hpp" +#include "ftxui/component/input.hpp" +#include "ftxui/component/menu.hpp" +#include "ftxui/component/radiobox.hpp" +#include "ftxui/component/screen_interactive.hpp" +#include "ftxui/component/toggle.hpp" +#include "ftxui/screen/string.hpp" + +using namespace ftxui; + +int shift = 0; +class Graph { + public: + std::vector operator()(int width, int height) { + std::vector output(width); + for (int i = 0; i < width; ++i) { + float v = 0; + v += 0.1 * sin((i + shift) * 0.1); + v += 0.2 * sin((i + shift+10) * 0.15); + v += 0.1 * sin((i + shift) * 0.03); + v *= height; + v += 0.5 * height; + output[i] = v; + } + return output; + } +}; + +class HTopComponent : public Component { + Graph my_graph; + + public: + HTopComponent() {} + ~HTopComponent() override {} + + Element Render() override { + return + vbox( + text(L"Frequency [Mhz]") | hcenter, + hbox( + vbox( + text(L"2400 "), filler(), + text(L"1200 "), filler(), + text(L"0% ") + ), + graph(std::ref(my_graph)) + ) | flex, + separator(), + text(L"Utilization [%]") | hcenter, + hbox( + vbox( + text(L"100 "), filler(), + text(L"50 "), filler(), + text(L"0 ") + ), + graph(std::ref(my_graph)) | color(Color::RedLight) + ) | flex, + separator(), + text(L"Ram [Go]") | hcenter, + hbox( + vbox( + text(L"8192"), filler(), + text(L"4096 "), filler(), + text(L"0 ") + ), + graph(std::ref(my_graph)) | color(Color::BlueLight) + ) | flex + ) | border; + } +}; + +class CompilerComponent : public Component { + Container container = Container::Horizontal(); + RadioBox compiler; + Container flag = Container::Vertical(); + CheckBox flag_checkbox[4]; + Container subcontainer = Container::Vertical(); + Container input_container = Container::Horizontal(); + Input input_add; + Menu input; + Input executable; + + public: + ~CompilerComponent() override {} + CompilerComponent() { + Add(&container); + + // Compiler ---------------------------------------------------------------- + compiler.entries = { + L"gcc", + L"clang", + L"emcc", + L"game_maker" + }; + container.Add(&compiler); + + // Flags ---------------------------------------------------------------- + container.Add(&flag); + flag_checkbox[0].label = L"-Wall"; + flag_checkbox[1].label = L"-Werror"; + flag_checkbox[2].label = L"-lpthread"; + flag_checkbox[3].label = L"-O3"; + for(auto& c : flag_checkbox) + flag.Add(&c); + + container.Add(&subcontainer); + // Executable ---------------------------------------------------------------- + executable.placeholder = L"executable"; + subcontainer.Add(&executable); + + // Input ---------------------------------------------------------------- + subcontainer.Add(&input_container); + + input_add.placeholder = L"input files"; + input_add.on_enter = [this] { + input.entries.push_back(input_add.content); + input_add.content = L""; + }; + input_container.Add(&input_add); + input_container.Add(&input); + } + + Element Render() override { + return + vbox( + hbox( + window(text(L"Compiler"), compiler.Render()), + window(text(L"Flags"), flag.Render()), + vbox( + window(text(L"Executable:"), executable.Render()) + | size(WIDTH, EQUAL, 20), + window(text(L"Input"), + hbox( + vbox( + hbox(text(L"Add: "), input_add.Render()) + | size(WIDTH, EQUAL, 20) + | size(HEIGHT, EQUAL, 1), + filler() + ), + separator(), + input.Render() | frame | size(HEIGHT, EQUAL, 3) | flex + ) + ) | size(WIDTH, EQUAL, 60) + ), + filler() + ), + hflow(RenderCommandLine()) + ) | border; + } + + Elements RenderCommandLine() { + Elements line; + // Compiler + line.push_back(text(compiler.entries[compiler.selected]) | bold); + // flags + for(auto& it : flag_checkbox) { + if (it.state) { + line.push_back(text(L" ")); + line.push_back(text(it.label) | dim); + } + } + // Executable + if (!executable.content.empty()) { + line.push_back(text(L" -O ") | bold); + line.push_back(text(executable.content) | color(Color::BlueLight) | bold); + } + // Input + for(auto& it : input.entries) { + line.push_back(text(L" " + it) | color(Color::RedLight)); + } + return line; + } +}; + +class SpinnerComponent : public Component { + Element Render() override { + Elements entries; + for(int i = 0; i<22; ++i) { + if (i != 0) + entries.push_back( + spinner(i, shift/2) + | bold + | size(WIDTH, GREATER_THAN, 2) + | border + ); + } + return hflow(std::move(entries)) | border; + } +}; + +class Tab : public Component { + public: + Container main_container = Container::Vertical(); + + Toggle tab_selection; + Container container = Container::Tab(&tab_selection.selected); + + HTopComponent htop; + CompilerComponent compiler; + SpinnerComponent spinner_component; + + Tab() { + Add(&main_container); + main_container.Add(&tab_selection); + tab_selection.entries = { + L"compiler", + L"htop", + L"spinner" + }; + main_container.Add(&container); + container.Add(&compiler); + container.Add(&htop); + container.Add(&spinner_component); + } + + Element Render() override { + return vbox( + text(L"FTXUI Demo") | bold | hcenter, + tab_selection.Render() | hcenter, + container.Render() + ); + } +}; + +int main(int argc, const char* argv[]) { + auto screen = ScreenInteractive::Fullscreen(); + + std::thread update([&screen]() { + for (;;) { + using namespace std::chrono_literals; + std::this_thread::sleep_for(0.05s); + shift++; + screen.PostEvent(Event::Custom); + } + }); + + Tab tab; + screen.Loop(&tab); + + return 0; +} diff --git a/examples/dom/graph.cpp b/examples/dom/graph.cpp index e600cf8..576e19f 100644 --- a/examples/dom/graph.cpp +++ b/examples/dom/graph.cpp @@ -1,13 +1,12 @@ -#include "ftxui/dom/elements.hpp" -#include "ftxui/dom/graph.hpp" -#include "ftxui/screen/screen.hpp" -#include "ftxui/screen/string.hpp" #include #include #include #include +#include "ftxui/dom/elements.hpp" +#include "ftxui/screen/screen.hpp" +#include "ftxui/screen/string.hpp" -class Graph : public ftxui::GraphFunction { +class Graph { public: std::vector operator()(int width, int height) { std::vector output(width); @@ -16,8 +15,6 @@ class Graph : public ftxui::GraphFunction { v += 0.1 * sin((i + shift) * 0.1); v += 0.2 * sin((i + shift+10) * 0.15); v += 0.1 * sin((i + shift) * 0.03); - // v += 0.2*sin((i+shift)*0.3); - // v += 0.1*sin((i+shift)*0.9); v *= height; v += 0.5 * height; output[i] = v; @@ -27,6 +24,14 @@ class Graph : public ftxui::GraphFunction { int shift = 0; }; +std::vector triangle(int width, int height) { + std::vector output(width); + for (int i = 0; i < width; ++i) { + output[i] = i % (height - 4) + 2; + } + return output; +} + int main(int argc, const char* argv[]) { using namespace ftxui; using namespace std::chrono_literals; @@ -36,20 +41,20 @@ int main(int argc, const char* argv[]) { std::string reset_position; for (int i = 0;; ++i) { auto document = - window(text(L"Your graphs"), - hbox( - vbox( - graph(my_graph), separator(), - graph(my_graph) | inverted - ) | flex, - separator(), - vbox( - graph(my_graph) | color(Color::BlueLight), separator(), - graph(my_graph) | color(Color::RedLight), separator(), - graph(my_graph) | color(Color::YellowLight) - ) | flex - ) - ) | size(HEIGHT, GREATER_THAN, 40); + hbox( + vbox( + graph(std::ref(my_graph)), separator(), + graph(triangle) | inverted + ) | flex, + separator(), + vbox( + graph(std::ref(my_graph)) | color(Color::BlueLight), separator(), + graph(std::ref(my_graph)) | color(Color::RedLight), separator(), + graph(std::ref(my_graph)) | color(Color::YellowLight) + ) | flex + ) + | border + | size(HEIGHT, GREATER_THAN, 40); auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document.get()); diff --git a/ftxui/CMakeLists.txt b/ftxui/CMakeLists.txt index c5451b0..bdbe098 100644 --- a/ftxui/CMakeLists.txt +++ b/ftxui/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.0) +find_package(Threads) add_library(screen src/ftxui/screen/box.cpp @@ -48,7 +49,7 @@ add_library(component target_link_libraries(dom PUBLIC screen) target_link_libraries(component PUBLIC dom) - +target_link_libraries(component PUBLIC Threads::Threads) foreach(lib screen dom component) target_include_directories(${lib} @@ -86,7 +87,6 @@ install(EXPORT ftxui-export # Note: For gtest, please follow: # https://stackoverflow.com/questions/24295876/cmake-cannot-find-a-googletest-required-library find_package(GTest) -find_package(Threads) if (GTEST_FOUND AND THREADS_FOUND) add_executable(dom_tests src/ftxui/dom/gauge_test.cpp diff --git a/ftxui/include/ftxui/component/event.hpp b/ftxui/include/ftxui/component/event.hpp index 724ab73..d9fc17c 100644 --- a/ftxui/include/ftxui/component/event.hpp +++ b/ftxui/include/ftxui/component/event.hpp @@ -24,10 +24,14 @@ struct Event{ static Event Escape; static Event F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12; + // --- Custom --- + static Event Custom; + bool operator==(const Event& other) { return values == other.values; } // Internal representation. std::array values = {0, 0, 0, 0, 0}; + }; diff --git a/ftxui/include/ftxui/component/screen_interactive.hpp b/ftxui/include/ftxui/component/screen_interactive.hpp index 71cef2c..65f42ba 100644 --- a/ftxui/include/ftxui/component/screen_interactive.hpp +++ b/ftxui/include/ftxui/component/screen_interactive.hpp @@ -1,9 +1,15 @@ #ifndef FTXUI_COMPONENT_SCREEN_INTERACTIVE_HPP #define FTXUI_COMPONENT_SCREEN_INTERACTIVE_HPP -#include "ftxui/screen/screen.hpp" +#include #include #include +#include +#include +#include + +#include "ftxui/component/event.hpp" +#include "ftxui/screen/screen.hpp" namespace ftxui { class Component; @@ -19,9 +25,11 @@ class ScreenInteractive : public Screen { void Loop(Component*); std::function ExitLoopClosure(); + void PostEvent(Event event); + private: void Draw(Component* component); - bool quit_ = false; + void EventLoop(Component* component); enum class Dimension { FitComponent, @@ -31,6 +39,11 @@ class ScreenInteractive : public Screen { }; Dimension dimension_ = Dimension::Fixed; ScreenInteractive(int dimx, int dimy, Dimension dimension); + + std::condition_variable events_queue_wait; + std::mutex events_queue_mutex; + std::queue events_queue; + std::atomic quit_ = false; }; } // namespace ftxui diff --git a/ftxui/include/ftxui/dom/elements.hpp b/ftxui/include/ftxui/dom/elements.hpp index 6a92117..60ac38a 100644 --- a/ftxui/include/ftxui/dom/elements.hpp +++ b/ftxui/include/ftxui/dom/elements.hpp @@ -3,7 +3,6 @@ #include -#include "ftxui/dom/graph.hpp" #include "ftxui/dom/node.hpp" #include "ftxui/screen/color.hpp" @@ -12,16 +11,19 @@ namespace ftxui { using Element = std::unique_ptr; using Elements = std::vector; using Decorator = std::function; +using GraphFunction = std::function(int,int)>; // --- Widget --- Element text(std::wstring text); Element separator(); +Element separator(Pixel); Element gauge(float ratio); Element border(Element); +Decorator borderWith(Pixel); Element window(Element title, Element content); Element spinner(int charset_index, size_t image_index); Elements paragraph(std::wstring text); // Use inside hflow(). Split by space. -Element graph(GraphFunction&); // See graph.hpp +Element graph(GraphFunction); // -- Decorator --- Element bold(Element); @@ -46,6 +48,7 @@ Element hflow(Elements); // container. Element filler(); Element flex(Element); +Element notflex(Element); // -- Size override; enum Direction { WIDTH, HEIGHT }; diff --git a/ftxui/include/ftxui/dom/graph.hpp b/ftxui/include/ftxui/dom/graph.hpp deleted file mode 100644 index 4f7a471..0000000 --- a/ftxui/include/ftxui/dom/graph.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef FTXUI_DOM_GRAPH_HPP -#define FTXUI_DOM_GRAPH_HPP - -#include "ftxui/dom/elements.hpp" - -namespace ftxui { - -class GraphFunction { - public: - virtual std::vector operator()(int width, int height) = 0; -}; - -} // namespace ftxui - -#endif /* end of include guard: FTXUI_DOM_GRAPH_HPP */ diff --git a/ftxui/src/ftxui/component/event.cpp b/ftxui/src/ftxui/component/event.cpp index 85c8054..0e1ca03 100644 --- a/ftxui/src/ftxui/component/event.cpp +++ b/ftxui/src/ftxui/component/event.cpp @@ -34,4 +34,6 @@ Event Event::F10{ESC, '[', '2', '1', '~'}; Event Event::F11{ESC, '[', '2', '1', '~'}; // Same as F10 ? Event Event::F12{ESC, '[', '2', '4', '~'}; +Event Event::Custom{0, 0, 0, 0, 0}; + } // namespace ftxui diff --git a/ftxui/src/ftxui/component/input.cpp b/ftxui/src/ftxui/component/input.cpp index 20f41a1..0f9ae96 100644 --- a/ftxui/src/ftxui/component/input.cpp +++ b/ftxui/src/ftxui/component/input.cpp @@ -5,19 +5,21 @@ namespace ftxui { // Component implementation. Element Input::Render() { + cursor_position = std::max(0, std::min(content.size(), cursor_position)); + auto main_decorator = flex | size(HEIGHT, EQUAL, 1); bool is_focused = Focused(); // Placeholder. if (content.size() == 0) { if (is_focused) - return text(placeholder) | dim | inverted | flex; + return text(placeholder) | dim | inverted | main_decorator; else - return text(placeholder) | dim | flex; + return text(placeholder) | dim | main_decorator; } // Not focused. if (!is_focused) - return text(content) | flex; + return text(content) | main_decorator; std::wstring part_before_cursor = content.substr(0,cursor_position); std::wstring part_at_cursor = cursor_position < (int)content.size() @@ -34,9 +36,11 @@ Element Input::Render() { text(part_before_cursor), text(part_at_cursor) | underlined | focused, text(part_after_cursor) - ) | flex | inverted | frame; + ) | flex | inverted | frame | main_decorator; + } bool Input::OnEvent(Event event) { + cursor_position = std::max(0, std::min(content.size(), cursor_position)); std::wstring c; // Backspace. @@ -50,9 +54,14 @@ bool Input::OnEvent(Event event) { // Enter. if (event == Event::Return) { + on_enter(); return true; } + if (event == Event::Custom) { + return false; + } + if (event == Event::ArrowLeft && cursor_position > 0) { cursor_position--; return true; diff --git a/ftxui/src/ftxui/component/screen_interactive.cpp b/ftxui/src/ftxui/component/screen_interactive.cpp index 459a06c..d883d15 100644 --- a/ftxui/src/ftxui/component/screen_interactive.cpp +++ b/ftxui/src/ftxui/component/screen_interactive.cpp @@ -6,6 +6,7 @@ #include #include "ftxui/component/component.hpp" #include "ftxui/screen/terminal.hpp" +#include namespace ftxui { @@ -70,6 +71,28 @@ ScreenInteractive ScreenInteractive::FitComponent() { return ScreenInteractive(0, 0, Dimension::FitComponent); } +void ScreenInteractive::PostEvent(Event event) { + std::unique_lock lock(events_queue_mutex); + events_queue.push(event); + events_queue_wait.notify_one(); +} + +void ScreenInteractive::EventLoop(Component* component) { + bool handled = 0; + for (;;) { + std::unique_lock lock(events_queue_mutex); + while (!events_queue.empty()) { + component->OnEvent(events_queue.front()); + events_queue.pop(); + handled = true; + } + + if (handled) + return; + events_queue_wait.wait(lock); + } +} + void ScreenInteractive::Loop(Component* component) { //std::cout << "\033[?9h"; [> Send Mouse Row & Column on Button Press <] //std::cout << "\033[?1000h"; [> Send Mouse X & Y on button press and release <] @@ -89,18 +112,25 @@ void ScreenInteractive::Loop(Component* component) { terminal_configuration_new.c_lflag &= ~ECHO; tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_new); + std::thread read_char([this]() { + while (!quit_) + PostEvent(GetEvent()); + }); + std::string reset_position; while (!quit_) { reset_position = ResetPosition(); Draw(component); std::cout << reset_position << ToString() << std::flush; Clear(); - component->OnEvent(GetEvent()); + EventLoop(component); } // Restore the old terminal configuration. tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_old); + read_char.join(); + std::cout << std::endl; } diff --git a/ftxui/src/ftxui/dom/border.cpp b/ftxui/src/ftxui/dom/border.cpp index eca5c75..90bf0c2 100644 --- a/ftxui/src/ftxui/dom/border.cpp +++ b/ftxui/src/ftxui/dom/border.cpp @@ -5,13 +5,21 @@ namespace ftxui { using namespace ftxui; -static wchar_t charset[] = L"┌┐└┘─│┬┴┤├"; +static wchar_t simple_border_charset[] = L"┌┐└┘─│┬┴┤├"; class Border : public Node { public: - Border(Elements children) : Node(std::move(children)) {} + Border(Elements children) + : Node(std::move(children)), + charset(std::begin(simple_border_charset), + std::end(simple_border_charset)) {} + Border(Elements children, Pixel pixel) + : Node(std::move(children)), charset_pixel(10, pixel) {} ~Border() override {} + std::vector charset_pixel; + std::vector charset; + void ComputeRequirement() override { Node::ComputeRequirement(); requirement_ = children[0]->requirement(); @@ -52,6 +60,13 @@ class Border : public Node { if (box_.x_min >= box_.x_max || box_.y_min >= box_.y_max) return; + if (!charset.empty()) + RenderPixel(screen); + else + RenderChar(screen); + } + + void RenderPixel(Screen& screen) { screen.at(box_.x_min, box_.y_min) = charset[0]; screen.at(box_.x_max, box_.y_min) = charset[1]; screen.at(box_.x_min, box_.y_max) = charset[2]; @@ -69,6 +84,21 @@ class Border : public Node { if (children.size() == 2) children[1]->Render(screen); } + + void RenderChar(Screen& screen) { + screen.PixelAt(box_.x_min, box_.y_min) = charset_pixel[0]; + screen.PixelAt(box_.x_max, box_.y_min) = charset_pixel[1]; + screen.PixelAt(box_.x_min, box_.y_max) = charset_pixel[2]; + screen.PixelAt(box_.x_max, box_.y_max) = charset_pixel[3]; + for(float x = box_.x_min + 1; x border(Element child) { @@ -79,9 +109,9 @@ std::unique_ptr window(Element title, Element content) { return std::make_unique(unpack(std::move(content), std::move(title))); } -Decorator boxed() { - return [](Element child) { - return border(std::move(child)); +Decorator borderWith(Pixel pixel) { + return [pixel](Element child) { + return std::make_unique(unpack(std::move(child)), pixel); }; } diff --git a/ftxui/src/ftxui/dom/flex.cpp b/ftxui/src/ftxui/dom/flex.cpp index f4945b9..d25f4d3 100644 --- a/ftxui/src/ftxui/dom/flex.cpp +++ b/ftxui/src/ftxui/dom/flex.cpp @@ -8,7 +8,7 @@ class Flex : public Node { Flex() {} Flex(Element child) : Node(unpack(std::move(child))) {} ~Flex() override {} - void ComputeRequirement() { + void ComputeRequirement() override { requirement_.min.x = 0; requirement_.min.y = 0; if (!children.empty()) { @@ -26,6 +26,23 @@ class Flex : public Node { } }; +class NotFlex : public Flex { + public: + NotFlex() {} + NotFlex(Element child) : Flex(std::move(child)) {} + ~NotFlex() override {} + void ComputeRequirement() override { + requirement_.min.x = 0; + requirement_.min.y = 0; + if (!children.empty()) { + children[0]->ComputeRequirement(); + requirement_ = children[0]->requirement(); + } + requirement_.flex.x = 0; + requirement_.flex.y = 0; + } +}; + std::unique_ptr filler() { return std::make_unique(); } @@ -34,4 +51,8 @@ std::unique_ptr flex(Element child) { return std::make_unique(std::move(child)); } +std::unique_ptr notflex(Element child) { + return std::make_unique(std::move(child)); +} + }; // namespace ftxui diff --git a/ftxui/src/ftxui/dom/graph.cpp b/ftxui/src/ftxui/dom/graph.cpp index 0e8b766..efe8555 100644 --- a/ftxui/src/ftxui/dom/graph.cpp +++ b/ftxui/src/ftxui/dom/graph.cpp @@ -6,7 +6,7 @@ const wchar_t charset[] = L" ▗▐▖▄▟▌▙█"; class Graph : public Node { public: - Graph(GraphFunction& graph_function) : graph_function_(graph_function) {} + Graph(GraphFunction graph_function) : graph_function_(graph_function) {} ~Graph() override {} void ComputeRequirement() override { @@ -35,10 +35,10 @@ class Graph : public Node { } private: - GraphFunction& graph_function_; + GraphFunction graph_function_; }; -std::unique_ptr graph(GraphFunction& graph_function) { +std::unique_ptr graph(GraphFunction graph_function) { return std::make_unique(graph_function); } diff --git a/ftxui/src/ftxui/dom/separator.cpp b/ftxui/src/ftxui/dom/separator.cpp index b66b905..a59fbea 100644 --- a/ftxui/src/ftxui/dom/separator.cpp +++ b/ftxui/src/ftxui/dom/separator.cpp @@ -23,16 +23,34 @@ class Separator : public Node { else c = U'│'; + Pixel p; + p.character = c; + RenderWithPixel(screen, p); + } + + void RenderWithPixel(Screen& screen, Pixel pixel) { for (int y = box_.y_min; y <= box_.y_max; ++y) { for (int x = box_.x_min; x <= box_.x_max; ++x) { - screen.at(x, y) = c; + screen.PixelAt(x, y) = pixel; } } } }; +class SeparatorWithPixel : public Separator { + public: + SeparatorWithPixel(Pixel p) : p(p) {} + ~SeparatorWithPixel() override {} + void Render(Screen& screen) override { RenderWithPixel(screen, p); } + Pixel p; +}; + std::unique_ptr separator() { return std::make_unique(); } +std::unique_ptr separator(Pixel pixel) { + return std::make_unique(pixel); +} + }; // namespace ftxui