From 86c3b60a6f0457b270015c165f25e1370a46b87c Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Sat, 29 Jun 2019 18:52:58 +0200 Subject: [PATCH] Move the cursor to the input location. Most CJK users use IME (input method) to type CJK characters. They need the cursor to be at the correct location, not in the bottom right corner. This CL does: * Move the cursor the focus() element. * Hide the cursor (and show it at exit) * Intercept SIGINT to guarantee proper cleanup all the time. This should fix the second issue mentionned on: https://github.com/ArthurSonzogni/FTXUI/issues/2 --- .../ftxui/component/screen_interactive.hpp | 3 + include/ftxui/dom/node.hpp | 2 +- include/ftxui/screen/screen.hpp | 8 +++ src/ftxui/component/screen_interactive.cpp | 69 +++++++++++++++---- src/ftxui/dom/frame.cpp | 5 ++ src/ftxui/screen/screen.cpp | 28 ++++---- 6 files changed, 85 insertions(+), 30 deletions(-) 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