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.

main
Zed A. Shaw 1 month ago
parent e3cff8142c
commit 96ee16e598
  1. 18
      gui.cpp
  2. 6
      gui.hpp
  3. 419
      input_parser.cpp
  4. 72
      input_parser.hpp
  5. 1
      meson.build
  6. 10
      panel.cpp
  7. 11
      panel.hpp
  8. 460
      sfml_screen.cpp
  9. 113
      sfml_screen.hpp

@ -33,7 +33,7 @@ GUI::GUI(DinkyECS::World &world, Map& game_map) :
$game_map(game_map),
$log({{"Welcome to the game!"}}),
$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},
$world(world),
$sounds("./assets"),
@ -65,12 +65,14 @@ void GUI::create_renderer() {
Terminal::SetColorSupport(Terminal::Color::TrueColor);
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);
return canvas($canvas);
});
}));
auto test_button = Button("TEST", [&]{ println("pressed"); });
$status_ui.set_renderer([&, player]{
auto status_rend = Renderer(test_button, [&, test_button, player]{
const auto& player_combat = $world.get<Combat>(player.entity);
const auto& inventory = $world.get<Inventory>(player.entity);
$status_text = player_combat.hp > 0 ? "NOT DEAD" : "DEAD!!!!!!";
@ -85,6 +87,7 @@ void GUI::create_renderer() {
return hbox({
hflow(
vbox(
test_button->Render(),
text(format("HP: {: >3} GOLD: {: >3}",
player_combat.hp, inventory.gold)) | border,
text($status_text) | border,
@ -96,6 +99,8 @@ void GUI::create_renderer() {
hbox(),
});
});
$status_ui.set_renderer(status_rend);
}
void GUI::handle_world_events() {
@ -151,6 +156,7 @@ bool GUI::handle_ui_events() {
if(event.type == sf::Event::Closed) {
$renderer.close();
} else if(event.type == sf::Event::KeyPressed) {
// ZED: Uh we can just do this...?
auto& player_motion = $world.get<Motion>(player.entity);
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
@ -171,6 +177,10 @@ bool GUI::handle_ui_events() {
resize_map(map_font_size - 10);
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
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);
}
}
}

@ -7,6 +7,9 @@
#include <ftxui/component/component.hpp>
#include <ftxui/screen/screen.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 <string>
#include "map.hpp"
@ -17,7 +20,7 @@
#include "panel.hpp"
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_Y = 30;
@ -41,6 +44,7 @@ class GUI {
Panel $status_ui;
Panel $map_view;
Point $view_port;
Component $test_button;
DinkyECS::World& $world;
SoundManager $sounds;
SFMLRender $renderer;

@ -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',
'config.cpp',
'save.cpp',
'sfml_screen.cpp',
'panel.cpp',
],
dependencies: dependencies)

@ -2,12 +2,16 @@
void Panel::resize(int width, int height) {
$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;
$component = Renderer(render);
$component = renderer;
}
void Panel::add(Component child) {
$component->Add(child);
}
void Panel::render() {

@ -6,7 +6,6 @@
#include <ftxui/dom/canvas.hpp>
#include <ftxui/screen/screen.hpp>
#include <ftxui/dom/canvas.hpp>
#include "sfml_screen.hpp" // for SFMLScreen
#include <locale>
#include <codecvt>
@ -20,7 +19,7 @@ struct Panel {
std::wstring $screenout;
bool $dirty = true;
Component $component;
SFMLScreen $screen;
Screen $screen;
bool $must_clear = true;
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $converter;
@ -29,12 +28,14 @@ struct Panel {
y(y),
width(width),
height(height),
$screen(SFMLScreen::FixedSize(width, height)),
$screen(Screen(width, height)),
$must_clear(must_clear)
{};
{
};
void resize(int width, int height);
void set_renderer(std::function< Element()> render);
void set_renderer(Component renderer);
void add(Component child);
void render();
const std::wstring &to_string();
const Screen &screen();

@ -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…
Cancel
Save