2019-01-07 00:10:35 +08:00
|
|
|
#include "ftxui/component/screen_interactive.hpp"
|
2019-01-05 09:03:49 +08:00
|
|
|
|
2018-10-10 01:06:03 +08:00
|
|
|
#include <stdio.h>
|
2020-03-24 04:26:00 +08:00
|
|
|
|
|
|
|
#include <algorithm>
|
2019-06-30 00:52:58 +08:00
|
|
|
#include <csignal>
|
2019-01-07 00:10:35 +08:00
|
|
|
#include <iostream>
|
2019-06-30 00:52:58 +08:00
|
|
|
#include <stack>
|
2019-06-23 23:47:33 +08:00
|
|
|
#include <thread>
|
2020-03-24 04:26:00 +08:00
|
|
|
|
2019-01-07 00:10:35 +08:00
|
|
|
#include "ftxui/component/component.hpp"
|
2019-06-23 23:47:33 +08:00
|
|
|
#include "ftxui/screen/string.hpp"
|
2019-01-12 22:00:08 +08:00
|
|
|
#include "ftxui/screen/terminal.hpp"
|
2018-10-10 01:06:03 +08:00
|
|
|
|
2020-03-21 23:21:32 +08:00
|
|
|
#ifdef WIN32
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
#define NOMINMAX
|
|
|
|
#include <Windows.h>
|
|
|
|
#else
|
|
|
|
#include <termios.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Quick exit is missing in standard CLang headers
|
2020-03-24 04:26:00 +08:00
|
|
|
#if defined(__clang__) && defined(__APPLE__)
|
|
|
|
#define quick_exit(a) exit(a)
|
2020-02-03 04:27:46 +08:00
|
|
|
#endif
|
|
|
|
|
2019-01-12 22:00:08 +08:00
|
|
|
namespace ftxui {
|
2018-10-10 01:06:03 +08:00
|
|
|
|
2020-02-12 04:44:55 +08:00
|
|
|
static const char* HIDE_CURSOR = "\x1B[?25l";
|
|
|
|
static const char* SHOW_CURSOR = "\x1B[?25h";
|
2019-06-30 00:52:58 +08:00
|
|
|
|
2020-02-12 04:44:55 +08:00
|
|
|
static const char* DISABLE_LINE_WRAP = "\x1B[7l";
|
|
|
|
static const char* ENABLE_LINE_WRAP = "\x1B[7h";
|
2019-07-01 05:53:56 +08:00
|
|
|
|
2020-03-23 16:23:57 +08:00
|
|
|
using SignalHandler = void(int);
|
2019-06-30 00:52:58 +08:00
|
|
|
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)
|
2019-06-30 16:11:37 +08:00
|
|
|
quick_exit(SIGINT);
|
2019-06-30 00:52:58 +08:00
|
|
|
}
|
|
|
|
|
2020-03-23 16:23:57 +08:00
|
|
|
auto install_signal_handler = [](int sig, SignalHandler handler) {
|
|
|
|
auto old_signal_handler = std::signal(sig, handler);
|
|
|
|
on_exit_functions.push([&]() { std::signal(sig, old_signal_handler); });
|
|
|
|
};
|
|
|
|
|
2020-03-24 04:26:00 +08:00
|
|
|
std::function<void()> on_resize = [] {};
|
2019-07-01 05:59:27 +08:00
|
|
|
void OnResize(int /* signal */) {
|
2019-07-01 05:53:56 +08:00
|
|
|
on_resize();
|
|
|
|
}
|
|
|
|
|
2020-03-24 04:26:00 +08:00
|
|
|
ScreenInteractive::ScreenInteractive(int dimx, int dimy, Dimension dimension)
|
2020-03-25 06:26:55 +08:00
|
|
|
: Screen(dimx, dimy), dimension_(dimension) {
|
2020-03-25 07:07:41 +08:00
|
|
|
event_receiver_ = MakeReceiver<Event>();
|
|
|
|
event_sender_ = event_receiver_->MakeSender();
|
2020-03-25 06:26:55 +08:00
|
|
|
}
|
|
|
|
|
2018-10-10 01:06:03 +08:00
|
|
|
ScreenInteractive::~ScreenInteractive() {}
|
|
|
|
|
2019-01-05 09:03:49 +08:00
|
|
|
// static
|
2019-01-27 04:52:55 +08:00
|
|
|
ScreenInteractive ScreenInteractive::FixedSize(int dimx, int dimy) {
|
2019-01-05 09:03:49 +08:00
|
|
|
return ScreenInteractive(dimx, dimy, Dimension::Fixed);
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
ScreenInteractive ScreenInteractive::Fullscreen() {
|
|
|
|
return ScreenInteractive(0, 0, Dimension::Fullscreen);
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
ScreenInteractive ScreenInteractive::TerminalOutput() {
|
|
|
|
return ScreenInteractive(0, 0, Dimension::TerminalOutput);
|
|
|
|
}
|
|
|
|
|
2019-01-19 07:20:29 +08:00
|
|
|
// static
|
|
|
|
ScreenInteractive ScreenInteractive::FitComponent() {
|
|
|
|
return ScreenInteractive(0, 0, Dimension::FitComponent);
|
|
|
|
}
|
|
|
|
|
2019-02-02 23:28:44 +08:00
|
|
|
void ScreenInteractive::PostEvent(Event event) {
|
2020-03-25 07:07:41 +08:00
|
|
|
event_sender_->Send(event);
|
2019-01-27 09:33:06 +08:00
|
|
|
}
|
|
|
|
|
2019-01-13 01:24:46 +08:00
|
|
|
void ScreenInteractive::Loop(Component* component) {
|
2019-06-30 00:52:58 +08:00
|
|
|
// Install a SIGINT handler and restore the old handler on exit.
|
2020-03-23 16:23:57 +08:00
|
|
|
install_signal_handler(SIGINT, OnExit);
|
2018-10-10 01:06:03 +08:00
|
|
|
|
2019-06-30 00:52:58 +08:00
|
|
|
// Save the old terminal configuration and restore it on exit.
|
2020-03-21 23:21:32 +08:00
|
|
|
#ifdef WIN32
|
2020-03-22 18:29:33 +08:00
|
|
|
// Enable VT processing on stdout and stdin
|
|
|
|
auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
|
|
|
|
|
|
|
|
DWORD out_mode = 0;
|
|
|
|
DWORD in_mode = 0;
|
|
|
|
GetConsoleMode(stdout_handle, &out_mode);
|
|
|
|
GetConsoleMode(stdin_handle, &in_mode);
|
|
|
|
on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
|
|
|
|
on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
|
|
|
|
|
|
|
|
out_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
|
|
out_mode |= DISABLE_NEWLINE_AUTO_RETURN;
|
|
|
|
|
|
|
|
in_mode &= ~ENABLE_ECHO_INPUT;
|
|
|
|
in_mode &= ~ENABLE_LINE_INPUT;
|
|
|
|
in_mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
|
|
|
|
in_mode |= ENABLE_WINDOW_INPUT;
|
|
|
|
|
|
|
|
SetConsoleMode(stdin_handle, in_mode);
|
|
|
|
SetConsoleMode(stdout_handle, out_mode);
|
2020-03-21 23:21:32 +08:00
|
|
|
#else
|
2020-03-22 18:29:33 +08:00
|
|
|
struct termios terminal;
|
|
|
|
tcgetattr(STDIN_FILENO, &terminal);
|
|
|
|
on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
|
|
|
|
|
|
|
|
terminal.c_lflag &= ~ICANON; // Non canonique terminal.
|
|
|
|
terminal.c_lflag &= ~ECHO; // Do not print after a key press.
|
|
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
|
2020-03-24 08:26:06 +08:00
|
|
|
|
|
|
|
// Handle resize.
|
|
|
|
on_resize = [&] { PostEvent(Event::Special({0})); };
|
|
|
|
install_signal_handler(SIGWINCH, OnResize);
|
2020-03-21 23:21:32 +08:00
|
|
|
#endif
|
2018-10-10 01:06:03 +08:00
|
|
|
|
2019-06-30 00:52:58 +08:00
|
|
|
// Hide the cursor and show it at exit.
|
2019-07-01 05:53:56 +08:00
|
|
|
std::cout << HIDE_CURSOR;
|
|
|
|
std::cout << DISABLE_LINE_WRAP;
|
|
|
|
std::cout << std::flush;
|
|
|
|
on_exit_functions.push([&] {
|
2019-06-30 00:52:58 +08:00
|
|
|
std::cout << reset_cursor_position;
|
|
|
|
std::cout << SHOW_CURSOR;
|
2019-07-01 05:53:56 +08:00
|
|
|
std::cout << ENABLE_LINE_WRAP;
|
2019-09-19 04:02:51 +08:00
|
|
|
std::cout << std::endl;
|
2019-06-30 00:52:58 +08:00
|
|
|
});
|
|
|
|
|
2020-03-25 07:07:41 +08:00
|
|
|
auto char_receiver = MakeReceiver<char>();
|
2020-03-25 06:26:55 +08:00
|
|
|
|
|
|
|
// Spawn a thread to produce char.
|
2020-03-25 07:07:41 +08:00
|
|
|
auto char_sender = char_receiver->MakeSender();
|
2020-03-25 06:26:55 +08:00
|
|
|
std::thread read_char([&] {
|
|
|
|
// TODO(arthursonzogni): Use a timeout so that it doesn't block even if the
|
|
|
|
// user doesn't generate new chars.
|
|
|
|
while (!quit_)
|
2020-03-25 07:07:41 +08:00
|
|
|
char_sender->Send((char)getchar());
|
|
|
|
char_sender.reset();
|
2020-03-25 06:26:55 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
// Spawn a thread producing events and consumer chars.
|
2020-03-25 07:07:41 +08:00
|
|
|
auto event_sender = event_receiver_->MakeSender();
|
2020-03-25 06:26:55 +08:00
|
|
|
std::thread convert_char_to_event([&] {
|
|
|
|
char c;
|
2020-03-25 07:07:41 +08:00
|
|
|
while (char_receiver->Receive(&c))
|
|
|
|
Event::Convert(char_receiver, event_sender, c);
|
|
|
|
event_sender.reset();
|
2019-01-27 09:33:06 +08:00
|
|
|
});
|
|
|
|
|
2020-03-23 16:23:57 +08:00
|
|
|
// The main loop.
|
2019-01-07 00:10:35 +08:00
|
|
|
while (!quit_) {
|
2019-06-30 00:52:58 +08:00
|
|
|
std::cout << reset_cursor_position << ResetPosition();
|
2019-01-13 01:24:46 +08:00
|
|
|
Draw(component);
|
2019-06-30 00:52:58 +08:00
|
|
|
std::cout << ToString() << set_cursor_position << std::flush;
|
2018-10-10 01:06:03 +08:00
|
|
|
Clear();
|
2020-03-25 06:26:55 +08:00
|
|
|
Event event;
|
2020-03-25 07:07:41 +08:00
|
|
|
if (event_receiver_->Receive(&event))
|
2020-03-25 06:26:55 +08:00
|
|
|
component->OnEvent(event);
|
2019-01-07 00:10:35 +08:00
|
|
|
}
|
2019-01-27 09:33:06 +08:00
|
|
|
read_char.join();
|
2020-03-25 06:26:55 +08:00
|
|
|
convert_char_to_event.join();
|
2019-06-30 00:52:58 +08:00
|
|
|
OnExit(0);
|
2018-10-10 01:06:03 +08:00
|
|
|
}
|
|
|
|
|
2019-01-13 01:24:46 +08:00
|
|
|
void ScreenInteractive::Draw(Component* component) {
|
|
|
|
auto document = component->Render();
|
2020-02-12 05:34:01 +08:00
|
|
|
int dimx = 0;
|
|
|
|
int dimy = 0;
|
2019-01-07 00:10:35 +08:00
|
|
|
switch (dimension_) {
|
2019-01-05 09:03:49 +08:00
|
|
|
case Dimension::Fixed:
|
2019-01-13 01:24:46 +08:00
|
|
|
dimx = dimx_;
|
|
|
|
dimy = dimy_;
|
2019-01-05 09:03:49 +08:00
|
|
|
break;
|
|
|
|
case Dimension::TerminalOutput:
|
|
|
|
document->ComputeRequirement();
|
|
|
|
dimx = Terminal::Size().dimx;
|
|
|
|
dimy = document->requirement().min.y;
|
|
|
|
break;
|
|
|
|
case Dimension::Fullscreen:
|
|
|
|
dimx = Terminal::Size().dimx;
|
|
|
|
dimy = Terminal::Size().dimy;
|
|
|
|
break;
|
2019-01-19 07:20:29 +08:00
|
|
|
case Dimension::FitComponent:
|
2019-01-21 06:04:10 +08:00
|
|
|
auto terminal = Terminal::Size();
|
2019-01-19 07:20:29 +08:00
|
|
|
document->ComputeRequirement();
|
2019-01-21 06:04:10 +08:00
|
|
|
dimx = std::min(document->requirement().min.x, terminal.dimx);
|
|
|
|
dimy = std::min(document->requirement().min.y, terminal.dimy);
|
2019-01-19 07:20:29 +08:00
|
|
|
break;
|
2019-01-05 09:03:49 +08:00
|
|
|
}
|
|
|
|
|
2019-06-30 00:52:58 +08:00
|
|
|
// Resize the screen if needed.
|
2019-01-05 09:03:49 +08:00
|
|
|
if (dimx != dimx_ || dimy != dimy_) {
|
|
|
|
dimx_ = dimx;
|
|
|
|
dimy_ = dimy;
|
2020-03-24 04:26:00 +08:00
|
|
|
pixels_ = std::vector<std::vector<Pixel>>(dimy, std::vector<Pixel>(dimx));
|
2019-06-30 00:52:58 +08:00
|
|
|
cursor_.x = dimx_ - 1;
|
|
|
|
cursor_.y = dimy_ - 1;
|
2019-01-05 09:03:49 +08:00
|
|
|
}
|
|
|
|
|
2018-10-10 01:06:03 +08:00
|
|
|
Render(*this, document.get());
|
2019-06-30 00:52:58 +08:00
|
|
|
|
|
|
|
// 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) {
|
2020-02-12 04:44:55 +08:00
|
|
|
set_cursor_position += "\x1B[" + std::to_string(dx) + "D";
|
|
|
|
reset_cursor_position += "\x1B[" + std::to_string(dx) + "C";
|
2019-06-30 00:52:58 +08:00
|
|
|
}
|
|
|
|
if (dy != 0) {
|
2020-03-24 04:26:00 +08:00
|
|
|
set_cursor_position += "\x1B[" + std::to_string(dy) + "A";
|
2020-02-12 04:44:55 +08:00
|
|
|
reset_cursor_position += "\x1B[" + std::to_string(dy) + "B";
|
2019-06-30 00:52:58 +08:00
|
|
|
}
|
2018-10-10 01:06:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
std::function<void()> ScreenInteractive::ExitLoopClosure() {
|
|
|
|
return [this]() { quit_ = true; };
|
|
|
|
}
|
|
|
|
|
2019-01-12 22:00:08 +08:00
|
|
|
} // namespace ftxui.
|