From 96ee16e598ab5d4bd1a744c29a6f51a5973cfa03 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Thu, 14 Nov 2024 12:37:31 -0500 Subject: [PATCH] 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. --- gui.cpp | 18 +- gui.hpp | 6 +- input_parser.cpp | 419 ------------------------------------------ input_parser.hpp | 72 -------- meson.build | 1 - panel.cpp | 10 +- panel.hpp | 11 +- sfml_screen.cpp | 460 ----------------------------------------------- sfml_screen.hpp | 113 ------------ 9 files changed, 32 insertions(+), 1078 deletions(-) delete mode 100644 input_parser.cpp delete mode 100644 input_parser.hpp delete mode 100644 sfml_screen.cpp delete mode 100644 sfml_screen.hpp diff --git a/gui.cpp b/gui.cpp index 2a1a313..0bdc2ad 100644 --- a/gui.cpp +++ b/gui.cpp @@ -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(); - $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(player.entity); const auto& inventory = $world.get(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(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); } } } diff --git a/gui.hpp b/gui.hpp index 4716cb9..837610e 100644 --- a/gui.hpp +++ b/gui.hpp @@ -7,6 +7,9 @@ #include #include #include +#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 #include #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; diff --git a/input_parser.cpp b/input_parser.cpp deleted file mode 100644 index 33d0eeb..0000000 --- a/input_parser.cpp +++ /dev/null @@ -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 // for uint32_t -#include // for Mouse, Mouse::Button, Mouse::Motion -#include // for SenderImpl, Sender -#include -#include // for unique_ptr, allocator -#include // for move - -#include "ftxui/component/event.hpp" // for Event -#include "ftxui/component/task.hpp" // for Task - -namespace ftxui { - -// NOLINTNEXTLINE -const std::map 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 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(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 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 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 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 diff --git a/input_parser.hpp b/input_parser.hpp deleted file mode 100644 index 5a808c5..0000000 --- a/input_parser.hpp +++ /dev/null @@ -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 // for unique_ptr -#include // for string -#include // 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 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 arguments); - Output ParseCursorReporting(std::vector arguments); - - Sender out_; - int position_ = -1; - int timeout_ = 0; - std::string pending_; -}; - -} // namespace ftxui - -#endif /* end of include guard: FTXUI_COMPONENT_TERMINAL_INPUT_PARSER */ diff --git a/meson.build b/meson.build index d01a2a9..43c835e 100644 --- a/meson.build +++ b/meson.build @@ -49,7 +49,6 @@ roguish = executable('roguish', [ 'render.cpp', 'config.cpp', 'save.cpp', - 'sfml_screen.cpp', 'panel.cpp', ], dependencies: dependencies) diff --git a/panel.cpp b/panel.cpp index 79be353..9b8d6cd 100644 --- a/panel.cpp +++ b/panel.cpp @@ -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() { diff --git a/panel.hpp b/panel.hpp index e99e9c9..3a578d1 100644 --- a/panel.hpp +++ b/panel.hpp @@ -6,7 +6,6 @@ #include #include #include -#include "sfml_screen.hpp" // for SFMLScreen #include #include @@ -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> $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(); diff --git a/sfml_screen.cpp b/sfml_screen.cpp deleted file mode 100644 index e068ed3..0000000 --- a/sfml_screen.cpp +++ /dev/null @@ -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 -#include // for copy, max, min -#include // for array -#include // for operator-, milliseconds, operator>=, duration, common_type<>::type, time_point -#include // for fileno, stdin -#include // for Task, Closure, AnimationTask -#include // for Pixel, Screen::Cursor, Screen, Screen::Cursor::Hidden -#include // for function -#include // for initializer_list -#include // for cout, ostream, operator<<, basic_ostream, endl, flush -#include // for stack -#include // for thread, sleep_for -#include // for _Swallow_assign, ignore -#include // for decay_t -#include // for move, swap -#include // for visit, variant -#include // for vector - -#include // for TimePoint, Clock, Duration, Params, RequestAnimationFrame -#include // for CapturedMouse, CapturedMouseInterface -#include // for ComponentBase -#include // for Event -#include // for Loop -#include // for ReceiverImpl, Sender, MakeReceiver, SenderImpl, Receiver -#include // for Node, Render -#include // for Requirement -#include // for Dimensions, Size -#include -#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 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 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 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(); -} - -// 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; - - // Handle Event. - if constexpr (std::is_same_v) { - 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) { - arg(); - return; - } - - // Handle Animation - if constexpr (std::is_same_v) { - 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>(dimy, std::vector(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(); -} diff --git a/sfml_screen.hpp b/sfml_screen.hpp deleted file mode 100644 index eab1009..0000000 --- a/sfml_screen.hpp +++ /dev/null @@ -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 // for atomic -#include // for Receiver, Sender -#include // for function -#include // for shared_ptr -#include // for string -#include // for thread -#include // for variant - -#include // for TimePoint -#include // for CapturedMouse -#include // for Event -#include // for Task, Closure -#include // 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; - 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_sender_; - Receiver 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 */