Figured out that I don't need a special screen, just send events to the component directly with OnEvent. However, you have to component->Add() or call Render(component, []) with it or else it's not considered a child.
parent
e3cff8142c
commit
96ee16e598
@ -1,419 +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 "input_parser.hpp" |
|
||||||
|
|
||||||
#include <cstdint> // for uint32_t |
|
||||||
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Button, Mouse::Motion |
|
||||||
#include <ftxui/component/receiver.hpp> // for SenderImpl, Sender |
|
||||||
#include <map> |
|
||||||
#include <memory> // for unique_ptr, allocator |
|
||||||
#include <utility> // for move |
|
||||||
|
|
||||||
#include "ftxui/component/event.hpp" // for Event |
|
||||||
#include "ftxui/component/task.hpp" // for Task |
|
||||||
|
|
||||||
namespace ftxui { |
|
||||||
|
|
||||||
// NOLINTNEXTLINE
|
|
||||||
const std::map<std::string, std::string> g_uniformize = { |
|
||||||
// Microsoft's terminal uses a different new line character for the return
|
|
||||||
// key. This also happens with linux with the `bind` command:
|
|
||||||
// See https://github.com/ArthurSonzogni/FTXUI/issues/337
|
|
||||||
// Here, we uniformize the new line character to `\n`.
|
|
||||||
{"\r", "\n"}, |
|
||||||
|
|
||||||
// See: https://github.com/ArthurSonzogni/FTXUI/issues/508
|
|
||||||
{std::string({8}), std::string({127})}, |
|
||||||
|
|
||||||
// See: https://github.com/ArthurSonzogni/FTXUI/issues/626
|
|
||||||
//
|
|
||||||
// Depending on the Cursor Key Mode (DECCKM), the terminal sends different
|
|
||||||
// escape sequences:
|
|
||||||
//
|
|
||||||
// Key Normal Application
|
|
||||||
// ----- -------- -----------
|
|
||||||
// Up ESC [ A ESC O A
|
|
||||||
// Down ESC [ B ESC O B
|
|
||||||
// Right ESC [ C ESC O C
|
|
||||||
// Left ESC [ D ESC O D
|
|
||||||
// Home ESC [ H ESC O H
|
|
||||||
// End ESC [ F ESC O F
|
|
||||||
//
|
|
||||||
{"\x1BOA", "\x1B[A"}, // UP
|
|
||||||
{"\x1BOB", "\x1B[B"}, // DOWN
|
|
||||||
{"\x1BOC", "\x1B[C"}, // RIGHT
|
|
||||||
{"\x1BOD", "\x1B[D"}, // LEFT
|
|
||||||
{"\x1BOH", "\x1B[H"}, // HOME
|
|
||||||
{"\x1BOF", "\x1B[F"}, // END
|
|
||||||
|
|
||||||
// Variations around the FN keys.
|
|
||||||
// Internally, we are using:
|
|
||||||
// vt220, xterm-vt200, xterm-xf86-v44, xterm-new, mgt, screen
|
|
||||||
// See: https://invisible-island.net/xterm/xterm-function-keys.html
|
|
||||||
|
|
||||||
// For linux OS console (CTRL+ALT+FN), who do not belong to any
|
|
||||||
// real standard.
|
|
||||||
// See: https://github.com/ArthurSonzogni/FTXUI/issues/685
|
|
||||||
{"\x1B[[A", "\x1BOP"}, // F1
|
|
||||||
{"\x1B[[B", "\x1BOQ"}, // F2
|
|
||||||
{"\x1B[[C", "\x1BOR"}, // F3
|
|
||||||
{"\x1B[[D", "\x1BOS"}, // F4
|
|
||||||
{"\x1B[[E", "\x1B[15~"}, // F5
|
|
||||||
|
|
||||||
// xterm-r5, xterm-r6, rxvt
|
|
||||||
{"\x1B[11~", "\x1BOP"}, // F1
|
|
||||||
{"\x1B[12~", "\x1BOQ"}, // F2
|
|
||||||
{"\x1B[13~", "\x1BOR"}, // F3
|
|
||||||
{"\x1B[14~", "\x1BOS"}, // F4
|
|
||||||
|
|
||||||
// vt100
|
|
||||||
{"\x1BOt", "\x1B[15~"}, // F5
|
|
||||||
{"\x1BOu", "\x1B[17~"}, // F6
|
|
||||||
{"\x1BOv", "\x1B[18~"}, // F7
|
|
||||||
{"\x1BOl", "\x1B[19~"}, // F8
|
|
||||||
{"\x1BOw", "\x1B[20~"}, // F9
|
|
||||||
{"\x1BOx", "\x1B[21~"}, // F10
|
|
||||||
|
|
||||||
// scoansi
|
|
||||||
{"\x1B[M", "\x1BOP"}, // F1
|
|
||||||
{"\x1B[N", "\x1BOQ"}, // F2
|
|
||||||
{"\x1B[O", "\x1BOR"}, // F3
|
|
||||||
{"\x1B[P", "\x1BOS"}, // F4
|
|
||||||
{"\x1B[Q", "\x1B[15~"}, // F5
|
|
||||||
{"\x1B[R", "\x1B[17~"}, // F6
|
|
||||||
{"\x1B[S", "\x1B[18~"}, // F7
|
|
||||||
{"\x1B[T", "\x1B[19~"}, // F8
|
|
||||||
{"\x1B[U", "\x1B[20~"}, // F9
|
|
||||||
{"\x1B[V", "\x1B[21~"}, // F10
|
|
||||||
{"\x1B[W", "\x1B[23~"}, // F11
|
|
||||||
{"\x1B[X", "\x1B[24~"}, // F12
|
|
||||||
}; |
|
||||||
|
|
||||||
TerminalInputParser::TerminalInputParser(Sender<Task> out) |
|
||||||
: out_(std::move(out)) {} |
|
||||||
|
|
||||||
void TerminalInputParser::Timeout(int time) { |
|
||||||
timeout_ += time; |
|
||||||
const int timeout_threshold = 50; |
|
||||||
if (timeout_ < timeout_threshold) { |
|
||||||
return; |
|
||||||
} |
|
||||||
timeout_ = 0; |
|
||||||
if (!pending_.empty()) { |
|
||||||
Send(SPECIAL); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void TerminalInputParser::Add(char c) { |
|
||||||
pending_ += c; |
|
||||||
timeout_ = 0; |
|
||||||
position_ = -1; |
|
||||||
Send(Parse()); |
|
||||||
} |
|
||||||
|
|
||||||
unsigned char TerminalInputParser::Current() { |
|
||||||
return pending_[position_]; |
|
||||||
} |
|
||||||
|
|
||||||
bool TerminalInputParser::Eat() { |
|
||||||
position_++; |
|
||||||
return position_ < static_cast<int>(pending_.size()); |
|
||||||
} |
|
||||||
|
|
||||||
void TerminalInputParser::Send(TerminalInputParser::Output output) { |
|
||||||
switch (output.type) { |
|
||||||
case UNCOMPLETED: |
|
||||||
return; |
|
||||||
|
|
||||||
case DROP: |
|
||||||
pending_.clear(); |
|
||||||
return; |
|
||||||
|
|
||||||
case CHARACTER: |
|
||||||
out_->Send(Event::Character(std::move(pending_))); |
|
||||||
pending_.clear(); |
|
||||||
return; |
|
||||||
|
|
||||||
case SPECIAL: { |
|
||||||
auto it = g_uniformize.find(pending_); |
|
||||||
if (it != g_uniformize.end()) { |
|
||||||
pending_ = it->second; |
|
||||||
} |
|
||||||
out_->Send(Event::Special(std::move(pending_))); |
|
||||||
pending_.clear(); |
|
||||||
} |
|
||||||
return; |
|
||||||
|
|
||||||
case MOUSE: |
|
||||||
out_->Send(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT
|
|
||||||
pending_.clear(); |
|
||||||
return; |
|
||||||
|
|
||||||
case CURSOR_REPORTING: |
|
||||||
out_->Send(Event::CursorReporting(std::move(pending_), // NOLINT
|
|
||||||
output.cursor.x, // NOLINT
|
|
||||||
output.cursor.y)); // NOLINT
|
|
||||||
pending_.clear(); |
|
||||||
return; |
|
||||||
} |
|
||||||
// NOT_REACHED().
|
|
||||||
} |
|
||||||
|
|
||||||
TerminalInputParser::Output TerminalInputParser::Parse() { |
|
||||||
if (!Eat()) { |
|
||||||
return UNCOMPLETED; |
|
||||||
} |
|
||||||
|
|
||||||
switch (Current()) { |
|
||||||
case 24: // CAN NOLINT
|
|
||||||
case 26: // SUB NOLINT
|
|
||||||
return DROP; |
|
||||||
|
|
||||||
case '\x1B': |
|
||||||
return ParseESC(); |
|
||||||
default: |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
if (Current() < 32) { // C0 NOLINT
|
|
||||||
return SPECIAL; |
|
||||||
} |
|
||||||
|
|
||||||
if (Current() == 127) { // Delete // NOLINT
|
|
||||||
return SPECIAL; |
|
||||||
} |
|
||||||
|
|
||||||
return ParseUTF8(); |
|
||||||
} |
|
||||||
|
|
||||||
// Code point <-> UTF-8 conversion
|
|
||||||
//
|
|
||||||
// ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
|
|
||||||
// ┃Byte 1 ┃Byte 2 ┃Byte 3 ┃Byte 4 ┃
|
|
||||||
// ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
|
|
||||||
// │0xxxxxxx│ │ │ │
|
|
||||||
// ├────────┼────────┼────────┼────────┤
|
|
||||||
// │110xxxxx│10xxxxxx│ │ │
|
|
||||||
// ├────────┼────────┼────────┼────────┤
|
|
||||||
// │1110xxxx│10xxxxxx│10xxxxxx│ │
|
|
||||||
// ├────────┼────────┼────────┼────────┤
|
|
||||||
// │11110xxx│10xxxxxx│10xxxxxx│10xxxxxx│
|
|
||||||
// └────────┴────────┴────────┴────────┘
|
|
||||||
//
|
|
||||||
// Then some sequences are illegal if it exist a shorter representation of the
|
|
||||||
// same codepoint.
|
|
||||||
TerminalInputParser::Output TerminalInputParser::ParseUTF8() { |
|
||||||
auto head = Current(); |
|
||||||
unsigned char selector = 0b1000'0000; // NOLINT
|
|
||||||
|
|
||||||
// The non code-point part of the first byte.
|
|
||||||
unsigned char mask = selector; |
|
||||||
|
|
||||||
// Find the first zero in the first byte.
|
|
||||||
unsigned int first_zero = 8; // NOLINT
|
|
||||||
for (unsigned int i = 0; i < 8; ++i) { // NOLINT
|
|
||||||
mask |= selector; |
|
||||||
if (!(head & selector)) { |
|
||||||
first_zero = i; |
|
||||||
break; |
|
||||||
} |
|
||||||
selector >>= 1U; |
|
||||||
} |
|
||||||
|
|
||||||
// Accumulate the value of the first byte.
|
|
||||||
auto value = uint32_t(head & ~mask); // NOLINT
|
|
||||||
|
|
||||||
// Invalid UTF8, with more than 5 bytes.
|
|
||||||
const unsigned int max_utf8_bytes = 5; |
|
||||||
if (first_zero == 1 || first_zero >= max_utf8_bytes) { |
|
||||||
return DROP; |
|
||||||
} |
|
||||||
|
|
||||||
// Multi byte UTF-8.
|
|
||||||
for (unsigned int i = 2; i <= first_zero; ++i) { |
|
||||||
if (!Eat()) { |
|
||||||
return UNCOMPLETED; |
|
||||||
} |
|
||||||
|
|
||||||
// Invalid continuation byte.
|
|
||||||
head = Current(); |
|
||||||
if ((head & 0b1100'0000) != 0b1000'0000) { // NOLINT
|
|
||||||
return DROP; |
|
||||||
} |
|
||||||
value <<= 6; // NOLINT
|
|
||||||
value += head & 0b0011'1111; // NOLINT
|
|
||||||
} |
|
||||||
|
|
||||||
// Check for overlong UTF8 encoding.
|
|
||||||
int extra_byte = 0; |
|
||||||
if (value <= 0b000'0000'0111'1111) { // NOLINT
|
|
||||||
extra_byte = 0; // NOLINT
|
|
||||||
} else if (value <= 0b000'0111'1111'1111) { // NOLINT
|
|
||||||
extra_byte = 1; // NOLINT
|
|
||||||
} else if (value <= 0b1111'1111'1111'1111) { // NOLINT
|
|
||||||
extra_byte = 2; // NOLINT
|
|
||||||
} else if (value <= 0b1'0000'1111'1111'1111'1111) { // NOLINT
|
|
||||||
extra_byte = 3; // NOLINT
|
|
||||||
} else { // NOLINT
|
|
||||||
return DROP; |
|
||||||
} |
|
||||||
|
|
||||||
if (extra_byte != position_) { |
|
||||||
return DROP; |
|
||||||
} |
|
||||||
|
|
||||||
return CHARACTER; |
|
||||||
} |
|
||||||
|
|
||||||
TerminalInputParser::Output TerminalInputParser::ParseESC() { |
|
||||||
if (!Eat()) { |
|
||||||
return UNCOMPLETED; |
|
||||||
} |
|
||||||
switch (Current()) { |
|
||||||
case 'P': |
|
||||||
return ParseDCS(); |
|
||||||
case '[': |
|
||||||
return ParseCSI(); |
|
||||||
case ']': |
|
||||||
return ParseOSC(); |
|
||||||
default: |
|
||||||
if (!Eat()) { |
|
||||||
return UNCOMPLETED; |
|
||||||
} else { |
|
||||||
return SPECIAL; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
TerminalInputParser::Output TerminalInputParser::ParseDCS() { |
|
||||||
// Parse until the string terminator ST.
|
|
||||||
while (true) { |
|
||||||
if (!Eat()) { |
|
||||||
return UNCOMPLETED; |
|
||||||
} |
|
||||||
|
|
||||||
if (Current() != '\x1B') { |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
if (!Eat()) { |
|
||||||
return UNCOMPLETED; |
|
||||||
} |
|
||||||
|
|
||||||
if (Current() != '\\') { |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
return SPECIAL; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
TerminalInputParser::Output TerminalInputParser::ParseCSI() { |
|
||||||
bool altered = false; |
|
||||||
int argument = 0; |
|
||||||
std::vector<int> arguments; |
|
||||||
while (true) { |
|
||||||
if (!Eat()) { |
|
||||||
return UNCOMPLETED; |
|
||||||
} |
|
||||||
|
|
||||||
if (Current() == '<') { |
|
||||||
altered = true; |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
if (Current() >= '0' && Current() <= '9') { |
|
||||||
argument *= 10; // NOLINT
|
|
||||||
argument += Current() - '0'; |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
if (Current() == ';') { |
|
||||||
arguments.push_back(argument); |
|
||||||
argument = 0; |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
// CSI is terminated by a character in the range 0x40–0x7E
|
|
||||||
// (ASCII @A–Z[\]^_`a–z{|}~),
|
|
||||||
if (Current() >= '@' && Current() <= '~' && |
|
||||||
// Note: I don't remember why we exclude '<'
|
|
||||||
Current() != '<' && |
|
||||||
// To handle F1-F4, we exclude '['.
|
|
||||||
Current() != '[') { |
|
||||||
arguments.push_back(argument); |
|
||||||
argument = 0; // NOLINT
|
|
||||||
|
|
||||||
switch (Current()) { |
|
||||||
case 'M': |
|
||||||
return ParseMouse(altered, true, std::move(arguments)); |
|
||||||
case 'm': |
|
||||||
return ParseMouse(altered, false, std::move(arguments)); |
|
||||||
case 'R': |
|
||||||
return ParseCursorReporting(std::move(arguments)); |
|
||||||
default: |
|
||||||
return SPECIAL; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Invalid ESC in CSI.
|
|
||||||
if (Current() == '\x1B') { |
|
||||||
return SPECIAL; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
TerminalInputParser::Output TerminalInputParser::ParseOSC() { |
|
||||||
// Parse until the string terminator ST.
|
|
||||||
while (true) { |
|
||||||
if (!Eat()) { |
|
||||||
return UNCOMPLETED; |
|
||||||
} |
|
||||||
if (Current() != '\x1B') { |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (!Eat()) { |
|
||||||
return UNCOMPLETED; |
|
||||||
} |
|
||||||
if (Current() != '\\') { |
|
||||||
continue; |
|
||||||
} |
|
||||||
return SPECIAL; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
TerminalInputParser::Output TerminalInputParser::ParseMouse( // NOLINT
|
|
||||||
bool altered, |
|
||||||
bool pressed, |
|
||||||
std::vector<int> arguments) { |
|
||||||
if (arguments.size() != 3) { |
|
||||||
return SPECIAL; |
|
||||||
} |
|
||||||
|
|
||||||
(void)altered; |
|
||||||
|
|
||||||
Output output(MOUSE); |
|
||||||
output.mouse.button = Mouse::Button((arguments[0] & 3) + // NOLINT
|
|
||||||
((arguments[0] & 64) >> 4)); // NOLINT
|
|
||||||
output.mouse.motion = Mouse::Motion(pressed); // NOLINT
|
|
||||||
output.mouse.shift = bool(arguments[0] & 4); // NOLINT
|
|
||||||
output.mouse.meta = bool(arguments[0] & 8); // NOLINT
|
|
||||||
output.mouse.x = arguments[1]; // NOLINT
|
|
||||||
output.mouse.y = arguments[2]; // NOLINT
|
|
||||||
return output; |
|
||||||
} |
|
||||||
|
|
||||||
// NOLINTNEXTLINE
|
|
||||||
TerminalInputParser::Output TerminalInputParser::ParseCursorReporting( |
|
||||||
std::vector<int> arguments) { |
|
||||||
if (arguments.size() != 2) { |
|
||||||
return SPECIAL; |
|
||||||
} |
|
||||||
Output output(CURSOR_REPORTING); |
|
||||||
output.cursor.y = arguments[0]; // NOLINT
|
|
||||||
output.cursor.x = arguments[1]; // NOLINT
|
|
||||||
return output; |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace ftxui
|
|
@ -1,72 +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.
|
|
||||||
#ifndef FTXUI_COMPONENT_TERMINAL_INPUT_PARSER |
|
||||||
#define FTXUI_COMPONENT_TERMINAL_INPUT_PARSER |
|
||||||
|
|
||||||
#include <memory> // for unique_ptr |
|
||||||
#include <string> // for string |
|
||||||
#include <vector> // for vector |
|
||||||
|
|
||||||
#include "ftxui/component/event.hpp" // for Event (ptr only) |
|
||||||
#include "ftxui/component/mouse.hpp" // for Mouse |
|
||||||
#include "ftxui/component/receiver.hpp" // for Sender |
|
||||||
#include "ftxui/component/task.hpp" // for Task |
|
||||||
|
|
||||||
namespace ftxui { |
|
||||||
struct Event; |
|
||||||
|
|
||||||
// Parse a sequence of |char| accross |time|. Produces |Event|.
|
|
||||||
class TerminalInputParser { |
|
||||||
public: |
|
||||||
TerminalInputParser(Sender<Task> out); |
|
||||||
void Timeout(int time); |
|
||||||
void Add(char c); |
|
||||||
|
|
||||||
private: |
|
||||||
unsigned char Current(); |
|
||||||
bool Eat(); |
|
||||||
|
|
||||||
enum Type { |
|
||||||
UNCOMPLETED, |
|
||||||
DROP, |
|
||||||
CHARACTER, |
|
||||||
SPECIAL, |
|
||||||
MOUSE, |
|
||||||
CURSOR_REPORTING, |
|
||||||
}; |
|
||||||
|
|
||||||
struct CursorReporting { |
|
||||||
int x; |
|
||||||
int y; |
|
||||||
}; |
|
||||||
|
|
||||||
struct Output { |
|
||||||
Type type; |
|
||||||
union { |
|
||||||
Mouse mouse; |
|
||||||
CursorReporting cursor; |
|
||||||
}; |
|
||||||
|
|
||||||
Output(Type t) : type(t) {} |
|
||||||
}; |
|
||||||
|
|
||||||
void Send(Output output); |
|
||||||
Output Parse(); |
|
||||||
Output ParseUTF8(); |
|
||||||
Output ParseESC(); |
|
||||||
Output ParseDCS(); |
|
||||||
Output ParseCSI(); |
|
||||||
Output ParseOSC(); |
|
||||||
Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments); |
|
||||||
Output ParseCursorReporting(std::vector<int> arguments); |
|
||||||
|
|
||||||
Sender<Task> out_; |
|
||||||
int position_ = -1; |
|
||||||
int timeout_ = 0; |
|
||||||
std::string pending_; |
|
||||||
}; |
|
||||||
|
|
||||||
} // namespace ftxui
|
|
||||||
|
|
||||||
#endif /* end of include guard: FTXUI_COMPONENT_TERMINAL_INPUT_PARSER */ |
|
@ -1,460 +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 <cassert> |
|
||||||
#include <algorithm> // for copy, max, min |
|
||||||
#include <array> // for array |
|
||||||
#include <chrono> // for operator-, milliseconds, operator>=, duration, common_type<>::type, time_point |
|
||||||
#include <cstdio> // for fileno, stdin |
|
||||||
#include <ftxui/component/task.hpp> // for Task, Closure, AnimationTask |
|
||||||
#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 |
|
||||||
#include <stack> // for stack |
|
||||||
#include <thread> // for thread, sleep_for |
|
||||||
#include <tuple> // for _Swallow_assign, ignore |
|
||||||
#include <type_traits> // for decay_t |
|
||||||
#include <utility> // for move, swap |
|
||||||
#include <variant> // for visit, variant |
|
||||||
#include <vector> // for vector |
|
||||||
|
|
||||||
#include <ftxui/component/animation.hpp> // for TimePoint, Clock, Duration, Params, RequestAnimationFrame |
|
||||||
#include <ftxui/component/captured_mouse.hpp> // for CapturedMouse, CapturedMouseInterface |
|
||||||
#include <ftxui/component/component_base.hpp> // for ComponentBase |
|
||||||
#include <ftxui/component/event.hpp> // for Event |
|
||||||
#include <ftxui/component/loop.hpp> // for Loop |
|
||||||
#include <ftxui/component/receiver.hpp> // for ReceiverImpl, Sender, MakeReceiver, SenderImpl, Receiver |
|
||||||
#include <ftxui/dom/node.hpp> // for Node, Render |
|
||||||
#include <ftxui/dom/requirement.hpp> // for Requirement |
|
||||||
#include <ftxui/screen/terminal.hpp> // for Dimensions, Size |
|
||||||
#include <fmt/core.h> |
|
||||||
#include "sfml_screen.hpp" |
|
||||||
|
|
||||||
// Quick exit is missing in standard CLang headers
|
|
||||||
#if defined(__clang__) && defined(__APPLE__) |
|
||||||
#define quick_exit(a) exit(a) |
|
||||||
#endif |
|
||||||
|
|
||||||
/*
|
|
||||||
namespace ftxui { |
|
||||||
namespace animation { |
|
||||||
void RequestAnimationFrame() { |
|
||||||
auto* screen = SFMLScreen::Active(); |
|
||||||
if (screen) { |
|
||||||
screen->RequestAnimationFrame(); |
|
||||||
} |
|
||||||
} |
|
||||||
} // namespace animation
|
|
||||||
} |
|
||||||
*/ |
|
||||||
|
|
||||||
namespace { |
|
||||||
SFMLScreen* g_active_screen = nullptr; // NOLINT
|
|
||||||
|
|
||||||
void Flush() { |
|
||||||
// Emscripten doesn't implement flush. We interpret zero as flush.
|
|
||||||
std::cout << '\0' << std::flush; |
|
||||||
} |
|
||||||
|
|
||||||
constexpr int timeout_milliseconds = 20; |
|
||||||
[[maybe_unused]] constexpr int timeout_microseconds = |
|
||||||
timeout_milliseconds * 1000; |
|
||||||
|
|
||||||
std::stack<Closure> on_exit_functions; // NOLINT
|
|
||||||
void OnExit() { |
|
||||||
while (!on_exit_functions.empty()) { |
|
||||||
on_exit_functions.top()(); |
|
||||||
on_exit_functions.pop(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
/*
|
|
||||||
* bruxisma: std::thread has some special magic built in so that if you pass in a std::reference_wrapper it'll unpack it and treat it as a reference. So you can pass it as a reference with `std::ref` for mutable references, and `st***ref` for constant references |
|
||||||
* |
|
||||||
* ZED: This is al Windows specific code that needs to be replaced |
|
||||||
* with SFML's events system, so the quit here will die. |
|
||||||
*/ |
|
||||||
void EventListener(Sender<Task> out) { |
|
||||||
using namespace std::chrono_literals; |
|
||||||
while (true) { |
|
||||||
// get the sfml window inputs
|
|
||||||
fmt::println("WAITING FOR EVENT"); |
|
||||||
std::this_thread::sleep_for(1000ms); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* ZED: This can stay but it doesn't need to be a thread, make it a function |
|
||||||
* that is called in the event loop. |
|
||||||
*/ |
|
||||||
void AnimationListener(Sender<Task> out) { |
|
||||||
// Animation at around 60fps.
|
|
||||||
const auto time_delta = std::chrono::milliseconds(15); |
|
||||||
while (true) { |
|
||||||
out->Send(ftxui::AnimationTask()); |
|
||||||
std::this_thread::sleep_for(time_delta); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
SFMLScreen::SFMLScreen(int dimx, |
|
||||||
int dimy, |
|
||||||
Dimension dimension, |
|
||||||
bool use_alternative_screen) |
|
||||||
: Screen(dimx, dimy), |
|
||||||
dimension_(dimension), |
|
||||||
use_alternative_screen_(use_alternative_screen) { |
|
||||||
task_receiver_ = ftxui::MakeReceiver<Task>(); |
|
||||||
} |
|
||||||
|
|
||||||
// static
|
|
||||||
SFMLScreen SFMLScreen::FixedSize(int dimx, int dimy) { |
|
||||||
return { |
|
||||||
dimx, |
|
||||||
dimy, |
|
||||||
Dimension::Fixed, |
|
||||||
false, |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
// static
|
|
||||||
SFMLScreen SFMLScreen::Fullscreen() { |
|
||||||
return { |
|
||||||
0, |
|
||||||
0, |
|
||||||
Dimension::Fullscreen, |
|
||||||
true, |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
// static
|
|
||||||
SFMLScreen SFMLScreen::TerminalOutput() { |
|
||||||
return { |
|
||||||
0, |
|
||||||
0, |
|
||||||
Dimension::TerminalOutput, |
|
||||||
false, |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
// static
|
|
||||||
SFMLScreen SFMLScreen::FitComponent() { |
|
||||||
return { |
|
||||||
0, |
|
||||||
0, |
|
||||||
Dimension::FitComponent, |
|
||||||
false, |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
/// @ingroup component
|
|
||||||
/// @brief Set whether mouse is tracked and events reported.
|
|
||||||
/// called outside of the main loop. E.g `SFMLScreen::Loop(...)`.
|
|
||||||
/// @param enable Whether to enable mouse event tracking.
|
|
||||||
/// @note This muse be called outside of the main loop. E.g. before calling
|
|
||||||
/// `SFMLScreen::Loop`.
|
|
||||||
/// @note Mouse tracking is enabled by default.
|
|
||||||
/// @note Mouse tracking is only supported on terminals that supports it.
|
|
||||||
///
|
|
||||||
/// ### Example
|
|
||||||
///
|
|
||||||
/// ```cpp
|
|
||||||
/// auto screen = SFMLScreen::TerminalOutput();
|
|
||||||
/// screen.TrackMouse(false);
|
|
||||||
/// screen.Loop(component);
|
|
||||||
/// ```
|
|
||||||
void SFMLScreen::TrackMouse(bool enable) { |
|
||||||
track_mouse_ = enable; |
|
||||||
} |
|
||||||
|
|
||||||
/// @brief Add a task to the main loop.
|
|
||||||
/// It will be executed later, after every other scheduled tasks.
|
|
||||||
/// @ingroup component
|
|
||||||
void SFMLScreen::Post(Task task) { |
|
||||||
// Task/Events sent toward inactive screen or screen waiting to become
|
|
||||||
// inactive are dropped.
|
|
||||||
if (!task_sender_) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
task_sender_->Send(std::move(task)); |
|
||||||
} |
|
||||||
|
|
||||||
/// @brief Add an event to the main loop.
|
|
||||||
/// It will be executed later, after every other scheduled events.
|
|
||||||
/// @ingroup component
|
|
||||||
void SFMLScreen::PostEvent(Event event) { |
|
||||||
Post(event); |
|
||||||
} |
|
||||||
|
|
||||||
/// @brief Add a task to draw the screen one more time, until all the animations
|
|
||||||
/// are done.
|
|
||||||
void SFMLScreen::RequestAnimationFrame() { |
|
||||||
if (animation_requested_) { |
|
||||||
return; |
|
||||||
} |
|
||||||
animation_requested_ = true; |
|
||||||
auto now = ftxui::animation::Clock::now(); |
|
||||||
const auto time_histeresis = std::chrono::milliseconds(33); |
|
||||||
if (now - previous_animation_time_ >= time_histeresis) { |
|
||||||
previous_animation_time_ = now; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// @brief Return whether the main loop has been quit.
|
|
||||||
/// @ingroup component
|
|
||||||
bool SFMLScreen::HasQuitted() { |
|
||||||
return task_receiver_->HasQuitted(); |
|
||||||
} |
|
||||||
|
|
||||||
// private
|
|
||||||
void SFMLScreen::PreMain() { |
|
||||||
// Suspend previously active screen:
|
|
||||||
if (g_active_screen) { |
|
||||||
std::swap(suspended_screen_, g_active_screen); |
|
||||||
// Reset cursor position to the top of the screen and clear the screen.
|
|
||||||
suspended_screen_->ResetCursorPosition(); |
|
||||||
std::cout << suspended_screen_->ResetPosition(/*clear=*/true); |
|
||||||
suspended_screen_->dimx_ = 0; |
|
||||||
suspended_screen_->dimy_ = 0; |
|
||||||
|
|
||||||
// Reset dimensions to force drawing the screen again next time:
|
|
||||||
suspended_screen_->Uninstall(); |
|
||||||
} |
|
||||||
|
|
||||||
// This screen is now active:
|
|
||||||
g_active_screen = this; |
|
||||||
g_active_screen->Install(); |
|
||||||
|
|
||||||
previous_animation_time_ = ftxui::animation::Clock::now(); |
|
||||||
} |
|
||||||
|
|
||||||
// private
|
|
||||||
void SFMLScreen::PostMain() { |
|
||||||
// Put cursor position at the end of the drawing.
|
|
||||||
ResetCursorPosition(); |
|
||||||
|
|
||||||
g_active_screen = nullptr; |
|
||||||
|
|
||||||
// Restore suspended screen.
|
|
||||||
if (suspended_screen_) { |
|
||||||
// Clear screen, and put the cursor at the beginning of the drawing.
|
|
||||||
std::cout << ResetPosition(/*clear=*/true); |
|
||||||
dimx_ = 0; |
|
||||||
dimy_ = 0; |
|
||||||
Uninstall(); |
|
||||||
std::swap(g_active_screen, suspended_screen_); |
|
||||||
g_active_screen->Install(); |
|
||||||
} else { |
|
||||||
Uninstall(); |
|
||||||
|
|
||||||
std::cout << '\r'; |
|
||||||
// On final exit, keep the current drawing and reset cursor position one
|
|
||||||
// line after it.
|
|
||||||
if (!use_alternative_screen_) { |
|
||||||
std::cout << std::endl; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// @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.
|
|
||||||
Closure SFMLScreen::WithRestoredIO(Closure fn) { // NOLINT
|
|
||||||
return [this, fn] { |
|
||||||
Uninstall(); |
|
||||||
fn(); |
|
||||||
Install(); |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
/// @brief Return the currently active screen, or null if none.
|
|
||||||
// static
|
|
||||||
SFMLScreen* SFMLScreen::Active() { |
|
||||||
return g_active_screen; |
|
||||||
} |
|
||||||
|
|
||||||
// private
|
|
||||||
void SFMLScreen::Install() { |
|
||||||
frame_valid_ = false; |
|
||||||
|
|
||||||
// After uninstalling the new configuration, flush it to the terminal to
|
|
||||||
// ensure it is fully applied:
|
|
||||||
on_exit_functions.push([] { Flush(); }); |
|
||||||
|
|
||||||
on_exit_functions.push([this] { ExitLoopClosure()(); }); |
|
||||||
|
|
||||||
// After installing the new configuration, flush it to the terminal to
|
|
||||||
// ensure it is fully applied:
|
|
||||||
Flush(); |
|
||||||
|
|
||||||
task_sender_ = task_receiver_->MakeSender(); |
|
||||||
event_listener_ = |
|
||||||
std::thread(&EventListener, task_receiver_->MakeSender()); |
|
||||||
animation_listener_ = |
|
||||||
std::thread(&AnimationListener, task_receiver_->MakeSender()); |
|
||||||
} |
|
||||||
|
|
||||||
// private
|
|
||||||
void SFMLScreen::Uninstall() { |
|
||||||
ExitNow(); |
|
||||||
event_listener_.join(); |
|
||||||
animation_listener_.join(); |
|
||||||
OnExit(); |
|
||||||
} |
|
||||||
|
|
||||||
// private
|
|
||||||
// NOLINTNEXTLINE
|
|
||||||
void SFMLScreen::RunOnceBlocking(Component component) { |
|
||||||
Task task; |
|
||||||
if (task_receiver_->Receive(&task)) { |
|
||||||
HandleTask(component, task); |
|
||||||
} |
|
||||||
RunOnce(component); |
|
||||||
} |
|
||||||
|
|
||||||
// private
|
|
||||||
void SFMLScreen::RunOnce(Component component) { |
|
||||||
Task task; |
|
||||||
while (task_receiver_->ReceiveNonBlocking(&task)) { |
|
||||||
HandleTask(component, task); |
|
||||||
} |
|
||||||
Draw(std::move(component)); |
|
||||||
} |
|
||||||
|
|
||||||
// private
|
|
||||||
void SFMLScreen::HandleTask(Component component, Task& task) { |
|
||||||
// clang-format off
|
|
||||||
std::visit([&](auto&& arg) { |
|
||||||
using T = std::decay_t<decltype(arg)>; |
|
||||||
|
|
||||||
// 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(); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if (arg.is_mouse()) { |
|
||||||
arg.mouse().x -= cursor_x_; |
|
||||||
arg.mouse().y -= cursor_y_; |
|
||||||
} |
|
||||||
|
|
||||||
// ZED: arg.screen_ = this;
|
|
||||||
component->OnEvent(arg); |
|
||||||
frame_valid_ = false; |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Handle callback
|
|
||||||
if constexpr (std::is_same_v<T, Closure>) { |
|
||||||
arg(); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Handle Animation
|
|
||||||
if constexpr (std::is_same_v<T, ftxui::AnimationTask>) { |
|
||||||
if (!animation_requested_) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
animation_requested_ = false; |
|
||||||
const ftxui::animation::TimePoint now = ftxui::animation::Clock::now(); |
|
||||||
const ftxui::animation::Duration delta = now - previous_animation_time_; |
|
||||||
previous_animation_time_ = now; |
|
||||||
|
|
||||||
ftxui::animation::Params params(delta); |
|
||||||
component->OnAnimation(params); |
|
||||||
frame_valid_ = false; |
|
||||||
return; |
|
||||||
} |
|
||||||
}, |
|
||||||
task); |
|
||||||
// clang-format on
|
|
||||||
} |
|
||||||
|
|
||||||
// private
|
|
||||||
// NOLINTNEXTLINE
|
|
||||||
void SFMLScreen::Draw(Component component) { |
|
||||||
if (frame_valid_) { |
|
||||||
return; |
|
||||||
} |
|
||||||
auto document = component->Render(); |
|
||||||
int dimx = 0; |
|
||||||
int dimy = 0; |
|
||||||
// ZED: replace this
|
|
||||||
// auto terminal = Terminal::Size();
|
|
||||||
document->ComputeRequirement(); |
|
||||||
switch (dimension_) { |
|
||||||
case Dimension::Fixed: |
|
||||||
dimx = dimx_; |
|
||||||
dimy = dimy_; |
|
||||||
break; |
|
||||||
case Dimension::TerminalOutput: |
|
||||||
assert(false && "NOT IMPLEMENTED!"); |
|
||||||
// dimx = terminal.dimx;
|
|
||||||
// dimy = document->requirement().min_y;
|
|
||||||
break; |
|
||||||
case Dimension::Fullscreen: |
|
||||||
assert(false && "NOT IMPLEMENTED!"); |
|
||||||
// dimx = terminal.dimx;
|
|
||||||
// dimy = terminal.dimy;
|
|
||||||
break; |
|
||||||
case Dimension::FitComponent: |
|
||||||
assert(false && "NOT IMPLEMENTED!"); |
|
||||||
// dimx = std::min(document->requirement().min_x, terminal.dimx);
|
|
||||||
// dimy = std::min(document->requirement().min_y, terminal.dimy);
|
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
const bool resized = (dimx != dimx_) || (dimy != dimy_); |
|
||||||
ResetCursorPosition(); |
|
||||||
std::cout << ResetPosition(/*clear=*/resized); |
|
||||||
|
|
||||||
// Resize the screen if needed.
|
|
||||||
if (resized) { |
|
||||||
dimx_ = dimx; |
|
||||||
dimy_ = dimy; |
|
||||||
pixels_ = std::vector<std::vector<ftxui::Pixel>>(dimy, std::vector<ftxui::Pixel>(dimx)); |
|
||||||
cursor_.x = dimx_ - 1; |
|
||||||
cursor_.y = dimy_ - 1; |
|
||||||
} |
|
||||||
|
|
||||||
// ZED: I removed a bunch of terminal stuff but probably need to bring back
|
|
||||||
// resizing?
|
|
||||||
//
|
|
||||||
previous_frame_resized_ = resized; |
|
||||||
|
|
||||||
Render(*this, document); |
|
||||||
|
|
||||||
std::cout << ToString() << set_cursor_position; |
|
||||||
Flush(); |
|
||||||
Clear(); |
|
||||||
frame_valid_ = true; |
|
||||||
} |
|
||||||
|
|
||||||
// private
|
|
||||||
void SFMLScreen::ResetCursorPosition() { |
|
||||||
std::cout << reset_cursor_position; |
|
||||||
reset_cursor_position = ""; |
|
||||||
} |
|
||||||
|
|
||||||
/// @brief Return a function to exit the main loop.
|
|
||||||
/// @ingroup component
|
|
||||||
Closure SFMLScreen::ExitLoopClosure() { |
|
||||||
return [this] { Exit(); }; |
|
||||||
} |
|
||||||
|
|
||||||
/// @brief Exit the main loop.
|
|
||||||
/// @ingroup component
|
|
||||||
void SFMLScreen::Exit() { |
|
||||||
Post([this] { ExitNow(); }); |
|
||||||
} |
|
||||||
|
|
||||||
// private:
|
|
||||||
void SFMLScreen::ExitNow() { |
|
||||||
task_sender_.reset(); |
|
||||||
} |
|
@ -1,113 +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.
|
|
||||||
#ifndef FTXUI_COMPONENT_SCREEN_INTERACTIVE_HPP |
|
||||||
#define FTXUI_COMPONENT_SCREEN_INTERACTIVE_HPP |
|
||||||
|
|
||||||
#include <atomic> // for atomic |
|
||||||
#include <ftxui/component/receiver.hpp> // for Receiver, Sender |
|
||||||
#include <functional> // for function |
|
||||||
#include <memory> // for shared_ptr |
|
||||||
#include <string> // for string |
|
||||||
#include <thread> // for thread |
|
||||||
#include <variant> // for variant |
|
||||||
|
|
||||||
#include <ftxui/component/animation.hpp> // for TimePoint |
|
||||||
#include <ftxui/component/captured_mouse.hpp> // for CapturedMouse |
|
||||||
#include <ftxui/component/event.hpp> // for Event |
|
||||||
#include <ftxui/component/task.hpp> // for Task, Closure |
|
||||||
#include <ftxui/screen/screen.hpp> // for Screen |
|
||||||
|
|
||||||
using ftxui::Component, ftxui::Task, ftxui::Closure, ftxui::Event, ftxui::Sender, ftxui::Receiver; |
|
||||||
|
|
||||||
namespace ftxui { |
|
||||||
class ComponentBase; |
|
||||||
struct Event; |
|
||||||
|
|
||||||
using Component = std::shared_ptr<ComponentBase>; |
|
||||||
class SFMLScreenPrivate; |
|
||||||
} |
|
||||||
|
|
||||||
class SFMLScreen : public ftxui::Screen { |
|
||||||
public: |
|
||||||
// Constructors:
|
|
||||||
static SFMLScreen FixedSize(int dimx, int dimy); |
|
||||||
static SFMLScreen Fullscreen(); |
|
||||||
static SFMLScreen FitComponent(); |
|
||||||
static SFMLScreen TerminalOutput(); |
|
||||||
|
|
||||||
// Options. Must be called before Loop().
|
|
||||||
void TrackMouse(bool enable = true); |
|
||||||
|
|
||||||
// Return the currently active screen, nullptr if none.
|
|
||||||
static SFMLScreen* Active(); |
|
||||||
|
|
||||||
// Start/Stop the main loop.
|
|
||||||
void Exit(); |
|
||||||
Closure ExitLoopClosure(); |
|
||||||
|
|
||||||
// Post tasks to be executed by the loop.
|
|
||||||
void Post(Task task); |
|
||||||
void PostEvent(Event event); |
|
||||||
void RequestAnimationFrame(); |
|
||||||
|
|
||||||
// Decorate a function. The outputted one will execute similarly to the
|
|
||||||
// inputted one, but with the currently active screen terminal hooks
|
|
||||||
// temporarily uninstalled.
|
|
||||||
ftxui::Closure WithRestoredIO(ftxui::Closure); |
|
||||||
|
|
||||||
void ExitNow(); |
|
||||||
|
|
||||||
void Install(); |
|
||||||
void Uninstall(); |
|
||||||
|
|
||||||
void PreMain(); |
|
||||||
void PostMain(); |
|
||||||
|
|
||||||
bool HasQuitted(); |
|
||||||
void RunOnce(Component component); |
|
||||||
void RunOnceBlocking(Component component); |
|
||||||
|
|
||||||
void HandleTask(Component component, Task& task); |
|
||||||
void Draw(Component component); |
|
||||||
void ResetCursorPosition(); |
|
||||||
|
|
||||||
SFMLScreen* suspended_screen_ = nullptr; |
|
||||||
enum class Dimension { |
|
||||||
FitComponent, |
|
||||||
Fixed, |
|
||||||
Fullscreen, |
|
||||||
TerminalOutput, |
|
||||||
}; |
|
||||||
Dimension dimension_ = Dimension::Fixed; |
|
||||||
bool use_alternative_screen_ = false; |
|
||||||
|
|
||||||
SFMLScreen(int dimx, |
|
||||||
int dimy, |
|
||||||
Dimension dimension, |
|
||||||
bool use_alternative_screen); |
|
||||||
|
|
||||||
bool track_mouse_ = true; |
|
||||||
|
|
||||||
Sender<Task> task_sender_; |
|
||||||
Receiver<Task> task_receiver_; |
|
||||||
|
|
||||||
std::string set_cursor_position; |
|
||||||
std::string reset_cursor_position; |
|
||||||
|
|
||||||
std::thread event_listener_; |
|
||||||
std::thread animation_listener_; |
|
||||||
bool animation_requested_ = false; |
|
||||||
ftxui::animation::TimePoint previous_animation_time_; |
|
||||||
|
|
||||||
int cursor_x_ = 1; |
|
||||||
int cursor_y_ = 1; |
|
||||||
|
|
||||||
bool mouse_captured = false; |
|
||||||
bool previous_frame_resized_ = false; |
|
||||||
|
|
||||||
bool frame_valid_ = false; |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
#endif /* end of include guard: FTXUI_COMPONENT_SCREEN_INTERACTIVE_HPP */ |
|
Loading…
Reference in new issue