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.
This commit is contained in:
parent
e3cff8142c
commit
96ee16e598
9 changed files with 32 additions and 1078 deletions
18
gui.cpp
18
gui.cpp
|
@ -33,7 +33,7 @@ GUI::GUI(DinkyECS::World &world, Map& game_map) :
|
||||||
$game_map(game_map),
|
$game_map(game_map),
|
||||||
$log({{"Welcome to the game!"}}),
|
$log({{"Welcome to the game!"}}),
|
||||||
$status_ui(SCREEN_X, SCREEN_Y, 0, 0),
|
$status_ui(SCREEN_X, SCREEN_Y, 0, 0),
|
||||||
$map_view(30, 10, GAME_MAP_POS, 0, false),
|
$map_view(0, 0, GAME_MAP_POS, 0, false),
|
||||||
$view_port{0,0},
|
$view_port{0,0},
|
||||||
$world(world),
|
$world(world),
|
||||||
$sounds("./assets"),
|
$sounds("./assets"),
|
||||||
|
@ -65,12 +65,14 @@ void GUI::create_renderer() {
|
||||||
Terminal::SetColorSupport(Terminal::Color::TrueColor);
|
Terminal::SetColorSupport(Terminal::Color::TrueColor);
|
||||||
auto player = $world.get_the<Player>();
|
auto player = $world.get_the<Player>();
|
||||||
|
|
||||||
$map_view.set_renderer([&] {
|
$map_view.set_renderer(Renderer([&] {
|
||||||
System::draw_map($world, $game_map, $canvas, $view_port.x, $view_port.y);
|
System::draw_map($world, $game_map, $canvas, $view_port.x, $view_port.y);
|
||||||
return canvas($canvas);
|
return canvas($canvas);
|
||||||
});
|
}));
|
||||||
|
|
||||||
$status_ui.set_renderer([&, player]{
|
auto test_button = Button("TEST", [&]{ println("pressed"); });
|
||||||
|
|
||||||
|
auto status_rend = Renderer(test_button, [&, test_button, player]{
|
||||||
const auto& player_combat = $world.get<Combat>(player.entity);
|
const auto& player_combat = $world.get<Combat>(player.entity);
|
||||||
const auto& inventory = $world.get<Inventory>(player.entity);
|
const auto& inventory = $world.get<Inventory>(player.entity);
|
||||||
$status_text = player_combat.hp > 0 ? "NOT DEAD" : "DEAD!!!!!!";
|
$status_text = player_combat.hp > 0 ? "NOT DEAD" : "DEAD!!!!!!";
|
||||||
|
@ -85,6 +87,7 @@ void GUI::create_renderer() {
|
||||||
return hbox({
|
return hbox({
|
||||||
hflow(
|
hflow(
|
||||||
vbox(
|
vbox(
|
||||||
|
test_button->Render(),
|
||||||
text(format("HP: {: >3} GOLD: {: >3}",
|
text(format("HP: {: >3} GOLD: {: >3}",
|
||||||
player_combat.hp, inventory.gold)) | border,
|
player_combat.hp, inventory.gold)) | border,
|
||||||
text($status_text) | border,
|
text($status_text) | border,
|
||||||
|
@ -96,6 +99,8 @@ void GUI::create_renderer() {
|
||||||
hbox(),
|
hbox(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$status_ui.set_renderer(status_rend);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GUI::handle_world_events() {
|
void GUI::handle_world_events() {
|
||||||
|
@ -151,6 +156,7 @@ bool GUI::handle_ui_events() {
|
||||||
if(event.type == sf::Event::Closed) {
|
if(event.type == sf::Event::Closed) {
|
||||||
$renderer.close();
|
$renderer.close();
|
||||||
} else if(event.type == sf::Event::KeyPressed) {
|
} else if(event.type == sf::Event::KeyPressed) {
|
||||||
|
// ZED: Uh we can just do this...?
|
||||||
auto& player_motion = $world.get<Motion>(player.entity);
|
auto& player_motion = $world.get<Motion>(player.entity);
|
||||||
|
|
||||||
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
|
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
|
||||||
|
@ -171,6 +177,10 @@ bool GUI::handle_ui_events() {
|
||||||
resize_map(map_font_size - 10);
|
resize_map(map_font_size - 10);
|
||||||
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
|
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
|
||||||
save_world();
|
save_world();
|
||||||
|
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Tab)) {
|
||||||
|
$status_ui.$component->OnEvent(Event::Tab);
|
||||||
|
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Enter)) {
|
||||||
|
$status_ui.$component->OnEvent(Event::Return);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6
gui.hpp
6
gui.hpp
|
@ -7,6 +7,9 @@
|
||||||
#include <ftxui/component/component.hpp>
|
#include <ftxui/component/component.hpp>
|
||||||
#include <ftxui/screen/screen.hpp>
|
#include <ftxui/screen/screen.hpp>
|
||||||
#include <ftxui/dom/canvas.hpp>
|
#include <ftxui/dom/canvas.hpp>
|
||||||
|
#include "ftxui/component/component.hpp" // for Button, operator|=, Renderer, Vertical, Modal
|
||||||
|
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive, Component
|
||||||
|
#include "ftxui/dom/elements.hpp" // for operator|, separator, text, size, Element, vbox, border, GREATER_THAN, WIDTH, center, HEIGHT
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "map.hpp"
|
#include "map.hpp"
|
||||||
|
@ -17,7 +20,7 @@
|
||||||
#include "panel.hpp"
|
#include "panel.hpp"
|
||||||
|
|
||||||
using std::string;
|
using std::string;
|
||||||
using ftxui::Canvas, ftxui::Component, ftxui::Screen;
|
using ftxui::Canvas, ftxui::Component, ftxui::Screen, ftxui::Button;
|
||||||
|
|
||||||
constexpr int SCREEN_X = 40;
|
constexpr int SCREEN_X = 40;
|
||||||
constexpr int SCREEN_Y = 30;
|
constexpr int SCREEN_Y = 30;
|
||||||
|
@ -41,6 +44,7 @@ class GUI {
|
||||||
Panel $status_ui;
|
Panel $status_ui;
|
||||||
Panel $map_view;
|
Panel $map_view;
|
||||||
Point $view_port;
|
Point $view_port;
|
||||||
|
Component $test_button;
|
||||||
DinkyECS::World& $world;
|
DinkyECS::World& $world;
|
||||||
SoundManager $sounds;
|
SoundManager $sounds;
|
||||||
SFMLRender $renderer;
|
SFMLRender $renderer;
|
||||||
|
|
419
input_parser.cpp
419
input_parser.cpp
|
@ -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 */
|
|
|
@ -49,7 +49,6 @@ roguish = executable('roguish', [
|
||||||
'render.cpp',
|
'render.cpp',
|
||||||
'config.cpp',
|
'config.cpp',
|
||||||
'save.cpp',
|
'save.cpp',
|
||||||
'sfml_screen.cpp',
|
|
||||||
'panel.cpp',
|
'panel.cpp',
|
||||||
],
|
],
|
||||||
dependencies: dependencies)
|
dependencies: dependencies)
|
||||||
|
|
10
panel.cpp
10
panel.cpp
|
@ -2,12 +2,16 @@
|
||||||
|
|
||||||
void Panel::resize(int width, int height) {
|
void Panel::resize(int width, int height) {
|
||||||
$dirty = true;
|
$dirty = true;
|
||||||
// $screen = ScreenInteractive::FixedSize(width, height);
|
$screen = Screen(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::set_renderer(std::function< Element()> render) {
|
void Panel::set_renderer(Component renderer) {
|
||||||
$dirty = true;
|
$dirty = true;
|
||||||
$component = Renderer(render);
|
$component = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::add(Component child) {
|
||||||
|
$component->Add(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::render() {
|
void Panel::render() {
|
||||||
|
|
11
panel.hpp
11
panel.hpp
|
@ -6,7 +6,6 @@
|
||||||
#include <ftxui/dom/canvas.hpp>
|
#include <ftxui/dom/canvas.hpp>
|
||||||
#include <ftxui/screen/screen.hpp>
|
#include <ftxui/screen/screen.hpp>
|
||||||
#include <ftxui/dom/canvas.hpp>
|
#include <ftxui/dom/canvas.hpp>
|
||||||
#include "sfml_screen.hpp" // for SFMLScreen
|
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <codecvt>
|
#include <codecvt>
|
||||||
|
|
||||||
|
@ -20,7 +19,7 @@ struct Panel {
|
||||||
std::wstring $screenout;
|
std::wstring $screenout;
|
||||||
bool $dirty = true;
|
bool $dirty = true;
|
||||||
Component $component;
|
Component $component;
|
||||||
SFMLScreen $screen;
|
Screen $screen;
|
||||||
bool $must_clear = true;
|
bool $must_clear = true;
|
||||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $converter;
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $converter;
|
||||||
|
|
||||||
|
@ -29,12 +28,14 @@ struct Panel {
|
||||||
y(y),
|
y(y),
|
||||||
width(width),
|
width(width),
|
||||||
height(height),
|
height(height),
|
||||||
$screen(SFMLScreen::FixedSize(width, height)),
|
$screen(Screen(width, height)),
|
||||||
$must_clear(must_clear)
|
$must_clear(must_clear)
|
||||||
{};
|
{
|
||||||
|
};
|
||||||
|
|
||||||
void resize(int width, int height);
|
void resize(int width, int height);
|
||||||
void set_renderer(std::function< Element()> render);
|
void set_renderer(Component renderer);
|
||||||
|
void add(Component child);
|
||||||
void render();
|
void render();
|
||||||
const std::wstring &to_string();
|
const std::wstring &to_string();
|
||||||
const Screen &screen();
|
const Screen &screen();
|
||||||
|
|
460
sfml_screen.cpp
460
sfml_screen.cpp
|
@ -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();
|
|
||||||
}
|
|
113
sfml_screen.hpp
113
sfml_screen.hpp
|
@ -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…
Add table
Add a link
Reference in a new issue