From 373b016ca98eff05a0d893df8cbfc5487b726991 Mon Sep 17 00:00:00 2001 From: Arthur Sonzogni Date: Mon, 22 Mar 2021 00:26:52 +0100 Subject: [PATCH] Add webassembly support (#79) --- CMakeLists.txt | 7 + README.md | 1 + examples/CMakeLists.txt | 11 ++ examples/dom/border.cpp | 2 +- examples/dom/color_gallery.cpp | 2 +- examples/dom/color_info_palette256.cpp | 2 +- examples/dom/color_truecolor_HSV.cpp | 2 +- examples/dom/color_truecolor_RGB.cpp | 2 +- examples/dom/dbox.cpp | 2 +- examples/dom/gauge.cpp | 3 +- examples/dom/graph.cpp | 4 +- examples/dom/hflow.cpp | 2 +- examples/dom/html_like.cpp | 4 +- examples/dom/package_manager.cpp | 3 +- examples/dom/paragraph.cpp | 2 +- examples/dom/separator.cpp | 3 +- examples/dom/size.cpp | 2 +- examples/dom/spinner.cpp | 3 +- examples/dom/style_blink.cpp | 3 +- examples/dom/style_bold.cpp | 3 +- examples/dom/style_color.cpp | 3 +- examples/dom/style_dim.cpp | 3 +- examples/dom/style_gallery.cpp | 3 +- examples/dom/style_inverted.cpp | 3 +- examples/dom/style_underlined.cpp | 3 +- examples/dom/vbox_hbox.cpp | 3 +- examples/dom/window.cpp | 2 +- examples/index.html | 164 +++++++++++++++++++++ examples/run_webassembly.sh | 6 + include/ftxui/screen/screen.hpp | 1 + src/ftxui/component/screen_interactive.cpp | 30 +++- src/ftxui/dom/text_test.cpp | 14 +- src/ftxui/dom/vbox_test.cpp | 1 + src/ftxui/screen/screen.cpp | 9 +- src/ftxui/screen/terminal.cpp | 6 +- 35 files changed, 267 insertions(+), 47 deletions(-) create mode 100644 examples/index.html create mode 100755 examples/run_webassembly.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 57efcf4..b237613 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,13 @@ foreach(lib screen dom component) endforeach() +if (EMSCRIPTEN) + #string(APPEND CMAKE_CXX_FLAGS " -s ASSERTIONS=1") + string(APPEND CMAKE_CXX_FLAGS " -s ASYNCIFY") + string(APPEND CMAKE_CXX_FLAGS " -s USE_PTHREADS") + string(APPEND CMAKE_CXX_FLAGS " -s PROXY_TO_PTHREAD") +endif() + if(FTXUI_ENABLE_INSTALL) include(GNUInstallDirs) install(TARGETS screen dom component diff --git a/README.md b/README.md index 194172c..d7f32da 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ A simple C++ library for terminal based user interface. - [Starter example project](https://github.com/ArthurSonzogni/ftxui-starter) - [Documentation](https://arthursonzogni.com/FTXUI/doc/) +- [Examples (WebAssembly)](https://arthursonzogni.com/FTXUI/examples/) - [Build using CMake](https://arthursonzogni.com/FTXUI/doc/#build-using-cmake) - [Build using nxxm](https://arthursonzogni.com/FTXUI/doc/#build-using-cmake) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ff779c0..41e3207 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,3 +1,14 @@ add_subdirectory(component) add_subdirectory(dom) add_subdirectory(util) + +if (EMSCRIPTEN) + foreach(file + "index.html" + "run_webassembly.sh") + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/${file} + ${CMAKE_CURRENT_BINARY_DIR}/${file} + ) + endforeach(file) +endif() diff --git a/examples/dom/border.cpp b/examples/dom/border.cpp index 7a49f3e..57fa7b3 100644 --- a/examples/dom/border.cpp +++ b/examples/dom/border.cpp @@ -28,7 +28,7 @@ int main(int argc, const char* argv[]) { }); auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - std::cout << screen.ToString() << std::endl; + screen.Print(); } // Copyright 2020 Arthur Sonzogni. All rights reserved. diff --git a/examples/dom/color_gallery.cpp b/examples/dom/color_gallery.cpp index db1c25b..0775ad1 100644 --- a/examples/dom/color_gallery.cpp +++ b/examples/dom/color_gallery.cpp @@ -126,7 +126,7 @@ int main(int argc, const char* argv[]) { auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - std::cout << screen.ToString(); + screen.Print(); return 0; } diff --git a/examples/dom/color_info_palette256.cpp b/examples/dom/color_info_palette256.cpp index 56820f3..68a1d18 100644 --- a/examples/dom/color_info_palette256.cpp +++ b/examples/dom/color_info_palette256.cpp @@ -30,7 +30,7 @@ int main(int argc, const char* argv[]) { auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - std::cout << screen.ToString(); + screen.Print(); return 0; } diff --git a/examples/dom/color_truecolor_HSV.cpp b/examples/dom/color_truecolor_HSV.cpp index 887f27c..ad902bb 100644 --- a/examples/dom/color_truecolor_HSV.cpp +++ b/examples/dom/color_truecolor_HSV.cpp @@ -26,7 +26,7 @@ int main(int argc, const char* argv[]) { auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - std::cout << screen.ToString(); + screen.Print(); return 0; } diff --git a/examples/dom/color_truecolor_RGB.cpp b/examples/dom/color_truecolor_RGB.cpp index ed1495b..d35788e 100644 --- a/examples/dom/color_truecolor_RGB.cpp +++ b/examples/dom/color_truecolor_RGB.cpp @@ -45,7 +45,7 @@ int main(int argc, const char* argv[]) { auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - std::cout << screen.ToString(); + screen.Print(); return 0; } diff --git a/examples/dom/dbox.cpp b/examples/dom/dbox.cpp index 67561ef..2fc712c 100644 --- a/examples/dom/dbox.cpp +++ b/examples/dom/dbox.cpp @@ -17,7 +17,7 @@ int main(int argc, const char* argv[]) { auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - std::cout << screen.ToString(); + screen.Print(); return 0; } diff --git a/examples/dom/gauge.cpp b/examples/dom/gauge.cpp index 2d7ecd3..eb2a96d 100644 --- a/examples/dom/gauge.cpp +++ b/examples/dom/gauge.cpp @@ -19,7 +19,8 @@ int main(int argc, const char* argv[]) { }); auto screen = Screen(100, 1); Render(screen, document); - std::cout << reset_position << screen.ToString() << std::flush; + std::cout << reset_position; + screen.Print(); reset_position = screen.ResetPosition(); std::this_thread::sleep_for(0.01s); diff --git a/examples/dom/graph.cpp b/examples/dom/graph.cpp index dbf7f1e..78c8f46 100644 --- a/examples/dom/graph.cpp +++ b/examples/dom/graph.cpp @@ -60,8 +60,8 @@ int main(int argc, const char* argv[]) { auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - std::cout << reset_position << screen.ToString() << std::flush; - + std::cout << reset_position; + screen.Print(); reset_position = screen.ResetPosition(); std::this_thread::sleep_for(0.03s); diff --git a/examples/dom/hflow.cpp b/examples/dom/hflow.cpp index ef6beb2..aa140a6 100644 --- a/examples/dom/hflow.cpp +++ b/examples/dom/hflow.cpp @@ -39,7 +39,7 @@ int main(int argc, const char* argv[]) { auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - std::cout << screen.ToString() << std::endl; + screen.Print(); return 0; } diff --git a/examples/dom/html_like.cpp b/examples/dom/html_like.cpp index 6485cf1..be565e8 100644 --- a/examples/dom/html_like.cpp +++ b/examples/dom/html_like.cpp @@ -41,8 +41,8 @@ int main(int argc, const char* argv[]) { auto screen = Screen::Create(Dimension::Full()); Render(screen, document); - std::cout << reset_position << screen.ToString() << std::flush; - + std::cout << reset_position; + screen.Print(); reset_position = screen.ResetPosition(); std::this_thread::sleep_for(0.01s); diff --git a/examples/dom/package_manager.cpp b/examples/dom/package_manager.cpp index 3875945..11cbd5f 100644 --- a/examples/dom/package_manager.cpp +++ b/examples/dom/package_manager.cpp @@ -123,7 +123,8 @@ int main(int argc, const char* argv[]) { auto document = render(); auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - std::cout << reset_position << screen.ToString() << std::flush; + std::cout << reset_position; + screen.Print(); reset_position = screen.ResetPosition(); // Simulate time. diff --git a/examples/dom/paragraph.cpp b/examples/dom/paragraph.cpp index 7ed3d14..adb1f38 100644 --- a/examples/dom/paragraph.cpp +++ b/examples/dom/paragraph.cpp @@ -25,7 +25,7 @@ int main(int argc, const char* argv[]) { auto screen = Screen::Create(Dimension::Full(), Dimension::Full()); Render(screen, document); - std::cout << screen.ToString(); + screen.Print(); getchar(); return 0; diff --git a/examples/dom/separator.cpp b/examples/dom/separator.cpp index e575868..f3bf1a3 100644 --- a/examples/dom/separator.cpp +++ b/examples/dom/separator.cpp @@ -18,8 +18,7 @@ int main(int argc, const char* argv[]) { border; auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - - std::cout << screen.ToString() << std::endl; + screen.Print(); return 0; } diff --git a/examples/dom/size.cpp b/examples/dom/size.cpp index 392c62e..dfeea1d 100644 --- a/examples/dom/size.cpp +++ b/examples/dom/size.cpp @@ -18,7 +18,7 @@ int main(int argc, const char* argv[]) { auto document = hbox(std::move(content)); auto screen = Screen::Create(Dimension::Fit(document)); Render(screen, document); - std::cout << screen.ToString() << std::endl; + screen.Print(); return 0; } diff --git a/examples/dom/spinner.cpp b/examples/dom/spinner.cpp index 52cfe87..7fff889 100644 --- a/examples/dom/spinner.cpp +++ b/examples/dom/spinner.cpp @@ -28,7 +28,8 @@ int main(int argc, const char* argv[]) { }); auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - std::cout << reset_position << screen.ToString() << std::flush; + std::cout << reset_position; + screen.Print(); reset_position = screen.ResetPosition(); std::this_thread::sleep_for(0.1s); diff --git a/examples/dom/style_blink.cpp b/examples/dom/style_blink.cpp index 0445add..114a0d4 100644 --- a/examples/dom/style_blink.cpp +++ b/examples/dom/style_blink.cpp @@ -12,8 +12,7 @@ int main(int argc, const char* argv[]) { }); auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - - std::cout << screen.ToString(); + screen.Print(); return 0; } diff --git a/examples/dom/style_bold.cpp b/examples/dom/style_bold.cpp index 89cb641..e395d78 100644 --- a/examples/dom/style_bold.cpp +++ b/examples/dom/style_bold.cpp @@ -12,8 +12,7 @@ int main(int argc, const char* argv[]) { }); auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - - std::cout << screen.ToString(); + screen.Print(); return 0; } diff --git a/examples/dom/style_color.cpp b/examples/dom/style_color.cpp index 5c4ec50..f50fddb 100644 --- a/examples/dom/style_color.cpp +++ b/examples/dom/style_color.cpp @@ -51,8 +51,7 @@ int main(int argc, const char* argv[]) { auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - - std::cout << screen.ToString(); + screen.Print(); return 0; } diff --git a/examples/dom/style_dim.cpp b/examples/dom/style_dim.cpp index 53823a2..1c4d446 100644 --- a/examples/dom/style_dim.cpp +++ b/examples/dom/style_dim.cpp @@ -12,8 +12,7 @@ int main(int argc, const char* argv[]) { }); auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - - std::cout << screen.ToString(); + screen.Print(); return 0; } diff --git a/examples/dom/style_gallery.cpp b/examples/dom/style_gallery.cpp index a180789..0ce864f 100644 --- a/examples/dom/style_gallery.cpp +++ b/examples/dom/style_gallery.cpp @@ -19,8 +19,7 @@ int main(int argc, const char* argv[]) { // clang-format on auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - - std::cout << screen.ToString(); + screen.Print(); return 0; } diff --git a/examples/dom/style_inverted.cpp b/examples/dom/style_inverted.cpp index ad930ab..9a19181 100644 --- a/examples/dom/style_inverted.cpp +++ b/examples/dom/style_inverted.cpp @@ -11,8 +11,7 @@ int main(int argc, const char* argv[]) { }); auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - - std::cout << screen.ToString(); + screen.Print(); return 0; } diff --git a/examples/dom/style_underlined.cpp b/examples/dom/style_underlined.cpp index 41c5fd4..dedf04a 100644 --- a/examples/dom/style_underlined.cpp +++ b/examples/dom/style_underlined.cpp @@ -12,8 +12,7 @@ int main(int argc, const char* argv[]) { }); auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); - - std::cout << screen.ToString(); + screen.Print(); return 0; } diff --git a/examples/dom/vbox_hbox.cpp b/examples/dom/vbox_hbox.cpp index 93bb984..92d23cd 100644 --- a/examples/dom/vbox_hbox.cpp +++ b/examples/dom/vbox_hbox.cpp @@ -26,8 +26,7 @@ int main(int argc, const char* argv[]) { }); auto screen = Screen::Create(Dimension::Full()); Render(screen, document); - - std::cout << screen.ToString(); + screen.Print(); getchar(); return 0; diff --git a/examples/dom/window.cpp b/examples/dom/window.cpp index 18726da..df84561 100644 --- a/examples/dom/window.cpp +++ b/examples/dom/window.cpp @@ -16,7 +16,7 @@ int main(void) { auto screen = Screen::Create(Dimension::Fixed(80), Dimension::Fixed(10)); Render(screen, document); - std::cout << screen.ToString() << '\n'; + screen.Print(); } // Copyright 2020 Arthur Sonzogni. All rights reserved. diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 0000000..fde820a --- /dev/null +++ b/examples/index.html @@ -0,0 +1,164 @@ + + + + FTXUI examples WebAssembly + + + + + +
+

FTXUI WebAssembly Example

+

+ FTXUI is a single + C++ library for terminal user interface. +

+

+ On this page, you can try all the examples contained in: ./example/ + Those are compiled using WebAssembly. +

+ +
+
+ + + + + + diff --git a/examples/run_webassembly.sh b/examples/run_webassembly.sh new file mode 100755 index 0000000..488c23c --- /dev/null +++ b/examples/run_webassembly.sh @@ -0,0 +1,6 @@ +#! /bin/bash +python3 -m http.server 8888 & +P1=$! +trap 'kill 0' SIGINT; P1 +python3 -m webbrowser http://localhost:8888 +wait $P1 diff --git a/include/ftxui/screen/screen.hpp b/include/ftxui/screen/screen.hpp index 7caba26..0c75202 100644 --- a/include/ftxui/screen/screen.hpp +++ b/include/ftxui/screen/screen.hpp @@ -56,6 +56,7 @@ class Screen { // Convert the screen into a printable string in the terminal. std::string ToString(); + void Print(); // Get screen dimensions. int dimx() { return dimx_; } diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 130ce34..b4f3753 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -38,6 +38,11 @@ namespace ftxui { namespace { +void Flush() { + // Emscripten doesn't implement flush. We interpret zero as flush. + std::cout << std::flush << (char)0; +} + constexpr int timeout_milliseconds = 20; constexpr int timeout_microseconds = timeout_milliseconds * 1000; #if defined(_WIN32) @@ -90,8 +95,25 @@ void EventListener(std::atomic* quit, } } -#else +#elif defined(__EMSCRIPTEN__) +#include +// Read char from the terminal. +void EventListener(std::atomic* quit, Sender out) { + (void)timeout_microseconds; + auto parser = TerminalInputParser(std::move(out)); + + char c; + while (!*quit) { + while(read(STDIN_FILENO, &c, 1), c) + parser.Add(c); + + emscripten_sleep(1); + parser.Timeout(1); + } +} + +#else #include int CheckStdinReady(int usec_timeout) { @@ -260,12 +282,13 @@ void ScreenInteractive::Loop(Component* component) { // Hide the cursor and show it at exit. std::cout << HIDE_CURSOR; std::cout << DISABLE_LINE_WRAP; - std::cout << std::flush; + Flush(); on_exit_functions.push([&] { std::cout << reset_cursor_position; std::cout << SHOW_CURSOR; std::cout << ENABLE_LINE_WRAP; std::cout << std::endl; + Flush(); }); auto event_listener = @@ -276,7 +299,8 @@ void ScreenInteractive::Loop(Component* component) { if (!event_receiver_->HasPending()) { std::cout << reset_cursor_position << ResetPosition(); Draw(component); - std::cout << ToString() << set_cursor_position << std::flush; + std::cout << ToString() << set_cursor_position; + Flush(); Clear(); } Event event; diff --git a/src/ftxui/dom/text_test.cpp b/src/ftxui/dom/text_test.cpp index d4f6e0e..34fe2a5 100644 --- a/src/ftxui/dom/text_test.cpp +++ b/src/ftxui/dom/text_test.cpp @@ -42,7 +42,7 @@ TEST(TextTest, ScreenBigger2) { Screen screen(6, 2); Render(screen, element); - EXPECT_EQ("test \n ", screen.ToString()); + EXPECT_EQ("test \r\n ", screen.ToString()); } // See https://github.com/ArthurSonzogni/FTXUI/issues/2#issuecomment-504871456 @@ -51,8 +51,8 @@ TEST(TextTest, CJK) { Screen screen(6, 3); Render(screen, element); EXPECT_EQ( - "┌────┐\n" - "│测试│\n" + "┌────┐\r\n" + "│测试│\r\n" "└────┘", screen.ToString()); } @@ -63,8 +63,8 @@ TEST(TextTest, CJK_2) { Screen screen(5, 3); Render(screen, element); EXPECT_EQ( - "┌───┐\n" - "│测试\n" + "┌───┐\r\n" + "│测试\r\n" "└───┘", screen.ToString()); } @@ -75,8 +75,8 @@ TEST(TextTest, CJK_3) { Screen screen(4, 3); Render(screen, element); EXPECT_EQ( - "┌──┐\n" - "│测│\n" + "┌──┐\r\n" + "│测│\r\n" "└──┘", screen.ToString()); } diff --git a/src/ftxui/dom/vbox_test.cpp b/src/ftxui/dom/vbox_test.cpp index cffa74a..917afd9 100644 --- a/src/ftxui/dom/vbox_test.cpp +++ b/src/ftxui/dom/vbox_test.cpp @@ -6,6 +6,7 @@ using namespace ftxui; using namespace ftxui; std::string rotate(std::string str) { + str.erase(std::remove(str.begin(), str.end(), '\r'), str.end()); str.erase(std::remove(str.begin(), str.end(), '\n'), str.end()); return str; } diff --git a/src/ftxui/screen/screen.cpp b/src/ftxui/screen/screen.cpp index f98cc50..d83e424 100644 --- a/src/ftxui/screen/screen.cpp +++ b/src/ftxui/screen/screen.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "ftxui/dom/node.hpp" #include "ftxui/screen/string.hpp" @@ -149,6 +150,7 @@ Screen::Screen(int dimx, int dimy) } /// Produce a std::string that can be used to print the Screen on the terminal. +/// Don't forget to flush stdout. Alternatively, you can use Screen::Print(); std::string Screen::ToString() { std::wstringstream ss; @@ -158,7 +160,7 @@ std::string Screen::ToString() { for (int y = 0; y < dimy_; ++y) { if (y != 0) { UpdatePixelStyle(ss, previous_pixel, final_pixel); - ss << '\n'; + ss << L"\r\n"; } for (int x = 0; x < dimx_;) { auto& pixel = pixels_[y][x]; @@ -181,6 +183,10 @@ std::string Screen::ToString() { return to_string(ss.str()); } +void Screen::Print() { + std::cout << ToString() << std::flush << (char)0; +} + /// @brief Access a character a given position. /// @param x The character position along the x-axis. /// @param y The character position along the y-axis. @@ -254,6 +260,7 @@ void Screen::ApplyShader() { } } } + // clang-format on } // namespace ftxui diff --git a/src/ftxui/screen/terminal.cpp b/src/ftxui/screen/terminal.cpp index fae1de8..e86c82d 100644 --- a/src/ftxui/screen/terminal.cpp +++ b/src/ftxui/screen/terminal.cpp @@ -19,7 +19,7 @@ namespace ftxui { Terminal::Dimensions Terminal::Size() { #if defined(__EMSCRIPTEN__) - return Dimensions{80, 43}; + return Dimensions{140, 43}; #elif defined(_WIN32) CONSOLE_SCREEN_BUFFER_INFO csbi; int columns, rows; @@ -48,6 +48,10 @@ bool Contains(const std::string& s, const char* key) { static bool cached = false; Terminal::Color cached_supported_color; Terminal::Color ComputeColorSupport() { +#if defined(__EMSCRIPTEN__) + return Terminal::Color::TrueColor; +#endif + std::string COLORTERM = Safe(std::getenv("COLORTERM")); if (Contains(COLORTERM, "24bit") || Contains(COLORTERM, "truecolor")) return Terminal::Color::TrueColor;