mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2024-11-26 04:31:34 +08:00
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:
parent
38df095b4a
commit
86c3b60a6f
@ -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
|
||||||
|
@ -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_;
|
||||||
|
@ -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
|
||||||
|
@ -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() {
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user