mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-28 16:29:34 +08:00
Compare commits
77 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d301fab1f4 | ||
![]() |
3e35f45830 | ||
![]() |
a4e70dfb93 | ||
![]() |
b2853c8f14 | ||
![]() |
6fe8310321 | ||
![]() |
350dcac032 | ||
![]() |
65848d1e5f | ||
![]() |
1561293140 | ||
![]() |
90dfceefcb | ||
![]() |
5410329ac6 | ||
![]() |
9f9571190a | ||
![]() |
b56afce48c | ||
![]() |
abd5b2a503 | ||
![]() |
60b9e491db | ||
![]() |
6cea410eaa | ||
![]() |
a52b959f66 | ||
![]() |
0542227ba7 | ||
![]() |
4dc1a9fff9 | ||
![]() |
b9f51844c3 | ||
![]() |
0d54285e19 | ||
![]() |
f21ca3aa14 | ||
![]() |
05f29ff3b3 | ||
![]() |
55b9706cfd | ||
![]() |
121bd0d046 | ||
![]() |
2c5681ee20 | ||
![]() |
1689802349 | ||
![]() |
9babfea36b | ||
![]() |
dfdbe1eecf | ||
![]() |
aeaf39b8ea | ||
![]() |
0acfd8f255 | ||
![]() |
26d63bc56f | ||
![]() |
f692a50195 | ||
![]() |
e04ea27dcd | ||
![]() |
f4b47333be | ||
![]() |
ccfe22bc24 | ||
![]() |
5ba29a9539 | ||
![]() |
c61fadd8ec | ||
![]() |
b3aad183da | ||
![]() |
fab74f745d | ||
![]() |
c8ec151154 | ||
![]() |
1d76a2321c | ||
![]() |
1661a5e83d | ||
![]() |
b3ba747d82 | ||
![]() |
8226c5aea7 | ||
![]() |
1e381fcad6 | ||
![]() |
d04e04adc6 | ||
![]() |
ec994a4e65 | ||
![]() |
3ec765e1f0 | ||
![]() |
251306a4bb | ||
![]() |
7cc68cfbd0 | ||
![]() |
36460fea2a | ||
![]() |
d755356481 | ||
![]() |
f461050759 | ||
![]() |
e42ab7b2e6 | ||
![]() |
d9241435ce | ||
![]() |
d5044bdaaf | ||
![]() |
f91677e79f | ||
![]() |
d6da30a518 | ||
![]() |
d5b1899290 | ||
![]() |
d545fb6f19 | ||
![]() |
f80e20c4aa | ||
![]() |
d805eb0648 | ||
![]() |
940f805b8f | ||
![]() |
57da24dfdb | ||
![]() |
0abaab6268 | ||
![]() |
ef0f1da147 | ||
![]() |
137f1fbf67 | ||
![]() |
094d8d9d0a | ||
![]() |
81e086788d | ||
![]() |
925a7578d4 | ||
![]() |
bb3231695f | ||
![]() |
0a522488a7 | ||
![]() |
b63aa9e375 | ||
![]() |
ed5b4cec49 | ||
![]() |
219daf46ff | ||
![]() |
11519ef1c6 | ||
![]() |
f9256fa132 |
10
.clang-tidy
10
.clang-tidy
@@ -8,14 +8,16 @@ Checks: "*,
|
||||
-llvm*,
|
||||
-modernize-use-trailing-return-type,
|
||||
-zircon-*,
|
||||
-readability-else-after-return,
|
||||
-readability-static-accessed-through-instance,
|
||||
-readability-avoid-const-params-in-decls,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||
-misc-no-recursion,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-modernize-use-nodiscard,
|
||||
-misc-no-recursion,
|
||||
-readability-avoid-const-params-in-decls,
|
||||
-readability-else-after-return,
|
||||
-readability-identifier-length,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-static-accessed-through-instance,
|
||||
"
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: ''
|
||||
|
24
.github/workflows/build.yaml
vendored
24
.github/workflows/build.yaml
vendored
@@ -51,7 +51,7 @@ jobs:
|
||||
ninja: true
|
||||
clangtidy: true
|
||||
cppcheck: false
|
||||
gcovr: true
|
||||
gcovr: "5.0"
|
||||
opencppcoverage: true
|
||||
|
||||
# make sure coverage is only enabled for Debug builds, since it sets -O0
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
ctest -C Debug --rerun-failed --output-on-failure;
|
||||
|
||||
- name: Publish to codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
flags: ${{ runner.os }}
|
||||
name: ${{ runner.os }}-coverage
|
||||
@@ -169,7 +169,7 @@ jobs:
|
||||
overwrite: true
|
||||
|
||||
documentation:
|
||||
needs: package
|
||||
if: github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Checkout repository"
|
||||
@@ -190,9 +190,23 @@ jobs:
|
||||
run: >
|
||||
mkdir build;
|
||||
cd build;
|
||||
emcmake cmake ..;
|
||||
emcmake cmake ..
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DFTXUI_BUILD_DOCS=ON
|
||||
-DFTXUI_BUILD_EXAMPLES=ON
|
||||
-DFTXUI_BUILD_TESTS=OFF
|
||||
-DFTXUI_BUILD_TESTS_FUZZER=OFF
|
||||
-DFTXUI_ENABLE_INSTALL=OFF;
|
||||
cmake --build . --target doc;
|
||||
rsync -amv --include='*/' --include='*.html' --include='*.js' --include='*.wasm' --exclude='*' examples doc/doxygen/html;
|
||||
cmake --build . ;
|
||||
rsync -amv
|
||||
--include='*/'
|
||||
--include='*.html'
|
||||
--include='*.js'
|
||||
--include='*.wasm'
|
||||
--exclude='*'
|
||||
examples
|
||||
doc/doxygen/html;
|
||||
|
||||
- name: "Deploy"
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
|
52
CHANGELOG.md
52
CHANGELOG.md
@@ -4,6 +4,58 @@ Changelog
|
||||
current (development)
|
||||
---------------------
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
||||
### DOM
|
||||
- Feature: more styles:
|
||||
- `strikethrough`
|
||||
- `underlinedDouble`
|
||||
- Feature: Customize the cursor. Add the following decorators:
|
||||
- `focusCursorBlock`
|
||||
- `focusCursorBlockBlinking`
|
||||
- `focusCursorBar`
|
||||
- `focusCursorBarBlinking`
|
||||
- `focusCursorUnderline`
|
||||
- `focusCursorUnderlineBlinking`
|
||||
- Bugfix: Fix `focus`/`select` when the `vbox`/`hbox`/`dbox` contains a
|
||||
`flexbox`
|
||||
- Bugfix: Fix the selected/focused area. It used to be 1 cell larger/longer than
|
||||
requested
|
||||
- Bugfix: Forward the selected/focused area from the child in gridbox.
|
||||
- Bugfix: Fix incorrect Canvas computed dimensions.
|
||||
- Bugfix: Support `vscroll_indicator` with a zero inner size.
|
||||
- Bugfix: Fix `vscroll_indicator` hidding the last column.
|
||||
|
||||
### Component:
|
||||
- Feature: Add the `Modal` component.
|
||||
- Feature: `Slider` supports taking references for all its arguments.
|
||||
- Feature: `Slider` supports `SliderOption`. It supports:
|
||||
- multiple directions.
|
||||
- multiple colors.
|
||||
- various values (value, min, max, increment).
|
||||
- Feature: Define `ScreenInteractive::Exit()`.
|
||||
- Feature: Add `Loop` to give developers a better control on the main loop. This
|
||||
can be used to integrate FTXUI into another main loop, without taking the full
|
||||
control.
|
||||
- Feature: `Input` supports CTRL+Left and CTRL+Right
|
||||
- Feature: Use a blinking bar in the `Input` component.
|
||||
- Improvement: The `Menu` keeps the focus when an entry is selected with the
|
||||
mouse.
|
||||
- Bugfix: Add implementation of `ButtonOption::Border()`. It was missing.
|
||||
- Bugfix: Provide the correct key for F1-F4 and F11.
|
||||
- Feature: Add the `Hoverable` component decorators.
|
||||
|
||||
### Screen
|
||||
- Feature: add `Box::Union(a,b) -> Box`
|
||||
- Bugfix: Fix resetting `dim` clashing with resetting of `bold`.
|
||||
- Feature: Add emscripten screen resize support.
|
||||
- Bugfix: Add unicode 13 support for full width characters.
|
||||
- Bugfix: Fix MSVC treating codecvt C++17 deprecated function as an error.
|
||||
|
||||
### Build
|
||||
- Support using the google test version provided by the package manager.
|
||||
|
||||
3.0.0
|
||||
-----
|
||||
|
||||
|
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.11)
|
||||
|
||||
project(ftxui
|
||||
LANGUAGES CXX
|
||||
VERSION 3.0.0
|
||||
VERSION 4.0.0
|
||||
)
|
||||
|
||||
option(FTXUI_BUILD_DOCS "Set to ON to build docs" ON)
|
||||
@@ -79,9 +79,11 @@ add_library(dom
|
||||
src/ftxui/dom/separator.cpp
|
||||
src/ftxui/dom/size.cpp
|
||||
src/ftxui/dom/spinner.cpp
|
||||
src/ftxui/dom/strikethrough.cpp
|
||||
src/ftxui/dom/table.cpp
|
||||
src/ftxui/dom/text.cpp
|
||||
src/ftxui/dom/underlined.cpp
|
||||
src/ftxui/dom/underlined_double.cpp
|
||||
src/ftxui/dom/util.cpp
|
||||
src/ftxui/dom/vbox.cpp
|
||||
)
|
||||
@@ -93,6 +95,7 @@ add_library(component
|
||||
include/ftxui/component/component_base.hpp
|
||||
include/ftxui/component/component_options.hpp
|
||||
include/ftxui/component/event.hpp
|
||||
include/ftxui/component/loop.hpp
|
||||
include/ftxui/component/mouse.hpp
|
||||
include/ftxui/component/receiver.hpp
|
||||
include/ftxui/component/screen_interactive.hpp
|
||||
@@ -107,9 +110,12 @@ add_library(component
|
||||
src/ftxui/component/container.cpp
|
||||
src/ftxui/component/dropdown.cpp
|
||||
src/ftxui/component/event.cpp
|
||||
src/ftxui/component/hoverable.cpp
|
||||
src/ftxui/component/input.cpp
|
||||
src/ftxui/component/loop.cpp
|
||||
src/ftxui/component/maybe.cpp
|
||||
src/ftxui/component/menu.cpp
|
||||
src/ftxui/component/modal.cpp
|
||||
src/ftxui/component/radiobox.cpp
|
||||
src/ftxui/component/radiobox.cpp
|
||||
src/ftxui/component/renderer.cpp
|
||||
@@ -125,15 +131,16 @@ target_link_libraries(dom
|
||||
PUBLIC screen
|
||||
)
|
||||
|
||||
find_package(Threads)
|
||||
target_link_libraries(component
|
||||
PUBLIC dom
|
||||
PUBLIC Threads::Threads
|
||||
)
|
||||
|
||||
set_target_properties(screen PROPERTIES VERSION ${PROJECT_VERSION})
|
||||
set_target_properties(dom PROPERTIES VERSION ${PROJECT_VERSION})
|
||||
set_target_properties(component PROPERTIES VERSION ${PROJECT_VERSION})
|
||||
if (NOT EMSCRIPTEN)
|
||||
find_package(Threads)
|
||||
target_link_libraries(component
|
||||
PUBLIC Threads::Threads
|
||||
)
|
||||
endif()
|
||||
|
||||
include(cmake/ftxui_set_options.cmake)
|
||||
ftxui_set_options(screen)
|
||||
@@ -145,22 +152,13 @@ ftxui_check_coverage(screen)
|
||||
ftxui_check_coverage(dom)
|
||||
ftxui_check_coverage(component)
|
||||
|
||||
if (FTXUI_BUILD_TESTS AND ${CMAKE_VERSION} VERSION_GREATER "3.11.4")
|
||||
include(cmake/ftxui_test.cmake)
|
||||
endif()
|
||||
|
||||
if(FTXUI_BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
|
||||
if(FTXUI_BUILD_DOCS)
|
||||
add_subdirectory(doc)
|
||||
endif()
|
||||
|
||||
include(cmake/ftxui_test.cmake)
|
||||
include(cmake/ftxui_benchmark.cmake)
|
||||
include(cmake/ftxui_fuzzer.cmake)
|
||||
include(cmake/iwyu.cmake)
|
||||
include(cmake/ftxui_export.cmake)
|
||||
include(cmake/ftxui_install.cmake)
|
||||
include(cmake/ftxui_package.cmake)
|
||||
|
||||
if(FTXUI_ENABLE_INSTALL)
|
||||
include(cmake/ftxui_install.cmake)
|
||||
include(cmake/ftxui_package.cmake)
|
||||
endif()
|
||||
add_subdirectory(examples)
|
||||
add_subdirectory(doc)
|
||||
|
24
README.md
24
README.md
@@ -35,8 +35,8 @@ A simple C++ library for terminal based user interfaces!
|
||||
* Simple and elegant syntax (in my opinion)
|
||||
* Keyboard & mouse navigation.
|
||||
* Support for [UTF8](https://en.wikipedia.org/wiki/UTF-8) and [fullwidth chars](https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms) (→ 测试)
|
||||
* Support for animations. [Demo 1](https://arthursonzogni.com/FTXUI/examples/?file=component/menu_underline_animated_gallery), [Demo 2](https://arthursonzogni.com/FTXUI/examples/?file=component/button_style)
|
||||
* Support for drawing. [Demo](https://arthursonzogni.com/FTXUI/examples/?file=component/canvas_animated)
|
||||
* Support for animations. [Demo 1](https://arthursonzogni.github.io/FTXUI/examples/?file=component/menu_underline_animated_gallery), [Demo 2](https://arthursonzogni.github.io/FTXUI/examples/?file=component/button_style)
|
||||
* Support for drawing. [Demo](https://arthursonzogni.github.io/FTXUI/examples/?file=component/canvas_animated)
|
||||
* No dependencies
|
||||
* Cross platform: Linux/MacOS (main target), WebAssembly, Windows (Thanks to contributors!).
|
||||
* Learn by [examples](#documentation), and [tutorials](#documentation)
|
||||
@@ -47,8 +47,8 @@ A simple C++ library for terminal based user interfaces!
|
||||
|
||||
- [Starter example project](https://github.com/ArthurSonzogni/ftxui-starter)
|
||||
- [Documentation](https://arthursonzogni.github.io/FTXUI/)
|
||||
- [Examples (WebAssembly)](https://arthursonzogni.com/FTXUI/examples/)
|
||||
- [Build using CMake](https://github.com/ArthurSonzogni/FTXUI/blob/master/doc/mainpage.md#using-cmake)
|
||||
- [Examples (WebAssembly)](https://arthursonzogni.github.io/FTXUI/examples/)
|
||||
- [Build using CMake](https://arthursonzogni.github.io/FTXUI/#build-cmake)
|
||||
|
||||
## Operating systems
|
||||
|
||||
@@ -111,7 +111,7 @@ Element can become flexible using the the `flex` decorator.
|
||||
|
||||

|
||||
|
||||
[See](https://arthursonzogni.github.io/FTXUI/examples_2dom_2hflow_8cpp-example.html) also this [demo](https://arthursonzogni.com/FTXUI/examples/?file=component/flexbox).
|
||||
[See](https://arthursonzogni.github.io/FTXUI/examples_2dom_2hflow_8cpp-example.html) also this [demo](https://arthursonzogni.github.io/FTXUI/examples/?file=component/flexbox).
|
||||
|
||||
</details>
|
||||
|
||||
@@ -122,7 +122,9 @@ An element can be decorated using the functions:
|
||||
- `dim`
|
||||
- `inverted`
|
||||
- `underlined`
|
||||
- `underlinedDouble`
|
||||
- `blink`
|
||||
- `strikethrough`
|
||||
- `color`
|
||||
- `bgcolor`
|
||||
|
||||
@@ -212,7 +214,7 @@ Prebuilt components are declared in [<ftxui/component/component.hpp>](https://ar
|
||||
|
||||
<details><summary>Gallery</summary>
|
||||
|
||||
[Gallery](https://arthursonzogni.github.io/FTXUI/examples_2component_2gallery_8cpp-example.html) of multiple components. ([demo](https://arthursonzogni.com/FTXUI/examples/?file=component/gallery))
|
||||
[Gallery](https://arthursonzogni.github.io/FTXUI/examples_2component_2gallery_8cpp-example.html) of multiple components. ([demo](https://arthursonzogni.github.io/FTXUI/examples/?file=component/gallery))
|
||||
|
||||

|
||||
|
||||
@@ -324,6 +326,10 @@ Feel free to add your projects here:
|
||||
- [tabdeeli](https://github.com/typon/tabdeeli)
|
||||
- [tiles](https://github.com/tusharpm/tiles)
|
||||
- [cachyos-cli-installer](https://github.com/cachyos/new-cli-installer)
|
||||
- [beagle-config](https://github.com/SAtacker/beagle-config)
|
||||
- [turing_cmd](https://github.com/DanArmor/turing_cmd)
|
||||
- [StartUp](https://github.com/StubbornVegeta/StartUp)
|
||||
- [eCAL monitor](https://github.com/eclipse-ecal/ecal)
|
||||
|
||||
## [cpp-best-practices/game_jam](https://github.com/cpp-best-practices/game_jam)
|
||||
|
||||
@@ -335,7 +341,7 @@ Several games using the FTXUI have been made during the Game Jam:
|
||||
- [DanteO](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/danteo.md)
|
||||
- [Sumo](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/sumo.md)
|
||||
- [Drag Me aROUND](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/drag_me_around.md)
|
||||
- [DisarmSelfDestruct](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/LightsRound.v.0.1.0.md)
|
||||
- [DisarmSelfDestruct](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/DisarmSelfDestruct.md)
|
||||
- [TheWorld](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/TheWorld.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)
|
||||
@@ -349,7 +355,7 @@ include(FetchContent)
|
||||
|
||||
FetchContent_Declare(ftxui
|
||||
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
|
||||
GIT_TAG v2.0.0
|
||||
GIT_TAG v3.0.0
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(ftxui)
|
||||
@@ -360,7 +366,7 @@ endif()
|
||||
```
|
||||
|
||||
If you don't, the following packages have been created:
|
||||
- [vcpkg](https://vcpkg.info/port/ftxui)
|
||||
- [vcpkg](https://vcpkgx.com/details.html?package=ftxui)
|
||||
- [Arch Linux PKGBUILD](https://aur.archlinux.org/packages/ftxui-git/).
|
||||
- [conan.io](https://conan.io/center/ftxui)
|
||||
|
||||
|
@@ -1,31 +1,21 @@
|
||||
if (NOT WIN32)
|
||||
FetchContent_Declare(googlebenchmark
|
||||
GIT_REPOSITORY "https://github.com/google/benchmark"
|
||||
GIT_TAG 62937f91b5c763a8e119d0c20c67b87bde8eff1c
|
||||
GIT_PROGRESS TRUE
|
||||
if (NOT FTXUI_BUILD_TESTS OR
|
||||
NOT ${CMAKE_VERSION} VERSION_GREATER "3.11.4" OR
|
||||
WIN32
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
FetchContent_GetProperties(googlebenchmark)
|
||||
set (BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE INTERNAL "")
|
||||
set (BENCHMARK_ENABLE_TESTING OFF CACHE INTERNAL "")
|
||||
if(NOT googlebenchmark_POPULATED)
|
||||
FetchContent_Populate(googlebenchmark)
|
||||
add_subdirectory(
|
||||
${googlebenchmark_SOURCE_DIR}
|
||||
${googlebenchmark_BINARY_DIR}
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
endif()
|
||||
include(cmake/ftxui_find_google_benchmark.cmake)
|
||||
|
||||
add_executable(ftxui_benchmark
|
||||
add_executable(ftxui-benchmark
|
||||
src/ftxui/dom/benchmark_test.cpp
|
||||
)
|
||||
target_link_libraries(ftxui_benchmark
|
||||
ftxui_set_options(ftxui-benchmark)
|
||||
target_link_libraries(ftxui-benchmark
|
||||
PRIVATE dom
|
||||
PRIVATE benchmark::benchmark
|
||||
PRIVATE benchmark::benchmark_main
|
||||
)
|
||||
target_include_directories(ftxui_benchmark
|
||||
target_include_directories(ftxui-benchmark
|
||||
PRIVATE src
|
||||
)
|
||||
endif()
|
||||
|
32
cmake/ftxui_find_google_benchmark.cmake
Normal file
32
cmake/ftxui_find_google_benchmark.cmake
Normal file
@@ -0,0 +1,32 @@
|
||||
# Some developers would be happier with the google benchmark version provided
|
||||
# from their package manager. Use it if it is installed the package provide
|
||||
# cmake support.
|
||||
# https://github.com/ArthurSonzogni/FTXUI/issues/551
|
||||
find_package(benchmark QUIET)
|
||||
if (benchmark_FOUND)
|
||||
return()
|
||||
endif()
|
||||
|
||||
option(FETCHCONTENT_UPDATES_DISCONNECTED TRUE)
|
||||
option(FETCHCONTENT_QUIET FALSE)
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(googlebenchmark
|
||||
GIT_REPOSITORY "https://github.com/google/benchmark"
|
||||
GIT_TAG 62937f91b5c763a8e119d0c20c67b87bde8eff1c
|
||||
GIT_PROGRESS TRUE
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(googlebenchmark)
|
||||
set (BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE INTERNAL "")
|
||||
set (BENCHMARK_ENABLE_TESTING OFF CACHE INTERNAL "")
|
||||
if(googlebenchmark_POPULATED)
|
||||
return()
|
||||
endif()
|
||||
|
||||
FetchContent_Populate(googlebenchmark)
|
||||
add_subdirectory(
|
||||
${googlebenchmark_SOURCE_DIR}
|
||||
${googlebenchmark_BINARY_DIR}
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
33
cmake/ftxui_find_google_test.cmake
Normal file
33
cmake/ftxui_find_google_test.cmake
Normal file
@@ -0,0 +1,33 @@
|
||||
# Some developers would be happier with the gtest version provided from their
|
||||
# package manager. Use it if it is installed the package provide cmake support.
|
||||
# https://github.com/ArthurSonzogni/FTXUI/issues/551
|
||||
find_package(GTest QUIET)
|
||||
|
||||
if (GTest_FOUND)
|
||||
return()
|
||||
endif()
|
||||
|
||||
option(FETCHCONTENT_UPDATES_DISCONNECTED TRUE)
|
||||
option(FETCHCONTENT_QUIET FALSE)
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(googletest
|
||||
GIT_REPOSITORY "https://github.com/google/googletest"
|
||||
GIT_TAG 23ef29555ef4789f555f1ba8c51b4c52975f0907
|
||||
GIT_PROGRESS TRUE
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(googletest)
|
||||
if(googletest_POPULATED)
|
||||
return()
|
||||
endif()
|
||||
|
||||
FetchContent_Populate(googletest)
|
||||
set(BUILD_GMOCK OFF CACHE INTERNAL "")
|
||||
set(INSTALL_GTEST OFF CACHE INTERNAL "")
|
||||
set(gtest_force_shared_crt ON CACHE INTERNAL "")
|
||||
add_subdirectory(
|
||||
${googletest_SOURCE_DIR}
|
||||
${googletest_BINARY_DIR}
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
@@ -1,3 +1,7 @@
|
||||
if (NOT FTXUI_BUILD_TESTS_FUZZER)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_COMPILER clang)
|
||||
set(CMAKE_CXX_COMPILER clang++)
|
||||
|
||||
|
@@ -1,3 +1,7 @@
|
||||
if(NOT FTXUI_ENABLE_INSTALL)
|
||||
return()
|
||||
endif()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS screen dom component
|
||||
EXPORT ftxui-export
|
||||
|
@@ -1,3 +1,7 @@
|
||||
if(NOT FTXUI_ENABLE_INSTALL)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
set(CPACK_GENERATOR "DEB;External;RPM;STGZ;TBZ2;TGZ;TXZ;TZ;TZST;ZIP")
|
||||
elseif (UNIX AND APPLE)
|
||||
|
@@ -1,30 +1,53 @@
|
||||
find_program( CLANG_TIDY_EXE NAMES "clang-tidy" DOC "Path to clang-tidy executable" )
|
||||
find_program(CLANG_TIDY_EXE NAMES "clang-tidy" DOC "Path to clang-tidy executable" )
|
||||
if(NOT CLANG_TIDY_EXE)
|
||||
message(STATUS "clang-tidy not found.")
|
||||
else()
|
||||
message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}")
|
||||
endif()
|
||||
|
||||
|
||||
function(ftxui_set_options library)
|
||||
message(STATUS "ftxui_set_options " ${library})
|
||||
set_target_properties(${library} PROPERTIES VERSION ${PROJECT_VERSION})
|
||||
if (NOT ${library} MATCHES "ftxui-*")
|
||||
set_target_properties(${library} PROPERTIES OUTPUT_NAME "ftxui-${library}")
|
||||
endif()
|
||||
|
||||
if(CLANG_TIDY_EXE AND FTXUI_CLANG_TIDY)
|
||||
set_target_properties(${library}
|
||||
PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_EXE};-warnings-as-errors=*"
|
||||
)
|
||||
endif()
|
||||
|
||||
# By using "PUBLIC" as opposed to "SYSTEM INTERFACE", the compiler warning
|
||||
# are enforced on the headers. This is behind "FTXUI_CLANG_TIDY", so that it
|
||||
# applies only when developing FTXUI and on the CI. User's of the library
|
||||
# get only the SYSTEM INTERFACE instead.
|
||||
target_include_directories(${library}
|
||||
PUBLIC
|
||||
$<INSTALL_INTERFACE:include>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
)
|
||||
else()
|
||||
target_include_directories(${library} SYSTEM
|
||||
INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(${library} SYSTEM
|
||||
INTERFACE
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
|
||||
target_include_directories(${library}
|
||||
PRIVATE
|
||||
src
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
)
|
||||
|
||||
# C++17 is used. We require fold expression at least.
|
||||
target_compile_features(${library} PUBLIC cxx_std_17)
|
||||
set_target_properties(${library} PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_EXTENSIONS OFF
|
||||
)
|
||||
|
||||
# Force Microsoft Visual Studio to decode sources files in UTF-8. This applies
|
||||
# to the library and the library users.
|
||||
@@ -60,9 +83,7 @@ function(ftxui_set_options library)
|
||||
endfunction()
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
#string(APPEND CMAKE_CXX_FLAGS " -s ASSERTIONS=1")
|
||||
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")
|
||||
endif()
|
||||
|
||||
|
@@ -1,40 +1,28 @@
|
||||
enable_testing()
|
||||
|
||||
option(FETCHCONTENT_UPDATES_DISCONNECTED TRUE)
|
||||
option(FETCHCONTENT_QUIET FALSE)
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(googletest
|
||||
GIT_REPOSITORY "https://github.com/google/googletest"
|
||||
GIT_TAG 23ef29555ef4789f555f1ba8c51b4c52975f0907
|
||||
GIT_PROGRESS TRUE
|
||||
)
|
||||
FetchContent_GetProperties(googletest)
|
||||
if(NOT googletest_POPULATED)
|
||||
FetchContent_Populate(googletest)
|
||||
set(BUILD_GMOCK OFF CACHE INTERNAL "")
|
||||
set(INSTALL_GTEST OFF CACHE INTERNAL "")
|
||||
set(gtest_force_shared_crt ON CACHE INTERNAL "")
|
||||
add_subdirectory(
|
||||
${googletest_SOURCE_DIR}
|
||||
${googletest_BINARY_DIR}
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
if (NOT FTXUI_BUILD_TESTS OR
|
||||
NOT ${CMAKE_VERSION} VERSION_GREATER "3.11.4")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(tests
|
||||
enable_testing()
|
||||
|
||||
include(cmake/ftxui_find_google_test.cmake)
|
||||
|
||||
add_executable(ftxui-tests
|
||||
src/ftxui/component/animation_test.cpp
|
||||
src/ftxui/component/button_test.cpp
|
||||
src/ftxui/component/collapsible_test.cpp
|
||||
src/ftxui/component/component_test.cpp
|
||||
src/ftxui/component/component_test.cpp
|
||||
src/ftxui/component/container_test.cpp
|
||||
src/ftxui/component/hoverable_test.cpp
|
||||
src/ftxui/component/input_test.cpp
|
||||
src/ftxui/component/menu_test.cpp
|
||||
src/ftxui/component/modal_test.cpp
|
||||
src/ftxui/component/radiobox_test.cpp
|
||||
src/ftxui/component/receiver_test.cpp
|
||||
src/ftxui/component/resizable_split_test.cpp
|
||||
src/ftxui/component/screen_interactive_test.cpp
|
||||
src/ftxui/component/slider_test.cpp
|
||||
src/ftxui/component/terminal_input_parser_test.cpp
|
||||
src/ftxui/component/toggle_test.cpp
|
||||
src/ftxui/dom/blink_test.cpp
|
||||
@@ -60,24 +48,18 @@ add_executable(tests
|
||||
src/ftxui/screen/string_test.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(tests
|
||||
target_link_libraries(ftxui-tests
|
||||
PRIVATE component
|
||||
PRIVATE gtest
|
||||
PRIVATE gtest_main
|
||||
PRIVATE GTest::gtest
|
||||
PRIVATE GTest::gtest_main
|
||||
)
|
||||
target_include_directories(tests
|
||||
target_include_directories(ftxui-tests
|
||||
PRIVATE src
|
||||
)
|
||||
ftxui_set_options(tests)
|
||||
ftxui_set_options(ftxui-tests)
|
||||
target_compile_features(ftxui-tests PUBLIC cxx_std_20)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(tests
|
||||
gtest_discover_tests(ftxui-tests
|
||||
DISCOVERY_TIMEOUT 600
|
||||
)
|
||||
|
||||
|
||||
include(cmake/ftxui_benchmark.cmake)
|
||||
|
||||
if (FTXUI_BUILD_TESTS_FUZZER)
|
||||
include(cmake/ftxui_fuzzer.cmake)
|
||||
endif()
|
||||
|
@@ -2,6 +2,6 @@ find_program(iwyu_path NAMES include-what-you-use iwyu)
|
||||
if(iwyu_path)
|
||||
set_property(TARGET ${lib}
|
||||
PROPERTY ${iwyu_path} -Xiwyu
|
||||
--mapping_file ${CMAKE_CURRENT_SOURCE_DIR}/iwyu.impl
|
||||
--mapping_file ${CMAKE_CURRENT_SOURCE_DIR}/iwyu.imp
|
||||
)
|
||||
endif()
|
||||
|
12
codecov.yml
Normal file
12
codecov.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
codecov:
|
||||
require_ci_to_pass: no
|
||||
notify:
|
||||
after_n_builds: 4
|
||||
wait_for_ci: yes
|
||||
|
||||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: "50...100"
|
||||
|
||||
comment: false
|
@@ -1,22 +1,27 @@
|
||||
if(NOT FTXUI_BUILD_DOCS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(Doxygen)
|
||||
if (DOXYGEN_FOUND)
|
||||
# Generate example list for documentation
|
||||
set(EXAMPLE_LIST "${CMAKE_CURRENT_BINARY_DIR}/example_list.md")
|
||||
file(WRITE ${EXAMPLE_LIST} "# Examples")
|
||||
get_property(EXAMPLES GLOBAL PROPERTY FTXUI::EXAMPLES)
|
||||
foreach(EXAMPLE IN LISTS EXAMPLES)
|
||||
if (NOT DOXYGEN_FOUND)
|
||||
message("Doxygen need to be installed to generate the doxygen documentation")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Generate example list for documentation
|
||||
set(EXAMPLE_LIST "${CMAKE_CURRENT_BINARY_DIR}/example_list.md")
|
||||
file(WRITE ${EXAMPLE_LIST} "# Examples")
|
||||
get_property(EXAMPLES GLOBAL PROPERTY FTXUI::EXAMPLES)
|
||||
foreach(EXAMPLE IN LISTS EXAMPLES)
|
||||
file(APPEND ${EXAMPLE_LIST} "\n@example examples/${EXAMPLE}.cpp")
|
||||
endforeach(EXAMPLE IN LISTS EXAMPLES)
|
||||
endforeach(EXAMPLE IN LISTS EXAMPLES)
|
||||
|
||||
configure_file(Doxyfile.in Doxyfile @ONLY)
|
||||
configure_file(Doxyfile.in Doxyfile @ONLY)
|
||||
|
||||
# note the option ALL which allows to build the docs together with the application
|
||||
add_custom_target(doc
|
||||
# note the option ALL which allows to build the docs together with the application
|
||||
add_custom_target(doc
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Generating API documentation with Doxygen"
|
||||
VERBATIM
|
||||
)
|
||||
else (DOXYGEN_FOUND)
|
||||
message("Doxygen need to be installed to generate the doxygen documentation")
|
||||
endif (DOXYGEN_FOUND)
|
||||
|
@@ -178,11 +178,12 @@ div.headertitle {
|
||||
}
|
||||
|
||||
div.fragment {
|
||||
padding: 16px;
|
||||
background-color: #f3f3f3;
|
||||
border: 0 solid;
|
||||
transition: all 0.5s ease-in-out;
|
||||
border-radius:5px;
|
||||
border: 0 solid;
|
||||
border: none;
|
||||
padding:16px;
|
||||
transition: all 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
div.fragment:hover {
|
||||
@@ -192,8 +193,9 @@ div.fragment:hover {
|
||||
|
||||
div.line {
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
font-size: 90%;
|
||||
font-variant-numeric: tabular-nums lining-nums;
|
||||
font-kerning: none;
|
||||
-webkit-transition-duration: 0;
|
||||
-moz-transition-duration: 0;
|
||||
-ms-transition-duration: 0;
|
||||
@@ -206,15 +208,6 @@ div.line.glow {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
pre.fragment {
|
||||
border: 0 solid #C4CFE5;
|
||||
padding: 16px;
|
||||
background-color: #f3f3f3;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
}
|
||||
|
||||
/* @group Code Colorization */
|
||||
span.keyword {
|
||||
color: #808000
|
||||
|
354
doc/mainpage.md
354
doc/mainpage.md
@@ -4,8 +4,8 @@
|
||||
|
||||
Welcome to the FTXUI documentation!
|
||||
|
||||
This is a brief tutorial. You are also encouraged to learn, by reading the
|
||||
[examples](./examples.html)
|
||||
This is a brief tutorial. You are also encouraged to self-learn by reading the
|
||||
[examples](./examples.html).
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@@ -45,17 +45,19 @@ int main(void) {
|
||||
|
||||
**output**
|
||||
```bash
|
||||
┌────┐┌─────────────────────────────────────────────────────────────────┐┌─────┐
|
||||
┌────┐┌────────────────────────────────────┐┌─────┐
|
||||
│left││middle ││right│
|
||||
└────┘└─────────────────────────────────────────────────────────────────┘└─────┘
|
||||
└────┘└────────────────────────────────────┘└─────┘
|
||||
```
|
||||
|
||||
# Build {#build}
|
||||
|
||||
## Using CMake {#build-cmake}
|
||||
|
||||
This is an example configuration for your **CMakeLists.txt**
|
||||
|
||||
CMakeLists.txt
|
||||
~~~cmake
|
||||
```cmake
|
||||
cmake_minimum_required (VERSION 3.11)
|
||||
|
||||
# --- Fetch FTXUI --------------------------------------------------------------
|
||||
@@ -89,38 +91,43 @@ target_link_libraries(ftxui-starter
|
||||
PRIVATE ftxui::component # Not needed for this example.
|
||||
)
|
||||
|
||||
~~~
|
||||
```
|
||||
|
||||
Build
|
||||
~~~sh
|
||||
Subsequently, you build the project in the standard fashion as follows:
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make
|
||||
./main
|
||||
~~~
|
||||
```
|
||||
|
||||
# List of modules. {#modules}
|
||||
|
||||
The project is made from into 3 modules:
|
||||
The project is comprised of 3 modules:
|
||||
|
||||
1. **ftxui/screen** defines a `ftxui::Screen`, this is a grid of `ftxui::Pixel`.
|
||||
1. **ftxui/screen** defines a `ftxui::Screen`, a grid of `ftxui::Pixel`.
|
||||
|
||||
2. **ftxui/dom** is the main module. It defines a hierarchical set of
|
||||
`ftxui::Element`. An element draws something on the `ftxui::Screen`. It is
|
||||
responsive to the size of its container.
|
||||
|
||||
3. **ftxui/component** The part is only needed if you need to respond to the
|
||||
User input. It defines a set of `ftxui::Component`. The use can navigates
|
||||
3. **ftxui/component** The module is required if your program needs to respond
|
||||
to user input. It defines a set of `ftxui::Component`. These components can
|
||||
be utilized to navigate the program by using the arrow keys and interacting
|
||||
with widgets such as the checkbox. You can also make you own components.
|
||||
The use can navigates
|
||||
using the arrow keys and interact with widgets like checkbox/inputbox/... You
|
||||
can make you own components.
|
||||
|
||||
# screen {#module-screen}
|
||||
|
||||
It defines a `ftxui::Screen`. This is a grid of `ftxui::Pixel`. A Pixel
|
||||
represent a Unicode character and its associated style (bold, colors, etc...).
|
||||
The screen can be printed as a string using `ftxui::Screen::ToString()`.
|
||||
This is the visual element of the program. It defines a `ftxui::Screen`, which
|
||||
is a grid of `ftxui::Pixel`. A Pixel represents a Unicode character and its
|
||||
associated style (bold, colors, etc.). The screen can be printed as a string
|
||||
using `ftxui::Screen::ToString()`. The following example highlights this
|
||||
process:
|
||||
|
||||
~~~cpp
|
||||
```cpp
|
||||
#include <ftxui/screen/screen.hpp>
|
||||
#include <iostream>
|
||||
|
||||
@@ -136,12 +143,14 @@ The screen can be printed as a string using `ftxui::Screen::ToString()`.
|
||||
std::cout << screen.ToString();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
~~~
|
||||
```
|
||||
|
||||
# dom {#module-dom}
|
||||
|
||||
This module defines a hierarchical set of `ftxui::Element`. An element manages
|
||||
layout and can be responsive to the terminal dimensions.
|
||||
the layout and can be responsive to the terminal dimension changes. Note the
|
||||
following example where this module is used to create a simple layout with a
|
||||
number of operators:
|
||||
|
||||
**Example:**
|
||||
```cpp
|
||||
@@ -165,7 +174,8 @@ document |= border
|
||||
|
||||
**List of elements**
|
||||
|
||||
They are all defined inside:
|
||||
The list of all elements are included and can be accessed by including the
|
||||
corresponding header file:
|
||||
```cpp
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
```
|
||||
@@ -175,41 +185,44 @@ They are all defined inside:
|
||||
## text ## {#dom-text}
|
||||
|
||||
The most simple widget. It displays a text.
|
||||
~~~cpp
|
||||
```cpp
|
||||
text("I am a piece of text");
|
||||
~~~
|
||||
~~~bash
|
||||
```
|
||||
```bash
|
||||
I am a piece of text.
|
||||
~~~
|
||||
```
|
||||
|
||||
## vtext {#dom-vtext}
|
||||
|
||||
Same as `ftxui::text`, but vertical.
|
||||
~~~cpp
|
||||
Identical to `ftxui::text`, but displayed vertically.
|
||||
|
||||
Code:
|
||||
```cpp
|
||||
vtext("HELLO");
|
||||
~~~
|
||||
~~~bash
|
||||
```
|
||||
|
||||
Terminal output:
|
||||
```bash
|
||||
H
|
||||
E
|
||||
L
|
||||
L
|
||||
O
|
||||
~~~
|
||||
```
|
||||
|
||||
## paragraph {#dom-paragraph}
|
||||
|
||||
Similar to `ftxui::text`, but the individual word are wrapped along multiple
|
||||
lines, depending on the width of its container.
|
||||
|
||||
Sample Code:
|
||||
```cpp
|
||||
paragraph("A very long text")
|
||||
```
|
||||
|
||||
Similar to `ftxui::text`, but this support line wrapping and alignments. The
|
||||
words are split by spaces
|
||||
|
||||
[Paragraph example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2paragraph_8cpp-example.html)
|
||||
|
||||

|
||||
|
||||
See:
|
||||
For a more detailed example refer to [detailed example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2paragraph_8cpp-example.html). Paragraph also includes a number of other variants as shown below:
|
||||
```cpp
|
||||
Element paragraph(std::string text);
|
||||
Element paragraphAlignLeft(std::string text);
|
||||
@@ -221,25 +234,28 @@ Element paragraphAlignJustify(std::string text);
|
||||
|
||||
## border {#dom-border}
|
||||
|
||||
Add a border around an element
|
||||
~~~cpp
|
||||
border(text("The element"))
|
||||
~~~
|
||||
Adds a border around an element.
|
||||
|
||||
~~~bash
|
||||
Code:
|
||||
```cpp
|
||||
border(text("The element"))
|
||||
```
|
||||
|
||||
Terminal output:
|
||||
```bash
|
||||
┌───────────┐
|
||||
│The element│
|
||||
└───────────┘
|
||||
~~~
|
||||
```
|
||||
|
||||
Same, with the pipe operator:
|
||||
You can achieve the same behavior by using the pipe operator.
|
||||
|
||||
Code:
|
||||
```cpp
|
||||
text("The element") | border
|
||||
```
|
||||
|
||||
Border come with different styles.
|
||||
See:
|
||||
Border also comes in a variety of styles as shown below:
|
||||
```cpp
|
||||
Element border(Element);
|
||||
Element borderLight(Element);
|
||||
@@ -254,24 +270,27 @@ Decorator borderWith(Pixel);
|
||||
|
||||
## window ## {#dom-window}
|
||||
|
||||
A `ftxui::window` is a `ftxui::border`, but with some text on top of the border.
|
||||
Add a border around an element
|
||||
~~~cpp
|
||||
A `ftxui::window` is a `ftxui::border`, but with an additional header. To add a
|
||||
window around an element, wrap it and specify a string as the header.
|
||||
Code:
|
||||
```cpp
|
||||
window("The window", text("The element"))
|
||||
~~~
|
||||
```
|
||||
|
||||
~~~bash
|
||||
Terminal output:
|
||||
```bash
|
||||
┌The window─┐
|
||||
│The element│
|
||||
└───────────┘
|
||||
~~~
|
||||
```
|
||||
|
||||
## separator {#dom-separator}
|
||||
|
||||
Display a vertical or horizontal line to visually split the content of a
|
||||
Displays a vertical/horizontal line to visually split the content of a
|
||||
container in two.
|
||||
|
||||
~~~cpp
|
||||
Code:
|
||||
```cpp
|
||||
border(
|
||||
hbox({
|
||||
text("Left"),
|
||||
@@ -279,17 +298,17 @@ border(
|
||||
text("Right")
|
||||
})
|
||||
)
|
||||
~~~
|
||||
```
|
||||
|
||||
~~~bash
|
||||
Terminal output:
|
||||
```bash
|
||||
┌────┬─────┐
|
||||
│left│right│
|
||||
└────┴─────┘
|
||||
~~~
|
||||
```
|
||||
|
||||
|
||||
Separators come with different styles:
|
||||
See:
|
||||
Separators come in a variety of flavors as shown below:
|
||||
```cpp
|
||||
Element separator(void);
|
||||
Element separatorLight();
|
||||
@@ -311,18 +330,21 @@ Element separatorVSelector(float up,
|
||||
|
||||
## gauge {#dom-gauge}
|
||||
|
||||
A gauge. It can be used to represent a progress bar.
|
||||
~~~cpp
|
||||
border(gauge(0.5))
|
||||
~~~
|
||||
It constitutes a gauge. It can be used to represent a progress bar.
|
||||
|
||||
~~~bash
|
||||
Code:
|
||||
```cpp
|
||||
border(gauge(0.5))
|
||||
```
|
||||
|
||||
Teminal output:
|
||||
```bash
|
||||
┌────────────────────────────────────────────────────────────────────────────┐
|
||||
│██████████████████████████████████████ │
|
||||
└────────────────────────────────────────────────────────────────────────────┘
|
||||
~~~
|
||||
```
|
||||
|
||||
A gauge can be displayed into several directions. See:
|
||||
Gauges can be displayed in many orientations as shown below:
|
||||
```cpp
|
||||
Element gauge(float ratio);
|
||||
Element gaugeLeft(float ratio);
|
||||
@@ -344,21 +366,21 @@ Element graph(GraphFunction);
|
||||
```
|
||||
|
||||
## Colors {#dom-colors}
|
||||
A terminal console can usually display colored text and colored background.
|
||||
|
||||
~~~cpp
|
||||
Most terminal consoles can display colored text and colored backgrounds. FTXUI
|
||||
supports every color palette:
|
||||
```cpp
|
||||
Decorator color(Color);
|
||||
Decorator bgcolor(Color);
|
||||
~~~
|
||||
```
|
||||
|
||||
FTXUI support every color palette:
|
||||
|
||||
Color [gallery](https://arthursonzogni.github.io/FTXUI/examples_2dom_2color_gallery_8cpp-example.html):
|
||||

|
||||
|
||||
### Palette16 #{#dom-colors-palette-16}
|
||||
|
||||
On most terminal the following colors are supported:
|
||||
On most terminals the following colors are supported:
|
||||
- Default
|
||||
|
||||
- Black
|
||||
@@ -385,7 +407,7 @@ On most terminal the following colors are supported:
|
||||
- Yellow
|
||||
- YellowLight
|
||||
|
||||
Example:
|
||||
Example use of the above colors using the pipe operator:
|
||||
```cpp
|
||||
text("Blue foreground") | color(Color::Blue);
|
||||
text("Blue background") | bgcolor(Color::Blue);
|
||||
@@ -405,7 +427,11 @@ text("HotPink") | color(Color::HotPink);
|
||||
|
||||
### TrueColor #{#dom-colors-true-color}
|
||||
|
||||
On terminal supporting trueColor, you can directly chose the 24bit RGB color:
|
||||
On terminal supporting trueColor, you can directly use the 24bit RGB color
|
||||
space:
|
||||
|
||||
Use the constructors below to specify the **RGB** or **HSV** values for your
|
||||
color:
|
||||
|
||||
There are two constructors:
|
||||
```cpp
|
||||
@@ -419,97 +445,107 @@ ftxui::Color::HSV(uint8_t hue, uint8_t saturation, uint8_t value);
|
||||
@endhtmlonly
|
||||
|
||||
## Style {#dom-style}
|
||||
A terminal console can usually display colored text and colored background.
|
||||
The text can also have different effects: bold, dim, underlined, inverted,
|
||||
blink.
|
||||
In addition to colored text and colored backgrounds. Many terminals support text
|
||||
effects such as: `bold`, `dim`, `underlined`, `inverted`, `blink`.
|
||||
|
||||
~~~cpp
|
||||
```cpp
|
||||
Element bold(Element);
|
||||
Element dim(Element);
|
||||
Element inverted(Element);
|
||||
Element underlined(Element);
|
||||
Element underlinedDouble(Element);
|
||||
Element strikethrough(Element);
|
||||
Element blink(Element);
|
||||
Decorator color(Color);
|
||||
Decorator bgcolor(Color);
|
||||
~~~
|
||||
```
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2style_gallery_8cpp-example.html)
|
||||
|
||||

|
||||
|
||||
Example:
|
||||
~~~cpp
|
||||
To use these effects, simply wrap your elements with your desired effect:
|
||||
```cpp
|
||||
underlined(bold(text("This text is bold and underlined")))
|
||||
~~~
|
||||
```
|
||||
|
||||
Tips: The pipe operator can be used to chain Decorator:
|
||||
~~~cpp
|
||||
text("This text is bold")) | bold | underlined
|
||||
~~~
|
||||
Alternatively, use the pipe operator to chain it on your element:
|
||||
```cpp
|
||||
text("This text is bold") | bold | underlined
|
||||
```
|
||||
|
||||
## Layout {#dom-layout}
|
||||
|
||||
Element can be arranged together:
|
||||
- horizontally with `ftxui::hbox`
|
||||
- vertically with `ftxui::vbox`
|
||||
- inside a grid with `ftxui::gridbox`
|
||||
- wrap along one direction using the `ftxui::flexbox`.
|
||||
Enables elements to be arranged in the following ways:
|
||||
- **Horizontally** with `ftxui::hbox`
|
||||
- **Vertically** with `ftxui::vbox`
|
||||
- **Inside a grid** with `ftxui::gridbox`
|
||||
- **Wrapped along one direction** using the `ftxui::flexbox`.
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2vbox_hbox_8cpp-example.html) using `ftxui::hbox`, `ftxui::vbox` and `ftxui::filler`.
|
||||
|
||||

|
||||
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2gridbox_8cpp-example.htmlp) using `ftxui::gridbox`:
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2gridbox_8cpp-example.htmlp)
|
||||
using `ftxui::gridbox`:
|
||||
|
||||

|
||||
|
||||
[Example](https://github.com/ArthurSonzogni/FTXUI/blob/master/examples/dom/hflow.cpp) using flexbox:
|
||||
[Example](https://github.com/ArthurSonzogni/FTXUI/blob/master/examples/dom/hflow.cpp)
|
||||
using flexbox:
|
||||
|
||||

|
||||
|
||||
[See](https://arthursonzogni.github.io/FTXUI/examples_2dom_2hflow_8cpp-example.html) also this [demo](https://arthursonzogni.com/FTXUI/examples/?file=component/flexbox).
|
||||
Checkout this
|
||||
[example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2hflow_8cpp-example.html)
|
||||
and the associated
|
||||
[demo](https://arthursonzogni.github.io/FTXUI/examples/?file=component/flexbox).
|
||||
|
||||
Element can become flexible using the the `ftxui::flex` decorator.
|
||||
Element can also become flexible using the the `ftxui::flex` decorator.
|
||||
|
||||
**Examples**
|
||||
~~~cpp
|
||||
Code:
|
||||
```cpp
|
||||
hbox({
|
||||
text("left") | border ,
|
||||
text("middle") | border | flex,
|
||||
text("right") | border,
|
||||
});
|
||||
~~~
|
||||
~~~bash
|
||||
┌────┐┌─────────────────────────────────────────────────────────────────┐┌─────┐
|
||||
```
|
||||
Terminal output:
|
||||
```bash
|
||||
┌────┐┌─────────────────────────────────────────────────────┐┌─────┐
|
||||
│left││middle ││right│
|
||||
└────┘└─────────────────────────────────────────────────────────────────┘└─────┘
|
||||
~~~
|
||||
└────┘└─────────────────────────────────────────────────────┘└─────┘
|
||||
```
|
||||
|
||||
~~~cpp
|
||||
Code:
|
||||
```cpp
|
||||
hbox({
|
||||
text("left") | border ,
|
||||
text("middle") | border | flex,
|
||||
text("right") | border | flex,
|
||||
});
|
||||
~~~
|
||||
~~~bash
|
||||
┌────┐┌───────────────────────────────────┐┌───────────────────────────────────┐
|
||||
```
|
||||
|
||||
Terminal output:
|
||||
```bash
|
||||
┌────┐┌───────────────────────────────┐┌───────────────────────────────┐
|
||||
│left││middle ││right │
|
||||
└────┘└───────────────────────────────────┘└───────────────────────────────────┘
|
||||
~~~
|
||||
└────┘└───────────────────────────────┘└───────────────────────────────┘
|
||||
```
|
||||
|
||||
## Table {#dom-table}
|
||||
|
||||
A class to easily style a table of data.
|
||||
Enables easy formatting of data into a neat table like visual form.
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2table_8cpp-example.html):
|
||||
[Code example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2table_8cpp-example.html):
|
||||
|
||||

|
||||
|
||||
## Canvas {#dom-canvas}
|
||||
|
||||
See [<ftxui/dom/canvas.hpp>](./canvas_8hpp_source.html)
|
||||
See the API [<ftxui/dom/canvas.hpp>](./canvas_8hpp_source.html)
|
||||
|
||||
```cpp
|
||||
auto c = Canvas(100, 100);
|
||||
@@ -517,41 +553,42 @@ See [<ftxui/dom/canvas.hpp>](./canvas_8hpp_source.html)
|
||||
auto element = canvas(c);
|
||||
```
|
||||
|
||||
Drawing can be made on a `ftxui::Canvas`, using braille, block, or simple
|
||||
Drawing can be performed on a `ftxui::Canvas`, using braille, block, or simple
|
||||
characters:
|
||||
|
||||
Simple [example](https://github.com/ArthurSonzogni/FTXUI/blob/master/examples/dom/canvas.cpp):
|
||||
|
||||

|
||||
|
||||
Complex [examples](https://github.com/ArthurSonzogni/FTXUI/blob/master/examples/component/canvas_animated.cpp):
|
||||
Complex [example](https://github.com/ArthurSonzogni/FTXUI/blob/master/examples/component/canvas_animated.cpp):
|
||||
|
||||

|
||||
|
||||
# component {#module-component}
|
||||
|
||||
The `ftxui::component`module defines the logic to produce interactive component
|
||||
responding to user's events (keyboard, mouse, etc...)
|
||||
The `ftxui::component` module defines the logic that produces interactive
|
||||
components that respond to user events (keyboard, mouse, etc.).
|
||||
|
||||
A `ftxui::ScreenInteractive` defines a main loop to render a component.
|
||||
A `ftxui::ScreenInteractive` defines a main loop that renders a component.
|
||||
|
||||
A `ftxui::Component` is a shared pointer to a `ftxui::ComponentBase`. The later
|
||||
defines
|
||||
A `ftxui::Component` is a shared pointer to a `ftxui::ComponentBase`. The latter defines:
|
||||
- `ftxui::ComponentBase::Render()`: How to render the interface.
|
||||
- `ftxui::ComponentBase::OnEvent()`: How to react to events.
|
||||
- `ftxui::ComponentBase::Add()`: Give a parent/child relation ship in between
|
||||
two component. This defines a tree a components, which help properly define
|
||||
how keyboard navigation works.
|
||||
- `ftxui::ComponentBase::Add()`: Construct a parent/child relationship
|
||||
between two components. The tree of component is used to define how to
|
||||
navigate using the keyboard.
|
||||
|
||||
`ftxui::Element` are used to render a single frame.
|
||||
|
||||
`ftxui::Element` are used to render a single frame. On the other side
|
||||
`ftxui::Component` are used to render dynamic user interface, producing multiple
|
||||
frame, and updating its state on events.
|
||||
|
||||
[Gallery](https://arthursonzogni.github.io/FTXUI/examples_2component_2gallery_8cpp-example.html) of multiple components. ([demo](https://arthursonzogni.com/FTXUI/examples/?file=component/gallery))
|
||||
[Gallery](https://arthursonzogni.github.io/FTXUI/examples_2component_2gallery_8cpp-example.html) of multiple components. ([demo](https://arthursonzogni.github.io/FTXUI/examples/?file=component/gallery))
|
||||
|
||||

|
||||
|
||||
Predefined components are available in ["ftxui/dom/component.hpp"](./component_8hpp.html)
|
||||
All predefined components are available in
|
||||
["ftxui/dom/component.hpp"](./component_8hpp.html)
|
||||
|
||||
\include ftxui/component/component.hpp
|
||||
|
||||
@@ -569,6 +606,8 @@ Produced by: `ftxui::Input()` from "ftxui/component/component.hpp"
|
||||
|
||||
## Menu {#component-menu}
|
||||
|
||||
Defines a menu object. It contains a list of entries, one of them is selected.
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2menu_8cpp-example.html):
|
||||
|
||||

|
||||
@@ -582,6 +621,8 @@ Produced by: `ftxui::Menu()` from "ftxui/component/component.hpp"
|
||||
|
||||
## Toggle {#component-toggle}
|
||||
|
||||
A special kind of menu. The entries are displayed horizontally.
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2toggle_8cpp-example.html):
|
||||
|
||||

|
||||
@@ -594,6 +635,9 @@ Produced by: `ftxui::Toggle()` from "ftxui/component/component.hpp"
|
||||
|
||||
## CheckBox {#component-checkbox}
|
||||
|
||||
This component defines a checkbox. It is a single entry that can be turned
|
||||
on/off.
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2checkbox_8cpp-example.html):
|
||||
|
||||

|
||||
@@ -606,6 +650,8 @@ Produced by: `ftxui::Checkbox()` from "ftxui/component/component.hpp"
|
||||
|
||||
## RadioBox {#component-radiobox}
|
||||
|
||||
A radiobutton component. This is a list of entries, where one can be turned on.
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2radiobox_8cpp-example.html):
|
||||
|
||||

|
||||
@@ -618,6 +664,9 @@ Produced by: `ftxui::Radiobox()` from "ftxui/component/component.hpp"
|
||||
|
||||
## Dropdown {#component-dropdown}
|
||||
|
||||
A drop down menu is a component that when checked display a list of element for
|
||||
the user to select one.
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2dropdown_8cpp-example.html):
|
||||
|
||||

|
||||
@@ -626,6 +675,9 @@ Produced by: `ftxui::Dropdown()` from "ftxui/component/component.hpp"
|
||||
|
||||
## Slider {#component-slider}
|
||||
|
||||
Represents a slider object that consists of a range with binned intermediate
|
||||
intervals. It can be created by `ftxui::Slider()`.
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2slider_8cpp-example.html):
|
||||
|
||||

|
||||
@@ -634,7 +686,7 @@ Produced by: `ftxui::Slider()` from "ftxui/component/component.hpp"
|
||||
|
||||
## Renderer {#component-renderer}
|
||||
|
||||
Produced by: `ftxui::Renderer()` from \ref 'ftxui/component/component.hpp'. This
|
||||
Produced by: `ftxui::Renderer()` from \ref ftxui/component/component.hpp. This
|
||||
component decorate another one by using a different function to render an
|
||||
interface.
|
||||
|
||||
@@ -647,11 +699,11 @@ auto renderer = Renderer(inner, [&] {
|
||||
});
|
||||
```
|
||||
|
||||
`ftxui::Renderer` also support the component decorator pattern:
|
||||
`ftxui::Renderer` also supports the component decorator pattern:
|
||||
```cpp
|
||||
auto component = [...]
|
||||
component = component
|
||||
| Renderer([] (Element e) { return e | border))
|
||||
| Renderer([](Element e) { return e | border))
|
||||
| Renderer(bold)
|
||||
```
|
||||
|
||||
@@ -663,9 +715,8 @@ component = component | border | bold;
|
||||
|
||||
## CatchEvent {#component-catchevent}
|
||||
|
||||
Produced by: `ftxui::CatchEvent()` from \ref 'ftxui/component/component.hpp'.
|
||||
This component decorate another one and catch the events before the underlying
|
||||
component.
|
||||
Produced by: `ftxui::CatchEvent()` from \ref ftxui/component/component.hpp.
|
||||
This component decorate others, catching events before the underlying component.
|
||||
|
||||
Examples:
|
||||
```cpp
|
||||
@@ -694,9 +745,9 @@ component = component
|
||||
|
||||
## Collapsible {#component-collapsible}
|
||||
|
||||
Useful for section whose visibility can be toggle on/off by the user.
|
||||
This is basically, a combinaison of a `ftxui::Checkbox` and a `ftxui::Maybe`
|
||||
components.
|
||||
Useful for visual elements whose visibility can be toggle on/off by the user.
|
||||
Essentially, this the combination of the `ftxui::Checkbox()` and
|
||||
`ftxui::Maybe()` components.
|
||||
|
||||
```cpp
|
||||
auto collabsible = Collapsible("Show more", inner_element);
|
||||
@@ -704,9 +755,9 @@ auto collabsible = Collapsible("Show more", inner_element);
|
||||
|
||||
## Maybe {#component-maybe}
|
||||
|
||||
Produced by: `ftxui::Maybe()` from \ref `ftxui/component/component.hpp`.
|
||||
This component decorate another one, by showing/hiding it depending on a boolean
|
||||
or a predicate.
|
||||
Produced by: `ftxui::Maybe()` from \ref ftxui/component/component.hpp.
|
||||
This component can be utilized to show/hide any other component via a boolean or
|
||||
a predicate.
|
||||
|
||||
Example with a boolean:
|
||||
```cpp
|
||||
@@ -721,9 +772,8 @@ auto component = Renderer([]{ return "Hello World!"; });
|
||||
auto maybe_component = Maybe(component, [&] { return time > 10; })
|
||||
```
|
||||
|
||||
`ftxui::Maybe` can be used as a decorator.
|
||||
|
||||
```
|
||||
As usual, `ftxui::Maybe` can also be used as a decorator:
|
||||
```cpp
|
||||
component = component
|
||||
| Maybe(&a_boolean)
|
||||
| Maybe([&] { return time > 10; })
|
||||
@@ -759,22 +809,20 @@ one of them. This is useful for implementing a tab bar.
|
||||

|
||||
|
||||
|
||||
## ResizableSplit::{Left, Right, Top, Bottom} {#component-resizable-split}
|
||||
## ResizableSplit {#component-resizable-split}
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2resizable_split_8cpp-example.html):
|
||||
|
||||

|
||||
|
||||
Produced by:
|
||||
It defines a horizontal or vertical separation between two children components.
|
||||
The position of the split is variable and controllable using the mouse.
|
||||
There are four possible splits:
|
||||
- `ftxui::ResizableSplitLeft()`
|
||||
- `ftxui::ResizableSplitRight()`
|
||||
- `ftxui::ResizableSplitTop()`
|
||||
- `ftxui::ResizableSplitBottom()`
|
||||
from "ftxui/component/component.hpp"
|
||||
|
||||
It defines an horizontal or vertical separation in between two children
|
||||
component. The position of the split is variable and controllable using the
|
||||
mouse.
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2resizable_split_8cpp-example.html):
|
||||
|
||||

|
||||
|
||||
@htmlonly
|
||||
<script id="asciicast-tprMH2EdkUoMb7D2YxgMGgpzx" src="https://asciinema.org/a/tprMH2EdkUoMb7D2YxgMGgpzx.js" async></script>
|
||||
@@ -782,16 +830,14 @@ mouse.
|
||||
|
||||
## Force a frame redraw. {#component-force-redraw}
|
||||
|
||||
Whenever a new group of events have been processed: keyboard, mouse, window
|
||||
resize, etc..., the `ftxui::ScreenInteractive::Loop()` is responsible for
|
||||
drawing a new frame.
|
||||
|
||||
You might want to react to arbitrary events that are unknown to FTXUI. This can
|
||||
be achieve by posting events via `ftxui::ScreenInteractive::PostEvent`, via a
|
||||
thread. You can post the event`ftxui::Event::Custom`.
|
||||
Typically, `ftxui::ScreenInteractive::Loop()` is responsible for drawing a new
|
||||
frame whenever a new group of events (e.g keyboard, mouse, window resize, etc.)
|
||||
has been processed. However, you might want to react to arbitrary events that
|
||||
are unknown to FTXUI. To accomplish this, you must post events using
|
||||
`ftxui::ScreenInteractive::PostEvent` (**this is thread safe**) via a thread.
|
||||
You will have to post the event `ftxui::Event::Custom`.
|
||||
|
||||
Example:
|
||||
```cpp
|
||||
screen->PostEvent(Event::Custom);
|
||||
```
|
||||
|
||||
`ftxui::ScreenInteractive::PostEvent` is thread safe.
|
||||
|
@@ -1,9 +1,16 @@
|
||||
if(NOT FTXUI_BUILD_EXAMPLES)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(EXAMPLES_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
function(example name)
|
||||
add_executable(ftxui_example_${name} ${name}.cpp)
|
||||
target_link_libraries(ftxui_example_${name} PUBLIC ${DIRECTORY_LIB})
|
||||
file(RELATIVE_PATH dir ${EXAMPLES_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set_property(GLOBAL APPEND PROPERTY FTXUI::EXAMPLES ${dir}/${name})
|
||||
set_target_properties(ftxui_example_${name} PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
)
|
||||
endfunction(example)
|
||||
|
||||
add_subdirectory(component)
|
||||
@@ -19,6 +26,7 @@ if (EMSCRIPTEN)
|
||||
get_property(EXAMPLES GLOBAL PROPERTY FTXUI::EXAMPLES)
|
||||
foreach(file
|
||||
"index.html"
|
||||
"sw.js"
|
||||
"run_webassembly.py")
|
||||
configure_file(${file} ${file})
|
||||
endforeach(file)
|
||||
|
@@ -9,9 +9,11 @@ example(checkbox)
|
||||
example(checkbox_in_frame)
|
||||
example(collapsible)
|
||||
example(composition)
|
||||
example(custom_loop)
|
||||
example(dropdown)
|
||||
example(flexbox_gallery)
|
||||
example(focus)
|
||||
example(focus_cursor)
|
||||
example(gallery)
|
||||
example(homescreen)
|
||||
example(input)
|
||||
@@ -25,6 +27,7 @@ example(menu_multiple)
|
||||
example(menu_style)
|
||||
example(menu_underline_animated_gallery)
|
||||
example(modal_dialog)
|
||||
example(modal_dialog_custom)
|
||||
example(nested_screen)
|
||||
example(print_key_press)
|
||||
example(radiobox)
|
||||
@@ -32,6 +35,7 @@ example(radiobox_in_frame)
|
||||
example(renderer)
|
||||
example(resizable_split)
|
||||
example(slider)
|
||||
example(slider_direction)
|
||||
example(slider_rgb)
|
||||
example(tab_horizontal)
|
||||
example(tab_vertical)
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for operator+, to_string
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
@@ -6,7 +5,7 @@
|
||||
#include "ftxui/component/component_base.hpp" // for Component
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for separator, Element, text, border
|
||||
#include "ftxui/dom/elements.hpp" // for Element, separator, text, border
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::Green, Color::Red
|
||||
|
||||
using namespace ftxui;
|
||||
|
55
examples/component/custom_loop.cpp
Normal file
55
examples/component/custom_loop.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include <stdlib.h> // for EXIT_SUCCESS
|
||||
#include <chrono> // for milliseconds
|
||||
#include <ftxui/component/event.hpp> // for Event
|
||||
#include <ftxui/component/mouse.hpp> // for ftxui
|
||||
#include <ftxui/dom/elements.hpp> // for text, separator, Element, operator|, vbox, border
|
||||
#include <memory> // for allocator, shared_ptr
|
||||
#include <string> // for operator+, to_string
|
||||
#include <thread> // for sleep_for
|
||||
|
||||
#include "ftxui/component/component.hpp" // for CatchEvent, Renderer, operator|=
|
||||
#include "ftxui/component/loop.hpp" // for Loop
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
using namespace ftxui;
|
||||
auto screen = ScreenInteractive::FitComponent();
|
||||
|
||||
// Create a component counting the number of frames drawn and event handled.
|
||||
int custom_loop_count = 0;
|
||||
int frame_count = 0;
|
||||
int event_count = 0;
|
||||
auto component = Renderer([&] {
|
||||
frame_count++;
|
||||
return vbox({
|
||||
text("This demonstrates using a custom ftxui::Loop. It "),
|
||||
text("runs at 100 iterations per seconds. The FTXUI events "),
|
||||
text("are all processed once per iteration and a new frame "),
|
||||
text("is rendered as needed"),
|
||||
separator(),
|
||||
text("ftxui event count: " + std::to_string(event_count)),
|
||||
text("ftxui frame count: " + std::to_string(frame_count)),
|
||||
text("Custom loop count: " + std::to_string(custom_loop_count)),
|
||||
}) |
|
||||
border;
|
||||
});
|
||||
|
||||
component |= CatchEvent([&](Event) -> bool {
|
||||
event_count++;
|
||||
return false;
|
||||
});
|
||||
|
||||
Loop loop(&screen, component);
|
||||
|
||||
while (!loop.HasQuitted()) {
|
||||
custom_loop_count++;
|
||||
loop.RunOnce();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// 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.
|
38
examples/component/focus_cursor.cpp
Normal file
38
examples/component/focus_cursor.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <ftxui/component/captured_mouse.hpp> // for ftxui
|
||||
#include <string> // for allocator, operator+, char_traits, string
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Renderer, Vertical
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive, Component
|
||||
#include "ftxui/dom/elements.hpp" // for text, Decorator, focus, focusCursorBar, focusCursorBarBlinking, focusCursorBlock, focusCursorBlockBlinking, focusCursorUnderline, focusCursorUnderlineBlinking, hbox, Element
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
Component Instance(std::string label, Decorator focusCursor) {
|
||||
return Renderer([=](bool focused) {
|
||||
if (focused) {
|
||||
return hbox({
|
||||
text("> " + label + " "),
|
||||
focusCursor(text(" ")),
|
||||
});
|
||||
}
|
||||
return text(" " + label + " ");
|
||||
});
|
||||
};
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
auto screen = ScreenInteractive::Fullscreen();
|
||||
screen.Loop(Container::Vertical({
|
||||
Instance("focus", focus),
|
||||
Instance("focusCursorBlock", focusCursorBlock),
|
||||
Instance("focusCursorBlockBlinking", focusCursorBlockBlinking),
|
||||
Instance("focusCursorBar", focusCursorBar),
|
||||
Instance("focusCursorBarBlinking", focusCursorBarBlinking),
|
||||
Instance("focusCursorUnderline", focusCursorUnderline),
|
||||
Instance("focusCursorUnderlineBlinking", focusCursorUnderlineBlinking),
|
||||
}));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
@@ -1,17 +1,18 @@
|
||||
#include <stddef.h> // for size_t
|
||||
#include <array> // for array
|
||||
#include <atomic> // for atomic
|
||||
#include <chrono> // for operator""s, chrono_literals
|
||||
#include <cmath> // for sin
|
||||
#include <functional> // for ref, reference_wrapper, function
|
||||
#include <memory> // for allocator, shared_ptr, __shared_ptr_access
|
||||
#include <string> // for string, basic_string, operator+, to_string, char_traits
|
||||
#include <string> // for string, basic_string, char_traits, operator+, to_string
|
||||
#include <thread> // for sleep_for, thread
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "../dom/color_info_sorted_2d.ipp" // for ColorInfoSorted2D
|
||||
#include "ftxui/component/component.hpp" // for Checkbox, Renderer, Horizontal, Vertical, Input, Menu, Radiobox, ResizableSplitLeft, Tab
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||
#include "ftxui/component/component_options.hpp" // for MenuOption, InputOption
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Custom
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
|
||||
@@ -499,13 +500,18 @@ int main(int argc, const char* argv[]) {
|
||||
});
|
||||
});
|
||||
|
||||
bool refresh_ui_continue = true;
|
||||
std::atomic<bool> refresh_ui_continue = true;
|
||||
std::thread refresh_ui([&] {
|
||||
while (refresh_ui_continue) {
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(0.05s);
|
||||
shift++;
|
||||
screen.PostEvent(Event::Custom);
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -1,12 +1,11 @@
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for string, basic_string, allocator
|
||||
#include <string> // for string, allocator, basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for operator|, Maybe, Checkbox, Radiobox, Renderer, Vertical
|
||||
#include "ftxui/component/component_base.hpp" // for Component
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for border, color, operator|, text, Element
|
||||
#include "ftxui/dom/elements.hpp" // for Element, border, color, operator|, text
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Red
|
||||
|
||||
using namespace ftxui;
|
||||
|
@@ -1,14 +1,14 @@
|
||||
#include <array> // for array
|
||||
#include <chrono> // for milliseconds
|
||||
#include <functional> // for function
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
|
||||
#include <string> // for string, char_traits, basic_string, operator+
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
#include <string> // for string, char_traits, operator+, basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for ElasticOut, Linear
|
||||
#include "ftxui/component/component.hpp" // for Menu, Horizontal, Renderer, Vertical
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption, AnimatedColorOption, AnimatedColorsOption, UnderlineOption
|
||||
#include "ftxui/component/component_options.hpp" // for MenuOption, EntryState, MenuEntryOption, AnimatedColorOption, AnimatedColorsOption, UnderlineOption
|
||||
#include "ftxui/component/mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for separator, operator|, Element, text, bgcolor, hbox, bold, color, filler, border, vbox, borderDouble, dim, flex, hcenter
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#include <chrono> // for operator""ms, literals
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
|
||||
#include <string> // for string, basic_string, operator+, to_string
|
||||
#include <memory> // for allocator, shared_ptr, __shared_ptr_access
|
||||
#include <string> // for string, operator+, to_string, basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for BackOut, Duration
|
||||
|
@@ -1,94 +1,83 @@
|
||||
#include <memory> // for allocator, shared_ptr, __shared_ptr_access
|
||||
#include <string> // for string, basic_string, char_traits, operator+
|
||||
#include <vector> // for vector
|
||||
#include <ftxui/component/component_options.hpp> // for ButtonOption
|
||||
#include <ftxui/component/mouse.hpp> // for ftxui
|
||||
#include <functional> // for function
|
||||
#include <memory> // for allocator, shared_ptr
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Button, Renderer, Horizontal, Tab
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, filler, text, hbox, separator, center, vbox, bold, border, clear_under, dbox, size, GREATER_THAN, HEIGHT
|
||||
#include "ftxui/component/component.hpp" // for Button, operator|=, Renderer, Vertical, Modal
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive, Component
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, separator, text, size, Element, vbox, border, GREATER_THAN, WIDTH, center, HEIGHT
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
auto button_style = ButtonOption::Animated();
|
||||
|
||||
// Definition of the main component. The details are not important.
|
||||
Component MainComponent(std::function<void()> show_modal,
|
||||
std::function<void()> exit) {
|
||||
auto component = Container::Vertical({
|
||||
Button("Show modal", show_modal, button_style),
|
||||
Button("Quit", exit, button_style),
|
||||
});
|
||||
// Polish how the two buttons are rendered:
|
||||
component |= Renderer([&](Element inner) {
|
||||
return vbox({
|
||||
text("Main component"),
|
||||
separator(),
|
||||
inner,
|
||||
}) //
|
||||
| size(WIDTH, GREATER_THAN, 15) //
|
||||
| size(HEIGHT, GREATER_THAN, 15) //
|
||||
| border //
|
||||
| center; //
|
||||
});
|
||||
return component;
|
||||
}
|
||||
|
||||
// Definition of the modal component. The details are not important.
|
||||
Component ModalComponent(std::function<void()> do_nothing,
|
||||
std::function<void()> hide_modal) {
|
||||
auto component = Container::Vertical({
|
||||
Button("Do nothing", do_nothing, button_style),
|
||||
Button("Quit modal", hide_modal, button_style),
|
||||
});
|
||||
// Polish how the two buttons are rendered:
|
||||
component |= Renderer([&](Element inner) {
|
||||
return vbox({
|
||||
text("Modal component "),
|
||||
separator(),
|
||||
inner,
|
||||
}) //
|
||||
| size(WIDTH, GREATER_THAN, 30) //
|
||||
| border; //
|
||||
});
|
||||
return component;
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
using namespace ftxui;
|
||||
auto screen = ScreenInteractive::TerminalOutput();
|
||||
|
||||
// There are two layers. One at depth = 0 and the modal window at depth = 1;
|
||||
int depth = 0;
|
||||
// State of the application:
|
||||
bool modal_shown = false;
|
||||
|
||||
// The current rating of FTXUI.
|
||||
std::string rating = "3/5 stars";
|
||||
// Some actions modifying the state:
|
||||
auto show_modal = [&] { modal_shown = true; };
|
||||
auto hide_modal = [&] { modal_shown = false; };
|
||||
auto exit = screen.ExitLoopClosure();
|
||||
auto do_nothing = [&] {};
|
||||
|
||||
// At depth=0, two buttons. One for rating FTXUI and one for quitting.
|
||||
auto button_rate_ftxui = Button("Rate FTXUI", [&] { depth = 1; });
|
||||
auto button_quit = Button("Quit", screen.ExitLoopClosure());
|
||||
// Instanciate the main and modal components:
|
||||
auto main_component = MainComponent(show_modal, exit);
|
||||
auto modal_component = ModalComponent(do_nothing, hide_modal);
|
||||
|
||||
auto depth_0_container = Container::Horizontal({
|
||||
button_rate_ftxui,
|
||||
button_quit,
|
||||
});
|
||||
auto depth_0_renderer = Renderer(depth_0_container, [&] {
|
||||
return vbox({
|
||||
text("Modal dialog example"),
|
||||
separator(),
|
||||
text("☆☆☆ FTXUI:" + rating + " ☆☆☆") | bold,
|
||||
filler(),
|
||||
hbox({
|
||||
button_rate_ftxui->Render(),
|
||||
filler(),
|
||||
button_quit->Render(),
|
||||
}),
|
||||
}) |
|
||||
border | size(HEIGHT, GREATER_THAN, 18) | center;
|
||||
});
|
||||
// Use the `Modal` function to use together the main component and its modal
|
||||
// window. The |modal_shown| boolean controls whether the modal is shown or
|
||||
// not.
|
||||
main_component |= Modal(modal_component, &modal_shown);
|
||||
|
||||
// At depth=1, The "modal" window.
|
||||
std::vector<std::string> rating_labels = {
|
||||
"1/5 stars", "2/5 stars", "3/5 stars", "4/5 stars", "5/5 stars",
|
||||
};
|
||||
auto on_rating = [&](std::string new_rating) {
|
||||
rating = new_rating;
|
||||
depth = 0;
|
||||
};
|
||||
auto depth_1_container = Container::Horizontal({
|
||||
Button(&rating_labels[0], [&] { on_rating(rating_labels[0]); }),
|
||||
Button(&rating_labels[1], [&] { on_rating(rating_labels[1]); }),
|
||||
Button(&rating_labels[2], [&] { on_rating(rating_labels[2]); }),
|
||||
Button(&rating_labels[3], [&] { on_rating(rating_labels[3]); }),
|
||||
Button(&rating_labels[4], [&] { on_rating(rating_labels[4]); }),
|
||||
});
|
||||
|
||||
auto depth_1_renderer = Renderer(depth_1_container, [&] {
|
||||
return vbox({
|
||||
text("Do you like FTXUI?"),
|
||||
separator(),
|
||||
hbox(depth_1_container->Render()),
|
||||
}) |
|
||||
border;
|
||||
});
|
||||
|
||||
auto main_container = Container::Tab(
|
||||
{
|
||||
depth_0_renderer,
|
||||
depth_1_renderer,
|
||||
},
|
||||
&depth);
|
||||
|
||||
auto main_renderer = Renderer(main_container, [&] {
|
||||
Element document = depth_0_renderer->Render();
|
||||
|
||||
if (depth == 1) {
|
||||
document = dbox({
|
||||
document,
|
||||
depth_1_renderer->Render() | clear_under | center,
|
||||
});
|
||||
}
|
||||
return document;
|
||||
});
|
||||
|
||||
screen.Loop(main_renderer);
|
||||
screen.Loop(main_component);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Copyright 2022 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
||||
|
94
examples/component/modal_dialog_custom.cpp
Normal file
94
examples/component/modal_dialog_custom.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include <memory> // for allocator, shared_ptr, __shared_ptr_access
|
||||
#include <string> // for string, basic_string, char_traits, operator+
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Button, Renderer, Horizontal, Tab
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, filler, text, hbox, separator, center, vbox, bold, border, clear_under, dbox, size, GREATER_THAN, HEIGHT
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
using namespace ftxui;
|
||||
auto screen = ScreenInteractive::TerminalOutput();
|
||||
|
||||
// There are two layers. One at depth = 0 and the modal window at depth = 1;
|
||||
int depth = 0;
|
||||
|
||||
// The current rating of FTXUI.
|
||||
std::string rating = "3/5 stars";
|
||||
|
||||
// At depth=0, two buttons. One for rating FTXUI and one for quitting.
|
||||
auto button_rate_ftxui = Button("Rate FTXUI", [&] { depth = 1; });
|
||||
auto button_quit = Button("Quit", screen.ExitLoopClosure());
|
||||
|
||||
auto depth_0_container = Container::Horizontal({
|
||||
button_rate_ftxui,
|
||||
button_quit,
|
||||
});
|
||||
auto depth_0_renderer = Renderer(depth_0_container, [&] {
|
||||
return vbox({
|
||||
text("Modal dialog example"),
|
||||
separator(),
|
||||
text("☆☆☆ FTXUI:" + rating + " ☆☆☆") | bold,
|
||||
filler(),
|
||||
hbox({
|
||||
button_rate_ftxui->Render(),
|
||||
filler(),
|
||||
button_quit->Render(),
|
||||
}),
|
||||
}) |
|
||||
border | size(HEIGHT, GREATER_THAN, 18) | center;
|
||||
});
|
||||
|
||||
// At depth=1, The "modal" window.
|
||||
std::vector<std::string> rating_labels = {
|
||||
"1/5 stars", "2/5 stars", "3/5 stars", "4/5 stars", "5/5 stars",
|
||||
};
|
||||
auto on_rating = [&](std::string new_rating) {
|
||||
rating = new_rating;
|
||||
depth = 0;
|
||||
};
|
||||
auto depth_1_container = Container::Horizontal({
|
||||
Button(&rating_labels[0], [&] { on_rating(rating_labels[0]); }),
|
||||
Button(&rating_labels[1], [&] { on_rating(rating_labels[1]); }),
|
||||
Button(&rating_labels[2], [&] { on_rating(rating_labels[2]); }),
|
||||
Button(&rating_labels[3], [&] { on_rating(rating_labels[3]); }),
|
||||
Button(&rating_labels[4], [&] { on_rating(rating_labels[4]); }),
|
||||
});
|
||||
|
||||
auto depth_1_renderer = Renderer(depth_1_container, [&] {
|
||||
return vbox({
|
||||
text("Do you like FTXUI?"),
|
||||
separator(),
|
||||
hbox(depth_1_container->Render()),
|
||||
}) |
|
||||
border;
|
||||
});
|
||||
|
||||
auto main_container = Container::Tab(
|
||||
{
|
||||
depth_0_renderer,
|
||||
depth_1_renderer,
|
||||
},
|
||||
&depth);
|
||||
|
||||
auto main_renderer = Renderer(main_container, [&] {
|
||||
Element document = depth_0_renderer->Render();
|
||||
|
||||
if (depth == 1) {
|
||||
document = dbox({
|
||||
document,
|
||||
depth_1_renderer->Render() | clear_under | center,
|
||||
});
|
||||
}
|
||||
return document;
|
||||
});
|
||||
|
||||
screen.Loop(main_renderer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
@@ -25,7 +25,7 @@ std::string Stringify(Event event) {
|
||||
|
||||
out = "(" + out + " ) -> ";
|
||||
if (event.is_character()) {
|
||||
out += "character(" + event.character() + ")";
|
||||
out += "Event::Character(\"" + event.character() + "\")";
|
||||
} else if (event.is_mouse()) {
|
||||
out += "mouse";
|
||||
switch (event.mouse().button) {
|
||||
@@ -66,6 +66,68 @@ std::string Stringify(Event event) {
|
||||
out += "(" + //
|
||||
std::to_string(event.mouse().x) + "," +
|
||||
std::to_string(event.mouse().y) + ")";
|
||||
} else if (event == Event::ArrowLeft) {
|
||||
out += "Event::ArrowLeft";
|
||||
} else if (event == Event::ArrowRight) {
|
||||
out += "Event::ArrowRight";
|
||||
} else if (event == Event::ArrowUp) {
|
||||
out += "Event::ArrowUp";
|
||||
} else if (event == Event::ArrowDown) {
|
||||
out += "Event::ArrowDown";
|
||||
} else if (event == Event::ArrowLeftCtrl) {
|
||||
out += "Event::ArrowLeftCtrl";
|
||||
} else if (event == Event ::ArrowRightCtrl) {
|
||||
out += "Event::ArrowRightCtrl";
|
||||
} else if (event == Event::ArrowUpCtrl) {
|
||||
out += "Event::ArrowUpCtrl";
|
||||
} else if (event == Event::ArrowDownCtrl) {
|
||||
out += "Event::ArrowDownCtrl";
|
||||
} else if (event == Event::Backspace) {
|
||||
out += "Event::Backspace";
|
||||
} else if (event == Event::Delete) {
|
||||
out += "Event::Delete";
|
||||
} else if (event == Event::Escape) {
|
||||
out += "Event::Escape";
|
||||
} else if (event == Event::Return) {
|
||||
out += "Event::Return";
|
||||
} else if (event == Event::Tab) {
|
||||
out += "Event::Tab";
|
||||
} else if (event == Event::TabReverse) {
|
||||
out += "Event::TabReverse";
|
||||
} else if (event == Event::F1) {
|
||||
out += "Event::F1";
|
||||
} else if (event == Event::F2) {
|
||||
out += "Event::F2";
|
||||
} else if (event == Event::F3) {
|
||||
out += "Event::F3";
|
||||
} else if (event == Event::F4) {
|
||||
out += "Event::F4";
|
||||
} else if (event == Event::F5) {
|
||||
out += "Event::F5";
|
||||
} else if (event == Event::F6) {
|
||||
out += "Event::F6";
|
||||
} else if (event == Event::F7) {
|
||||
out += "Event::F7";
|
||||
} else if (event == Event::F8) {
|
||||
out += "Event::F8";
|
||||
} else if (event == Event::F9) {
|
||||
out += "Event::F9";
|
||||
} else if (event == Event::F10) {
|
||||
out += "Event::F10";
|
||||
} else if (event == Event::F11) {
|
||||
out += "Event::F11";
|
||||
} else if (event == Event::F12) {
|
||||
out += "Event::F12";
|
||||
} else if (event == Event::Home) {
|
||||
out += "Event::Home";
|
||||
} else if (event == Event::End) {
|
||||
out += "Event::End";
|
||||
} else if (event == Event::PageUp) {
|
||||
out += "Event::PageUp";
|
||||
} else if (event == Event::PageDown) {
|
||||
out += "Event::PageDown";
|
||||
} else if (event == Event::Custom) {
|
||||
out += "Custom";
|
||||
} else {
|
||||
out += "(special)";
|
||||
}
|
||||
|
49
examples/component/slider_direction.cpp
Normal file
49
examples/component/slider_direction.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <array> // for array
|
||||
#include <cmath> // for sin
|
||||
#include <ftxui/component/component_base.hpp> // for ComponentBase
|
||||
#include <ftxui/component/component_options.hpp> // for SliderOption
|
||||
#include <ftxui/dom/elements.hpp> // for size, GREATER_THAN, GaugeDirection, GaugeDirection::Up, HEIGHT
|
||||
#include <ftxui/util/ref.hpp> // for ConstRef, Ref
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Horizontal, Slider, operator|=
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
auto screen = ScreenInteractive::TerminalOutput();
|
||||
std::array<int, 30> values;
|
||||
for (int i = 0; i < values.size(); ++i) {
|
||||
values[i] = 50 + 20 * std::sin(i * 0.3);
|
||||
}
|
||||
|
||||
auto layout_horizontal = Container::Horizontal({});
|
||||
for (int i = 0; i < values.size(); ++i) {
|
||||
// In C++17:
|
||||
SliderOption<int> option;
|
||||
option.value = &values[i];
|
||||
option.max = 100;
|
||||
option.increment = 5;
|
||||
option.direction = GaugeDirection::Up;
|
||||
layout_horizontal->Add(Slider<int>(option));
|
||||
|
||||
/* In C++20:
|
||||
layout_horizontal->Add(Slider<int>({
|
||||
.value = &values[i],
|
||||
.max = 100,
|
||||
.increment = 5,
|
||||
.direction = GaugeDirection::Up,
|
||||
}));
|
||||
*/
|
||||
}
|
||||
|
||||
layout_horizontal |= size(HEIGHT, GREATER_THAN, 20);
|
||||
|
||||
screen.Loop(layout_horizontal);
|
||||
}
|
||||
|
||||
// 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.
|
@@ -26,7 +26,9 @@ example(style_color)
|
||||
example(style_dim)
|
||||
example(style_gallery)
|
||||
example(style_inverted)
|
||||
example(style_strikethrough)
|
||||
example(style_underlined)
|
||||
example(style_underlined_double)
|
||||
example(table)
|
||||
example(vbox_hbox)
|
||||
example(vflow)
|
||||
|
@@ -2,7 +2,7 @@
|
||||
#include <ftxui/dom/elements.hpp> // for text, gauge, operator|, flex, hbox, Element
|
||||
#include <ftxui/screen/screen.hpp> // for Screen
|
||||
#include <iostream> // for cout, endl, ostream
|
||||
#include <string> // for allocator, operator+, char_traits, operator<<, string, to_string, basic_string
|
||||
#include <string> // for allocator, char_traits, operator+, operator<<, string, to_string, basic_string
|
||||
#include <thread> // for sleep_for
|
||||
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#include <chrono> // for operator""s, chrono_literals
|
||||
#include <ftxui/dom/elements.hpp> // for text, gauge, operator|, flex, hbox, Element
|
||||
#include <ftxui/dom/elements.hpp> // for filler, operator|, separator, text, border, Element, vbox, vtext, hbox, center, gaugeDown, gaugeLeft, gaugeRight, gaugeUp
|
||||
#include <ftxui/screen/screen.hpp> // for Screen
|
||||
#include <iostream> // for cout, endl, ostream
|
||||
#include <string> // for allocator, operator+, char_traits, operator<<, string, to_string, basic_string
|
||||
#include <string> // for allocator, operator+, operator<<, string, to_string
|
||||
#include <thread> // for sleep_for
|
||||
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
|
@@ -1,8 +1,7 @@
|
||||
#include <stdio.h> // for getchar
|
||||
#include <ftxui/dom/elements.hpp> // for operator|, size, Element, text, hcenter, Decorator, Fit, WIDTH, hflow, window, EQUAL, GREATER_THAN, HEIGHT, bold, border, dim, LESS_THAN
|
||||
#include <ftxui/screen/screen.hpp> // for Full, Screen
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for allocator, operator+, to_string, char_traits, string
|
||||
#include <string> // for allocator, char_traits, operator+, to_string, string
|
||||
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/color.hpp" // for ftxui
|
||||
|
@@ -1,7 +1,6 @@
|
||||
#include <chrono> // for operator""s, chrono_literals
|
||||
#include <ftxui/screen/screen.hpp> // for Screen
|
||||
#include <iostream> // for cout, ostream
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for allocator, operator<<, string
|
||||
#include <thread> // for sleep_for
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
#include <ftxui/dom/elements.hpp> // for operator|, text, Element, hbox, bold, color, filler, separator, vbox, window, gauge, Fit, size, dim, EQUAL, WIDTH
|
||||
#include <ftxui/screen/screen.hpp> // for Full, Screen
|
||||
#include <iostream> // for cout, endl, ostream
|
||||
#include <list> // for list, operator!=, _List_iterator, _List_iterator<>::_Self
|
||||
#include <list> // for list, operator==, _List_iterator, _List_iterator<>::_Self
|
||||
#include <memory> // for allocator, shared_ptr, allocator_traits<>::value_type
|
||||
#include <string> // for string, operator<<, to_string
|
||||
#include <thread> // for sleep_for
|
||||
|
@@ -17,7 +17,7 @@ int main(int argc, const char* argv[]) {
|
||||
std::string reset_position;
|
||||
for (int index = 0; index < 200; ++index) {
|
||||
std::vector<Element> entries;
|
||||
for (int i = 0; i < 22; ++i) {
|
||||
for (int i = 0; i < 23; ++i) {
|
||||
if (i != 0)
|
||||
entries.push_back(separator());
|
||||
entries.push_back( //
|
||||
|
@@ -14,10 +14,12 @@ int main(int argc, const char* argv[]) {
|
||||
text("bold") | bold , text(" ") ,
|
||||
text("dim") | dim , text(" ") ,
|
||||
text("inverted") | inverted , text(" ") ,
|
||||
text("underlined")| underlined , text(" ") ,
|
||||
text("underlined") | underlined , text(" ") ,
|
||||
text("underlinedDouble") | underlinedDouble , text(" ") ,
|
||||
text("blink") | blink , text(" ") ,
|
||||
text("strikethrough") | strikethrough , text(" ") ,
|
||||
text("color") | color(Color::Blue) , text(" ") ,
|
||||
text("bgcolor") | bgcolor(Color::Blue),
|
||||
text("bgcolor") | bgcolor(Color::Blue) ,
|
||||
});
|
||||
// clang-format on
|
||||
auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
|
||||
|
25
examples/dom/style_strikethrough.cpp
Normal file
25
examples/dom/style_strikethrough.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <ftxui/dom/elements.hpp> // for text, operator|, strikethrough, Fit, hbox, Element
|
||||
#include <ftxui/screen/screen.hpp> // for Full, Screen
|
||||
#include <memory> // for allocator
|
||||
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/color.hpp" // for ftxui
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
using namespace ftxui;
|
||||
auto document = //
|
||||
hbox({
|
||||
text("This text is "),
|
||||
text("strikethrough") | strikethrough,
|
||||
text(". Do you like it?"),
|
||||
});
|
||||
auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
|
||||
Render(screen, document);
|
||||
screen.Print();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
25
examples/dom/style_underlined_double.cpp
Normal file
25
examples/dom/style_underlined_double.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <ftxui/dom/elements.hpp> // for text, operator|, underlinedDouble, Fit, hbox, Element
|
||||
#include <ftxui/screen/screen.hpp> // for Full, Screen
|
||||
#include <memory> // for allocator
|
||||
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/color.hpp" // for ftxui
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
using namespace ftxui;
|
||||
auto document = //
|
||||
hbox({
|
||||
text("This text is "),
|
||||
text("underlinedDouble") | underlinedDouble,
|
||||
text(". Do you like it?"),
|
||||
});
|
||||
auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
|
||||
Render(screen, document);
|
||||
screen.Print();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
@@ -1,8 +1,7 @@
|
||||
#include <stdio.h> // for getchar
|
||||
#include <ftxui/dom/elements.hpp> // for operator|, Element, size, text, hcenter, Fit, vflow, window, EQUAL, bold, border, dim, HEIGHT, WIDTH
|
||||
#include <ftxui/screen/screen.hpp> // for Full, Screen
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for allocator, operator+, to_string, char_traits, string
|
||||
#include <string> // for allocator, char_traits, operator+, to_string, string
|
||||
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/color.hpp" // for ftxui
|
||||
|
@@ -4,7 +4,20 @@
|
||||
<title>FTXUI examples WebAssembly</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm@4.18.0/lib/xterm.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-webgl@0.11.4/lib/xterm-addon-webgl.min.js"></script>
|
||||
<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>
|
||||
<!--Add COOP/COEP via a ServiceWorker to use SharedArrayBuffer-->
|
||||
<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>
|
||||
<body>
|
||||
<script id="example_script"></script>
|
||||
@@ -67,21 +80,43 @@
|
||||
}
|
||||
}
|
||||
const term = new Terminal();
|
||||
term.open(document.querySelector('#terminal'));
|
||||
term.resize(140,43);
|
||||
term.loadAddon(new (WebglAddon.WebglAddon)());
|
||||
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: () => {},
|
||||
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('/')
|
||||
@@ -125,9 +160,11 @@
|
||||
}
|
||||
|
||||
#terminal {
|
||||
padding:10px;
|
||||
width:100%;
|
||||
height: 500px;
|
||||
height: calc(clamp(200px, 100vh - 300px, 900px));
|
||||
overflow: hidden;
|
||||
border:none;
|
||||
background-color:black;
|
||||
padding:auto;
|
||||
}
|
||||
|
||||
|
25
examples/sw.js
Normal file
25
examples/sw.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// sw.js
|
||||
self.addEventListener("install", () => self.skipWaiting());
|
||||
self.addEventListener("activate", e => e.waitUntil(self.clients.claim()));
|
||||
self.addEventListener("fetch", e => {
|
||||
if (e.request.mode != 'navigate' &&
|
||||
!e.request.url.includes(".worker.js")) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.respondWith((async () => {
|
||||
const response = await fetch(e.request);
|
||||
|
||||
const newHeaders = new Headers(response.headers);
|
||||
newHeaders.set("Cross-Origin-Embedder-Policy", "require-corp");
|
||||
newHeaders.set("Cross-Origin-Opener-Policy", "same-origin");
|
||||
|
||||
const moddedResponse = new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: newHeaders,
|
||||
});
|
||||
|
||||
return moddedResponse;
|
||||
})());
|
||||
});
|
@@ -8,9 +8,9 @@
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/component_base.hpp" // for Component, Components
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption, CheckboxOption, InputOption (ptr only), MenuEntryOption (ptr only), MenuOption, RadioboxOption (ptr only)
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption, CheckboxOption, MenuOption
|
||||
#include "ftxui/dom/elements.hpp" // for Element
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringRef, ConstStringListRef, StringRef
|
||||
#include "ftxui/util/ref.hpp" // for ConstRef, Ref, ConstStringRef, ConstStringListRef, StringRef
|
||||
|
||||
namespace ftxui {
|
||||
struct ButtonOption;
|
||||
@@ -67,8 +67,26 @@ Component Radiobox(ConstStringListRef entries,
|
||||
Ref<RadioboxOption> option = {});
|
||||
Component Toggle(ConstStringListRef entries, int* selected);
|
||||
|
||||
template <class T> // T = {int, float, long}
|
||||
Component Slider(ConstStringRef label, T* value, T min, T max, T increment);
|
||||
// General slider constructor:
|
||||
template <typename T>
|
||||
Component Slider(SliderOption<T> options = {});
|
||||
|
||||
// Shorthand without the `SliderOption` constructor:
|
||||
Component Slider(ConstStringRef label,
|
||||
Ref<int> value,
|
||||
ConstRef<int> min = 0,
|
||||
ConstRef<int> max = 100,
|
||||
ConstRef<int> increment = 5);
|
||||
Component Slider(ConstStringRef label,
|
||||
Ref<float> value,
|
||||
ConstRef<float> min = 0.f,
|
||||
ConstRef<float> max = 100.f,
|
||||
ConstRef<float> increment = 5.f);
|
||||
Component Slider(ConstStringRef label,
|
||||
Ref<long> value,
|
||||
ConstRef<long> min = 0l,
|
||||
ConstRef<long> max = 100l,
|
||||
ConstRef<long> increment = 5l);
|
||||
|
||||
Component ResizableSplitLeft(Component main, Component back, int* main_size);
|
||||
Component ResizableSplitRight(Component main, Component back, int* main_size);
|
||||
@@ -88,9 +106,24 @@ Component Maybe(Component, std::function<bool()>);
|
||||
ComponentDecorator Maybe(const bool* show);
|
||||
ComponentDecorator Maybe(std::function<bool()>);
|
||||
|
||||
Component Modal(Component main, Component modal, const bool* show_modal);
|
||||
ComponentDecorator Modal(Component modal, const bool* show_modal);
|
||||
|
||||
Component Collapsible(ConstStringRef label,
|
||||
Component child,
|
||||
Ref<bool> show = false);
|
||||
|
||||
Component Hoverable(Component component, bool* hover);
|
||||
Component Hoverable(Component component,
|
||||
std::function<void()> on_enter,
|
||||
std::function<void()> on_leave);
|
||||
Component Hoverable(Component component, //
|
||||
std::function<void(bool)> on_change);
|
||||
ComponentDecorator Hoverable(bool* hover);
|
||||
ComponentDecorator Hoverable(std::function<void()> on_enter,
|
||||
std::function<void()> on_leave);
|
||||
ComponentDecorator Hoverable(std::function<void(bool)> on_change);
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_COMPONENT_HPP */
|
||||
|
@@ -3,8 +3,8 @@
|
||||
|
||||
#include <chrono> // for milliseconds
|
||||
#include <ftxui/component/animation.hpp> // for Duration, QuadraticInOut, Function
|
||||
#include <ftxui/dom/elements.hpp> // for Element
|
||||
#include <ftxui/util/ref.hpp> // for Ref
|
||||
#include <ftxui/dom/elements.hpp> // for Element, GaugeDirection, GaugeDirection::Right
|
||||
#include <ftxui/util/ref.hpp> // for Ref, ConstRef
|
||||
#include <functional> // for function
|
||||
#include <optional> // for optional
|
||||
#include <string> // for string
|
||||
@@ -94,7 +94,7 @@ struct MenuOption {
|
||||
std::function<Element()> elements_postfix;
|
||||
|
||||
// Observers:
|
||||
std::function<void()> on_change; ///> Called when the seelcted entry changes.
|
||||
std::function<void()> on_change; ///> Called when the selected entry changes.
|
||||
std::function<void()> on_enter; ///> Called when the user presses enter.
|
||||
Ref<int> focused_entry = 0;
|
||||
};
|
||||
@@ -164,6 +164,19 @@ struct RadioboxOption {
|
||||
Ref<int> focused_entry = 0;
|
||||
};
|
||||
|
||||
// @brief Option for the `Slider` component.
|
||||
// @ingroup component
|
||||
template <typename T>
|
||||
struct SliderOption {
|
||||
Ref<T> value;
|
||||
ConstRef<T> min = T(0);
|
||||
ConstRef<T> max = T(100);
|
||||
ConstRef<T> increment = (max() - min()) / 20;
|
||||
GaugeDirection direction = GaugeDirection::Right;
|
||||
Color color_active = Color::White;
|
||||
Color color_inactive = Color::GrayDark;
|
||||
};
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP */
|
||||
|
@@ -38,6 +38,11 @@ struct Event {
|
||||
static const Event ArrowUp;
|
||||
static const Event ArrowDown;
|
||||
|
||||
static const Event ArrowLeftCtrl;
|
||||
static const Event ArrowRightCtrl;
|
||||
static const Event ArrowUpCtrl;
|
||||
static const Event ArrowDownCtrl;
|
||||
|
||||
// --- Other ---
|
||||
static const Event Backspace;
|
||||
static const Event Delete;
|
||||
@@ -61,9 +66,7 @@ struct Event {
|
||||
std::string character() const { return input_; }
|
||||
|
||||
bool is_mouse() const { return type_ == Type::Mouse; }
|
||||
struct Mouse& mouse() {
|
||||
return mouse_;
|
||||
}
|
||||
struct Mouse& mouse() { return mouse_; }
|
||||
|
||||
bool is_cursor_reporting() const { return type_ == Type::CursorReporting; }
|
||||
int cursor_x() const { return cursor_.x; }
|
||||
|
39
include/ftxui/component/loop.hpp
Normal file
39
include/ftxui/component/loop.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef FTXUI_COMPONENT_LOOP_HPP
|
||||
#define FTXUI_COMPONENT_LOOP_HPP
|
||||
|
||||
#include <memory> // for shared_ptr
|
||||
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
|
||||
namespace ftxui {
|
||||
class ComponentBase;
|
||||
|
||||
using Component = std::shared_ptr<ComponentBase>;
|
||||
class ScreenInteractive;
|
||||
|
||||
class Loop {
|
||||
public:
|
||||
Loop(ScreenInteractive* screen, Component component);
|
||||
~Loop();
|
||||
|
||||
bool HasQuitted();
|
||||
void RunOnce();
|
||||
void RunOnceBlocking();
|
||||
void Run();
|
||||
|
||||
private:
|
||||
// This class is non copyable.
|
||||
Loop(const ScreenInteractive&) = delete;
|
||||
Loop& operator=(const Loop&) = delete;
|
||||
|
||||
ScreenInteractive* screen_;
|
||||
Component component_;
|
||||
};
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
#endif // FTXUI_COMPONENT_LOOP_HPP
|
||||
|
||||
// Copyright 2022 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
@@ -86,11 +86,25 @@ class ReceiverImpl {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReceiveNonBlocking(T* t) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
if (queue_.empty())
|
||||
return false;
|
||||
*t = queue_.front();
|
||||
queue_.pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasPending() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
return !queue_.empty();
|
||||
}
|
||||
|
||||
bool HasQuitted() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
return queue_.empty() && !senders_;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class SenderImpl<T>;
|
||||
|
||||
|
@@ -12,11 +12,12 @@
|
||||
#include "ftxui/component/animation.hpp" // for TimePoint
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/event.hpp" // for Event
|
||||
#include "ftxui/component/task.hpp" // for Closure, Task
|
||||
#include "ftxui/component/task.hpp" // for Task, Closure
|
||||
#include "ftxui/screen/screen.hpp" // for Screen
|
||||
|
||||
namespace ftxui {
|
||||
class ComponentBase;
|
||||
class Loop;
|
||||
struct Event;
|
||||
|
||||
using Component = std::shared_ptr<ComponentBase>;
|
||||
@@ -33,9 +34,12 @@ class ScreenInteractive : public Screen {
|
||||
// Return the currently active screen, nullptr if none.
|
||||
static ScreenInteractive* Active();
|
||||
|
||||
// Start/Stop the main loop.
|
||||
void Loop(Component);
|
||||
void Exit();
|
||||
Closure ExitLoopClosure();
|
||||
|
||||
// Post tasks to be executed by the loop.
|
||||
void Post(Task task);
|
||||
void PostEvent(Event event);
|
||||
void RequestAnimationFrame();
|
||||
@@ -48,13 +52,23 @@ class ScreenInteractive : public Screen {
|
||||
Closure WithRestoredIO(Closure);
|
||||
|
||||
private:
|
||||
void ExitNow();
|
||||
|
||||
void Install();
|
||||
void Uninstall();
|
||||
|
||||
void Main(Component component);
|
||||
void PreMain();
|
||||
void PostMain();
|
||||
|
||||
bool HasQuitted();
|
||||
void RunOnce(Component component);
|
||||
void RunOnceBlocking(Component component);
|
||||
|
||||
void HandleTask(Component component, Task& task);
|
||||
void Draw(Component component);
|
||||
void SigStop();
|
||||
void ResetCursorPosition();
|
||||
|
||||
void Signal(int signal);
|
||||
|
||||
ScreenInteractive* suspended_screen_ = nullptr;
|
||||
enum class Dimension {
|
||||
@@ -80,7 +94,7 @@ class ScreenInteractive : public Screen {
|
||||
std::thread event_listener_;
|
||||
std::thread animation_listener_;
|
||||
bool animation_requested_ = false;
|
||||
animation::TimePoint previous_animation_time;
|
||||
animation::TimePoint previous_animation_time_;
|
||||
|
||||
int cursor_x_ = 1;
|
||||
int cursor_y_ = 1;
|
||||
@@ -88,10 +102,14 @@ class ScreenInteractive : public Screen {
|
||||
bool mouse_captured = false;
|
||||
bool previous_frame_resized_ = false;
|
||||
|
||||
bool frame_valid_ = false;
|
||||
|
||||
friend class Loop;
|
||||
|
||||
public:
|
||||
class Private {
|
||||
public:
|
||||
static void SigStop(ScreenInteractive& s) { return s.SigStop(); }
|
||||
static void Signal(ScreenInteractive& s, int signal) { s.Signal(signal); }
|
||||
};
|
||||
friend Private;
|
||||
};
|
||||
|
@@ -83,7 +83,9 @@ Element bold(Element);
|
||||
Element dim(Element);
|
||||
Element inverted(Element);
|
||||
Element underlined(Element);
|
||||
Element underlinedDouble(Element);
|
||||
Element blink(Element);
|
||||
Element strikethrough(Element);
|
||||
Decorator color(Color);
|
||||
Decorator bgcolor(Color);
|
||||
Element color(Color, Element);
|
||||
@@ -126,9 +128,6 @@ enum Direction { WIDTH, HEIGHT };
|
||||
enum Constraint { LESS_THAN, EQUAL, GREATER_THAN };
|
||||
Decorator size(Direction, Constraint, int value);
|
||||
|
||||
// --
|
||||
Decorator reflect(Box& box);
|
||||
|
||||
// --- Frame ---
|
||||
// A frame is a scrollable area. The internal area is potentially larger than
|
||||
// the external one. The internal area is scrolled in order to make visible the
|
||||
@@ -139,7 +138,21 @@ Element yframe(Element);
|
||||
Element focus(Element);
|
||||
Element select(Element);
|
||||
|
||||
// --- Cursor ---
|
||||
// Those are similar to `focus`, but also change the shape of the cursor.
|
||||
Element focusCursorBlock(Element);
|
||||
Element focusCursorBlockBlinking(Element);
|
||||
Element focusCursorBar(Element);
|
||||
Element focusCursorBarBlinking(Element);
|
||||
Element focusCursorUnderline(Element);
|
||||
Element focusCursorUnderlineBlinking(Element);
|
||||
|
||||
// --- Misc ---
|
||||
Element vscroll_indicator(Element);
|
||||
Decorator reflect(Box& box);
|
||||
// Before drawing the |element| clear the pixel below. This is useful in
|
||||
// combinaison with dbox.
|
||||
Element clear_under(Element element);
|
||||
|
||||
// --- Util --------------------------------------------------------------------
|
||||
Element hcenter(Element);
|
||||
@@ -148,10 +161,6 @@ Element center(Element);
|
||||
Element align_right(Element);
|
||||
Element nothing(Element element);
|
||||
|
||||
// Before drawing the |element| clear the pixel below. This is useful in
|
||||
// combinaison with dbox.
|
||||
Element clear_under(Element element);
|
||||
|
||||
namespace Dimension {
|
||||
Dimensions Fit(Element&);
|
||||
} // namespace Dimension
|
||||
|
@@ -10,6 +10,7 @@ struct Box {
|
||||
int y_max = 0;
|
||||
|
||||
static auto Intersection(Box a, Box b) -> Box;
|
||||
static auto Union(Box a, Box b) -> Box;
|
||||
bool Contain(int x, int y) const;
|
||||
bool operator==(const Box& other) const;
|
||||
bool operator!=(const Box& other) const;
|
||||
|
@@ -30,6 +30,8 @@ struct Pixel {
|
||||
bool dim : 1;
|
||||
bool inverted : 1;
|
||||
bool underlined : 1;
|
||||
bool underlined_double : 1;
|
||||
bool strikethrough : 1;
|
||||
bool automerge : 1;
|
||||
|
||||
Pixel()
|
||||
@@ -38,6 +40,8 @@ struct Pixel {
|
||||
dim(false),
|
||||
inverted(false),
|
||||
underlined(false),
|
||||
underlined_double(false),
|
||||
strikethrough(false),
|
||||
automerge(false) {}
|
||||
};
|
||||
|
||||
@@ -80,6 +84,17 @@ class Screen {
|
||||
struct Cursor {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
enum Shape {
|
||||
Hidden = 0,
|
||||
BlockBlinking = 1,
|
||||
Block = 2,
|
||||
UnderlineBlinking = 3,
|
||||
Underline = 4,
|
||||
BarBlinking = 5,
|
||||
Bar = 6,
|
||||
};
|
||||
Shape shape;
|
||||
};
|
||||
Cursor cursor() const { return cursor_; }
|
||||
void SetCursor(Cursor cursor) { cursor_ = cursor; }
|
||||
@@ -91,8 +106,6 @@ class Screen {
|
||||
int dimy_;
|
||||
std::vector<std::vector<Pixel>> pixels_;
|
||||
Cursor cursor_;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace ftxui
|
||||
|
@@ -26,6 +26,33 @@ int GlyphPosition(const std::string& input,
|
||||
// Returns the number of glyphs in |input|.
|
||||
int GlyphCount(const std::string& input);
|
||||
|
||||
// Properties from:
|
||||
// https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt
|
||||
enum class WordBreakProperty {
|
||||
ALetter,
|
||||
CR,
|
||||
Double_Quote,
|
||||
Extend,
|
||||
ExtendNumLet,
|
||||
Format,
|
||||
Hebrew_Letter,
|
||||
Katakana,
|
||||
LF,
|
||||
MidLetter,
|
||||
MidNum,
|
||||
MidNumLet,
|
||||
Newline,
|
||||
Numeric,
|
||||
Regional_Indicator,
|
||||
Single_Quote,
|
||||
WSegSpace,
|
||||
ZWJ,
|
||||
};
|
||||
std::vector<WordBreakProperty> Utf8ToWordBreakProperty(
|
||||
const std::string& input);
|
||||
|
||||
bool IsWordBreakingCharacter(const std::string& input, size_t glyph_index);
|
||||
|
||||
// Map every cells drawn by |input| to their corresponding Glyphs. Half-size
|
||||
// Glyphs takes one cell, full-size Glyphs take two cells.
|
||||
std::vector<int> CellToGlyphIndex(const std::string& input);
|
||||
|
@@ -66,6 +66,9 @@ class ConstStringRef {
|
||||
ConstStringRef(const wchar_t* ref) : ConstStringRef(std::wstring(ref)) {}
|
||||
ConstStringRef(const char* ref)
|
||||
: ConstStringRef(to_wstring(std::string(ref))) {}
|
||||
const std::string& operator()() const {
|
||||
return address_ ? *address_ : owned_;
|
||||
}
|
||||
const std::string& operator*() const { return address_ ? *address_ : owned_; }
|
||||
const std::string* operator->() const {
|
||||
return address_ ? address_ : &owned_;
|
||||
|
30
iwyu.imp
30
iwyu.imp
@@ -1,17 +1,27 @@
|
||||
[
|
||||
{ include: ["<bits/chrono.h>", "private", "<chrono>", "public"]},
|
||||
{ include: ["<bits/std_abs.h>", "private", "<cmath>", "public"]},
|
||||
{ include: ["<bits/termios-c_cc.h>", "private", "<termios.h>", "public"]},
|
||||
{ include: ["<bits/termios-c_lflag.h>", "private", "<termios.h>", "public"]},
|
||||
{ include: ["<bits/termios-struct.h>", "private", "<termios.h>", "public"]},
|
||||
{ include: ["<bits/termios-tcflow.h>", "private", "<termios.h>", "public"]},
|
||||
{ include: ["<ext/alloc_traits.h>", "private", "<vector>", "public"] },
|
||||
{ symbol: [ "ftxui", "private", "", "public" ] },
|
||||
{ symbol: [ "char_traits", "private", "<string>", "public" ] },
|
||||
{ symbol: [ "ECHO", "private", "<termios.h>", "public" ] },
|
||||
{ symbol: [ "ICANON", "private", "<termios.h>", "public" ] },
|
||||
{ symbol: [ "TCSANOW", "private", "<termios.h>", "public" ] },
|
||||
{ symbol: [ "VMIN", "private", "<termios.h>", "public" ] },
|
||||
{ symbol: [ "VTIME", "private", "<termios.h>", "public" ] },
|
||||
{ symbol: [ "__shared_ptr_access", "private", "<memory>", "public" ] },
|
||||
{ symbol: [ "termios", "private", "<termios.h>", "public" ] },
|
||||
{ symbol: ["__alloc_traits<>:value_type", "private", "<vector>", "public" ] },
|
||||
{ include: ["<gtest/internal/gtest-internal.h>", "private", "<gtest/gtest.h>", "public" ] },
|
||||
{ include: ["<gtest/internal/gtest-string.h>", "private", "<gtest/gtest.h>", "public" ] },
|
||||
{ include: ["<gtest/gtest-death-test.h>", "private", "<gtest/gtest.h>", "public" ] },
|
||||
{ include: ["<gtest/gtest-message.h>", "private", "<gtest/gtest.h>", "public" ] },
|
||||
{ include: ["<gtest/gtest-param-test.h>", "private", "<gtest/gtest.h>", "public" ] },
|
||||
{ include: ["<gtest/gtest-printers.h>", "private", "<gtest/gtest.h>", "public" ] },
|
||||
{ include: ["<gtest/gtest-test-part.h>", "private", "<gtest/gtest.h>", "public" ] },
|
||||
{ include: ["<gtest/gtest-typed-test.h>", "private", "<gtest/gtest.h>", "public" ] },
|
||||
{ symbol: ["ftxui", "private", "", "public" ] },
|
||||
{ symbol: ["char_traits", "private", "<string>", "public" ] },
|
||||
{ symbol: ["ECHO", "private", "<termios.h>", "public" ] },
|
||||
{ symbol: ["ICANON", "private", "<termios.h>", "public" ] },
|
||||
{ symbol: ["TCSANOW", "private", "<termios.h>", "public" ] },
|
||||
{ symbol: ["VMIN", "private", "<termios.h>", "public" ] },
|
||||
{ symbol: ["VTIME", "private", "<termios.h>", "public" ] },
|
||||
{ symbol: ["__shared_ptr_access", "private", "<memory>", "public" ] },
|
||||
{ symbol: ["termios", "private", "<termios.h>", "public" ] },
|
||||
{ symbol: ["__alloc_traits<>:value_type", "private", "<vector>", "public" ] }
|
||||
]
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#include <cmath>
|
||||
#include <cmath> // for sin, pow, sqrt, cos
|
||||
#include <ratio> // for ratio
|
||||
#include <utility> // for move
|
||||
|
||||
@@ -58,7 +58,7 @@ float CubicIn(float p) {
|
||||
|
||||
// Modeled after the cubic y = (x - 1)^3 + 1
|
||||
float CubicOut(float p) {
|
||||
float f = (p - 1);
|
||||
const float f = (p - 1);
|
||||
return f * f * f + 1;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ float CubicInOut(float p) {
|
||||
if (p < 0.5F) { // NOLINT
|
||||
return 4 * p * p * p;
|
||||
} else {
|
||||
float f = ((2 * p) - 2);
|
||||
const float f = ((2 * p) - 2);
|
||||
return 0.5F * f * f * f + 1; // NOLINT
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ float QuarticIn(float p) {
|
||||
|
||||
// Modeled after the quartic y = 1 - (x - 1)^4
|
||||
float QuarticOut(float p) {
|
||||
float f = (p - 1);
|
||||
const float f = (p - 1);
|
||||
return f * f * f * (1 - p) + 1;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ float QuarticInOut(float p) {
|
||||
if (p < 0.5F) { // NOLINT
|
||||
return 8 * p * p * p * p; // NOLINT
|
||||
} else {
|
||||
float f = (p - 1);
|
||||
const float f = (p - 1);
|
||||
return -8 * f * f * f * f + 1; // NOLINT
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ float QuinticIn(float p) {
|
||||
|
||||
// Modeled after the quintic y = (x - 1)^5 + 1
|
||||
float QuinticOut(float p) {
|
||||
float f = (p - 1);
|
||||
const float f = (p - 1);
|
||||
return f * f * f * f * f + 1;
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ float BackIn(float p) {
|
||||
|
||||
// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
|
||||
float BackOut(float p) {
|
||||
float f = (1 - p);
|
||||
const float f = (1 - p);
|
||||
return 1 - (f * f * f - f * std::sin(f * kPi));
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ float BackOut(float p) {
|
||||
// y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]
|
||||
float BackInOut(float p) {
|
||||
if (p < 0.5F) { // NOLINT
|
||||
float f = 2 * p;
|
||||
const float f = 2 * p;
|
||||
return 0.5F * (f * f * f - f * std::sin(f * kPi)); // NOLINT
|
||||
} else {
|
||||
float f = (1 - (2 * p - 1)); // NOLINT
|
||||
|
@@ -1,10 +1,8 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for SuiteApiResolver, TestPartResult, TestFactoryImpl
|
||||
#include <gtest/gtest.h>
|
||||
#include <functional> // for function
|
||||
#include <vector> // for allocator, vector
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for Function, BackIn, BackInOut, BackOut, BounceIn, BounceInOut, BounceOut, CircularIn, CircularInOut, CircularOut, CubicIn, CubicInOut, CubicOut, ElasticIn, ElasticInOut, ElasticOut, ExponentialIn, ExponentialInOut, ExponentialOut, Linear, QuadraticIn, QuadraticInOut, QuadraticOut, QuarticIn, QuarticInOut, QuarticOut, QuinticIn, QuinticInOut, QuinticOut, SineIn, SineInOut, SineOut
|
||||
#include "gtest/gtest_pred_impl.h" // for Test, EXPECT_NEAR, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
@@ -28,8 +26,8 @@ TEST(AnimationTest, StartAndEnd) {
|
||||
animation::easing::BounceInOut,
|
||||
};
|
||||
for (auto& it : functions) {
|
||||
EXPECT_NEAR(0.f, it(0.f), 1.0e-4);
|
||||
EXPECT_NEAR(1.f, it(1.f), 1.0e-4);
|
||||
EXPECT_NEAR(0.F, it(0.F), 1.0e-4);
|
||||
EXPECT_NEAR(1.F, it(1.F), 1.0e-4);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -80,7 +80,7 @@ Component Button(ConstStringRef label,
|
||||
}
|
||||
|
||||
auto focus_management = focused ? focus : active ? select : nothing;
|
||||
EntryState state = {
|
||||
const EntryState state = {
|
||||
*label_,
|
||||
false,
|
||||
active,
|
||||
|
@@ -1,5 +1,4 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, EXPECT_EQ, Test, EXPECT_FALSE, EXPECT_TRUE, TestInfo (ptr only), TEST
|
||||
#include <chrono> // for operator""s, chrono_literals
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
#include <string> // for string
|
||||
@@ -13,7 +12,6 @@
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/screen.hpp" // for Screen
|
||||
#include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor
|
||||
#include "gtest/gtest_pred_impl.h" // for AssertionResult, EXPECT_EQ, Test, EXPECT_FALSE, EXPECT_TRUE, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
@@ -1,9 +1,10 @@
|
||||
#include <functional> // for function
|
||||
#include <memory> // for __shared_ptr_access, __shared_ptr_access<>::element_type, shared_ptr
|
||||
#include <type_traits> // for remove_reference, remove_reference<>::type
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Component, Make, CatchEvent
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component.hpp" // for Make, CatchEvent, ComponentDecorator
|
||||
#include "ftxui/component/component_base.hpp" // for Component, ComponentBase
|
||||
#include "ftxui/component/event.hpp" // for Event
|
||||
|
||||
namespace ftxui {
|
||||
|
@@ -1,14 +1,13 @@
|
||||
#include <functional> // for function
|
||||
#include <memory> // for shared_ptr
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/component.hpp" // for Make, Component, Checkbox
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for CheckboxOption
|
||||
#include "ftxui/component/component.hpp" // for Make, Checkbox
|
||||
#include "ftxui/component/component_base.hpp" // for Component, ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for CheckboxOption, EntryState
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Return
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, text, Element, hbox, reflect, focus, nothing, select
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, reflect, focus, nothing, select
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringRef
|
||||
|
||||
@@ -23,8 +22,8 @@ class CheckboxBase : public ComponentBase {
|
||||
private:
|
||||
// Component implementation.
|
||||
Element Render() override {
|
||||
bool is_focused = Focused();
|
||||
bool is_active = Active();
|
||||
const bool is_focused = Focused();
|
||||
const bool is_active = Active();
|
||||
auto focus_management = is_focused ? focus : is_active ? select : nothing;
|
||||
auto state = EntryState{
|
||||
*label_,
|
||||
|
@@ -26,7 +26,7 @@ namespace ftxui {
|
||||
///
|
||||
/// ▼ Show details
|
||||
/// <details component>
|
||||
/// ```
|
||||
/// ```
|
||||
Component Collapsible(ConstStringRef label, Component child, Ref<bool> show) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
|
@@ -1,5 +1,4 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Collapsible, Renderer
|
||||
@@ -8,7 +7,6 @@
|
||||
#include "ftxui/dom/elements.hpp" // for text, Element
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/screen.hpp" // for Screen
|
||||
#include "gtest/gtest_pred_impl.h" // for AssertionResult, Test, EXPECT_EQ, EXPECT_FALSE, EXPECT_TRUE, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
@@ -114,7 +114,7 @@ bool ComponentBase::OnEvent(Event event) { // NOLINT
|
||||
/// The default implementation dispatch the event to every child.
|
||||
/// @ingroup component
|
||||
void ComponentBase::OnAnimation(animation::Params& params) {
|
||||
for (Component& child : children_) {
|
||||
for (const Component& child : children_) {
|
||||
child->OnAnimation(params);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#include "ftxui/component/component_options.hpp"
|
||||
|
||||
#include <ftxui/screen/color.hpp> // for Color, Color::Black, Color::White, Color::GrayDark, Color::GrayLight
|
||||
#include <memory> // for shared_ptr
|
||||
#include <utility> // for move
|
||||
|
||||
@@ -122,7 +123,7 @@ MenuOption MenuOption::Toggle() {
|
||||
ButtonOption ButtonOption::Ascii() {
|
||||
ButtonOption option;
|
||||
option.transform = [](const EntryState& s) {
|
||||
std::string label = s.focused ? "[" + s.label + "]" //
|
||||
const std::string label = s.focused ? "[" + s.label + "]" //
|
||||
: " " + s.label + " ";
|
||||
return text(label);
|
||||
};
|
||||
@@ -143,6 +144,23 @@ ButtonOption ButtonOption::Simple() {
|
||||
return option;
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption. The button is shown using a border, inverted
|
||||
/// when focused. This is the current default.
|
||||
ButtonOption ButtonOption::Border() {
|
||||
ButtonOption option;
|
||||
option.transform = [](const EntryState& s) {
|
||||
auto element = text(s.label) | border;
|
||||
if (s.active) {
|
||||
element |= bold;
|
||||
}
|
||||
if (s.focused) {
|
||||
element |= inverted;
|
||||
}
|
||||
return element;
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, using animated colors.
|
||||
// static
|
||||
ButtonOption ButtonOption::Animated() {
|
||||
@@ -163,7 +181,13 @@ ButtonOption ButtonOption::Animated(Color color) {
|
||||
/// @brief Create a ButtonOption, using animated colors.
|
||||
// static
|
||||
ButtonOption ButtonOption::Animated(Color background, Color foreground) {
|
||||
return ButtonOption::Animated(background, foreground, foreground, background);
|
||||
// NOLINTBEGIN
|
||||
return ButtonOption::Animated(
|
||||
/*bakground=*/background,
|
||||
/*foreground=*/foreground,
|
||||
/*background_active=*/foreground,
|
||||
/*foreground_active=*/background);
|
||||
// NOLINTEND
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, using animated colors.
|
||||
|
@@ -1,10 +1,8 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access, allocator, __shared_ptr_access<>::element_type, make_shared
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Make
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||
#include "gtest/gtest_pred_impl.h" // for EXPECT_EQ, Test, AssertionResult, TEST, EXPECT_FALSE
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
@@ -82,7 +82,7 @@ class ContainerBase : public ComponentBase {
|
||||
return;
|
||||
}
|
||||
for (size_t offset = 1; offset < children_.size(); ++offset) {
|
||||
size_t i = ((size_t(*selector_ + offset * dir + children_.size())) %
|
||||
const size_t i = ((size_t(*selector_ + offset * dir + children_.size())) %
|
||||
children_.size());
|
||||
if (children_[i]->Focusable()) {
|
||||
*selector_ = (int)i;
|
||||
@@ -108,7 +108,7 @@ class VerticalContainer : public ContainerBase {
|
||||
}
|
||||
|
||||
bool EventHandler(Event event) override {
|
||||
int old_selected = *selector_;
|
||||
const int old_selected = *selector_;
|
||||
if (event == Event::ArrowUp || event == Event::Character('k')) {
|
||||
MoveSelector(-1);
|
||||
}
|
||||
@@ -190,7 +190,7 @@ class HorizontalContainer : public ContainerBase {
|
||||
}
|
||||
|
||||
bool EventHandler(Event event) override {
|
||||
int old_selected = *selector_;
|
||||
const int old_selected = *selector_;
|
||||
if (event == Event::ArrowLeft || event == Event::Character('h')) {
|
||||
MoveSelector(-1);
|
||||
}
|
||||
@@ -214,7 +214,7 @@ class TabContainer : public ContainerBase {
|
||||
using ContainerBase::ContainerBase;
|
||||
|
||||
Element Render() override {
|
||||
Component active_child = ActiveChild();
|
||||
const Component active_child = ActiveChild();
|
||||
if (active_child) {
|
||||
return active_child->Render();
|
||||
}
|
||||
@@ -229,7 +229,7 @@ class TabContainer : public ContainerBase {
|
||||
}
|
||||
|
||||
bool OnMouseEvent(Event event) override {
|
||||
return ActiveChild()->OnEvent(event);
|
||||
return ActiveChild() && ActiveChild()->OnEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -1,11 +1,9 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Horizontal, Vertical, Button, Tab
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Tab, Event::TabReverse, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp
|
||||
#include "gtest/gtest_pred_impl.h" // for AssertionResult, EXPECT_EQ, EXPECT_FALSE, EXPECT_TRUE, Test, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
@@ -55,24 +55,32 @@ const Event Event::ArrowLeft = Event::Special("\x1B[D"); // NOLINT
|
||||
const Event Event::ArrowRight = Event::Special("\x1B[C"); // NOLINT
|
||||
const Event Event::ArrowUp = Event::Special("\x1B[A"); // NOLINT
|
||||
const Event Event::ArrowDown = Event::Special("\x1B[B"); // NOLINT
|
||||
const Event Event::ArrowLeftCtrl = Event::Special("\x1B[1;5D"); // NOLINT
|
||||
const Event Event::ArrowRightCtrl = Event::Special("\x1B[1;5C"); // NOLINT
|
||||
const Event Event::ArrowUpCtrl = Event::Special("\x1B[1;5A"); // NOLINT
|
||||
const Event Event::ArrowDownCtrl = Event::Special("\x1B[1;5B"); // NOLINT
|
||||
const Event Event::Backspace = Event::Special({127}); // NOLINT
|
||||
const Event Event::Delete = Event::Special("\x1B[3~"); // NOLINT
|
||||
const Event Event::Escape = Event::Special("\x1B"); // NOLINT
|
||||
const Event Event::Return = Event::Special({10}); // NOLINT
|
||||
const Event Event::Tab = Event::Special({9}); // NOLINT
|
||||
const Event Event::TabReverse = Event::Special({27, 91, 90}); // NOLINT
|
||||
const Event Event::F1 = Event::Special("\x1B[OP"); // NOLINT
|
||||
const Event Event::F2 = Event::Special("\x1B[OQ"); // NOLINT
|
||||
const Event Event::F3 = Event::Special("\x1B[OR"); // NOLINT
|
||||
const Event Event::F4 = Event::Special("\x1B[OS"); // NOLINT
|
||||
|
||||
// See https://invisible-island.net/xterm/xterm-function-keys.html
|
||||
// We follow xterm-new / vterm-xf86-v4 / mgt / screen
|
||||
const Event Event::F1 = Event::Special("\x1BOP"); // NOLINT
|
||||
const Event Event::F2 = Event::Special("\x1BOQ"); // NOLINT
|
||||
const Event Event::F3 = Event::Special("\x1BOR"); // NOLINT
|
||||
const Event Event::F4 = Event::Special("\x1BOS"); // NOLINT
|
||||
const Event Event::F5 = Event::Special("\x1B[15~"); // NOLINT
|
||||
const Event Event::F6 = Event::Special("\x1B[17~"); // NOLINT
|
||||
const Event Event::F7 = Event::Special("\x1B[18~"); // NOLINT
|
||||
const Event Event::F8 = Event::Special("\x1B[19~"); // NOLINT
|
||||
const Event Event::F9 = Event::Special("\x1B[20~"); // NOLINT
|
||||
const Event Event::F10 = Event::Special("\x1B[21~"); // NOLINT
|
||||
const Event Event::F11 = Event::Special("\x1B[21~"); // Doesn't exist // NOLINT
|
||||
const Event Event::F11 = Event::Special("\x1B[23~"); // NOLINT
|
||||
const Event Event::F12 = Event::Special("\x1B[24~"); // NOLINT
|
||||
|
||||
const Event Event::Home = Event::Special({27, 91, 72}); // NOLINT
|
||||
const Event Event::End = Event::Special({27, 91, 70}); // NOLINT
|
||||
const Event Event::PageUp = Event::Special({27, 91, 53, 126}); // NOLINT
|
||||
|
217
src/ftxui/component/hoverable.cpp
Normal file
217
src/ftxui/component/hoverable.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include <ftxui/component/captured_mouse.hpp> // for CapturedMouse
|
||||
#include <functional> // for function
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/component.hpp" // for ComponentDecorator, Hoverable, Make
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/event.hpp" // for Event
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, reflect, Element
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
|
||||
void Post(std::function<void()> f) {
|
||||
if (auto* screen = ScreenInteractive::Active()) {
|
||||
screen->Post(std::move(f));
|
||||
return;
|
||||
}
|
||||
f();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// @brief Wrap a component. Gives the ability to know if it is hovered by the
|
||||
/// mouse.
|
||||
/// @param component: The wrapped component.
|
||||
/// @param hover: The value to reflect whether the component is hovered or not.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto button = Button("exit", screen.ExitLoopClosure());
|
||||
/// bool hover = false;
|
||||
/// auto button_hover = Hoverable(button, &hover);
|
||||
/// ```
|
||||
// NOLINTNEXTLINE
|
||||
Component Hoverable(Component component, bool* hover) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
Impl(Component component, bool* hover)
|
||||
: component_(std::move(component)), hover_(hover) {
|
||||
Add(component_);
|
||||
}
|
||||
|
||||
private:
|
||||
Element Render() override {
|
||||
return ComponentBase::Render() | reflect(box_);
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
if (event.is_mouse()) {
|
||||
*hover_ = box_.Contain(event.mouse().x, event.mouse().y) &&
|
||||
CaptureMouse(event);
|
||||
}
|
||||
|
||||
return ComponentBase::OnEvent(event);
|
||||
}
|
||||
|
||||
Component component_;
|
||||
bool* hover_;
|
||||
Box box_;
|
||||
};
|
||||
|
||||
return Make<Impl>(component, hover);
|
||||
}
|
||||
|
||||
/// @brief Wrap a component. Gives the ability to know if it is hovered by the
|
||||
/// mouse.
|
||||
/// @param component: The wrapped component.
|
||||
/// @param hover: The value to reflect whether the component is hovered or not.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto button = Button("exit", screen.ExitLoopClosure());
|
||||
/// bool hover = false;
|
||||
/// auto button_hover = Hoverable(button, &hover);
|
||||
/// ```
|
||||
Component Hoverable(Component component,
|
||||
std::function<void()> on_enter,
|
||||
std::function<void()> on_leave) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
Impl(Component component,
|
||||
std::function<void()> on_enter,
|
||||
std::function<void()> on_leave)
|
||||
: component_(std::move(component)),
|
||||
on_enter_(std::move(on_enter)),
|
||||
on_leave_(std::move(on_leave)) {
|
||||
Add(component_);
|
||||
}
|
||||
|
||||
private:
|
||||
Element Render() override {
|
||||
return ComponentBase::Render() | reflect(box_);
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
if (event.is_mouse()) {
|
||||
const bool hover = box_.Contain(event.mouse().x, event.mouse().y) &&
|
||||
CaptureMouse(event);
|
||||
if (hover != hover_) {
|
||||
Post(hover ? on_enter_ : on_leave_);
|
||||
}
|
||||
hover_ = hover;
|
||||
}
|
||||
|
||||
return ComponentBase::OnEvent(event);
|
||||
}
|
||||
|
||||
Component component_;
|
||||
Box box_;
|
||||
bool hover_ = false;
|
||||
std::function<void()> on_enter_;
|
||||
std::function<void()> on_leave_;
|
||||
};
|
||||
|
||||
return Make<Impl>(std::move(component), std::move(on_enter),
|
||||
std::move(on_leave));
|
||||
}
|
||||
|
||||
/// @brief Wrap a component. Gives the ability to know if it is hovered by the
|
||||
/// mouse.
|
||||
/// @param hover: The value to reflect whether the component is hovered or not.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// bool hover = false;
|
||||
/// auto button = Button("exit", screen.ExitLoopClosure());
|
||||
/// button |= Hoverable(&hover);
|
||||
/// ```
|
||||
ComponentDecorator Hoverable(bool* hover) {
|
||||
return [hover](Component component) {
|
||||
return Hoverable(std::move(component), hover);
|
||||
};
|
||||
}
|
||||
|
||||
/// @brief Wrap a component. Gives the ability to know if it is hovered by the
|
||||
/// mouse.
|
||||
/// @param on_enter is called when the mouse hover the component.
|
||||
/// @param on_leave is called when the mouse leave the component.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto button = Button("exit", screen.ExitLoopClosure());
|
||||
/// int on_enter_cnt = 0;
|
||||
/// int on_leave_cnt = 0;
|
||||
/// button |= Hoverable(
|
||||
/// [&]{ on_enter_cnt++; },
|
||||
/// [&]{ on_leave_cnt++; }
|
||||
// );
|
||||
/// ```
|
||||
// NOLINTNEXTLINE
|
||||
ComponentDecorator Hoverable(std::function<void()> on_enter,
|
||||
// NOLINTNEXTLINE
|
||||
std::function<void()> on_leave) {
|
||||
return [on_enter, on_leave](Component component) {
|
||||
return Hoverable(std::move(component), on_enter, on_leave);
|
||||
};
|
||||
}
|
||||
|
||||
/// @brief Wrap a component. Gives the ability to know if it is hovered by the
|
||||
/// mouse.
|
||||
/// @param component the wrapped component.
|
||||
/// @param on_change is called when the mouse enter or leave the component.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto button = Button("exit", screen.ExitLoopClosure());
|
||||
/// bool hovered = false;
|
||||
/// auto button_hoverable = Hoverable(button,
|
||||
// [&](bool hover) { hovered = hover;});
|
||||
/// ```
|
||||
// NOLINTNEXTLINE
|
||||
Component Hoverable(Component component, std::function<void(bool)> on_change) {
|
||||
return Hoverable(
|
||||
std::move(component), //
|
||||
[on_change] { on_change(true); }, //
|
||||
[on_change] { on_change(false); } //
|
||||
);
|
||||
}
|
||||
|
||||
/// @brief Wrap a component. Gives the ability to know if it is hovered by the
|
||||
/// mouse.
|
||||
/// @param on_change is called when the mouse enter or leave the component.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto button = Button("exit", screen.ExitLoopClosure());
|
||||
/// bool hovered = false;
|
||||
/// button |= Hoverable([&](bool hover) { hovered = hover;});
|
||||
/// ```
|
||||
// NOLINTNEXTLINE
|
||||
ComponentDecorator Hoverable(std::function<void(bool)> on_change) {
|
||||
return [on_change](Component component) {
|
||||
return Hoverable(std::move(component), on_change);
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2022 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
192
src/ftxui/component/hoverable_test.cpp
Normal file
192
src/ftxui/component/hoverable_test.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, EXPECT_FALSE, EXPECT_EQ, Test, EXPECT_TRUE, TestInfo (ptr only), TEST
|
||||
#include <ftxui/dom/elements.hpp> // for Element, text
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Hoverable, Horizontal, operator|=, Renderer
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||
#include "ftxui/component/event.hpp" // for Event
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Released
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/screen.hpp" // for Screen
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
Event HoverEvent(int x, int y) {
|
||||
Mouse mouse;
|
||||
mouse.button = Mouse::Left;
|
||||
mouse.motion = Mouse::Released;
|
||||
mouse.shift = false;
|
||||
mouse.meta = false;
|
||||
mouse.control = false;
|
||||
mouse.x = x;
|
||||
mouse.y = y;
|
||||
return Event::Mouse("jjj", mouse);
|
||||
}
|
||||
|
||||
Component BasicComponent() {
|
||||
return Renderer([] { return text("[ ]"); });
|
||||
}
|
||||
|
||||
TEST(HoverableTest, BasicBool) {
|
||||
bool hover_1 = false;
|
||||
bool hover_2 = false;
|
||||
auto c1 = Hoverable(BasicComponent(), &hover_1);
|
||||
auto c2 = Hoverable(BasicComponent(), &hover_2);
|
||||
auto layout = Container::Horizontal({c1, c2});
|
||||
auto screen = Screen(8, 2);
|
||||
Render(screen, layout->Render());
|
||||
EXPECT_FALSE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(1, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(2, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(3, 0)));
|
||||
EXPECT_FALSE(hover_1);
|
||||
EXPECT_TRUE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
}
|
||||
|
||||
TEST(HoverableTest, BasicCallback) {
|
||||
int on_enter_1 = 0;
|
||||
int on_enter_2 = 0;
|
||||
int on_leave_1 = 0;
|
||||
int on_leave_2 = 0;
|
||||
auto c1 = Hoverable(
|
||||
BasicComponent(), [&] { on_enter_1++; }, [&] { on_leave_1++; });
|
||||
auto c2 = Hoverable(
|
||||
BasicComponent(), [&] { on_enter_2++; }, [&] { on_leave_2++; });
|
||||
auto layout = Container::Horizontal({c1, c2});
|
||||
auto screen = Screen(8, 2);
|
||||
Render(screen, layout->Render());
|
||||
EXPECT_EQ(on_enter_1, 0);
|
||||
EXPECT_EQ(on_enter_2, 0);
|
||||
EXPECT_EQ(on_leave_1, 0);
|
||||
EXPECT_EQ(on_leave_2, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_EQ(on_enter_1, 1);
|
||||
EXPECT_EQ(on_enter_2, 0);
|
||||
EXPECT_EQ(on_leave_1, 0);
|
||||
EXPECT_EQ(on_leave_2, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(1, 0)));
|
||||
EXPECT_EQ(on_enter_1, 1);
|
||||
EXPECT_EQ(on_enter_2, 0);
|
||||
EXPECT_EQ(on_leave_1, 0);
|
||||
EXPECT_EQ(on_leave_2, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(2, 0)));
|
||||
EXPECT_EQ(on_enter_1, 1);
|
||||
EXPECT_EQ(on_enter_2, 0);
|
||||
EXPECT_EQ(on_leave_1, 0);
|
||||
EXPECT_EQ(on_leave_2, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(3, 0)));
|
||||
EXPECT_EQ(on_enter_1, 1);
|
||||
EXPECT_EQ(on_enter_2, 1);
|
||||
EXPECT_EQ(on_leave_1, 1);
|
||||
EXPECT_EQ(on_leave_2, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_EQ(on_enter_1, 2);
|
||||
EXPECT_EQ(on_enter_2, 1);
|
||||
EXPECT_EQ(on_leave_1, 1);
|
||||
EXPECT_EQ(on_leave_2, 1);
|
||||
}
|
||||
|
||||
TEST(HoverableTest, BasicBoolCallback) {
|
||||
bool hover_1 = false;
|
||||
bool hover_2 = false;
|
||||
auto c1 = Hoverable(BasicComponent(), [&](bool hover) { hover_1 = hover; });
|
||||
auto c2 = Hoverable(BasicComponent(), [&](bool hover) { hover_2 = hover; });
|
||||
auto layout = Container::Horizontal({c1, c2});
|
||||
auto screen = Screen(8, 2);
|
||||
Render(screen, layout->Render());
|
||||
EXPECT_FALSE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(1, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(2, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(3, 0)));
|
||||
EXPECT_FALSE(hover_1);
|
||||
EXPECT_TRUE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
}
|
||||
|
||||
TEST(HoverableTest, Coverage) {
|
||||
bool hover_1 = false;
|
||||
bool hover_2 = false;
|
||||
int on_enter = 0;
|
||||
int on_leave = 0;
|
||||
auto c1 = BasicComponent();
|
||||
c1 |= Hoverable(&hover_1);
|
||||
c1 |= Hoverable([&](bool hover) { hover_2 = hover; });
|
||||
c1 |= Hoverable([&] { on_enter++; }, [&] { on_leave++; });
|
||||
auto c2 = BasicComponent();
|
||||
auto layout = Container::Horizontal({c1, c2});
|
||||
|
||||
auto screen = Screen(8, 2);
|
||||
Render(screen, layout->Render());
|
||||
EXPECT_FALSE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
EXPECT_EQ(on_enter, 0);
|
||||
EXPECT_EQ(on_leave, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_TRUE(hover_2);
|
||||
EXPECT_EQ(on_enter, 1);
|
||||
EXPECT_EQ(on_leave, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(1, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_TRUE(hover_2);
|
||||
EXPECT_EQ(on_enter, 1);
|
||||
EXPECT_EQ(on_leave, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(3, 0)));
|
||||
EXPECT_FALSE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
EXPECT_EQ(on_enter, 1);
|
||||
EXPECT_EQ(on_leave, 1);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_TRUE(hover_2);
|
||||
EXPECT_EQ(on_enter, 2);
|
||||
EXPECT_EQ(on_leave, 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
@@ -1,8 +1,8 @@
|
||||
#include <algorithm> // for max, min
|
||||
#include <cstddef> // for size_t
|
||||
#include <functional> // for function
|
||||
#include <memory> // for shared_ptr, allocator
|
||||
#include <string> // for string, wstring
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for string, allocator
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
#include "ftxui/component/component.hpp" // for Make, Input
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for InputOption
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowRight, Event::Backspace, Event::Custom, Event::Delete, Event::End, Event::Home, Event::Return
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowLeftCtrl, Event::ArrowRight, Event::ArrowRightCtrl, Event::Backspace, Event::Custom, Event::Delete, Event::End, Event::Home, Event::Return
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, text, Element, reflect, inverted, Decorator, flex, focus, hbox, size, bold, dim, frame, select, EQUAL, HEIGHT
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, text, Element, reflect, operator|=, flex, inverted, hbox, size, bold, dim, focus, focusCursorBarBlinking, frame, select, Decorator, EQUAL, HEIGHT
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/string.hpp" // for GlyphPosition, GlyphCount, to_string, CellToGlyphIndex, to_wstring
|
||||
#include "ftxui/screen/string.hpp" // for GlyphPosition, WordBreakProperty, GlyphCount, Utf8ToWordBreakProperty, CellToGlyphIndex, WordBreakProperty::ALetter, WordBreakProperty::CR, WordBreakProperty::Double_Quote, WordBreakProperty::Extend, WordBreakProperty::ExtendNumLet, WordBreakProperty::Format, WordBreakProperty::Hebrew_Letter, WordBreakProperty::Katakana, WordBreakProperty::LF, WordBreakProperty::MidLetter, WordBreakProperty::MidNum, WordBreakProperty::MidNumLet, WordBreakProperty::Newline, WordBreakProperty::Numeric, WordBreakProperty::Regional_Indicator, WordBreakProperty::Single_Quote, WordBreakProperty::WSegSpace, WordBreakProperty::ZWJ
|
||||
#include "ftxui/screen/util.hpp" // for clamp
|
||||
#include "ftxui/util/ref.hpp" // for StringRef, Ref, ConstStringRef
|
||||
|
||||
@@ -23,6 +23,36 @@ namespace ftxui {
|
||||
|
||||
namespace {
|
||||
|
||||
// Group together several propertiej so they appear to form a similar group.
|
||||
// For instance, letters are grouped with number and form a single word.
|
||||
bool IsWordCharacter(WordBreakProperty property) {
|
||||
switch (property) {
|
||||
case WordBreakProperty::ALetter:
|
||||
case WordBreakProperty::Hebrew_Letter:
|
||||
case WordBreakProperty::Katakana:
|
||||
case WordBreakProperty::Numeric:
|
||||
return true;
|
||||
|
||||
case WordBreakProperty::CR:
|
||||
case WordBreakProperty::Double_Quote:
|
||||
case WordBreakProperty::LF:
|
||||
case WordBreakProperty::MidLetter:
|
||||
case WordBreakProperty::MidNum:
|
||||
case WordBreakProperty::MidNumLet:
|
||||
case WordBreakProperty::Newline:
|
||||
case WordBreakProperty::Single_Quote:
|
||||
case WordBreakProperty::WSegSpace:
|
||||
// Unsure:
|
||||
case WordBreakProperty::Extend:
|
||||
case WordBreakProperty::ExtendNumLet:
|
||||
case WordBreakProperty::Format:
|
||||
case WordBreakProperty::Regional_Indicator:
|
||||
case WordBreakProperty::ZWJ:
|
||||
return false;
|
||||
}
|
||||
return true; // NOT_REACHED();
|
||||
}
|
||||
|
||||
std::string PasswordField(size_t size) {
|
||||
std::string out;
|
||||
out.reserve(2 * size);
|
||||
@@ -57,49 +87,51 @@ class InputBase : public ComponentBase {
|
||||
if (option_->password()) {
|
||||
password_content = PasswordField(content_->size());
|
||||
}
|
||||
std::string& content = option_->password() ? password_content : *content_;
|
||||
const std::string& content =
|
||||
option_->password() ? password_content : *content_;
|
||||
|
||||
int size = GlyphCount(content);
|
||||
const int size = GlyphCount(content);
|
||||
|
||||
cursor_position() = std::max(0, std::min<int>(size, cursor_position()));
|
||||
auto main_decorator = flex | ftxui::size(HEIGHT, EQUAL, 1);
|
||||
bool is_focused = Focused();
|
||||
const bool is_focused = Focused();
|
||||
|
||||
// placeholder.
|
||||
if (size == 0) {
|
||||
bool hovered = hovered_;
|
||||
Decorator decorator = dim | main_decorator;
|
||||
auto element = text(*placeholder_) | dim | main_decorator | reflect(box_);
|
||||
if (is_focused) {
|
||||
decorator = decorator | focus;
|
||||
element |= focus;
|
||||
}
|
||||
if (hovered || is_focused) {
|
||||
decorator = decorator | inverted;
|
||||
if (hovered_ || is_focused) {
|
||||
element |= inverted;
|
||||
}
|
||||
return text(*placeholder_) | decorator | reflect(box_);
|
||||
return element;
|
||||
}
|
||||
|
||||
// Not focused.
|
||||
if (!is_focused) {
|
||||
auto element = text(content) | main_decorator | reflect(box_);
|
||||
if (hovered_) {
|
||||
return text(content) | main_decorator | inverted | reflect(box_);
|
||||
} else {
|
||||
return text(content) | main_decorator | reflect(box_);
|
||||
element |= inverted;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
int index_before_cursor = GlyphPosition(content, cursor_position());
|
||||
int index_after_cursor = GlyphPosition(content, 1, index_before_cursor);
|
||||
std::string part_before_cursor = content.substr(0, index_before_cursor);
|
||||
const int index_before_cursor = GlyphPosition(content, cursor_position());
|
||||
const int index_after_cursor =
|
||||
GlyphPosition(content, 1, index_before_cursor);
|
||||
const std::string part_before_cursor =
|
||||
content.substr(0, index_before_cursor);
|
||||
std::string part_at_cursor = " ";
|
||||
if (cursor_position() < size) {
|
||||
part_at_cursor = content.substr(index_before_cursor,
|
||||
index_after_cursor - index_before_cursor);
|
||||
}
|
||||
std::string part_after_cursor = content.substr(index_after_cursor);
|
||||
auto focused = (is_focused || hovered_) ? focus : select;
|
||||
const std::string part_after_cursor = content.substr(index_after_cursor);
|
||||
auto focused = (is_focused || hovered_) ? focusCursorBarBlinking : select;
|
||||
return hbox({
|
||||
text(part_before_cursor),
|
||||
text(part_at_cursor) | focused | inverted | reflect(cursor_box_),
|
||||
text(part_at_cursor) | focused | reflect(cursor_box_),
|
||||
text(part_after_cursor),
|
||||
}) |
|
||||
flex | frame | bold | main_decorator | reflect(box_);
|
||||
@@ -113,15 +145,13 @@ class InputBase : public ComponentBase {
|
||||
return OnMouseEvent(event);
|
||||
}
|
||||
|
||||
std::string c;
|
||||
|
||||
// Backspace.
|
||||
if (event == Event::Backspace) {
|
||||
if (cursor_position() == 0) {
|
||||
return false;
|
||||
}
|
||||
size_t start = GlyphPosition(*content_, cursor_position() - 1);
|
||||
size_t end = GlyphPosition(*content_, cursor_position());
|
||||
const size_t start = GlyphPosition(*content_, cursor_position() - 1);
|
||||
const size_t end = GlyphPosition(*content_, cursor_position());
|
||||
content_->erase(start, end - start);
|
||||
cursor_position()--;
|
||||
option_->on_change();
|
||||
@@ -133,8 +163,8 @@ class InputBase : public ComponentBase {
|
||||
if (cursor_position() == int(content_->size())) {
|
||||
return false;
|
||||
}
|
||||
size_t start = GlyphPosition(*content_, cursor_position());
|
||||
size_t end = GlyphPosition(*content_, cursor_position() + 1);
|
||||
const size_t start = GlyphPosition(*content_, cursor_position());
|
||||
const size_t end = GlyphPosition(*content_, cursor_position() + 1);
|
||||
content_->erase(start, end - start);
|
||||
option_->on_change();
|
||||
return true;
|
||||
@@ -150,6 +180,7 @@ class InputBase : public ComponentBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Arrow
|
||||
if (event == Event::ArrowLeft && cursor_position() > 0) {
|
||||
cursor_position()--;
|
||||
return true;
|
||||
@@ -161,6 +192,16 @@ class InputBase : public ComponentBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
// CTRL + Arrow:
|
||||
if (event == Event::ArrowLeftCtrl) {
|
||||
HandleLeftCtrl();
|
||||
return true;
|
||||
}
|
||||
if (event == Event::ArrowRightCtrl) {
|
||||
HandleRightCtrl();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event == Event::Home) {
|
||||
cursor_position() = 0;
|
||||
return true;
|
||||
@@ -173,7 +214,7 @@ class InputBase : public ComponentBase {
|
||||
|
||||
// Content
|
||||
if (event.is_character()) {
|
||||
size_t start = GlyphPosition(*content_, cursor_position());
|
||||
const size_t start = GlyphPosition(*content_, cursor_position());
|
||||
content_->insert(start, event.character());
|
||||
cursor_position()++;
|
||||
option_->on_change();
|
||||
@@ -183,6 +224,39 @@ class InputBase : public ComponentBase {
|
||||
}
|
||||
|
||||
private:
|
||||
void HandleLeftCtrl() {
|
||||
auto properties = Utf8ToWordBreakProperty(*content_);
|
||||
|
||||
// Move left, as long as left is not a word character.
|
||||
while (cursor_position() > 0 &&
|
||||
!IsWordCharacter(properties[cursor_position() - 1])) {
|
||||
cursor_position()--;
|
||||
}
|
||||
|
||||
// Move left, as long as left is a word character:
|
||||
while (cursor_position() > 0 &&
|
||||
IsWordCharacter(properties[cursor_position() - 1])) {
|
||||
cursor_position()--;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleRightCtrl() {
|
||||
auto properties = Utf8ToWordBreakProperty(*content_);
|
||||
const int max = (int)properties.size();
|
||||
|
||||
// Move right, as long as right is not a word character.
|
||||
while (cursor_position() < max &&
|
||||
!IsWordCharacter(properties[cursor_position()])) {
|
||||
cursor_position()++;
|
||||
}
|
||||
|
||||
// Move right, as long as right is a word character:
|
||||
while (cursor_position() < max &&
|
||||
IsWordCharacter(properties[cursor_position()])) {
|
||||
cursor_position()++;
|
||||
}
|
||||
}
|
||||
|
||||
bool OnMouseEvent(Event event) {
|
||||
hovered_ =
|
||||
box_.Contain(event.mouse().x, event.mouse().y) && CaptureMouse(event);
|
||||
@@ -213,7 +287,8 @@ class InputBase : public ComponentBase {
|
||||
if (mapping[original_cell] != original_glyph) {
|
||||
original_cell = mapping.size();
|
||||
}
|
||||
int target_cell = int(original_cell) + event.mouse().x - cursor_box_.x_min;
|
||||
const int target_cell =
|
||||
int(original_cell) + event.mouse().x - cursor_box_.x_min;
|
||||
int target_glyph = target_cell < (int)mapping.size() ? mapping[target_cell]
|
||||
: (int)mapping.size();
|
||||
target_glyph = util::clamp(target_glyph, 0, GlyphCount(*content_));
|
||||
|
@@ -1,5 +1,4 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
#include <string> // for string
|
||||
|
||||
@@ -12,7 +11,6 @@
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/screen.hpp" // for Fixed, Screen, Pixel
|
||||
#include "ftxui/util/ref.hpp" // for Ref
|
||||
#include "gtest/gtest_pred_impl.h" // for EXPECT_EQ, Test, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
@@ -371,6 +369,124 @@ TEST(InputTest, MouseClickComplex) {
|
||||
EXPECT_EQ(option.cursor_position(), 4u);
|
||||
}
|
||||
|
||||
TEST(InputTest, CtrlArrowLeft) {
|
||||
std::string content = "word word 测ord wo测d word";
|
||||
// 0 5 10 15 20
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
option.cursor_position = 22;
|
||||
auto input = Input(&content, &placeholder, &option);
|
||||
|
||||
// Use CTRL+Left several time
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 20u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 15u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 10u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 5u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
}
|
||||
|
||||
TEST(InputTest, CtrlArrowLeft2) {
|
||||
std::string content = " word word 测ord wo测d word ";
|
||||
// 0 3 6 9 12 15 18 21 24 27 30 33
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
option.cursor_position = 33;
|
||||
auto input = Input(&content, &placeholder, &option);
|
||||
|
||||
// Use CTRL+Left several time
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 27u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 21u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 15u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 9u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 3u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
}
|
||||
|
||||
TEST(InputTest, CtrlArrowRight) {
|
||||
std::string content = "word word 测ord wo测d word";
|
||||
// 0 5 10 15 20
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
option.cursor_position = 2;
|
||||
auto input = Input(&content, &placeholder, &option);
|
||||
|
||||
// Use CTRL+Left several time
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 4);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 9);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 14u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 19u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 24u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 24u);
|
||||
}
|
||||
|
||||
TEST(InputTest, CtrlArrowRight2) {
|
||||
std::string content = " word word 测ord wo测d word ";
|
||||
// 0 3 6 9 12 15 18 21 24 27 30 33
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
option.cursor_position = 0;
|
||||
auto input = Input(&content, &placeholder, &option);
|
||||
|
||||
// Use CTRL+Left several time
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 7u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 13u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 19u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 25u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 31u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 34u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 34u);
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
||||
|
48
src/ftxui/component/loop.cpp
Normal file
48
src/ftxui/component/loop.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "ftxui/component/loop.hpp"
|
||||
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive, Component
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
Loop::Loop(ScreenInteractive* screen, Component component)
|
||||
: screen_(screen), component_(std::move(component)) {
|
||||
screen_->PreMain();
|
||||
}
|
||||
|
||||
Loop::~Loop() {
|
||||
screen_->PostMain();
|
||||
}
|
||||
|
||||
bool Loop::HasQuitted() {
|
||||
return screen_->HasQuitted();
|
||||
}
|
||||
|
||||
/// @brief Execute the loop. Make the `component` to process every pending
|
||||
/// tasks/events. A new frame might be drawn if the previous was invalidated.
|
||||
/// Return true until the loop hasn't completed.
|
||||
void Loop::RunOnce() {
|
||||
screen_->RunOnce(component_);
|
||||
}
|
||||
|
||||
/// @brief Wait for at least one event to be handled and execute
|
||||
/// `Loop::RunOnce()`.
|
||||
void Loop::RunOnceBlocking() {
|
||||
screen_->RunOnceBlocking(component_);
|
||||
}
|
||||
|
||||
/// Execute the loop, blocking the current thread, up until the loop has
|
||||
/// quitted.
|
||||
void Loop::Run() {
|
||||
while (!HasQuitted()) {
|
||||
RunOnceBlocking();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2022 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
@@ -1,5 +1,6 @@
|
||||
#include <functional> // for function
|
||||
#include <memory> // for make_unique, __shared_ptr_access, __shared_ptr_access<>::element_type, shared_ptr
|
||||
#include <type_traits> // for remove_reference, remove_reference<>::type
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/component.hpp" // for ComponentDecorator, Maybe, Make
|
||||
@@ -43,7 +44,7 @@ Component Maybe(Component child, std::function<bool()> show) {
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto component = Renderer([]{ return "Hello World!"; });
|
||||
/// auto component = Renderer([]{ return text("Hello World!"); });
|
||||
/// auto maybe_component = component | Maybe([&]{ return counter == 42; });
|
||||
/// ```
|
||||
ComponentDecorator Maybe(std::function<bool()> show) {
|
||||
@@ -60,7 +61,7 @@ ComponentDecorator Maybe(std::function<bool()> show) {
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto component = Renderer([]{ return "Hello World!"; });
|
||||
/// auto component = Renderer([]{ return text("Hello World!"); });
|
||||
/// auto maybe_component = Maybe(component, &show);
|
||||
/// ```
|
||||
Component Maybe(Component child, const bool* show) {
|
||||
@@ -74,7 +75,7 @@ Component Maybe(Component child, const bool* show) {
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto component = Renderer([]{ return "Hello World!"; });
|
||||
/// auto component = Renderer([]{ return text("Hello World!"); });
|
||||
/// auto maybe_component = component | Maybe(&show);
|
||||
/// ```
|
||||
ComponentDecorator Maybe(const bool* show) {
|
||||
|
@@ -1,16 +1,16 @@
|
||||
#include <algorithm> // for max, reverse
|
||||
#include <algorithm> // for max, fill_n, reverse
|
||||
#include <chrono> // for milliseconds
|
||||
#include <functional> // for function
|
||||
#include <memory> // for allocator, shared_ptr, allocator_traits<>::value_type, swap
|
||||
#include <string> // for char_traits, operator+, string, basic_string
|
||||
#include <memory> // for allocator_traits<>::value_type, swap
|
||||
#include <string> // for operator+, string
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector, __alloc_traits<>::value_type
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for Animator, Linear, Params (ptr only)
|
||||
#include "ftxui/component/animation.hpp" // for Animator, Linear
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/component.hpp" // for Make, Menu, MenuEntry, Toggle
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption, MenuOption::Direction, UnderlineOption, AnimatedColorOption, AnimatedColorsOption, MenuOption::Down, MenuOption::Left, MenuOption::Right, MenuOption::Up
|
||||
#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption, MenuOption::Direction, UnderlineOption, AnimatedColorOption, AnimatedColorsOption, EntryState, MenuOption::Down, MenuOption::Left, MenuOption::Right, MenuOption::Up
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp, Event::Return, Event::Tab, Event::TabReverse
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Released, Mouse::WheelDown, Mouse::WheelUp, Mouse::None
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component
|
||||
@@ -83,8 +83,13 @@ class MenuBase : public ComponentBase {
|
||||
}
|
||||
|
||||
void Clamp() {
|
||||
if (*selected_ != selected_previous_) {
|
||||
SelectedTakeFocus();
|
||||
}
|
||||
boxes_.resize(size());
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
selected_previous_ = util::clamp(selected_previous_, 0, size() - 1);
|
||||
selected_focus_ = util::clamp(selected_focus_, 0, size() - 1);
|
||||
focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
|
||||
}
|
||||
|
||||
@@ -104,7 +109,7 @@ class MenuBase : public ComponentBase {
|
||||
UpdateAnimationTarget();
|
||||
|
||||
Elements elements;
|
||||
bool is_menu_focused = Focused();
|
||||
const bool is_menu_focused = Focused();
|
||||
if (option_->elements_prefix) {
|
||||
elements.push_back(option_->elements_prefix());
|
||||
}
|
||||
@@ -112,20 +117,20 @@ class MenuBase : public ComponentBase {
|
||||
if (i != 0 && option_->elements_infix) {
|
||||
elements.push_back(option_->elements_infix());
|
||||
}
|
||||
bool is_focused = (focused_entry() == i) && is_menu_focused;
|
||||
bool is_selected = (*selected_ == i);
|
||||
const bool is_focused = (focused_entry() == i) && is_menu_focused;
|
||||
const bool is_selected = (*selected_ == i);
|
||||
|
||||
auto focus_management = !is_selected ? nothing
|
||||
: is_menu_focused ? focus
|
||||
: nothing;
|
||||
EntryState state = {
|
||||
const EntryState state = {
|
||||
entries_[i],
|
||||
false,
|
||||
is_selected,
|
||||
is_focused,
|
||||
};
|
||||
|
||||
Element element =
|
||||
auto focus_management =
|
||||
is_menu_focused && (selected_focus_ == i) ? focus : nothing;
|
||||
|
||||
const Element element =
|
||||
(option_->entries.transform ? option_->entries.transform
|
||||
: DefaultOptionTransform) //
|
||||
(state);
|
||||
@@ -140,7 +145,7 @@ class MenuBase : public ComponentBase {
|
||||
std::reverse(elements.begin(), elements.end());
|
||||
}
|
||||
|
||||
Element bar =
|
||||
const Element bar =
|
||||
IsHorizontal() ? hbox(std::move(elements)) : vbox(std::move(elements));
|
||||
|
||||
if (!option_->underline.enabled) {
|
||||
@@ -166,6 +171,11 @@ class MenuBase : public ComponentBase {
|
||||
}
|
||||
}
|
||||
|
||||
void SelectedTakeFocus() {
|
||||
selected_previous_ = *selected_;
|
||||
selected_focus_ = *selected_;
|
||||
}
|
||||
|
||||
void OnUp() {
|
||||
switch (option_->direction) {
|
||||
case MenuOption::Direction::Up:
|
||||
@@ -234,7 +244,7 @@ class MenuBase : public ComponentBase {
|
||||
}
|
||||
|
||||
if (Focused()) {
|
||||
int old_selected = *selected_;
|
||||
const int old_selected = *selected_;
|
||||
if (event == Event::ArrowUp || event == Event::Character('k')) {
|
||||
OnUp();
|
||||
}
|
||||
@@ -270,6 +280,7 @@ class MenuBase : public ComponentBase {
|
||||
|
||||
if (*selected_ != old_selected) {
|
||||
focused_entry() = *selected_;
|
||||
SelectedTakeFocus();
|
||||
OnChange();
|
||||
return true;
|
||||
}
|
||||
@@ -307,6 +318,7 @@ class MenuBase : public ComponentBase {
|
||||
event.mouse().motion == Mouse::Released) {
|
||||
if (*selected_ != i) {
|
||||
*selected_ = i;
|
||||
selected_previous_ = *selected_;
|
||||
OnChange();
|
||||
}
|
||||
return true;
|
||||
@@ -319,7 +331,7 @@ class MenuBase : public ComponentBase {
|
||||
if (!box_.Contain(event.mouse().x, event.mouse().y)) {
|
||||
return false;
|
||||
}
|
||||
int old_selected = *selected_;
|
||||
const int old_selected = *selected_;
|
||||
|
||||
if (event.mouse().button == Mouse::WheelUp) {
|
||||
(*selected_)--;
|
||||
@@ -331,6 +343,7 @@ class MenuBase : public ComponentBase {
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
|
||||
if (*selected_ != old_selected) {
|
||||
SelectedTakeFocus();
|
||||
OnChange();
|
||||
}
|
||||
return true;
|
||||
@@ -360,10 +373,10 @@ class MenuBase : public ComponentBase {
|
||||
}
|
||||
}
|
||||
|
||||
bool is_menu_focused = Focused();
|
||||
const bool is_menu_focused = Focused();
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
bool is_focused = (focused_entry() == i) && is_menu_focused;
|
||||
bool is_selected = (*selected_ == i);
|
||||
const bool is_focused = (focused_entry() == i) && is_menu_focused;
|
||||
const bool is_selected = (*selected_ == i);
|
||||
float target = is_selected ? 1.F : is_focused ? 0.5F : 0.F; // NOLINT
|
||||
if (animator_background_[i].to() != target) {
|
||||
animator_background_[i] = animation::Animator(
|
||||
@@ -434,7 +447,7 @@ class MenuBase : public ComponentBase {
|
||||
if (boxes_.empty()) {
|
||||
return 0.F;
|
||||
}
|
||||
int value = IsHorizontal() ? boxes_[*selected_].x_min - box_.x_min
|
||||
const int value = IsHorizontal() ? boxes_[*selected_].x_min - box_.x_min
|
||||
: boxes_[*selected_].y_min - box_.y_min;
|
||||
return float(value);
|
||||
}
|
||||
@@ -442,14 +455,16 @@ class MenuBase : public ComponentBase {
|
||||
if (boxes_.empty()) {
|
||||
return 0.F;
|
||||
}
|
||||
int value = IsHorizontal() ? boxes_[*selected_].x_max - box_.x_min
|
||||
const int value = IsHorizontal() ? boxes_[*selected_].x_max - box_.x_min
|
||||
: boxes_[*selected_].y_max - box_.y_min;
|
||||
return float(value);
|
||||
}
|
||||
|
||||
protected:
|
||||
ConstStringListRef entries_;
|
||||
int* selected_ = nullptr;
|
||||
int* selected_;
|
||||
int selected_previous_ = *selected_;
|
||||
int selected_focus_ = *selected_;
|
||||
Ref<MenuOption> option_;
|
||||
|
||||
std::vector<Box> boxes_;
|
||||
@@ -542,17 +557,17 @@ Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
|
||||
|
||||
private:
|
||||
Element Render() override {
|
||||
bool focused = Focused();
|
||||
const bool focused = Focused();
|
||||
UpdateAnimationTarget();
|
||||
|
||||
EntryState state = {
|
||||
const EntryState state = {
|
||||
*label_,
|
||||
false,
|
||||
hovered_,
|
||||
focused,
|
||||
};
|
||||
|
||||
Element element =
|
||||
const Element element =
|
||||
(option_->transform ? option_->transform : DefaultOptionTransform) //
|
||||
(state);
|
||||
|
||||
@@ -561,7 +576,7 @@ Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
|
||||
}
|
||||
|
||||
void UpdateAnimationTarget() {
|
||||
bool focused = Focused();
|
||||
const bool focused = Focused();
|
||||
float target = focused ? 1.F : hovered_ ? 0.5F : 0.F; // NOLINT
|
||||
if (target == animator_background_.to()) {
|
||||
return;
|
||||
|
@@ -1,8 +1,7 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <gtest/gtest.h> // for Test, EXPECT_EQ, Message, TestPartResult, TestInfo (ptr only), TEST
|
||||
#include <chrono> // for operator""s, chrono_literals
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
#include <string> // for string
|
||||
#include <string> // for string, basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for Duration, Params
|
||||
@@ -13,7 +12,6 @@
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/screen.hpp" // for Screen
|
||||
#include "ftxui/util/ref.hpp" // for Ref
|
||||
#include "gtest/gtest_pred_impl.h" // for EXPECT_EQ, Test, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
65
src/ftxui/component/modal.cpp
Normal file
65
src/ftxui/component/modal.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include <ftxui/component/event.hpp> // for Event
|
||||
#include <ftxui/dom/elements.hpp> // for operator|, Element, center, clear_under, dbox
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Make, Tab, ComponentDecorator, Modal
|
||||
#include "ftxui/component/component_base.hpp" // for Component, ComponentBase
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
// Add a |modal| window on top of the |main| component. It is shown one on the
|
||||
// top of the other when |show_modal| is true.
|
||||
/// @ingroup component
|
||||
// NOLINTNEXTLINE
|
||||
Component Modal(Component main, Component modal, const bool* show_modal) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
explicit Impl(Component main, Component modal, const bool* show_modal)
|
||||
: main_(std::move(main)),
|
||||
modal_(std::move(modal)),
|
||||
show_modal_(show_modal) {
|
||||
Add(Container::Tab({main_, modal_}, &selector_));
|
||||
}
|
||||
|
||||
private:
|
||||
Element Render() override {
|
||||
selector_ = *show_modal_;
|
||||
auto document = main_->Render();
|
||||
if (*show_modal_) {
|
||||
document = dbox({
|
||||
document,
|
||||
modal_->Render() | clear_under | center,
|
||||
});
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
selector_ = *show_modal_;
|
||||
return ComponentBase::OnEvent(event);
|
||||
}
|
||||
|
||||
Component main_;
|
||||
Component modal_;
|
||||
const bool* show_modal_;
|
||||
int selector_ = *show_modal_;
|
||||
};
|
||||
return Make<Impl>(main, modal, show_modal);
|
||||
}
|
||||
|
||||
// Decorate a component. Add a |modal| window on top of it. It is shown one on
|
||||
// the top of the other when |show_modal| is true.
|
||||
/// @ingroup component
|
||||
// NOLINTNEXTLINE
|
||||
ComponentDecorator Modal(Component modal, const bool* show_modal) {
|
||||
return [modal, show_modal](Component main) {
|
||||
return Modal(std::move(main), modal, show_modal);
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2022 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
45
src/ftxui/component/modal_test.cpp
Normal file
45
src/ftxui/component/modal_test.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <ftxui/dom/elements.hpp> // for Element, operator|, text, border
|
||||
#include <memory> // for shared_ptr, allocator, __shared_ptr_access
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Renderer, Modal
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/screen.hpp" // for Screen
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
TEST(ModalTest, Basic) {
|
||||
auto main = Renderer([] { return text("main") | border; });
|
||||
auto modal = Renderer([] { return text("modal") | border; });
|
||||
bool show_modal = false;
|
||||
auto component = Modal(main, modal, &show_modal);
|
||||
|
||||
Screen screen(10, 7);
|
||||
Render(screen, component->Render());
|
||||
EXPECT_EQ(screen.ToString(),
|
||||
"╭────────╮\r\n"
|
||||
"│main │\r\n"
|
||||
"│ │\r\n"
|
||||
"│ │\r\n"
|
||||
"│ │\r\n"
|
||||
"│ │\r\n"
|
||||
"╰────────╯");
|
||||
|
||||
show_modal = true;
|
||||
Render(screen, component->Render());
|
||||
EXPECT_EQ(screen.ToString(),
|
||||
"╭────────╮\r\n"
|
||||
"│main │\r\n"
|
||||
"│╭─────╮ │\r\n"
|
||||
"││modal│ │\r\n"
|
||||
"│╰─────╯ │\r\n"
|
||||
"│ │\r\n"
|
||||
"╰────────╯");
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2022 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
@@ -1,6 +1,5 @@
|
||||
#include <algorithm> // for max
|
||||
#include <functional> // for function
|
||||
#include <memory> // for shared_ptr, allocator_traits<>::value_type
|
||||
#include <memory> // for allocator_traits<>::value_type
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
@@ -27,18 +26,16 @@ class RadioboxBase : public ComponentBase {
|
||||
RadioboxBase(ConstStringListRef entries,
|
||||
int* selected,
|
||||
Ref<RadioboxOption> option)
|
||||
: entries_(entries), selected_(selected), option_(std::move(option)) {
|
||||
hovered_ = *selected_;
|
||||
}
|
||||
: entries_(entries), selected_(selected), option_(std::move(option)) {}
|
||||
|
||||
private:
|
||||
Element Render() override {
|
||||
Clamp();
|
||||
Elements elements;
|
||||
bool is_menu_focused = Focused();
|
||||
const bool is_menu_focused = Focused();
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
bool is_focused = (focused_entry() == i) && is_menu_focused;
|
||||
bool is_selected = (hovered_ == i);
|
||||
const bool is_focused = (focused_entry() == i) && is_menu_focused;
|
||||
const bool is_selected = (hovered_ == i);
|
||||
auto focus_management = !is_selected ? nothing
|
||||
: is_menu_focused ? focus
|
||||
: select;
|
||||
@@ -69,7 +66,7 @@ class RadioboxBase : public ComponentBase {
|
||||
}
|
||||
|
||||
if (Focused()) {
|
||||
int old_hovered = hovered_;
|
||||
const int old_hovered = hovered_;
|
||||
if (event == Event::ArrowUp || event == Event::Character('k')) {
|
||||
(hovered_)--;
|
||||
}
|
||||
@@ -106,8 +103,8 @@ class RadioboxBase : public ComponentBase {
|
||||
|
||||
if (event == Event::Character(' ') || event == Event::Return) {
|
||||
*selected_ = hovered_;
|
||||
//*selected_ = focused_entry();
|
||||
option_->on_change();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -144,7 +141,7 @@ class RadioboxBase : public ComponentBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
int old_hovered = hovered_;
|
||||
const int old_hovered = hovered_;
|
||||
|
||||
if (event.mouse().button == Mouse::WheelUp) {
|
||||
(hovered_)--;
|
||||
@@ -175,7 +172,7 @@ class RadioboxBase : public ComponentBase {
|
||||
|
||||
ConstStringListRef entries_;
|
||||
int* selected_;
|
||||
int hovered_;
|
||||
int hovered_ = *selected_;
|
||||
std::vector<Box> boxes_;
|
||||
Box box_;
|
||||
Ref<RadioboxOption> option_;
|
||||
|
@@ -1,118 +1,276 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, EXPECT_EQ, EXPECT_TRUE, Test, TestInfo (ptr only), EXPECT_FALSE, TEST
|
||||
#include <ftxui/dom/elements.hpp> // for yframe
|
||||
#include <ftxui/dom/node.hpp> // for Render
|
||||
#include <ftxui/screen/screen.hpp> // for Screen
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
#include <string> // for string
|
||||
#include <string> // for string, basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Radiobox
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component.hpp" // for Radiobox, operator|
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||
#include "ftxui/component/component_options.hpp" // for RadioboxOption
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::ArrowUp, Event::Tab, Event::TabReverse
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::End, Event::Home, Event::Tab, Event::TabReverse, Event::PageDown, Event::PageUp, Event::ArrowUp
|
||||
#include "ftxui/util/ref.hpp" // for Ref
|
||||
#include "gtest/gtest_pred_impl.h" // for EXPECT_EQ, Test, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
TEST(RadioboxTest, Navigation) {
|
||||
TEST(RadioboxTest, NavigationArrow) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
auto radiobox = Radiobox(&entries, &selected);
|
||||
|
||||
// With arrow key.
|
||||
// Down + Return
|
||||
EXPECT_EQ(selected, 0);
|
||||
radiobox->OnEvent(Event::ArrowDown);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 1);
|
||||
radiobox->OnEvent(Event::ArrowDown);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 2);
|
||||
radiobox->OnEvent(Event::ArrowDown);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 2);
|
||||
radiobox->OnEvent(Event::ArrowUp);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 1);
|
||||
radiobox->OnEvent(Event::ArrowUp);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(selected, 0);
|
||||
radiobox->OnEvent(Event::ArrowUp);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
|
||||
// Up + Return
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 0);
|
||||
}
|
||||
|
||||
TEST(RadioboxTest, NavigationArrowVim) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
auto radiobox = Radiobox(&entries, &selected);
|
||||
|
||||
// J + Return
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Character('j')));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Character('j')));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::Character('j')));
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
|
||||
// K + Return
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Character('k')));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Character('k')));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::Character('k')));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 0);
|
||||
}
|
||||
|
||||
TEST(RadioboxTest, NavigationTab) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
auto radiobox = Radiobox(&entries, &selected);
|
||||
|
||||
// Tab + Return
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Tab));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Tab));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Tab));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Tab));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Tab));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
|
||||
// TabReverse + Return
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::TabReverse));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::TabReverse));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::TabReverse));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::TabReverse));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::TabReverse));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
}
|
||||
|
||||
TEST(RadioboxTest, NavigationHome) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
auto radiobox = Radiobox(&entries, &selected);
|
||||
|
||||
selected = 0;
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::Home));
|
||||
EXPECT_EQ(selected, 0);
|
||||
|
||||
// With vim like characters.
|
||||
EXPECT_EQ(selected, 0);
|
||||
radiobox->OnEvent(Event::Character('j'));
|
||||
radiobox->OnEvent(Event::Return);
|
||||
selected = 1;
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::Home));
|
||||
EXPECT_EQ(selected, 1);
|
||||
radiobox->OnEvent(Event::Character('j'));
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 2);
|
||||
radiobox->OnEvent(Event::Character('j'));
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 2);
|
||||
radiobox->OnEvent(Event::Character('k'));
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 1);
|
||||
radiobox->OnEvent(Event::Character('k'));
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 0);
|
||||
radiobox->OnEvent(Event::Character('k'));
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::Home));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 0);
|
||||
|
||||
// With more entries
|
||||
entries = {"1", "2", "3"};
|
||||
EXPECT_EQ(selected, 0);
|
||||
radiobox->OnEvent(Event::ArrowDown);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 1);
|
||||
radiobox->OnEvent(Event::ArrowDown);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
selected = 2;
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::Home));
|
||||
EXPECT_EQ(selected, 2);
|
||||
radiobox->OnEvent(Event::ArrowDown);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 2);
|
||||
radiobox->OnEvent(Event::ArrowUp);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 1);
|
||||
radiobox->OnEvent(Event::ArrowUp);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 0);
|
||||
radiobox->OnEvent(Event::ArrowUp);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::Home));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 0);
|
||||
}
|
||||
|
||||
TEST(RadioboxTest, NavigationPageDown) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3", "4", "5", "6"};
|
||||
auto radiobox = Radiobox(&entries, &selected) | yframe;
|
||||
|
||||
Screen screen(1, 3);
|
||||
Render(screen, radiobox->Render());
|
||||
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::PageDown));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::PageDown));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 4);
|
||||
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::PageDown));
|
||||
EXPECT_EQ(selected, 4);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 5);
|
||||
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::PageDown));
|
||||
EXPECT_EQ(selected, 5);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 5);
|
||||
}
|
||||
|
||||
TEST(RadioboxTest, NavigationPageUp) {
|
||||
int selected = 5;
|
||||
std::vector<std::string> entries = {"1", "2", "3", "4", "5", "6"};
|
||||
auto radiobox = Radiobox(&entries, &selected) | yframe;
|
||||
|
||||
Screen screen(1, 3);
|
||||
Render(screen, radiobox->Render());
|
||||
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::PageUp));
|
||||
EXPECT_EQ(selected, 5);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 3);
|
||||
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::PageUp));
|
||||
EXPECT_EQ(selected, 3);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 1);
|
||||
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::PageUp));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 0);
|
||||
|
||||
// With tab.
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::PageUp));
|
||||
EXPECT_EQ(selected, 0);
|
||||
radiobox->OnEvent(Event::Tab);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 1);
|
||||
radiobox->OnEvent(Event::Tab);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 2);
|
||||
radiobox->OnEvent(Event::Tab);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 0);
|
||||
radiobox->OnEvent(Event::Tab);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 1);
|
||||
radiobox->OnEvent(Event::Tab);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 2);
|
||||
radiobox->OnEvent(Event::TabReverse);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_EQ(selected, 1);
|
||||
radiobox->OnEvent(Event::TabReverse);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
}
|
||||
|
||||
TEST(RadioboxTest, NavigationEnd) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
auto radiobox = Radiobox(&entries, &selected);
|
||||
|
||||
selected = 0;
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::End));
|
||||
EXPECT_EQ(selected, 0);
|
||||
radiobox->OnEvent(Event::TabReverse);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
radiobox->OnEvent(Event::TabReverse);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::End));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
|
||||
selected = 1;
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::End));
|
||||
EXPECT_EQ(selected, 1);
|
||||
radiobox->OnEvent(Event::TabReverse);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::End));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
|
||||
selected = 2;
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::End));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
EXPECT_EQ(selected, 2);
|
||||
}
|
||||
|
||||
TEST(RadioboxTest, EventSpace) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
auto radiobox = Radiobox(&entries, &selected);
|
||||
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Character(' ')));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Character(' ')));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_FALSE(radiobox->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Character(' ')));
|
||||
EXPECT_EQ(selected, 2);
|
||||
}
|
||||
|
||||
TEST(RadioboxTest, RemoveEntries) {
|
||||
@@ -126,9 +284,9 @@ TEST(RadioboxTest, RemoveEntries) {
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_EQ(focused_entry, 0);
|
||||
|
||||
radiobox->OnEvent(Event::ArrowDown);
|
||||
radiobox->OnEvent(Event::ArrowDown);
|
||||
radiobox->OnEvent(Event::Return);
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::ArrowDown));
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::ArrowDown));
|
||||
EXPECT_TRUE(radiobox->OnEvent(Event::Return));
|
||||
|
||||
EXPECT_EQ(selected, 2);
|
||||
EXPECT_EQ(focused_entry, 2);
|
||||
|
@@ -1,10 +1,8 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult
|
||||
#include <gtest/gtest.h>
|
||||
#include <thread> // for thread
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/receiver.hpp"
|
||||
#include "gtest/gtest_pred_impl.h" // for AssertionResult, Test, EXPECT_EQ
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop
|
||||
@@ -9,7 +8,6 @@
|
||||
#include "ftxui/dom/elements.hpp" // for text, Element
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/screen.hpp" // for Screen
|
||||
#include "gtest/gtest_pred_impl.h" // for AssertionResult, Test, EXPECT_EQ, EXPECT_TRUE, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
@@ -1,28 +1,32 @@
|
||||
#include <algorithm> // for copy, max, min
|
||||
#include <array> // for array
|
||||
#include <chrono> // for operator-, milliseconds, duration, operator>=, time_point, common_type<>::type
|
||||
#include <csignal> // for signal, raise, SIGTSTP, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, SIGWINCH
|
||||
#include <cstdio> // for fileno, size_t, stdin
|
||||
#include <chrono> // for operator-, milliseconds, operator>=, duration, common_type<>::type, time_point
|
||||
#include <csignal> // for signal, SIGTSTP, SIGABRT, SIGWINCH, raise, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, __sighandler_t, size_t
|
||||
#include <cstdio> // for fileno, stdin
|
||||
#include <ftxui/component/task.hpp> // for Task, Closure, AnimationTask
|
||||
#include <ftxui/screen/screen.hpp> // for Pixel, Screen::Cursor, Screen, Screen::Cursor::Hidden
|
||||
#include <functional> // for function
|
||||
#include <initializer_list> // for initializer_list
|
||||
#include <iostream> // for cout, ostream, basic_ostream, operator<<, endl, flush
|
||||
#include <iostream> // for cout, ostream, operator<<, basic_ostream, endl, flush
|
||||
#include <stack> // for stack
|
||||
#include <thread> // for thread, sleep_for
|
||||
#include <tuple> // for _Swallow_assign, ignore
|
||||
#include <type_traits> // for decay_t
|
||||
#include <utility> // for move, swap
|
||||
#include <variant> // for visit
|
||||
#include <variant> // for visit, variant
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for TimePoint, Clock, Duration, Params, RequestAnimationFrame
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/event.hpp" // for Event
|
||||
#include "ftxui/component/loop.hpp" // for Loop
|
||||
#include "ftxui/component/receiver.hpp" // for ReceiverImpl, Sender, MakeReceiver, SenderImpl, Receiver
|
||||
#include "ftxui/component/screen_interactive.hpp"
|
||||
#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
|
||||
#include "ftxui/dom/node.hpp" // for Node, Render
|
||||
#include "ftxui/dom/requirement.hpp" // for Requirement
|
||||
#include "ftxui/screen/terminal.hpp" // for Size, Dimensions
|
||||
#include "ftxui/screen/terminal.hpp" // for Dimensions, Size
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define DEFINE_CONSOLEV2_PROPERTIES
|
||||
@@ -30,12 +34,12 @@
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
#include <windows.h>
|
||||
#ifndef UNICODE
|
||||
#error Must be compiled in UNICODE mode
|
||||
#endif
|
||||
#else
|
||||
#include <sys/select.h> // for select, FD_ISSET, FD_SET, FD_ZERO, fd_set
|
||||
#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 <unistd.h> // for STDIN_FILENO, read
|
||||
#endif
|
||||
@@ -100,7 +104,11 @@ void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
// ignore UP key events
|
||||
if (key_event.bKeyDown == FALSE)
|
||||
continue;
|
||||
parser.Add((char)key_event.uChar.UnicodeChar);
|
||||
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}));
|
||||
@@ -133,8 +141,18 @@ void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
#include <sys/time.h> // for timeval
|
||||
extern "C" {
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void ftxui_on_resize(int columns, int rows) {
|
||||
Terminal::SetFallbackSize({
|
||||
columns,
|
||||
rows,
|
||||
});
|
||||
std::raise(SIGWINCH);
|
||||
}
|
||||
}
|
||||
|
||||
#else // POSIX (Linux & Mac)
|
||||
|
||||
int CheckStdinReady(int usec_timeout) {
|
||||
timeval tv = {0, usec_timeout};
|
||||
@@ -163,18 +181,88 @@ void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::stack<Closure> on_exit_functions; // NOLINT
|
||||
void OnExit() {
|
||||
while (!on_exit_functions.empty()) {
|
||||
on_exit_functions.top()();
|
||||
on_exit_functions.pop();
|
||||
}
|
||||
}
|
||||
|
||||
std::atomic<int> g_signal_exit_count = 0; // NOLINT
|
||||
#if !defined(_WIN32)
|
||||
std::atomic<int> g_signal_stop_count = 0; // NOLINT
|
||||
std::atomic<int> g_signal_resize_count = 0; // NOLINT
|
||||
#endif
|
||||
|
||||
// Async signal safe function
|
||||
void RecordSignal(int signal) {
|
||||
switch (signal) {
|
||||
case SIGABRT:
|
||||
case SIGFPE:
|
||||
case SIGILL:
|
||||
case SIGINT:
|
||||
case SIGSEGV:
|
||||
case SIGTERM:
|
||||
g_signal_exit_count++;
|
||||
break;
|
||||
|
||||
#if !defined(_WIN32)
|
||||
case SIGTSTP:
|
||||
g_signal_stop_count++;
|
||||
break;
|
||||
|
||||
case SIGWINCH:
|
||||
g_signal_resize_count++;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ExecuteSignalHandlers() {
|
||||
int signal_exit_count = g_signal_exit_count.exchange(0);
|
||||
while (signal_exit_count--) {
|
||||
ScreenInteractive::Private::Signal(*g_active_screen, SIGABRT);
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
int signal_stop_count = g_signal_stop_count.exchange(0);
|
||||
while (signal_stop_count--) {
|
||||
ScreenInteractive::Private::Signal(*g_active_screen, SIGTSTP);
|
||||
}
|
||||
|
||||
int signal_resize_count = g_signal_resize_count.exchange(0);
|
||||
while (signal_resize_count--) {
|
||||
ScreenInteractive::Private::Signal(*g_active_screen, SIGWINCH);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void InstallSignalHandler(int sig) {
|
||||
auto old_signal_handler = std::signal(sig, RecordSignal);
|
||||
on_exit_functions.push(
|
||||
[=] { std::ignore = std::signal(sig, old_signal_handler); });
|
||||
}
|
||||
|
||||
const std::string CSI = "\x1b["; // NOLINT
|
||||
|
||||
// DEC: Digital Equipment Corporation
|
||||
enum class DECMode {
|
||||
kLineWrap = 7,
|
||||
kMouseX10 = 9,
|
||||
kCursor = 25,
|
||||
|
||||
kMouseX10 = 9,
|
||||
kMouseVt200 = 1000,
|
||||
kMouseVt200Highlight = 1001,
|
||||
|
||||
kMouseBtnEventMouse = 1002,
|
||||
kMouseAnyEvent = 1003,
|
||||
|
||||
kMouseUtf8 = 1005,
|
||||
kMouseSgrExtMode = 1006,
|
||||
kMouseUrxvtMode = 1015,
|
||||
@@ -190,7 +278,7 @@ enum class DSRMode {
|
||||
std::string Serialize(const std::vector<DECMode>& parameters) {
|
||||
bool first = true;
|
||||
std::string out;
|
||||
for (DECMode parameter : parameters) {
|
||||
for (const DECMode parameter : parameters) {
|
||||
if (!first) {
|
||||
out += ";";
|
||||
}
|
||||
@@ -215,30 +303,6 @@ std::string DeviceStatusReport(DSRMode ps) {
|
||||
return CSI + std::to_string(int(ps)) + "n";
|
||||
}
|
||||
|
||||
using SignalHandler = void(int);
|
||||
std::stack<Closure> on_exit_functions; // NOLINT
|
||||
void OnExit(int signal) {
|
||||
(void)signal;
|
||||
while (!on_exit_functions.empty()) {
|
||||
on_exit_functions.top()();
|
||||
on_exit_functions.pop();
|
||||
}
|
||||
}
|
||||
|
||||
const auto install_signal_handler = [](int sig, SignalHandler handler) {
|
||||
auto old_signal_handler = std::signal(sig, handler);
|
||||
on_exit_functions.push([=] { std::signal(sig, old_signal_handler); });
|
||||
};
|
||||
|
||||
Closure g_on_resize = [] {}; // NOLINT
|
||||
void OnResize(int /* signal */) {
|
||||
g_on_resize();
|
||||
}
|
||||
|
||||
void OnSigStop(int /*signal*/) {
|
||||
ScreenInteractive::Private::SigStop(*g_active_screen);
|
||||
}
|
||||
|
||||
class CapturedMouseImpl : public CapturedMouseInterface {
|
||||
public:
|
||||
explicit CapturedMouseImpl(std::function<void(void)> callback)
|
||||
@@ -276,29 +340,54 @@ ScreenInteractive::ScreenInteractive(int dimx,
|
||||
|
||||
// static
|
||||
ScreenInteractive ScreenInteractive::FixedSize(int dimx, int dimy) {
|
||||
return ScreenInteractive(dimx, dimy, Dimension::Fixed, false);
|
||||
return {
|
||||
dimx,
|
||||
dimy,
|
||||
Dimension::Fixed,
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
// static
|
||||
ScreenInteractive ScreenInteractive::Fullscreen() {
|
||||
return ScreenInteractive(0, 0, Dimension::Fullscreen, true);
|
||||
return {
|
||||
0,
|
||||
0,
|
||||
Dimension::Fullscreen,
|
||||
true,
|
||||
};
|
||||
}
|
||||
|
||||
// static
|
||||
ScreenInteractive ScreenInteractive::TerminalOutput() {
|
||||
return ScreenInteractive(0, 0, Dimension::TerminalOutput, false);
|
||||
return {
|
||||
0,
|
||||
0,
|
||||
Dimension::TerminalOutput,
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
// static
|
||||
ScreenInteractive ScreenInteractive::FitComponent() {
|
||||
return ScreenInteractive(0, 0, Dimension::FitComponent, false);
|
||||
return {
|
||||
0,
|
||||
0,
|
||||
Dimension::FitComponent,
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
void ScreenInteractive::Post(Task task) {
|
||||
if (!quit_) {
|
||||
task_sender_->Send(std::move(task));
|
||||
// Task/Events sent toward inactive screen or screen waiting to become
|
||||
// inactive are dropped.
|
||||
if (!task_sender_) {
|
||||
return;
|
||||
}
|
||||
|
||||
task_sender_->Send(std::move(task));
|
||||
}
|
||||
|
||||
void ScreenInteractive::PostEvent(Event event) {
|
||||
Post(event);
|
||||
}
|
||||
@@ -310,8 +399,8 @@ void ScreenInteractive::RequestAnimationFrame() {
|
||||
animation_requested_ = true;
|
||||
auto now = animation::Clock::now();
|
||||
const auto time_histeresis = std::chrono::milliseconds(33);
|
||||
if (now - previous_animation_time >= time_histeresis) {
|
||||
previous_animation_time = now;
|
||||
if (now - previous_animation_time_ >= time_histeresis) {
|
||||
previous_animation_time_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,34 +414,52 @@ CapturedMouse ScreenInteractive::CaptureMouse() {
|
||||
}
|
||||
|
||||
void ScreenInteractive::Loop(Component component) { // NOLINT
|
||||
class Loop loop(this, std::move(component));
|
||||
loop.Run();
|
||||
}
|
||||
|
||||
bool ScreenInteractive::HasQuitted() {
|
||||
return task_receiver_->HasQuitted();
|
||||
}
|
||||
|
||||
void ScreenInteractive::PreMain() {
|
||||
// Suspend previously active screen:
|
||||
if (g_active_screen) {
|
||||
std::swap(suspended_screen_, g_active_screen);
|
||||
std::cout << suspended_screen_->reset_cursor_position
|
||||
<< suspended_screen_->ResetPosition(/*clear=*/true);
|
||||
// Reset cursor position to the top of the screen and clear the screen.
|
||||
suspended_screen_->ResetCursorPosition();
|
||||
std::cout << suspended_screen_->ResetPosition(/*clear=*/true);
|
||||
suspended_screen_->dimx_ = 0;
|
||||
suspended_screen_->dimy_ = 0;
|
||||
|
||||
// Reset dimensions to force drawing the screen again next time:
|
||||
suspended_screen_->Uninstall();
|
||||
}
|
||||
|
||||
// This screen is now active:
|
||||
g_active_screen = this;
|
||||
g_active_screen->Install();
|
||||
g_active_screen->Main(std::move(component));
|
||||
g_active_screen->Uninstall();
|
||||
g_active_screen = nullptr;
|
||||
|
||||
previous_animation_time_ = animation::Clock::now();
|
||||
}
|
||||
|
||||
void ScreenInteractive::PostMain() {
|
||||
// Put cursor position at the end of the drawing.
|
||||
std::cout << reset_cursor_position;
|
||||
ResetCursorPosition();
|
||||
|
||||
g_active_screen = nullptr;
|
||||
|
||||
// Restore suspended screen.
|
||||
if (suspended_screen_) {
|
||||
// Clear screen, and put the cursor at the beginning of the drawing.
|
||||
std::cout << ResetPosition(/*clear=*/true);
|
||||
dimx_ = 0;
|
||||
dimy_ = 0;
|
||||
Uninstall();
|
||||
std::swap(g_active_screen, suspended_screen_);
|
||||
g_active_screen->Install();
|
||||
} else {
|
||||
Uninstall();
|
||||
// On final exit, keep the current drawing and reset cursor position one
|
||||
// line after it.
|
||||
std::cout << std::endl;
|
||||
@@ -376,6 +483,8 @@ ScreenInteractive* ScreenInteractive::Active() {
|
||||
}
|
||||
|
||||
void ScreenInteractive::Install() {
|
||||
frame_valid_ = false;
|
||||
|
||||
// After uninstalling the new configuration, flush it to the terminal to
|
||||
// ensure it is fully applied:
|
||||
on_exit_functions.push([] { Flush(); });
|
||||
@@ -384,11 +493,11 @@ void ScreenInteractive::Install() {
|
||||
|
||||
// Install signal handlers to restore the terminal state on exit. The default
|
||||
// signal handlers are restored on exit.
|
||||
for (int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
|
||||
install_signal_handler(signal, OnExit);
|
||||
for (const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
|
||||
InstallSignalHandler(signal);
|
||||
}
|
||||
|
||||
// Save the old terminal configuration and restore it on exit.
|
||||
// Save the old terminal configuration and restore it on exit.
|
||||
#if defined(_WIN32)
|
||||
// Enable VT processing on stdout and stdin
|
||||
auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
@@ -420,6 +529,10 @@ void ScreenInteractive::Install() {
|
||||
SetConsoleMode(stdin_handle, in_mode);
|
||||
SetConsoleMode(stdout_handle, out_mode);
|
||||
#else
|
||||
for (const int signal : {SIGWINCH, SIGTSTP}) {
|
||||
InstallSignalHandler(signal);
|
||||
}
|
||||
|
||||
struct termios terminal; // NOLINT
|
||||
tcgetattr(STDIN_FILENO, &terminal);
|
||||
on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
|
||||
@@ -434,12 +547,6 @@ void ScreenInteractive::Install() {
|
||||
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
|
||||
|
||||
// Handle resize.
|
||||
g_on_resize = [&] { task_sender_->Send(Event::Special({0})); };
|
||||
install_signal_handler(SIGWINCH, OnResize);
|
||||
|
||||
// Handle SIGTSTP/SIGCONT.
|
||||
install_signal_handler(SIGTSTP, OnSigStop);
|
||||
#endif
|
||||
|
||||
auto enable = [&](const std::vector<DECMode>& parameters) {
|
||||
@@ -458,20 +565,23 @@ void ScreenInteractive::Install() {
|
||||
});
|
||||
}
|
||||
|
||||
on_exit_functions.push([=] {
|
||||
std::cout << "\033[?25h"; // Enable cursor.
|
||||
std::cout << "\033[?1 q"; // Cursor block blinking.
|
||||
});
|
||||
|
||||
disable({
|
||||
DECMode::kCursor,
|
||||
// DECMode::kCursor,
|
||||
DECMode::kLineWrap,
|
||||
});
|
||||
|
||||
enable({
|
||||
// DECMode::kMouseVt200,
|
||||
DECMode::kMouseAnyEvent,
|
||||
DECMode::kMouseUtf8,
|
||||
DECMode::kMouseSgrExtMode,
|
||||
});
|
||||
enable({DECMode::kMouseVt200});
|
||||
enable({DECMode::kMouseAnyEvent});
|
||||
enable({DECMode::kMouseUrxvtMode});
|
||||
enable({DECMode::kMouseSgrExtMode});
|
||||
|
||||
// After installing the new configuration, flush it to the terminal to ensure
|
||||
// it is fully applied:
|
||||
// After installing the new configuration, flush it to the terminal to
|
||||
// ensure it is fully applied:
|
||||
Flush();
|
||||
|
||||
quit_ = false;
|
||||
@@ -483,36 +593,32 @@ void ScreenInteractive::Install() {
|
||||
}
|
||||
|
||||
void ScreenInteractive::Uninstall() {
|
||||
ExitLoopClosure()();
|
||||
ExitNow();
|
||||
event_listener_.join();
|
||||
animation_listener_.join();
|
||||
|
||||
OnExit(0);
|
||||
OnExit();
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
void ScreenInteractive::Main(Component component) {
|
||||
previous_animation_time = animation::Clock::now();
|
||||
|
||||
auto draw = [&] {
|
||||
Draw(component);
|
||||
std::cout << ToString() << set_cursor_position;
|
||||
Flush();
|
||||
Clear();
|
||||
};
|
||||
|
||||
bool attempt_draw = true;
|
||||
while (!quit_) {
|
||||
if (attempt_draw && !task_receiver_->HasPending()) {
|
||||
draw();
|
||||
attempt_draw = false;
|
||||
}
|
||||
|
||||
void ScreenInteractive::RunOnceBlocking(Component component) {
|
||||
ExecuteSignalHandlers();
|
||||
Task task;
|
||||
if (!task_receiver_->Receive(&task)) {
|
||||
break;
|
||||
if (task_receiver_->Receive(&task)) {
|
||||
HandleTask(component, task);
|
||||
}
|
||||
RunOnce(component);
|
||||
}
|
||||
|
||||
void ScreenInteractive::RunOnce(Component component) {
|
||||
Task task;
|
||||
while (task_receiver_->ReceiveNonBlocking(&task)) {
|
||||
HandleTask(component, task);
|
||||
ExecuteSignalHandlers();
|
||||
}
|
||||
Draw(std::move(component));
|
||||
}
|
||||
|
||||
void ScreenInteractive::HandleTask(Component component, Task& task) {
|
||||
// clang-format off
|
||||
std::visit([&](auto&& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
@@ -532,7 +638,7 @@ void ScreenInteractive::Main(Component component) {
|
||||
|
||||
arg.screen_ = this;
|
||||
component->OnEvent(arg);
|
||||
attempt_draw = true;
|
||||
frame_valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -549,50 +655,52 @@ void ScreenInteractive::Main(Component component) {
|
||||
}
|
||||
|
||||
animation_requested_ = false;
|
||||
animation::TimePoint now = animation::Clock::now();
|
||||
animation::Duration delta = now - previous_animation_time;
|
||||
previous_animation_time = now;
|
||||
const animation::TimePoint now = animation::Clock::now();
|
||||
const animation::Duration delta = now - previous_animation_time_;
|
||||
previous_animation_time_ = now;
|
||||
|
||||
animation::Params params(delta);
|
||||
component->OnAnimation(params);
|
||||
attempt_draw = true;
|
||||
frame_valid_ = false;
|
||||
return;
|
||||
}
|
||||
},
|
||||
task);
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
void ScreenInteractive::Draw(Component component) {
|
||||
if (frame_valid_) {
|
||||
return;
|
||||
}
|
||||
auto document = component->Render();
|
||||
int dimx = 0;
|
||||
int dimy = 0;
|
||||
auto terminal = Terminal::Size();
|
||||
document->ComputeRequirement();
|
||||
switch (dimension_) {
|
||||
case Dimension::Fixed:
|
||||
dimx = dimx_;
|
||||
dimy = dimy_;
|
||||
break;
|
||||
case Dimension::TerminalOutput:
|
||||
document->ComputeRequirement();
|
||||
dimx = Terminal::Size().dimx;
|
||||
dimx = terminal.dimx;
|
||||
dimy = document->requirement().min_y;
|
||||
break;
|
||||
case Dimension::Fullscreen:
|
||||
dimx = Terminal::Size().dimx;
|
||||
dimy = Terminal::Size().dimy;
|
||||
dimx = terminal.dimx;
|
||||
dimy = terminal.dimy;
|
||||
break;
|
||||
case Dimension::FitComponent:
|
||||
auto terminal = Terminal::Size();
|
||||
document->ComputeRequirement();
|
||||
dimx = std::min(document->requirement().min_x, terminal.dimx);
|
||||
dimy = std::min(document->requirement().min_y, terminal.dimy);
|
||||
break;
|
||||
}
|
||||
|
||||
bool resized = (dimx != dimx_) || (dimy != dimy_);
|
||||
std::cout << reset_cursor_position << ResetPosition(/*clear=*/resized);
|
||||
const bool resized = (dimx != dimx_) || (dimy != dimy_);
|
||||
ResetCursorPosition();
|
||||
std::cout << ResetPosition(/*clear=*/resized);
|
||||
|
||||
// Resize the screen if needed.
|
||||
if (resized) {
|
||||
@@ -631,44 +739,74 @@ void ScreenInteractive::Draw(Component component) {
|
||||
Render(*this, document);
|
||||
|
||||
// Set cursor position for user using tools to insert CJK characters.
|
||||
set_cursor_position = "";
|
||||
{
|
||||
const int dx = dimx_ - 1 - cursor_.x + int(dimx_ != terminal.dimx);
|
||||
const int dy = dimy_ - 1 - cursor_.y;
|
||||
|
||||
set_cursor_position = "\x1B[" + std::to_string(dy) + "A" + //
|
||||
"\x1B[" + std::to_string(dx) + "D";
|
||||
reset_cursor_position = "\x1B[" + std::to_string(dy) + "B" + //
|
||||
"\x1B[" + std::to_string(dx) + "C";
|
||||
|
||||
if (cursor_.shape == Cursor::Hidden) {
|
||||
set_cursor_position += "\033[?25l";
|
||||
} else {
|
||||
set_cursor_position += "\033[?25h";
|
||||
set_cursor_position +=
|
||||
"\033[" + std::to_string(int(cursor_.shape)) + " q";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << ToString() << set_cursor_position;
|
||||
Flush();
|
||||
Clear();
|
||||
frame_valid_ = true;
|
||||
}
|
||||
|
||||
void ScreenInteractive::ResetCursorPosition() {
|
||||
std::cout << reset_cursor_position;
|
||||
reset_cursor_position = "";
|
||||
|
||||
int dx = dimx_ - 1 - cursor_.x;
|
||||
int dy = dimy_ - 1 - cursor_.y;
|
||||
|
||||
if (dx != 0) {
|
||||
set_cursor_position += "\x1B[" + std::to_string(dx) + "D";
|
||||
reset_cursor_position += "\x1B[" + std::to_string(dx) + "C";
|
||||
}
|
||||
if (dy != 0) {
|
||||
set_cursor_position += "\x1B[" + std::to_string(dy) + "A";
|
||||
reset_cursor_position += "\x1B[" + std::to_string(dy) + "B";
|
||||
}
|
||||
}
|
||||
|
||||
Closure ScreenInteractive::ExitLoopClosure() {
|
||||
return [this] {
|
||||
quit_ = true;
|
||||
task_sender_.reset();
|
||||
};
|
||||
return [this] { Exit(); };
|
||||
}
|
||||
|
||||
void ScreenInteractive::SigStop() {
|
||||
#if defined(_WIN32)
|
||||
// Windows do no support SIGTSTP.
|
||||
#else
|
||||
void ScreenInteractive::Exit() {
|
||||
Post([this] { ExitNow(); });
|
||||
}
|
||||
|
||||
void ScreenInteractive::ExitNow() {
|
||||
quit_ = true;
|
||||
task_sender_.reset();
|
||||
}
|
||||
|
||||
void ScreenInteractive::Signal(int signal) {
|
||||
if (signal == SIGABRT) {
|
||||
OnExit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Windows do no support SIGTSTP / SIGWINCH
|
||||
#if !defined(_WIN32)
|
||||
if (signal == SIGTSTP) {
|
||||
Post([&] {
|
||||
ResetCursorPosition();
|
||||
std::cout << ResetPosition(/*clear*/ true); // Cursor to the beginning
|
||||
Uninstall();
|
||||
std::cout << reset_cursor_position;
|
||||
reset_cursor_position = "";
|
||||
std::cout << ResetPosition(/*clear=*/true);
|
||||
dimx_ = 0;
|
||||
dimy_ = 0;
|
||||
Flush();
|
||||
std::raise(SIGTSTP);
|
||||
std::ignore = std::raise(SIGTSTP);
|
||||
Install();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (signal == SIGWINCH) {
|
||||
Post(Event::Special({0}));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@@ -1,11 +1,10 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for SuiteApiResolver, TestFactoryImpl, TestPartResult
|
||||
#include <gtest/gtest.h>
|
||||
#include <csignal> // for raise, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM
|
||||
#include <ftxui/component/event.hpp> // for Event, Event::Custom
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Renderer
|
||||
#include "ftxui/component/screen_interactive.hpp"
|
||||
#include "ftxui/dom/elements.hpp" // for text, Element
|
||||
#include "gtest/gtest_pred_impl.h" // for Test, TEST, EXPECT_EQ
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
@@ -47,6 +46,20 @@ TEST(ScreenInteractive, Signal_SIGFPE) {
|
||||
TestSignal(SIGFPE);
|
||||
}
|
||||
|
||||
// Regression test for:
|
||||
// https://github.com/ArthurSonzogni/FTXUI/issues/402
|
||||
TEST(ScreenInteractive, PostEventToNonActive) {
|
||||
auto screen = ScreenInteractive::FitComponent();
|
||||
screen.Post(Event::Custom);
|
||||
}
|
||||
|
||||
// Regression test for:
|
||||
// https://github.com/ArthurSonzogni/FTXUI/issues/402
|
||||
TEST(ScreenInteractive, PostTaskToNonActive) {
|
||||
auto screen = ScreenInteractive::FitComponent();
|
||||
screen.Post([] {});
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
||||
|
@@ -1,42 +1,109 @@
|
||||
#include <algorithm> // for max, min
|
||||
#include <ftxui/component/component_options.hpp> // for SliderOption
|
||||
#include <string> // for allocator
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/component.hpp" // for Make, Slider
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowRight
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed, Mouse::Released
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, text, Element, reflect, xflex, gauge, hbox, underlined, color, dim, vcenter
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, text, GaugeDirection, Element, xflex, hbox, color, underlined, GaugeDirection::Down, GaugeDirection::Left, GaugeDirection::Right, GaugeDirection::Up, reflect, Decorator, dim, vcenter, yflex, gaugeDirection
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::GrayDark, Color::GrayLight
|
||||
#include "ftxui/util/ref.hpp" // for StringRef
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::GrayDark, Color::White
|
||||
#include "ftxui/screen/util.hpp" // for clamp
|
||||
#include "ftxui/util/ref.hpp" // for ConstRef, Ref, ConstStringRef
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
Decorator flexDirection(GaugeDirection direction) {
|
||||
switch (direction) {
|
||||
case GaugeDirection::Up:
|
||||
case GaugeDirection::Down:
|
||||
return yflex;
|
||||
case GaugeDirection::Left:
|
||||
case GaugeDirection::Right:
|
||||
return xflex;
|
||||
}
|
||||
return xflex; // NOT_REACHED()
|
||||
}
|
||||
} // namespace
|
||||
|
||||
template <class T>
|
||||
class SliderBase : public ComponentBase {
|
||||
public:
|
||||
SliderBase(ConstStringRef label, T* value, T min, T max, T increment)
|
||||
: label_(std::move(label)),
|
||||
value_(value),
|
||||
min_(min),
|
||||
max_(max),
|
||||
increment_(increment) {}
|
||||
explicit SliderBase(Ref<SliderOption<T>> options)
|
||||
: value_(options->value),
|
||||
min_(options->min),
|
||||
max_(options->max),
|
||||
increment_(options->increment),
|
||||
options_(options) {}
|
||||
|
||||
Element Render() override {
|
||||
auto gauge_color =
|
||||
Focused() ? color(Color::GrayLight) : color(Color::GrayDark);
|
||||
float percent = float(*value_ - min_) / float(max_ - min_);
|
||||
return hbox({
|
||||
text(*label_) | dim | vcenter,
|
||||
hbox({
|
||||
text("["),
|
||||
gauge(percent) | underlined | xflex | reflect(gauge_box_),
|
||||
text("]"),
|
||||
}) | xflex,
|
||||
}) |
|
||||
gauge_color | xflex | reflect(box_);
|
||||
auto gauge_color = Focused() ? color(options_->color_active)
|
||||
: color(options_->color_inactive);
|
||||
const float percent = float(value_() - min_()) / float(max_() - min_());
|
||||
return gaugeDirection(percent, options_->direction) |
|
||||
flexDirection(options_->direction) | reflect(gauge_box_) |
|
||||
gauge_color;
|
||||
}
|
||||
|
||||
void OnLeft() {
|
||||
switch (options_->direction) {
|
||||
case GaugeDirection::Right:
|
||||
value_() -= increment_();
|
||||
break;
|
||||
case GaugeDirection::Left:
|
||||
value_() += increment_();
|
||||
break;
|
||||
case GaugeDirection::Up:
|
||||
case GaugeDirection::Down:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnRight() {
|
||||
switch (options_->direction) {
|
||||
case GaugeDirection::Right:
|
||||
value_() += increment_();
|
||||
break;
|
||||
case GaugeDirection::Left:
|
||||
value_() -= increment_();
|
||||
break;
|
||||
case GaugeDirection::Up:
|
||||
case GaugeDirection::Down:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnUp() {
|
||||
switch (options_->direction) {
|
||||
case GaugeDirection::Up:
|
||||
value_() -= increment_();
|
||||
break;
|
||||
case GaugeDirection::Down:
|
||||
value_() += increment_();
|
||||
break;
|
||||
case GaugeDirection::Left:
|
||||
case GaugeDirection::Right:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDown() {
|
||||
switch (options_->direction) {
|
||||
case GaugeDirection::Down:
|
||||
value_() -= increment_();
|
||||
break;
|
||||
case GaugeDirection::Up:
|
||||
value_() += increment_();
|
||||
break;
|
||||
case GaugeDirection::Left:
|
||||
case GaugeDirection::Right:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) final {
|
||||
@@ -44,15 +111,22 @@ class SliderBase : public ComponentBase {
|
||||
return OnMouseEvent(event);
|
||||
}
|
||||
|
||||
T old_value = value_();
|
||||
if (event == Event::ArrowLeft || event == Event::Character('h')) {
|
||||
*value_ -= increment_;
|
||||
*value_ = std::max(*value_, min_);
|
||||
return true;
|
||||
OnLeft();
|
||||
}
|
||||
if (event == Event::ArrowRight || event == Event::Character('l')) {
|
||||
OnRight();
|
||||
}
|
||||
if (event == Event::ArrowUp || event == Event::Character('k')) {
|
||||
OnDown();
|
||||
}
|
||||
if (event == Event::ArrowDown || event == Event::Character('j')) {
|
||||
OnUp();
|
||||
}
|
||||
|
||||
if (event == Event::ArrowRight || event == Event::Character('l')) {
|
||||
*value_ += increment_;
|
||||
*value_ = std::min(*value_, max_);
|
||||
value_() = util::clamp(value_(), min_(), max_());
|
||||
if (old_value != value_()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -65,7 +139,8 @@ class SliderBase : public ComponentBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (box_.Contain(event.mouse().x, event.mouse().y) && CaptureMouse(event)) {
|
||||
if (gauge_box_.Contain(event.mouse().x, event.mouse().y) &&
|
||||
CaptureMouse(event)) {
|
||||
TakeFocus();
|
||||
}
|
||||
|
||||
@@ -77,9 +152,33 @@ class SliderBase : public ComponentBase {
|
||||
}
|
||||
|
||||
if (captured_mouse_) {
|
||||
*value_ = min_ + (event.mouse().x - gauge_box_.x_min) * (max_ - min_) /
|
||||
switch (options_->direction) {
|
||||
case GaugeDirection::Right: {
|
||||
value_() = min_() + (event.mouse().x - gauge_box_.x_min) *
|
||||
(max_() - min_()) /
|
||||
(gauge_box_.x_max - gauge_box_.x_min);
|
||||
*value_ = std::max(min_, std::min(max_, *value_));
|
||||
break;
|
||||
}
|
||||
case GaugeDirection::Left: {
|
||||
value_() = max_() - (event.mouse().x - gauge_box_.x_min) *
|
||||
(max_() - min_()) /
|
||||
(gauge_box_.x_max - gauge_box_.x_min);
|
||||
break;
|
||||
}
|
||||
case GaugeDirection::Down: {
|
||||
value_() = min_() + (event.mouse().y - gauge_box_.y_min) *
|
||||
(max_() - min_()) /
|
||||
(gauge_box_.y_max - gauge_box_.y_min);
|
||||
break;
|
||||
}
|
||||
case GaugeDirection::Up: {
|
||||
value_() = max_() - (event.mouse().y - gauge_box_.y_min) *
|
||||
(max_() - min_()) /
|
||||
(gauge_box_.y_max - gauge_box_.y_min);
|
||||
break;
|
||||
}
|
||||
}
|
||||
value_() = std::max(min_(), std::min(max_(), value_()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -88,16 +187,63 @@ class SliderBase : public ComponentBase {
|
||||
bool Focusable() const final { return true; }
|
||||
|
||||
private:
|
||||
ConstStringRef label_;
|
||||
T* value_;
|
||||
T min_;
|
||||
T max_;
|
||||
T increment_ = 1;
|
||||
Box box_;
|
||||
Ref<T> value_;
|
||||
ConstRef<T> min_;
|
||||
ConstRef<T> max_;
|
||||
ConstRef<T> increment_;
|
||||
Ref<SliderOption<T>> options_;
|
||||
Box gauge_box_;
|
||||
CapturedMouse captured_mouse_;
|
||||
};
|
||||
|
||||
class SliderWithLabel : public ComponentBase {
|
||||
public:
|
||||
SliderWithLabel(ConstStringRef label, Component inner)
|
||||
: label_(std::move(label)) {
|
||||
Add(std::move(inner));
|
||||
SetActiveChild(ChildAt(0));
|
||||
}
|
||||
|
||||
private:
|
||||
bool OnEvent(Event event) final {
|
||||
if (ComponentBase::OnEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!event.is_mouse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!box_.Contain(event.mouse().x, event.mouse().y)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CaptureMouse(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TakeFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
Element Render() override {
|
||||
auto focus_management = Focused() ? focus : Active() ? select : nothing;
|
||||
auto gauge_color = Focused() ? color(Color::White) : color(Color::GrayDark);
|
||||
return hbox({
|
||||
text(label_()) | dim | vcenter,
|
||||
hbox({
|
||||
text("["),
|
||||
ComponentBase::Render() | underlined,
|
||||
text("]"),
|
||||
}) | xflex,
|
||||
}) |
|
||||
gauge_color | xflex | reflect(box_) | focus_management;
|
||||
}
|
||||
|
||||
ConstStringRef label_;
|
||||
Box box_;
|
||||
};
|
||||
|
||||
/// @brief An horizontal slider.
|
||||
/// @param label The name of the slider.
|
||||
/// @param value The current value of the slider.
|
||||
@@ -120,28 +266,78 @@ class SliderBase : public ComponentBase {
|
||||
/// ```bash
|
||||
/// Value:[██████████████████████████ ]
|
||||
/// ```
|
||||
template <class T>
|
||||
Component Slider(ConstStringRef label, T* value, T min, T max, T increment) {
|
||||
return Make<SliderBase<T>>(std::move(label), value, min, max, increment);
|
||||
Component Slider(ConstStringRef label,
|
||||
Ref<int> value,
|
||||
ConstRef<int> min,
|
||||
ConstRef<int> max,
|
||||
ConstRef<int> increment) {
|
||||
SliderOption<int> option;
|
||||
option.value = value;
|
||||
option.min = min;
|
||||
option.max = max;
|
||||
option.increment = increment;
|
||||
auto slider = Make<SliderBase<int>>(option);
|
||||
return Make<SliderWithLabel>(std::move(label), slider);
|
||||
}
|
||||
|
||||
template Component Slider(ConstStringRef label,
|
||||
int* value,
|
||||
int min,
|
||||
int max,
|
||||
int increment);
|
||||
Component Slider(ConstStringRef label,
|
||||
Ref<float> value,
|
||||
ConstRef<float> min,
|
||||
ConstRef<float> max,
|
||||
ConstRef<float> increment) {
|
||||
SliderOption<float> option;
|
||||
option.value = value;
|
||||
option.min = min;
|
||||
option.max = max;
|
||||
option.increment = increment;
|
||||
auto slider = Make<SliderBase<float>>(option);
|
||||
return Make<SliderWithLabel>(std::move(label), slider);
|
||||
}
|
||||
Component Slider(ConstStringRef label,
|
||||
Ref<long> value,
|
||||
ConstRef<long> min,
|
||||
ConstRef<long> max,
|
||||
ConstRef<long> increment) {
|
||||
SliderOption<long> option;
|
||||
option.value = value;
|
||||
option.min = min;
|
||||
option.max = max;
|
||||
option.increment = increment;
|
||||
auto slider = Make<SliderBase<long>>(option);
|
||||
return Make<SliderWithLabel>(std::move(label), slider);
|
||||
}
|
||||
|
||||
template Component Slider(ConstStringRef label,
|
||||
float* value,
|
||||
float min,
|
||||
float max,
|
||||
float increment);
|
||||
/// @brief A slider in any direction.
|
||||
/// @param option The options
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto screen = ScreenInteractive::TerminalOutput();
|
||||
/// int value = 50;
|
||||
/// auto slider = Slider({
|
||||
/// .value = &value,
|
||||
/// .min = 0,
|
||||
/// .max = 100,
|
||||
/// .increment= 20,
|
||||
/// });
|
||||
/// screen.Loop(slider);
|
||||
/// ```
|
||||
template <typename T>
|
||||
Component Slider(SliderOption<T> options) {
|
||||
return Make<SliderBase<T>>(options);
|
||||
}
|
||||
template Component Slider(SliderOption<int8_t>);
|
||||
template Component Slider(SliderOption<int16_t>);
|
||||
template Component Slider(SliderOption<int32_t>);
|
||||
template Component Slider(SliderOption<int64_t>);
|
||||
|
||||
template Component Slider(ConstStringRef label,
|
||||
long* value,
|
||||
long min,
|
||||
long max,
|
||||
long increment);
|
||||
template Component Slider(SliderOption<uint8_t>);
|
||||
template Component Slider(SliderOption<uint16_t>);
|
||||
template Component Slider(SliderOption<uint32_t>);
|
||||
template Component Slider(SliderOption<uint64_t>);
|
||||
|
||||
template Component Slider(SliderOption<float>);
|
||||
template Component Slider(SliderOption<double>);
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
|
192
src/ftxui/component/slider_test.cpp
Normal file
192
src/ftxui/component/slider_test.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, Test, EXPECT_EQ, EXPECT_TRUE, TestInfo (ptr only), EXPECT_FALSE, TEST
|
||||
#include <stddef.h> // for size_t
|
||||
#include <array> // for array
|
||||
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Left, Mouse::Pressed, Mouse::Released
|
||||
#include <ftxui/dom/elements.hpp> // for GaugeDirection, GaugeDirection::Down, GaugeDirection::Left, GaugeDirection::Right, GaugeDirection::Up, frame
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
|
||||
#include <string> // for to_string
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Slider, Vertical, operator|=
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/screen.hpp" // for Screen
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
Event MousePressed(int x, int y) {
|
||||
Mouse mouse;
|
||||
mouse.button = Mouse::Left;
|
||||
mouse.motion = Mouse::Pressed;
|
||||
mouse.shift = false;
|
||||
mouse.meta = false;
|
||||
mouse.control = false;
|
||||
mouse.x = x;
|
||||
mouse.y = y;
|
||||
return Event::Mouse("jjj", mouse);
|
||||
}
|
||||
|
||||
Event MouseReleased(int x, int y) {
|
||||
Mouse mouse;
|
||||
mouse.button = Mouse::Left;
|
||||
mouse.motion = Mouse::Released;
|
||||
mouse.shift = false;
|
||||
mouse.meta = false;
|
||||
mouse.control = false;
|
||||
mouse.x = x;
|
||||
mouse.y = y;
|
||||
return Event::Mouse("jjj", mouse);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(SliderTest, Right) {
|
||||
int value = 50;
|
||||
auto slider = Slider<int>({
|
||||
.value = &value,
|
||||
.min = 0,
|
||||
.max = 100,
|
||||
.increment = 10,
|
||||
.direction = GaugeDirection::Right,
|
||||
});
|
||||
Screen screen(11, 1);
|
||||
Render(screen, slider->Render());
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(3, 0)));
|
||||
EXPECT_EQ(value, 30);
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(9, 0)));
|
||||
EXPECT_EQ(value, 90);
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(9, 2)));
|
||||
EXPECT_EQ(value, 90);
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(5, 2)));
|
||||
EXPECT_EQ(value, 50);
|
||||
EXPECT_TRUE(slider->OnEvent(MouseReleased(5, 2)));
|
||||
EXPECT_FALSE(slider->OnEvent(MousePressed(5, 2)));
|
||||
}
|
||||
|
||||
TEST(SliderTest, Left) {
|
||||
int value = 50;
|
||||
auto slider = Slider<int>({
|
||||
.value = &value,
|
||||
.min = 0,
|
||||
.max = 100,
|
||||
.increment = 10,
|
||||
.direction = GaugeDirection::Left,
|
||||
});
|
||||
Screen screen(11, 1);
|
||||
Render(screen, slider->Render());
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(3, 0)));
|
||||
EXPECT_EQ(value, 70);
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(9, 0)));
|
||||
EXPECT_EQ(value, 10);
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(9, 2)));
|
||||
EXPECT_EQ(value, 10);
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(5, 2)));
|
||||
EXPECT_EQ(value, 50);
|
||||
EXPECT_TRUE(slider->OnEvent(MouseReleased(5, 2)));
|
||||
EXPECT_FALSE(slider->OnEvent(MousePressed(5, 2)));
|
||||
}
|
||||
|
||||
TEST(SliderTest, Down) {
|
||||
int value = 50;
|
||||
auto slider = Slider<int>({
|
||||
.value = &value,
|
||||
.min = 0,
|
||||
.max = 100,
|
||||
.increment = 10,
|
||||
.direction = GaugeDirection::Down,
|
||||
});
|
||||
Screen screen(1, 11);
|
||||
Render(screen, slider->Render());
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(0, 3)));
|
||||
EXPECT_EQ(value, 30);
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(0, 9)));
|
||||
EXPECT_EQ(value, 90);
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(2, 9)));
|
||||
EXPECT_EQ(value, 90);
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(2, 5)));
|
||||
EXPECT_EQ(value, 50);
|
||||
EXPECT_TRUE(slider->OnEvent(MouseReleased(2, 5)));
|
||||
EXPECT_FALSE(slider->OnEvent(MousePressed(2, 5)));
|
||||
}
|
||||
|
||||
TEST(SliderTest, Up) {
|
||||
int value = 50;
|
||||
auto slider = Slider<int>({
|
||||
.value = &value,
|
||||
.min = 0,
|
||||
.max = 100,
|
||||
.increment = 10,
|
||||
.direction = GaugeDirection::Up,
|
||||
});
|
||||
Screen screen(1, 11);
|
||||
Render(screen, slider->Render());
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(0, 3)));
|
||||
EXPECT_EQ(value, 70);
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(0, 9)));
|
||||
EXPECT_EQ(value, 10);
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(2, 9)));
|
||||
EXPECT_EQ(value, 10);
|
||||
EXPECT_TRUE(slider->OnEvent(MousePressed(2, 5)));
|
||||
EXPECT_EQ(value, 50);
|
||||
EXPECT_TRUE(slider->OnEvent(MouseReleased(2, 5)));
|
||||
EXPECT_FALSE(slider->OnEvent(MousePressed(2, 5)));
|
||||
}
|
||||
|
||||
TEST(SliderTest, Focus) {
|
||||
static std::array<int, 10> values;
|
||||
auto container = Container::Vertical({});
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
container->Add(Slider(std::to_string(i), &values[i]));
|
||||
}
|
||||
container |= frame;
|
||||
|
||||
Screen screen(10, 3);
|
||||
|
||||
Render(screen, container->Render());
|
||||
EXPECT_EQ(screen.at(0, 0), "0"); // Select 0
|
||||
EXPECT_EQ(screen.at(0, 1), "1");
|
||||
EXPECT_EQ(screen.at(0, 2), "2");
|
||||
|
||||
EXPECT_TRUE(container->OnEvent(Event::ArrowDown));
|
||||
Render(screen, container->Render());
|
||||
EXPECT_EQ(screen.at(0, 0), "0");
|
||||
EXPECT_EQ(screen.at(0, 1), "1"); // Select 1
|
||||
EXPECT_EQ(screen.at(0, 2), "2");
|
||||
|
||||
EXPECT_TRUE(container->OnEvent(Event::ArrowDown));
|
||||
Render(screen, container->Render());
|
||||
EXPECT_EQ(screen.at(0, 0), "1");
|
||||
EXPECT_EQ(screen.at(0, 1), "2"); // Select 2
|
||||
EXPECT_EQ(screen.at(0, 2), "3");
|
||||
|
||||
EXPECT_TRUE(container->OnEvent(Event::ArrowDown)); // Select 3
|
||||
EXPECT_TRUE(container->OnEvent(Event::ArrowDown)); // Select 4
|
||||
EXPECT_TRUE(container->OnEvent(Event::ArrowDown)); // Select 5
|
||||
EXPECT_TRUE(container->OnEvent(Event::ArrowDown)); // Select 6
|
||||
|
||||
EXPECT_TRUE(container->OnEvent(Event::ArrowDown));
|
||||
Render(screen, container->Render());
|
||||
EXPECT_EQ(screen.at(0, 0), "6");
|
||||
EXPECT_EQ(screen.at(0, 1), "7"); // Select 7
|
||||
EXPECT_EQ(screen.at(0, 2), "8");
|
||||
|
||||
EXPECT_TRUE(container->OnEvent(Event::ArrowDown));
|
||||
Render(screen, container->Render());
|
||||
EXPECT_EQ(screen.at(0, 0), "7");
|
||||
EXPECT_EQ(screen.at(0, 1), "8"); // Select 8
|
||||
EXPECT_EQ(screen.at(0, 2), "9");
|
||||
|
||||
EXPECT_TRUE(container->OnEvent(Event::ArrowDown));
|
||||
Render(screen, container->Render());
|
||||
EXPECT_EQ(screen.at(0, 0), "7");
|
||||
EXPECT_EQ(screen.at(0, 1), "8");
|
||||
EXPECT_EQ(screen.at(0, 2), "9"); // Select 9
|
||||
|
||||
EXPECT_FALSE(container->OnEvent(Event::ArrowDown));
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2022 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
@@ -1,7 +1,10 @@
|
||||
#include "ftxui/component/terminal_input_parser.hpp"
|
||||
|
||||
#include <cstdint> // for uint32_t
|
||||
#include <memory> // for unique_ptr
|
||||
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Button, Mouse::Motion
|
||||
#include <ftxui/component/receiver.hpp> // for SenderImpl, Sender
|
||||
#include <map>
|
||||
#include <memory> // for unique_ptr, allocator
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/event.hpp" // for Event
|
||||
@@ -9,6 +12,17 @@
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
const std::map<std::string, std::string> g_uniformize = {
|
||||
// Microsoft's terminal uses a different new line character for the return
|
||||
// key. This also happens with linux with the `bind` command:
|
||||
// See https://github.com/ArthurSonzogni/FTXUI/issues/337
|
||||
// Here, we uniformize the new line character to `\n`.
|
||||
{"\r", "\n"},
|
||||
// See: https://github.com/ArthurSonzogni/FTXUI/issues/508
|
||||
{std::string({8}), std::string({127})},
|
||||
};
|
||||
|
||||
TerminalInputParser::TerminalInputParser(Sender<Task> out)
|
||||
: out_(std::move(out)) {}
|
||||
|
||||
@@ -54,17 +68,14 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) {
|
||||
pending_.clear();
|
||||
return;
|
||||
|
||||
case SPECIAL:
|
||||
// Microsoft's terminal uses a different new line character for the return
|
||||
// key. This also happens with linux with the `bind` command:
|
||||
// See https://github.com/ArthurSonzogni/FTXUI/issues/337
|
||||
// Here, we uniformize the new line character to `\n`.
|
||||
if (pending_ == "\r") {
|
||||
out_->Send(Event::Special("\n"));
|
||||
} else {
|
||||
out_->Send(Event::Special(std::move(pending_)));
|
||||
case SPECIAL: {
|
||||
auto it = g_uniformize.find(pending_);
|
||||
if (it != g_uniformize.end()) {
|
||||
pending_ = it->second;
|
||||
}
|
||||
out_->Send(Event::Special(std::move(pending_)));
|
||||
pending_.clear();
|
||||
}
|
||||
return;
|
||||
|
||||
case MOUSE:
|
||||
|
@@ -1,14 +1,13 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <algorithm> // for max
|
||||
#include <gtest/gtest.h> // for AssertionResult, Test, Message, TestPartResult, SuiteApiResolver, TestInfo (ptr only), EXPECT_EQ, EXPECT_TRUE, TEST, TestFactoryImpl, EXPECT_FALSE
|
||||
#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 <initializer_list> // for initializer_list
|
||||
#include <memory> // for unique_ptr, allocator
|
||||
#include <memory> // for allocator, unique_ptr
|
||||
#include <variant> // for get
|
||||
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::Backspace, Event::Custom, Event::Delete, Event::End, Event::Escape, Event::F10, Event::F11, Event::F12, Event::F5, Event::F6, Event::F7, Event::F8, Event::F9, Event::Home, Event::PageDown, Event::PageUp, Event::Tab, Event::TabReverse
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::Backspace, Event::Custom, Event::Delete, Event::End, Event::F10, Event::F11, Event::F12, Event::F5, Event::F6, Event::F7, Event::F8, Event::F9, Event::Home, 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 "gtest/gtest_pred_impl.h" // for AssertionResult, Test, EXPECT_EQ, EXPECT_TRUE, TEST, EXPECT_FALSE
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
@@ -303,7 +302,7 @@ TEST(Event, Control) {
|
||||
};
|
||||
std::vector<TestCase> cases;
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
if (i == 13 || i == 24 || i == 26 || i == 27)
|
||||
if (i == 8 || i == 13 || i == 24 || i == 26 || i == 27)
|
||||
continue;
|
||||
cases.push_back({char(i), false});
|
||||
}
|
||||
@@ -343,22 +342,24 @@ TEST(Event, Special) {
|
||||
{str("\x1B[A"), Event::ArrowUp},
|
||||
{str("\x1B[B"), Event::ArrowDown},
|
||||
{{127}, Event::Backspace},
|
||||
// Quirk for: https://github.com/ArthurSonzogni/FTXUI/issues/508
|
||||
{{8}, Event::Backspace},
|
||||
{str("\x1B[3~"), Event::Delete},
|
||||
//{str("\x1B"), Event::Escape},
|
||||
{{10}, Event::Return},
|
||||
{{9}, Event::Tab},
|
||||
{{27, 91, 90}, Event::TabReverse},
|
||||
//{str("\x1B[OP"), Event::F1},
|
||||
//{str("\x1B[OQ"), Event::F2},
|
||||
//{str("\x1B[OR"), Event::F3},
|
||||
//{str("\x1B[OS"), Event::F4},
|
||||
{str("\x1BOP"), Event::F1},
|
||||
{str("\x1BOQ"), Event::F2},
|
||||
{str("\x1BOR"), Event::F3},
|
||||
{str("\x1BOS"), Event::F4},
|
||||
{str("\x1B[15~"), Event::F5},
|
||||
{str("\x1B[17~"), Event::F6},
|
||||
{str("\x1B[18~"), Event::F7},
|
||||
{str("\x1B[19~"), Event::F8},
|
||||
{str("\x1B[20~"), Event::F9},
|
||||
{str("\x1B[21~"), Event::F10},
|
||||
{str("\x1B[21~"), Event::F11},
|
||||
{str("\x1B[23~"), Event::F11},
|
||||
{str("\x1B[24~"), Event::F12},
|
||||
{{27, 91, 72}, Event::Home},
|
||||
{{27, 91, 70}, Event::End},
|
||||
|
@@ -1,5 +1,3 @@
|
||||
//#include "ftxui/component/event.hpp"
|
||||
//#include "ftxui/component/receiver.hpp"
|
||||
#include <vector>
|
||||
#include "ftxui/component/terminal_input_parser.hpp"
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <gtest/gtest.h>
|
||||
#include <functional> // for function
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
#include <string> // for string, basic_string
|
||||
@@ -11,7 +10,6 @@
|
||||
#include "ftxui/component/component_options.hpp" // for ToggleOption
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowRight, Event::Return, Event::Tab, Event::TabReverse
|
||||
#include "ftxui/util/ref.hpp" // for Ref
|
||||
#include "gtest/gtest_pred_impl.h" // for AssertionResult, EXPECT_EQ, Test, EXPECT_TRUE, EXPECT_FALSE, TEST
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
|
@@ -1,11 +1,9 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for SuiteApiResolver, TestFactoryImpl, TestPartResult
|
||||
#include <gtest/gtest.h>
|
||||
#include <string> // for allocator
|
||||
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, text, blink, Element
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
|
||||
#include "gtest/gtest_pred_impl.h" // for Test, AssertionResult, EXPECT_TRUE, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
@@ -1,11 +1,9 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for SuiteApiResolver, TestFactoryImpl, TestPartResult
|
||||
#include <gtest/gtest.h>
|
||||
#include <string> // for allocator
|
||||
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, text, bold, Element
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
|
||||
#include "gtest/gtest_pred_impl.h" // for Test, AssertionResult, EXPECT_TRUE, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
@@ -14,7 +14,7 @@
|
||||
namespace ftxui {
|
||||
|
||||
using Charset = std::array<std::string, 6>; // NOLINT
|
||||
using Charsets = std::array<Charset, 6>; // NOLINT
|
||||
using Charsets = std::array<Charset, 5>; // NOLINT
|
||||
// NOLINTNEXTLINE
|
||||
static Charsets simple_border_charset = {
|
||||
Charset{"┌", "┐", "└", "┘", "─", "│"},
|
||||
|
@@ -1,11 +1,9 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for SuiteApiResolver, TestFactoryImpl, TestPartResult
|
||||
#include <gtest/gtest.h>
|
||||
#include <string> // for allocator, string
|
||||
|
||||
#include "ftxui/dom/elements.hpp" // for text, operator|, Element, borderStyled, borderWith, window, border, borderDouble, borderEmpty, borderHeavy, borderLight, borderRounded, DOUBLE
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
|
||||
#include "gtest/gtest_pred_impl.h" // for Test, EXPECT_EQ, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
@@ -12,7 +12,7 @@ void ComputeGrow(std::vector<Element>* elements,
|
||||
int extra_space,
|
||||
int flex_grow_sum) {
|
||||
for (Element& element : *elements) {
|
||||
int added_space =
|
||||
const int added_space =
|
||||
extra_space * element.flex_grow / std::max(flex_grow_sum, 1);
|
||||
extra_space -= added_space;
|
||||
flex_grow_sum -= element.flex_grow;
|
||||
@@ -27,8 +27,8 @@ void ComputeShrinkEasy(std::vector<Element>* elements,
|
||||
int extra_space,
|
||||
int flex_shrink_sum) {
|
||||
for (Element& element : *elements) {
|
||||
int added_space = extra_space * element.min_size * element.flex_shrink /
|
||||
std::max(flex_shrink_sum, 1);
|
||||
const int added_space = extra_space * element.min_size *
|
||||
element.flex_shrink / std::max(flex_shrink_sum, 1);
|
||||
extra_space -= added_space;
|
||||
flex_shrink_sum -= element.flex_shrink * element.min_size;
|
||||
element.size = element.min_size + added_space;
|
||||
@@ -48,7 +48,7 @@ void ComputeShrinkHard(std::vector<Element>* elements,
|
||||
continue;
|
||||
}
|
||||
|
||||
int added_space = extra_space * element.min_size / std::max(1, size);
|
||||
const int added_space = extra_space * element.min_size / std::max(1, size);
|
||||
extra_space -= added_space;
|
||||
size -= element.min_size;
|
||||
|
||||
@@ -73,7 +73,7 @@ void Compute(std::vector<Element>* elements, int target_size) {
|
||||
size += element.min_size;
|
||||
}
|
||||
|
||||
int extra_space = target_size - size;
|
||||
const int extra_space = target_size - size;
|
||||
if (extra_space >= 0) {
|
||||
ComputeGrow(elements, extra_space, flex_grow_sum);
|
||||
} else if (flex_shrink_size + extra_space >= 0) {
|
||||
|
@@ -1,9 +1,11 @@
|
||||
#include "ftxui/dom/canvas.hpp"
|
||||
|
||||
#include <algorithm> // for max, min
|
||||
#include <cmath> // for abs
|
||||
#include <cstdint> // for uint8_t
|
||||
#include <cstdlib> // for abs
|
||||
#include <map> // for allocator, map
|
||||
#include <ftxui/screen/color.hpp> // for Color
|
||||
#include <map> // for map
|
||||
#include <memory> // for make_shared
|
||||
#include <utility> // for move, pair
|
||||
#include <vector> // for vector
|
||||
@@ -468,7 +470,7 @@ void Canvas::DrawBlockOn(int x, int y) {
|
||||
cell.type = CellType::kBlock;
|
||||
}
|
||||
|
||||
uint8_t bit = (x % 2) * 2 + y % 2;
|
||||
const uint8_t bit = (x % 2) * 2 + y % 2;
|
||||
uint8_t value = g_map_block_inversed.at(cell.content.character);
|
||||
value |= 1U << bit;
|
||||
cell.content.character = g_map_block[value];
|
||||
@@ -488,7 +490,7 @@ void Canvas::DrawBlockOff(int x, int y) {
|
||||
}
|
||||
y /= 2;
|
||||
|
||||
uint8_t bit = (y % 2) * 2 + x % 2;
|
||||
const uint8_t bit = (y % 2) * 2 + x % 2;
|
||||
uint8_t value = g_map_block_inversed.at(cell.content.character);
|
||||
value &= ~(1U << bit);
|
||||
cell.content.character = g_map_block[value];
|
||||
@@ -509,7 +511,7 @@ void Canvas::DrawBlockToggle(int x, int y) {
|
||||
}
|
||||
y /= 2;
|
||||
|
||||
uint8_t bit = (y % 2) * 2 + x % 2;
|
||||
const uint8_t bit = (y % 2) * 2 + x % 2;
|
||||
uint8_t value = g_map_block_inversed.at(cell.content.character);
|
||||
value ^= 1U << bit;
|
||||
cell.content.character = g_map_block[value];
|
||||
@@ -801,6 +803,7 @@ void Canvas::DrawText(int x,
|
||||
const Stylizer& style) {
|
||||
for (const auto& it : Utf8ToGlyphs(value)) {
|
||||
if (!IsIn(x, y)) {
|
||||
x += 2;
|
||||
continue;
|
||||
}
|
||||
Cell& cell = storage_[XY{x / 2, y / 4}];
|
||||
@@ -827,8 +830,8 @@ class CanvasNodeBase : public Node {
|
||||
|
||||
void Render(Screen& screen) override {
|
||||
const Canvas& c = canvas();
|
||||
int y_max = std::min(c.height() / 4, box_.y_max - box_.y_min + 1);
|
||||
int x_max = std::min(c.width() / 2, box_.x_max - box_.x_min + 1);
|
||||
const int y_max = std::min(c.height() / 4, box_.y_max - box_.y_min + 1);
|
||||
const int x_max = std::min(c.width() / 2, box_.x_max - box_.x_min + 1);
|
||||
for (int y = 0; y < y_max; ++y) {
|
||||
for (int x = 0; x < x_max; ++x) {
|
||||
screen.PixelAt(box_.x_min + x, box_.y_min + y) = c.GetPixel(x, y);
|
||||
@@ -871,8 +874,8 @@ Element canvas(int width, int height, std::function<void(Canvas&)> fn) {
|
||||
}
|
||||
|
||||
void Render(Screen& screen) final {
|
||||
int width = (box_.y_max - box_.y_min + 1) * 2;
|
||||
int height = (box_.x_max - box_.x_min + 1) * 4;
|
||||
const int width = (box_.x_max - box_.x_min + 1) * 2;
|
||||
const int height = (box_.y_max - box_.y_min + 1) * 4;
|
||||
canvas_ = Canvas(width, height);
|
||||
fn_(canvas_);
|
||||
CanvasNodeBase::Render(screen);
|
||||
|
@@ -1,5 +1,4 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for SuiteApiResolver, TestFactoryImpl, TestPartResult
|
||||
#include <gtest/gtest.h>
|
||||
#include <stdint.h> // for uint32_t
|
||||
#include <string> // for allocator, string
|
||||
|
||||
@@ -9,7 +8,6 @@
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Black, Color::Blue, Color::Red, Color::White, Color::Yellow, Color::Cyan, Color::Green
|
||||
#include "ftxui/screen/screen.hpp" // for Screen
|
||||
#include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor
|
||||
#include "gtest/gtest_pred_impl.h" // for Test, EXPECT_EQ, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
@@ -39,7 +37,7 @@ TEST(CanvasTest, GoldPoint) {
|
||||
});
|
||||
Screen screen(30, 10);
|
||||
Render(screen, element);
|
||||
EXPECT_EQ(Hash(screen.ToString()), -1195891837);
|
||||
EXPECT_EQ(Hash(screen.ToString()), 2143518726);
|
||||
}
|
||||
|
||||
TEST(CanvasTest, GoldPointColor) {
|
||||
@@ -54,7 +52,7 @@ TEST(CanvasTest, GoldPointColor) {
|
||||
});
|
||||
Screen screen(30, 10);
|
||||
Render(screen, element);
|
||||
EXPECT_EQ(Hash(screen.ToString()), 1109533029);
|
||||
EXPECT_EQ(Hash(screen.ToString()), 1264423298);
|
||||
}
|
||||
|
||||
TEST(CanvasTest, GoldBlock) {
|
||||
@@ -72,7 +70,7 @@ TEST(CanvasTest, GoldBlock) {
|
||||
});
|
||||
Screen screen(30, 10);
|
||||
Render(screen, element);
|
||||
EXPECT_EQ(Hash(screen.ToString()), 817159424);
|
||||
EXPECT_EQ(Hash(screen.ToString()), 3826174883);
|
||||
}
|
||||
|
||||
TEST(CanvasTest, GoldBlockColor) {
|
||||
@@ -87,7 +85,7 @@ TEST(CanvasTest, GoldBlockColor) {
|
||||
});
|
||||
Screen screen(30, 10);
|
||||
Render(screen, element);
|
||||
EXPECT_EQ(Hash(screen.ToString()), 2869205941);
|
||||
EXPECT_EQ(Hash(screen.ToString()), 3048712696);
|
||||
}
|
||||
|
||||
TEST(CanvasTest, GoldText) {
|
||||
|
@@ -1,12 +1,10 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for SuiteApiResolver, TestPartResult, TestFactoryImpl
|
||||
#include <gtest/gtest.h>
|
||||
#include <string> // for allocator
|
||||
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, text, bgcolor, color, Element
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Red
|
||||
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
|
||||
#include "gtest/gtest_pred_impl.h" // for Test, EXPECT_EQ, TEST
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user