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
This commit is contained in:
ArthurSonzogni 2019-06-29 18:52:58 +02:00
parent 38df095b4a
commit 86c3b60a6f
6 changed files with 85 additions and 30 deletions

View File

@ -44,6 +44,9 @@ class ScreenInteractive : public Screen {
std::mutex events_queue_mutex; std::mutex events_queue_mutex;
std::queue<Event> events_queue; std::queue<Event> events_queue;
std::atomic<bool> quit_ = false; std::atomic<bool> quit_ = false;
std::string set_cursor_position;
std::string reset_cursor_position;
}; };
} // namespace ftxui } // namespace ftxui

View File

@ -29,7 +29,7 @@ class Node {
// Step 3: Draw this element. // Step 3: Draw this element.
virtual void Render(Screen& screen); virtual void Render(Screen& screen);
std::vector<std::unique_ptr<Node>> children; std::vector<std::unique_ptr<Node>> children;
protected: protected:
Requirement requirement_; Requirement requirement_;
Box box_; Box box_;

View File

@ -61,10 +61,18 @@ class Screen {
void ApplyShader(); void ApplyShader();
Box stencil; Box stencil;
struct Cursor {
int x;
int y;
};
Cursor cursor() const { return cursor_; }
void SetCursor(Cursor cursor) { cursor_ = cursor; }
protected: protected:
int dimx_; int dimx_;
int dimy_; int dimy_;
std::vector<std::vector<Pixel>> pixels_; std::vector<std::vector<Pixel>> pixels_;
Cursor cursor_;
}; };
}; // namespace ftxui }; // namespace ftxui

View File

@ -3,7 +3,9 @@
#include <stdio.h> #include <stdio.h>
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
#include <csignal>
#include <iostream> #include <iostream>
#include <stack>
#include <thread> #include <thread>
#include "ftxui/component/component.hpp" #include "ftxui/component/component.hpp"
#include "ftxui/screen/string.hpp" #include "ftxui/screen/string.hpp"
@ -11,6 +13,20 @@
namespace ftxui { namespace ftxui {
static const char* HIDE_CURSOR = "\e[?25l";
static const char* SHOW_CURSOR = "\e[?25h";
std::stack<std::function<void()>> 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, ScreenInteractive::ScreenInteractive(int dimx,
int dimy, int dimy,
Dimension dimension) Dimension dimension)
@ -56,13 +72,18 @@ void ScreenInteractive::EventLoop(Component* component) {
} }
void ScreenInteractive::Loop(Component* component) { void ScreenInteractive::Loop(Component* component) {
//std::cout << "\033[?9h"; [> Send Mouse Row & Column on Button Press <] // Install a SIGINT handler and restore the old handler on exit.
//std::cout << "\033[?1000h"; [> Send Mouse X & Y on button press and release <] auto old_sigint_handler = std::signal(SIGINT, OnExit);
//std::cout << std::flush; 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; struct termios terminal_configuration_old;
tcgetattr(STDIN_FILENO, &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 // Set the new terminal configuration
struct termios terminal_configuration_new; struct termios terminal_configuration_new;
@ -74,28 +95,31 @@ void ScreenInteractive::Loop(Component* component) {
terminal_configuration_new.c_lflag &= ~ECHO; terminal_configuration_new.c_lflag &= ~ECHO;
tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_new); 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_) { while (!quit_) {
auto event = Event::GetEvent([] { return (char)getchar(); }); auto event = Event::GetEvent([] { return (char)getchar(); });
PostEvent(std::move(event)); PostEvent(std::move(event));
} }
}); });
std::string reset_position;
while (!quit_) { while (!quit_) {
reset_position = ResetPosition(); std::cout << reset_cursor_position << ResetPosition();
Draw(component); Draw(component);
std::cout << reset_position << ToString() << std::flush; std::cout << ToString() << set_cursor_position << std::flush;
Clear(); Clear();
EventLoop(component); EventLoop(component);
} }
// Restore the old terminal configuration.
tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_old);
read_char.join(); read_char.join();
OnExit(0);
std::cout << std::endl;
} }
void ScreenInteractive::Draw(Component* component) { void ScreenInteractive::Draw(Component* component) {
@ -124,14 +148,33 @@ void ScreenInteractive::Draw(Component* component) {
break; break;
} }
// Resize the screen if needed.
if (dimx != dimx_ || dimy != dimy_) { if (dimx != dimx_ || dimy != dimy_) {
dimx_ = dimx; dimx_ = dimx;
dimy_ = dimy; dimy_ = dimy;
pixels_ = std::vector<std::vector<Pixel>>( pixels_ = std::vector<std::vector<Pixel>>(
dimy, std::vector<Pixel>(dimx)); dimy, std::vector<Pixel>(dimx));
cursor_.x = dimx_ - 1;
cursor_.y = dimy_ - 1;
} }
Render(*this, document.get()); 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<void()> ScreenInteractive::ExitLoopClosure() { std::function<void()> ScreenInteractive::ExitLoopClosure() {

View File

@ -45,6 +45,11 @@ class Focus : public Select {
Select::ComputeRequirement(); Select::ComputeRequirement();
requirement_.selection = Requirement::FOCUSED; 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<Node> focus(Element child) { std::unique_ptr<Node> focus(Element child) {

View File

@ -139,8 +139,11 @@ std::string Screen::ResetPosition() {
void Screen::Clear() { void Screen::Clear() {
pixels_ = std::vector<std::vector<Pixel>>(dimy_, pixels_ = std::vector<std::vector<Pixel>>(dimy_,
std::vector<Pixel>(dimx_, Pixel())); std::vector<Pixel>(dimx_, Pixel()));
cursor_.x = dimx_ - 1;
cursor_.y = dimy_ - 1;
} }
// clang-format off
void Screen::ApplyShader() { void Screen::ApplyShader() {
// Merge box characters togethers. // Merge box characters togethers.
for (int y = 1; y < dimy_; ++y) { for (int y = 1; y < dimy_; ++y) {
@ -150,26 +153,19 @@ void Screen::ApplyShader() {
wchar_t& cur = at(x, y); wchar_t& cur = at(x, y);
// Left vs current // Left vs current
if (cur == U'' && left == U'') if (cur == U'' && left == U'') cur = U'';
cur = U''; if (cur == U'' && left == U'') left = U'';
if (cur == U'' && left == U'') if (cur == U'' && left == U'') cur = U'';
left = 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 // Top vs current
if (cur == U'' && top == U'') if (cur == U'' && top == U'') cur = U'';
cur = U''; if (cur == U'' && top == U'') top = U'';
if (cur == U'' && top == U'') if (cur == U'' && top == U'') cur = U'';
top = 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 }; // namespace ftxui