mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-28 16:29:34 +08:00
Compare commits
79 Commits
v5.1.0
...
Version_5.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
751c8fab26 | ||
![]() |
daa421fa6a | ||
![]() |
e213cfda37 | ||
![]() |
58ff448e76 | ||
![]() |
dfa461b46b | ||
![]() |
0683285f01 | ||
![]() |
7f74917887 | ||
![]() |
ad0392ec39 | ||
![]() |
70bc44d28b | ||
![]() |
55af678fb9 | ||
![]() |
edaa7a24e7 | ||
![]() |
8922e6d55e | ||
![]() |
99df1ac8ba | ||
![]() |
1d40687a40 | ||
![]() |
dfb9558eaf | ||
![]() |
c5357acbaa | ||
![]() |
fbd56cdf43 | ||
![]() |
66d1c1f61f | ||
![]() |
f5d8c7deb5 | ||
![]() |
535290bb3b | ||
![]() |
fcd050c017 | ||
![]() |
d7de24cd9e | ||
![]() |
547d9278d8 | ||
![]() |
5a9ef876a1 | ||
![]() |
307e4eb4b3 | ||
![]() |
b28d57086a | ||
![]() |
ff305147ca | ||
![]() |
d6a2049483 | ||
![]() |
b5e11ba1f6 | ||
![]() |
a715a767b5 | ||
![]() |
7b1f4d435b | ||
![]() |
ecacb22d37 | ||
![]() |
af49b57e60 | ||
![]() |
4913379625 | ||
![]() |
d40cafde5c | ||
![]() |
65296b9aa3 | ||
![]() |
a58e6e6bcf | ||
![]() |
8a2a9b0799 | ||
![]() |
6a755f3760 | ||
![]() |
d386df6f94 | ||
![]() |
d38b14ffb6 | ||
![]() |
7e3e1d4bca | ||
![]() |
affa787244 | ||
![]() |
014bdb4a05 | ||
![]() |
293ff179f6 | ||
![]() |
1f6e1101e8 | ||
![]() |
0dfd59bd09 | ||
![]() |
e03a0797be | ||
![]() |
3c9fa60d28 | ||
![]() |
2216f3a5da | ||
![]() |
f609c12846 | ||
![]() |
ce5ac6b12f | ||
![]() |
f495ce029c | ||
![]() |
810657dab8 | ||
![]() |
65bbb4f0eb | ||
![]() |
5112d9139d | ||
![]() |
91a162a30e | ||
![]() |
4d5cc41c65 | ||
![]() |
cc3bcbf069 | ||
![]() |
d0634e1ca0 | ||
![]() |
a7b6785420 | ||
![]() |
348c3853d4 | ||
![]() |
bfadcb7165 | ||
![]() |
6c2b43a2aa | ||
![]() |
b970cb6ea8 | ||
![]() |
c31aecf2ed | ||
![]() |
e8589dd533 | ||
![]() |
0631c3ab3f | ||
![]() |
d548a18658 | ||
![]() |
f499d34f7e | ||
![]() |
d4c9c5e226 | ||
![]() |
62c0b43caf | ||
![]() |
c24a274292 | ||
![]() |
20d4be286b | ||
![]() |
550a59f0a5 | ||
![]() |
5db2be0f4d | ||
![]() |
8d1665022a | ||
![]() |
e5978a8e76 | ||
![]() |
0f1588e3d1 |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -220,6 +220,8 @@ jobs:
|
|||||||
rsync -amv
|
rsync -amv
|
||||||
--include='*/'
|
--include='*/'
|
||||||
--include='*.html'
|
--include='*.html'
|
||||||
|
--include='*.css'
|
||||||
|
--include='*.mjs'
|
||||||
--include='*.js'
|
--include='*.js'
|
||||||
--include='*.wasm'
|
--include='*.wasm'
|
||||||
--exclude='*'
|
--exclude='*'
|
||||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@@ -38,14 +38,16 @@ out/
|
|||||||
!doc/**/*.md
|
!doc/**/*.md
|
||||||
|
|
||||||
# examples directory:
|
# examples directory:
|
||||||
!examples/**/*.txt
|
|
||||||
!examples/**/*.cpp
|
!examples/**/*.cpp
|
||||||
|
!examples/**/*.css
|
||||||
!examples/**/*.hpp
|
!examples/**/*.hpp
|
||||||
!examples/**/*.ipp
|
|
||||||
!examples/**/*.html
|
!examples/**/*.html
|
||||||
!examples/**/*.py
|
|
||||||
!examples/**/*.js
|
|
||||||
!examples/**/*.html.disabled
|
!examples/**/*.html.disabled
|
||||||
|
!examples/**/*.ipp
|
||||||
|
!examples/**/*.js
|
||||||
|
!examples/**/*.mjs
|
||||||
|
!examples/**/*.py
|
||||||
|
!examples/**/*.txt
|
||||||
|
|
||||||
# include directory:
|
# include directory:
|
||||||
!include/ftxui/**/*.hpp
|
!include/ftxui/**/*.hpp
|
||||||
|
@@ -35,6 +35,10 @@ current (development)
|
|||||||
- Bugfix: Fix cursor position in when in the last column. See #831.
|
- Bugfix: Fix cursor position in when in the last column. See #831.
|
||||||
- Bugfix: Fix `ResizeableSplit` keyboard navigation. Fixed by #842.
|
- Bugfix: Fix `ResizeableSplit` keyboard navigation. Fixed by #842.
|
||||||
- Bugfix: Fix `Menu` focus. See #841
|
- Bugfix: Fix `Menu` focus. See #841
|
||||||
|
- Feature: Add `ComponentBase::Index()`. This allows to get the index of a
|
||||||
|
component in its parent. See #932
|
||||||
|
- Feature: Add `EntryState::index`. This allows to get the index of a menu entry.
|
||||||
|
See #932
|
||||||
- Feature: Add `SliderOption::on_change`. This allows to set a callback when the
|
- Feature: Add `SliderOption::on_change`. This allows to set a callback when the
|
||||||
slider value changes. See #938.
|
slider value changes. See #938.
|
||||||
|
|
||||||
|
47
README.md
47
README.md
@@ -43,7 +43,7 @@ A simple cross-platform C++ library for terminal based user interfaces!
|
|||||||
* **Cross platform**: Linux/MacOS (main target), WebAssembly, Windows (Thanks to contributors!).
|
* **Cross platform**: Linux/MacOS (main target), WebAssembly, Windows (Thanks to contributors!).
|
||||||
* Learn by [examples](#documentation), and [tutorials](#documentation)
|
* Learn by [examples](#documentation), and [tutorials](#documentation)
|
||||||
* Multiple packages: CMake [FetchContent]([https://bewagner.net/programming/2020/05/02/cmake-fetchcontent/](https://cmake.org/cmake/help/latest/module/FetchContent.html)) (preferred), vcpkg, pkgbuild, conan.
|
* Multiple packages: CMake [FetchContent]([https://bewagner.net/programming/2020/05/02/cmake-fetchcontent/](https://cmake.org/cmake/help/latest/module/FetchContent.html)) (preferred), vcpkg, pkgbuild, conan.
|
||||||
* Good practises: documentation, tests, fuzzers, performance tests, automated CI, automated packaging, etc...
|
* Good practices: documentation, tests, fuzzers, performance tests, automated CI, automated packaging, etc...
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ A simple cross-platform C++ library for terminal based user interfaces!
|
|||||||
|
|
||||||
#### DOM
|
#### DOM
|
||||||
|
|
||||||
This module defines a hierarchical set of Element. An element manages layout and can be responsive to the terminal dimensions.
|
This module defines a hierarchical set of Element. An Element manages layout and can be responsive to the terminal dimensions.
|
||||||
|
|
||||||
They are declared in [<ftxui/dom/elements.hpp>](https://arthursonzogni.github.io/FTXUI/elements_8hpp_source.html
|
They are declared in [<ftxui/dom/elements.hpp>](https://arthursonzogni.github.io/FTXUI/elements_8hpp_source.html
|
||||||
)
|
)
|
||||||
@@ -199,7 +199,7 @@ Complex [examples](https://github.com/ArthurSonzogni/FTXUI/blob/master/examples/
|
|||||||
|
|
||||||
#### Component
|
#### Component
|
||||||
|
|
||||||
The ftxui/component is needed when you want to produce dynamic UI, reactive to the user's input. It defines a set of ftxui::Component. A component reacts to Events (keyboard, mouse, resize, ...) and Render Element (see previous section).
|
ftxui/component produces dynamic UI, reactive to the user's input. It defines a set of ftxui::Component. A component reacts to Events (keyboard, mouse, resize, ...) and Renders as an Element (see previous section).
|
||||||
|
|
||||||
Prebuilt components are declared in [<ftxui/component/component.hpp>](https://arthursonzogni.github.io/FTXUI/component_8hpp_source.html)
|
Prebuilt components are declared in [<ftxui/component/component.hpp>](https://arthursonzogni.github.io/FTXUI/component_8hpp_source.html)
|
||||||
|
|
||||||
@@ -293,7 +293,10 @@ Prebuilt components are declared in [<ftxui/component/component.hpp>](https://ar
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Libraries for FTXUI
|
## Libraries for FTXUI
|
||||||
- *Want to share a useful component using FTXUI? Feel free adding yours here*
|
- *Want to share a useful Component for FTXUI? Feel free to add yours here*
|
||||||
|
- [ftxui-grid-container](https://github.com/mingsheng13/grid-container-ftxui)
|
||||||
|
- [ftxui-ip-input](https://github.com/mingsheng13/ip-input-ftxui)
|
||||||
|
- [ftxui-image-view](https://github.com/ljrrjl/ftxui-image-view.git): For Image Display.
|
||||||
|
|
||||||
|
|
||||||
## Project using FTXUI
|
## Project using FTXUI
|
||||||
@@ -301,12 +304,12 @@ Prebuilt components are declared in [<ftxui/component/component.hpp>](https://ar
|
|||||||
Feel free to add your projects here:
|
Feel free to add your projects here:
|
||||||
- [json-tui](https://github.com/ArthurSonzogni/json-tui)
|
- [json-tui](https://github.com/ArthurSonzogni/json-tui)
|
||||||
- [git-tui](https://github.com/ArthurSonzogni/git-tui)
|
- [git-tui](https://github.com/ArthurSonzogni/git-tui)
|
||||||
|
- [ostree-tui](https://github.com/AP-Sensing/ostree-tui)
|
||||||
- [rgb-tui](https://github.com/ArthurSonzogni/rgb-tui)
|
- [rgb-tui](https://github.com/ArthurSonzogni/rgb-tui)
|
||||||
- [chrome-log-beautifier](https://github.com/ArthurSonzogni/chrome-log-beautifier)
|
- [chrome-log-beautifier](https://github.com/ArthurSonzogni/chrome-log-beautifier)
|
||||||
- [x86-64 CPU Architecture Simulation](https://github.com/AnisBdz/CPU)
|
- [x86-64 CPU Architecture Simulation](https://github.com/AnisBdz/CPU)
|
||||||
- [ltuiny](https://github.com/adrianoviana87/ltuiny)
|
- [ltuiny](https://github.com/adrianoviana87/ltuiny)
|
||||||
- [i3-termdialogs](https://github.com/mibli/i3-termdialogs)
|
- [i3-termdialogs](https://github.com/mibli/i3-termdialogs)
|
||||||
- [Just-Fast](https://github.com/GiuseppeCesarano/just-fast)
|
|
||||||
- [simpPRU](https://github.com/VedantParanjape/simpPRU)
|
- [simpPRU](https://github.com/VedantParanjape/simpPRU)
|
||||||
- [Pigeon ROS TUI](https://github.com/PigeonSensei/Pigeon_ros_tui)
|
- [Pigeon ROS TUI](https://github.com/PigeonSensei/Pigeon_ros_tui)
|
||||||
- [hastur](https://github.com/robinlinden/hastur)
|
- [hastur](https://github.com/robinlinden/hastur)
|
||||||
@@ -323,6 +326,22 @@ Feel free to add your projects here:
|
|||||||
- [eCAL monitor](https://github.com/eclipse-ecal/ecal)
|
- [eCAL monitor](https://github.com/eclipse-ecal/ecal)
|
||||||
- [Path Finder](https://github.com/Ruebled/Path_Finder)
|
- [Path Finder](https://github.com/Ruebled/Path_Finder)
|
||||||
- [rw-tui](https://github.com/LeeKyuHyuk/rw-tui)
|
- [rw-tui](https://github.com/LeeKyuHyuk/rw-tui)
|
||||||
|
- [resource-monitor](https://github.com/catalincd/resource-monitor)
|
||||||
|
- [ftxuiFileReader](https://github.com/J0sephDavis/ftxuiFileReader)
|
||||||
|
- [ftxui_CPUMeter](https://github.com/tzzzzzzzx/ftxui_CPUMeter)
|
||||||
|
- [Captain's log](https://github.com/nikoladucak/caps-log)
|
||||||
|
- [FTowerX](https://github.com/MhmRhm/FTowerX)
|
||||||
|
- [Caravan](https://github.com/r3w0p/caravan)
|
||||||
|
- [Step-Writer](https://github.com/BrianAnakPintar/step-writer)
|
||||||
|
- [XJ music](https://github.com/xjmusic/xjmusic)
|
||||||
|
- [UDP chat](https://github.com/Sergeydigl3/udp-chat-tui)
|
||||||
|
- [2048-cpp](https://github.com/Chessom/2048-cpp)
|
||||||
|
- [Memory game](https://github.com/mikolajlubiak/memory)
|
||||||
|
- [Terminal Animation](https://github.com/mikolajlubiak/terminal_animation)
|
||||||
|
- [pciex](https://github.com/s0nx/pciex)
|
||||||
|
- [Fallout terminal hacking](https://github.com/gshigin/yet-another-fallout-terminal-hacking-game)
|
||||||
|
- [Lazylist](https://github.com/zhuyongqi9/lazylist)
|
||||||
|
- [TUISIC](https://github.com/Dark-Kernel/tuisic)
|
||||||
|
|
||||||
### [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)
|
||||||
|
|
||||||
@@ -339,16 +358,15 @@ Several games using the FTXUI have been made during the Game Jam:
|
|||||||
- [smoothlife](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/smoothlife.md)
|
- [smoothlife](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/smoothlife.md)
|
||||||
- [Consu](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/consu.md)
|
- [Consu](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/consu.md)
|
||||||
|
|
||||||
## External package
|
## Utilization
|
||||||
|
|
||||||
It is **highly** recommended to use CMake FetchContent to depend on FTXUI. This
|
It is **highly** recommended to use CMake FetchContent to depend on FTXUI so you may specify which commit you would like to depend on.
|
||||||
way you can specify which commit you would like to depend on.
|
|
||||||
```cmake
|
```cmake
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
FetchContent_Declare(ftxui
|
FetchContent_Declare(ftxui
|
||||||
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
|
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
|
||||||
GIT_TAG v3.0.0
|
GIT_TAG v5.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
FetchContent_GetProperties(ftxui)
|
FetchContent_GetProperties(ftxui)
|
||||||
@@ -358,14 +376,21 @@ if(NOT ftxui_POPULATED)
|
|||||||
endif()
|
endif()
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don't, the following packages have been created:
|
If you don't, FTXUI may be used from the following packages:
|
||||||
- [vcpkg](https://vcpkgx.com/details.html?package=ftxui)
|
- [vcpkg](https://vcpkgx.com/details.html?package=ftxui)
|
||||||
- [Arch Linux PKGBUILD](https://aur.archlinux.org/packages/ftxui-git/).
|
- [Arch Linux PKGBUILD](https://aur.archlinux.org/packages/ftxui-git/).
|
||||||
- [conan.io](https://conan.io/center/ftxui)
|
- [conan.io](https://conan.io/center/ftxui)
|
||||||
- [openSUSE](https://build.opensuse.org/package/show/devel:libraries:c_c++/ftxui)
|
- [openSUSE](https://build.opensuse.org/package/show/devel:libraries:c_c++/ftxui)
|
||||||
|
-
|
||||||
[](https://repology.org/project/ftxui/versions)
|
[](https://repology.org/project/ftxui/versions)
|
||||||
|
|
||||||
|
If you choose to build and link FTXUI yourself, `ftxui-component` must be first in the linking order relative to the other FTXUI libraries, i.e.
|
||||||
|
```bash
|
||||||
|
g++ . . . -lftxui-component -lftxui-dom -lftxui-screen . . .
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
<a href="https://github.com/ArthurSonzogni/FTXUI/graphs/contributors">
|
<a href="https://github.com/ArthurSonzogni/FTXUI/graphs/contributors">
|
||||||
|
@@ -50,42 +50,17 @@ int main(void) {
|
|||||||
└────┘└────────────────────────────────────┘└─────┘
|
└────┘└────────────────────────────────────┘└─────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
# Build {#build}
|
## Configure {#configure}
|
||||||
|
### Using CMake and find_package {#build-cmake-find-package}
|
||||||
|
|
||||||
## Using CMake {#build-cmake}
|
Assuming FTXUI is available or installed on the system.
|
||||||
|
|
||||||
This is an example configuration for your **CMakeLists.txt**
|
**CMakeLists.txt**
|
||||||
|
|
||||||
CMakeLists.txt
|
|
||||||
```cmake
|
```cmake
|
||||||
cmake_minimum_required (VERSION 3.11)
|
cmake_minimum_required (VERSION 3.11)
|
||||||
|
find_package(ftxui 5 REQUIRED)
|
||||||
# --- Fetch FTXUI --------------------------------------------------------------
|
project(ftxui-starter LANGUAGES CXX VERSION 1.0.0)
|
||||||
include(FetchContent)
|
|
||||||
|
|
||||||
set(FETCHCONTENT_UPDATES_DISCONNECTED TRUE)
|
|
||||||
FetchContent_Declare(ftxui
|
|
||||||
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
|
|
||||||
# Important: Specify a GIT_TAG XXXXX here.
|
|
||||||
GIT_TAG main
|
|
||||||
)
|
|
||||||
|
|
||||||
FetchContent_GetProperties(ftxui)
|
|
||||||
if(NOT ftxui_POPULATED)
|
|
||||||
FetchContent_Populate(ftxui)
|
|
||||||
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
project(ftxui-starter
|
|
||||||
LANGUAGES CXX
|
|
||||||
VERSION 1.0.0
|
|
||||||
)
|
|
||||||
|
|
||||||
add_executable(ftxui-starter src/main.cpp)
|
add_executable(ftxui-starter src/main.cpp)
|
||||||
target_include_directories(ftxui-starter PRIVATE src)
|
|
||||||
|
|
||||||
target_link_libraries(ftxui-starter
|
target_link_libraries(ftxui-starter
|
||||||
PRIVATE ftxui::screen
|
PRIVATE ftxui::screen
|
||||||
PRIVATE ftxui::dom
|
PRIVATE ftxui::dom
|
||||||
@@ -94,7 +69,33 @@ target_link_libraries(ftxui-starter
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Subsequently, you build the project in the standard fashion as follows:
|
### Using CMake and FetchContent {#build-cmake}
|
||||||
|
|
||||||
|
If you want to fetch FTXUI using cmake:
|
||||||
|
|
||||||
|
**CMakeLists.txt**
|
||||||
|
```cmake
|
||||||
|
cmake_minimum_required (VERSION 3.11)
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
set(FETCHCONTENT_UPDATES_DISCONNECTED TRUE)
|
||||||
|
FetchContent_Declare(ftxui
|
||||||
|
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
|
||||||
|
GIT_TAG main # Important: Specify a version or a commit hash here.
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(ftxui)
|
||||||
|
|
||||||
|
project(ftxui-starter LANGUAGES CXX VERSION 1.0.0)
|
||||||
|
add_executable(ftxui-starter src/main.cpp)
|
||||||
|
target_link_libraries(ftxui-starter
|
||||||
|
PRIVATE ftxui::screen
|
||||||
|
PRIVATE ftxui::dom
|
||||||
|
PRIVATE ftxui::component # Not needed for this example.
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake ..
|
cmake ..
|
||||||
@@ -634,6 +635,26 @@ Produced by: `ftxui::Input()` from "ftxui/component/component.hpp"
|
|||||||
<script id="asciicast-223719" src="https://asciinema.org/a/223719.js" async></script>
|
<script id="asciicast-223719" src="https://asciinema.org/a/223719.js" async></script>
|
||||||
@endhtmlonly
|
@endhtmlonly
|
||||||
|
|
||||||
|
### Filtered input
|
||||||
|
|
||||||
|
On can filter out the characters received by the input component, using
|
||||||
|
`ftxui::CatchEvent`.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::string phone_number;
|
||||||
|
Component input = Input(&phone_number, "phone number");
|
||||||
|
|
||||||
|
// Filter out non-digit characters.
|
||||||
|
input |= CatchEvent([&](Event event) {
|
||||||
|
return event.is_character() && !std::isdigit(event.character()[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter out characters past the 10th one.
|
||||||
|
input |= CatchEvent([&](Event event) {
|
||||||
|
return event.is_character() && phone_number.size() >= 10;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## Menu {#component-menu}
|
## Menu {#component-menu}
|
||||||
|
|
||||||
Defines a menu object. It contains a list of entries, one of them is selected.
|
Defines a menu object. It contains a list of entries, one of them is selected.
|
||||||
|
@@ -21,6 +21,8 @@ if (EMSCRIPTEN)
|
|||||||
get_property(EXAMPLES GLOBAL PROPERTY FTXUI::EXAMPLES)
|
get_property(EXAMPLES GLOBAL PROPERTY FTXUI::EXAMPLES)
|
||||||
foreach(file
|
foreach(file
|
||||||
"index.html"
|
"index.html"
|
||||||
|
"index.mjs"
|
||||||
|
"index.css"
|
||||||
"sw.js"
|
"sw.js"
|
||||||
"run_webassembly.py")
|
"run_webassembly.py")
|
||||||
configure_file(${file} ${file})
|
configure_file(${file} ${file})
|
||||||
|
@@ -38,6 +38,7 @@ example(radiobox)
|
|||||||
example(radiobox_in_frame)
|
example(radiobox_in_frame)
|
||||||
example(renderer)
|
example(renderer)
|
||||||
example(resizable_split)
|
example(resizable_split)
|
||||||
|
example(scrollbar)
|
||||||
example(slider)
|
example(slider)
|
||||||
example(slider_direction)
|
example(slider_direction)
|
||||||
example(slider_rgb)
|
example(slider_rgb)
|
||||||
|
@@ -34,8 +34,8 @@ int main() {
|
|||||||
int value = 50;
|
int value = 50;
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
auto btn_dec_01 = Button("-1", [&] { value += 1; }, Style());
|
auto btn_dec_01 = Button("-1", [&] { value -= 1; }, Style());
|
||||||
auto btn_inc_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_dec_10 = Button("-10", [&] { value -= 10; }, Style());
|
||||||
auto btn_inc_10 = Button("+10", [&] { value += 10; }, Style());
|
auto btn_inc_10 = Button("+10", [&] { value += 10; }, Style());
|
||||||
// clang-format on
|
// clang-format on
|
||||||
@@ -53,11 +53,9 @@ int main() {
|
|||||||
return vbox({
|
return vbox({
|
||||||
text("value = " + std::to_string(value)),
|
text("value = " + std::to_string(value)),
|
||||||
separator(),
|
separator(),
|
||||||
gauge(value * 0.01f),
|
buttons->Render() | flex,
|
||||||
separator(),
|
|
||||||
buttons->Render(),
|
|
||||||
}) |
|
}) |
|
||||||
border;
|
flex | border;
|
||||||
});
|
});
|
||||||
|
|
||||||
auto screen = ScreenInteractive::FitComponent();
|
auto screen = ScreenInteractive::FitComponent();
|
||||||
|
@@ -504,7 +504,10 @@ int main() {
|
|||||||
auto main_renderer = Renderer(main_container, [&] {
|
auto main_renderer = Renderer(main_container, [&] {
|
||||||
return vbox({
|
return vbox({
|
||||||
text("FTXUI Demo") | bold | hcenter,
|
text("FTXUI Demo") | bold | hcenter,
|
||||||
tab_selection->Render(),
|
hbox({
|
||||||
|
tab_selection->Render() | flex,
|
||||||
|
exit_button->Render(),
|
||||||
|
}),
|
||||||
tab_content->Render() | flex,
|
tab_content->Render() | flex,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -15,30 +15,50 @@
|
|||||||
int main() {
|
int main() {
|
||||||
using namespace ftxui;
|
using namespace ftxui;
|
||||||
|
|
||||||
|
// The data:
|
||||||
std::string first_name;
|
std::string first_name;
|
||||||
std::string last_name;
|
std::string last_name;
|
||||||
std::string password;
|
std::string password;
|
||||||
|
std::string phoneNumber;
|
||||||
|
|
||||||
|
// The basic input components:
|
||||||
Component input_first_name = Input(&first_name, "first name");
|
Component input_first_name = Input(&first_name, "first name");
|
||||||
Component input_last_name = Input(&last_name, "last name");
|
Component input_last_name = Input(&last_name, "last name");
|
||||||
|
|
||||||
|
// The password input component:
|
||||||
InputOption password_option;
|
InputOption password_option;
|
||||||
password_option.password = true;
|
password_option.password = true;
|
||||||
Component input_password = Input(&password, "password", password_option);
|
Component input_password = Input(&password, "password", password_option);
|
||||||
|
|
||||||
|
// The phone number input component:
|
||||||
|
// We are using `CatchEvent` to filter out non-digit characters.
|
||||||
|
Component input_phone_number = Input(&phoneNumber, "phone number");
|
||||||
|
input_phone_number |= CatchEvent([&](Event event) {
|
||||||
|
return event.is_character() && !std::isdigit(event.character()[0]);
|
||||||
|
});
|
||||||
|
input_phone_number |= CatchEvent([&](Event event) {
|
||||||
|
return event.is_character() && phoneNumber.size() > 10;
|
||||||
|
});
|
||||||
|
|
||||||
|
// The component tree:
|
||||||
auto component = Container::Vertical({
|
auto component = Container::Vertical({
|
||||||
input_first_name,
|
input_first_name,
|
||||||
input_last_name,
|
input_last_name,
|
||||||
input_password,
|
input_password,
|
||||||
|
input_phone_number,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Tweak how the component tree is rendered:
|
||||||
auto renderer = Renderer(component, [&] {
|
auto renderer = Renderer(component, [&] {
|
||||||
return vbox({
|
return vbox({
|
||||||
text("Hello " + first_name + " " + last_name),
|
|
||||||
separator(),
|
|
||||||
hbox(text(" First name : "), input_first_name->Render()),
|
hbox(text(" First name : "), input_first_name->Render()),
|
||||||
hbox(text(" Last name : "), input_last_name->Render()),
|
hbox(text(" Last name : "), input_last_name->Render()),
|
||||||
hbox(text(" Password : "), input_password->Render()),
|
hbox(text(" Password : "), input_password->Render()),
|
||||||
|
hbox(text(" Phone num : "), input_phone_number->Render()),
|
||||||
|
separator(),
|
||||||
|
text("Hello " + first_name + " " + last_name),
|
||||||
|
text("Your password is " + password),
|
||||||
|
text("Your phone number is " + phoneNumber),
|
||||||
}) |
|
}) |
|
||||||
border;
|
border;
|
||||||
});
|
});
|
||||||
|
112
examples/component/scrollbar.cpp
Normal file
112
examples/component/scrollbar.cpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// Copyright 2023 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/component.hpp>
|
||||||
|
#include <ftxui/component/screen_interactive.hpp>
|
||||||
|
|
||||||
|
using namespace ftxui;
|
||||||
|
|
||||||
|
Component DummyWindowContent() {
|
||||||
|
class Impl : public ComponentBase {
|
||||||
|
private:
|
||||||
|
float scroll_x = 0.1;
|
||||||
|
float scroll_y = 0.1;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Impl() {
|
||||||
|
auto content = Renderer([=] {
|
||||||
|
const std::string lorem =
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed "
|
||||||
|
"do eiusmod tempor incididunt ut labore et dolore magna "
|
||||||
|
"aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
|
||||||
|
"ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis "
|
||||||
|
"aute irure dolor in reprehenderit in voluptate velit esse "
|
||||||
|
"cillum dolore eu fugiat nulla pariatur. Excepteur sint "
|
||||||
|
"occaecat cupidatat non proident, sunt in culpa qui officia "
|
||||||
|
"deserunt mollit anim id est laborum.";
|
||||||
|
return vbox({
|
||||||
|
text(lorem.substr(0, -1)), text(lorem.substr(5, -1)), text(""),
|
||||||
|
text(lorem.substr(10, -1)), text(lorem.substr(15, -1)), text(""),
|
||||||
|
text(lorem.substr(20, -1)), text(lorem.substr(25, -1)), text(""),
|
||||||
|
text(lorem.substr(30, -1)), text(lorem.substr(35, -1)), text(""),
|
||||||
|
text(lorem.substr(40, -1)), text(lorem.substr(45, -1)), text(""),
|
||||||
|
text(lorem.substr(50, -1)), text(lorem.substr(55, -1)), text(""),
|
||||||
|
text(lorem.substr(60, -1)), text(lorem.substr(65, -1)), text(""),
|
||||||
|
text(lorem.substr(70, -1)), text(lorem.substr(75, -1)), text(""),
|
||||||
|
text(lorem.substr(80, -1)), text(lorem.substr(85, -1)), text(""),
|
||||||
|
text(lorem.substr(90, -1)), text(lorem.substr(95, -1)), text(""),
|
||||||
|
text(lorem.substr(100, -1)), text(lorem.substr(105, -1)), text(""),
|
||||||
|
text(lorem.substr(110, -1)), text(lorem.substr(115, -1)), text(""),
|
||||||
|
text(lorem.substr(120, -1)), text(lorem.substr(125, -1)), text(""),
|
||||||
|
text(lorem.substr(130, -1)), text(lorem.substr(135, -1)), text(""),
|
||||||
|
text(lorem.substr(140, -1)),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
auto scrollable_content = Renderer(content, [&, content] {
|
||||||
|
return content->Render() | focusPositionRelative(scroll_x, scroll_y) |
|
||||||
|
frame | flex;
|
||||||
|
});
|
||||||
|
|
||||||
|
SliderOption<float> option_x;
|
||||||
|
option_x.value = &scroll_x;
|
||||||
|
option_x.min = 0.f;
|
||||||
|
option_x.max = 1.f;
|
||||||
|
option_x.increment = 0.1f;
|
||||||
|
option_x.direction = Direction::Right;
|
||||||
|
option_x.color_active = Color::Blue;
|
||||||
|
option_x.color_inactive = Color::BlueLight;
|
||||||
|
auto scrollbar_x = Slider(option_x);
|
||||||
|
|
||||||
|
SliderOption<float> option_y;
|
||||||
|
option_y.value = &scroll_y;
|
||||||
|
option_y.min = 0.f;
|
||||||
|
option_y.max = 1.f;
|
||||||
|
option_y.increment = 0.1f;
|
||||||
|
option_y.direction = Direction::Down;
|
||||||
|
option_y.color_active = Color::Yellow;
|
||||||
|
option_y.color_inactive = Color::YellowLight;
|
||||||
|
auto scrollbar_y = Slider(option_y);
|
||||||
|
|
||||||
|
Add(Container::Vertical({
|
||||||
|
Container::Horizontal({
|
||||||
|
scrollable_content,
|
||||||
|
scrollbar_y,
|
||||||
|
}) | flex,
|
||||||
|
Container::Horizontal({
|
||||||
|
scrollbar_x,
|
||||||
|
Renderer([] { return text(L"x"); }),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Make<Impl>();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto window_1 = Window({
|
||||||
|
.inner = DummyWindowContent(),
|
||||||
|
.title = "First window",
|
||||||
|
.width = 80,
|
||||||
|
.height = 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
auto window_2 = Window({
|
||||||
|
.inner = DummyWindowContent(),
|
||||||
|
.title = "My window",
|
||||||
|
.left = 40,
|
||||||
|
.top = 20,
|
||||||
|
.width = 80,
|
||||||
|
.height = 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
auto window_container = Container::Stacked({
|
||||||
|
window_1,
|
||||||
|
window_2,
|
||||||
|
});
|
||||||
|
|
||||||
|
auto screen = ScreenInteractive::Fullscreen();
|
||||||
|
screen.Loop(window_container);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
107
examples/index.css
Normal file
107
examples/index.css
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
@import url(https://fonts.googleapis.com/css?family=Khula:700);
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color:#EEE;
|
||||||
|
padding:0px;
|
||||||
|
margin:0px;
|
||||||
|
font-family: Khula, Helvetica, sans-serif;
|
||||||
|
font-size: 130%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
max-width:1300px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
box-shadow: inset 0 0 0 0 #54b3d6;
|
||||||
|
color: #0087b9;
|
||||||
|
margin: 0 -.25rem;
|
||||||
|
padding: 0 .25rem;
|
||||||
|
transition: color .3s ease-in-out,
|
||||||
|
box-shadow .3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
box-shadow: inset 120px 0 0 0 #54b3d6;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-decoration: underline;
|
||||||
|
width:100%;
|
||||||
|
background-color: rgba(100,100,255,0.5);
|
||||||
|
padding: 10px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#selectExample {
|
||||||
|
flex:1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selectExample, #selectExample option {
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.3;
|
||||||
|
border:0px;
|
||||||
|
background-color: #bbb;
|
||||||
|
color:black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selectExample:focus {
|
||||||
|
outline:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#terminal {
|
||||||
|
width:100%;
|
||||||
|
height 500px;
|
||||||
|
height: calc(clamp(200px, 100vh - 300px, 900px));
|
||||||
|
overflow: hidden;
|
||||||
|
border:none;
|
||||||
|
background-color:black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#terminalContainer {
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0px 2px 10px 0px rgba(0,0,0,0.75),
|
||||||
|
0px 2px 80px 0px rgba(0,0,0,0.50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fakeButtons {
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #000;
|
||||||
|
margin:6px;
|
||||||
|
background-color: #ff3b47;
|
||||||
|
border-color: #9d252b;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fakeMinimize {
|
||||||
|
left: 11px;
|
||||||
|
background-color: #ffc100;
|
||||||
|
border-color: #9d802c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fakeZoom {
|
||||||
|
left: 16px;
|
||||||
|
background-color: #00d742;
|
||||||
|
border-color: #049931;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fakeMenu {
|
||||||
|
display:flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width:100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 25px;
|
||||||
|
background-color: #bbb;
|
||||||
|
color:black;
|
||||||
|
margin: 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
@@ -1,174 +1,32 @@
|
|||||||
<!DOCTYPE html> <html lang="en">
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>FTXUI examples WebAssembly</title>
|
<title>FTXUI examples WebAssembly</title>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/xterm@4.18.0/lib/xterm.min.js"></script>
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>➡️</text></svg>">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-webgl@0.11.4/lib/xterm-addon-webgl.min.js"></script>
|
<link rel="stylesheet" href="index.css">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.min.js"></script>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@4.11.0/css/xterm.css"></link>
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@4.11.0/css/xterm.css"></link>
|
||||||
<!--Add COOP/COEP via a ServiceWorker to use SharedArrayBuffer-->
|
<script type="module" src="index.mjs"></script>
|
||||||
<script>
|
|
||||||
if ("serviceWorker" in navigator && !window.crossOriginIsolated) {
|
|
||||||
navigator.serviceWorker.register(new URL("./sw.js", location.href)).then(
|
|
||||||
registration => {
|
|
||||||
if (registration.active && !navigator.serviceWorker.controller) {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script id="example_script"></script>
|
<script id="example_script"></script>
|
||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<h1>FTXUI WebAssembly Example </h1>
|
|
||||||
<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. <br/>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
|
||||||
On this page, you can try all the examples contained in: <a
|
<div id="terminalContainer">
|
||||||
href="https://github.com/ArthurSonzogni/FTXUI/tree/master/examples">./example/</a>
|
<div class="fakeMenu">
|
||||||
Those are compiled using WebAssembly.
|
<div class="fakeButtons fakeClose"></div>
|
||||||
</p>
|
<div class="fakeButtons fakeMinimize"></div>
|
||||||
|
<div class="fakeButtons fakeZoom"></div>
|
||||||
<select id="selectExample"></select>
|
<select id="selectExample"></select>
|
||||||
|
</div>
|
||||||
<div id="terminal"></div>
|
<div id="terminal"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script>
|
|
||||||
const example_list = "@EXAMPLES@".split(";");
|
|
||||||
|
|
||||||
const url_search_params = new URLSearchParams(window.location.search);
|
|
||||||
const example = url_search_params.get("file") || "dom/color_gallery";
|
|
||||||
const select = document.getElementById("selectExample");
|
|
||||||
|
|
||||||
for(var i = 0; i < example_list.length; i++) {
|
|
||||||
var opt = example_list[i];
|
|
||||||
var el = document.createElement("option");
|
|
||||||
el.textContent = opt;
|
|
||||||
el.value = opt;
|
|
||||||
select.appendChild(el);
|
|
||||||
}
|
|
||||||
select.selectedIndex = example_list.findIndex(path => path == example) || 0;
|
|
||||||
select.addEventListener("change", () => {
|
|
||||||
location.href = (location.href).split('?')[0] + "?file=" +
|
|
||||||
example_list[select.selectedIndex];
|
|
||||||
});
|
|
||||||
|
|
||||||
let stdin_buffer = [];
|
|
||||||
const stdin = () => {
|
|
||||||
return stdin_buffer.shift() || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let stdout_buffer = [];
|
|
||||||
const stdout = code => {
|
|
||||||
if (code == 0) {
|
|
||||||
term.write(new Uint8Array(stdout_buffer));
|
|
||||||
stdout_buffer = [];
|
|
||||||
} else {
|
|
||||||
stdout_buffer.push(code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let stderrbuffer = [];
|
|
||||||
const stderr = code => {
|
|
||||||
if (code == 0 || code == 10) {
|
|
||||||
console.error(String.fromCodePoint(...stderrbuffer));
|
|
||||||
stderrbuffer = [];
|
|
||||||
} else {
|
|
||||||
stderrbuffer.push(code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const term = new Terminal();
|
|
||||||
const term_element = document.querySelector('#terminal');
|
|
||||||
term.open(term_element);
|
|
||||||
|
|
||||||
const webgl_addon = new (WebglAddon.WebglAddon)();
|
|
||||||
term.loadAddon(webgl_addon);
|
|
||||||
|
|
||||||
const onBinary = e => {
|
|
||||||
for(c of e)
|
|
||||||
stdin_buffer.push(c.charCodeAt(0));
|
|
||||||
}
|
|
||||||
term.onBinary(onBinary);
|
|
||||||
term.onData(onBinary)
|
|
||||||
term.resize(140,43);
|
|
||||||
window.Module = {
|
|
||||||
preRun: () => {
|
|
||||||
FS.init(stdin, stdout, stderr);
|
|
||||||
},
|
|
||||||
postRun: [],
|
|
||||||
onRuntimeInitialized: () => {
|
|
||||||
if (window.Module._ftxui_on_resize == undefined)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const fit_addon = new (FitAddon.FitAddon)();
|
|
||||||
term.loadAddon(fit_addon);
|
|
||||||
fit_addon.fit();
|
|
||||||
const resize_handler = () => {
|
|
||||||
const {cols, rows} = fit_addon.proposeDimensions();
|
|
||||||
term.resize(cols, rows);
|
|
||||||
window.Module._ftxui_on_resize(cols, rows);
|
|
||||||
};
|
|
||||||
const resize_observer = new ResizeObserver(resize_handler);
|
|
||||||
resize_observer.observe(term_element);
|
|
||||||
resize_handler();
|
|
||||||
|
|
||||||
// Disable scrollbar
|
|
||||||
term.write('\x1b[?47h')
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const words = example.split('/')
|
|
||||||
words[1] = "ftxui_example_" + words[1] + ".js"
|
|
||||||
document.querySelector("#example_script").src = words.join('/');
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color:#EEE;
|
|
||||||
padding:20px;
|
|
||||||
font-family: Helvetica, sans-serif;
|
|
||||||
font-size: 130%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page {
|
|
||||||
max-width:1300px;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
display:block;
|
|
||||||
padding: .6em 1.4em .5em .8em;
|
|
||||||
border-radius: 20px 20px 0px 0px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-weight: 700;
|
|
||||||
|
|
||||||
color: #444;
|
|
||||||
line-height: 1.3;
|
|
||||||
background-color:black;
|
|
||||||
border:0px;
|
|
||||||
color:white;
|
|
||||||
transition: color 0.2s linear;
|
|
||||||
transition: background-color 0.2s linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
#terminal {
|
|
||||||
width:100%;
|
|
||||||
height: 500px;
|
|
||||||
height: calc(clamp(200px, 100vh - 300px, 900px));
|
|
||||||
overflow: hidden;
|
|
||||||
border:none;
|
|
||||||
padding:auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
100
examples/index.mjs
Normal file
100
examples/index.mjs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import xterm from 'https://cdn.jsdelivr.net/npm/xterm@4.18.0/+esm'
|
||||||
|
import xterm_addon_webgl from 'https://cdn.jsdelivr.net/npm/xterm-addon-webgl@0.11.4/+esm'
|
||||||
|
import xterm_addon_fit from 'https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/+esm'
|
||||||
|
|
||||||
|
// Add COOP/COEP via a ServiceWorker to use SharedArrayBuffer
|
||||||
|
if ("serviceWorker" in navigator && !window.crossOriginIsolated) {
|
||||||
|
const url_sw = new URL("./sw.js", location.href);
|
||||||
|
const registration = await navigator.serviceWorker.register(url_sw);
|
||||||
|
window.location.reload(); // Reload to ensure the COOP/COEP headers are set.
|
||||||
|
}
|
||||||
|
|
||||||
|
const example_list = "@EXAMPLES@".split(";");
|
||||||
|
const url_search_params = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
const select = document.getElementById("selectExample");
|
||||||
|
for(const example of example_list) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.textContent = example;
|
||||||
|
option.value = example;
|
||||||
|
select.appendChild(option);
|
||||||
|
}
|
||||||
|
const example = url_search_params.get("file") || "dom/color_gallery";
|
||||||
|
select.selectedIndex = example_list.findIndex(path => path == example) || 0;
|
||||||
|
select.addEventListener("change", () => {
|
||||||
|
history.pushState({}, "", "?file=" + example_list[select.selectedIndex]);
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
const term_element = document.querySelector('#terminal');
|
||||||
|
const term = new xterm.Terminal();
|
||||||
|
term.options.scrollback = 0;
|
||||||
|
term.open(term_element);
|
||||||
|
const fit_addon = new xterm_addon_fit.FitAddon();
|
||||||
|
const webgl_addon = new xterm_addon_webgl.WebglAddon();
|
||||||
|
term.loadAddon(webgl_addon);
|
||||||
|
term.loadAddon(fit_addon);
|
||||||
|
|
||||||
|
const stdin_buffer = [];
|
||||||
|
const stdout_buffer = [];
|
||||||
|
const stderr_buffer = [];
|
||||||
|
|
||||||
|
const stdin = () => {
|
||||||
|
return stdin_buffer.shift() || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stdout = code => {
|
||||||
|
if (code == 0) {
|
||||||
|
term.write(new Uint8Array(stdout_buffer));
|
||||||
|
stdout_buffer.length = 0;
|
||||||
|
} else {
|
||||||
|
stdout_buffer.push(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stderr = code => {
|
||||||
|
if (code == 0 || code == 10) {
|
||||||
|
console.error(String.fromCodePoint(...stderr_buffer));
|
||||||
|
stderr_buffer = [];
|
||||||
|
} else {
|
||||||
|
stderr_buffer.push(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onBinary = e => {
|
||||||
|
for(const c of e)
|
||||||
|
stdin_buffer.push(c.charCodeAt(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
term.onBinary(onBinary);
|
||||||
|
term.onData(onBinary)
|
||||||
|
term.resize(140,43);
|
||||||
|
|
||||||
|
window.Module = {
|
||||||
|
preRun: () => {
|
||||||
|
FS.init(stdin, stdout, stderr);
|
||||||
|
},
|
||||||
|
postRun: [],
|
||||||
|
onRuntimeInitialized: () => {
|
||||||
|
if (window.Module._ftxui_on_resize == undefined)
|
||||||
|
return;
|
||||||
|
fit_addon.fit();
|
||||||
|
|
||||||
|
const resize_handler = () => {
|
||||||
|
const {cols, rows} = fit_addon.proposeDimensions();
|
||||||
|
term.resize(cols, rows);
|
||||||
|
window.Module._ftxui_on_resize(cols, rows);
|
||||||
|
fit_addon.fit();
|
||||||
|
};
|
||||||
|
const resize_observer = new ResizeObserver(resize_handler);
|
||||||
|
resize_observer.observe(term_element);
|
||||||
|
resize_handler();
|
||||||
|
|
||||||
|
// Disable scrollbar
|
||||||
|
//term.write('\x1b[?47h')
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const words = example.split('/')
|
||||||
|
words[1] = "ftxui_example_" + words[1] + ".js"
|
||||||
|
document.querySelector("#example_script").src = words.join('/');
|
30
flake.lock
generated
30
flake.lock
generated
@@ -1,12 +1,15 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678901627,
|
"lastModified": 1694529238,
|
||||||
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
|
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
|
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -17,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1679734080,
|
"lastModified": 1697915759,
|
||||||
"narHash": "sha256-z846xfGLlon6t9lqUzlNtBOmsgQLQIZvR6Lt2dImk1M=",
|
"narHash": "sha256-WyMj5jGcecD+KC8gEs+wFth1J1wjisZf8kVZH13f1Zo=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "dbf5322e93bcc6cfc52268367a8ad21c09d76fea",
|
"rev": "51d906d2341c9e866e48c2efcaac0f2d70bfd43e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -36,6 +39,21 @@
|
|||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
17
flake.nix
17
flake.nix
@@ -9,8 +9,10 @@
|
|||||||
outputs = {self, nixpkgs, flake-utils}:
|
outputs = {self, nixpkgs, flake-utils}:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
let pkgs = import nixpkgs { inherit system; }; in
|
let pkgs = import nixpkgs { inherit system; }; in
|
||||||
|
let llvm = pkgs.llvmPackages_latest; in
|
||||||
{
|
{
|
||||||
packages.ftxui = pkgs.stdenv.mkDerivation rec {
|
packages = rec {
|
||||||
|
default = pkgs.stdenv.mkDerivation rec {
|
||||||
pname = "ftxui";
|
pname = "ftxui";
|
||||||
version = "v4.0.0";
|
version = "v4.0.0";
|
||||||
src = pkgs.fetchFromGitHub {
|
src = pkgs.fetchFromGitHub {
|
||||||
@@ -56,6 +58,19 @@
|
|||||||
platforms = pkgs.lib.platforms.all;
|
platforms = pkgs.lib.platforms.all;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ftxui = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells = {
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkgs.cmake
|
||||||
|
pkgs.clang-tools
|
||||||
|
llvm.clang
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -44,6 +44,7 @@ class ComponentBase {
|
|||||||
ComponentBase* Parent() const;
|
ComponentBase* Parent() const;
|
||||||
Component& ChildAt(size_t i);
|
Component& ChildAt(size_t i);
|
||||||
size_t ChildCount() const;
|
size_t ChildCount() const;
|
||||||
|
int Index() const;
|
||||||
void Add(Component children);
|
void Add(Component children);
|
||||||
void Detach();
|
void Detach();
|
||||||
void DetachAllChildren();
|
void DetachAllChildren();
|
||||||
|
@@ -25,6 +25,7 @@ struct EntryState {
|
|||||||
bool state; ///< The state of the button/checkbox/radiobox
|
bool state; ///< The state of the button/checkbox/radiobox
|
||||||
bool active; ///< Whether the entry is the active one.
|
bool active; ///< Whether the entry is the active one.
|
||||||
bool focused; ///< Whether the entry is one focused by the user.
|
bool focused; ///< Whether the entry is one focused by the user.
|
||||||
|
int index; ///< Index of the entry when applicable or -1.
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UnderlineOption {
|
struct UnderlineOption {
|
||||||
|
@@ -22,7 +22,7 @@ using Elements = std::vector<Element>;
|
|||||||
class Node {
|
class Node {
|
||||||
public:
|
public:
|
||||||
Node();
|
Node();
|
||||||
Node(Elements children);
|
explicit Node(Elements children);
|
||||||
Node(const Node&) = delete;
|
Node(const Node&) = delete;
|
||||||
Node(const Node&&) = delete;
|
Node(const Node&&) = delete;
|
||||||
Node& operator=(const Node&) = delete;
|
Node& operator=(const Node&) = delete;
|
||||||
|
@@ -36,8 +36,8 @@ class TableSelection;
|
|||||||
class Table {
|
class Table {
|
||||||
public:
|
public:
|
||||||
Table();
|
Table();
|
||||||
Table(std::vector<std::vector<std::string>>);
|
explicit Table(std::vector<std::vector<std::string>>);
|
||||||
Table(std::vector<std::vector<Element>>);
|
explicit Table(std::vector<std::vector<Element>>);
|
||||||
Table(std::initializer_list<std::vector<std::string>> init);
|
Table(std::initializer_list<std::vector<std::string>> init);
|
||||||
TableSelection SelectAll();
|
TableSelection SelectAll();
|
||||||
TableSelection SelectCell(int column, int row);
|
TableSelection SelectCell(int column, int row);
|
||||||
|
@@ -48,11 +48,8 @@ class ButtonBase : public ComponentBase, public ButtonOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto focus_management = focused ? focus : active ? select : nothing;
|
auto focus_management = focused ? focus : active ? select : nothing;
|
||||||
const EntryState state = {
|
const EntryState state{
|
||||||
*label,
|
*label, false, active, focused_or_hover, Index(),
|
||||||
false,
|
|
||||||
active,
|
|
||||||
focused_or_hover,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auto element = (transform ? transform : DefaultTransform) //
|
auto element = (transform ? transform : DefaultTransform) //
|
||||||
|
@@ -28,10 +28,7 @@ class CheckboxBase : public ComponentBase, public CheckboxOption {
|
|||||||
const bool is_active = Active();
|
const bool is_active = Active();
|
||||||
auto focus_management = is_focused ? focus : is_active ? select : nothing;
|
auto focus_management = is_focused ? focus : is_active ? select : nothing;
|
||||||
auto entry_state = EntryState{
|
auto entry_state = EntryState{
|
||||||
*label,
|
*label, *checked, is_active, is_focused || hovered_, -1,
|
||||||
*checked,
|
|
||||||
is_active,
|
|
||||||
is_focused || hovered_,
|
|
||||||
};
|
};
|
||||||
auto element = (transform ? transform : CheckboxOption::Simple().transform)(
|
auto element = (transform ? transform : CheckboxOption::Simple().transform)(
|
||||||
entry_state);
|
entry_state);
|
||||||
|
@@ -51,6 +51,22 @@ size_t ComponentBase::ChildCount() const {
|
|||||||
return children_.size();
|
return children_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief Return index of the component in its parent. -1 if no parent.
|
||||||
|
/// @ingroup component
|
||||||
|
int ComponentBase::Index() const {
|
||||||
|
if (parent_ == nullptr) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
for (const Component& child : parent_->children_) {
|
||||||
|
if (child.get() == this) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return -1; // Not reached.
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief Add a child.
|
/// @brief Add a child.
|
||||||
/// @@param child The child to be attached.
|
/// @@param child The child to be attached.
|
||||||
/// @ingroup component
|
/// @ingroup component
|
||||||
|
@@ -123,10 +123,7 @@ class MenuBase : public ComponentBase, public MenuOption {
|
|||||||
const bool is_selected = (selected() == i);
|
const bool is_selected = (selected() == i);
|
||||||
|
|
||||||
const EntryState state = {
|
const EntryState state = {
|
||||||
entries[i],
|
entries[i], false, is_selected, is_focused, i,
|
||||||
false,
|
|
||||||
is_selected,
|
|
||||||
is_focused,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auto focus_management = (selected_focus_ != i) ? nothing
|
auto focus_management = (selected_focus_ != i) ? nothing
|
||||||
@@ -625,11 +622,8 @@ Component MenuEntry(MenuEntryOption option) {
|
|||||||
const bool focused = Focused();
|
const bool focused = Focused();
|
||||||
UpdateAnimationTarget();
|
UpdateAnimationTarget();
|
||||||
|
|
||||||
const EntryState state = {
|
const EntryState state{
|
||||||
label(),
|
label(), false, hovered_, focused, Index(),
|
||||||
false,
|
|
||||||
hovered_,
|
|
||||||
focused,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Element element =
|
const Element element =
|
||||||
|
@@ -226,5 +226,50 @@ TEST(MenuTest, AnimationsVertical) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(MenuTest, EntryIndex) {
|
||||||
|
int selected = 0;
|
||||||
|
std::vector<std::string> entries = {"0", "1", "2"};
|
||||||
|
|
||||||
|
auto option = MenuOption::Vertical();
|
||||||
|
option.entries = &entries;
|
||||||
|
option.selected = &selected;
|
||||||
|
option.entries_option.transform = [&](const EntryState& state) {
|
||||||
|
int curidx = std::stoi(state.label);
|
||||||
|
EXPECT_EQ(state.index, curidx);
|
||||||
|
return text(state.label);
|
||||||
|
};
|
||||||
|
auto menu = Menu(option);
|
||||||
|
menu->OnEvent(Event::ArrowDown);
|
||||||
|
menu->OnEvent(Event::ArrowDown);
|
||||||
|
menu->OnEvent(Event::Return);
|
||||||
|
entries.resize(2);
|
||||||
|
(void)menu->Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MenuTest, MenuEntryIndex) {
|
||||||
|
int selected = 0;
|
||||||
|
|
||||||
|
MenuEntryOption option;
|
||||||
|
option.transform = [&](const EntryState& state) {
|
||||||
|
int curidx = std::stoi(state.label);
|
||||||
|
EXPECT_EQ(state.index, curidx);
|
||||||
|
return text(state.label);
|
||||||
|
};
|
||||||
|
auto menu = Container::Vertical(
|
||||||
|
{
|
||||||
|
MenuEntry("0", option),
|
||||||
|
MenuEntry("1", option),
|
||||||
|
MenuEntry("2", option),
|
||||||
|
},
|
||||||
|
&selected);
|
||||||
|
|
||||||
|
menu->OnEvent(Event::ArrowDown);
|
||||||
|
menu->OnEvent(Event::ArrowDown);
|
||||||
|
menu->OnEvent(Event::Return);
|
||||||
|
for (int index = 0; index < menu->ChildCount(); index++) {
|
||||||
|
EXPECT_EQ(menu->ChildAt(index)->Index(), index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ftxui
|
} // namespace ftxui
|
||||||
// NOLINTEND
|
// NOLINTEND
|
||||||
|
@@ -40,10 +40,7 @@ class RadioboxBase : public ComponentBase, public RadioboxOption {
|
|||||||
: is_menu_focused ? focus
|
: is_menu_focused ? focus
|
||||||
: select;
|
: select;
|
||||||
auto state = EntryState{
|
auto state = EntryState{
|
||||||
entries[i],
|
entries[i], selected() == i, is_selected, is_focused, i,
|
||||||
selected() == i,
|
|
||||||
is_selected,
|
|
||||||
is_focused,
|
|
||||||
};
|
};
|
||||||
auto element =
|
auto element =
|
||||||
(transform ? transform : RadioboxOption::Simple().transform)(state);
|
(transform ? transform : RadioboxOption::Simple().transform)(state);
|
||||||
|
@@ -93,7 +93,7 @@ class ResizeDecorator : public NodeDecorator {
|
|||||||
|
|
||||||
Element DefaultRenderState(const WindowRenderState& state) {
|
Element DefaultRenderState(const WindowRenderState& state) {
|
||||||
Element element = state.inner;
|
Element element = state.inner;
|
||||||
if (state.active) {
|
if (!state.active) {
|
||||||
element |= dim;
|
element |= dim;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user