Barely working stripped down version of ScreenInteractive. Now to bring on the SFML events.
This commit is contained in:
parent
7d3605f58b
commit
e3cff8142c
9 changed files with 1070 additions and 7 deletions
460
sfml_screen.cpp
Normal file
460
sfml_screen.cpp
Normal file
|
@ -0,0 +1,460 @@
|
|||
// 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();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue