diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 8d1043e8..99f5ea1f 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -44,6 +44,9 @@ class ScreenInteractive : public Screen { std::mutex events_queue_mutex; std::queue events_queue; std::atomic quit_ = false; + + std::string set_cursor_position; + std::string reset_cursor_position; }; } // namespace ftxui diff --git a/include/ftxui/dom/node.hpp b/include/ftxui/dom/node.hpp index 9b9471ee..f0553268 100644 --- a/include/ftxui/dom/node.hpp +++ b/include/ftxui/dom/node.hpp @@ -29,7 +29,7 @@ class Node { // Step 3: Draw this element. virtual void Render(Screen& screen); - std::vector> children; + std::vector> children; protected: Requirement requirement_; Box box_; diff --git a/include/ftxui/screen/screen.hpp b/include/ftxui/screen/screen.hpp index 373e237c..dcbba220 100644 --- a/include/ftxui/screen/screen.hpp +++ b/include/ftxui/screen/screen.hpp @@ -61,10 +61,18 @@ class Screen { void ApplyShader(); Box stencil; + struct Cursor { + int x; + int y; + }; + Cursor cursor() const { return cursor_; } + void SetCursor(Cursor cursor) { cursor_ = cursor; } + protected: int dimx_; int dimy_; std::vector> pixels_; + Cursor cursor_; }; }; // namespace ftxui diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 7e940f5e..a25017cf 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include "ftxui/component/component.hpp" #include "ftxui/screen/string.hpp" @@ -11,6 +13,20 @@ namespace ftxui { +static const char* HIDE_CURSOR = "\e[?25l"; +static const char* SHOW_CURSOR = "\e[?25h"; + +std::stack> on_exit_functions; +void OnExit(int signal) { + while (!on_exit_functions.empty()) { + on_exit_functions.top()(); + on_exit_functions.pop(); + } + + if (signal == SIGINT) + std::quick_exit(SIGINT); +} + ScreenInteractive::ScreenInteractive(int dimx, int dimy, Dimension dimension) @@ -56,13 +72,18 @@ void ScreenInteractive::EventLoop(Component* component) { } 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 <] - //std::cout << std::flush; + // Install a SIGINT handler and restore the old handler on exit. + auto old_sigint_handler = std::signal(SIGINT, OnExit); + on_exit_functions.push( + [old_sigint_handler]() { std::signal(SIGINT, old_sigint_handler); }); - // Save the old terminal configuration. + // Save the old terminal configuration and restore it on exit. struct termios terminal_configuration_old; tcgetattr(STDIN_FILENO, &terminal_configuration_old); + on_exit_functions.push( + [terminal_configuration_old = terminal_configuration_old]() { + tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_old); + }); // Set the new terminal configuration struct termios terminal_configuration_new; @@ -74,28 +95,31 @@ void ScreenInteractive::Loop(Component* component) { terminal_configuration_new.c_lflag &= ~ECHO; tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_new); - std::thread read_char([this]() { + // Hide the cursor and show it at exit. + std::cout << HIDE_CURSOR << std::flush; + on_exit_functions.push([&]{ + std::cout << reset_cursor_position; + std::cout << SHOW_CURSOR; + std::cout << std::flush; + }); + + std::thread read_char([this] { while (!quit_) { auto event = Event::GetEvent([] { return (char)getchar(); }); PostEvent(std::move(event)); } }); - std::string reset_position; while (!quit_) { - reset_position = ResetPosition(); + std::cout << reset_cursor_position << ResetPosition(); Draw(component); - std::cout << reset_position << ToString() << std::flush; + std::cout << ToString() << set_cursor_position << std::flush; Clear(); EventLoop(component); } - // Restore the old terminal configuration. - tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_old); - read_char.join(); - - std::cout << std::endl; + OnExit(0); } void ScreenInteractive::Draw(Component* component) { @@ -124,14 +148,33 @@ void ScreenInteractive::Draw(Component* component) { break; } + // Resize the screen if needed. if (dimx != dimx_ || dimy != dimy_) { dimx_ = dimx; dimy_ = dimy; pixels_ = std::vector>( dimy, std::vector(dimx)); + cursor_.x = dimx_ - 1; + cursor_.y = dimy_ - 1; } Render(*this, document.get()); + + // Set cursor position for user using tools to insert CJK characters. + set_cursor_position = ""; + reset_cursor_position = ""; + + int dx = dimx_ - 1 - cursor_.x; + int dy = dimy_ - 1 - cursor_.y; + + if (dx != 0) { + set_cursor_position += "\e[" + std::to_string(dx) + "D"; + reset_cursor_position += "\e[" + std::to_string(dx) + "C"; + } + if (dy != 0) { + set_cursor_position += "\e[" + std::to_string(dy) + "A"; + reset_cursor_position += "\e[" + std::to_string(dy) + "B"; + } } std::function ScreenInteractive::ExitLoopClosure() { diff --git a/src/ftxui/dom/frame.cpp b/src/ftxui/dom/frame.cpp index 92f64cd9..958ce65c 100644 --- a/src/ftxui/dom/frame.cpp +++ b/src/ftxui/dom/frame.cpp @@ -45,6 +45,11 @@ class Focus : public Select { Select::ComputeRequirement(); requirement_.selection = Requirement::FOCUSED; }; + + void Render(Screen& screen) override { + Select::Render(screen); + screen.SetCursor(Screen::Cursor{box_.x_min, box_.y_min}); + } }; std::unique_ptr focus(Element child) { diff --git a/src/ftxui/screen/screen.cpp b/src/ftxui/screen/screen.cpp index 06549191..c6ad86f9 100644 --- a/src/ftxui/screen/screen.cpp +++ b/src/ftxui/screen/screen.cpp @@ -139,8 +139,11 @@ std::string Screen::ResetPosition() { void Screen::Clear() { pixels_ = std::vector>(dimy_, std::vector(dimx_, Pixel())); + cursor_.x = dimx_ - 1; + cursor_.y = dimy_ - 1; } +// clang-format off void Screen::ApplyShader() { // Merge box characters togethers. for (int y = 1; y < dimy_; ++y) { @@ -150,26 +153,19 @@ void Screen::ApplyShader() { wchar_t& cur = at(x, y); // Left vs current - if (cur == U'│' && left == U'─') - cur = U'┤'; - if (cur == U'─' && left == U'│') - left = U'├'; - if (cur == U'├' && left == U'─') - cur = U'┼'; - if (cur == U'─' && left == U'┤') - left = U'┼'; + if (cur == U'│' && left == U'─') cur = U'┤'; + if (cur == U'─' && left == U'│') left = U'├'; + if (cur == U'├' && left == U'─') cur = U'┼'; + if (cur == U'─' && left == U'┤') left = U'┼'; // Top vs current - if (cur == U'─' && top == U'│') - cur = U'┴'; - if (cur == U'│' && top == U'─') - top = U'┬'; - if (cur == U'┬' && top == U'│') - cur = U'┼'; - if (cur == U'│' && top == U'┴') - top = U'┼'; + if (cur == U'─' && top == U'│') cur = U'┴'; + if (cur == U'│' && top == U'─') top = U'┬'; + if (cur == U'┬' && top == U'│') cur = U'┼'; + if (cur == U'│' && top == U'┴') top = U'┼'; } } } +// clang-format on }; // namespace ftxui