2022-09-03 18:12:59 +08:00
|
|
|
#include <algorithm> // for copy, max, min
|
2022-03-31 08:17:43 +08:00
|
|
|
#include <array> // for array
|
2023-02-12 21:09:47 +08:00
|
|
|
#include <chrono> // for operator-, milliseconds, operator>=, duration, common_type<>::type, time_point
|
2022-12-20 01:51:25 +08:00
|
|
|
#include <csignal> // for signal, SIGTSTP, SIGABRT, SIGWINCH, raise, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, __sighandler_t, size_t
|
|
|
|
#include <cstdio> // for fileno, stdin
|
2022-06-12 23:08:22 +08:00
|
|
|
#include <ftxui/component/task.hpp> // for Task, Closure, AnimationTask
|
2022-12-20 01:51:25 +08:00
|
|
|
#include <ftxui/screen/screen.hpp> // for Pixel, Screen::Cursor, Screen, Screen::Cursor::Hidden
|
|
|
|
#include <functional> // for function
|
|
|
|
#include <initializer_list> // for initializer_list
|
|
|
|
#include <iostream> // for cout, ostream, operator<<, basic_ostream, endl, flush
|
2021-05-10 02:32:27 +08:00
|
|
|
#include <stack> // for stack
|
2022-03-14 01:51:46 +08:00
|
|
|
#include <thread> // for thread, sleep_for
|
2022-12-20 01:51:25 +08:00
|
|
|
#include <tuple> // for _Swallow_assign, ignore
|
2022-02-13 18:11:34 +08:00
|
|
|
#include <type_traits> // for decay_t
|
2022-03-31 08:17:43 +08:00
|
|
|
#include <utility> // for move, swap
|
2022-12-20 01:51:25 +08:00
|
|
|
#include <variant> // for visit, variant
|
2022-02-13 18:11:34 +08:00
|
|
|
#include <vector> // for vector
|
2021-05-10 02:32:27 +08:00
|
|
|
|
2022-03-14 01:51:46 +08:00
|
|
|
#include "ftxui/component/animation.hpp" // for TimePoint, Clock, Duration, Params, RequestAnimationFrame
|
2021-05-10 02:32:27 +08:00
|
|
|
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
|
|
|
|
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
|
|
|
#include "ftxui/component/event.hpp" // for Event
|
2022-10-19 03:29:27 +08:00
|
|
|
#include "ftxui/component/loop.hpp" // for Loop
|
2022-12-20 01:51:25 +08:00
|
|
|
#include "ftxui/component/receiver.hpp" // for ReceiverImpl, Sender, MakeReceiver, SenderImpl, Receiver
|
2021-05-10 02:32:27 +08:00
|
|
|
#include "ftxui/component/screen_interactive.hpp"
|
|
|
|
#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
|
2021-05-02 02:40:35 +08:00
|
|
|
#include "ftxui/dom/node.hpp" // for Node, Render
|
|
|
|
#include "ftxui/dom/requirement.hpp" // for Requirement
|
2023-02-12 21:07:28 +08:00
|
|
|
#include "ftxui/screen/terminal.hpp" // for Dimensions, Size
|
2018-10-10 01:06:03 +08:00
|
|
|
|
2020-03-25 15:54:03 +08:00
|
|
|
#if defined(_WIN32)
|
2020-08-16 06:24:18 +08:00
|
|
|
#define DEFINE_CONSOLEV2_PROPERTIES
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
#ifndef NOMINMAX
|
|
|
|
#define NOMINMAX
|
|
|
|
#endif
|
2022-10-10 06:08:28 +08:00
|
|
|
#include <windows.h>
|
2020-08-16 06:24:18 +08:00
|
|
|
#ifndef UNICODE
|
|
|
|
#error Must be compiled in UNICODE mode
|
|
|
|
#endif
|
2020-03-21 23:21:32 +08:00
|
|
|
#else
|
2022-12-20 01:51:25 +08:00
|
|
|
#include <sys/select.h> // for select, FD_ISSET, FD_SET, FD_ZERO, fd_set, timeval
|
2022-02-13 18:11:34 +08:00
|
|
|
#include <termios.h> // for tcsetattr, termios, tcgetattr, TCSANOW, cc_t, ECHO, ICANON, VMIN, VTIME
|
|
|
|
#include <unistd.h> // for STDIN_FILENO, read
|
2020-03-21 23:21:32 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// Quick exit is missing in standard CLang headers
|
2020-03-24 04:26:00 +08:00
|
|
|
#if defined(__clang__) && defined(__APPLE__)
|
2020-08-16 06:24:18 +08:00
|
|
|
#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
|
|
|
|
2022-03-14 01:51:46 +08:00
|
|
|
namespace animation {
|
|
|
|
void RequestAnimationFrame() {
|
|
|
|
auto* screen = ScreenInteractive::Active();
|
2022-03-31 08:17:43 +08:00
|
|
|
if (screen) {
|
2022-03-14 01:51:46 +08:00
|
|
|
screen->RequestAnimationFrame();
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2022-03-14 01:51:46 +08:00
|
|
|
}
|
|
|
|
} // namespace animation
|
|
|
|
|
2020-08-09 20:53:56 +08:00
|
|
|
namespace {
|
2020-03-25 08:15:46 +08:00
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
ScreenInteractive* g_active_screen = nullptr; // NOLINT
|
2022-02-13 18:11:34 +08:00
|
|
|
|
2021-03-22 05:54:39 +08:00
|
|
|
void Flush() {
|
|
|
|
// Emscripten doesn't implement flush. We interpret zero as flush.
|
2021-04-25 22:58:16 +08:00
|
|
|
std::cout << '\0' << std::flush;
|
2021-03-22 05:54:39 +08:00
|
|
|
}
|
|
|
|
|
2020-10-25 07:57:56 +08:00
|
|
|
constexpr int timeout_milliseconds = 20;
|
2023-07-02 23:50:31 +08:00
|
|
|
[[maybe_unused]] constexpr int timeout_microseconds = timeout_milliseconds * 1000;
|
2020-03-25 15:54:03 +08:00
|
|
|
#if defined(_WIN32)
|
2020-03-25 08:38:50 +08:00
|
|
|
|
2022-02-13 18:11:34 +08:00
|
|
|
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
2020-03-25 08:38:50 +08:00
|
|
|
auto console = GetStdHandle(STD_INPUT_HANDLE);
|
2020-10-25 07:57:56 +08:00
|
|
|
auto parser = TerminalInputParser(out->Clone());
|
2020-03-25 08:38:50 +08:00
|
|
|
while (!*quit) {
|
|
|
|
// Throttle ReadConsoleInput by waiting 250ms, this wait function will
|
|
|
|
// return if there is input in the console.
|
2020-10-25 07:57:56 +08:00
|
|
|
auto wait_result = WaitForSingleObject(console, timeout_milliseconds);
|
|
|
|
if (wait_result == WAIT_TIMEOUT) {
|
|
|
|
parser.Timeout(timeout_milliseconds);
|
2020-03-25 08:38:50 +08:00
|
|
|
continue;
|
2020-10-25 07:57:56 +08:00
|
|
|
}
|
2020-03-25 08:38:50 +08:00
|
|
|
|
|
|
|
DWORD number_of_events = 0;
|
|
|
|
if (!GetNumberOfConsoleInputEvents(console, &number_of_events))
|
|
|
|
continue;
|
|
|
|
if (number_of_events <= 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
std::vector<INPUT_RECORD> records{number_of_events};
|
|
|
|
DWORD number_of_events_read = 0;
|
2021-05-02 02:40:35 +08:00
|
|
|
ReadConsoleInput(console, records.data(), (DWORD)records.size(),
|
2020-03-25 08:38:50 +08:00
|
|
|
&number_of_events_read);
|
|
|
|
records.resize(number_of_events_read);
|
|
|
|
|
|
|
|
for (const auto& r : records) {
|
|
|
|
switch (r.EventType) {
|
|
|
|
case KEY_EVENT: {
|
|
|
|
auto key_event = r.Event.KeyEvent;
|
|
|
|
// ignore UP key events
|
|
|
|
if (key_event.bKeyDown == FALSE)
|
|
|
|
continue;
|
2022-12-28 18:47:11 +08:00
|
|
|
std::wstring wstring;
|
|
|
|
wstring += key_event.uChar.UnicodeChar;
|
2023-02-12 21:07:28 +08:00
|
|
|
for (auto it : to_string(wstring)) {
|
2022-12-28 18:47:11 +08:00
|
|
|
parser.Add(it);
|
|
|
|
}
|
2020-03-25 08:38:50 +08:00
|
|
|
} break;
|
|
|
|
case WINDOW_BUFFER_SIZE_EVENT:
|
2020-10-25 07:57:56 +08:00
|
|
|
out->Send(Event::Special({0}));
|
2020-03-25 08:38:50 +08:00
|
|
|
break;
|
|
|
|
case MENU_EVENT:
|
|
|
|
case FOCUS_EVENT:
|
|
|
|
case MOUSE_EVENT:
|
|
|
|
// TODO(mauve): Implement later.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-22 05:54:39 +08:00
|
|
|
#elif defined(__EMSCRIPTEN__)
|
|
|
|
#include <emscripten.h>
|
|
|
|
|
|
|
|
// Read char from the terminal.
|
2022-02-13 18:11:34 +08:00
|
|
|
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
2021-03-22 05:54:39 +08:00
|
|
|
(void)timeout_microseconds;
|
|
|
|
auto parser = TerminalInputParser(std::move(out));
|
2020-03-25 08:38:50 +08:00
|
|
|
|
2021-03-22 05:54:39 +08:00
|
|
|
char c;
|
|
|
|
while (!*quit) {
|
2021-05-10 02:32:27 +08:00
|
|
|
while (read(STDIN_FILENO, &c, 1), c)
|
2021-03-22 05:54:39 +08:00
|
|
|
parser.Add(c);
|
|
|
|
|
|
|
|
emscripten_sleep(1);
|
|
|
|
parser.Timeout(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-22 05:04:32 +08:00
|
|
|
extern "C" {
|
2022-08-31 00:52:33 +08:00
|
|
|
EMSCRIPTEN_KEEPALIVE
|
|
|
|
void ftxui_on_resize(int columns, int rows) {
|
|
|
|
Terminal::SetFallbackSize({
|
|
|
|
columns,
|
|
|
|
rows,
|
|
|
|
});
|
|
|
|
std::raise(SIGWINCH);
|
|
|
|
}
|
2022-08-22 05:04:32 +08:00
|
|
|
}
|
|
|
|
|
2022-12-02 05:56:35 +08:00
|
|
|
#else // POSIX (Linux & Mac)
|
|
|
|
|
2020-05-02 08:02:04 +08:00
|
|
|
int CheckStdinReady(int usec_timeout) {
|
|
|
|
timeval tv = {0, usec_timeout};
|
|
|
|
fd_set fds;
|
2022-03-31 08:17:43 +08:00
|
|
|
FD_ZERO(&fds); // NOLINT
|
|
|
|
FD_SET(STDIN_FILENO, &fds); // NOLINT
|
|
|
|
select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &tv); // NOLINT
|
|
|
|
return FD_ISSET(STDIN_FILENO, &fds); // NOLINT
|
2020-05-02 08:02:04 +08:00
|
|
|
}
|
|
|
|
|
2020-03-25 08:15:46 +08:00
|
|
|
// Read char from the terminal.
|
2022-02-13 18:11:34 +08:00
|
|
|
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
2020-10-25 07:57:56 +08:00
|
|
|
auto parser = TerminalInputParser(std::move(out));
|
2020-05-02 05:33:21 +08:00
|
|
|
|
|
|
|
while (!*quit) {
|
2020-10-25 07:57:56 +08:00
|
|
|
if (!CheckStdinReady(timeout_microseconds)) {
|
|
|
|
parser.Timeout(timeout_milliseconds);
|
2020-05-02 05:33:21 +08:00
|
|
|
continue;
|
2020-10-25 07:57:56 +08:00
|
|
|
}
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
const size_t buffer_size = 100;
|
2023-02-27 04:49:52 +08:00
|
|
|
std::array<char, buffer_size> buffer; // NOLINT;
|
|
|
|
size_t l = read(fileno(stdin), buffer.data(), buffer_size); // NOLINT
|
|
|
|
for (size_t i = 0; i < l; ++i) {
|
2022-03-31 08:17:43 +08:00
|
|
|
parser.Add(buffer[i]); // NOLINT
|
|
|
|
}
|
2020-05-02 05:33:21 +08:00
|
|
|
}
|
2020-03-25 08:15:46 +08:00
|
|
|
}
|
2022-12-02 05:56:35 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
std::stack<Closure> on_exit_functions; // NOLINT
|
|
|
|
void OnExit() {
|
|
|
|
while (!on_exit_functions.empty()) {
|
|
|
|
on_exit_functions.top()();
|
|
|
|
on_exit_functions.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-20 01:51:25 +08:00
|
|
|
std::atomic<int> g_signal_exit_count = 0; // NOLINT
|
2022-12-02 05:56:35 +08:00
|
|
|
#if !defined(_WIN32)
|
2022-12-20 01:51:25 +08:00
|
|
|
std::atomic<int> g_signal_stop_count = 0; // NOLINT
|
|
|
|
std::atomic<int> g_signal_resize_count = 0; // NOLINT
|
2022-12-02 05:56:35 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// Async signal safe function
|
|
|
|
void RecordSignal(int signal) {
|
|
|
|
switch (signal) {
|
|
|
|
case SIGABRT:
|
|
|
|
case SIGFPE:
|
|
|
|
case SIGILL:
|
|
|
|
case SIGINT:
|
|
|
|
case SIGSEGV:
|
|
|
|
case SIGTERM:
|
|
|
|
g_signal_exit_count++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
#if !defined(_WIN32)
|
|
|
|
case SIGTSTP:
|
|
|
|
g_signal_stop_count++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SIGWINCH:
|
|
|
|
g_signal_resize_count++;
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExecuteSignalHandlers() {
|
|
|
|
int signal_exit_count = g_signal_exit_count.exchange(0);
|
|
|
|
while (signal_exit_count--) {
|
|
|
|
ScreenInteractive::Private::Signal(*g_active_screen, SIGABRT);
|
|
|
|
}
|
2020-03-25 08:15:46 +08:00
|
|
|
|
2022-12-02 05:56:35 +08:00
|
|
|
#if !defined(_WIN32)
|
|
|
|
int signal_stop_count = g_signal_stop_count.exchange(0);
|
|
|
|
while (signal_stop_count--) {
|
|
|
|
ScreenInteractive::Private::Signal(*g_active_screen, SIGTSTP);
|
|
|
|
}
|
|
|
|
|
|
|
|
int signal_resize_count = g_signal_resize_count.exchange(0);
|
|
|
|
while (signal_resize_count--) {
|
|
|
|
ScreenInteractive::Private::Signal(*g_active_screen, SIGWINCH);
|
|
|
|
}
|
2020-03-25 08:38:50 +08:00
|
|
|
#endif
|
2022-12-02 05:56:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void InstallSignalHandler(int sig) {
|
|
|
|
auto old_signal_handler = std::signal(sig, RecordSignal);
|
|
|
|
on_exit_functions.push(
|
|
|
|
[=] { std::ignore = std::signal(sig, old_signal_handler); });
|
2022-12-23 01:00:27 +08:00
|
|
|
}
|
2020-03-25 08:38:50 +08:00
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
const std::string CSI = "\x1b["; // NOLINT
|
2021-04-25 21:22:38 +08:00
|
|
|
|
|
|
|
// DEC: Digital Equipment Corporation
|
|
|
|
enum class DECMode {
|
|
|
|
kLineWrap = 7,
|
|
|
|
kCursor = 25,
|
2022-12-15 05:09:25 +08:00
|
|
|
|
|
|
|
kMouseX10 = 9,
|
2021-04-25 21:22:38 +08:00
|
|
|
kMouseVt200 = 1000,
|
2022-12-15 05:09:25 +08:00
|
|
|
kMouseVt200Highlight = 1001,
|
|
|
|
|
|
|
|
kMouseBtnEventMouse = 1002,
|
2021-04-25 21:22:38 +08:00
|
|
|
kMouseAnyEvent = 1003,
|
2022-12-15 05:09:25 +08:00
|
|
|
|
2021-04-25 21:22:38 +08:00
|
|
|
kMouseUtf8 = 1005,
|
|
|
|
kMouseSgrExtMode = 1006,
|
|
|
|
kMouseUrxvtMode = 1015,
|
|
|
|
kMouseSgrPixelsMode = 1016,
|
|
|
|
kAlternateScreen = 1049,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Device Status Report (DSR) {
|
|
|
|
enum class DSRMode {
|
|
|
|
kCursor = 6,
|
|
|
|
};
|
2019-06-30 00:52:58 +08:00
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
std::string Serialize(const std::vector<DECMode>& parameters) {
|
2021-04-25 21:22:38 +08:00
|
|
|
bool first = true;
|
|
|
|
std::string out;
|
2022-12-20 01:51:25 +08:00
|
|
|
for (const DECMode parameter : parameters) {
|
2022-03-31 08:17:43 +08:00
|
|
|
if (!first) {
|
2021-04-25 21:22:38 +08:00
|
|
|
out += ";";
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2021-04-25 21:22:38 +08:00
|
|
|
out += std::to_string(int(parameter));
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
2019-07-01 05:53:56 +08:00
|
|
|
|
2021-04-25 21:22:38 +08:00
|
|
|
// DEC Private Mode Set (DECSET)
|
2022-03-31 08:17:43 +08:00
|
|
|
std::string Set(const std::vector<DECMode>& parameters) {
|
2021-04-25 21:22:38 +08:00
|
|
|
return CSI + "?" + Serialize(parameters) + "h";
|
|
|
|
}
|
2021-04-06 04:03:37 +08:00
|
|
|
|
2021-04-25 21:22:38 +08:00
|
|
|
// DEC Private Mode Reset (DECRST)
|
2022-03-31 08:17:43 +08:00
|
|
|
std::string Reset(const std::vector<DECMode>& parameters) {
|
2021-04-25 21:22:38 +08:00
|
|
|
return CSI + "?" + Serialize(parameters) + "l";
|
|
|
|
}
|
2021-04-25 00:16:13 +08:00
|
|
|
|
2021-04-25 21:22:38 +08:00
|
|
|
// Device Status Report (DSR)
|
2022-03-31 08:17:43 +08:00
|
|
|
std::string DeviceStatusReport(DSRMode ps) {
|
2021-04-25 21:22:38 +08:00
|
|
|
return CSI + std::to_string(int(ps)) + "n";
|
|
|
|
}
|
2020-05-03 02:39:56 +08:00
|
|
|
|
2021-05-02 00:13:56 +08:00
|
|
|
class CapturedMouseImpl : public CapturedMouseInterface {
|
|
|
|
public:
|
2022-03-31 08:17:43 +08:00
|
|
|
explicit CapturedMouseImpl(std::function<void(void)> callback)
|
|
|
|
: callback_(std::move(callback)) {}
|
2021-05-02 00:13:56 +08:00
|
|
|
~CapturedMouseImpl() override { callback_(); }
|
2022-03-31 08:17:43 +08:00
|
|
|
CapturedMouseImpl(const CapturedMouseImpl&) = delete;
|
|
|
|
CapturedMouseImpl(CapturedMouseImpl&&) = delete;
|
|
|
|
CapturedMouseImpl& operator=(const CapturedMouseImpl&) = delete;
|
|
|
|
CapturedMouseImpl& operator=(CapturedMouseImpl&&) = delete;
|
2021-05-02 00:13:56 +08:00
|
|
|
|
|
|
|
private:
|
|
|
|
std::function<void(void)> callback_;
|
|
|
|
};
|
|
|
|
|
2022-03-14 01:51:46 +08:00
|
|
|
void AnimationListener(std::atomic<bool>* quit, Sender<Task> out) {
|
2022-03-31 08:17:43 +08:00
|
|
|
// Animation at around 60fps.
|
|
|
|
const auto time_delta = std::chrono::milliseconds(15);
|
2022-03-14 01:51:46 +08:00
|
|
|
while (!*quit) {
|
|
|
|
out->Send(AnimationTask());
|
2022-03-31 08:17:43 +08:00
|
|
|
std::this_thread::sleep_for(time_delta);
|
2022-03-14 01:51:46 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-09 20:53:56 +08:00
|
|
|
} // namespace
|
|
|
|
|
2020-05-03 02:39:56 +08:00
|
|
|
ScreenInteractive::ScreenInteractive(int dimx,
|
|
|
|
int dimy,
|
|
|
|
Dimension dimension,
|
|
|
|
bool use_alternative_screen)
|
|
|
|
: Screen(dimx, dimy),
|
|
|
|
dimension_(dimension),
|
|
|
|
use_alternative_screen_(use_alternative_screen) {
|
2022-02-13 18:11:34 +08:00
|
|
|
task_receiver_ = MakeReceiver<Task>();
|
2020-03-25 06:26:55 +08:00
|
|
|
}
|
|
|
|
|
2019-01-05 09:03:49 +08:00
|
|
|
// static
|
2019-01-27 04:52:55 +08:00
|
|
|
ScreenInteractive ScreenInteractive::FixedSize(int dimx, int dimy) {
|
2022-09-06 02:56:41 +08:00
|
|
|
return {
|
|
|
|
dimx,
|
|
|
|
dimy,
|
|
|
|
Dimension::Fixed,
|
|
|
|
false,
|
|
|
|
};
|
2019-01-05 09:03:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
ScreenInteractive ScreenInteractive::Fullscreen() {
|
2022-09-06 02:56:41 +08:00
|
|
|
return {
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
Dimension::Fullscreen,
|
|
|
|
true,
|
|
|
|
};
|
2019-01-05 09:03:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
ScreenInteractive ScreenInteractive::TerminalOutput() {
|
2022-09-06 02:56:41 +08:00
|
|
|
return {
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
Dimension::TerminalOutput,
|
|
|
|
false,
|
|
|
|
};
|
2019-01-05 09:03:49 +08:00
|
|
|
}
|
|
|
|
|
2019-01-19 07:20:29 +08:00
|
|
|
// static
|
|
|
|
ScreenInteractive ScreenInteractive::FitComponent() {
|
2022-09-06 02:56:41 +08:00
|
|
|
return {
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
Dimension::FitComponent,
|
|
|
|
false,
|
|
|
|
};
|
2019-01-19 07:20:29 +08:00
|
|
|
}
|
|
|
|
|
2022-02-13 18:11:34 +08:00
|
|
|
void ScreenInteractive::Post(Task task) {
|
2022-05-22 21:37:27 +08:00
|
|
|
// Task/Events sent toward inactive screen or screen waiting to become
|
|
|
|
// inactive are dropped.
|
2022-05-23 03:41:29 +08:00
|
|
|
if (!task_sender_) {
|
2022-05-22 21:37:27 +08:00
|
|
|
return;
|
2022-05-23 03:41:29 +08:00
|
|
|
}
|
2022-05-22 21:37:27 +08:00
|
|
|
|
|
|
|
task_sender_->Send(std::move(task));
|
2022-02-13 18:11:34 +08:00
|
|
|
}
|
2022-05-22 21:37:27 +08:00
|
|
|
|
2022-02-13 18:11:34 +08:00
|
|
|
void ScreenInteractive::PostEvent(Event event) {
|
|
|
|
Post(event);
|
2019-01-27 09:33:06 +08:00
|
|
|
}
|
|
|
|
|
2022-03-14 01:51:46 +08:00
|
|
|
void ScreenInteractive::RequestAnimationFrame() {
|
2022-03-31 08:17:43 +08:00
|
|
|
if (animation_requested_) {
|
2022-03-14 01:51:46 +08:00
|
|
|
return;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2022-03-14 01:51:46 +08:00
|
|
|
animation_requested_ = true;
|
|
|
|
auto now = animation::Clock::now();
|
2022-03-31 08:17:43 +08:00
|
|
|
const auto time_histeresis = std::chrono::milliseconds(33);
|
2022-10-19 03:29:27 +08:00
|
|
|
if (now - previous_animation_time_ >= time_histeresis) {
|
|
|
|
previous_animation_time_ = now;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2022-03-14 01:51:46 +08:00
|
|
|
}
|
|
|
|
|
2021-05-02 00:13:56 +08:00
|
|
|
CapturedMouse ScreenInteractive::CaptureMouse() {
|
2022-03-31 08:17:43 +08:00
|
|
|
if (mouse_captured) {
|
2021-05-02 00:13:56 +08:00
|
|
|
return nullptr;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2021-05-02 00:13:56 +08:00
|
|
|
mouse_captured = true;
|
|
|
|
return std::make_unique<CapturedMouseImpl>(
|
|
|
|
[this] { mouse_captured = false; });
|
|
|
|
}
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
void ScreenInteractive::Loop(Component component) { // NOLINT
|
2022-11-27 03:43:09 +08:00
|
|
|
class Loop loop(this, std::move(component));
|
2022-10-19 03:29:27 +08:00
|
|
|
loop.Run();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ScreenInteractive::HasQuitted() {
|
|
|
|
return task_receiver_->HasQuitted();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ScreenInteractive::PreMain() {
|
2021-09-01 23:47:48 +08:00
|
|
|
// Suspend previously active screen:
|
|
|
|
if (g_active_screen) {
|
|
|
|
std::swap(suspended_screen_, g_active_screen);
|
2022-12-02 05:56:35 +08:00
|
|
|
// Reset cursor position to the top of the screen and clear the screen.
|
|
|
|
suspended_screen_->ResetCursorPosition();
|
|
|
|
std::cout << suspended_screen_->ResetPosition(/*clear=*/true);
|
2021-09-01 23:47:48 +08:00
|
|
|
suspended_screen_->dimx_ = 0;
|
|
|
|
suspended_screen_->dimy_ = 0;
|
2022-12-02 05:56:35 +08:00
|
|
|
|
|
|
|
// Reset dimensions to force drawing the screen again next time:
|
2021-09-01 23:47:48 +08:00
|
|
|
suspended_screen_->Uninstall();
|
|
|
|
}
|
|
|
|
|
|
|
|
// This screen is now active:
|
|
|
|
g_active_screen = this;
|
|
|
|
g_active_screen->Install();
|
2022-10-19 03:29:27 +08:00
|
|
|
|
|
|
|
previous_animation_time_ = animation::Clock::now();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ScreenInteractive::PostMain() {
|
2021-09-01 23:47:48 +08:00
|
|
|
// Put cursor position at the end of the drawing.
|
2022-12-02 05:56:35 +08:00
|
|
|
ResetCursorPosition();
|
|
|
|
|
|
|
|
g_active_screen = nullptr;
|
2021-09-01 23:47:48 +08:00
|
|
|
|
|
|
|
// Restore suspended screen.
|
|
|
|
if (suspended_screen_) {
|
2022-12-02 05:56:35 +08:00
|
|
|
// Clear screen, and put the cursor at the beginning of the drawing.
|
2021-09-01 23:47:48 +08:00
|
|
|
std::cout << ResetPosition(/*clear=*/true);
|
|
|
|
dimx_ = 0;
|
|
|
|
dimy_ = 0;
|
2022-12-02 05:56:35 +08:00
|
|
|
Uninstall();
|
2021-09-01 23:47:48 +08:00
|
|
|
std::swap(g_active_screen, suspended_screen_);
|
|
|
|
g_active_screen->Install();
|
|
|
|
} else {
|
2022-12-02 05:56:35 +08:00
|
|
|
Uninstall();
|
2023-03-31 23:44:01 +08:00
|
|
|
|
|
|
|
std::cout << '\r';
|
2021-09-01 23:47:48 +08:00
|
|
|
// On final exit, keep the current drawing and reset cursor position one
|
|
|
|
// line after it.
|
2023-03-31 23:44:01 +08:00
|
|
|
if (!use_alternative_screen_) {
|
|
|
|
std::cout << std::endl;
|
|
|
|
}
|
2021-09-01 23:47:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-19 20:38:39 +08:00
|
|
|
/// @brief Decorate a function. It executes the same way, but with the currently
|
|
|
|
/// active screen terminal hooks temporarilly uninstalled during its execution.
|
|
|
|
/// @param fn The function to decorate.
|
2022-03-31 08:17:43 +08:00
|
|
|
Closure ScreenInteractive::WithRestoredIO(Closure fn) { // NOLINT
|
2022-01-19 20:38:39 +08:00
|
|
|
return [this, fn] {
|
|
|
|
Uninstall();
|
|
|
|
fn();
|
|
|
|
Install();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-03-14 01:51:46 +08:00
|
|
|
// static
|
|
|
|
ScreenInteractive* ScreenInteractive::Active() {
|
|
|
|
return g_active_screen;
|
|
|
|
}
|
|
|
|
|
2021-09-01 23:47:48 +08:00
|
|
|
void ScreenInteractive::Install() {
|
2022-12-02 05:56:35 +08:00
|
|
|
frame_valid_ = false;
|
|
|
|
|
2022-01-19 20:38:39 +08:00
|
|
|
// After uninstalling the new configuration, flush it to the terminal to
|
|
|
|
// ensure it is fully applied:
|
|
|
|
on_exit_functions.push([] { Flush(); });
|
|
|
|
|
2021-06-20 00:44:43 +08:00
|
|
|
on_exit_functions.push([this] { ExitLoopClosure()(); });
|
|
|
|
|
2021-06-26 06:42:08 +08:00
|
|
|
// Install signal handlers to restore the terminal state on exit. The default
|
|
|
|
// signal handlers are restored on exit.
|
2022-12-20 01:51:25 +08:00
|
|
|
for (const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
|
2022-12-02 05:56:35 +08:00
|
|
|
InstallSignalHandler(signal);
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2020-05-02 05:48:22 +08:00
|
|
|
|
2022-12-02 05:56:35 +08:00
|
|
|
// Save the old terminal configuration and restore it on exit.
|
2020-03-25 15:54:03 +08:00
|
|
|
#if defined(_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); });
|
|
|
|
|
2020-07-17 02:58:58 +08:00
|
|
|
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
|
|
|
|
const int enable_virtual_terminal_processing = 0x0004;
|
|
|
|
const int disable_newline_auto_return = 0x0008;
|
|
|
|
out_mode |= enable_virtual_terminal_processing;
|
|
|
|
out_mode |= disable_newline_auto_return;
|
|
|
|
|
|
|
|
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
|
|
|
|
const int enable_line_input = 0x0002;
|
|
|
|
const int enable_echo_input = 0x0004;
|
|
|
|
const int enable_virtual_terminal_input = 0x0200;
|
|
|
|
const int enable_window_input = 0x0008;
|
|
|
|
in_mode &= ~enable_echo_input;
|
|
|
|
in_mode &= ~enable_line_input;
|
|
|
|
in_mode |= enable_virtual_terminal_input;
|
|
|
|
in_mode |= enable_window_input;
|
2020-03-22 18:29:33 +08:00
|
|
|
|
|
|
|
SetConsoleMode(stdin_handle, in_mode);
|
|
|
|
SetConsoleMode(stdout_handle, out_mode);
|
2020-03-21 23:21:32 +08:00
|
|
|
#else
|
2022-12-20 01:51:25 +08:00
|
|
|
for (const int signal : {SIGWINCH, SIGTSTP}) {
|
2022-12-02 05:56:35 +08:00
|
|
|
InstallSignalHandler(signal);
|
|
|
|
}
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
struct termios terminal; // NOLINT
|
2020-03-22 18:29:33 +08:00
|
|
|
tcgetattr(STDIN_FILENO, &terminal);
|
|
|
|
on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
terminal.c_lflag &= ~ICANON; // NOLINT Non canonique terminal.
|
|
|
|
terminal.c_lflag &= ~ECHO; // NOLINT Do not print after a key press.
|
2020-05-02 08:02:04 +08:00
|
|
|
terminal.c_cc[VMIN] = 0;
|
|
|
|
terminal.c_cc[VTIME] = 0;
|
2020-05-21 02:51:07 +08:00
|
|
|
// auto oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
|
|
|
|
// fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
|
|
|
|
// on_exit_functions.push([=] { fcntl(STDIN_FILENO, F_GETFL, oldf); });
|
2020-05-02 08:02:04 +08:00
|
|
|
|
2020-03-22 18:29:33 +08:00
|
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
|
2020-03-24 08:26:06 +08:00
|
|
|
|
2020-03-21 23:21:32 +08:00
|
|
|
#endif
|
2018-10-10 01:06:03 +08:00
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
auto enable = [&](const std::vector<DECMode>& parameters) {
|
2021-04-25 21:22:38 +08:00
|
|
|
std::cout << Set(parameters);
|
|
|
|
on_exit_functions.push([=] { std::cout << Reset(parameters); });
|
|
|
|
};
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
auto disable = [&](const std::vector<DECMode>& parameters) {
|
2021-04-25 21:22:38 +08:00
|
|
|
std::cout << Reset(parameters);
|
|
|
|
on_exit_functions.push([=] { std::cout << Set(parameters); });
|
|
|
|
};
|
|
|
|
|
2021-03-16 16:46:02 +08:00
|
|
|
if (use_alternative_screen_) {
|
2021-04-25 21:22:38 +08:00
|
|
|
enable({
|
|
|
|
DECMode::kAlternateScreen,
|
|
|
|
});
|
2021-03-16 16:46:02 +08:00
|
|
|
}
|
|
|
|
|
2022-11-11 21:09:53 +08:00
|
|
|
on_exit_functions.push([=] {
|
|
|
|
std::cout << "\033[?25h"; // Enable cursor.
|
|
|
|
std::cout << "\033[?1 q"; // Cursor block blinking.
|
|
|
|
});
|
|
|
|
|
2021-04-25 21:22:38 +08:00
|
|
|
disable({
|
2022-12-02 05:56:35 +08:00
|
|
|
// DECMode::kCursor,
|
2021-04-25 21:22:38 +08:00
|
|
|
DECMode::kLineWrap,
|
|
|
|
});
|
|
|
|
|
2022-12-15 05:09:25 +08:00
|
|
|
enable({DECMode::kMouseVt200});
|
|
|
|
enable({DECMode::kMouseAnyEvent});
|
|
|
|
enable({DECMode::kMouseUrxvtMode});
|
|
|
|
enable({DECMode::kMouseSgrExtMode});
|
2019-06-30 00:52:58 +08:00
|
|
|
|
2022-12-02 05:56:35 +08:00
|
|
|
// After installing the new configuration, flush it to the terminal to
|
|
|
|
// ensure it is fully applied:
|
2022-01-19 20:38:39 +08:00
|
|
|
Flush();
|
2021-04-25 22:58:16 +08:00
|
|
|
|
2021-09-01 23:47:48 +08:00
|
|
|
quit_ = false;
|
2022-02-13 18:11:34 +08:00
|
|
|
task_sender_ = task_receiver_->MakeSender();
|
2021-09-01 23:47:48 +08:00
|
|
|
event_listener_ =
|
2022-02-13 18:11:34 +08:00
|
|
|
std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
|
2022-03-14 01:51:46 +08:00
|
|
|
animation_listener_ =
|
|
|
|
std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
|
2021-09-01 23:47:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ScreenInteractive::Uninstall() {
|
2022-12-02 05:56:35 +08:00
|
|
|
ExitNow();
|
2021-09-01 23:47:48 +08:00
|
|
|
event_listener_.join();
|
2022-03-14 01:51:46 +08:00
|
|
|
animation_listener_.join();
|
2022-12-02 05:56:35 +08:00
|
|
|
OnExit();
|
2021-09-01 23:47:48 +08:00
|
|
|
}
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
// NOLINTNEXTLINE
|
2022-10-19 03:29:27 +08:00
|
|
|
void ScreenInteractive::RunOnceBlocking(Component component) {
|
2022-12-02 05:56:35 +08:00
|
|
|
ExecuteSignalHandlers();
|
2022-10-19 03:29:27 +08:00
|
|
|
Task task;
|
|
|
|
if (task_receiver_->Receive(&task)) {
|
|
|
|
HandleTask(component, task);
|
|
|
|
}
|
|
|
|
RunOnce(component);
|
|
|
|
}
|
2021-04-25 00:16:13 +08:00
|
|
|
|
2022-10-19 03:29:27 +08:00
|
|
|
void ScreenInteractive::RunOnce(Component component) {
|
|
|
|
Task task;
|
|
|
|
while (task_receiver_->ReceiveNonBlocking(&task)) {
|
|
|
|
HandleTask(component, task);
|
2022-12-02 05:56:35 +08:00
|
|
|
ExecuteSignalHandlers();
|
2022-10-19 03:29:27 +08:00
|
|
|
}
|
2022-11-27 03:43:09 +08:00
|
|
|
Draw(std::move(component));
|
2022-10-19 03:29:27 +08:00
|
|
|
}
|
2022-03-26 14:55:52 +08:00
|
|
|
|
2022-10-19 03:29:27 +08:00
|
|
|
void ScreenInteractive::HandleTask(Component component, Task& task) {
|
|
|
|
// clang-format off
|
|
|
|
std::visit([&](auto&& arg) {
|
|
|
|
using T = std::decay_t<decltype(arg)>;
|
2022-03-26 14:55:52 +08:00
|
|
|
|
2022-10-19 03:29:27 +08:00
|
|
|
// Handle Event.
|
|
|
|
if constexpr (std::is_same_v<T, Event>) {
|
|
|
|
if (arg.is_cursor_reporting()) {
|
|
|
|
cursor_x_ = arg.cursor_x();
|
|
|
|
cursor_y_ = arg.cursor_y();
|
2022-03-26 14:55:52 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-10-19 03:29:27 +08:00
|
|
|
if (arg.is_mouse()) {
|
|
|
|
arg.mouse().x -= cursor_x_;
|
|
|
|
arg.mouse().y -= cursor_y_;
|
2022-03-26 14:55:52 +08:00
|
|
|
}
|
|
|
|
|
2022-10-19 03:29:27 +08:00
|
|
|
arg.screen_ = this;
|
|
|
|
component->OnEvent(arg);
|
|
|
|
frame_valid_ = false;
|
|
|
|
return;
|
|
|
|
}
|
2022-03-26 14:55:52 +08:00
|
|
|
|
2022-10-19 03:29:27 +08:00
|
|
|
// Handle callback
|
|
|
|
if constexpr (std::is_same_v<T, Closure>) {
|
|
|
|
arg();
|
|
|
|
return;
|
|
|
|
}
|
2022-03-26 14:55:52 +08:00
|
|
|
|
2022-10-19 03:29:27 +08:00
|
|
|
// Handle Animation
|
|
|
|
if constexpr (std::is_same_v<T, AnimationTask>) {
|
|
|
|
if (!animation_requested_) {
|
2022-03-26 14:55:52 +08:00
|
|
|
return;
|
|
|
|
}
|
2022-10-19 03:29:27 +08:00
|
|
|
|
|
|
|
animation_requested_ = false;
|
2022-12-20 01:51:25 +08:00
|
|
|
const animation::TimePoint now = animation::Clock::now();
|
|
|
|
const animation::Duration delta = now - previous_animation_time_;
|
2022-10-19 03:29:27 +08:00
|
|
|
previous_animation_time_ = now;
|
|
|
|
|
|
|
|
animation::Params params(delta);
|
|
|
|
component->OnAnimation(params);
|
|
|
|
frame_valid_ = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
task);
|
|
|
|
// clang-format on
|
2018-10-10 01:06:03 +08:00
|
|
|
}
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
// NOLINTNEXTLINE
|
2021-05-10 02:32:27 +08:00
|
|
|
void ScreenInteractive::Draw(Component component) {
|
2022-11-27 03:43:09 +08:00
|
|
|
if (frame_valid_) {
|
2022-10-19 03:29:27 +08:00
|
|
|
return;
|
2022-11-27 03:43:09 +08:00
|
|
|
}
|
2019-01-13 01:24:46 +08:00
|
|
|
auto document = component->Render();
|
2020-02-12 05:34:01 +08:00
|
|
|
int dimx = 0;
|
|
|
|
int dimy = 0;
|
2023-01-22 18:51:37 +08:00
|
|
|
auto terminal = Terminal::Size();
|
|
|
|
document->ComputeRequirement();
|
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:
|
2023-01-22 18:51:37 +08:00
|
|
|
dimx = terminal.dimx;
|
2020-06-01 22:13:29 +08:00
|
|
|
dimy = document->requirement().min_y;
|
2019-01-05 09:03:49 +08:00
|
|
|
break;
|
|
|
|
case Dimension::Fullscreen:
|
2023-01-22 18:51:37 +08:00
|
|
|
dimx = terminal.dimx;
|
|
|
|
dimy = terminal.dimy;
|
2019-01-05 09:03:49 +08:00
|
|
|
break;
|
2019-01-19 07:20:29 +08:00
|
|
|
case Dimension::FitComponent:
|
2020-06-01 22:13:29 +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
|
|
|
}
|
|
|
|
|
2022-12-20 01:51:25 +08:00
|
|
|
const bool resized = (dimx != dimx_) || (dimy != dimy_);
|
2022-12-02 05:56:35 +08:00
|
|
|
ResetCursorPosition();
|
|
|
|
std::cout << ResetPosition(/*clear=*/resized);
|
2021-05-17 06:44:37 +08:00
|
|
|
|
2019-06-30 00:52:58 +08:00
|
|
|
// Resize the screen if needed.
|
2021-05-17 06:44:37 +08:00
|
|
|
if (resized) {
|
2019-01-05 09:03:49 +08:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-07-04 23:38:31 +08:00
|
|
|
// Periodically request the terminal emulator the frame position relative to
|
|
|
|
// the screen. This is useful for converting mouse position reported in
|
|
|
|
// screen's coordinates to frame's coordinates.
|
|
|
|
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
|
2022-01-12 06:06:36 +08:00
|
|
|
// Microsoft's terminal suffers from a [bug]. When reporting the cursor
|
|
|
|
// position, several output sequences are mixed together into garbage.
|
|
|
|
// This causes FTXUI user to see some "1;1;R" sequences into the Input
|
|
|
|
// component. See [issue]. Solution is to request cursor position less
|
|
|
|
// often. [bug]: https://github.com/microsoft/terminal/pull/7583 [issue]:
|
|
|
|
// https://github.com/ArthurSonzogni/FTXUI/issues/136
|
|
|
|
static int i = -3;
|
|
|
|
++i;
|
2022-03-31 08:17:43 +08:00
|
|
|
if (!use_alternative_screen_ && (i % 150 == 0)) { // NOLINT
|
2022-01-12 06:06:36 +08:00
|
|
|
std::cout << DeviceStatusReport(DSRMode::kCursor);
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2021-07-04 23:38:31 +08:00
|
|
|
#else
|
|
|
|
static int i = -3;
|
2021-05-17 06:44:37 +08:00
|
|
|
++i;
|
2022-03-31 08:17:43 +08:00
|
|
|
if (!use_alternative_screen_ &&
|
|
|
|
(previous_frame_resized_ || i % 40 == 0)) { // NOLINT
|
2021-07-04 23:38:31 +08:00
|
|
|
std::cout << DeviceStatusReport(DSRMode::kCursor);
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2022-01-12 06:06:36 +08:00
|
|
|
#endif
|
|
|
|
previous_frame_resized_ = resized;
|
2021-05-17 06:44:37 +08:00
|
|
|
|
2020-05-21 03:23:59 +08:00
|
|
|
Render(*this, document);
|
2019-06-30 00:52:58 +08:00
|
|
|
|
|
|
|
// Set cursor position for user using tools to insert CJK characters.
|
2022-11-11 21:09:53 +08:00
|
|
|
{
|
2023-01-22 18:51:37 +08:00
|
|
|
const int dx = dimx_ - 1 - cursor_.x + int(dimx_ != terminal.dimx);
|
2022-12-20 01:51:25 +08:00
|
|
|
const int dy = dimy_ - 1 - cursor_.y;
|
2019-06-30 00:52:58 +08:00
|
|
|
|
2023-01-22 18:51:37 +08:00
|
|
|
set_cursor_position = "\x1B[" + std::to_string(dy) + "A" + //
|
|
|
|
"\x1B[" + std::to_string(dx) + "D";
|
|
|
|
reset_cursor_position = "\x1B[" + std::to_string(dy) + "B" + //
|
|
|
|
"\x1B[" + std::to_string(dx) + "C";
|
2022-11-11 21:09:53 +08:00
|
|
|
|
|
|
|
if (cursor_.shape == Cursor::Hidden) {
|
|
|
|
set_cursor_position += "\033[?25l";
|
|
|
|
} else {
|
|
|
|
set_cursor_position += "\033[?25h";
|
2022-12-02 05:56:35 +08:00
|
|
|
set_cursor_position +=
|
|
|
|
"\033[" + std::to_string(int(cursor_.shape)) + " q";
|
2022-11-11 21:09:53 +08:00
|
|
|
}
|
2019-06-30 00:52:58 +08:00
|
|
|
}
|
2022-10-19 03:29:27 +08:00
|
|
|
|
|
|
|
std::cout << ToString() << set_cursor_position;
|
|
|
|
Flush();
|
|
|
|
Clear();
|
|
|
|
frame_valid_ = true;
|
2018-10-10 01:06:03 +08:00
|
|
|
}
|
|
|
|
|
2022-12-02 05:56:35 +08:00
|
|
|
void ScreenInteractive::ResetCursorPosition() {
|
|
|
|
std::cout << reset_cursor_position;
|
|
|
|
reset_cursor_position = "";
|
|
|
|
}
|
|
|
|
|
2022-02-13 18:11:34 +08:00
|
|
|
Closure ScreenInteractive::ExitLoopClosure() {
|
2022-10-19 03:29:27 +08:00
|
|
|
return [this] { Exit(); };
|
|
|
|
}
|
|
|
|
|
|
|
|
void ScreenInteractive::Exit() {
|
2022-12-02 05:56:35 +08:00
|
|
|
Post([this] { ExitNow(); });
|
2018-10-10 01:06:03 +08:00
|
|
|
}
|
|
|
|
|
2022-12-02 05:56:35 +08:00
|
|
|
void ScreenInteractive::ExitNow() {
|
|
|
|
quit_ = true;
|
|
|
|
task_sender_.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ScreenInteractive::Signal(int signal) {
|
|
|
|
if (signal == SIGABRT) {
|
|
|
|
OnExit();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Windows do no support SIGTSTP / SIGWINCH
|
|
|
|
#if !defined(_WIN32)
|
|
|
|
if (signal == SIGTSTP) {
|
|
|
|
Post([&] {
|
|
|
|
ResetCursorPosition();
|
|
|
|
std::cout << ResetPosition(/*clear*/ true); // Cursor to the beginning
|
|
|
|
Uninstall();
|
|
|
|
dimx_ = 0;
|
|
|
|
dimy_ = 0;
|
|
|
|
Flush();
|
|
|
|
std::ignore = std::raise(SIGTSTP);
|
|
|
|
Install();
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (signal == SIGWINCH) {
|
|
|
|
Post(Event::Special({0}));
|
|
|
|
return;
|
|
|
|
}
|
2022-02-13 18:11:34 +08:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-01-12 22:00:08 +08:00
|
|
|
} // namespace ftxui.
|
2020-08-16 06:24:18 +08:00
|
|
|
|
|
|
|
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
|
|
|
// Use of this source code is governed by the MIT license that can be found in
|
|
|
|
// the LICENSE file.
|