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::queue<Event> events_queue;
std::atomic<bool> quit_ = false;
std::string set_cursor_position;
std::string reset_cursor_position;
};
} // namespace ftxui

View File

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

View File

@ -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<std::vector<Pixel>> pixels_;
Cursor cursor_;
};
}; // namespace ftxui

View File

@ -3,7 +3,9 @@
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <csignal>
#include <iostream>
#include <stack>
#include <thread>
#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<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,
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<std::vector<Pixel>>(
dimy, std::vector<Pixel>(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<void()> ScreenInteractive::ExitLoopClosure() {

View File

@ -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<Node> focus(Element child) {

View File

@ -139,8 +139,11 @@ std::string Screen::ResetPosition() {
void Screen::Clear() {
pixels_ = std::vector<std::vector<Pixel>>(dimy_,
std::vector<Pixel>(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