22 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
ac93764bbf Fix CMake 3.12 compatibility by adding required install destinations
Co-authored-by: ArthurSonzogni <4759106+ArthurSonzogni@users.noreply.github.com>
2025-09-19 12:48:50 +00:00
copilot-swe-agent[bot]
c0cb3e84cc Initial plan 2025-09-19 12:43:10 +00:00
Benjamin Gwin
f21fcc1995 Fix use of uninitialized cursor variable (#1111)
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
The cursor_ variable was being default initialized, which causes
undefined behaviour when accessing properties in
ScreenInteractive::Draw. This caused a crash when running with UBSAN.

```
ftxui/src/ftxui/component/screen_interactive.cpp:852:17: runtime error:
load of value 4195502944, which is not a valid value for type 'Shape'
```

This change causes the shape variable to be explicitly initialized,
similar to the x and y members.

Co-authored-by: Benjamin Gwin <bgwin@google.com>
2025-09-09 07:34:35 +02:00
birland
f7ac35ed35 Add tic-tac-toe as an example project using FTXUI (#1109)
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-09-07 09:20:11 +02:00
nodeluna
fba510ec02 fixed a typo (#1110) 2025-09-07 09:19:17 +02:00
Xiao Di
775ad9ce5e Improved the installation method via Conan. (#1106)
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-09-01 10:02:57 +02:00
Samuel Bridgham
f5785fd3b4 Fixed bug in component/button example: (#1107)
- The '-1' and '+1' buttons now correctly increment and decrement.
 - Previously it was vice versa.s
2025-09-01 10:02:21 +02:00
Arthur Sonzogni
853d87e917 Fix HTML entities in README.md
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-08-29 07:36:35 +02:00
d06i
11f7132886 Add new project to the list (#1102)
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
Co-authored-by: d06i <llll@DESKTOP-C8VGJLV>
2025-08-26 12:47:10 +02:00
ArthurSonzogni
346f751527 Fix example in docs not being generated.
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
Fixed:https://github.com/ArthurSonzogni/FTXUI/issues/1088
2025-08-21 08:05:23 +02:00
Arthur Sonzogni
e56ff89cf3 Improve example style. (#1101)
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
Based uppon @yurenchen000 suggestion.

Fixed:https://github.com/ArthurSonzogni/FTXUI/issues/1090
2025-08-20 06:53:42 +02:00
Arthur Sonzogni
21b24a1b78 Fix slider Up key press. (#1099)
Some checks are pending
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Waiting to run
Build / Test modules (llvm, ubuntu-latest) (push) Waiting to run
Documentation / documentation (push) Waiting to run
The direction was inverted. It caused the inability to increase it.

Fixed:https://github.com/ArthurSonzogni/FTXUI/issues/1093
2025-08-19 09:34:30 +02:00
ArthurSonzogni
bfd07ba309 Example. Add missing file.
Some checks are pending
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Waiting to run
Build / Test modules (llvm, ubuntu-latest) (push) Waiting to run
Documentation / documentation (push) Waiting to run
The file "sw.js" was removed mistakenly

Fixed: https://github.com/ArthurSonzogni/FTXUI/issues/1098
2025-08-18 20:50:31 +02:00
ArthurSonzogni
d20b84f720 Fix receiver includes.
Some checks are pending
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Waiting to run
Build / Test modules (llvm, ubuntu-latest) (push) Waiting to run
Documentation / documentation (push) Waiting to run
2025-08-17 21:08:36 +02:00
ArthurSonzogni
0dde21f09e Fix Bazel build. 2025-08-17 19:23:53 +02:00
Sylko Olzscher
40e1fac3d4 Warn against Microsoft <windows.h> min and max macro (#1084)
Warn users they have defined the min/max macros which is not 
compatible with other code from the standard library or FTXUI.

Co-authored-by: Sylko Olzscher <sylko.olzscher@solostec.ch>
Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
2025-08-17 11:18:25 +02:00
Arthur Sonzogni
8ef18ab647 Remove pthread dependency
Some checks are pending
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Waiting to run
Build / Test modules (llvm, ubuntu-latest) (push) Waiting to run
Documentation / documentation (push) Waiting to run
2025-08-16 18:40:50 +02:00
tattwamasi
994915dbb9 Add ftxui convenience/umbrella module to cmake rules to fix #1083 (#1085)
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
* Add the umbrella module ftxui to the cmake module build.

* Update cpp20 modules documentation.
2025-07-27 11:39:46 +02:00
Ivan Deyna
3b359e8cd7 #1078: Fix Examples section link (#1079)
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-07-10 13:22:04 +02:00
Mirion
1073ba414d Remove redundant member from ButtonBase (#1076)
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-07-08 08:55:37 +02:00
Arthur Sonzogni
b78b97056b Stop using Sender/Receiver in TerminalInputParser. (#1073)
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
Stop using Sender/Receiver in TerminalInputParser.

This will help removing usage of thread.

At some point, my goal is to have an initialization step when installing
the ScreenInteractive so that we can provide the terminal ID
synchronously without losing some events. This will help with:
https://github.com/ArthurSonzogni/FTXUI/pull/1069
2025-07-02 15:23:01 +02:00
Zane Zhou
68fc9b1212 Fix ScreenInteractive::FixedSize screen stomps on the history terminal output (#1064)
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
2025-06-20 15:59:36 +02:00
67 changed files with 1475 additions and 734 deletions

View File

@@ -1,3 +1,5 @@
common --enable_bzlmod
build --features=layering_check build --features=layering_check
build --enable_bzlmod build --enable_bzlmod

View File

@@ -2,3 +2,6 @@
# http://clang.llvm.org/docs/ClangFormatStyleOptions.html # http://clang.llvm.org/docs/ClangFormatStyleOptions.html
BasedOnStyle: Chromium BasedOnStyle: Chromium
Standard: Cpp11 Standard: Cpp11
InsertBraces: true
InsertNewlineAtEOF: true

View File

@@ -13,7 +13,6 @@ load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
load(":bazel/ftxui.bzl", "ftxui_cc_library") load(":bazel/ftxui.bzl", "ftxui_cc_library")
load(":bazel/ftxui.bzl", "generate_examples") load(":bazel/ftxui.bzl", "generate_examples")
load(":bazel/ftxui.bzl", "windows_copts") load(":bazel/ftxui.bzl", "windows_copts")
load(":bazel/ftxui.bzl", "pthread_linkopts")
# A meta target depending on all of the ftxui submodules. # A meta target depending on all of the ftxui submodules.
# Note that component depends on dom and screen, so ftxui is just an alias for # Note that component depends on dom and screen, so ftxui is just an alias for
@@ -159,6 +158,12 @@ ftxui_cc_library(
"src/ftxui/component/resizable_split.cpp", "src/ftxui/component/resizable_split.cpp",
"src/ftxui/component/screen_interactive.cpp", "src/ftxui/component/screen_interactive.cpp",
"src/ftxui/component/slider.cpp", "src/ftxui/component/slider.cpp",
"src/ftxui/component/task.cpp",
"src/ftxui/component/task_internal.hpp",
"src/ftxui/component/task_queue.cpp",
"src/ftxui/component/task_queue.hpp",
"src/ftxui/component/task_runner.cpp",
"src/ftxui/component/task_runner.hpp",
"src/ftxui/component/terminal_input_parser.cpp", "src/ftxui/component/terminal_input_parser.cpp",
"src/ftxui/component/terminal_input_parser.hpp", "src/ftxui/component/terminal_input_parser.hpp",
"src/ftxui/component/util.cpp", "src/ftxui/component/util.cpp",
@@ -170,6 +175,9 @@ ftxui_cc_library(
# Private header from ftxui:screen. # Private header from ftxui:screen.
"src/ftxui/screen/string_internal.hpp", "src/ftxui/screen/string_internal.hpp",
"src/ftxui/screen/util.hpp", "src/ftxui/screen/util.hpp",
# Private header.
"include/ftxui/util/warn_windows_macro.hpp",
], ],
hdrs = [ hdrs = [
"include/ftxui/component/animation.hpp", "include/ftxui/component/animation.hpp",
@@ -184,7 +192,6 @@ ftxui_cc_library(
"include/ftxui/component/screen_interactive.hpp", "include/ftxui/component/screen_interactive.hpp",
"include/ftxui/component/task.hpp", "include/ftxui/component/task.hpp",
], ],
linkopts = pthread_linkopts(),
deps = [ deps = [
":dom", ":dom",
":screen", ":screen",
@@ -207,7 +214,6 @@ cc_test(
"src/ftxui/component/menu_test.cpp", "src/ftxui/component/menu_test.cpp",
"src/ftxui/component/modal_test.cpp", "src/ftxui/component/modal_test.cpp",
"src/ftxui/component/radiobox_test.cpp", "src/ftxui/component/radiobox_test.cpp",
"src/ftxui/component/receiver_test.cpp",
"src/ftxui/component/resizable_split_test.cpp", "src/ftxui/component/resizable_split_test.cpp",
"src/ftxui/component/slider_test.cpp", "src/ftxui/component/slider_test.cpp",
"src/ftxui/component/terminal_input_parser_test.cpp", "src/ftxui/component/terminal_input_parser_test.cpp",

View File

@@ -24,6 +24,13 @@ Next
import ftxui.util; import ftxui.util;
``` ```
Thanks @mikomikotaishi for PR #1015. Thanks @mikomikotaishi for PR #1015.
- Remove dependency on 'pthread'.
### Component
- Fix ScreenInteractive::FixedSize screen stomps on the preceding terminal
output. Thanks @zozowell in #1064.
- Fix vertical `ftxui::Slider`. The "up" key was previously decreasing the
value. Thanks @its-pablo in #1093 for reporting the issue.
6.1.9 (2025-05-07) 6.1.9 (2025-05-07)

View File

@@ -144,26 +144,20 @@ add_library(component
src/ftxui/component/resizable_split.cpp src/ftxui/component/resizable_split.cpp
src/ftxui/component/screen_interactive.cpp src/ftxui/component/screen_interactive.cpp
src/ftxui/component/slider.cpp src/ftxui/component/slider.cpp
src/ftxui/component/task.cpp
src/ftxui/component/task_internal.hpp
src/ftxui/component/task_queue.cpp
src/ftxui/component/task_queue.hpp
src/ftxui/component/task_runner.cpp
src/ftxui/component/task_runner.hpp
src/ftxui/component/terminal_input_parser.cpp src/ftxui/component/terminal_input_parser.cpp
src/ftxui/component/terminal_input_parser.hpp src/ftxui/component/terminal_input_parser.hpp
src/ftxui/component/util.cpp src/ftxui/component/util.cpp
src/ftxui/component/window.cpp src/ftxui/component/window.cpp
) )
target_link_libraries(dom target_link_libraries(dom PUBLIC screen)
PUBLIC screen target_link_libraries(component PUBLIC dom)
)
target_link_libraries(component
PUBLIC dom
)
if (NOT EMSCRIPTEN)
find_package(Threads)
target_link_libraries(component
PUBLIC Threads::Threads
)
endif()
include(cmake/ftxui_set_options.cmake) include(cmake/ftxui_set_options.cmake)
ftxui_set_options(screen) ftxui_set_options(screen)
@@ -184,8 +178,8 @@ include(cmake/ftxui_install.cmake)
include(cmake/ftxui_package.cmake) include(cmake/ftxui_package.cmake)
include(cmake/ftxui_modules.cmake) include(cmake/ftxui_modules.cmake)
add_subdirectory(doc)
add_subdirectory(examples) add_subdirectory(examples)
add_subdirectory(doc)
# You can generate ./examples_modules/ by running # You can generate ./examples_modules/ by running
# ./tools/generate_examples_modules.sh # ./tools/generate_examples_modules.sh

View File

@@ -1,3 +1,4 @@
<p align="center"> <p align="center">
<img src="https://github.com/ArthurSonzogni/FTXUI/assets/4759106/6925b6da-0a7e-49d9-883c-c890e1f36007" alt="Demo image"></img> <img src="https://github.com/ArthurSonzogni/FTXUI/assets/4759106/6925b6da-0a7e-49d9-883c-c890e1f36007" alt="Demo image"></img>
<br/> <br/>
@@ -18,7 +19,7 @@
<br/> <br/>
<a href="https://arthursonzogni.github.io/FTXUI/">Documentation</a> · <a href="https://arthursonzogni.github.io/FTXUI/">Documentation</a> ·
<a href="https://github.com/ArthurSonzogni/FTXUI/issues">Report a Bug</a> · <a href="https://github.com/ArthurSonzogni/FTXUI/issues">Report a Bug</a> ·
<a href="https://arthursonzogni.github.io/FTXUI/examples.html">Examples</a> . <a href="https://arthursonzogni.github.io/FTXUI/examples/">Examples</a> .
<a href="https://github.com/ArthurSonzogni/FTXUI/issues">Request Feature</a> · <a href="https://github.com/ArthurSonzogni/FTXUI/issues">Request Feature</a> ·
<a href="https://github.com/ArthurSonzogni/FTXUI/pulls">Send a Pull Request</a> <a href="https://github.com/ArthurSonzogni/FTXUI/pulls">Send a Pull Request</a>
@@ -362,6 +363,8 @@ Feel free to add your projects here:
- [FTB - tertminal file browser](https://github.com/Cyxuan0311/FTB) - [FTB - tertminal file browser](https://github.com/Cyxuan0311/FTB)
- [openJuice](https://github.com/mikomikotaishi/openJuice) - [openJuice](https://github.com/mikomikotaishi/openJuice)
- [SHOOT!](https://github.com/ShingZhanho/ENGG1340-Project-25Spring) - [SHOOT!](https://github.com/ShingZhanho/ENGG1340-Project-25Spring)
- [VerifySN (Fast Hash Tool)](https://github.com/d06i/verifySN)
- [tic-tac-toe](https://github.com/birland/tic-tac-toe)
### [cpp-best-practices/game_jam](https://github.com/cpp-best-practices/game_jam) ### [cpp-best-practices/game_jam](https://github.com/cpp-best-practices/game_jam)

View File

@@ -43,16 +43,6 @@ def windows_copts():
"//conditions:default": [], "//conditions:default": [],
}) })
def pthread_linkopts():
return select({
# With MSVC, threading is already built-in (you don't need -pthread.
"@rules_cc//cc/compiler:msvc-cl": [],
"@rules_cc//cc/compiler:clang-cl": [],
"@rules_cc//cc/compiler:clang": ["-pthread"],
"@rules_cc//cc/compiler:gcc": ["-pthread"],
"//conditions:default": ["-pthread"],
})
def ftxui_cc_library( def ftxui_cc_library(
name, name,
srcs = [], srcs = [],

Submodule build/_deps/googlebenchmark-src added at 015d1a091a

View File

@@ -0,0 +1,42 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
cmake_minimum_required(VERSION 3.31.6)
# Reject any attempt to use a toolchain file. We must not use one because
# we could be downloading it here. If the CMAKE_TOOLCHAIN_FILE environment
# variable is set, the cache variable will have been initialized from it.
unset(CMAKE_TOOLCHAIN_FILE CACHE)
unset(ENV{CMAKE_TOOLCHAIN_FILE})
# We name the project and the target for the ExternalProject_Add() call
# to something that will highlight to the user what we are working on if
# something goes wrong and an error message is produced.
project(googlebenchmark-populate NONE)
# Pass through things we've already detected in the main project to avoid
# paying the cost of redetecting them again in ExternalProject_Add()
set(GIT_EXECUTABLE [==[/usr/bin/git]==])
set(GIT_VERSION_STRING [==[2.51.0]==])
set_property(GLOBAL PROPERTY _CMAKE_FindGit_GIT_EXECUTABLE_VERSION
[==[/usr/bin/git;2.51.0]==]
)
include(ExternalProject)
ExternalProject_Add(googlebenchmark-populate
"UPDATE_DISCONNECTED" "False" "GIT_REPOSITORY" "https://github.com/google/benchmark" "EXTERNALPROJECT_INTERNAL_ARGUMENT_SEPARATOR" "GIT_TAG" "015d1a091af6937488242b70121858bce8fd40e9" "GIT_PROGRESS" "TRUE"
SOURCE_DIR "/home/runner/work/FTXUI/FTXUI/build/_deps/googlebenchmark-src"
BINARY_DIR "/home/runner/work/FTXUI/FTXUI/build/_deps/googlebenchmark-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
USES_TERMINAL_DOWNLOAD YES
USES_TERMINAL_UPDATE YES
USES_TERMINAL_PATCH YES
)

Submodule build/_deps/googletest-src added at 23ef29555e

View File

@@ -0,0 +1,42 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
cmake_minimum_required(VERSION 3.31.6)
# Reject any attempt to use a toolchain file. We must not use one because
# we could be downloading it here. If the CMAKE_TOOLCHAIN_FILE environment
# variable is set, the cache variable will have been initialized from it.
unset(CMAKE_TOOLCHAIN_FILE CACHE)
unset(ENV{CMAKE_TOOLCHAIN_FILE})
# We name the project and the target for the ExternalProject_Add() call
# to something that will highlight to the user what we are working on if
# something goes wrong and an error message is produced.
project(googletest-populate NONE)
# Pass through things we've already detected in the main project to avoid
# paying the cost of redetecting them again in ExternalProject_Add()
set(GIT_EXECUTABLE [==[/usr/bin/git]==])
set(GIT_VERSION_STRING [==[2.51.0]==])
set_property(GLOBAL PROPERTY _CMAKE_FindGit_GIT_EXECUTABLE_VERSION
[==[/usr/bin/git;2.51.0]==]
)
include(ExternalProject)
ExternalProject_Add(googletest-populate
"UPDATE_DISCONNECTED" "False" "GIT_REPOSITORY" "https://github.com/google/googletest" "EXTERNALPROJECT_INTERNAL_ARGUMENT_SEPARATOR" "GIT_TAG" "23ef29555ef4789f555f1ba8c51b4c52975f0907" "GIT_PROGRESS" "TRUE"
SOURCE_DIR "/home/runner/work/FTXUI/FTXUI/build/_deps/googletest-src"
BINARY_DIR "/home/runner/work/FTXUI/FTXUI/build/_deps/googletest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
USES_TERMINAL_DOWNLOAD YES
USES_TERMINAL_UPDATE YES
USES_TERMINAL_PATCH YES
)

View File

@@ -11,6 +11,9 @@ include(CMakePackageConfigHelpers)
install( install(
TARGETS screen dom component TARGETS screen dom component
EXPORT ftxui-targets EXPORT ftxui-targets
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
) )
install( install(

View File

@@ -6,6 +6,7 @@ add_library(ftxui-modules)
target_sources(ftxui-modules target_sources(ftxui-modules
PUBLIC FILE_SET CXX_MODULES FILES PUBLIC FILE_SET CXX_MODULES FILES
src/ftxui/ftxui.cppm
src/ftxui/component.cppm src/ftxui/component.cppm
src/ftxui/component/animation.cppm src/ftxui/component/animation.cppm
src/ftxui/component/captured_mouse.cppm src/ftxui/component/captured_mouse.cppm

View File

@@ -101,6 +101,5 @@ endfunction()
if (EMSCRIPTEN) if (EMSCRIPTEN)
string(APPEND CMAKE_CXX_FLAGS " -s USE_PTHREADS") string(APPEND CMAKE_CXX_FLAGS " -s USE_PTHREADS")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -s ASYNCIFY")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -s PROXY_TO_PTHREAD") string(APPEND CMAKE_EXE_LINKER_FLAGS " -s PROXY_TO_PTHREAD")
endif() endif()

View File

@@ -19,11 +19,10 @@ add_executable(ftxui-tests
src/ftxui/component/menu_test.cpp src/ftxui/component/menu_test.cpp
src/ftxui/component/modal_test.cpp src/ftxui/component/modal_test.cpp
src/ftxui/component/radiobox_test.cpp src/ftxui/component/radiobox_test.cpp
src/ftxui/util/ref_test.cpp
src/ftxui/component/receiver_test.cpp
src/ftxui/component/resizable_split_test.cpp src/ftxui/component/resizable_split_test.cpp
src/ftxui/component/screen_interactive_test.cpp src/ftxui/component/screen_interactive_test.cpp
src/ftxui/component/slider_test.cpp src/ftxui/component/slider_test.cpp
src/ftxui/component/task_test.cpp
src/ftxui/component/terminal_input_parser_test.cpp src/ftxui/component/terminal_input_parser_test.cpp
src/ftxui/component/toggle_test.cpp src/ftxui/component/toggle_test.cpp
src/ftxui/dom/blink_test.cpp src/ftxui/dom/blink_test.cpp
@@ -51,6 +50,7 @@ add_executable(ftxui-tests
src/ftxui/dom/vbox_test.cpp src/ftxui/dom/vbox_test.cpp
src/ftxui/screen/color_test.cpp src/ftxui/screen/color_test.cpp
src/ftxui/screen/string_test.cpp src/ftxui/screen/string_test.cpp
src/ftxui/util/ref_test.cpp
) )
target_link_libraries(ftxui-tests target_link_libraries(ftxui-tests

View File

@@ -12,8 +12,24 @@ FTXUI experimentally supports
compilation times and improve code organization. Each header has a compilation times and improve code organization. Each header has a
corresponding module. corresponding module.
**Example with CMake and Ninja** Use the FTXUI_BUILD_MODULES option to build the FTXUI project itself to provide C++ 20 modules,
for example with CMake and Ninja:
```sh
cmake \
-DCMAKE_GENERATOR=Ninja \
-DFTXUI_BUILD_MODULES=ON \
..
ninja
```
> [!NOTE]
> To use modules, you need a C++20 compatible compiler, CMake version 3.20 or
> higher, and use a compatible generator like Ninja. Note that Makefile
> generators **do not support modules**.
Then, in your own code you can consume the modules and code as normal:
```cpp ```cpp
import ftxui; import ftxui;
@@ -26,18 +42,25 @@ int main() {
} }
``` ```
```sh Note, the `ftxui` convenience module which simply pulls together all the modules:
cmake \
-DCMAKE_GENERATOR=Ninja \
-DFTXUI_BUILD_MODULES=ON \
..
ninja ```cpp
export import ftxui.component;
export import ftxui.dom;
export import ftxui.screen;
export import ftxui.util;
```
You can instead import only the module(s) you need if desired.
To properly find and link the modules with CMake, use `target_link_libraries` to get the right
compiler, linker, etc. flags.
```cmake
target_link_libraries(my_executable
#...whatever...
PRIVATE ftxui::modules
)
``` ```
> [!NOTE]
> To use modules, you need a C++20 compatible compiler, CMake version 3.20 or
> higher, and use a compatible generator like Ninja. Note that Makefile
> generators **do not support modules**.
### Module list ### Module list

View File

@@ -1,19 +1,99 @@
@page installation_conan Conan @page installation_conan Conan
@tableofcontents
Unofficial recipe for FTXUI exists on Conan Center: FTXUI can be easily obtained and integrated into your project using the Conan package manager.
<https://conan.io/center/recipes/ftxui>
## Prerequisites
First, ensure that Conan is installed on your system. If not, you can install it via pip:
```powershell
pip install conan
```
Conan often works in tandem with CMake, so you will need to have CMake installed as well. Once you have confirmed both Conan and CMake are installed, create a project directory, for example, `ftxui-demo`:
```powershell
mkdir C:\ftxui-demo
cd C:\ftxui-demo
```
## Configuration
After ensuring your environment is set up correctly, create a Conan configuration file `conanfile.txt`. This file is used to declare your project's dependencies. The community-maintained package for FTXUI can be found on [Conan Center](https://conan.io/center/recipes/ftxui).
> [!note] > [!note]
> This is an unofficial recipe. That means it is not maintained by the FTXUI > This is an unofficial build script. This means it is not maintained by the FTXUI
> team, but by the community. The package maintainers seems to actively update > team but by the community. The package maintainer appears to actively update it
> the package to the latest version. Thanks to the maintainers for their work! > to the latest releases. Many thanks to the maintainer for their work!
@todo If you are familiar with the process, please consider adding an "official" build script to Conan Center.
This could be a GitHub Action that automatically updates Conan Center upon new releases.
@todo Add instructions on how to use the conan recipe. ```ini
[requires]
ftxui/6.0.2
@todo Please consider adding an "official" recipe to Conan Center if know how. [generators]
It could be a github action that will automatically update the conan center CMakeDeps
when a new release is made. CMakeToolchain
[layout]
cmake_layout
```
## Install Dependencies and Build
Once configured, run the following command to install FTXUI and its dependencies:
```powershell
conan install . --output-folder=build --build=missing
```
This will download and install `ftxui/6.0.2` along with all its dependencies from Conan's remote repositories.
After the installation completes, you can test it by creating a `demo.cpp` file in your project directory:
```cpp
#include <ftxui/screen/screen.hpp>
#include <ftxui/dom/elements.hpp>
#include <iostream>
int main() {
using namespace ftxui;
auto document = hbox({
text(" Hello "),
text("FTXUI ") | bold | color(Color::Red),
text(" world! ")
});
auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
Render(screen, document);
std::cout << screen.ToString() << std::endl;
return 0;
}
```
If the test is successful, you can then create a `CMakeLists.txt` file in the project directory:
```cmake
cmake_minimum_required(VERSION 3.20)
project(ftxui-demo)
# Set the C++ standard
set(CMAKE_CXX_STANDARD 20)
# Find the FTXUI package installed via Conan
find_package(ftxui CONFIG REQUIRED)
# Create the executable
add_executable(demo demo.cpp)
# Link the executable to the FTXUI library
target_link_libraries(demo PRIVATE ftxui::component)
```
@todo 考虑到中国多数地区使用Conan很有可能遇到各种网络问题我想做一个定制的版本说明但是我对conan的了解有限再加上没有找到合适的资料因此这个计划短暂的被搁置了如果您知道方法欢迎在[中文版本](xiaoditx.girhub.io/public/docs/ftxui%E4%B8%AD%E6%96%87%E7%BF%BB%E8%AF%91/installation/conan/)的下方留下评论以提醒我
---
<div class="section_buttons"> <div class="section_buttons">

View File

@@ -15,16 +15,14 @@ add_subdirectory(component)
add_subdirectory(dom) add_subdirectory(dom)
if (EMSCRIPTEN) if (EMSCRIPTEN)
string(APPEND CMAKE_EXE_LINKER_FLAGS " -s ALLOW_MEMORY_GROWTH=1")
target_link_options(component PUBLIC "SHELL: -s ALLOW_MEMORY_GROWTH=1")
get_property(EXAMPLES GLOBAL PROPERTY FTXUI::EXAMPLES) get_property(EXAMPLES GLOBAL PROPERTY FTXUI::EXAMPLES)
foreach(file foreach(file
"index.css"
"index.html" "index.html"
"index.mjs" "index.mjs"
"index.css" "run_webassembly.py"
"sw.js" "sw.js"
"run_webassembly.py") )
configure_file(${file} ${file}) configure_file(${file} ${file})
endforeach(file) endforeach(file)
endif() endif()

View File

@@ -1,9 +1,64 @@
#include "ftxui/component/component.hpp" // Copyright 2020 Arthur Sonzogni. All rights reserved.
#include "ftxui/component/screen_interactive.hpp" // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#include <memory> // for shared_ptr, __shared_ptr_access
#include <string> // for operator+, to_string
int main(){ #include "ftxui/component/captured_mouse.hpp" // for ftxui
auto screen = ftxui::ScreenInteractive::Fullscreen(); #include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer
auto testComponent = ftxui::Renderer([](){return ftxui::text("test Component");}); #include "ftxui/component/component_base.hpp" // for ComponentBase
screen.Loop(testComponent); #include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
return 0; #include "ftxui/dom/elements.hpp" // for separator, gauge, text, Element, operator|, vbox, border
using namespace ftxui;
// This is a helper function to create a button with a custom style.
// The style is defined by a lambda function that takes an EntryState and
// returns an Element.
// We are using `center` to center the text inside the button, then `border` to
// add a border around the button, and finally `flex` to make the button fill
// the available space.
ButtonOption Style() {
auto option = ButtonOption::Animated();
option.transform = [](const EntryState& s) {
auto element = text(s.label);
if (s.focused) {
element |= bold;
}
return element | center | borderEmpty | flex;
};
return option;
}
int main() {
int value = 50;
// clang-format off
auto btn_dec_01 = Button("-1", [&] { value -= 1; }, Style());
auto btn_inc_01 = Button("+1", [&] { value += 1; }, Style());
auto btn_dec_10 = Button("-10", [&] { value -= 10; }, Style());
auto btn_inc_10 = Button("+10", [&] { value += 10; }, Style());
// clang-format on
// The tree of components. This defines how to navigate using the keyboard.
// The selected `row` is shared to get a grid layout.
int row = 0;
auto buttons = Container::Vertical({
Container::Horizontal({btn_dec_01, btn_inc_01}, &row) | flex,
Container::Horizontal({btn_dec_10, btn_inc_10}, &row) | flex,
});
// Modify the way to render them on screen:
auto component = Renderer(buttons, [&] {
return vbox({
text("value = " + std::to_string(value)),
separator(),
buttons->Render() | flex,
}) |
flex | border;
});
auto screen = ScreenInteractive::FitComponent();
screen.Loop(component);
return 0;
} }

View File

@@ -133,8 +133,9 @@ int main() {
float dy = 50.f; float dy = 50.f;
ys[x] = int(dy + 20 * cos(dx * 0.14) + 10 * sin(dx * 0.42)); ys[x] = int(dy + 20 * cos(dx * 0.14) + 10 * sin(dx * 0.42));
} }
for (int x = 1; x < 99; x++) for (int x = 1; x < 99; x++) {
c.DrawPointLine(x, ys[x], x + 1, ys[x + 1]); c.DrawPointLine(x, ys[x], x + 1, ys[x + 1]);
}
return canvas(std::move(c)); return canvas(std::move(c));
}); });

View File

@@ -82,10 +82,12 @@ int main() {
size(WIDTH, EQUAL, dimx) | size(HEIGHT, EQUAL, dimy) | size(WIDTH, EQUAL, dimx) | size(HEIGHT, EQUAL, dimy) |
bgcolor(Color::HSV(index * 25, 255, 255)) | bgcolor(Color::HSV(index * 25, 255, 255)) |
color(Color::Black); color(Color::Black);
if (element_xflex_grow) if (element_xflex_grow) {
element = element | xflex_grow; element = element | xflex_grow;
if (element_yflex_grow) }
if (element_yflex_grow) {
element = element | yflex_grow; element = element | yflex_grow;
}
return element; return element;
}; };
@@ -119,10 +121,12 @@ int main() {
group = group | notflex; group = group | notflex;
if (!group_xflex_grow) if (!group_xflex_grow) {
group = hbox(group, filler()); group = hbox(group, filler());
if (!group_yflex_grow) }
if (!group_yflex_grow) {
group = vbox(group, filler()); group = vbox(group, filler());
}
group = group | flex; group = group | flex;
return group; return group;

View File

@@ -1,11 +1,12 @@
// Copyright 2020 Arthur Sonzogni. All rights reserved. // Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.
#include <stddef.h> // for size_t #include <stddef.h> // for size_t
#include <array> // for array #include <array> // for array
#include <atomic> // for atomic #include <atomic> // for atomic
#include <chrono> // for operator""s, chrono_literals #include <chrono> // for operator""s, chrono_literals
#include <cmath> // for sin #include <cmath> // for sin
#include <ftxui/component/loop.hpp>
#include <functional> // for ref, reference_wrapper, function #include <functional> // for ref, reference_wrapper, function
#include <memory> // for allocator, shared_ptr, __shared_ptr_access #include <memory> // for allocator, shared_ptr, __shared_ptr_access
#include <string> // for string, basic_string, char_traits, operator+, to_string #include <string> // for string, basic_string, char_traits, operator+, to_string
@@ -269,7 +270,7 @@ int main() {
auto spinner_tab_renderer = Renderer([&] { auto spinner_tab_renderer = Renderer([&] {
Elements entries; Elements entries;
for (int i = 0; i < 22; ++i) { for (int i = 0; i < 22; ++i) {
entries.push_back(spinner(i, shift / 2) | bold | entries.push_back(spinner(i, shift / 5) | bold |
size(WIDTH, GREATER_THAN, 2) | border); size(WIDTH, GREATER_THAN, 2) | border);
} }
return hflow(std::move(entries)); return hflow(std::move(entries));
@@ -512,24 +513,20 @@ int main() {
}); });
}); });
std::atomic<bool> refresh_ui_continue = true; Loop loop(&screen, main_renderer);
std::thread refresh_ui([&] { while (!loop.HasQuitted()) {
while (refresh_ui_continue) { // Update the state of the application.
using namespace std::chrono_literals; shift++;
std::this_thread::sleep_for(0.05s);
// The |shift| variable belong to the main thread. `screen.Post(task)`
// will execute the update on the thread where |screen| lives (e.g. the
// main thread). Using `screen.Post(task)` is threadsafe.
screen.Post([&] { shift++; });
// After updating the state, request a new frame to be drawn. This is done
// by simulating a new "custom" event to be handled.
screen.Post(Event::Custom);
}
});
screen.Loop(main_renderer); // Request a new frame to be drawn.
refresh_ui_continue = false; screen.RequestAnimationFrame();
refresh_ui.join();
// Execute events, and draw the next frame.
loop.RunOnce();
// Sleep for a short duration to control the frame rate (60 FPS).
std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 60));
}
return 0; return 0;
} }

View File

@@ -22,10 +22,12 @@ MenuEntryOption Colored(ftxui::Color c) {
option.transform = [c](EntryState state) { option.transform = [c](EntryState state) {
state.label = (state.active ? "> " : " ") + state.label; state.label = (state.active ? "> " : " ") + state.label;
Element e = text(state.label) | color(c); Element e = text(state.label) | color(c);
if (state.focused) if (state.focused) {
e = e | inverted; e = e | inverted;
if (state.active) }
if (state.active) {
e = e | bold; e = e | bold;
}
return e; return e;
}; };
return option; return option;

View File

@@ -17,8 +17,9 @@ int main() {
std::vector<std::string> entries; std::vector<std::string> entries;
int selected = 0; int selected = 0;
for (int i = 0; i < 30; ++i) for (int i = 0; i < 30; ++i) {
entries.push_back("Entry " + std::to_string(i)); entries.push_back("Entry " + std::to_string(i));
}
auto radiobox = Menu(&entries, &selected); auto radiobox = Menu(&entries, &selected);
auto renderer = Renderer(radiobox, [&] { auto renderer = Renderer(radiobox, [&] {
return radiobox->Render() | vscroll_indicator | frame | return radiobox->Render() | vscroll_indicator | frame |

View File

@@ -17,8 +17,9 @@ int main() {
std::vector<std::string> entries; std::vector<std::string> entries;
int selected = 0; int selected = 0;
for (int i = 0; i < 100; ++i) for (int i = 0; i < 100; ++i) {
entries.push_back(std::to_string(i)); entries.push_back(std::to_string(i));
}
auto radiobox = Menu(&entries, &selected, MenuOption::Horizontal()); auto radiobox = Menu(&entries, &selected, MenuOption::Horizontal());
auto renderer = Renderer( auto renderer = Renderer(
radiobox, [&] { return radiobox->Render() | hscroll_indicator | frame; }); radiobox, [&] { return radiobox->Render() | hscroll_indicator | frame; });

View File

@@ -116,10 +116,12 @@ Component VMenu1(std::vector<std::string>* entries, int* selected) {
option.entries_option.transform = [](EntryState state) { option.entries_option.transform = [](EntryState state) {
state.label = (state.active ? "> " : " ") + state.label; state.label = (state.active ? "> " : " ") + state.label;
Element e = text(state.label); Element e = text(state.label);
if (state.focused) if (state.focused) {
e = e | bgcolor(Color::Blue); e = e | bgcolor(Color::Blue);
if (state.active) }
if (state.active) {
e = e | bold; e = e | bold;
}
return e; return e;
}; };
return Menu(entries, selected, option); return Menu(entries, selected, option);
@@ -130,10 +132,12 @@ Component VMenu2(std::vector<std::string>* entries, int* selected) {
option.entries_option.transform = [](EntryState state) { option.entries_option.transform = [](EntryState state) {
state.label += (state.active ? " <" : " "); state.label += (state.active ? " <" : " ");
Element e = hbox(filler(), text(state.label)); Element e = hbox(filler(), text(state.label));
if (state.focused) if (state.focused) {
e = e | bgcolor(Color::Red); e = e | bgcolor(Color::Red);
if (state.active) }
if (state.active) {
e = e | bold; e = e | bold;
}
return e; return e;
}; };
return Menu(entries, selected, option); return Menu(entries, selected, option);
@@ -144,13 +148,16 @@ Component VMenu3(std::vector<std::string>* entries, int* selected) {
option.entries_option.transform = [](EntryState state) { option.entries_option.transform = [](EntryState state) {
Element e = state.active ? text("[" + state.label + "]") Element e = state.active ? text("[" + state.label + "]")
: text(" " + state.label + " "); : text(" " + state.label + " ");
if (state.focused) if (state.focused) {
e = e | bold; e = e | bold;
}
if (state.focused) if (state.focused) {
e = e | color(Color::Blue); e = e | color(Color::Blue);
if (state.active) }
if (state.active) {
e = e | bold; e = e | bold;
}
return e; return e;
}; };
return Menu(entries, selected, option); return Menu(entries, selected, option);
@@ -245,10 +252,12 @@ Component HMenu5(std::vector<std::string>* entries, int* selected) {
animation::easing::ElasticOut); animation::easing::ElasticOut);
option.entries_option.transform = [](EntryState state) { option.entries_option.transform = [](EntryState state) {
Element e = text(state.label) | hcenter | flex; Element e = text(state.label) | hcenter | flex;
if (state.active && state.focused) if (state.active && state.focused) {
e = e | bold; e = e | bold;
if (!state.focused && !state.active) }
if (!state.focused && !state.active) {
e = e | dim; e = e | dim;
}
return e; return e;
}; };
option.underline.color_inactive = Color::Default; option.underline.color_inactive = Color::Default;

View File

@@ -20,8 +20,9 @@ using namespace ftxui;
Component DummyComponent(int id) { Component DummyComponent(int id) {
return Renderer([id](bool focused) { return Renderer([id](bool focused) {
auto t = text("component " + std::to_string(id)); auto t = text("component " + std::to_string(id));
if (focused) if (focused) {
t = t | inverted; t = t | inverted;
}
return t; return t;
}); });
} }

View File

@@ -17,8 +17,9 @@ int main() {
std::vector<std::string> entries; std::vector<std::string> entries;
int selected = 0; int selected = 0;
for (int i = 0; i < 30; ++i) for (int i = 0; i < 30; ++i) {
entries.push_back("RadioBox " + std::to_string(i)); entries.push_back("RadioBox " + std::to_string(i));
}
auto radiobox = Radiobox(&entries, &selected); auto radiobox = Radiobox(&entries, &selected);
auto renderer = Renderer(radiobox, [&] { auto renderer = Renderer(radiobox, [&] {
return radiobox->Render() | vscroll_indicator | frame | return radiobox->Render() | vscroll_indicator | frame |

View File

@@ -19,10 +19,11 @@ int main() {
// 1. Example of focusable renderer: // 1. Example of focusable renderer:
auto renderer_focusable = Renderer([](bool focused) { auto renderer_focusable = Renderer([](bool focused) {
if (focused) if (focused) {
return text("FOCUSABLE RENDERER()") | center | bold | border; return text("FOCUSABLE RENDERER()") | center | bold | border;
else } else {
return text(" Focusable renderer() ") | center | border; return text(" Focusable renderer() ") | center | border;
}
}); });
// 2. Examples of a non focusable renderer. // 2. Examples of a non focusable renderer.
@@ -33,10 +34,11 @@ int main() {
// 3. Renderer can wrap other components to redefine their Render() function. // 3. Renderer can wrap other components to redefine their Render() function.
auto button = Button("Wrapped quit button", screen.ExitLoopClosure()); auto button = Button("Wrapped quit button", screen.ExitLoopClosure());
auto renderer_wrap = Renderer(button, [&] { auto renderer_wrap = Renderer(button, [&] {
if (button->Focused()) if (button->Focused()) {
return button->Render() | bold | color(Color::Red); return button->Render() | bold | color(Color::Red);
else } else {
return button->Render(); return button->Render();
}
}); });
// Let's renderer everyone: // Let's renderer everyone:

View File

@@ -32,10 +32,12 @@ int main() {
// Plot a function: // Plot a function:
std::vector<int> ys(100); std::vector<int> ys(100);
for (int x = 0; x < 100; x++) for (int x = 0; x < 100; x++) {
ys[x] = int(80 + 20 * cos(x * 0.2)); ys[x] = int(80 + 20 * cos(x * 0.2));
for (int x = 0; x < 99; x++) }
for (int x = 0; x < 99; x++) {
c.DrawPointLine(x, ys[x], x + 1, ys[x + 1], Color::Red); c.DrawPointLine(x, ys[x], x + 1, ys[x + 1], Color::Red);
}
auto document = canvas(&c) | border; auto document = canvas(&c) | border;

View File

@@ -86,8 +86,9 @@ int main() {
auto render = [&]() { auto render = [&]() {
std::vector<Element> entries; std::vector<Element> entries;
for (auto& task : displayed_task) for (auto& task : displayed_task) {
entries.push_back(renderTask(task)); entries.push_back(renderTask(task));
}
return vbox({ return vbox({
// List of tasks. // List of tasks.
@@ -138,8 +139,9 @@ int main() {
std::this_thread::sleep_for(0.01s); std::this_thread::sleep_for(0.01s);
// Exit // Exit
if (nb_active + nb_queued == 0) if (nb_active + nb_queued == 0) {
break; break;
}
// Update the model for the next frame. // Update the model for the next frame.
updateModel(); updateModel();

View File

@@ -21,8 +21,9 @@ int main() {
for (int index = 0; index < 200; ++index) { for (int index = 0; index < 200; ++index) {
std::vector<Element> entries; std::vector<Element> entries;
for (int i = 0; i < 23; ++i) { for (int i = 0; i < 23; ++i) {
if (i != 0) if (i != 0) {
entries.push_back(separator()); entries.push_back(separator());
}
entries.push_back( // entries.push_back( //
hbox({ hbox({
text(std::to_string(i)) | size(WIDTH, EQUAL, 2), text(std::to_string(i)) | size(WIDTH, EQUAL, 2),

View File

@@ -1,15 +1,19 @@
@import url(https://fonts.googleapis.com/css?family=Khula:700); @import url(https://fonts.googleapis.com/css?family=Khula:700);
html {
--toc-width: 250px;
}
body { body {
background-color:#EEE; background-color: #EEE;
padding:0px; padding: 0px;
margin:0px; margin: 0px;
font-family: Khula, Helvetica, sans-serif; font-family: Khula, Helvetica, sans-serif;
font-size: 130%; font-size: 130%;
} }
.page { .page {
max-width:1300px; max-width: 1300px;
margin: auto; margin: auto;
padding: 10px; padding: 10px;
} }
@@ -20,7 +24,7 @@ a {
margin: 0 -.25rem; margin: 0 -.25rem;
padding: 0 .25rem; padding: 0 .25rem;
transition: color .3s ease-in-out, transition: color .3s ease-in-out,
box-shadow .3s ease-in-out; box-shadow .3s ease-in-out;
} }
a:hover { a:hover {
@@ -30,45 +34,48 @@ a:hover {
h1 { h1 {
text-decoration: underline; text-decoration: underline;
width:100%; width: 100%;
background-color: rgba(100,100,255,0.5); background-color: rgba(100, 100, 255, 0.5);
padding: 10px; padding: 10px;
margin: 0; margin: 0;
} }
#selectExample { #selectExample {
flex:1; flex: 1;
} }
#selectExample, #selectExample option { #selectExample,
#selectExample option {
font-size: 16px; font-size: 16px;
font-family: sans-serif; font-family: sans-serif;
font-weight: 700; font-weight: 700;
line-height: 1.3; line-height: 1.3;
border:0px; border: 0px;
background-color: #bbb; background-color: #bbb;
color:black; color: black;
} }
#selectExample:focus { #selectExample:focus {
outline:none; outline: none;
} }
#terminal { #terminal {
width:100%; width: 100%;
height 500px; height 500px;
height: calc(clamp(200px, 100vh - 300px, 900px)); height: calc(clamp(200px, 100vh - 300px, 900px));
overflow: hidden; overflow: hidden;
border:none; border: none;
background-color:black; padding: 10px;
margin: 10px;
} }
#terminalContainer { #terminalContainer {
overflow: hidden; overflow: hidden;
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 2px 10px 0px rgba(0,0,0,0.75), box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.75),
0px 2px 80px 0px rgba(0,0,0,0.50); 0px 2px 80px 0px rgba(0, 0, 0, 0.50);
background-color: black;
} }
.fakeButtons { .fakeButtons {
@@ -76,7 +83,7 @@ h1 {
width: 10px; width: 10px;
border-radius: 50%; border-radius: 50%;
border: 1px solid #000; border: 1px solid #000;
margin:6px; margin: 6px;
background-color: #ff3b47; background-color: #ff3b47;
border-color: #9d252b; border-color: #9d252b;
display: inline-block; display: inline-block;
@@ -95,13 +102,79 @@ h1 {
} }
.fakeMenu { .fakeMenu {
display:flex; display: flex;
flex-direction: row; flex-direction: row;
width:100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
height: 25px; height: 25px;
background-color: #bbb; background-color: #bbb;
color:black; color: black;
margin: 0 auto; margin: 0 auto;
overflow: hidden; overflow: hidden;
} }
.toc-container {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: var(--toc-width);
background: white;
padding: 0;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: thin;
}
.toc-title {
font-weight: bold;
margin-bottom: 5px;
font-size: 0.9em;
color: #555;
position: sticky;
transition: position 1.0s ease-in-out;
top: 0;
z-index: 1;
padding: 20px;
margin: 0;
border-bottom: 1px solid #ddd;
/* Gradient background for the title */
background-color: #f0f0f0;
}
.toc-item {
padding: 3px 8px;
margin: 0;
cursor: pointer;
font-size: 0.85em;
border-radius: 3px;
transition: background 0.2s;
}
.toc-item:hover {
background: #f0f0f0;
}
.toc-item.selected {
background: #e0e0e0;
font-weight: bold;
}
@media (max-width: 1024px) {
.toc-container {
display: none;
}
.page {
margin-left: 0;
}
}
@media (min-width: 1025px) {
.page {
margin-left: calc(var(--toc-width) + 20px);
}
}

View File

@@ -9,13 +9,18 @@
<script type="module" src="index.mjs"></script> <script type="module" src="index.mjs"></script>
</head> </head>
<body> <body>
<div class="toc-container">
<div class="toc-list"></div>
</div>
<script id="example_script"></script> <script id="example_script"></script>
<div class="page"> <div class="page">
<p> <p>
<a href="https://github.com/ArthurSonzogni/FTXUI">FTXUI</a> is a simple <a href="https://github.com/ArthurSonzogni/FTXUI">FTXUI</a> is a simple
functional C++ library for terminal user interface. <br/> functional C++ library for terminal user interface. <br/>
This showcases the: <a href="https://github.com/ArthurSonzogni/FTXUI/tree/master/examples">./example/</a> folder. <br/> This showcases the: <a
href="https://github.com/ArthurSonzogni/FTXUI/tree/master/examples">./example/</a>
folder. See <a id="source">source</a>.
</p> </p>
<div id="terminalContainer"> <div id="terminalContainer">

View File

@@ -55,7 +55,7 @@ const stdout = code => {
const stderr = code => { const stderr = code => {
if (code == 0 || code == 10) { if (code == 0 || code == 10) {
console.error(String.fromCodePoint(...stderr_buffer)); console.error(String.fromCodePoint(...stderr_buffer));
stderr_buffer = []; stderr_buffer.length = 0;
} else { } else {
stderr_buffer.push(code) stderr_buffer.push(code)
} }
@@ -89,12 +89,72 @@ window.Module = {
const resize_observer = new ResizeObserver(resize_handler); const resize_observer = new ResizeObserver(resize_handler);
resize_observer.observe(term_element); resize_observer.observe(term_element);
resize_handler(); resize_handler();
// Disable scrollbar
//term.write('\x1b[?47h')
}, },
}; };
const source = document.querySelector("#source");
source.href = "https://github.com/ArthurSonzogni/FTXUI/blob/main/examples/" + example + ".cpp";
const words = example.split('/') const words = example.split('/')
words[1] = "ftxui_example_" + words[1] + ".js" words[1] = "ftxui_example_" + words[1] + ".js"
document.querySelector("#example_script").src = words.join('/'); document.querySelector("#example_script").src = words.join('/');
// Table of Contents (TOC) for quick navigation.
// Get select element
const selectEl = document.querySelector('select#selectExample');
if (!selectEl) {
console.error('select#selectExample not found');
} else {
// Get TOC container
const tocContainer = document.querySelector('.toc-container');
const tocList = tocContainer.querySelector('.toc-list');
// Group options by directory
const groupedOptions = Array.from(selectEl.options).reduce((acc, option) => {
const [dir, file] = option.text.split('/');
if (!acc[dir]) {
acc[dir] = [];
}
acc[dir].push({ option, file });
return acc;
}, {});
// Generate TOC items
for (const dir in groupedOptions) {
const dirContainer = document.createElement('div');
const dirHeader = document.createElement('div');
dirHeader.textContent = dir;
dirHeader.className = 'toc-title';
dirContainer.appendChild(dirHeader);
groupedOptions[dir].forEach(({ option, file }) => {
const tocItem = document.createElement('div');
tocItem.textContent = file;
tocItem.className = 'toc-item';
if (selectEl.options[selectEl.selectedIndex].value === option.value) {
tocItem.classList.add('selected');
}
// Click handler
tocItem.addEventListener('click', () => {
for(let i=0; i<selectEl.options.length; ++i) {
if (selectEl.options[i].value == option.value) {
selectEl.selectedIndex = i;
break;
}
}
history.pushState({}, "", "?file=" + option.value);
location.reload();
});
dirContainer.appendChild(tocItem);
});
tocList.appendChild(dirContainer);
}
}''

View File

@@ -8,6 +8,7 @@
#include <memory> // for make_shared, shared_ptr #include <memory> // for make_shared, shared_ptr
#include <utility> // for forward #include <utility> // for forward
#include <ftxui/util/warn_windows_macro.hpp>
#include "ftxui/component/component_base.hpp" // for Component, Components #include "ftxui/component/component_base.hpp" // for Component, Components
#include "ftxui/component/component_options.hpp" // for ButtonOption, CheckboxOption, MenuOption #include "ftxui/component/component_options.hpp" // for ButtonOption, CheckboxOption, MenuOption
#include "ftxui/dom/elements.hpp" // for Element #include "ftxui/dom/elements.hpp" // for Element

View File

@@ -9,8 +9,9 @@
#include <ftxui/dom/direction.hpp> // for Direction, Direction::Left, Direction::Right, Direction::Down #include <ftxui/dom/direction.hpp> // for Direction, Direction::Left, Direction::Right, Direction::Down
#include <ftxui/dom/elements.hpp> // for Element, separator #include <ftxui/dom/elements.hpp> // for Element, separator
#include <ftxui/util/ref.hpp> // for Ref, ConstRef, StringRef #include <ftxui/util/ref.hpp> // for Ref, ConstRef, StringRef
#include <functional> // for function #include <ftxui/util/warn_windows_macro.hpp>
#include <string> // for string #include <functional> // for function
#include <string> // for string
#include "ftxui/component/component_base.hpp" // for Component #include "ftxui/component/component_base.hpp" // for Component
#include "ftxui/screen/color.hpp" // for Color, Color::GrayDark, Color::White #include "ftxui/screen/color.hpp" // for Color, Color::GrayDark, Color::White

View File

@@ -4,6 +4,7 @@
#ifndef FTXUI_COMPONENT_RECEIVER_HPP_ #ifndef FTXUI_COMPONENT_RECEIVER_HPP_
#define FTXUI_COMPONENT_RECEIVER_HPP_ #define FTXUI_COMPONENT_RECEIVER_HPP_
#include <ftxui/util/warn_windows_macro.hpp>
#include <algorithm> // for copy, max #include <algorithm> // for copy, max
#include <atomic> // for atomic, __atomic_base #include <atomic> // for atomic, __atomic_base
#include <condition_variable> // for condition_variable #include <condition_variable> // for condition_variable
@@ -14,6 +15,8 @@
namespace ftxui { namespace ftxui {
// Deprecated
//
// Usage: // Usage:
// //
// Initialization: // Initialization:
@@ -39,17 +42,24 @@ namespace ftxui {
// Receiver::Receive() returns true when there are no more senders. // Receiver::Receive() returns true when there are no more senders.
// clang-format off // clang-format off
// Deprecated:
template<class T> class SenderImpl; template<class T> class SenderImpl;
// Deprecated:
template<class T> class ReceiverImpl; template<class T> class ReceiverImpl;
// Deprecated:
// Deprecated:
template<class T> using Sender = std::unique_ptr<SenderImpl<T>>; template<class T> using Sender = std::unique_ptr<SenderImpl<T>>;
// Deprecated:
template<class T> using Receiver = std::unique_ptr<ReceiverImpl<T>>; template<class T> using Receiver = std::unique_ptr<ReceiverImpl<T>>;
// Deprecated:
template<class T> Receiver<T> MakeReceiver(); template<class T> Receiver<T> MakeReceiver();
// clang-format on // clang-format on
// ---- Implementation part ---- // ---- Implementation part ----
template <class T> template <class T>
// Deprecated:
class SenderImpl { class SenderImpl {
public: public:
SenderImpl(const SenderImpl&) = delete; SenderImpl(const SenderImpl&) = delete;

View File

@@ -4,12 +4,10 @@
#ifndef FTXUI_COMPONENT_SCREEN_INTERACTIVE_HPP #ifndef FTXUI_COMPONENT_SCREEN_INTERACTIVE_HPP
#define FTXUI_COMPONENT_SCREEN_INTERACTIVE_HPP #define FTXUI_COMPONENT_SCREEN_INTERACTIVE_HPP
#include <atomic> // for atomic #include <atomic> // for atomic
#include <ftxui/component/receiver.hpp> // for Receiver, Sender #include <functional> // for function
#include <functional> // for function #include <memory> // for shared_ptr
#include <memory> // for shared_ptr #include <string> // for string
#include <string> // for string
#include <thread> // for thread
#include "ftxui/component/animation.hpp" // for TimePoint #include "ftxui/component/animation.hpp" // for TimePoint
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
@@ -26,6 +24,10 @@ struct Event;
using Component = std::shared_ptr<ComponentBase>; using Component = std::shared_ptr<ComponentBase>;
class ScreenInteractivePrivate; class ScreenInteractivePrivate;
namespace task {
class TaskRunner;
}
/// @brief ScreenInteractive is a `Screen` that can handle events, run a main /// @brief ScreenInteractive is a `Screen` that can handle events, run a main
/// loop, and manage components. /// loop, and manage components.
/// ///
@@ -40,6 +42,9 @@ class ScreenInteractive : public Screen {
static ScreenInteractive FitComponent(); static ScreenInteractive FitComponent();
static ScreenInteractive TerminalOutput(); static ScreenInteractive TerminalOutput();
// Destructor.
~ScreenInteractive();
// Options. Must be called before Loop(). // Options. Must be called before Loop().
void TrackMouse(bool enable = true); void TrackMouse(bool enable = true);
@@ -97,6 +102,10 @@ class ScreenInteractive : public Screen {
void Signal(int signal); void Signal(int signal);
void FetchTerminalEvents();
void PostAnimationTask();
ScreenInteractive* suspended_screen_ = nullptr; ScreenInteractive* suspended_screen_ = nullptr;
enum class Dimension { enum class Dimension {
FitComponent, FitComponent,
@@ -113,15 +122,10 @@ class ScreenInteractive : public Screen {
bool track_mouse_ = true; bool track_mouse_ = true;
Sender<Task> task_sender_;
Receiver<Task> task_receiver_;
std::string set_cursor_position; std::string set_cursor_position;
std::string reset_cursor_position; std::string reset_cursor_position;
std::atomic<bool> quit_{false}; std::atomic<bool> quit_{false};
std::thread event_listener_;
std::thread animation_listener_;
bool animation_requested_ = false; bool animation_requested_ = false;
animation::TimePoint previous_animation_time_; animation::TimePoint previous_animation_time_;
@@ -156,8 +160,14 @@ class ScreenInteractive : public Screen {
std::unique_ptr<Selection> selection_; std::unique_ptr<Selection> selection_;
std::function<void()> selection_on_change_; std::function<void()> selection_on_change_;
// PIMPL private implementation idiom (Pimpl).
struct Internal;
std::unique_ptr<Internal> internal_;
friend class Loop; friend class Loop;
Component component_;
public: public:
class Private { class Private {
public: public:

View File

@@ -12,7 +12,6 @@
namespace ftxui { namespace ftxui {
/// @brief FlexboxConfig is a configuration structure that defines the layout /// @brief FlexboxConfig is a configuration structure that defines the layout
/// properties for a flexbox container. /// properties for a flexbox container.
// //

View File

@@ -20,6 +20,9 @@ class Image {
Image() = delete; Image() = delete;
Image(int dimx, int dimy); Image(int dimx, int dimy);
// Destructor:
virtual ~Image() = default;
// Access a character in the grid at a given position. // Access a character in the grid at a given position.
std::string& at(int x, int y); std::string& at(int x, int y);
const std::string& at(int x, int y) const; const std::string& at(int x, int y) const;

View File

@@ -11,7 +11,6 @@
#include "ftxui/screen/image.hpp" // for Pixel, Image #include "ftxui/screen/image.hpp" // for Pixel, Image
#include "ftxui/screen/terminal.hpp" // for Dimensions #include "ftxui/screen/terminal.hpp" // for Dimensions
#include "ftxui/util/autoreset.hpp" // for AutoReset
namespace ftxui { namespace ftxui {
@@ -31,6 +30,9 @@ class Screen : public Image {
static Screen Create(Dimensions dimension); static Screen Create(Dimensions dimension);
static Screen Create(Dimensions width, Dimensions height); static Screen Create(Dimensions width, Dimensions height);
// Destructor:
~Screen() override = default;
std::string ToString() const; std::string ToString() const;
// Print the Screen on to the terminal. // Print the Screen on to the terminal.
@@ -58,7 +60,7 @@ class Screen : public Image {
BarBlinking = 5, BarBlinking = 5,
Bar = 6, Bar = 6,
}; };
Shape shape; Shape shape = Hidden;
}; };
Cursor cursor() const { return cursor_; } Cursor cursor() const { return cursor_; }

View File

@@ -0,0 +1,18 @@
// Copyright 2025 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_UTIL_WARN_WINDOWS_MACRO_H_
#define FTXUI_UTIL_WARN_WINDOWS_MACRO_H_
#ifdef min
#error \
"The macro 'min' is defined, which conflicts with the standard C++ library and FTXUI. This is often caused by including <windows.h>. To fix this, add '#define NOMINMAX' before including <windows.h>, or pass '/DNOMINMAX' as a compiler flag."
#endif
#ifdef max
#error \
"The macro 'max' is defined, which conflicts with the standard C++ library and FTXUI. This is often caused by including <windows.h>. To fix this, add '#define NOMINMAX' before including <windows.h>, or pass '/DNOMINMAX' as a compiler flag."
#endif
#endif // FTXUI_UTIL_WARN_WINDOWS_MACRO_H_

View File

@@ -139,7 +139,6 @@ class ButtonBase : public ComponentBase, public ButtonOption {
private: private:
bool mouse_hover_ = false; bool mouse_hover_ = false;
Box box_; Box box_;
ButtonOption option_;
float animation_background_ = 0; float animation_background_ = 0;
float animation_foreground_ = 0; float animation_foreground_ = 0;
animation::Animator animator_background_ = animation::Animator animator_background_ =

View File

@@ -2,6 +2,7 @@
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.
#include <cassert> #include <cassert>
#include <ftxui/component/event.hpp>
#include <vector> #include <vector>
#include "ftxui/component/component.hpp" #include "ftxui/component/component.hpp"
#include "ftxui/component/terminal_input_parser.hpp" #include "ftxui/component/terminal_input_parser.hpp"
@@ -22,8 +23,9 @@ bool GeneratorBool(const char*& data, size_t& size) {
std::string GeneratorString(const char*& data, size_t& size) { std::string GeneratorString(const char*& data, size_t& size) {
int index = 0; int index = 0;
while (index < size && data[index]) while (index < size && data[index]) {
++index; ++index;
}
auto out = std::string(data, data + index); auto out = std::string(data, data + index);
data += index; data += index;
@@ -39,8 +41,9 @@ std::string GeneratorString(const char*& data, size_t& size) {
} }
int GeneratorInt(const char* data, size_t size) { int GeneratorInt(const char* data, size_t size) {
if (size == 0) if (size == 0) {
return 0; return 0;
}
auto out = int(data[0]); auto out = int(data[0]);
data++; data++;
size--; size--;
@@ -112,8 +115,9 @@ Components GeneratorComponents(const char*& data, size_t& size, int depth);
Component GeneratorComponent(const char*& data, size_t& size, int depth) { Component GeneratorComponent(const char*& data, size_t& size, int depth) {
depth--; depth--;
int value = GeneratorInt(data, size); int value = GeneratorInt(data, size);
if (depth <= 0) if (depth <= 0) {
return Button(GeneratorString(data, size), [] {}); return Button(GeneratorString(data, size), [] {});
}
constexpr int value_max = 19; constexpr int value_max = 19;
value = (value % value_max + value_max) % value_max; value = (value % value_max + value_max) % value_max;
@@ -212,16 +216,17 @@ extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) {
auto screen = auto screen =
Screen::Create(Dimension::Fixed(width), Dimension::Fixed(height)); Screen::Create(Dimension::Fixed(width), Dimension::Fixed(height));
auto event_receiver = MakeReceiver<Task>(); // Generate some events.
{ std::vector<Event> events;
auto parser = TerminalInputParser(event_receiver->MakeSender()); auto parser =
for (size_t i = 0; i < size; ++i) TerminalInputParser([&](const Event& event) { events.push_back(event); });
parser.Add(data[i]);
for (size_t i = 0; i < size; ++i) {
parser.Add(data[i]);
} }
Task event; for (const auto& event : events) {
while (event_receiver->Receive(&event)) { component->OnEvent(event);
component->OnEvent(std::get<Event>(event));
auto document = component->Render(); auto document = component->Render();
Render(screen, document); Render(screen, document);
} }

View File

@@ -12,9 +12,15 @@ export module ftxui.component.receiver;
* @brief The FTXUI ftxui:: namespace * @brief The FTXUI ftxui:: namespace
*/ */
export namespace ftxui { export namespace ftxui {
// Deprecated:
using ftxui::SenderImpl; using ftxui::SenderImpl;
// Deprecated:
using ftxui::ReceiverImpl; using ftxui::ReceiverImpl;
// Deprecated:
using ftxui::Sender; using ftxui::Sender;
// Deprecated:
using ftxui::Receiver; using ftxui::Receiver;
// Deprecated:
using ftxui::MakeReceiver; using ftxui::MakeReceiver;
// Deprecated:
} }

View File

@@ -1,81 +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 <thread> // for thread
#include <utility> // for move
#include "ftxui/component/receiver.hpp"
#include "gtest/gtest.h" // for AssertionResult, Message, Test, TestPartResult, EXPECT_EQ, EXPECT_TRUE, EXPECT_FALSE, TEST
// NOLINTBEGIN
namespace ftxui {
TEST(Receiver, Basic) {
auto receiver = MakeReceiver<char>();
auto sender = receiver->MakeSender();
sender->Send('a');
sender->Send('b');
sender->Send('c');
sender.reset();
char a, b, c, d;
EXPECT_TRUE(receiver->Receive(&a));
EXPECT_TRUE(receiver->Receive(&b));
EXPECT_TRUE(receiver->Receive(&c));
EXPECT_FALSE(receiver->Receive(&d));
EXPECT_EQ(a, 'a');
EXPECT_EQ(b, 'b');
EXPECT_EQ(c, 'c');
}
TEST(Receiver, BasicWithThread) {
auto r1 = MakeReceiver<char>();
auto r2 = MakeReceiver<char>();
auto r3 = MakeReceiver<char>();
auto s1 = r1->MakeSender();
auto s2 = r2->MakeSender();
auto s3 = r3->MakeSender();
auto s1_bis = r1->MakeSender();
auto stream = [](Receiver<char> receiver, Sender<char> sender) {
char c;
while (receiver->Receive(&c))
sender->Send(c);
};
// Convert data from a different thread.
auto t12 = std::thread(stream, std::move(r1), std::move(s2));
auto t23 = std::thread(stream, std::move(r2), std::move(s3));
// Send some data.
s1->Send('1');
s1_bis->Send('2');
s1->Send('3');
s1_bis->Send('4');
// Close the stream.
s1.reset();
s1_bis.reset();
char c;
EXPECT_TRUE(r3->Receive(&c));
EXPECT_EQ(c, '1');
EXPECT_TRUE(r3->Receive(&c));
EXPECT_EQ(c, '2');
EXPECT_TRUE(r3->Receive(&c));
EXPECT_EQ(c, '3');
EXPECT_TRUE(r3->Receive(&c));
EXPECT_EQ(c, '4');
EXPECT_FALSE(r3->Receive(&c));
// Thread will end at the end of the stream.
t12.join();
t23.join();
}
} // namespace ftxui
// NOLINTEND

View File

@@ -17,24 +17,22 @@
#include <memory> #include <memory>
#include <stack> // for stack #include <stack> // for stack
#include <string> #include <string>
#include <thread> // for thread, sleep_for #include <thread> // for thread, sleep_for
#include <tuple> // for _Swallow_assign, ignore #include <tuple> // for _Swallow_assign, ignore
#include <type_traits> // for decay_t #include <utility> // for move, swap
#include <utility> // for move, swap #include <variant> // for visit, variant
#include <variant> // for visit, variant #include <vector> // for vector
#include <vector> // for vector
#include "ftxui/component/animation.hpp" // for TimePoint, Clock, Duration, Params, RequestAnimationFrame #include "ftxui/component/animation.hpp" // for TimePoint, Clock, Duration, Params, RequestAnimationFrame
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
#include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/event.hpp" // for Event #include "ftxui/component/event.hpp" // for Event
#include "ftxui/component/loop.hpp" // for Loop #include "ftxui/component/loop.hpp" // for Loop
#include "ftxui/component/receiver.hpp" // for ReceiverImpl, Sender, MakeReceiver, SenderImpl, Receiver #include "ftxui/component/task_runner.hpp"
#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser #include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
#include "ftxui/dom/node.hpp" // for Node, Render #include "ftxui/dom/node.hpp" // for Node, Render
#include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/screen/pixel.hpp" // for Pixel
#include "ftxui/screen/terminal.hpp" // for Dimensions, Size #include "ftxui/screen/terminal.hpp" // for Dimensions, Size
#include "ftxui/screen/util.hpp" // for util::clamp #include "ftxui/screen/util.hpp" // for util::clamp
#include "ftxui/util/autoreset.hpp" // for AutoReset
#if defined(_WIN32) #if defined(_WIN32)
#define DEFINE_CONSOLEV2_PROPERTIES #define DEFINE_CONSOLEV2_PROPERTIES
@@ -47,9 +45,11 @@
#error Must be compiled in UNICODE mode #error Must be compiled in UNICODE mode
#endif #endif
#else #else
#include <fcntl.h>
#include <sys/select.h> // for select, FD_ISSET, FD_SET, FD_ZERO, fd_set, timeval #include <sys/select.h> // for select, FD_ISSET, FD_SET, FD_ZERO, fd_set, timeval
#include <termios.h> // for tcsetattr, termios, tcgetattr, TCSANOW, cc_t, ECHO, ICANON, VMIN, VTIME #include <termios.h> // for tcsetattr, termios, tcgetattr, TCSANOW, cc_t, ECHO, ICANON, VMIN, VTIME
#include <unistd.h> // for STDIN_FILENO, read #include <unistd.h> // for STDIN_FILENO, read
#include <cerrno>
#endif #endif
// Quick exit is missing in standard CLang headers // Quick exit is missing in standard CLang headers
@@ -59,6 +59,20 @@
namespace ftxui { namespace ftxui {
struct ScreenInteractive::Internal {
// Convert char to Event.
TerminalInputParser terminal_input_parser;
task::TaskRunner task_runner;
// The last time a character was received.
std::chrono::time_point<std::chrono::steady_clock> last_char_time =
std::chrono::steady_clock::now();
explicit Internal(std::function<void(Event)> out)
: terminal_input_parser(std::move(out)) {}
};
namespace animation { namespace animation {
void RequestAnimationFrame() { void RequestAnimationFrame() {
auto* screen = ScreenInteractive::Active(); auto* screen = ScreenInteractive::Active();
@@ -82,73 +96,9 @@ constexpr int timeout_milliseconds = 20;
timeout_milliseconds * 1000; timeout_milliseconds * 1000;
#if defined(_WIN32) #if defined(_WIN32)
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
auto console = GetStdHandle(STD_INPUT_HANDLE);
auto parser = TerminalInputParser(out->Clone());
while (!*quit) {
// Throttle ReadConsoleInput by waiting 250ms, this wait function will
// return if there is input in the console.
auto wait_result = WaitForSingleObject(console, timeout_milliseconds);
if (wait_result == WAIT_TIMEOUT) {
parser.Timeout(timeout_milliseconds);
continue;
}
DWORD number_of_events = 0;
if (!GetNumberOfConsoleInputEvents(console, &number_of_events))
continue;
if (number_of_events <= 0)
continue;
std::vector<INPUT_RECORD> records{number_of_events};
DWORD number_of_events_read = 0;
ReadConsoleInput(console, records.data(), (DWORD)records.size(),
&number_of_events_read);
records.resize(number_of_events_read);
for (const auto& r : records) {
switch (r.EventType) {
case KEY_EVENT: {
auto key_event = r.Event.KeyEvent;
// ignore UP key events
if (key_event.bKeyDown == FALSE)
continue;
std::wstring wstring;
wstring += key_event.uChar.UnicodeChar;
for (auto it : to_string(wstring)) {
parser.Add(it);
}
} break;
case WINDOW_BUFFER_SIZE_EVENT:
out->Send(Event::Special({0}));
break;
case MENU_EVENT:
case FOCUS_EVENT:
case MOUSE_EVENT:
// TODO(mauve): Implement later.
break;
}
}
}
}
#elif defined(__EMSCRIPTEN__) #elif defined(__EMSCRIPTEN__)
#include <emscripten.h> #include <emscripten.h>
// Read char from the terminal.
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
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);
}
}
extern "C" { extern "C" {
EMSCRIPTEN_KEEPALIVE EMSCRIPTEN_KEEPALIVE
void ftxui_on_resize(int columns, int rows) { void ftxui_on_resize(int columns, int rows) {
@@ -162,8 +112,8 @@ void ftxui_on_resize(int columns, int rows) {
#else // POSIX (Linux & Mac) #else // POSIX (Linux & Mac)
int CheckStdinReady(int usec_timeout) { int CheckStdinReady() {
timeval tv = {0, usec_timeout}; // NOLINT timeval tv = {0, 0}; // NOLINT
fd_set fds; fd_set fds;
FD_ZERO(&fds); // NOLINT FD_ZERO(&fds); // NOLINT
FD_SET(STDIN_FILENO, &fds); // NOLINT FD_SET(STDIN_FILENO, &fds); // NOLINT
@@ -171,24 +121,6 @@ int CheckStdinReady(int usec_timeout) {
return FD_ISSET(STDIN_FILENO, &fds); // NOLINT return FD_ISSET(STDIN_FILENO, &fds); // NOLINT
} }
// Read char from the terminal.
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
auto parser = TerminalInputParser(std::move(out));
while (!*quit) {
if (!CheckStdinReady(timeout_microseconds)) {
parser.Timeout(timeout_milliseconds);
continue;
}
const size_t buffer_size = 100;
std::array<char, buffer_size> buffer; // NOLINT;
size_t l = read(fileno(stdin), buffer.data(), buffer_size); // NOLINT
for (size_t i = 0; i < l; ++i) {
parser.Add(buffer[i]); // NOLINT
}
}
}
#endif #endif
std::stack<Closure> on_exit_functions; // NOLINT std::stack<Closure> on_exit_functions; // NOLINT
@@ -335,15 +267,6 @@ class CapturedMouseImpl : public CapturedMouseInterface {
std::function<void(void)> callback_; std::function<void(void)> callback_;
}; };
void AnimationListener(std::atomic<bool>* quit, Sender<Task> out) {
// Animation at around 60fps.
const auto time_delta = std::chrono::milliseconds(15);
while (!*quit) {
out->Send(AnimationTask());
std::this_thread::sleep_for(time_delta);
}
}
} // namespace } // namespace
ScreenInteractive::ScreenInteractive(Dimension dimension, ScreenInteractive::ScreenInteractive(Dimension dimension,
@@ -353,7 +276,8 @@ ScreenInteractive::ScreenInteractive(Dimension dimension,
: Screen(dimx, dimy), : Screen(dimx, dimy),
dimension_(dimension), dimension_(dimension),
use_alternative_screen_(use_alternative_screen) { use_alternative_screen_(use_alternative_screen) {
task_receiver_ = MakeReceiver<Task>(); internal_ = std::make_unique<Internal>(
[&](Event event) { PostEvent(std::move(event)); });
} }
// static // static
@@ -381,10 +305,10 @@ ScreenInteractive ScreenInteractive::Fullscreen() {
ScreenInteractive ScreenInteractive::FullscreenPrimaryScreen() { ScreenInteractive ScreenInteractive::FullscreenPrimaryScreen() {
auto terminal = Terminal::Size(); auto terminal = Terminal::Size();
return { return {
Dimension::Fullscreen, Dimension::Fullscreen,
terminal.dimx, terminal.dimx,
terminal.dimy, terminal.dimy,
/*use_alternative_screen=*/false, /*use_alternative_screen=*/false,
}; };
} }
@@ -409,11 +333,13 @@ ScreenInteractive ScreenInteractive::TerminalOutput() {
return { return {
Dimension::TerminalOutput, Dimension::TerminalOutput,
terminal.dimx, terminal.dimx,
terminal.dimy, // Best guess. terminal.dimy, // Best guess.
/*use_alternative_screen=*/false, /*use_alternative_screen=*/false,
}; };
} }
ScreenInteractive::~ScreenInteractive() = default;
/// Create a ScreenInteractive whose width and height match the component being /// Create a ScreenInteractive whose width and height match the component being
/// drawn. /// drawn.
// static // static
@@ -421,8 +347,8 @@ ScreenInteractive ScreenInteractive::FitComponent() {
auto terminal = Terminal::Size(); auto terminal = Terminal::Size();
return { return {
Dimension::FitComponent, Dimension::FitComponent,
terminal.dimx, // Best guess. terminal.dimx, // Best guess.
terminal.dimy, // Best guess. terminal.dimy, // Best guess.
false, false,
}; };
} }
@@ -449,13 +375,9 @@ void ScreenInteractive::TrackMouse(bool enable) {
/// @brief Add a task to the main loop. /// @brief Add a task to the main loop.
/// It will be executed later, after every other scheduled tasks. /// It will be executed later, after every other scheduled tasks.
void ScreenInteractive::Post(Task task) { void ScreenInteractive::Post(Task task) {
// Task/Events sent toward inactive screen or screen waiting to become internal_->task_runner.PostTask([this, task = std::move(task)]() mutable {
// inactive are dropped. HandleTask(component_, task);
if (!task_sender_) { });
return;
}
task_sender_->Send(std::move(task));
} }
/// @brief Add an event to the main loop. /// @brief Add an event to the main loop.
@@ -499,7 +421,7 @@ void ScreenInteractive::Loop(Component component) { // NOLINT
/// @brief Return whether the main loop has been quit. /// @brief Return whether the main loop has been quit.
bool ScreenInteractive::HasQuitted() { bool ScreenInteractive::HasQuitted() {
return task_receiver_->HasQuitted(); return quit_;
} }
// private // private
@@ -656,7 +578,15 @@ void ScreenInteractive::Install() {
SetConsoleMode(stdin_handle, in_mode); SetConsoleMode(stdin_handle, in_mode);
SetConsoleMode(stdout_handle, out_mode); SetConsoleMode(stdout_handle, out_mode);
#else #else // POSIX (Linux & Mac)
// #if defined(__EMSCRIPTEN__)
//// Reading stdin isn't blocking.
// int flags = fcntl(0, F_GETFL, 0);
// fcntl(0, F_SETFL, flags | O_NONBLOCK);
//// Restore the terminal configuration on exit.
// on_exit_functions.emplace([flags] { fcntl(0, F_SETFL, flags); });
// #endif
for (const int signal : {SIGWINCH, SIGTSTP}) { for (const int signal : {SIGWINCH, SIGTSTP}) {
InstallSignalHandler(signal); InstallSignalHandler(signal);
} }
@@ -729,40 +659,57 @@ void ScreenInteractive::Install() {
Flush(); Flush();
quit_ = false; quit_ = false;
task_sender_ = task_receiver_->MakeSender();
event_listener_ = PostAnimationTask();
std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
animation_listener_ =
std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
} }
// private // private
void ScreenInteractive::Uninstall() { void ScreenInteractive::Uninstall() {
ExitNow(); ExitNow();
event_listener_.join();
animation_listener_.join();
OnExit(); OnExit();
} }
// private // private
// NOLINTNEXTLINE // NOLINTNEXTLINE
void ScreenInteractive::RunOnceBlocking(Component component) { void ScreenInteractive::RunOnceBlocking(Component component) {
ExecuteSignalHandlers(); // Set FPS to 60 at most.
Task task; const auto time_per_frame = std::chrono::microseconds(16666); // 1s / 60fps
if (task_receiver_->Receive(&task)) {
HandleTask(component, task); auto time = std::chrono::steady_clock::now();
size_t executed_task = internal_->task_runner.ExecutedTasks();
// Wait for at least one task to execute.
while (executed_task == internal_->task_runner.ExecutedTasks() &&
!HasQuitted()) {
RunOnce(component);
const auto now = std::chrono::steady_clock::now();
const auto delta = now - time;
time = now;
if (delta < time_per_frame) {
const auto sleep_duration = time_per_frame - delta;
std::this_thread::sleep_for(sleep_duration);
}
} }
RunOnce(component);
} }
// private // private
void ScreenInteractive::RunOnce(Component component) { void ScreenInteractive::RunOnce(Component component) {
Task task; AutoReset set_component(&component_, component);
while (task_receiver_->ReceiveNonBlocking(&task)) { ExecuteSignalHandlers();
HandleTask(component, task); FetchTerminalEvents();
ExecuteSignalHandlers();
// Execute the pending tasks from the queue.
const size_t executed_task = internal_->task_runner.ExecutedTasks();
internal_->task_runner.RunUntilIdle();
// If no executed task, we can return early without redrawing the screen.
if (executed_task == internal_->task_runner.ExecutedTasks()) {
return;
} }
Draw(std::move(component));
ExecuteSignalHandlers();
Draw(component);
if (selection_data_previous_ != selection_data_) { if (selection_data_previous_ != selection_data_) {
selection_data_previous_ = selection_data_; selection_data_previous_ = selection_data_;
@@ -783,6 +730,7 @@ void ScreenInteractive::HandleTask(Component component, Task& task) {
// clang-format off // clang-format off
// Handle Event. // Handle Event.
if constexpr (std::is_same_v<T, Event>) { if constexpr (std::is_same_v<T, Event>) {
if (arg.is_cursor_position()) { if (arg.is_cursor_position()) {
cursor_x_ = arg.cursor_x(); cursor_x_ = arg.cursor_x();
cursor_y_ = arg.cursor_y(); cursor_y_ = arg.cursor_y();
@@ -1034,7 +982,6 @@ void ScreenInteractive::Exit() {
// private: // private:
void ScreenInteractive::ExitNow() { void ScreenInteractive::ExitNow() {
quit_ = true; quit_ = true;
task_sender_.reset();
} }
// private: // private:
@@ -1067,6 +1014,118 @@ void ScreenInteractive::Signal(int signal) {
#endif #endif
} }
void ScreenInteractive::FetchTerminalEvents() {
#if defined(_WIN32)
auto get_input_records = [&]() -> std::vector<INPUT_RECORD> {
// Check if there is input in the console.
auto console = GetStdHandle(STD_INPUT_HANDLE);
DWORD number_of_events = 0;
if (!GetNumberOfConsoleInputEvents(console, &number_of_events)) {
return std::vector<INPUT_RECORD>();
}
if (number_of_events <= 0) {
// No input, return.
return std::vector<INPUT_RECORD>();
}
// Read the input events.
std::vector<INPUT_RECORD> records(number_of_events);
DWORD number_of_events_read = 0;
if (!ReadConsoleInput(console, records.data(), (DWORD)records.size(),
&number_of_events_read)) {
return std::vector<INPUT_RECORD>();
}
records.resize(number_of_events_read);
return records;
};
auto records = get_input_records();
if (records.size() == 0) {
const auto timeout =
std::chrono::steady_clock::now() - internal_->last_char_time;
const size_t timeout_microseconds =
std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
internal_->terminal_input_parser.Timeout(timeout_microseconds);
return;
}
internal_->last_char_time = std::chrono::steady_clock::now();
// Convert the input events to FTXUI events.
// For each event, we call the terminal input parser to convert it to
// Event.
for (const auto& r : records) {
switch (r.EventType) {
case KEY_EVENT: {
auto key_event = r.Event.KeyEvent;
// ignore UP key events
if (key_event.bKeyDown == FALSE) {
continue;
}
std::wstring wstring;
wstring += key_event.uChar.UnicodeChar;
for (auto it : to_string(wstring)) {
internal_->terminal_input_parser.Add(it);
}
} break;
case WINDOW_BUFFER_SIZE_EVENT:
Post(Event::Special({0}));
break;
case MENU_EVENT:
case FOCUS_EVENT:
case MOUSE_EVENT:
// TODO(mauve): Implement later.
break;
}
}
#elif defined(__EMSCRIPTEN__)
// Read chars from the terminal.
// We configured it to be non blocking.
std::array<char, 128> out{};
size_t l = read(STDIN_FILENO, out.data(), out.size());
if (l == 0) {
const auto timeout =
std::chrono::steady_clock::now() - internal_->last_char_time;
const size_t timeout_microseconds =
std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
internal_->terminal_input_parser.Timeout(timeout_microseconds);
return;
}
internal_->last_char_time = std::chrono::steady_clock::now();
// Convert the chars to events.
for (size_t i = 0; i < l; ++i) {
internal_->terminal_input_parser.Add(out[i]);
}
#else // POSIX (Linux & Mac)
if (!CheckStdinReady()) {
const auto timeout =
std::chrono::steady_clock::now() - internal_->last_char_time;
const size_t timeout_ms =
std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count();
internal_->terminal_input_parser.Timeout(timeout_ms);
return;
}
internal_->last_char_time = std::chrono::steady_clock::now();
// Read chars from the terminal.
std::array<char, 128> out{};
size_t l = read(fileno(stdin), out.data(), out.size());
// Convert the chars to events.
for (size_t i = 0; i < l; ++i) {
internal_->terminal_input_parser.Add(out[i]);
}
#endif
}
void ScreenInteractive::PostAnimationTask() {
Post(AnimationTask());
// Repeat the animation task every 15ms. This correspond to a frame rate
// of around 66fps.
internal_->task_runner.PostDelayedTask([this] { PostAnimationTask(); },
std::chrono::milliseconds(15));
}
bool ScreenInteractive::SelectionData::operator==( bool ScreenInteractive::SelectionData::operator==(
const ScreenInteractive::SelectionData& other) const { const ScreenInteractive::SelectionData& other) const {
if (empty && other.empty) { if (empty && other.empty) {

View File

@@ -27,9 +27,10 @@ namespace {
// Capture the standard output (stdout) to a string. // Capture the standard output (stdout) to a string.
class StdCapture { class StdCapture {
public: public:
explicit StdCapture(std::string* captured) explicit StdCapture(std::string* captured) : captured_(captured) {
: captured_(captured) { if (pipe(pipefd_) != 0) {
if (pipe(pipefd_) != 0) return; return;
}
old_stdout_ = dup(fileno(stdout)); old_stdout_ = dup(fileno(stdout));
fflush(stdout); fflush(stdout);
dup2(pipefd_[1], fileno(stdout)); dup2(pipefd_[1], fileno(stdout));
@@ -188,14 +189,14 @@ TEST(ScreenInteractive, FixedSizeInitialFrame) {
auto capture = StdCapture(&output); auto capture = StdCapture(&output);
auto screen = ScreenInteractive::FixedSize(2, 2); auto screen = ScreenInteractive::FixedSize(2, 2);
auto component = Renderer([&] { auto component = Renderer([&] { return text("AB"); });
return text("AB");
});
Loop loop(&screen, component); Loop loop(&screen, component);
loop.RunOnce(); loop.RunOnce();
} }
ASSERT_EQ( using namespace std::string_view_literals;
auto expected =
// Install the ScreenInteractive. // Install the ScreenInteractive.
"\0" // Flush stdout. "\0" // Flush stdout.
"\x1BP$q q" // Set cursor shape to 1 (block). "\x1BP$q q" // Set cursor shape to 1 (block).
@@ -236,10 +237,9 @@ TEST(ScreenInteractive, FixedSizeInitialFrame) {
"\0" // Flush stdout. "\0" // Flush stdout.
// Skip one line to avoid the prompt to be printed over the last drawing. // Skip one line to avoid the prompt to be printed over the last drawing.
"\r\n", "\r\n"sv;
output); ASSERT_EQ(expected, output);
#endif #endif
} }
} // namespace ftxui } // namespace ftxui

View File

@@ -33,6 +33,20 @@ Decorator flexDirection(Direction direction) {
return xflex; // NOT_REACHED() return xflex; // NOT_REACHED()
} }
Direction Opposite(Direction d) {
switch (d) {
case Direction::Up:
return Direction::Down;
case Direction::Down:
return Direction::Up;
case Direction::Left:
return Direction::Right;
case Direction::Right:
return Direction::Left;
}
return d; // NOT_REACHED()
}
template <class T> template <class T>
class SliderBase : public SliderOption<T>, public ComponentBase { class SliderBase : public SliderOption<T>, public ComponentBase {
public: public:
@@ -47,59 +61,15 @@ class SliderBase : public SliderOption<T>, public ComponentBase {
flexDirection(this->direction) | reflect(gauge_box_) | gauge_color; flexDirection(this->direction) | reflect(gauge_box_) | gauge_color;
} }
void OnLeft() { void OnDirection(Direction pressed) {
switch (this->direction) { if (pressed == this->direction) {
case Direction::Right: this->value() += this->increment();
this->value() -= this->increment(); return;
break;
case Direction::Left:
this->value() += this->increment();
break;
case Direction::Up:
case Direction::Down:
break;
} }
}
void OnRight() { if (pressed == Opposite(this->direction)) {
switch (this->direction) { this->value() -= this->increment();
case Direction::Right: return;
this->value() += this->increment();
break;
case Direction::Left:
this->value() -= this->increment();
break;
case Direction::Up:
case Direction::Down:
break;
}
}
void OnUp() {
switch (this->direction) {
case Direction::Up:
this->value() -= this->increment();
break;
case Direction::Down:
this->value() += this->increment();
break;
case Direction::Left:
case Direction::Right:
break;
}
}
void OnDown() {
switch (this->direction) {
case Direction::Down:
this->value() += this->increment();
break;
case Direction::Up:
this->value() -= this->increment();
break;
case Direction::Left:
case Direction::Right:
break;
} }
} }
@@ -110,16 +80,16 @@ class SliderBase : public SliderOption<T>, public ComponentBase {
T old_value = this->value(); T old_value = this->value();
if (event == Event::ArrowLeft || event == Event::Character('h')) { if (event == Event::ArrowLeft || event == Event::Character('h')) {
OnLeft(); OnDirection(Direction::Left);
} }
if (event == Event::ArrowRight || event == Event::Character('l')) { if (event == Event::ArrowRight || event == Event::Character('l')) {
OnRight(); OnDirection(Direction::Right);
} }
if (event == Event::ArrowUp || event == Event::Character('k')) { if (event == Event::ArrowUp || event == Event::Character('k')) {
OnDown(); OnDirection(Direction::Up);
} }
if (event == Event::ArrowDown || event == Event::Character('j')) { if (event == Event::ArrowDown || event == Event::Character('j')) {
OnUp(); OnDirection(Direction::Down);
} }
this->value() = std::max(this->min(), std::min(this->max(), this->value())); this->value() = std::max(this->min(), std::min(this->max(), this->value()));

View File

@@ -0,0 +1,19 @@
// Copyright 2024 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 "ftxui/component/task_internal.hpp"
namespace ftxui::task {
bool PendingTask::operator<(const PendingTask& other) const {
if (!time && !other.time) {
return false;
}
if (!time) {
return true;
}
if (!other.time) {
return false;
}
return time.value() > other.time.value();
}
} // namespace ftxui::task

View File

@@ -0,0 +1,40 @@
// Copyright 2024 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 TASK_HPP
#define TASK_HPP
#include <chrono>
#include <functional>
#include <optional>
namespace ftxui::task {
/// A task represents a unit of work.
using Task = std::function<void()>;
/// A PendingTask represents a task that is scheduled to be executed at a
/// specific time, or as soon as possible.
struct PendingTask {
// Immediate task:
PendingTask(Task t) : task(std::move(t)) {} // NOLINT
// Delayed task with a duration
PendingTask(Task t, std::chrono::steady_clock::duration duration)
: task(std::move(t)), time(std::chrono::steady_clock::now() + duration) {}
/// The task to be executed.
Task task;
/// The time when the task should be executed. If the time is empty, the task
/// should be executed as soon as possible.
std::optional<std::chrono::steady_clock::time_point> time;
/// Compare two PendingTasks by their time.
/// If both tasks have no time, they are considered equal.
bool operator<(const PendingTask& other) const;
};
} // namespace ftxui::task
#endif // TASK_HPP_

View File

@@ -0,0 +1,53 @@
// Copyright 2024 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 "ftxui/component/task_queue.hpp"
namespace ftxui::task {
auto TaskQueue::PostTask(PendingTask task) -> void {
if (!task.time) {
immediate_tasks_.push(task);
return;
}
if (task.time.value() < std::chrono::steady_clock::now()) {
immediate_tasks_.push(task);
return;
}
delayed_tasks_.push(task);
}
auto TaskQueue::Get() -> MaybeTask {
// Attempt to execute a task immediately.
if (!immediate_tasks_.empty()) {
auto task = immediate_tasks_.front();
immediate_tasks_.pop();
return task.task;
}
// Move all tasks that can be executed to the immediate queue.
auto now = std::chrono::steady_clock::now();
while (!delayed_tasks_.empty() && delayed_tasks_.top().time.value() <= now) {
immediate_tasks_.push(delayed_tasks_.top());
delayed_tasks_.pop();
}
// Attempt to execute a task immediately.
if (!immediate_tasks_.empty()) {
auto task = immediate_tasks_.front();
immediate_tasks_.pop();
return task.task;
}
// If there are no tasks to execute, return the delay until the next task.
if (!delayed_tasks_.empty()) {
return delayed_tasks_.top().time.value() - now;
}
// If there are no tasks to execute, return the maximum duration.
return std::monostate{};
}
} // namespace ftxui::task

View File

@@ -0,0 +1,37 @@
// Copyright 2024 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 TASK_QUEUE_HPP
#define TASK_QUEUE_HPP
#include <queue>
#include <variant>
#include "ftxui/component/task_internal.hpp" // for PendingTask, Task
namespace ftxui::task {
/// A task queue that schedules tasks to be executed in the future. Tasks can be
/// scheduled to be executed immediately, or after a certain duration.
/// - The tasks are executed in the order they were scheduled.
/// - If multiple tasks are scheduled to be executed at the same time, they are
/// executed in the order they were scheduled.
/// - If a task is scheduled to be executed in the past, it is executed
/// immediately.
struct TaskQueue {
auto PostTask(PendingTask task) -> void;
using MaybeTask =
std::variant<Task, std::chrono::steady_clock::duration, std::monostate>;
auto Get() -> MaybeTask;
bool HasImmediateTasks() const { return !immediate_tasks_.empty(); }
private:
std::queue<PendingTask> immediate_tasks_;
std::priority_queue<PendingTask> delayed_tasks_;
};
} // namespace ftxui::task
#endif

View File

@@ -0,0 +1,75 @@
// Copyright 2024 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 "ftxui/component/task_runner.hpp"
#include <cassert>
#include <thread>
namespace ftxui::task {
static thread_local TaskRunner* current_task_runner = nullptr; // NOLINT
TaskRunner::TaskRunner() {
assert(!previous_task_runner_);
previous_task_runner_ = current_task_runner;
current_task_runner = this;
}
TaskRunner::~TaskRunner() {
current_task_runner = previous_task_runner_;
}
// static
auto TaskRunner::Current() -> TaskRunner* {
assert(current_task_runner);
return current_task_runner;
}
auto TaskRunner::PostTask(Task task) -> void {
queue_.PostTask(PendingTask{std::move(task)});
}
auto TaskRunner::PostDelayedTask(Task task,
std::chrono::steady_clock::duration duration)
-> void {
queue_.PostTask(PendingTask{std::move(task), duration});
}
/// Runs the tasks in the queue.
auto TaskRunner::RunUntilIdle()
-> std::optional<std::chrono::steady_clock::duration> {
while (true) {
auto maybe_task = queue_.Get();
if (std::holds_alternative<std::monostate>(maybe_task)) {
// No more tasks to execute, exit the loop.
return std::nullopt;
}
if (std::holds_alternative<Task>(maybe_task)) {
executed_tasks_++;
std::get<Task>(maybe_task)();
continue;
}
if (std::holds_alternative<std::chrono::steady_clock::duration>(
maybe_task)) {
return std::get<std::chrono::steady_clock::duration>(maybe_task);
}
}
}
auto TaskRunner::Run() -> void {
while (true) {
auto duration = RunUntilIdle();
if (!duration) {
// No more tasks to execute, exit the loop.
return;
}
// Sleep for the duration until the next task can be executed.
std::this_thread::sleep_for(duration.value());
}
}
} // namespace ftxui::task

View File

@@ -0,0 +1,46 @@
// Copyright 2024 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 TASK_RUNNER_HPP
#define TASK_RUNNER_HPP
#include "ftxui/component/task_internal.hpp"
#include "ftxui/component/task_queue.hpp"
namespace ftxui::task {
class TaskRunner {
public:
TaskRunner();
~TaskRunner();
// Returns the task runner for the current thread.
static auto Current() -> TaskRunner*;
/// Schedules a task to be executed immediately.
auto PostTask(Task task) -> void;
/// Schedules a task to be executed after a certain duration.
auto PostDelayedTask(Task task, std::chrono::steady_clock::duration duration)
-> void;
/// Runs the tasks in the queue, return the delay until the next delayed task
/// can be executed.
auto RunUntilIdle() -> std::optional<std::chrono::steady_clock::duration>;
// Runs the tasks in the queue, blocking until all tasks are executed.
auto Run() -> void;
bool HasImmediateTasks() const { return queue_.HasImmediateTasks(); }
size_t ExecutedTasks() const { return executed_tasks_; }
private:
TaskRunner* previous_task_runner_ = nullptr;
TaskQueue queue_;
size_t executed_tasks_ = 0;
};
} // namespace ftxui::task
#endif // TASK_RUNNER_HPP

View File

@@ -0,0 +1,94 @@
// Copyright 2024 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
// Copyright 2024 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 "ftxui/component/task_internal.hpp"
#include <gtest/gtest.h>
#include <thread> // for sleep_for
#include "ftxui/component/task_runner.hpp"
namespace ftxui::task {
TEST(TaskTest, Basic) {
std::vector<int> values;
auto task_1 = [&values] { values.push_back(1); };
auto task_2 = [&values] { values.push_back(2); };
auto task_3 = [&values] { values.push_back(3); };
auto runner = TaskRunner();
runner.PostTask(task_1);
runner.PostTask(task_2);
runner.PostTask(task_3);
while (true) {
auto duration = runner.RunUntilIdle();
if (!duration) {
break;
}
std::this_thread::sleep_for(duration.value());
}
EXPECT_EQ(values, (std::vector<int>{1, 2, 3}));
}
TEST(TaskTest, PostedWithinTask) {
std::vector<int> values;
auto task_1 = [&values] {
values.push_back(1);
auto task_2 = [&values] { values.push_back(5); };
TaskRunner::Current()->PostTask(std::move(task_2));
values.push_back(2);
};
auto task_2 = [&values] {
values.push_back(3);
auto task_2 = [&values] { values.push_back(6); };
TaskRunner::Current()->PostTask(std::move(task_2));
values.push_back(4);
};
auto runner = TaskRunner();
runner.PostTask(task_1);
runner.PostTask(task_2);
while (true) {
auto duration = runner.RunUntilIdle();
if (!duration) {
break;
}
std::this_thread::sleep_for(duration.value());
}
EXPECT_EQ(values, (std::vector<int>{1, 2, 3, 4, 5, 6}));
}
TEST(TaskTest, RunDelayedTask) {
std::vector<int> values;
auto task_1 = [&values] { values.push_back(1); };
auto task_2 = [&values] { values.push_back(2); };
auto task_3 = [&values] { values.push_back(3); };
auto runner = TaskRunner();
runner.PostDelayedTask(task_3, std::chrono::milliseconds(300));
runner.PostDelayedTask(task_1, std::chrono::milliseconds(100));
runner.PostDelayedTask(task_2, std::chrono::milliseconds(200));
while (true) {
auto duration = runner.RunUntilIdle();
if (!duration) {
break;
}
std::this_thread::sleep_for(duration.value());
}
EXPECT_EQ(values, (std::vector<int>{1, 2, 3}));
}
} // namespace ftxui::task

View File

@@ -5,7 +5,7 @@
#include <cstdint> // for uint32_t #include <cstdint> // for uint32_t
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Button, Mouse::Motion #include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Button, Mouse::Motion
#include <ftxui/component/receiver.hpp> // for SenderImpl, Sender #include <functional> // for std::function
#include <map> #include <map>
#include <memory> // for unique_ptr, allocator #include <memory> // for unique_ptr, allocator
#include <utility> // for move #include <utility> // for move
@@ -90,7 +90,7 @@ const std::map<std::string, std::string> g_uniformize = {
{"\x1B[X", "\x1B[24~"}, // F12 {"\x1B[X", "\x1B[24~"}, // F12
}; };
TerminalInputParser::TerminalInputParser(Sender<Task> out) TerminalInputParser::TerminalInputParser(std::function<void(Event)> out)
: out_(std::move(out)) {} : out_(std::move(out)) {}
void TerminalInputParser::Timeout(int time) { void TerminalInputParser::Timeout(int time) {
@@ -131,7 +131,7 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) {
return; return;
case CHARACTER: case CHARACTER:
out_->Send(Event::Character(std::move(pending_))); out_(Event::Character(std::move(pending_)));
pending_.clear(); pending_.clear();
return; return;
@@ -140,25 +140,25 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) {
if (it != g_uniformize.end()) { if (it != g_uniformize.end()) {
pending_ = it->second; pending_ = it->second;
} }
out_->Send(Event::Special(std::move(pending_))); out_(Event::Special(std::move(pending_)));
pending_.clear(); pending_.clear();
} }
return; return;
case MOUSE: case MOUSE:
out_->Send(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT out_(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT
pending_.clear(); pending_.clear();
return; return;
case CURSOR_POSITION: case CURSOR_POSITION:
out_->Send(Event::CursorPosition(std::move(pending_), // NOLINT out_(Event::CursorPosition(std::move(pending_), // NOLINT
output.cursor.x, // NOLINT output.cursor.x, // NOLINT
output.cursor.y)); // NOLINT output.cursor.y)); // NOLINT
pending_.clear(); pending_.clear();
return; return;
case CURSOR_SHAPE: case CURSOR_SHAPE:
out_->Send(Event::CursorShape(std::move(pending_), output.cursor_shape)); out_(Event::CursorShape(std::move(pending_), output.cursor_shape));
pending_.clear(); pending_.clear();
return; return;
} }

View File

@@ -4,12 +4,11 @@
#ifndef FTXUI_COMPONENT_TERMINAL_INPUT_PARSER #ifndef FTXUI_COMPONENT_TERMINAL_INPUT_PARSER
#define FTXUI_COMPONENT_TERMINAL_INPUT_PARSER #define FTXUI_COMPONENT_TERMINAL_INPUT_PARSER
#include <functional>
#include <string> // for string #include <string> // for string
#include <vector> // for vector #include <vector> // for vector
#include "ftxui/component/mouse.hpp" // for Mouse #include "ftxui/component/mouse.hpp" // for Mouse
#include "ftxui/component/receiver.hpp" // for Sender
#include "ftxui/component/task.hpp" // for Task
namespace ftxui { namespace ftxui {
struct Event; struct Event;
@@ -17,7 +16,7 @@ struct Event;
// Parse a sequence of |char| accross |time|. Produces |Event|. // Parse a sequence of |char| accross |time|. Produces |Event|.
class TerminalInputParser { class TerminalInputParser {
public: public:
explicit TerminalInputParser(Sender<Task> out); explicit TerminalInputParser(std::function<void(Event)> out);
void Timeout(int time); void Timeout(int time);
void Add(char c); void Add(char c);
@@ -62,7 +61,7 @@ class TerminalInputParser {
Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments); Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments);
Output ParseCursorPosition(std::vector<int> arguments); Output ParseCursorPosition(std::vector<int> arguments);
Sender<Task> out_; std::function<void(Event)> out_;
int position_ = -1; int position_ = -1;
int timeout_ = 0; int timeout_ = 0;
std::string pending_; std::string pending_;

View File

@@ -2,12 +2,12 @@
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Left, Mouse::Middle, Mouse::Pressed, Mouse::Released, Mouse::Right #include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Left, Mouse::Middle, Mouse::Pressed, Mouse::Released, Mouse::Right
#include <ftxui/component/task.hpp> // for Task #include <functional> // for function
#include <initializer_list> // for initializer_list #include <initializer_list> // for initializer_list
#include <memory> // for allocator, unique_ptr #include <memory> // for allocator, unique_ptr
#include <vector> // for vector
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::Backspace, Event::End, Event::Home, Event::Custom, Event::Delete, Event::F1, Event::F10, Event::F11, Event::F12, Event::F2, Event::F3, Event::F4, Event::F5, Event::F6, Event::F7, Event::F8, Event::F9, Event::PageDown, Event::PageUp, Event::Tab, Event::TabReverse, Event::Escape #include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::Backspace, Event::End, Event::Home, Event::Custom, Event::Delete, Event::F1, Event::F10, Event::F11, Event::F12, Event::F2, Event::F3, Event::F4, Event::F5, Event::F6, Event::F7, Event::F8, Event::F9, Event::PageDown, Event::PageUp, Event::Tab, Event::TabReverse, Event::Escape
#include "ftxui/component/receiver.hpp" // for MakeReceiver, ReceiverImpl
#include "ftxui/component/terminal_input_parser.hpp" #include "ftxui/component/terminal_input_parser.hpp"
#include "gtest/gtest.h" // for AssertionResult, Test, Message, TestPartResult, EXPECT_EQ, EXPECT_TRUE, TEST, EXPECT_FALSE #include "gtest/gtest.h" // for AssertionResult, Test, Message, TestPartResult, EXPECT_EQ, EXPECT_TRUE, TEST, EXPECT_FALSE
@@ -17,233 +17,205 @@ namespace ftxui {
// Test char |c| to are trivially converted into |Event::Character(c)|. // Test char |c| to are trivially converted into |Event::Character(c)|.
TEST(Event, Character) { TEST(Event, Character) {
std::vector<char> basic_char; std::vector<char> basic_char;
for (char c = 'a'; c <= 'z'; ++c) for (char c = 'a'; c <= 'z'; ++c) {
basic_char.push_back(c); basic_char.push_back(c);
for (char c = 'A'; c <= 'Z'; ++c) }
for (char c = 'A'; c <= 'Z'; ++c) {
basic_char.push_back(c); basic_char.push_back(c);
auto event_receiver = MakeReceiver<Task>();
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
for (char c : basic_char)
parser.Add(c);
} }
Task received; std::vector<Event> received_events;
auto parser = TerminalInputParser(
[&](Event event) { received_events.push_back(std::move(event)); });
for (char c : basic_char) { for (char c : basic_char) {
EXPECT_TRUE(event_receiver->Receive(&received)); parser.Add(c);
EXPECT_TRUE(std::get<Event>(received).is_character());
EXPECT_EQ(c, std::get<Event>(received).character()[0]);
} }
EXPECT_FALSE(event_receiver->Receive(&received));
for (size_t i = 0; i < basic_char.size(); ++i) {
EXPECT_TRUE(received_events[i].is_character());
EXPECT_EQ(basic_char[i], received_events[i].character()[0]);
}
EXPECT_EQ(received_events.size(), basic_char.size());
} }
TEST(Event, EscapeKeyWithoutWaiting) { TEST(Event, EscapeKeyWithoutWaiting) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
}
Task received; EXPECT_TRUE(received_events.empty());
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, EscapeKeyNotEnoughWait) { TEST(Event, EscapeKeyNotEnoughWait) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Timeout(49); parser.Timeout(49);
}
Task received; EXPECT_TRUE(received_events.empty());
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, EscapeKeyEnoughWait) { TEST(Event, EscapeKeyEnoughWait) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Timeout(50); parser.Timeout(50);
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_EQ(received_events[0], Event::Escape);
EXPECT_EQ(std::get<Event>(received), Event::Escape);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, EscapeFast) { TEST(Event, EscapeFast) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('a'); parser.Add('a');
parser.Add('\x1B'); parser.Add('');
parser.Add('b'); parser.Add('b');
parser.Timeout(49); parser.Timeout(49);
}
Task received; EXPECT_EQ(2, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_EQ(received_events[0], Event::AltA);
EXPECT_EQ(std::get<Event>(received), Event::AltA); EXPECT_EQ(received_events[1], Event::AltB);
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_EQ(std::get<Event>(received), Event::AltB);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseLeftClickPressed) { TEST(Event, MouseLeftClickPressed) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('['); parser.Add('[');
parser.Add('0'); parser.Add('0');
parser.Add(';'); parser.Add(';');
parser.Add('1'); parser.Add('1');
parser.Add('2'); parser.Add('2');
parser.Add(';'); parser.Add(';');
parser.Add('4'); parser.Add('4');
parser.Add('2'); parser.Add('2');
parser.Add('M'); parser.Add('M');
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_mouse());
EXPECT_TRUE(std::get<Event>(received).is_mouse()); EXPECT_EQ(Mouse::Left, received_events[0].mouse().button);
EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button); EXPECT_EQ(12, received_events[0].mouse().x);
EXPECT_EQ(12, std::get<Event>(received).mouse().x); EXPECT_EQ(42, received_events[0].mouse().y);
EXPECT_EQ(42, std::get<Event>(received).mouse().y); EXPECT_EQ(received_events[0].mouse().motion, Mouse::Pressed);
EXPECT_EQ(std::get<Event>(received).mouse().motion, Mouse::Pressed);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseLeftMoved) { TEST(Event, MouseLeftMoved) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('['); parser.Add('[');
parser.Add('3'); parser.Add('3');
parser.Add('2'); parser.Add('2');
parser.Add(';'); parser.Add(';');
parser.Add('1'); parser.Add('1');
parser.Add('2'); parser.Add('2');
parser.Add(';'); parser.Add(';');
parser.Add('4'); parser.Add('4');
parser.Add('2'); parser.Add('2');
parser.Add('M'); parser.Add('M');
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_mouse());
EXPECT_TRUE(std::get<Event>(received).is_mouse()); EXPECT_EQ(Mouse::Left, received_events[0].mouse().button);
EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button); EXPECT_EQ(12, received_events[0].mouse().x);
EXPECT_EQ(12, std::get<Event>(received).mouse().x); EXPECT_EQ(42, received_events[0].mouse().y);
EXPECT_EQ(42, std::get<Event>(received).mouse().y); EXPECT_EQ(received_events[0].mouse().motion, Mouse::Moved);
EXPECT_EQ(std::get<Event>(received).mouse().motion, Mouse::Moved);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseLeftClickReleased) { TEST(Event, MouseLeftClickReleased) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('['); parser.Add('[');
parser.Add('0'); parser.Add('0');
parser.Add(';'); parser.Add(';');
parser.Add('1'); parser.Add('1');
parser.Add('2'); parser.Add('2');
parser.Add(';'); parser.Add(';');
parser.Add('4'); parser.Add('4');
parser.Add('2'); parser.Add('2');
parser.Add('m'); parser.Add('m');
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_mouse());
EXPECT_TRUE(std::get<Event>(received).is_mouse()); EXPECT_EQ(Mouse::Left, received_events[0].mouse().button);
EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button); EXPECT_EQ(12, received_events[0].mouse().x);
EXPECT_EQ(12, std::get<Event>(received).mouse().x); EXPECT_EQ(42, received_events[0].mouse().y);
EXPECT_EQ(42, std::get<Event>(received).mouse().y); EXPECT_EQ(received_events[0].mouse().motion, Mouse::Released);
EXPECT_EQ(std::get<Event>(received).mouse().motion, Mouse::Released);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseReporting) { TEST(Event, MouseReporting) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('['); parser.Add('[');
parser.Add('1'); parser.Add('1');
parser.Add('2'); parser.Add('2');
parser.Add(';'); parser.Add(';');
parser.Add('4'); parser.Add('4');
parser.Add('2'); parser.Add('2');
parser.Add('R'); parser.Add('R');
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_cursor_position());
EXPECT_TRUE(std::get<Event>(received).is_cursor_position()); EXPECT_EQ(42, received_events[0].cursor_x());
EXPECT_EQ(42, std::get<Event>(received).cursor_x()); EXPECT_EQ(12, received_events[0].cursor_y());
EXPECT_EQ(12, std::get<Event>(received).cursor_y());
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseMiddleClick) { TEST(Event, MouseMiddleClick) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('['); parser.Add('[');
parser.Add('3'); parser.Add('3');
parser.Add('3'); parser.Add('3');
parser.Add(';'); parser.Add(';');
parser.Add('1'); parser.Add('1');
parser.Add('2'); parser.Add('2');
parser.Add(';'); parser.Add(';');
parser.Add('4'); parser.Add('4');
parser.Add('2'); parser.Add('2');
parser.Add('M'); parser.Add('M');
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_mouse());
EXPECT_TRUE(std::get<Event>(received).is_mouse()); EXPECT_EQ(Mouse::Middle, received_events[0].mouse().button);
EXPECT_EQ(Mouse::Middle, std::get<Event>(received).mouse().button); EXPECT_EQ(12, received_events[0].mouse().x);
EXPECT_EQ(12, std::get<Event>(received).mouse().x); EXPECT_EQ(42, received_events[0].mouse().y);
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, MouseRightClick) { TEST(Event, MouseRightClick) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add('\x1B'); parser.Add('');
parser.Add('['); parser.Add('[');
parser.Add('3'); parser.Add('3');
parser.Add('4'); parser.Add('4');
parser.Add(';'); parser.Add(';');
parser.Add('1'); parser.Add('1');
parser.Add('2'); parser.Add('2');
parser.Add(';'); parser.Add(';');
parser.Add('4'); parser.Add('4');
parser.Add('2'); parser.Add('2');
parser.Add('M'); parser.Add('M');
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_mouse());
EXPECT_TRUE(std::get<Event>(received).is_mouse()); EXPECT_EQ(Mouse::Right, received_events[0].mouse().button);
EXPECT_EQ(Mouse::Right, std::get<Event>(received).mouse().button); EXPECT_EQ(12, received_events[0].mouse().x);
EXPECT_EQ(12, std::get<Event>(received).mouse().x); EXPECT_EQ(42, received_events[0].mouse().y);
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, UTF8) { TEST(Event, UTF8) {
@@ -313,31 +285,30 @@ TEST(Event, UTF8) {
}; };
for (auto test : kTestCase) { for (auto test : kTestCase) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
for (auto input : test.input) for (auto input : test.input) {
parser.Add(input); parser.Add(input);
} }
Task received;
if (test.valid) { if (test.valid) {
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(std::get<Event>(received).is_character()); EXPECT_TRUE(received_events[0].is_character());
} else {
EXPECT_TRUE(received_events.empty());
} }
EXPECT_FALSE(event_receiver->Receive(&received));
} }
} }
TEST(Event, NewLine) { TEST(Event, NewLine) {
for (char newline : {'\r', '\n'}) { for (char newline : {'\r', '\n'}) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add(newline); parser.Add(newline);
} EXPECT_EQ(1, received_events.size());
Task received; EXPECT_TRUE(received_events[0] == Event::Return);
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received) == Event::Return);
} }
} }
@@ -348,8 +319,9 @@ TEST(Event, Control) {
}; };
std::vector<TestCase> cases; std::vector<TestCase> cases;
for (int i = 0; i < 32; ++i) { for (int i = 0; i < 32; ++i) {
if (i == 8 || i == 13 || i == 24 || i == 26 || i == 27) if (i == 8 || i == 13 || i == 24 || i == 26 || i == 27) {
continue; continue;
}
cases.push_back({char(i), false}); cases.push_back({char(i), false});
} }
cases.push_back({char(24), false}); cases.push_back({char(24), false});
@@ -357,17 +329,16 @@ TEST(Event, Control) {
cases.push_back({char(127), false}); cases.push_back({char(127), false});
for (auto test : cases) { for (auto test : cases) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add(test.input); parser.Add(test.input);
}
Task received;
if (test.cancel) { if (test.cancel) {
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events.empty());
} else { } else {
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_EQ(1, received_events.size());
EXPECT_EQ(std::get<Event>(received), Event::Special({test.input})); EXPECT_EQ(received_events[0], Event::Special({test.input}));
} }
} }
} }
@@ -375,8 +346,9 @@ TEST(Event, Control) {
TEST(Event, Special) { TEST(Event, Special) {
auto str = [](std::string input) { auto str = [](std::string input) {
std::vector<unsigned char> output; std::vector<unsigned char> output;
for (auto it : input) for (auto it : input) {
output.push_back(it); output.push_back(it);
}
return output; return output;
}; };
@@ -385,10 +357,12 @@ TEST(Event, Special) {
Event expected; Event expected;
} kTestCase[] = { } kTestCase[] = {
// Arrow (default cursor mode) // Arrow (default cursor mode)
{str("\x1B[A"), Event::ArrowUp}, {str("\x1B[B"), Event::ArrowDown}, {str(""), Event::ArrowUp},
{str("\x1B[C"), Event::ArrowRight}, {str("\x1B[D"), Event::ArrowLeft}, {str(""), Event::ArrowDown},
{str("\x1B[H"), Event::Home}, {str("\x1B[F"), Event::End}, {str(""), Event::ArrowRight},
/* {str(""), Event::ArrowLeft},
{str(""), Event::Home},
{str(""), Event::End},
// Arrow (application cursor mode) // Arrow (application cursor mode)
{str("\x1BOA"), Event::ArrowUp}, {str("\x1BOA"), Event::ArrowUp},
@@ -469,45 +443,38 @@ TEST(Event, Special) {
// Custom: // Custom:
{{0}, Event::Custom}, {{0}, Event::Custom},
*/
}; };
for (auto test : kTestCase) { for (auto test : kTestCase) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
for (auto input : test.input) { for (auto input : test.input) {
parser.Add(input); parser.Add(input);
}
} }
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_EQ(received_events[0], test.expected);
EXPECT_EQ(std::get<Event>(received), test.expected);
EXPECT_FALSE(event_receiver->Receive(&received));
} }
} }
TEST(Event, DeviceControlString) { TEST(Event, DeviceControlString) {
auto event_receiver = MakeReceiver<Task>(); std::vector<Event> received_events;
{ auto parser = TerminalInputParser(
auto parser = TerminalInputParser(event_receiver->MakeSender()); [&](Event event) { received_events.push_back(std::move(event)); });
parser.Add(27); // ESC parser.Add(27); // ESC
parser.Add(80); // P parser.Add(80); // P
parser.Add(49); // 1 parser.Add(49); // 1
parser.Add(36); // $ parser.Add(36); // $
parser.Add(114); // r parser.Add(114); // r
parser.Add(49); // 1 parser.Add(49); // 1
parser.Add(32); // SP parser.Add(32); // SP
parser.Add(113); // q parser.Add(113); // q
parser.Add(27); // ESC parser.Add(27); // ESC
parser.Add(92); // (backslash) parser.Add(92); // (backslash)
}
Task received; EXPECT_EQ(1, received_events.size());
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received_events[0].is_cursor_shape());
EXPECT_TRUE(std::get<Event>(received).is_cursor_shape()); EXPECT_EQ(1, received_events[0].cursor_shape());
EXPECT_EQ(1, std::get<Event>(received).cursor_shape());
EXPECT_FALSE(event_receiver->Receive(&received));
} }
} // namespace ftxui } // namespace ftxui

View File

@@ -1,21 +1,16 @@
// Copyright 2021 Arthur Sonzogni. All rights reserved. // Copyright 2021 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.
#include <cstddef>
#include <ftxui/component/event.hpp>
#include "ftxui/component/terminal_input_parser.hpp" #include "ftxui/component/terminal_input_parser.hpp"
extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) { extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) {
using namespace ftxui; using namespace ftxui;
auto event_receiver = MakeReceiver<Task>(); auto parser = TerminalInputParser([&](Event) {});
{ for (size_t i = 0; i < size; ++i) {
auto parser = TerminalInputParser(event_receiver->MakeSender()); parser.Add(data[i]);
for (size_t i = 0; i < size; ++i) {
parser.Add(data[i]);
}
} }
Task received;
while (event_receiver->Receive(&received)) {
// Do nothing.
}
return 0; // Non-zero return values are reserved for future use. return 0; // Non-zero return values are reserved for future use.
} }

View File

@@ -81,7 +81,7 @@ class Size : public Node {
} // namespace } // namespace
/// @brief Apply a constraint on the size of an element. /// @brief Apply a constraint on the size of an element.
/// @param direction Whether the WIDTH of the HEIGHT of the element must be /// @param direction Whether the WIDTH or the HEIGHT of the element must be
/// constrained. /// constrained.
/// @param constraint The type of constaint. /// @param constraint The type of constaint.
/// @param value The value. /// @param value The value.

View File

@@ -47,8 +47,9 @@ namespace {
#if defined(_WIN32) #if defined(_WIN32)
void WindowsEmulateVT100Terminal() { void WindowsEmulateVT100Terminal() {
static bool done = false; static bool done = false;
if (done) if (done) {
return; return;
}
done = true; done = true;
// Enable VT processing on stdout and stdin // Enable VT processing on stdout and stdin

View File

@@ -1284,8 +1284,9 @@ bool IsCombining(uint32_t ucs) {
} }
bool IsFullWidth(uint32_t ucs) { bool IsFullWidth(uint32_t ucs) {
if (ucs < 0x0300) // Quick path: // NOLINT if (ucs < 0x0300) { // Quick path: // NOLINT
return false; return false;
}
return Bisearch(ucs, g_full_width_characters); return Bisearch(ucs, g_full_width_characters);
} }

View File

@@ -1,4 +1,4 @@
// Copyright 2024 Arthur Sonzogni. All rights reserved. // Copyright 2025 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.