From 2ccc599db992bcaa2c85051a04d6dd4e0f03e51c Mon Sep 17 00:00:00 2001 From: Arthur Sonzogni Date: Wed, 1 Sep 2021 17:47:48 +0200 Subject: [PATCH] Support reentrant screen. (#196) --- examples/component/CMakeLists.txt | 1 + examples/component/nested_screen.cpp | 51 ++++++++++++++++ .../ftxui/component/screen_interactive.hpp | 7 +++ src/ftxui/component/screen_interactive.cpp | 59 +++++++++++++++---- 4 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 examples/component/nested_screen.cpp diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt index d819a890..0ef9a825 100644 --- a/examples/component/CMakeLists.txt +++ b/examples/component/CMakeLists.txt @@ -2,6 +2,7 @@ set(DIRECTORY_LIB component) example(button) example(checkbox) +example(nested_screen) example(checkbox_in_frame) example(composition) example(gallery) diff --git a/examples/component/nested_screen.cpp b/examples/component/nested_screen.cpp new file mode 100644 index 00000000..b492480a --- /dev/null +++ b/examples/component/nested_screen.cpp @@ -0,0 +1,51 @@ +#include // for shared_ptr, __shared_ptr_access +#include // for operator+, to_wstring + +#include "ftxui/component/captured_mouse.hpp" // for ftxui +#include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer +#include "ftxui/component/component_base.hpp" // for ComponentBase +#include "ftxui/component/component_options.hpp" // for ButtonOption +#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive +#include "ftxui/dom/elements.hpp" // for separator, gauge, Element, operator|, vbox, border + +using namespace ftxui; + +void Nested(std::string path) { + auto screen = ScreenInteractive::FitComponent(); + auto back_button = Button("Back", screen.ExitLoopClosure()); + auto goto_1 = Button("Goto /1", [path] { Nested(path + "/1"); }); + auto goto_2 = Button("Goto /2", [path] { Nested(path + "/2"); }); + auto goto_3 = Button("Goto /3", [path] { Nested(path + "/3"); }); + auto layout = Container::Vertical({ + back_button, + goto_1, + goto_2, + goto_3, + }); + auto renderer = Renderer(layout, [&] { + return vbox({ + text("path: " + path), + separator(), + back_button->Render(), + goto_1->Render(), + goto_2->Render(), + goto_3->Render(), + }) | border; + }); + screen.Loop(renderer); +} + +int main(int argc, const char* argv[]) { + auto screen = ScreenInteractive::FitComponent(); + auto button_quit = Button("Quit", screen.ExitLoopClosure()); + auto button_nested = Button("Nested", [] { Nested(""); }); + screen.Loop(Container::Vertical({ + button_quit, + button_nested, + })); + return 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. diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index e5b7e4f9..5d53a9f4 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -6,6 +6,7 @@ #include // for function #include // for shared_ptr #include // for string +#include #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse #include "ftxui/component/event.hpp" // for Event @@ -31,6 +32,11 @@ class ScreenInteractive : public Screen { CapturedMouse CaptureMouse(); private: + void Install(); + void Uninstall(); + void Main(Component component); + ScreenInteractive* suspended_screen_ = nullptr; + void Draw(Component component); void EventLoop(Component component); @@ -54,6 +60,7 @@ class ScreenInteractive : public Screen { std::string reset_cursor_position; std::atomic quit_ = false; + std::thread event_listener_; int cursor_x_ = 1; int cursor_y_ = 1; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 7d3552c7..27bf866d 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -275,6 +275,43 @@ CapturedMouse ScreenInteractive::CaptureMouse() { } void ScreenInteractive::Loop(Component component) { + static ScreenInteractive* g_active_screen = nullptr; + + // Suspend previously active screen: + if (g_active_screen) { + std::swap(suspended_screen_, g_active_screen); + std::cout << suspended_screen_->reset_cursor_position + << suspended_screen_->ResetPosition(/*clear=*/true); + suspended_screen_->dimx_ = 0; + suspended_screen_->dimy_ = 0; + suspended_screen_->Uninstall(); + } + + // This screen is now active: + g_active_screen = this; + g_active_screen->Install(); + g_active_screen->Main(component); + g_active_screen->Uninstall(); + g_active_screen = nullptr; + + // Put cursor position at the end of the drawing. + std::cout << reset_cursor_position; + + // Restore suspended screen. + if (suspended_screen_) { + std::cout << ResetPosition(/*clear=*/true); + dimx_ = 0; + dimy_ = 0; + std::swap(g_active_screen, suspended_screen_); + g_active_screen->Install(); + } else { + // On final exit, keep the current drawing and reset cursor position one + // line after it. + std::cout << std::endl; + } +} + +void ScreenInteractive::Install() { on_exit_functions.push([this] { ExitLoopClosure()(); }); // Install signal handlers to restore the terminal state on exit. The default @@ -349,18 +386,12 @@ void ScreenInteractive::Loop(Component component) { on_exit_functions.push([=] { std::cout << Set(parameters); }); }; - flush(); - if (use_alternative_screen_) { enable({ DECMode::kAlternateScreen, }); } - // On exit, reset cursor one line after the current drawing. - on_exit_functions.push( - [this] { std::cout << reset_cursor_position << std::endl; }); - disable({ DECMode::kCursor, DECMode::kLineWrap, @@ -375,10 +406,19 @@ void ScreenInteractive::Loop(Component component) { flush(); - auto event_listener = + quit_ = false; + event_listener_ = std::thread(&EventListener, &quit_, event_receiver_->MakeSender()); +} - // The main loop. +void ScreenInteractive::Uninstall() { + ExitLoopClosure()(); + event_listener_.join(); + + OnExit(0); +} + +void ScreenInteractive::Main(Component component) { while (!quit_) { if (!event_receiver_->HasPending()) { Draw(component); @@ -405,9 +445,6 @@ void ScreenInteractive::Loop(Component component) { event.screen_ = this; component->OnEvent(event); } - - event_listener.join(); - OnExit(0); } void ScreenInteractive::Draw(Component component) {