mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-12-16 01:48:56 +08:00
Update
This commit is contained in:
@@ -12,9 +12,15 @@ export module ftxui.component.receiver;
|
||||
* @brief The FTXUI ftxui:: namespace
|
||||
*/
|
||||
export namespace ftxui {
|
||||
// Deprecated:
|
||||
using ftxui::SenderImpl;
|
||||
// Deprecated:
|
||||
using ftxui::ReceiverImpl;
|
||||
// Deprecated:
|
||||
using ftxui::Sender;
|
||||
// Deprecated:
|
||||
using ftxui::Receiver;
|
||||
// Deprecated:
|
||||
using ftxui::MakeReceiver;
|
||||
// Deprecated:
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
// 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.
|
||||
#include <thread> // for thread
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/receiver.hpp"
|
||||
#include "gtest/gtest.h" // for AssertionResult, Message, Test, TestPartResult, EXPECT_EQ, EXPECT_TRUE, EXPECT_FALSE, TEST
|
||||
|
||||
// NOLINTBEGIN
|
||||
namespace ftxui {
|
||||
|
||||
TEST(Receiver, Basic) {
|
||||
auto receiver = MakeReceiver<char>();
|
||||
auto sender = receiver->MakeSender();
|
||||
|
||||
sender->Send('a');
|
||||
sender->Send('b');
|
||||
sender->Send('c');
|
||||
sender.reset();
|
||||
|
||||
char a, b, c, d;
|
||||
EXPECT_TRUE(receiver->Receive(&a));
|
||||
EXPECT_TRUE(receiver->Receive(&b));
|
||||
EXPECT_TRUE(receiver->Receive(&c));
|
||||
EXPECT_FALSE(receiver->Receive(&d));
|
||||
|
||||
EXPECT_EQ(a, 'a');
|
||||
EXPECT_EQ(b, 'b');
|
||||
EXPECT_EQ(c, 'c');
|
||||
}
|
||||
|
||||
TEST(Receiver, BasicWithThread) {
|
||||
auto r1 = MakeReceiver<char>();
|
||||
auto r2 = MakeReceiver<char>();
|
||||
auto r3 = MakeReceiver<char>();
|
||||
|
||||
auto s1 = r1->MakeSender();
|
||||
auto s2 = r2->MakeSender();
|
||||
auto s3 = r3->MakeSender();
|
||||
|
||||
auto s1_bis = r1->MakeSender();
|
||||
|
||||
auto stream = [](Receiver<char> receiver, Sender<char> sender) {
|
||||
char c;
|
||||
while (receiver->Receive(&c))
|
||||
sender->Send(c);
|
||||
};
|
||||
|
||||
// Convert data from a different thread.
|
||||
auto t12 = std::thread(stream, std::move(r1), std::move(s2));
|
||||
auto t23 = std::thread(stream, std::move(r2), std::move(s3));
|
||||
|
||||
// Send some data.
|
||||
s1->Send('1');
|
||||
s1_bis->Send('2');
|
||||
s1->Send('3');
|
||||
s1_bis->Send('4');
|
||||
|
||||
// Close the stream.
|
||||
s1.reset();
|
||||
s1_bis.reset();
|
||||
|
||||
char c;
|
||||
EXPECT_TRUE(r3->Receive(&c));
|
||||
EXPECT_EQ(c, '1');
|
||||
EXPECT_TRUE(r3->Receive(&c));
|
||||
EXPECT_EQ(c, '2');
|
||||
EXPECT_TRUE(r3->Receive(&c));
|
||||
EXPECT_EQ(c, '3');
|
||||
EXPECT_TRUE(r3->Receive(&c));
|
||||
EXPECT_EQ(c, '4');
|
||||
EXPECT_FALSE(r3->Receive(&c));
|
||||
|
||||
// Thread will end at the end of the stream.
|
||||
t12.join();
|
||||
t23.join();
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
// NOLINTEND
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "ftxui/component/screen_interactive.hpp"
|
||||
#include <algorithm> // for copy, max, min
|
||||
#include <array> // for array
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono> // for operator-, milliseconds, operator>=, duration, common_type<>::type, time_point
|
||||
#include <csignal> // for signal, SIGTSTP, SIGABRT, SIGWINCH, raise, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, __sighandler_t, size_t
|
||||
@@ -17,7 +18,6 @@
|
||||
#include <memory>
|
||||
#include <stack> // for stack
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <thread> // for thread, sleep_for
|
||||
#include <tuple> // for _Swallow_assign, ignore
|
||||
#include <type_traits> // for decay_t
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "ftxui/screen/pixel.hpp" // for Pixel
|
||||
#include "ftxui/screen/terminal.hpp" // for Dimensions, Size
|
||||
#include "ftxui/screen/util.hpp" // for util::clamp
|
||||
#include "ftxui/util/autoreset.hpp" // for AutoReset
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define DEFINE_CONSOLEV2_PROPERTIES
|
||||
@@ -49,9 +50,11 @@
|
||||
#error Must be compiled in UNICODE mode
|
||||
#endif
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <sys/select.h> // for select, FD_ISSET, FD_SET, FD_ZERO, fd_set, timeval
|
||||
#include <termios.h> // for tcsetattr, termios, tcgetattr, TCSANOW, cc_t, ECHO, ICANON, VMIN, VTIME
|
||||
#include <unistd.h> // for STDIN_FILENO, read
|
||||
#include <cerrno>
|
||||
#endif
|
||||
|
||||
// Quick exit is missing in standard CLang headers
|
||||
@@ -98,75 +101,9 @@ constexpr int timeout_milliseconds = 20;
|
||||
timeout_milliseconds * 1000;
|
||||
#if defined(_WIN32)
|
||||
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
auto console = GetStdHandle(STD_INPUT_HANDLE);
|
||||
auto parser =
|
||||
TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
|
||||
while (!*quit) {
|
||||
// Throttle ReadConsoleInput by waiting 250ms, this wait function will
|
||||
// return if there is input in the console.
|
||||
auto wait_result = WaitForSingleObject(console, timeout_milliseconds);
|
||||
if (wait_result == WAIT_TIMEOUT) {
|
||||
parser.Timeout(timeout_milliseconds);
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
ReadConsoleInput(console, records.data(), (DWORD)records.size(),
|
||||
&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;
|
||||
std::wstring wstring;
|
||||
wstring += key_event.uChar.UnicodeChar;
|
||||
for (auto it : to_string(wstring)) {
|
||||
parser.Add(it);
|
||||
}
|
||||
} break;
|
||||
case WINDOW_BUFFER_SIZE_EVENT:
|
||||
out->Send(Event::Special({0}));
|
||||
break;
|
||||
case MENU_EVENT:
|
||||
case FOCUS_EVENT:
|
||||
case MOUSE_EVENT:
|
||||
// TODO(mauve): Implement later.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
#include <emscripten.h>
|
||||
|
||||
// Read char from the terminal.
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
auto parser =
|
||||
TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
|
||||
|
||||
char c;
|
||||
while (!*quit) {
|
||||
while (read(STDIN_FILENO, &c, 1), c)
|
||||
parser.Add(c);
|
||||
|
||||
emscripten_sleep(1);
|
||||
parser.Timeout(1);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void ftxui_on_resize(int columns, int rows) {
|
||||
@@ -180,8 +117,8 @@ void ftxui_on_resize(int columns, int rows) {
|
||||
|
||||
#else // POSIX (Linux & Mac)
|
||||
|
||||
int CheckStdinReady(int usec_timeout) {
|
||||
timeval tv = {0, usec_timeout}; // NOLINT
|
||||
int CheckStdinReady() {
|
||||
timeval tv = {0, 0}; // NOLINT
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds); // NOLINT
|
||||
FD_SET(STDIN_FILENO, &fds); // NOLINT
|
||||
@@ -189,25 +126,6 @@ int CheckStdinReady(int usec_timeout) {
|
||||
return FD_ISSET(STDIN_FILENO, &fds); // NOLINT
|
||||
}
|
||||
|
||||
|
||||
// Read char from the terminal.
|
||||
//void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
//auto parser =
|
||||
//TerminalInputParser([&](Event event) { out->Send(std::move(event)); });
|
||||
|
||||
//while (!*quit) {
|
||||
//auto pending = ReadPendingChars();
|
||||
//if (pending.empty()) {
|
||||
//parser.Timeout(timeout_milliseconds);
|
||||
//continue;
|
||||
//}
|
||||
|
||||
//for (auto c : pending) {
|
||||
//parser.Add(c);
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
|
||||
#endif
|
||||
|
||||
std::stack<Closure> on_exit_functions; // NOLINT
|
||||
@@ -665,7 +583,15 @@ void ScreenInteractive::Install() {
|
||||
|
||||
SetConsoleMode(stdin_handle, in_mode);
|
||||
SetConsoleMode(stdout_handle, out_mode);
|
||||
#else
|
||||
#else // POSIX (Linux & Mac)
|
||||
//#if defined(__EMSCRIPTEN__)
|
||||
//// Reading stdin isn't blocking.
|
||||
//int flags = fcntl(0, F_GETFL, 0);
|
||||
//fcntl(0, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
//// Restore the terminal configuration on exit.
|
||||
//on_exit_functions.emplace([flags] { fcntl(0, F_SETFL, flags); });
|
||||
//#endif
|
||||
for (const int signal : {SIGWINCH, SIGTSTP}) {
|
||||
InstallSignalHandler(signal);
|
||||
}
|
||||
@@ -751,16 +677,31 @@ void ScreenInteractive::Uninstall() {
|
||||
// private
|
||||
// NOLINTNEXTLINE
|
||||
void ScreenInteractive::RunOnceBlocking(Component component) {
|
||||
// Set FPS to 60 at most.
|
||||
const auto time_per_frame = std::chrono::microseconds(16666); // 1s / 60fps
|
||||
|
||||
auto time = std::chrono::steady_clock::now();
|
||||
size_t executed_task = internal_->task_runner.ExecutedTasks();
|
||||
|
||||
// Wait for at least one task to execute.
|
||||
while(executed_task == internal_->task_runner.ExecutedTasks() &&
|
||||
!HasQuitted()) {
|
||||
RunOnce(component);
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto delta = now - time;
|
||||
time = now;
|
||||
|
||||
if (delta < time_per_frame) {
|
||||
const auto sleep_duration = time_per_frame - delta;
|
||||
std::this_thread::sleep_for(sleep_duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private
|
||||
void ScreenInteractive::RunOnce(Component component) {
|
||||
component_ = component;
|
||||
AutoReset set_component(&component_, component);
|
||||
ExecuteSignalHandlers();
|
||||
FetchTerminalEvents();
|
||||
|
||||
@@ -773,7 +714,7 @@ void ScreenInteractive::RunOnce(Component component) {
|
||||
}
|
||||
|
||||
ExecuteSignalHandlers();
|
||||
Draw(std::move(component));
|
||||
Draw(component);
|
||||
|
||||
if (selection_data_previous_ != selection_data_) {
|
||||
selection_data_previous_ = selection_data_;
|
||||
@@ -782,8 +723,6 @@ void ScreenInteractive::RunOnce(Component component) {
|
||||
Post(Event::Custom);
|
||||
}
|
||||
}
|
||||
|
||||
component_.reset();
|
||||
}
|
||||
|
||||
// private
|
||||
@@ -793,9 +732,10 @@ void ScreenInteractive::HandleTask(Component component, Task& task) {
|
||||
[&](auto&& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
|
||||
// clang-format off
|
||||
// clang-format off
|
||||
// Handle Event.
|
||||
if constexpr (std::is_same_v<T, Event>) {
|
||||
|
||||
if (arg.is_cursor_position()) {
|
||||
cursor_x_ = arg.cursor_x();
|
||||
cursor_y_ = arg.cursor_y();
|
||||
@@ -1080,7 +1020,87 @@ void ScreenInteractive::Signal(int signal) {
|
||||
}
|
||||
|
||||
void ScreenInteractive::FetchTerminalEvents() {
|
||||
if (!CheckStdinReady(timeout_microseconds)) {
|
||||
#if defined(_WIN32)
|
||||
auto get_input_records = [&] {
|
||||
// Check if there is input in the console.
|
||||
auto console = GetStdHandle(STD_INPUT_HANDLE);
|
||||
DWORD number_of_events = 0;
|
||||
if (!GetNumberOfConsoleInputEvents(console, &number_of_events)) {
|
||||
return;
|
||||
}
|
||||
if (number_of_events <= 0) {
|
||||
// No input, return.
|
||||
return;
|
||||
}
|
||||
// Read the input events.
|
||||
std::vector<INPUT_RECORD> records(number_of_events);
|
||||
DWORD number_of_events_read = 0;
|
||||
if (!ReadConsoleInput(console, records.data(), (DWORD)records.size(),
|
||||
&number_of_events_read)) {
|
||||
return;
|
||||
}
|
||||
records.resize(number_of_events_read);
|
||||
return records;
|
||||
};
|
||||
|
||||
auto records = get_input_records();
|
||||
if (records.size() == 0) {
|
||||
const auto timeout =
|
||||
std::chrono::steady_clock::now() - internal_->last_char_time;
|
||||
const size_t timeout_microseconds =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
|
||||
internal_->terminal_input_parser.Timeout(timeout_microseconds);
|
||||
return;
|
||||
}
|
||||
internal_->last_char_time = std::chrono::steady_clock::now();
|
||||
|
||||
// Convert the input events to FTXUI events.
|
||||
// For each event, we call the terminal input parser to convert it to
|
||||
// Event.
|
||||
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;
|
||||
std::wstring wstring;
|
||||
wstring += key_event.uChar.UnicodeChar;
|
||||
for (auto it : to_string(wstring)) {
|
||||
internal_->terminal_input_parser.Add(it);
|
||||
}
|
||||
} break;
|
||||
case WINDOW_BUFFER_SIZE_EVENT:
|
||||
Post(Event::Special({0}));
|
||||
break;
|
||||
case MENU_EVENT:
|
||||
case FOCUS_EVENT:
|
||||
case MOUSE_EVENT:
|
||||
// TODO(mauve): Implement later.
|
||||
break;
|
||||
}
|
||||
}
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
// Read chars from the terminal.
|
||||
// We configured it to be non blocking.
|
||||
std::array<char, 128> out{};
|
||||
size_t l = read(STDIN_FILENO, out.data(), out.size());
|
||||
if (l == 0) {
|
||||
const auto timeout =
|
||||
std::chrono::steady_clock::now() - internal_->last_char_time;
|
||||
const size_t timeout_microseconds =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
|
||||
internal_->terminal_input_parser.Timeout(timeout_microseconds);
|
||||
return;
|
||||
}
|
||||
internal_->last_char_time = std::chrono::steady_clock::now();
|
||||
|
||||
// Convert the chars to events.
|
||||
for(size_t i = 0; i < l; ++i) {
|
||||
internal_->terminal_input_parser.Add(out[i]);
|
||||
}
|
||||
#else // POSIX (Linux & Mac)
|
||||
if (!CheckStdinReady()) {
|
||||
const auto timeout =
|
||||
std::chrono::steady_clock::now() - internal_->last_char_time;
|
||||
const size_t timeout_ms =
|
||||
@@ -1091,13 +1111,14 @@ void ScreenInteractive::FetchTerminalEvents() {
|
||||
internal_->last_char_time = std::chrono::steady_clock::now();
|
||||
|
||||
// Read chars from the terminal.
|
||||
std::array<char, 128> out;
|
||||
std::array<char, 128> out{};
|
||||
size_t l = read(fileno(stdin), out.data(), out.size());
|
||||
|
||||
// Convert the chars to events.
|
||||
for(size_t i = 0; i < l; ++i) {
|
||||
internal_->terminal_input_parser.Add(out[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ScreenInteractive::PostAnimationTask() {
|
||||
|
||||
Reference in New Issue
Block a user