mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-28 16:29:34 +08:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c033ca61ae | ||
![]() |
a6e04b4346 | ||
![]() |
09a2c077eb | ||
![]() |
bc206f85da | ||
![]() |
e9772a0116 | ||
![]() |
e4c5c7b43b | ||
![]() |
c2e1920449 | ||
![]() |
3d56146447 | ||
![]() |
c33e805a76 | ||
![]() |
d0890f94d1 | ||
![]() |
84d6e6b3dd | ||
![]() |
001dd0a8c3 | ||
![]() |
114cbfcffd | ||
![]() |
5ba301d316 | ||
![]() |
06ed8567b8 | ||
![]() |
04b36df567 | ||
![]() |
764c24ef40 | ||
![]() |
b2896aba49 | ||
![]() |
9f610a0110 | ||
![]() |
aebde94352 | ||
![]() |
62fb6298be | ||
![]() |
548fa51b71 | ||
![]() |
1a3fcc1bd8 | ||
![]() |
0137d2a9ac | ||
![]() |
c76612a3c8 | ||
![]() |
4da63b9260 | ||
![]() |
95c766e9e4 | ||
![]() |
3e28fd6520 | ||
![]() |
a254e36632 | ||
![]() |
9dbc23a7d4 | ||
![]() |
3fe12b8a2f | ||
![]() |
43dd70979d | ||
![]() |
779c2d5b1a | ||
![]() |
20f16b3984 | ||
![]() |
f95ed885bb | ||
![]() |
63e8dadad9 | ||
![]() |
9b83205b3e | ||
![]() |
5da7b8a59a | ||
![]() |
9c4218c2a8 | ||
![]() |
62747a49b6 | ||
![]() |
8ba3698437 | ||
![]() |
7c3ca1beb5 | ||
![]() |
689d5dd299 | ||
![]() |
372d0ace4a |
22
.clang-tidy
Normal file
22
.clang-tidy
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
Checks: "*,
|
||||
-abseil-*,
|
||||
-altera-*,
|
||||
-android-*,
|
||||
-fuchsia-*,
|
||||
-google-*,
|
||||
-llvm*,
|
||||
-modernize-use-trailing-return-type,
|
||||
-zircon-*,
|
||||
-readability-else-after-return,
|
||||
-readability-static-accessed-through-instance,
|
||||
-readability-avoid-const-params-in-decls,
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-modernize-use-nodiscard,
|
||||
-misc-no-recursion,
|
||||
-readability-implicit-bool-conversion,
|
||||
"
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: ''
|
||||
FormatStyle: none
|
102
.github/workflows/build.yaml
vendored
102
.github/workflows/build.yaml
vendored
@@ -15,58 +15,102 @@ jobs:
|
||||
test:
|
||||
name: "Tests"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Linux GCC
|
||||
os: ubuntu-latest
|
||||
compiler: g++-9
|
||||
test: true
|
||||
compiler: gcc
|
||||
gcov_executable: gcov
|
||||
|
||||
- name: Linux Clang
|
||||
os: ubuntu-latest
|
||||
compiler: clang++
|
||||
test: true
|
||||
compiler: llvm
|
||||
gcov_executable: "llvm-cov gcov"
|
||||
|
||||
- name: MacOS clang
|
||||
os: macos-latest
|
||||
compiler: clang++
|
||||
test: true
|
||||
compiler: llvm
|
||||
gcov_executable: "llvm-cov gcov"
|
||||
|
||||
- name: Windows MSVC
|
||||
os: windows-latest
|
||||
compiler: cl
|
||||
test: false
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: "Checkout repository"
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: "Enable MSVC command prompt"
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: "Setup Cpp"
|
||||
uses: aminya/setup-cpp@v1
|
||||
with:
|
||||
compiler: ${{ matrix.compiler }}
|
||||
vcvarsall: ${{ contains(matrix.os, 'windows' )}}
|
||||
cmake: true
|
||||
ninja: true
|
||||
clangtidy: true
|
||||
cppcheck: false
|
||||
gcovr: true
|
||||
opencppcoverage: true
|
||||
|
||||
- name: "Install cmake"
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: "Build debug mode"
|
||||
# make sure coverage is only enabled for Debug builds, since it sets -O0
|
||||
# to make sure coverage has meaningful results
|
||||
- name: "Configure CMake"
|
||||
run: >
|
||||
mkdir build;
|
||||
cd build;
|
||||
cmake ..
|
||||
-DCMAKE_CXX_COMPILER=${{ matrix.compiler }}
|
||||
-DFTXUI_BUILD_DOCS=OFF
|
||||
-DFTXUI_BUILD_EXAMPLES=ON
|
||||
-DFTXUI_BUILD_TESTS=ON
|
||||
-DFTXUI_BUILD_TESTS_FUZZER=OFF
|
||||
-DFTXUI_ENABLE_INSTALL=ON ;
|
||||
cmake --build . --config Debug;
|
||||
cmake -S .
|
||||
-B ./build
|
||||
-DCMAKE_BUILD_TYPE:STRING=Debug
|
||||
-DFTXUI_ENABLE_COVERAGE:BOOL=ON
|
||||
-DFTXUI_BUILD_DOCS:BOOL=OFF
|
||||
-DFTXUI_BUILD_EXAMPLES:BOOL=ON
|
||||
-DFTXUI_BUILD_TESTS:BOOL=ON
|
||||
-DFTXUI_BUILD_TESTS_FUZZER:BOOL=OFF
|
||||
-DFTXUI_ENABLE_INSTALL:BOOL=ON ;
|
||||
|
||||
- name: "Run tests"
|
||||
if: matrix.test
|
||||
- name: "Build"
|
||||
run: >
|
||||
cd build;
|
||||
./tests
|
||||
cmake
|
||||
--build ./build
|
||||
|
||||
- name: Unix - Test and coverage
|
||||
if: runner.os != 'Windows'
|
||||
working-directory: ./build
|
||||
run: >
|
||||
ctest -C Debug --rerun-failed --output-on-failure;
|
||||
gcovr
|
||||
-j ${{env.nproc}}
|
||||
--delete
|
||||
--root ../
|
||||
--exclude "../examples"
|
||||
--exclude ".*google.*"
|
||||
--exclude ".*test.*"
|
||||
--exclude-unreachable-branches
|
||||
--exclude-throw-branches
|
||||
--sort-uncovered
|
||||
--print-summary
|
||||
--xml-pretty
|
||||
--xml
|
||||
coverage.xml
|
||||
.
|
||||
--gcov-executable '${{ matrix.gcov_executable }}';
|
||||
|
||||
- name: Windows - Test and coverage
|
||||
if: runner.os == 'Windows'
|
||||
working-directory: ./build
|
||||
run: >
|
||||
OpenCppCoverage.exe
|
||||
--export_type cobertura:coverage.xml
|
||||
--cover_children
|
||||
--
|
||||
ctest -C Debug --rerun-failed --output-on-failure;
|
||||
|
||||
- name: Publish to codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
flags: ${{ runner.os }}
|
||||
name: ${{ runner.os }}-coverage
|
||||
files: ./build/coverage.xml
|
||||
|
||||
# Create a release on new v* tags
|
||||
release:
|
||||
@@ -117,7 +161,7 @@ jobs:
|
||||
-DFTXUI_BUILD_TESTS=OFF
|
||||
-DFTXUI_BUILD_TESTS_FUZZER=OFF
|
||||
-DFTXUI_ENABLE_INSTALL=ON;
|
||||
cmake --build . --config Release --target package;
|
||||
cmake --build . --target package;
|
||||
- uses: shogo82148/actions-upload-release-asset@v1
|
||||
with:
|
||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
||||
|
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"platform": [
|
||||
"GTest::+gtest"
|
||||
]
|
||||
}
|
60
CHANGELOG.md
60
CHANGELOG.md
@@ -4,6 +4,66 @@ Changelog
|
||||
current (development)
|
||||
---------------------
|
||||
|
||||
3.0.0
|
||||
-----
|
||||
|
||||
### Build
|
||||
- **breaking**: The library prefix is now back to "lib" (the default). This
|
||||
means non-cmake users should not link against "libftxui-dom" for instance.
|
||||
|
||||
### Component
|
||||
- **Animations** module! Components can implement the `OnAnimation` method and
|
||||
the animation::Animator to define some animated properties.
|
||||
- `Menu` now support animations.
|
||||
- `Button` now supports animations.
|
||||
- Support SIGTSTP. (ctrl+z).
|
||||
- Support task posting. `ScreenInteractive::Post(Task)`.
|
||||
- `Menu` can now be used in the 4 directions, using `MenuOption.direction`.
|
||||
- `Menu` can display an animated underline, using
|
||||
`MenuOption.underline.enabled`.
|
||||
- `Button` is now taking the focus in frame.
|
||||
- **breaking** All the options are now using a transform function.
|
||||
- **breaking** The `Toggle` component is now implemented using `Menu`.
|
||||
- **bugfix** Container::Tab implements `Focusable()`.
|
||||
- **bugfix** Improved default implementations of ComponentBase `Focusable()` and
|
||||
`ActiveChild()` methods.
|
||||
- **bugfix** Automatically convert '\r' keys into '\n' for Linux programs that
|
||||
do not send the correct code for the return key, like the 'bind'.
|
||||
https://github.com/ArthurSonzogni/FTXUI/issues/337
|
||||
- Add decorator for components:
|
||||
- `operator|(Component, ComponentDecorator)`
|
||||
- `operator|(Component, ElementDecorator)`
|
||||
- `operator|=(Component, ComponentDecorator)`
|
||||
- `operator|=(Component, ElementDecorator)`
|
||||
- Add the `Maybe` decorator.
|
||||
- Add the `CatchEvent` decorator.
|
||||
- Add the `Renderer` decorator.
|
||||
- **breaking** remove the "deprectated.hpp" header and Input support for wide
|
||||
string.
|
||||
|
||||
### DOM:
|
||||
- **breaking**: The `inverted` decorator now toggle in the inverted attribute.
|
||||
- Add `gauge` for the 4 directions. Expose the following API:
|
||||
```cpp
|
||||
Element gauge(float ratio);
|
||||
Element gaugeLeft(float ratio);
|
||||
Element gaugeRight(float ratio);
|
||||
Element gaugeUp(float ratio);
|
||||
Element gaugeDown(float ratio);
|
||||
Element gaugeDirection(float ratio, GaugeDirection);
|
||||
```
|
||||
- Add `separatorHSelector` and `separatorVSelector` elements. This can be used
|
||||
to highlight an area.
|
||||
- Add the `automerge` decorator. This makes separator characters to be merged
|
||||
with others nearby.
|
||||
- Fix the `Table` rendering function, to allow automerging characters.
|
||||
- **Bugfix**: The `vscroll_indicator` now computes its offset and size
|
||||
correctly.
|
||||
- Add the `operator|=(Element, Decorator)`
|
||||
|
||||
### Screen:
|
||||
- Add: `Color::Interpolate(lambda, color_a, color_b)`.
|
||||
|
||||
2.0.0
|
||||
-----
|
||||
|
||||
|
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.11)
|
||||
|
||||
project(ftxui
|
||||
LANGUAGES CXX
|
||||
VERSION 2.0.0
|
||||
VERSION 3.0.0
|
||||
)
|
||||
|
||||
option(FTXUI_BUILD_DOCS "Set to ON to build docs" ON)
|
||||
@@ -10,6 +10,8 @@ option(FTXUI_BUILD_EXAMPLES "Set to ON to build examples" ON)
|
||||
option(FTXUI_BUILD_TESTS "Set to ON to build tests" OFF)
|
||||
option(FTXUI_BUILD_TESTS_FUZZER "Set to ON to enable fuzzing" OFF)
|
||||
option(FTXUI_ENABLE_INSTALL "Generate the install target" ON)
|
||||
option(FTXUI_CLANG_TIDY "Execute clang-tidy" OFF)
|
||||
option(FTXUI_ENABLE_COVERAGE "Execute code coverage" OFF)
|
||||
|
||||
set(FTXUI_MICROSOFT_TERMINAL_FALLBACK_HELP_TEXT "On windows, assume the \
|
||||
terminal used will be one of Microsoft and use a set of reasonnable fallback \
|
||||
@@ -22,6 +24,7 @@ else()
|
||||
${FTXUI_MICROSOFT_TERMINAL_FALLBACK_HELP_TEXT} OFF)
|
||||
endif()
|
||||
|
||||
|
||||
add_library(screen
|
||||
include/ftxui/screen/box.hpp
|
||||
include/ftxui/screen/color.hpp
|
||||
@@ -44,6 +47,7 @@ add_library(dom
|
||||
include/ftxui/dom/node.hpp
|
||||
include/ftxui/dom/requirement.hpp
|
||||
include/ftxui/dom/take_any_args.hpp
|
||||
src/ftxui/dom/automerge.cpp
|
||||
src/ftxui/dom/blink.cpp
|
||||
src/ftxui/dom/bold.cpp
|
||||
src/ftxui/dom/border.cpp
|
||||
@@ -83,18 +87,23 @@ add_library(dom
|
||||
)
|
||||
|
||||
add_library(component
|
||||
include/ftxui/component/animation.hpp
|
||||
include/ftxui/component/captured_mouse.hpp
|
||||
include/ftxui/component/component.hpp
|
||||
include/ftxui/component/component_base.hpp
|
||||
include/ftxui/component/component_options.hpp
|
||||
include/ftxui/component/event.hpp
|
||||
include/ftxui/component/mouse.hpp
|
||||
include/ftxui/component/receiver.hpp
|
||||
include/ftxui/component/screen_interactive.hpp
|
||||
include/ftxui/component/task.hpp
|
||||
src/ftxui/component/animation.cpp
|
||||
src/ftxui/component/button.cpp
|
||||
src/ftxui/component/catch_event.cpp
|
||||
src/ftxui/component/checkbox.cpp
|
||||
src/ftxui/component/collapsible.cpp
|
||||
src/ftxui/component/component.cpp
|
||||
src/ftxui/component/component_options.cpp
|
||||
src/ftxui/component/container.cpp
|
||||
src/ftxui/component/dropdown.cpp
|
||||
src/ftxui/component/event.cpp
|
||||
@@ -109,7 +118,7 @@ add_library(component
|
||||
src/ftxui/component/slider.cpp
|
||||
src/ftxui/component/terminal_input_parser.cpp
|
||||
src/ftxui/component/terminal_input_parser.hpp
|
||||
src/ftxui/component/toggle.cpp
|
||||
src/ftxui/component/util.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(dom
|
||||
@@ -131,6 +140,11 @@ ftxui_set_options(screen)
|
||||
ftxui_set_options(dom)
|
||||
ftxui_set_options(component)
|
||||
|
||||
include(cmake/ftxui_coverage.cmake)
|
||||
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()
|
||||
|
92
README.md
92
README.md
@@ -8,10 +8,14 @@
|
||||
<a href="#"><img src="https://img.shields.io/github/repo-size/ArthurSonzogni/FTXUI"></img></a>
|
||||
<a href="https://github.com/ArthurSonzogni/FTXUI/issues"><img src="https://img.shields.io/github/issues/ArthurSonzogni/FTXUI"></img></a>
|
||||
<a href="https://github.com/ArthurSonzogni/FTXUI/graphs/contributors"><img src="https://img.shields.io/github/contributors/arthursonzogni/FTXUI?color=blue"></img></a>
|
||||
<a href="https://codecov.io/gh/ArthurSonzogni/FTXUI">
|
||||
<img src="https://codecov.io/gh/ArthurSonzogni/FTXUI/branch/master/graph/badge.svg?token=C41FdRpNVA"/>
|
||||
</a>
|
||||
|
||||
|
||||
<br/>
|
||||
<a href="https://arthursonzogni.github.io/FTXUI/">Documentation</a> ·
|
||||
<a href="https://github.com/ArthurSonzogni/FTXUI/issues">Report Bug</a> ·
|
||||
<a href="https://github.com/ArthurSonzogni/FTXUI/issues">Report a Bug</a> ·
|
||||
<a href="https://arthursonzogni.github.io/FTXUI/examples.html">Examples</a> .
|
||||
<a href="https://github.com/ArthurSonzogni/FTXUI/issues">Request Feature</a> ·
|
||||
<a href="https://github.com/ArthurSonzogni/FTXUI/pulls">Send a Pull Request</a>
|
||||
@@ -22,21 +26,34 @@
|
||||
|
||||
<i>Functional Terminal (X) User interface</i>
|
||||
|
||||
A simple C++ library for terminal based user interface.
|
||||
A simple C++ library for terminal based user interfaces!
|
||||
|
||||
## Feature
|
||||
* Functional style. Inspired by
|
||||
[[1]](https://hackernoon.com/building-reactive-terminal-interfaces-in-c-d392ce34e649?gi=d9fb9ce35901)
|
||||
and [React](https://reactjs.org/)
|
||||
* Simple and elegant syntax (in my opinion).
|
||||
* Support for [UTF8](https://en.wikipedia.org/wiki/UTF-8) and [fullwidth chars](https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms) (→ 测试).
|
||||
* No dependencies.
|
||||
* Cross platform. Linux/mac (main target), Windows (experimental thanks to contributors), WebAssembly.
|
||||
* 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)
|
||||
* No dependencies
|
||||
* Cross platform: Linux/MacOS (main target), WebAssembly, Windows (Thanks to contributors!).
|
||||
* Learn by [examples](#documentation), and [tutorials](#documentation)
|
||||
* Multiple packages: CMake [FetchContent](https://bewagner.net/programming/2020/05/02/cmake-fetchcontent/) (preferred), vcpkg, pkgbuild, conan.
|
||||
* Good practises: documentation, tests, fuzzers, performance tests, automated CI, automated packaging, etc...
|
||||
|
||||
## Documentation
|
||||
|
||||
- [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)
|
||||
|
||||
## Operating systems
|
||||
|
||||
- Webassembly
|
||||
This is expected to be cross platform. This supports / tests:
|
||||
- WebAssembly
|
||||
- Linux
|
||||
- MacOS
|
||||
- Windows
|
||||
@@ -62,13 +79,6 @@ A simple C++ library for terminal based user interface.
|
||||
└────────────────────────────────────────────────────────────────────────────┘
|
||||
~~~
|
||||
|
||||
## Documentation
|
||||
|
||||
- [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)
|
||||
|
||||
## Short gallery
|
||||
|
||||
#### DOM
|
||||
@@ -120,13 +130,13 @@ An element can be decorated using the functions:
|
||||
|
||||

|
||||
|
||||
FTXUI support the pipe operator. It means: `decorator1(decorator2(element))` and `element | decorator1 | decorator2` can be used.
|
||||
FTXUI supports the pipe operator. It means: `decorator1(decorator2(element))` and `element | decorator1 | decorator2` can be used.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Colors</summary>
|
||||
|
||||
FTXUI support every color palettes:
|
||||
FTXUI support every color palette:
|
||||
|
||||
Color [gallery](https://arthursonzogni.github.io/FTXUI/examples_2dom_2color_gallery_8cpp-example.html):
|
||||

|
||||
@@ -156,7 +166,7 @@ auto document = vbox({
|
||||
|
||||
A simple piece of text is represented using `text("content")`.
|
||||
|
||||
To support text wrapping following spaces the following function are provided:
|
||||
To support text wrapping following spaces the following functions are provided:
|
||||
```cpp
|
||||
Element paragraph(std::string text);
|
||||
Element paragraphAlignLeft(std::string text);
|
||||
@@ -165,7 +175,7 @@ Element paragraphAlignCenter(std::string text);
|
||||
Element paragraphAlignJustify(std::string text);
|
||||
```
|
||||
|
||||
[Paragraph example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2table_8cpp-example.html):
|
||||
[Paragraph example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2paragraph_8cpp-example.html)
|
||||
|
||||

|
||||
|
||||
@@ -312,16 +322,50 @@ Feel free to add your projects here:
|
||||
- [TimeAccumulator](https://github.com/asari555/TimeAccumulator)
|
||||
- [vantage](https://github.com/gokulmaxi/vantage)
|
||||
- [tabdeeli](https://github.com/typon/tabdeeli)
|
||||
- [tiles](https://github.com/tusharpm/tiles)
|
||||
- [cachyos-cli-installer](https://github.com/cachyos/new-cli-installer)
|
||||
|
||||
## Hosted on
|
||||
* [github](https://github.com/ArthurSonzogni/ftxui)
|
||||
* [gitlab](https://gitlab.com/ArthurSonzogni/ftxui)
|
||||
## [cpp-best-practices/game_jam](https://github.com/cpp-best-practices/game_jam)
|
||||
|
||||
Several games using the FTXUI have been made during the Game Jam:
|
||||
- [TermBreaker](https://github.com/ArthurSonzogni/termBreaker) [**[Play web version]**](https://arthursonzogni.com/TermBreaker/)
|
||||
- [Minesweeper Marathon](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/minesweeper_marathon.md) [**[Play web version]**](https://barlasgarden.com/minesweeper/index.html)
|
||||
- [Grand Rounds](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/grandrounds.md)
|
||||
- [LightsRound](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/LightsRound.v.0.1.0.md)
|
||||
- [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)
|
||||
- [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)
|
||||
|
||||
## External package
|
||||
|
||||
It is **highly** recommanded to use cmake FetchContent to depends on FTXUI. This
|
||||
way you can specify which commit you would like to depends on.
|
||||
It is **highly** recommended to use CMake FetchContent to depend on FTXUI. This
|
||||
way you can specify which commit you would like to depend on.
|
||||
```cmake
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(ftxui
|
||||
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
|
||||
GIT_TAG v2.0.0
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(ftxui)
|
||||
if(NOT ftxui_POPULATED)
|
||||
FetchContent_Populate(ftxui)
|
||||
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
```
|
||||
|
||||
If you don't, the following packages have been created:
|
||||
- vcpkg ([soon](https://github.com/ArthurSonzogni/FTXUI/issues/112))
|
||||
- [vcpkg](https://vcpkg.info/port/ftxui)
|
||||
- [Arch Linux PKGBUILD](https://aur.archlinux.org/packages/ftxui-git/).
|
||||
- [conan.io](https://conan.io/center/ftxui)
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/ArthurSonzogni/FTXUI/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=ArthurSonzogni/FTXUI" />
|
||||
</a>
|
||||
|
@@ -2,12 +2,19 @@ if (NOT WIN32)
|
||||
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(NOT googlebenchmark_POPULATED)
|
||||
FetchContent_Populate(googlebenchmark)
|
||||
add_subdirectory(${googlebenchmark_SOURCE_DIR} ${googlebenchmark_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(
|
||||
${googlebenchmark_SOURCE_DIR}
|
||||
${googlebenchmark_BINARY_DIR}
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
endif()
|
||||
|
||||
add_executable(ftxui_benchmark
|
||||
|
8
cmake/ftxui_coverage.cmake
Normal file
8
cmake/ftxui_coverage.cmake
Normal file
@@ -0,0 +1,8 @@
|
||||
function(ftxui_check_coverage library)
|
||||
if (FTXUI_ENABLE_COVERAGE)
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
|
||||
target_compile_options(${library} INTERFACE --coverage -O0 -g)
|
||||
target_link_libraries(${library} INTERFACE --coverage)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
@@ -1,7 +1,20 @@
|
||||
function(ftxui_set_options library)
|
||||
set_target_properties(${library} PROPERTIES PREFIX "ftxui-")
|
||||
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)
|
||||
set_target_properties(${library} PROPERTIES OUTPUT_NAME "ftxui-${library}")
|
||||
|
||||
if(CLANG_TIDY_EXE AND FTXUI_CLANG_TIDY)
|
||||
set_target_properties(${library}
|
||||
PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_EXE};-warnings-as-errors=*"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(${library}
|
||||
PUBLIC
|
||||
$<INSTALL_INTERFACE:include>
|
||||
|
@@ -1,55 +1,82 @@
|
||||
enable_testing()
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
|
||||
set(FETCHCONTENT_UPDATES_DISCONNECTED TRUE)
|
||||
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)
|
||||
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||
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
|
||||
)
|
||||
endif()
|
||||
|
||||
add_executable(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/input_test.cpp
|
||||
src/ftxui/component/menu_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/terminal_input_parser_test.cpp
|
||||
src/ftxui/component/toggle_test.cpp
|
||||
src/ftxui/dom/blink_test.cpp
|
||||
src/ftxui/dom/bold_test.cpp
|
||||
src/ftxui/dom/border_test.cpp
|
||||
src/ftxui/dom/canvas_test.cpp
|
||||
src/ftxui/dom/color_test.cpp
|
||||
src/ftxui/dom/dbox_test.cpp
|
||||
src/ftxui/dom/dim_test.cpp
|
||||
src/ftxui/dom/flexbox_helper_test.cpp
|
||||
src/ftxui/dom/flexbox_test.cpp
|
||||
src/ftxui/dom/gauge_test.cpp
|
||||
src/ftxui/dom/gridbox_test.cpp
|
||||
src/ftxui/dom/hbox_test.cpp
|
||||
src/ftxui/dom/scroll_indicator_test.cpp
|
||||
src/ftxui/dom/separator_test.cpp
|
||||
src/ftxui/dom/spinner_test.cpp
|
||||
src/ftxui/dom/table_test.cpp
|
||||
src/ftxui/dom/text_test.cpp
|
||||
src/ftxui/dom/underlined_test.cpp
|
||||
src/ftxui/dom/vbox_test.cpp
|
||||
src/ftxui/screen/color_test.cpp
|
||||
src/ftxui/screen/string_test.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(tests
|
||||
PRIVATE component
|
||||
PRIVATE gtest
|
||||
PRIVATE gmock
|
||||
PRIVATE gtest_main
|
||||
)
|
||||
target_include_directories(tests
|
||||
PRIVATE src
|
||||
)
|
||||
target_compile_options(tests PRIVATE -fsanitize=address)
|
||||
target_link_libraries(tests PRIVATE -fsanitize=address)
|
||||
ftxui_set_options(tests)
|
||||
|
||||
if (NOT MSVC)
|
||||
include(cmake/ftxui_benchmark.cmake)
|
||||
endif()
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(tests
|
||||
DISCOVERY_TIMEOUT 600
|
||||
)
|
||||
|
||||
|
||||
include(cmake/ftxui_benchmark.cmake)
|
||||
|
||||
if (FTXUI_BUILD_TESTS_FUZZER)
|
||||
include(cmake/ftxui_fuzzer.cmake)
|
||||
|
425
doc/mainpage.md
425
doc/mainpage.md
@@ -1,14 +1,19 @@
|
||||
\mainpage
|
||||
|
||||
# Introduction
|
||||
# Introduction {#introduction}
|
||||
|
||||
Welcome to the FTXUI documentation. Here, you will find the detail of every
|
||||
functions and classes.
|
||||
Welcome to the FTXUI documentation!
|
||||
|
||||
This is a brief tutorial. You are also encouraged to learn, by reading the
|
||||
[examples](./examples.html)
|
||||
|
||||
@tableofcontents
|
||||
|
||||
**Short example**
|
||||
|
||||
To build a single frame, you need create an `ftxui::Element`, and display it on
|
||||
a `ftxui::Screen`.
|
||||
|
||||
**main.cpp**
|
||||
```cpp
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
@@ -37,6 +42,7 @@ int main(void) {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**output**
|
||||
```bash
|
||||
┌────┐┌─────────────────────────────────────────────────────────────────┐┌─────┐
|
||||
@@ -44,9 +50,9 @@ int main(void) {
|
||||
└────┘└─────────────────────────────────────────────────────────────────┘└─────┘
|
||||
```
|
||||
|
||||
# Build
|
||||
# Build {#build}
|
||||
|
||||
## Using CMake
|
||||
## Using CMake {#build-cmake}
|
||||
|
||||
CMakeLists.txt
|
||||
~~~cmake
|
||||
@@ -58,7 +64,7 @@ include(FetchContent)
|
||||
set(FETCHCONTENT_UPDATES_DISCONNECTED TRUE)
|
||||
FetchContent_Declare(ftxui
|
||||
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
|
||||
# Specify a GIT_TAG here.
|
||||
# Important: Specify a GIT_TAG XXXXX here.
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(ftxui)
|
||||
@@ -86,16 +92,16 @@ target_link_libraries(ftxui-starter
|
||||
~~~
|
||||
|
||||
Build
|
||||
~~~
|
||||
~~~sh
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make
|
||||
./main
|
||||
~~~
|
||||
|
||||
# List of modules.
|
||||
# List of modules. {#modules}
|
||||
|
||||
The project is split into 3 modules:
|
||||
The project is made from into 3 modules:
|
||||
|
||||
1. **ftxui/screen** defines a `ftxui::Screen`, this is a grid of `ftxui::Pixel`.
|
||||
|
||||
@@ -108,7 +114,7 @@ The project is split into 3 modules:
|
||||
using the arrow keys and interact with widgets like checkbox/inputbox/... You
|
||||
can make you own components.
|
||||
|
||||
# screen
|
||||
# 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...).
|
||||
@@ -132,31 +138,41 @@ The screen can be printed as a string using `ftxui::Screen::ToString()`.
|
||||
}
|
||||
~~~
|
||||
|
||||
# dom
|
||||
# dom {#module-dom}
|
||||
|
||||
This module defines a hierarchical set of Element. An element manages layout and
|
||||
can be responsive to the terminal dimensions.
|
||||
This module defines a hierarchical set of `ftxui::Element`. An element manages
|
||||
layout and can be responsive to the terminal dimensions.
|
||||
|
||||
**Example:**
|
||||
```cpp
|
||||
// Define the document
|
||||
Element document = vbox({
|
||||
text("The window") | bold | color(Color::Blue),
|
||||
gauge(0.5)
|
||||
text("The footer")
|
||||
});
|
||||
text("The window") | bold | color(Color::Blue),
|
||||
gauge(0.5)
|
||||
text("The footer")
|
||||
});
|
||||
|
||||
// Add a border.
|
||||
// Add a border, by calling the `ftxui::border` decorator function.
|
||||
document = border(document);
|
||||
|
||||
// Add another border, using the pipe operator.
|
||||
document = document | border.
|
||||
|
||||
// Add another border, using the |= operator.
|
||||
document |= border
|
||||
|
||||
```
|
||||
|
||||
**List of elements**
|
||||
|
||||
You only need one header: ftxui/dom/elements.hpp
|
||||
They are all defined inside:
|
||||
```cpp
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
```
|
||||
|
||||
\include ftxui/dom/elements.hpp
|
||||
|
||||
## text
|
||||
## text ## {#dom-text}
|
||||
|
||||
The most simple widget. It displays a text.
|
||||
~~~cpp
|
||||
@@ -166,7 +182,44 @@ text("I am a piece of text");
|
||||
I am a piece of text.
|
||||
~~~
|
||||
|
||||
## border
|
||||
## vtext {#dom-vtext}
|
||||
|
||||
Same as `ftxui::text`, but vertical.
|
||||
~~~cpp
|
||||
vtext("HELLO");
|
||||
~~~
|
||||
~~~bash
|
||||
H
|
||||
E
|
||||
L
|
||||
L
|
||||
O
|
||||
~~~
|
||||
|
||||
## paragraph {#dom-paragraph}
|
||||
|
||||
```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:
|
||||
```cpp
|
||||
Element paragraph(std::string text);
|
||||
Element paragraphAlignLeft(std::string text);
|
||||
Element paragraphAlignRight(std::string text);
|
||||
Element paragraphAlignCenter(std::string text);
|
||||
Element paragraphAlignJustify(std::string text);
|
||||
```
|
||||
|
||||
|
||||
## border {#dom-border}
|
||||
|
||||
Add a border around an element
|
||||
~~~cpp
|
||||
@@ -179,7 +232,27 @@ border(text("The element"))
|
||||
└───────────┘
|
||||
~~~
|
||||
|
||||
## window
|
||||
Same, with the pipe operator:
|
||||
|
||||
```cpp
|
||||
text("The element") | border
|
||||
```
|
||||
|
||||
Border come with different styles.
|
||||
See:
|
||||
```cpp
|
||||
Element border(Element);
|
||||
Element borderLight(Element);
|
||||
Element borderHeavy(Element);
|
||||
Element borderDouble(Element);
|
||||
Element borderRounded(Element);
|
||||
Element borderEmpty(Element);
|
||||
Decorator borderStyled(BorderStyle);
|
||||
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
|
||||
@@ -193,7 +266,7 @@ window("The window", text("The element"))
|
||||
└───────────┘
|
||||
~~~
|
||||
|
||||
## separator
|
||||
## separator {#dom-separator}
|
||||
|
||||
Display a vertical or horizontal line to visually split the content of a
|
||||
container in two.
|
||||
@@ -214,7 +287,29 @@ border(
|
||||
└────┴─────┘
|
||||
~~~
|
||||
|
||||
## gauge
|
||||
|
||||
Separators come with different styles:
|
||||
See:
|
||||
```cpp
|
||||
Element separator(void);
|
||||
Element separatorLight();
|
||||
Element separatorHeavy();
|
||||
Element separatorDouble();
|
||||
Element separatorEmpty();
|
||||
Element separatorStyled(BorderStyle);
|
||||
Element separator(Pixel);
|
||||
Element separatorCharacter(std::string);
|
||||
Element separatorHSelector(float left,
|
||||
float right,
|
||||
Color background,
|
||||
Color foreground);
|
||||
Element separatorVSelector(float up,
|
||||
float down,
|
||||
Color background,
|
||||
Color foreground);
|
||||
```
|
||||
|
||||
## gauge {#dom-gauge}
|
||||
|
||||
A gauge. It can be used to represent a progress bar.
|
||||
~~~cpp
|
||||
@@ -227,13 +322,28 @@ border(gauge(0.5))
|
||||
└────────────────────────────────────────────────────────────────────────────┘
|
||||
~~~
|
||||
|
||||
## graph
|
||||
A gauge can be displayed into several directions. See:
|
||||
```cpp
|
||||
Element gauge(float ratio);
|
||||
Element gaugeLeft(float ratio);
|
||||
Element gaugeRight(float ratio);
|
||||
Element gaugeUp(float ratio);
|
||||
Element gaugeDown(float ratio);
|
||||
Element gaugeDirection(float ratio, GaugeDirection);
|
||||
```
|
||||
|
||||
## graph {#dom-graph}
|
||||
|
||||
@htmlonly
|
||||
<script id="asciicast-223726" src="https://asciinema.org/a/223726.js" async></script>
|
||||
@endhtmlonly
|
||||
|
||||
## Colors
|
||||
See:
|
||||
```cpp
|
||||
Element graph(GraphFunction);
|
||||
```
|
||||
|
||||
## Colors {#dom-colors}
|
||||
A terminal console can usually display colored text and colored background.
|
||||
|
||||
~~~cpp
|
||||
@@ -241,7 +351,12 @@ Decorator color(Color);
|
||||
Decorator bgcolor(Color);
|
||||
~~~
|
||||
|
||||
### Palette16
|
||||
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:
|
||||
- Default
|
||||
@@ -277,7 +392,7 @@ text("Blue background") | bgcolor(Color::Blue);
|
||||
text("Black on white") | color(Color::Black) | bgcolor(Color::White);
|
||||
```
|
||||
|
||||
### Palette256
|
||||
### Palette256 #{#dom-colors-palette-256}
|
||||
|
||||
On terminal supporting 256 colors.
|
||||
@htmlonly
|
||||
@@ -288,7 +403,7 @@ On terminal supporting 256 colors.
|
||||
text("HotPink") | color(Color::HotPink);
|
||||
```
|
||||
|
||||
### TrueColor
|
||||
### TrueColor #{#dom-colors-true-color}
|
||||
|
||||
On terminal supporting trueColor, you can directly chose the 24bit RGB color:
|
||||
|
||||
@@ -303,7 +418,7 @@ ftxui::Color::HSV(uint8_t hue, uint8_t saturation, uint8_t value);
|
||||
<script id="asciicast-xwzzghmqcqzIuyLwCpQFEqbEu" src="https://asciinema.org/a/xwzzghmqcqzIuyLwCpQFEqbEu.js" async></script>
|
||||
@endhtmlonly
|
||||
|
||||
## Style
|
||||
## 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.
|
||||
@@ -318,6 +433,10 @@ Decorator color(Color);
|
||||
Decorator bgcolor(Color);
|
||||
~~~
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2style_gallery_8cpp-example.html)
|
||||
|
||||

|
||||
|
||||
Example:
|
||||
~~~cpp
|
||||
underlined(bold(text("This text is bold and underlined")))
|
||||
@@ -328,22 +447,30 @@ Tips: The pipe operator can be used to chain Decorator:
|
||||
text("This text is bold")) | bold | underlined
|
||||
~~~
|
||||
|
||||
## Layout
|
||||
## Layout {#dom-layout}
|
||||
|
||||
These layout are similar to the HTML flexbox:
|
||||
* vbox (Vertical-box)
|
||||
* hbox (Horizontal-box)
|
||||
* dbox (Z-axis-box)
|
||||
They are used to compose all the elements together. Each
|
||||
children are put side by side. If the container is flexible, the extra space
|
||||
available will be shared among the remaining flexible children.
|
||||
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`.
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2vbox_hbox_8cpp-example.html) using `ftxui::hbox`, `ftxui::vbox` and `ftxui::filler`.
|
||||
|
||||
`flex(element)` can be used to make a non-flexible element flexible. `filler()`
|
||||
is a flexible empty element. You can use it align children on one side of the
|
||||
container.
|
||||

|
||||
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2gridbox_8cpp-example.htmlp) using `ftxui::gridbox`:
|
||||
|
||||
An horizontal flow layout is implemented by:
|
||||
* hflow (Horizontal flow)
|
||||

|
||||
|
||||
[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).
|
||||
|
||||
Element can become flexible using the the `ftxui::flex` decorator.
|
||||
|
||||
**Examples**
|
||||
~~~cpp
|
||||
@@ -372,11 +499,39 @@ An horizontal flow layout is implemented by:
|
||||
└────┘└───────────────────────────────────┘└───────────────────────────────────┘
|
||||
~~~
|
||||
|
||||
## Table {#dom-table}
|
||||
|
||||
# component
|
||||
A class to easily style a table of data.
|
||||
|
||||
The `ftxui/component` directory defines the logic to get produce
|
||||
interactive component responding to user's events (keyboard, mouse, etc...)
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2table_8cpp-example.html):
|
||||
|
||||

|
||||
|
||||
## Canvas {#dom-canvas}
|
||||
|
||||
See [<ftxui/dom/canvas.hpp>](./canvas_8hpp_source.html)
|
||||
|
||||
```cpp
|
||||
auto c = Canvas(100, 100);
|
||||
c.DrawPointLine(10, 10, 80, 10, Color::Red);
|
||||
auto element = canvas(c);
|
||||
```
|
||||
|
||||
Drawing can be made 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):
|
||||
|
||||

|
||||
|
||||
# component {#module-component}
|
||||
|
||||
The `ftxui::component`module defines the logic to produce interactive component
|
||||
responding to user's events (keyboard, mouse, etc...)
|
||||
|
||||
A `ftxui::ScreenInteractive` defines a main loop to render a component.
|
||||
|
||||
@@ -388,15 +543,23 @@ defines
|
||||
two component. This defines a tree a components, which help properly define
|
||||
how keyboard navigation works.
|
||||
|
||||
Predefined components are available in `ftxui/dom/component.hpp`:
|
||||
`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))
|
||||
|
||||

|
||||
|
||||
Predefined components are available in ["ftxui/dom/component.hpp"](./component_8hpp.html)
|
||||
|
||||
\include ftxui/component/component.hpp
|
||||
|
||||
Element are stateless object. On the other side, components are used when an
|
||||
internal state is needed. Components are used to interact with the user with
|
||||
its keyboard. They handle keyboard navigation, including component focus.
|
||||
## Input {#component-input}
|
||||
|
||||
## Input
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2input_8cpp-example.html):
|
||||
|
||||

|
||||
|
||||
Produced by: `ftxui::Input()` from "ftxui/component/component.hpp"
|
||||
|
||||
@@ -404,7 +567,12 @@ Produced by: `ftxui::Input()` from "ftxui/component/component.hpp"
|
||||
<script id="asciicast-223719" src="https://asciinema.org/a/223719.js" async></script>
|
||||
@endhtmlonly
|
||||
|
||||
## Menu
|
||||
## Menu {#component-menu}
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2menu_8cpp-example.html):
|
||||
|
||||

|
||||
|
||||
|
||||
Produced by: `ftxui::Menu()` from "ftxui/component/component.hpp"
|
||||
|
||||
@@ -412,7 +580,11 @@ Produced by: `ftxui::Menu()` from "ftxui/component/component.hpp"
|
||||
<script id="asciicast-223720" src="https://asciinema.org/a/223720.js" async></script>
|
||||
@endhtmlonly
|
||||
|
||||
## Toggle.
|
||||
## Toggle {#component-toggle}
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2toggle_8cpp-example.html):
|
||||
|
||||

|
||||
|
||||
Produced by: `ftxui::Toggle()` from "ftxui/component/component.hpp"
|
||||
|
||||
@@ -420,7 +592,11 @@ Produced by: `ftxui::Toggle()` from "ftxui/component/component.hpp"
|
||||
<script id="asciicast-223722" src="https://asciinema.org/a/223722.js" async></script>
|
||||
@endhtmlonly
|
||||
|
||||
## CheckBox
|
||||
## CheckBox {#component-checkbox}
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2checkbox_8cpp-example.html):
|
||||
|
||||

|
||||
|
||||
Produced by: `ftxui::Checkbox()` from "ftxui/component/component.hpp"
|
||||
|
||||
@@ -428,7 +604,11 @@ Produced by: `ftxui::Checkbox()` from "ftxui/component/component.hpp"
|
||||
<script id="asciicast-223724" src="https://asciinema.org/a/223724.js" async></script>
|
||||
@endhtmlonly
|
||||
|
||||
## RadioBox
|
||||
## RadioBox {#component-radiobox}
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2radiobox_8cpp-example.html):
|
||||
|
||||

|
||||
|
||||
Produced by: `ftxui::Radiobox()` from "ftxui/component/component.hpp"
|
||||
|
||||
@@ -436,37 +616,154 @@ Produced by: `ftxui::Radiobox()` from "ftxui/component/component.hpp"
|
||||
<script id="asciicast-223725" src="https://asciinema.org/a/223725.js" async></script>
|
||||
@endhtmlonly
|
||||
|
||||
## Renderer
|
||||
## Dropdown {#component-dropdown}
|
||||
|
||||
Produced by: `ftxui::Renderer()` from \ref "ftxui/component/component.hpp". This
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2dropdown_8cpp-example.html):
|
||||
|
||||

|
||||
|
||||
Produced by: `ftxui::Dropdown()` from "ftxui/component/component.hpp"
|
||||
|
||||
## Slider {#component-slider}
|
||||
|
||||
[Example](https://arthursonzogni.github.io/FTXUI/examples_2component_2slider_8cpp-example.html):
|
||||
|
||||

|
||||
|
||||
Produced by: `ftxui::Slider()` from "ftxui/component/component.hpp"
|
||||
|
||||
## Renderer {#component-renderer}
|
||||
|
||||
Produced by: `ftxui::Renderer()` from \ref 'ftxui/component/component.hpp'. This
|
||||
component decorate another one by using a different function to render an
|
||||
interface.
|
||||
|
||||
## CatchEvent
|
||||
Example:
|
||||
```cpp
|
||||
auto inner = [...]
|
||||
|
||||
Produced by: `ftxui::CatchEvent()` from \ref "ftxui/component/component.hpp".
|
||||
auto renderer = Renderer(inner, [&] {
|
||||
return inner->Render() | border
|
||||
});
|
||||
```
|
||||
|
||||
`ftxui::Renderer` also support the component decorator pattern:
|
||||
```cpp
|
||||
auto component = [...]
|
||||
component = component
|
||||
| Renderer([] (Element e) { return e | border))
|
||||
| Renderer(bold)
|
||||
```
|
||||
|
||||
As a short hand, you can also compose a component with an element decorator:
|
||||
```cpp
|
||||
auto component = [...]
|
||||
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.
|
||||
|
||||
## Container::Horizontal
|
||||
Examples:
|
||||
```cpp
|
||||
auto screen = ScreenInteractive::TerminalOutput();
|
||||
auto renderer = Renderer([] {
|
||||
return text("My interface");
|
||||
});
|
||||
auto component = CatchEvent(renderer, [&](Event event) {
|
||||
if (event == Event::Character('q')) {
|
||||
screen.ExitLoopClosure()();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
screen.Loop(component);
|
||||
```
|
||||
|
||||
The `ftxui::CatchEvent` can also be used as a decorator:
|
||||
```cpp
|
||||
component = component
|
||||
| CatchEvent(handler_1)
|
||||
| CatchEvent(handler_2)
|
||||
| CatchEvent(handler_3)
|
||||
;
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
```cpp
|
||||
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.
|
||||
|
||||
Example with a boolean:
|
||||
```cpp
|
||||
bool show = true;
|
||||
auto component = Renderer([]{ return "Hello World!"; });
|
||||
auto maybe_component = Maybe(component, &show)
|
||||
```
|
||||
|
||||
Example with a predicate:
|
||||
```cpp
|
||||
auto component = Renderer([]{ return "Hello World!"; });
|
||||
auto maybe_component = Maybe(component, [&] { return time > 10; })
|
||||
```
|
||||
|
||||
`ftxui::Maybe` can be used as a decorator.
|
||||
|
||||
```
|
||||
component = component
|
||||
| Maybe(&a_boolean)
|
||||
| Maybe([&] { return time > 10; })
|
||||
;
|
||||
```
|
||||
|
||||
## Container {#component-container}
|
||||
|
||||
### Horizontal {#component-horizontal}
|
||||
|
||||
Produced by: `ftxui::Container::Horizontal()` from
|
||||
"ftxui/component/component.hpp". It displays a list of components horizontally
|
||||
and handle keyboard/mouse navigation.
|
||||
|
||||
## Container::Vertial
|
||||
### Vertical {#component-vertical}
|
||||
|
||||
Produced by: `ftxui::Container::Vertical()` from
|
||||
"ftxui/component/component.hpp". It displays a list of components vertically
|
||||
and handles keyboard/mouse navigation.
|
||||
|
||||
## Container::Tab
|
||||
### Tab {#component-tab}
|
||||
|
||||
Produced by: `ftxui::Container::Tab()` from
|
||||
"ftxui/component/component.hpp". It take a list of component and display only
|
||||
one of them. This is useful for implementing a tab bar.
|
||||
|
||||
## ResizableSplit::{Left, Right, Top, Bottom}
|
||||
[Vertical](https://arthursonzogni.github.io/FTXUI/examples_2component_2tab_vertical_8cpp-example.html):
|
||||
|
||||

|
||||
|
||||
[Horizontal](https://arthursonzogni.github.io/FTXUI/examples_2component_2tab_horizontal_8cpp-example.html):
|
||||
|
||||

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

|
||||
|
||||
Produced by:
|
||||
- `ftxui::ResizableSplitLeft()`
|
||||
@@ -483,7 +780,7 @@ mouse.
|
||||
<script id="asciicast-tprMH2EdkUoMb7D2YxgMGgpzx" src="https://asciinema.org/a/tprMH2EdkUoMb7D2YxgMGgpzx.js" async></script>
|
||||
@endhtmlonly
|
||||
|
||||
## Force a frame redraw.
|
||||
## 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
|
||||
|
@@ -10,6 +10,12 @@ add_subdirectory(component)
|
||||
add_subdirectory(dom)
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
# 32MB should be enough to run all the examples, in debug mode.
|
||||
target_link_options(component PUBLIC "SHELL: -s TOTAL_MEMORY=33554432")
|
||||
target_link_options(component PUBLIC "SHELL: -s ASSERTIONS=1")
|
||||
#string(APPEND CMAKE_EXE_LINKER_FLAGS " -s ALLOW_MEMORY_GROWTH=1")
|
||||
#target_link_options(component PUBLIC "SHELL: -s ALLOW_MEMORY_GROWTH=1")
|
||||
|
||||
get_property(EXAMPLES GLOBAL PROPERTY FTXUI::EXAMPLES)
|
||||
foreach(file
|
||||
"index.html"
|
||||
|
@@ -1,6 +1,9 @@
|
||||
set(DIRECTORY_LIB component)
|
||||
|
||||
example(button)
|
||||
example(button_animated)
|
||||
example(button_in_frame)
|
||||
example(button_style)
|
||||
example(canvas_animated)
|
||||
example(checkbox)
|
||||
example(checkbox_in_frame)
|
||||
@@ -16,9 +19,11 @@ example(maybe)
|
||||
example(menu)
|
||||
example(menu2)
|
||||
example(menu_entries)
|
||||
example(menu_entries_animated)
|
||||
example(menu_in_frame)
|
||||
example(menu_multiple)
|
||||
example(menu_style)
|
||||
example(menu_underline_animated_gallery)
|
||||
example(modal_dialog)
|
||||
example(nested_screen)
|
||||
example(print_key_press)
|
||||
|
@@ -1,12 +1,11 @@
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access
|
||||
#include <string> // for operator+, to_wstring
|
||||
#include <string> // for operator+, to_string
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for separator, gauge, Element, operator|, vbox, border
|
||||
#include "ftxui/dom/elements.hpp" // for separator, gauge, text, Element, operator|, vbox, border
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
@@ -14,13 +13,9 @@ int main(int argc, const char* argv[]) {
|
||||
int value = 50;
|
||||
|
||||
// The tree of components. This defines how to navigate using the keyboard.
|
||||
auto button_option = ButtonOption();
|
||||
button_option.border = false;
|
||||
auto buttons = Container::Horizontal({
|
||||
Button(
|
||||
"[Decrease]", [&] { value--; }, &button_option),
|
||||
Button(
|
||||
"[Increase]", [&] { value++; }, &button_option),
|
||||
Button("Decrease", [&] { value--; }),
|
||||
Button("Increase", [&] { value++; }),
|
||||
});
|
||||
|
||||
// Modify the way to render them on screen:
|
||||
|
46
examples/component/button_animated.cpp
Normal file
46
examples/component/button_animated.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access
|
||||
#include <string> // for operator+, to_string
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for gauge, separator, text, vbox, operator|, Element, border
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::Green, Color::Red
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
int value = 50;
|
||||
|
||||
// The tree of components. This defines how to navigate using the keyboard.
|
||||
auto buttons = Container::Horizontal({
|
||||
Button(
|
||||
"Decrease", [&] { value--; }, ButtonOption::Animated(Color::Red)),
|
||||
Button(
|
||||
"Reset", [&] { value = 50; }, ButtonOption::Animated(Color::Green)),
|
||||
Button(
|
||||
"Increase", [&] { value++; }, ButtonOption::Animated(Color::Blue)),
|
||||
});
|
||||
|
||||
// Modify the way to render them on screen:
|
||||
auto component = Renderer(buttons, [&] {
|
||||
return vbox({
|
||||
vbox({
|
||||
text("value = " + std::to_string(value)),
|
||||
separator(),
|
||||
gauge(value * 0.01f),
|
||||
}) | border,
|
||||
buttons->Render(),
|
||||
});
|
||||
});
|
||||
|
||||
auto screen = ScreenInteractive::FitComponent();
|
||||
screen.Loop(component);
|
||||
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.
|
49
examples/component/button_in_frame.cpp
Normal file
49
examples/component/button_in_frame.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <memory> // for allocator, __shared_ptr_access, shared_ptr
|
||||
#include <string> // for to_string, operator+
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Button, Renderer, Vertical
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, text, Element, hbox, separator, size, vbox, border, frame, vscroll_indicator, HEIGHT, LESS_THAN
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Default, Color::GrayDark, Color::White
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
int counter = 0;
|
||||
auto on_click = [&] { counter++; };
|
||||
|
||||
auto button_style = ButtonOption::Animated(Color::Default, Color::GrayDark,
|
||||
Color::Default, Color::White);
|
||||
|
||||
auto container = Container::Vertical({});
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
auto button =
|
||||
Button("Button " + std::to_string(i), on_click, &button_style);
|
||||
container->Add(button);
|
||||
}
|
||||
|
||||
auto renderer = Renderer(container, [&] {
|
||||
return vbox({
|
||||
hbox({
|
||||
text("Counter:"),
|
||||
text(std::to_string(counter)),
|
||||
}),
|
||||
separator(),
|
||||
container->Render() | vscroll_indicator | frame |
|
||||
size(HEIGHT, LESS_THAN, 20),
|
||||
}) |
|
||||
border;
|
||||
});
|
||||
|
||||
auto screen = ScreenInteractive::FitComponent();
|
||||
screen.Loop(renderer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 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.
|
62
examples/component/button_style.cpp
Normal file
62
examples/component/button_style.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for operator+, to_string
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Button, Vertical, Renderer, Horizontal, operator|
|
||||
#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/screen/color.hpp" // for Color, Color::Blue, Color::Green, Color::Red
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
int value = 0;
|
||||
auto action = [&] { value++; };
|
||||
auto action_renderer =
|
||||
Renderer([&] { return text("count = " + std::to_string(value)); });
|
||||
|
||||
auto buttons =
|
||||
Container::Vertical({
|
||||
action_renderer,
|
||||
Renderer([] { return separator(); }),
|
||||
Container::Horizontal({
|
||||
Container::Vertical({
|
||||
Button("Ascii 1", action, ButtonOption::Ascii()),
|
||||
Button("Ascii 2", action, ButtonOption::Ascii()),
|
||||
Button("Ascii 3", action, ButtonOption::Ascii()),
|
||||
}),
|
||||
Renderer([] { return separator(); }),
|
||||
Container::Vertical({
|
||||
Button("Simple 1", action, ButtonOption::Simple()),
|
||||
Button("Simple 2", action, ButtonOption::Simple()),
|
||||
Button("Simple 3", action, ButtonOption::Simple()),
|
||||
}),
|
||||
Renderer([] { return separator(); }),
|
||||
Container::Vertical({
|
||||
Button("Animated 1", action, ButtonOption::Animated()),
|
||||
Button("Animated 2", action, ButtonOption::Animated()),
|
||||
Button("Animated 3", action, ButtonOption::Animated()),
|
||||
}),
|
||||
Renderer([] { return separator(); }),
|
||||
Container::Vertical({
|
||||
Button("Animated 4", action,
|
||||
ButtonOption::Animated(Color::Red)),
|
||||
Button("Animated 5", action,
|
||||
ButtonOption::Animated(Color::Green)),
|
||||
Button("Animated 6", action,
|
||||
ButtonOption::Animated(Color::Blue)),
|
||||
}),
|
||||
}),
|
||||
}) |
|
||||
border;
|
||||
|
||||
auto screen = ScreenInteractive::FitComponent();
|
||||
screen.Loop(buttons);
|
||||
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.
|
@@ -126,9 +126,9 @@ int main(int argc, const char* argv[]) {
|
||||
|
||||
std::vector<int> ys(100);
|
||||
for (int x = 0; x < 100; x++) {
|
||||
float dx = x - mouse_x;
|
||||
float dy = 50;
|
||||
ys[x] = dy + 20 * cos(dx * 0.14) + 10 * sin(dx * 0.42);
|
||||
float dx = float(x - mouse_x);
|
||||
float dy = 50.f;
|
||||
ys[x] = int(dy + 20 * cos(dx * 0.14) + 10 * sin(dx * 0.42));
|
||||
}
|
||||
for (int x = 1; x < 99; x++)
|
||||
c.DrawPointLine(x, ys[x], x + 1, ys[x + 1]);
|
||||
@@ -141,10 +141,10 @@ int main(int argc, const char* argv[]) {
|
||||
c.DrawText(0, 0, "A symmetrical graph filled");
|
||||
std::vector<int> ys(100);
|
||||
for (int x = 0; x < 100; x++) {
|
||||
ys[x] = 30 + //
|
||||
10 * cos(x * 0.2 - mouse_x * 0.05) + //
|
||||
5 * sin(x * 0.4) + //
|
||||
5 * sin(x * 0.3 - mouse_y * 0.05); //
|
||||
ys[x] = int(30 + //
|
||||
10 * cos(x * 0.2 - mouse_x * 0.05) + //
|
||||
5 * sin(x * 0.4) + //
|
||||
5 * sin(x * 0.3 - mouse_y * 0.05)); //
|
||||
}
|
||||
for (int x = 0; x < 100; x++) {
|
||||
c.DrawPointLine(x, 50 + ys[x], x, 50 - ys[x], Color::Red);
|
||||
|
@@ -1,26 +1,22 @@
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access, allocator_traits<>::value_type
|
||||
#include <array> // for array
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access
|
||||
#include <string> // for operator+, to_string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Checkbox, Renderer, Vertical
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, size, border, frame, HEIGHT, LESS_THAN
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, size, border, frame, vscroll_indicator, HEIGHT, LESS_THAN
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
struct CheckboxState {
|
||||
bool checked;
|
||||
};
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
std::vector<CheckboxState> states(30);
|
||||
std::array<bool, 30> states;
|
||||
|
||||
auto container = Container::Vertical({});
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
states[i].checked = false;
|
||||
container->Add(
|
||||
Checkbox("Checkbox" + std::to_string(i), &states[i].checked));
|
||||
states[i] = false;
|
||||
container->Add(Checkbox("Checkbox" + std::to_string(i), &states[i]));
|
||||
}
|
||||
|
||||
auto renderer = Renderer(container, [&] {
|
||||
|
@@ -4,7 +4,6 @@
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for text, separator, Element, operator|, vbox, border
|
||||
|
||||
@@ -13,24 +12,17 @@ using namespace ftxui;
|
||||
// An example of how to compose multiple components into one and maintain their
|
||||
// interactiveness.
|
||||
int main(int argc, const char* argv[]) {
|
||||
auto button_option = ButtonOption();
|
||||
button_option.border = false;
|
||||
|
||||
auto left_count = 0;
|
||||
auto right_count = 0;
|
||||
|
||||
auto left_buttons = Container::Horizontal({
|
||||
Button(
|
||||
"[Decrease]", [&] { left_count--; }, &button_option),
|
||||
Button(
|
||||
"[Increase]", [&] { left_count++; }, &button_option),
|
||||
Button("Decrease", [&] { left_count--; }),
|
||||
Button("Increase", [&] { left_count++; }),
|
||||
});
|
||||
|
||||
auto right_buttons = Container::Horizontal({
|
||||
Button(
|
||||
"[Decrease]", [&] { right_count--; }, &button_option),
|
||||
Button(
|
||||
"[Increase]", [&] { right_count++; }, &button_option),
|
||||
Button("Decrease", [&] { right_count--; }),
|
||||
Button("Increase", [&] { right_count++; }),
|
||||
});
|
||||
|
||||
// Renderer decorates its child with a new rendering function. The way the
|
||||
|
@@ -32,8 +32,8 @@ Element make_grid() {
|
||||
};
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
float focus_x = 0.0f;
|
||||
float focus_y = 0.0f;
|
||||
float focus_x = 0.5f;
|
||||
float focus_y = 0.5f;
|
||||
|
||||
auto slider_x = Slider("x", &focus_x, 0.f, 1.f, 0.01f);
|
||||
auto slider_y = Slider("y", &focus_y, 0.f, 1.f, 0.01f);
|
||||
|
@@ -9,15 +9,17 @@
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Checkbox, Renderer, Horizontal, Vertical, Input, Menu, Radiobox, ResizableSplitLeft, Tab, Toggle
|
||||
#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_options.hpp" // for InputOption
|
||||
#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
|
||||
#include "ftxui/dom/elements.hpp" // for text, operator|, color, bgcolor, filler, Element, size, vbox, flex, hbox, separator, graph, EQUAL, paragraph, hcenter, WIDTH, bold, window, border, vscroll_indicator, Elements, HEIGHT, hflow, frame, flex_grow, flexbox, gauge, paragraphAlignCenter, paragraphAlignJustify, paragraphAlignLeft, paragraphAlignRight, dim, spinner, Decorator, LESS_THAN, center, yflex, GREATER_THAN
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::BlueLight, Color::RedLight, Color::Black, Color::Cyan, Color::CyanLight, Color::GrayDark, Color::GrayLight, Color::Green, Color::GreenLight, Color::Magenta, Color::MagentaLight, Color::Red, Color::White, Color::Yellow, Color::YellowLight, Color::Default
|
||||
#include "ftxui/screen/terminal.hpp" // for Size, Dimensions
|
||||
#include "ftxui/dom/elements.hpp" // for text, color, operator|, bgcolor, filler, Element, vbox, size, hbox, separator, flex, window, graph, EQUAL, paragraph, WIDTH, hcenter, Elements, bold, vscroll_indicator, HEIGHT, flexbox, hflow, border, frame, flex_grow, gauge, paragraphAlignCenter, paragraphAlignJustify, paragraphAlignLeft, paragraphAlignRight, dim, spinner, LESS_THAN, center, yframe, GREATER_THAN
|
||||
#include "ftxui/dom/flexbox_config.hpp" // for FlexboxConfig
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::BlueLight, Color::RedLight, Color::Black, Color::Blue, Color::Cyan, Color::CyanLight, Color::GrayDark, Color::GrayLight, Color::Green, Color::GreenLight, Color::Magenta, Color::MagentaLight, Color::Red, Color::White, Color::Yellow, Color::YellowLight, Color::Default, Color::Palette256, ftxui
|
||||
#include "ftxui/screen/color_info.hpp" // for ColorInfo
|
||||
#include "ftxui/screen/terminal.hpp" // for Size, Dimensions
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
@@ -27,7 +29,6 @@ int main(int argc, const char* argv[]) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// HTOP
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int shift = 0;
|
||||
|
||||
auto my_graph = [&shift](int width, int height) {
|
||||
@@ -95,7 +96,7 @@ int main(int argc, const char* argv[]) {
|
||||
separator(),
|
||||
ram | flex,
|
||||
}) |
|
||||
flex | border;
|
||||
flex;
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -255,7 +256,7 @@ int main(int argc, const char* argv[]) {
|
||||
}) | size(HEIGHT, LESS_THAN, 8),
|
||||
hflow(render_command()) | flex_grow,
|
||||
}) |
|
||||
flex_grow | border;
|
||||
flex_grow;
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -267,61 +268,116 @@ int main(int argc, const char* argv[]) {
|
||||
entries.push_back(spinner(i, shift / 2) | bold |
|
||||
size(WIDTH, GREATER_THAN, 2) | border);
|
||||
}
|
||||
return hflow(std::move(entries)) | border;
|
||||
return hflow(std::move(entries));
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Colors
|
||||
// ---------------------------------------------------------------------------
|
||||
auto color_tab_renderer = Renderer([] {
|
||||
return hbox({
|
||||
vbox({
|
||||
color(Color::Default, text("Default")),
|
||||
color(Color::Black, text("Black")),
|
||||
color(Color::GrayDark, text("GrayDark")),
|
||||
color(Color::GrayLight, text("GrayLight")),
|
||||
color(Color::White, text("White")),
|
||||
color(Color::Blue, text("Blue")),
|
||||
color(Color::BlueLight, text("BlueLight")),
|
||||
color(Color::Cyan, text("Cyan")),
|
||||
color(Color::CyanLight, text("CyanLight")),
|
||||
color(Color::Green, text("Green")),
|
||||
color(Color::GreenLight, text("GreenLight")),
|
||||
color(Color::Magenta, text("Magenta")),
|
||||
color(Color::MagentaLight, text("MagentaLight")),
|
||||
color(Color::Red, text("Red")),
|
||||
color(Color::RedLight, text("RedLight")),
|
||||
color(Color::Yellow, text("Yellow")),
|
||||
color(Color::YellowLight, text("YellowLight")),
|
||||
}),
|
||||
vbox({
|
||||
bgcolor(Color::Default, text("Default")),
|
||||
bgcolor(Color::Black, text("Black")),
|
||||
bgcolor(Color::GrayDark, text("GrayDark")),
|
||||
bgcolor(Color::GrayLight, text("GrayLight")),
|
||||
bgcolor(Color::White, text("White")),
|
||||
bgcolor(Color::Blue, text("Blue")),
|
||||
bgcolor(Color::BlueLight, text("BlueLight")),
|
||||
bgcolor(Color::Cyan, text("Cyan")),
|
||||
bgcolor(Color::CyanLight, text("CyanLight")),
|
||||
bgcolor(Color::Green, text("Green")),
|
||||
bgcolor(Color::GreenLight, text("GreenLight")),
|
||||
bgcolor(Color::Magenta, text("Magenta")),
|
||||
bgcolor(Color::MagentaLight, text("MagentaLight")),
|
||||
bgcolor(Color::Red, text("Red")),
|
||||
bgcolor(Color::RedLight, text("RedLight")),
|
||||
bgcolor(Color::Yellow, text("Yellow")),
|
||||
bgcolor(Color::YellowLight, text("YellowLight")),
|
||||
}),
|
||||
}) |
|
||||
hcenter | border;
|
||||
auto basic_color_display =
|
||||
vbox({
|
||||
text("16 color palette:"),
|
||||
separator(),
|
||||
hbox({
|
||||
vbox({
|
||||
color(Color::Default, text("Default")),
|
||||
color(Color::Black, text("Black")),
|
||||
color(Color::GrayDark, text("GrayDark")),
|
||||
color(Color::GrayLight, text("GrayLight")),
|
||||
color(Color::White, text("White")),
|
||||
color(Color::Blue, text("Blue")),
|
||||
color(Color::BlueLight, text("BlueLight")),
|
||||
color(Color::Cyan, text("Cyan")),
|
||||
color(Color::CyanLight, text("CyanLight")),
|
||||
color(Color::Green, text("Green")),
|
||||
color(Color::GreenLight, text("GreenLight")),
|
||||
color(Color::Magenta, text("Magenta")),
|
||||
color(Color::MagentaLight, text("MagentaLight")),
|
||||
color(Color::Red, text("Red")),
|
||||
color(Color::RedLight, text("RedLight")),
|
||||
color(Color::Yellow, text("Yellow")),
|
||||
color(Color::YellowLight, text("YellowLight")),
|
||||
}),
|
||||
vbox({
|
||||
bgcolor(Color::Default, text("Default")),
|
||||
bgcolor(Color::Black, text("Black")),
|
||||
bgcolor(Color::GrayDark, text("GrayDark")),
|
||||
bgcolor(Color::GrayLight, text("GrayLight")),
|
||||
bgcolor(Color::White, text("White")),
|
||||
bgcolor(Color::Blue, text("Blue")),
|
||||
bgcolor(Color::BlueLight, text("BlueLight")),
|
||||
bgcolor(Color::Cyan, text("Cyan")),
|
||||
bgcolor(Color::CyanLight, text("CyanLight")),
|
||||
bgcolor(Color::Green, text("Green")),
|
||||
bgcolor(Color::GreenLight, text("GreenLight")),
|
||||
bgcolor(Color::Magenta, text("Magenta")),
|
||||
bgcolor(Color::MagentaLight, text("MagentaLight")),
|
||||
bgcolor(Color::Red, text("Red")),
|
||||
bgcolor(Color::RedLight, text("RedLight")),
|
||||
bgcolor(Color::Yellow, text("Yellow")),
|
||||
bgcolor(Color::YellowLight, text("YellowLight")),
|
||||
}),
|
||||
}),
|
||||
}) |
|
||||
border;
|
||||
|
||||
auto palette_256_color_display = text("256 colors palette:");
|
||||
{
|
||||
std::vector<std::vector<ColorInfo>> info_columns = ColorInfoSorted2D();
|
||||
Elements columns;
|
||||
for (auto& column : info_columns) {
|
||||
Elements column_elements;
|
||||
for (auto& it : column) {
|
||||
column_elements.push_back(
|
||||
text(" ") | bgcolor(Color(Color::Palette256(it.index_256))));
|
||||
}
|
||||
columns.push_back(hbox(std::move(column_elements)));
|
||||
}
|
||||
palette_256_color_display = vbox({
|
||||
palette_256_color_display,
|
||||
separator(),
|
||||
vbox(columns),
|
||||
}) |
|
||||
border;
|
||||
}
|
||||
|
||||
// True color display.
|
||||
auto true_color_display = text("TrueColors: 24bits:");
|
||||
{
|
||||
int saturation = 255;
|
||||
Elements array;
|
||||
for (int value = 0; value < 255; value += 16) {
|
||||
Elements line;
|
||||
for (int hue = 0; hue < 255; hue += 6) {
|
||||
line.push_back(text("▀") //
|
||||
| color(Color::HSV(hue, saturation, value)) //
|
||||
| bgcolor(Color::HSV(hue, saturation, value + 8)));
|
||||
}
|
||||
array.push_back(hbox(std::move(line)));
|
||||
}
|
||||
true_color_display = vbox({
|
||||
true_color_display,
|
||||
separator(),
|
||||
vbox(std::move(array)),
|
||||
}) |
|
||||
border;
|
||||
}
|
||||
|
||||
return flexbox(
|
||||
{
|
||||
basic_color_display,
|
||||
palette_256_color_display,
|
||||
true_color_display,
|
||||
},
|
||||
FlexboxConfig().SetGap(1, 1));
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Gauges
|
||||
// ---------------------------------------------------------------------------
|
||||
auto render_gauge = [&shift](int delta) {
|
||||
float progress = (shift + delta) % 1000 / 1000.f;
|
||||
float progress = (shift + delta) % 500 / 500.f;
|
||||
return hbox({
|
||||
text(std::to_string(int(progress * 100)) + "% ") |
|
||||
size(WIDTH, EQUAL, 5),
|
||||
@@ -331,25 +387,24 @@ int main(int argc, const char* argv[]) {
|
||||
|
||||
auto gauge_component = Renderer([render_gauge] {
|
||||
return vbox({
|
||||
render_gauge(0) | color(Color::Black),
|
||||
render_gauge(100) | color(Color::GrayDark),
|
||||
render_gauge(50) | color(Color::GrayLight),
|
||||
render_gauge(6894) | color(Color::White),
|
||||
separator(),
|
||||
render_gauge(6841) | color(Color::Blue),
|
||||
render_gauge(9813) | color(Color::BlueLight),
|
||||
render_gauge(98765) | color(Color::Cyan),
|
||||
render_gauge(98) | color(Color::CyanLight),
|
||||
render_gauge(9846) | color(Color::Green),
|
||||
render_gauge(1122) | color(Color::GreenLight),
|
||||
render_gauge(84) | color(Color::Magenta),
|
||||
render_gauge(645) | color(Color::MagentaLight),
|
||||
render_gauge(568) | color(Color::Red),
|
||||
render_gauge(2222) | color(Color::RedLight),
|
||||
render_gauge(220) | color(Color::Yellow),
|
||||
render_gauge(348) | color(Color::YellowLight),
|
||||
}) |
|
||||
border;
|
||||
render_gauge(0) | color(Color::Black),
|
||||
render_gauge(100) | color(Color::GrayDark),
|
||||
render_gauge(50) | color(Color::GrayLight),
|
||||
render_gauge(6894) | color(Color::White),
|
||||
separator(),
|
||||
render_gauge(6841) | color(Color::Blue),
|
||||
render_gauge(9813) | color(Color::BlueLight),
|
||||
render_gauge(98765) | color(Color::Cyan),
|
||||
render_gauge(98) | color(Color::CyanLight),
|
||||
render_gauge(9846) | color(Color::Green),
|
||||
render_gauge(1122) | color(Color::GreenLight),
|
||||
render_gauge(84) | color(Color::Magenta),
|
||||
render_gauge(645) | color(Color::MagentaLight),
|
||||
render_gauge(568) | color(Color::Red),
|
||||
render_gauge(2222) | color(Color::RedLight),
|
||||
render_gauge(220) | color(Color::Yellow),
|
||||
render_gauge(348) | color(Color::YellowLight),
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -363,48 +418,36 @@ int main(int argc, const char* argv[]) {
|
||||
};
|
||||
|
||||
auto paragraph_renderer_left = Renderer([&] {
|
||||
auto title_style = bold | bgcolor(Color::Blue) | color(Color::Black);
|
||||
std::string str =
|
||||
"Lorem Ipsum is simply dummy text of the printing and typesetting "
|
||||
"industry. Lorem Ipsum has been the industry's standard dummy text "
|
||||
"ever since the 1500s, when an unknown printer took a galley of type "
|
||||
"and scrambled it to make a type specimen book.";
|
||||
return vbox({
|
||||
// [ Left ]
|
||||
text("Align left:") | title_style,
|
||||
paragraphAlignLeft(str),
|
||||
// [ Center ]
|
||||
text("Align center:") | title_style,
|
||||
paragraphAlignCenter(str),
|
||||
// [ Right ]
|
||||
text("Align right:") | title_style,
|
||||
paragraphAlignRight(str),
|
||||
// [ Justify]
|
||||
text("Align justify:") | title_style,
|
||||
paragraphAlignJustify(str),
|
||||
// [ Side by side ]
|
||||
text("Side by side:") | title_style,
|
||||
hbox({
|
||||
paragraph(str),
|
||||
separator() | color(Color::Blue),
|
||||
paragraph(str),
|
||||
}),
|
||||
// [ Misc ]
|
||||
text("Elements with different size:") | title_style,
|
||||
flexbox({
|
||||
make_box(10, 5),
|
||||
make_box(9, 4),
|
||||
make_box(8, 4),
|
||||
make_box(6, 3),
|
||||
make_box(10, 5),
|
||||
make_box(9, 4),
|
||||
make_box(8, 4),
|
||||
make_box(6, 3),
|
||||
make_box(10, 5),
|
||||
make_box(9, 4),
|
||||
make_box(8, 4),
|
||||
make_box(6, 3),
|
||||
}),
|
||||
window(text("Align left:"), paragraphAlignLeft(str)),
|
||||
window(text("Align center:"), paragraphAlignCenter(str)),
|
||||
window(text("Align right:"), paragraphAlignRight(str)),
|
||||
window(text("Align justify:"), paragraphAlignJustify(str)),
|
||||
window(text("Side by side"), hbox({
|
||||
paragraph(str),
|
||||
separator(),
|
||||
paragraph(str),
|
||||
})),
|
||||
window(text("Elements with different size:"),
|
||||
flexbox({
|
||||
make_box(10, 5),
|
||||
make_box(9, 4),
|
||||
make_box(8, 4),
|
||||
make_box(6, 3),
|
||||
make_box(10, 5),
|
||||
make_box(9, 4),
|
||||
make_box(8, 4),
|
||||
make_box(6, 3),
|
||||
make_box(10, 5),
|
||||
make_box(9, 4),
|
||||
make_box(8, 4),
|
||||
make_box(6, 3),
|
||||
})),
|
||||
}) |
|
||||
vscroll_indicator | yframe | flex;
|
||||
});
|
||||
@@ -420,7 +463,7 @@ int main(int argc, const char* argv[]) {
|
||||
¶graph_renderer_split_position);
|
||||
auto paragraph_renderer_group_renderer =
|
||||
Renderer(paragraph_renderer_group,
|
||||
[&] { return paragraph_renderer_group->Render() | border; });
|
||||
[&] { return paragraph_renderer_group->Render(); });
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tabs
|
||||
@@ -430,7 +473,8 @@ int main(int argc, const char* argv[]) {
|
||||
std::vector<std::string> tab_entries = {
|
||||
"htop", "color", "spinner", "gauge", "compiler", "paragraph",
|
||||
};
|
||||
auto tab_selection = Toggle(&tab_entries, &tab_index);
|
||||
auto tab_selection =
|
||||
Menu(&tab_entries, &tab_index, MenuOption::HorizontalAnimated());
|
||||
auto tab_content = Container::Tab(
|
||||
{
|
||||
htop,
|
||||
@@ -450,7 +494,7 @@ int main(int argc, const char* argv[]) {
|
||||
auto main_renderer = Renderer(main_container, [&] {
|
||||
return vbox({
|
||||
text("FTXUI Demo") | bold | hcenter,
|
||||
tab_selection->Render() | hcenter,
|
||||
tab_selection->Render(),
|
||||
tab_content->Render() | flex,
|
||||
});
|
||||
});
|
||||
|
@@ -1,17 +1,15 @@
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for string, basic_string, allocator
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Checkbox, Maybe, Radiobox, Renderer, Vertical
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for Element, operator|, border
|
||||
#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/screen/color.hpp" // for Color, Color::Red
|
||||
|
||||
using namespace ftxui;
|
||||
Component Border(Component child) {
|
||||
return Renderer(child, [child] { return child->Render() | border; });
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
std::vector<std::string> entries = {
|
||||
@@ -21,20 +19,19 @@ int main(int argc, const char* argv[]) {
|
||||
};
|
||||
int menu_1_selected = 0;
|
||||
int menu_2_selected = 0;
|
||||
auto menu_1 = Radiobox(&entries, &menu_1_selected);
|
||||
auto menu_2 = Radiobox(&entries, &menu_2_selected);
|
||||
|
||||
menu_1 = Border(menu_1);
|
||||
menu_2 = Border(menu_2);
|
||||
|
||||
bool menu_1_show = false;
|
||||
bool menu_2_show = false;
|
||||
|
||||
auto layout = Container::Vertical({
|
||||
Checkbox("Show menu_1", &menu_1_show),
|
||||
Maybe(menu_1, &menu_1_show),
|
||||
Radiobox(&entries, &menu_1_selected) | border | Maybe(&menu_1_show),
|
||||
Checkbox("Show menu_2", &menu_2_show),
|
||||
Maybe(menu_2, &menu_2_show),
|
||||
Radiobox(&entries, &menu_2_selected) | border | Maybe(&menu_2_show),
|
||||
|
||||
Renderer([] {
|
||||
return text("You found the secret combinaison!") | color(Color::Red);
|
||||
}) | Maybe([&] { return menu_1_selected == 1 && menu_2_selected == 2; }),
|
||||
});
|
||||
|
||||
auto screen = ScreenInteractive::TerminalOutput();
|
||||
|
@@ -1,25 +1,31 @@
|
||||
#include <functional> // for function
|
||||
#include <iostream> // for basic_ostream::operator<<, operator<<, endl, basic_ostream, basic_ostream<>::__ostream_type, cout, ostream
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access
|
||||
#include <string> // for to_string, allocator
|
||||
#include <memory> // for allocator, shared_ptr, __shared_ptr_access
|
||||
#include <string> // for char_traits, to_string, operator+, string, basic_string
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for MenuEntry, Renderer, Vertical
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for MenuEntryOption
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, separator, Element, Decorator, color, text, hbox, size, bold, frame, inverted, vbox, HEIGHT, LESS_THAN, border
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, separator, text, hbox, size, frame, color, vbox, HEIGHT, LESS_THAN, bold, border, inverted
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::Cyan, Color::Green, Color::Red, Color::Yellow
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
// Define a special style for some menu entry.
|
||||
MenuEntryOption Colored(ftxui::Color c) {
|
||||
MenuEntryOption special_style;
|
||||
special_style.style_normal = Decorator(color(c));
|
||||
special_style.style_focused = Decorator(color(c)) | inverted;
|
||||
special_style.style_selected = Decorator(color(c)) | bold;
|
||||
special_style.style_selected_focused = Decorator(color(c)) | inverted | bold;
|
||||
return special_style;
|
||||
MenuEntryOption option;
|
||||
option.transform = [c](EntryState state) {
|
||||
state.label = (state.active ? "> " : " ") + state.label;
|
||||
Element e = text(state.label) | color(c);
|
||||
if (state.focused)
|
||||
e = e | inverted;
|
||||
if (state.active)
|
||||
e = e | bold;
|
||||
return e;
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
|
66
examples/component/menu_entries_animated.cpp
Normal file
66
examples/component/menu_entries_animated.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include <iostream> // for basic_ostream::operator<<, operator<<, endl, basic_ostream, basic_ostream<>::__ostream_type, cout, ostream
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access
|
||||
#include <string> // for to_string, allocator
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for MenuEntryAnimated, Renderer, Vertical
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for MenuEntryAnimated
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, separator, Element, Decorator, color, text, hbox, size, bold, frame, inverted, vbox, HEIGHT, LESS_THAN, border
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::Cyan, Color::Green, Color::Red, Color::Yellow
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
// Define a special style for some menu entry.
|
||||
MenuEntryOption Colored(ftxui::Color c) {
|
||||
MenuEntryOption option;
|
||||
option.animated_colors.foreground.enabled = true;
|
||||
option.animated_colors.background.enabled = true;
|
||||
option.animated_colors.background.active = c;
|
||||
option.animated_colors.background.inactive = Color::Black;
|
||||
option.animated_colors.foreground.active = Color::White;
|
||||
option.animated_colors.foreground.inactive = c;
|
||||
return option;
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
auto screen = ScreenInteractive::TerminalOutput();
|
||||
|
||||
int selected = 0;
|
||||
auto menu = Container::Vertical(
|
||||
{
|
||||
MenuEntry(" 1. rear", Colored(Color::Red)),
|
||||
MenuEntry(" 2. drown", Colored(Color::Yellow)),
|
||||
MenuEntry(" 3. nail", Colored(Color::Green)),
|
||||
MenuEntry(" 4. quit", Colored(Color::Cyan)),
|
||||
MenuEntry(" 5. decorative", Colored(Color::Blue)),
|
||||
MenuEntry(" 7. costume"),
|
||||
MenuEntry(" 8. pick"),
|
||||
MenuEntry(" 9. oral"),
|
||||
MenuEntry("11. minister"),
|
||||
MenuEntry("12. football"),
|
||||
MenuEntry("13. welcome"),
|
||||
MenuEntry("14. copper"),
|
||||
MenuEntry("15. inhabitant"),
|
||||
},
|
||||
&selected);
|
||||
|
||||
// Display together the menu with a border
|
||||
auto renderer = Renderer(menu, [&] {
|
||||
return vbox({
|
||||
hbox(text("selected = "), text(std::to_string(selected))),
|
||||
separator(),
|
||||
menu->Render() | frame,
|
||||
}) |
|
||||
border | bgcolor(Color::Black);
|
||||
});
|
||||
|
||||
screen.Loop(renderer);
|
||||
|
||||
std::cout << "Selected element = " << selected << std::endl;
|
||||
}
|
||||
|
||||
// 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,100 +1,258 @@
|
||||
#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, basic_string
|
||||
#include <string> // for string, char_traits, basic_string, operator+
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Menu, Horizontal, Renderer
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for MenuOption
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, color, separator, Decorator, bgcolor, flex, Element, bold, hbox, border, dim
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::BlueLight, Color::Red, Color::Yellow
|
||||
#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/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
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Red, Color::Black, Color::Yellow, Color::Blue, Color::Default, Color::White
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
Component VMenu1(std::vector<std::string>* entries, int* selected);
|
||||
Component VMenu2(std::vector<std::string>* entries, int* selected);
|
||||
Component VMenu3(std::vector<std::string>* entries, int* selected);
|
||||
Component VMenu4(std::vector<std::string>* entries, int* selected);
|
||||
Component VMenu5(std::vector<std::string>* entries, int* selected);
|
||||
Component VMenu6(std::vector<std::string>* entries, int* selected);
|
||||
Component VMenu7(std::vector<std::string>* entries, int* selected);
|
||||
Component VMenu8(std::vector<std::string>* entries, int* selected);
|
||||
Component HMenu1(std::vector<std::string>* entries, int* selected);
|
||||
Component HMenu2(std::vector<std::string>* entries, int* selected);
|
||||
Component HMenu3(std::vector<std::string>* entries, int* selected);
|
||||
Component HMenu4(std::vector<std::string>* entries, int* selected);
|
||||
Component HMenu5(std::vector<std::string>* entries, int* selected);
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
using namespace ftxui;
|
||||
auto screen = ScreenInteractive::TerminalOutput();
|
||||
|
||||
std::vector<std::string> entries = {
|
||||
"Monkey", "Dog", "Cat", "Bird", "Elephant",
|
||||
std::vector<std::string> entries{
|
||||
"Monkey", "Dog", "Cat", "Bird", "Elephant", "Cat",
|
||||
};
|
||||
int menu_1_selected_ = 0;
|
||||
int menu_2_selected_ = 0;
|
||||
int menu_3_selected_ = 0;
|
||||
int menu_4_selected_ = 0;
|
||||
int menu_5_selected_ = 0;
|
||||
int menu_6_selected_ = 0;
|
||||
std::array<int, 12> selected = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
MenuOption option_1;
|
||||
option_1.style_focused = bold | color(Color::Blue);
|
||||
option_1.style_selected = color(Color::Blue);
|
||||
option_1.style_selected_focused = bold | color(Color::Blue);
|
||||
option_1.on_enter = screen.ExitLoopClosure();
|
||||
auto menu_1_ = Menu(&entries, &menu_1_selected_, &option_1);
|
||||
auto vmenu_1_ = VMenu1(&entries, &selected[0]);
|
||||
auto vmenu_2_ = VMenu2(&entries, &selected[1]);
|
||||
auto vmenu_3_ = VMenu3(&entries, &selected[2]);
|
||||
auto vmenu_4_ = VMenu4(&entries, &selected[3]);
|
||||
auto vmenu_5_ = VMenu5(&entries, &selected[4]);
|
||||
auto vmenu_6_ = VMenu6(&entries, &selected[5]);
|
||||
auto vmenu_7_ = VMenu7(&entries, &selected[6]);
|
||||
auto vmenu_8_ = VMenu8(&entries, &selected[7]);
|
||||
|
||||
MenuOption option_2;
|
||||
option_2.style_focused = bold | color(Color::Blue);
|
||||
option_2.style_selected = color(Color::Blue);
|
||||
option_2.style_selected_focused = bold | color(Color::Blue);
|
||||
option_2.on_enter = screen.ExitLoopClosure();
|
||||
auto menu_2_ = Menu(&entries, &menu_2_selected_, &option_2);
|
||||
auto hmenu_1_ = HMenu1(&entries, &selected[8]);
|
||||
auto hmenu_2_ = HMenu2(&entries, &selected[9]);
|
||||
auto hmenu_3_ = HMenu3(&entries, &selected[10]);
|
||||
auto hmenu_4_ = HMenu4(&entries, &selected[11]);
|
||||
auto hmenu_5_ = HMenu5(&entries, &selected[12]);
|
||||
|
||||
MenuOption option_3;
|
||||
option_3.style_selected = color(Color::Blue);
|
||||
option_3.style_focused = bgcolor(Color::Blue);
|
||||
option_3.style_selected_focused = bgcolor(Color::Blue);
|
||||
option_3.on_enter = screen.ExitLoopClosure();
|
||||
auto menu_3_ = Menu(&entries, &menu_3_selected_, &option_3);
|
||||
|
||||
MenuOption option_4;
|
||||
option_4.style_selected = bgcolor(Color::Blue);
|
||||
option_4.style_focused = bgcolor(Color::BlueLight);
|
||||
option_4.style_selected_focused = bgcolor(Color::BlueLight);
|
||||
option_4.on_enter = screen.ExitLoopClosure();
|
||||
auto menu_4_ = Menu(&entries, &menu_4_selected_, &option_4);
|
||||
|
||||
MenuOption option_5;
|
||||
option_5.style_normal = bgcolor(Color::Blue);
|
||||
option_5.style_selected = bgcolor(Color::Yellow);
|
||||
option_5.style_focused = bgcolor(Color::Red);
|
||||
option_5.style_selected_focused = bgcolor(Color::Red);
|
||||
option_5.on_enter = screen.ExitLoopClosure();
|
||||
auto menu_5_ = Menu(&entries, &menu_5_selected_, &option_5);
|
||||
|
||||
MenuOption option_6;
|
||||
option_6.style_normal = dim | color(Color::Blue);
|
||||
option_6.style_selected = color(Color::Blue);
|
||||
option_6.style_focused = bold | color(Color::Blue);
|
||||
option_6.style_selected_focused = bold | color(Color::Blue);
|
||||
option_6.on_enter = screen.ExitLoopClosure();
|
||||
auto menu_6_ = Menu(&entries, &menu_6_selected_, &option_6);
|
||||
|
||||
auto container = Container::Horizontal({
|
||||
menu_1_,
|
||||
menu_2_,
|
||||
menu_3_,
|
||||
menu_4_,
|
||||
menu_5_,
|
||||
menu_6_,
|
||||
auto container = Container::Vertical({
|
||||
Container::Horizontal({
|
||||
vmenu_1_,
|
||||
vmenu_2_,
|
||||
vmenu_3_,
|
||||
vmenu_4_,
|
||||
vmenu_5_,
|
||||
vmenu_6_,
|
||||
vmenu_7_,
|
||||
vmenu_8_,
|
||||
}),
|
||||
hmenu_1_,
|
||||
hmenu_2_,
|
||||
hmenu_3_,
|
||||
hmenu_4_,
|
||||
hmenu_5_,
|
||||
});
|
||||
|
||||
// clang-format off
|
||||
auto renderer = Renderer(container, [&] {
|
||||
return
|
||||
hbox({
|
||||
menu_1_->Render() | flex, separator(),
|
||||
menu_2_->Render() | flex, separator(),
|
||||
menu_3_->Render() | flex, separator(),
|
||||
menu_4_->Render() | flex, separator(),
|
||||
menu_5_->Render() | flex, separator(),
|
||||
menu_6_->Render() | flex,
|
||||
}) | border;
|
||||
return //
|
||||
hbox({
|
||||
vbox({
|
||||
hbox({
|
||||
vmenu_1_->Render(),
|
||||
separator(),
|
||||
vmenu_2_->Render(),
|
||||
separator(),
|
||||
vmenu_3_->Render(),
|
||||
separator(),
|
||||
vmenu_4_->Render(),
|
||||
separator(),
|
||||
vmenu_5_->Render(),
|
||||
vmenu_6_->Render(),
|
||||
separator(),
|
||||
vmenu_7_->Render(),
|
||||
separator(),
|
||||
vmenu_8_->Render(),
|
||||
}),
|
||||
separator(),
|
||||
hmenu_1_->Render(),
|
||||
separator(),
|
||||
hmenu_2_->Render(),
|
||||
separator(),
|
||||
hmenu_3_->Render(),
|
||||
separator(),
|
||||
hmenu_4_->Render(),
|
||||
hmenu_5_->Render(),
|
||||
}) | border,
|
||||
filler(),
|
||||
});
|
||||
});
|
||||
// clang-format on
|
||||
|
||||
screen.Loop(renderer);
|
||||
}
|
||||
|
||||
Component VMenu1(std::vector<std::string>* entries, int* selected) {
|
||||
auto option = MenuOption::Vertical();
|
||||
option.entries.transform = [](EntryState state) {
|
||||
state.label = (state.active ? "> " : " ") + state.label;
|
||||
Element e = text(state.label);
|
||||
if (state.focused)
|
||||
e = e | bgcolor(Color::Blue);
|
||||
if (state.active)
|
||||
e = e | bold;
|
||||
return e;
|
||||
};
|
||||
return Menu(entries, selected, option);
|
||||
}
|
||||
|
||||
Component VMenu2(std::vector<std::string>* entries, int* selected) {
|
||||
auto option = MenuOption::Vertical();
|
||||
option.entries.transform = [](EntryState state) {
|
||||
state.label += (state.active ? " <" : " ");
|
||||
Element e = hbox(filler(), text(state.label));
|
||||
if (state.focused)
|
||||
e = e | bgcolor(Color::Red);
|
||||
if (state.active)
|
||||
e = e | bold;
|
||||
return e;
|
||||
};
|
||||
return Menu(entries, selected, option);
|
||||
}
|
||||
|
||||
Component VMenu3(std::vector<std::string>* entries, int* selected) {
|
||||
auto option = MenuOption::Vertical();
|
||||
option.entries.transform = [](EntryState state) {
|
||||
Element e = state.active ? text("[" + state.label + "]")
|
||||
: text(" " + state.label + " ");
|
||||
if (state.focused)
|
||||
e = e | bold;
|
||||
|
||||
if (state.focused)
|
||||
e = e | color(Color::Blue);
|
||||
if (state.active)
|
||||
e = e | bold;
|
||||
return e;
|
||||
};
|
||||
return Menu(entries, selected, option);
|
||||
}
|
||||
|
||||
Component VMenu4(std::vector<std::string>* entries, int* selected) {
|
||||
auto option = MenuOption::Vertical();
|
||||
option.entries.transform = [](EntryState state) {
|
||||
if (state.active && state.focused) {
|
||||
return text(state.label) | color(Color::Yellow) | bgcolor(Color::Black) |
|
||||
bold;
|
||||
}
|
||||
|
||||
if (state.active) {
|
||||
return text(state.label) | color(Color::Yellow) | bgcolor(Color::Black);
|
||||
}
|
||||
if (state.focused) {
|
||||
return text(state.label) | color(Color::Black) | bgcolor(Color::Yellow) |
|
||||
bold;
|
||||
}
|
||||
return text(state.label) | color(Color::Black) | bgcolor(Color::Yellow);
|
||||
};
|
||||
return Menu(entries, selected, option);
|
||||
}
|
||||
|
||||
Component VMenu5(std::vector<std::string>* entries, int* selected) {
|
||||
auto option = MenuOption::Vertical();
|
||||
option.entries.transform = [](EntryState state) {
|
||||
auto element = text(state.label);
|
||||
if (state.active && state.focused) {
|
||||
return element | borderDouble;
|
||||
}
|
||||
if (state.active) {
|
||||
return element | border;
|
||||
}
|
||||
if (state.focused) {
|
||||
return element | bold;
|
||||
}
|
||||
return element;
|
||||
};
|
||||
return Menu(entries, selected, option);
|
||||
}
|
||||
|
||||
Component VMenu6(std::vector<std::string>* entries, int* selected) {
|
||||
auto option = MenuOption::VerticalAnimated();
|
||||
option.underline.color_inactive = Color::Default;
|
||||
option.underline.color_active = Color::Red;
|
||||
option.underline.SetAnimationFunction(animation::easing::Linear);
|
||||
return Menu(entries, selected, option);
|
||||
}
|
||||
|
||||
Component VMenu7(std::vector<std::string>* entries, int* selected) {
|
||||
auto option = MenuOption::Vertical();
|
||||
option.entries.animated_colors.foreground.enabled = true;
|
||||
option.entries.animated_colors.background.enabled = true;
|
||||
option.entries.animated_colors.background.active = Color::Red;
|
||||
option.entries.animated_colors.background.inactive = Color::Black;
|
||||
option.entries.animated_colors.foreground.active = Color::White;
|
||||
option.entries.animated_colors.foreground.inactive = Color::Red;
|
||||
return Menu(entries, selected, option);
|
||||
}
|
||||
|
||||
Component VMenu8(std::vector<std::string>* entries, int* selected) {
|
||||
auto option = MenuOption::Vertical();
|
||||
option.entries.animated_colors.foreground.Set(Color::Red, Color::White,
|
||||
std::chrono::milliseconds(500));
|
||||
return Menu(entries, selected, option);
|
||||
}
|
||||
|
||||
Component HMenu1(std::vector<std::string>* entries, int* selected) {
|
||||
return Menu(entries, selected, MenuOption::Horizontal());
|
||||
}
|
||||
|
||||
Component HMenu2(std::vector<std::string>* entries, int* selected) {
|
||||
return Menu(entries, selected, MenuOption::Toggle());
|
||||
}
|
||||
|
||||
Component HMenu3(std::vector<std::string>* entries, int* selected) {
|
||||
auto option = MenuOption::Toggle();
|
||||
option.elements_infix = [] { return text(" 🮣🮠 "); };
|
||||
|
||||
return Menu(entries, selected, option);
|
||||
}
|
||||
|
||||
Component HMenu4(std::vector<std::string>* entries, int* selected) {
|
||||
return Menu(entries, selected, MenuOption::HorizontalAnimated());
|
||||
}
|
||||
|
||||
Component HMenu5(std::vector<std::string>* entries, int* selected) {
|
||||
auto option = MenuOption::HorizontalAnimated();
|
||||
option.underline.SetAnimation(std::chrono::milliseconds(1500),
|
||||
animation::easing::ElasticOut);
|
||||
option.entries.transform = [](EntryState state) {
|
||||
Element e = text(state.label) | hcenter | flex;
|
||||
if (state.active && state.focused)
|
||||
e = e | bold;
|
||||
if (!state.focused && !state.active)
|
||||
e = e | dim;
|
||||
return e;
|
||||
};
|
||||
option.underline.color_inactive = Color::Default;
|
||||
option.underline.color_active = Color::Red;
|
||||
return Menu(entries, selected, option);
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
94
examples/component/menu_underline_animated_gallery.cpp
Normal file
94
examples/component/menu_underline_animated_gallery.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#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 <vector> // for vector
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for BackOut, Duration
|
||||
#include "ftxui/component/component.hpp" // for Menu, Renderer, Vertical
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for MenuOption, UnderlineOption
|
||||
#include "ftxui/component/mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for text, Element, operator|, borderEmpty, inverted
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::Red
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
Component DummyComponent(int id) {
|
||||
return Renderer([id](bool focused) {
|
||||
auto t = text("component " + std::to_string(id));
|
||||
if (focused)
|
||||
t = t | inverted;
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
Component Text(const std::string& t) {
|
||||
return Renderer([t] { return text(t) | borderEmpty; });
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
using namespace std::literals;
|
||||
std::vector<std::string> tab_values{
|
||||
"Tab 1", "Tab 2", "Tab 3", "A very very long tab", "탭",
|
||||
};
|
||||
int tab_selected = 0;
|
||||
|
||||
auto container = Container::Vertical({});
|
||||
|
||||
int frame_count = 0;
|
||||
container->Add(Renderer(
|
||||
[&] { return text("Frame count: " + std::to_string(frame_count++)); }));
|
||||
|
||||
{
|
||||
auto option = MenuOption::HorizontalAnimated();
|
||||
container->Add(Text("This demonstrate the Menu component"));
|
||||
container->Add(Menu(&tab_values, &tab_selected, option));
|
||||
}
|
||||
|
||||
{
|
||||
container->Add(Text("Set underline color to blue"));
|
||||
auto option = MenuOption::HorizontalAnimated();
|
||||
option.underline.color_inactive = Color::Blue;
|
||||
container->Add(Menu(&tab_values, &tab_selected, option));
|
||||
}
|
||||
|
||||
{
|
||||
container->Add(Text("Set underline active color to red"));
|
||||
auto option = MenuOption::HorizontalAnimated();
|
||||
option.underline.color_active = Color::Red;
|
||||
container->Add(Menu(&tab_values, &tab_selected, option));
|
||||
}
|
||||
|
||||
{
|
||||
container->Add(Text("Set animation duration to 0ms"));
|
||||
auto option = MenuOption::HorizontalAnimated();
|
||||
option.underline.SetAnimationDuration(0ms);
|
||||
container->Add(Menu(&tab_values, &tab_selected, option));
|
||||
}
|
||||
|
||||
{
|
||||
container->Add(Text("Set animation easing function to back-out"));
|
||||
auto option = MenuOption::HorizontalAnimated();
|
||||
option.underline.SetAnimationFunction(animation::easing::BackOut);
|
||||
option.underline.SetAnimationDuration(350ms);
|
||||
container->Add(Menu(&tab_values, &tab_selected, option));
|
||||
}
|
||||
|
||||
// option.underline_animation_follower_delay = 250ms
|
||||
{
|
||||
container->Add(Text("Add delay to desynchronize animation"));
|
||||
auto option = MenuOption::HorizontalAnimated();
|
||||
option.underline.follower_delay = 250ms;
|
||||
container->Add(Menu(&tab_values, &tab_selected, option));
|
||||
}
|
||||
|
||||
container->SetActiveChild(container->ChildAt(2));
|
||||
|
||||
auto screen = ScreenInteractive::TerminalOutput();
|
||||
screen.Loop(container);
|
||||
}
|
||||
|
||||
// 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.
|
@@ -84,7 +84,7 @@ int main(int argc, const char* argv[]) {
|
||||
return window(text("keys"), vbox(std::move(children)));
|
||||
});
|
||||
|
||||
component = CatchEvent(component, [&](Event event) {
|
||||
component |= CatchEvent([&](Event event) {
|
||||
keys.push_back(event);
|
||||
return true;
|
||||
});
|
||||
|
@@ -1,5 +1,13 @@
|
||||
#include "ftxui/component/component.hpp" // for Menu
|
||||
#include <cstdlib> // for system, EXIT_SUCCESS
|
||||
#include <iostream> // for operator<<, basic_ostream, basic_ostream::operator<<, cout, endl, flush, ostream, basic_ostream<>::__ostream_type, cin
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
|
||||
#include <string> // for getline, string
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, filler, Element, borderEmpty, hbox, size, paragraph, vbox, LESS_THAN, border, center, HEIGHT, WIDTH
|
||||
|
||||
int main() {
|
||||
using namespace ftxui;
|
||||
@@ -10,14 +18,12 @@ int main() {
|
||||
// temporarily uninstall the terminal hook and execute the provided callback
|
||||
// function. This allow running the application in a non-interactive mode.
|
||||
auto btn_run = Button("Execute with restored IO", screen.WithRestoredIO([] {
|
||||
std::system("bash");
|
||||
std::cout << "This is a child program using stdin/stdout." << std::endl;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
std::cout << "Please enter 10 strings (" << i << "/10)" << std::flush;
|
||||
std::string input;
|
||||
std::getline(std::cin, input);
|
||||
}
|
||||
std::system("bash");
|
||||
}));
|
||||
|
||||
auto btn_quit = Button("Quit", screen.ExitLoopClosure());
|
||||
|
@@ -9,6 +9,7 @@ example(color_truecolor_RGB)
|
||||
example(dbox)
|
||||
example(canvas)
|
||||
example(gauge)
|
||||
example(gauge_direction)
|
||||
example(graph)
|
||||
example(gridbox)
|
||||
example(hflow)
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#include <stdlib.h> // for EXIT_SUCCESS
|
||||
#include <ftxui/dom/elements.hpp> // for text, operator|, vbox, border, Element, Fit, hbox
|
||||
#include <ftxui/screen/screen.hpp> // for Full, Screen
|
||||
#include <memory> // for allocator
|
||||
@@ -5,7 +6,7 @@
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/color.hpp" // for ftxui
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
int main() {
|
||||
using namespace ftxui;
|
||||
auto document = //
|
||||
hbox({
|
||||
@@ -30,6 +31,7 @@ int main(int argc, const char* argv[]) {
|
||||
auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
|
||||
Render(screen, document);
|
||||
screen.Print();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
|
@@ -30,7 +30,7 @@ int main(int argc, const char* argv[]) {
|
||||
// Plot a function:
|
||||
std::vector<int> ys(100);
|
||||
for (int x = 0; x < 100; x++)
|
||||
ys[x] = 80 + 20 * cos(x * 0.2);
|
||||
ys[x] = int(80 + 20 * cos(x * 0.2));
|
||||
for (int x = 0; x < 99; x++)
|
||||
c.DrawPointLine(x, ys[x], x + 1, ys[x + 1], Color::Red);
|
||||
|
||||
|
@@ -5,13 +5,14 @@
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
using namespace ftxui;
|
||||
#include "./color_info_sorted_2d.ipp" // for ColorInfoSorted2D
|
||||
#include "ftxui/dom/elements.hpp" // for text, bgcolor, color, vbox, hbox, separator, operator|, Elements, Element, Fit, border
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Black, Color::Blue, Color::BlueLight, Color::Cyan, Color::CyanLight, Color::Default, Color::GrayDark, Color::GrayLight, Color::Green, Color::GreenLight, Color::Magenta, Color::MagentaLight, Color::Red, Color::RedLight, Color::White, Color::Yellow, Color::YellowLight, Color::Palette256, ftxui
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
using namespace ftxui;
|
||||
#include "./color_info_sorted_2d.ipp" // for ColorInfoSorted2D
|
||||
|
||||
int main() {
|
||||
// clang-format off
|
||||
auto basic_color_display =
|
||||
vbox(
|
||||
@@ -82,14 +83,18 @@ int main(int argc, const char* argv[]) {
|
||||
// True color display.
|
||||
auto true_color_display = text("TrueColors: 24bits:");
|
||||
{
|
||||
int saturation = 255;
|
||||
const int max_value = 255;
|
||||
const int value_increment = 8;
|
||||
const int hue_increment = 6;
|
||||
int saturation = max_value;
|
||||
Elements array;
|
||||
for (int value = 0; value < 255; value += 16) {
|
||||
for (int value = 0; value < max_value; value += 2 * value_increment) {
|
||||
Elements line;
|
||||
for (int hue = 0; hue < 255; hue += 6) {
|
||||
line.push_back(text("▀") //
|
||||
| color(Color::HSV(hue, saturation, value)) //
|
||||
| bgcolor(Color::HSV(hue, saturation, value + 8)));
|
||||
for (int hue = 0; hue < max_value; hue += hue_increment) {
|
||||
line.push_back(
|
||||
text("▀") //
|
||||
| color(Color::HSV(hue, saturation, value)) //
|
||||
| bgcolor(Color::HSV(hue, saturation, value + value_increment)));
|
||||
}
|
||||
array.push_back(hbox(std::move(line)));
|
||||
}
|
||||
|
@@ -1,12 +1,13 @@
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <ftxui/screen/color_info.hpp> // for ftxui::ColorInfo
|
||||
|
||||
std::vector<std::vector<ColorInfo>> ColorInfoSorted2D() {
|
||||
std::vector<std::vector<ftxui::ColorInfo>> ColorInfoSorted2D() {
|
||||
// Acquire the color information for the palette256.
|
||||
std::vector<ColorInfo> info_gray;
|
||||
std::vector<ColorInfo> info_color;
|
||||
std::vector<ftxui::ColorInfo> info_gray;
|
||||
std::vector<ftxui::ColorInfo> info_color;
|
||||
for (int i = 16; i < 256; ++i) {
|
||||
ColorInfo info = GetColorInfo(Color::Palette256(i));
|
||||
ftxui::ColorInfo info = GetColorInfo(ftxui::Color::Palette256(i));
|
||||
if (info.saturation == 0)
|
||||
info_gray.push_back(info);
|
||||
else
|
||||
@@ -16,10 +17,10 @@ std::vector<std::vector<ColorInfo>> ColorInfoSorted2D() {
|
||||
// Sort info_color by hue.
|
||||
std::sort(
|
||||
info_color.begin(), info_color.end(),
|
||||
[](const ColorInfo& A, const ColorInfo& B) { return A.hue < B.hue; });
|
||||
[](const ftxui::ColorInfo& A, const ftxui::ColorInfo& B) { return A.hue < B.hue; });
|
||||
|
||||
// Make 8 colums, one gray and seven colored.
|
||||
std::vector<std::vector<ColorInfo>> info_columns(8);
|
||||
std::vector<std::vector<ftxui::ColorInfo>> info_columns(8);
|
||||
info_columns[0] = info_gray;
|
||||
for (size_t i = 0; i < info_color.size(); ++i) {
|
||||
info_columns[1 + 7 * i / info_color.size()].push_back(info_color[i]);
|
||||
@@ -28,13 +29,13 @@ std::vector<std::vector<ColorInfo>> ColorInfoSorted2D() {
|
||||
// Minimize discontinuities for every columns.
|
||||
for (auto& column : info_columns) {
|
||||
std::sort(column.begin(), column.end(),
|
||||
[](const ColorInfo& A, const ColorInfo& B) {
|
||||
[](const ftxui::ColorInfo& A, const ftxui::ColorInfo& B) {
|
||||
return A.value < B.value;
|
||||
});
|
||||
for (size_t i = 0; i < column.size() - 1; ++i) {
|
||||
for (int i = 0; i < int(column.size()) - 1; ++i) {
|
||||
int best_index = i + 1;
|
||||
int best_distance = 255 * 255 * 3;
|
||||
for (size_t j = i + 1; j < column.size(); ++j) {
|
||||
for (int j = i + 1; j < column.size(); ++j) {
|
||||
int dx = column[i].red - column[j].red;
|
||||
int dy = column[i].green - column[j].green;
|
||||
int dz = column[i].blue - column[j].blue;
|
||||
|
79
examples/dom/gauge_direction.cpp
Normal file
79
examples/dom/gauge_direction.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include <chrono> // for operator""s, chrono_literals
|
||||
#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 <thread> // for sleep_for
|
||||
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/color.hpp" // for ftxui
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
using namespace ftxui;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
std::string reset_position;
|
||||
for (float percentage = 0.0f; percentage <= 1.0f; percentage += 0.002f) {
|
||||
std::string data_downloaded =
|
||||
std::to_string(int(percentage * 5000)) + "/5000";
|
||||
|
||||
auto gauge_up = //
|
||||
hbox({
|
||||
vtext("gauge vertical"),
|
||||
separator(),
|
||||
gaugeUp(percentage),
|
||||
}) |
|
||||
border;
|
||||
|
||||
auto gauge_down = //
|
||||
hbox({
|
||||
vtext("gauge vertical"),
|
||||
separator(),
|
||||
gaugeDown(percentage),
|
||||
}) |
|
||||
border;
|
||||
|
||||
auto gauge_right = //
|
||||
vbox({
|
||||
text("gauge horizontal"),
|
||||
separator(),
|
||||
gaugeRight(percentage),
|
||||
}) |
|
||||
border;
|
||||
|
||||
auto gauge_left = //
|
||||
vbox({
|
||||
text("gauge horizontal"),
|
||||
separator(),
|
||||
gaugeLeft(percentage),
|
||||
}) |
|
||||
border;
|
||||
|
||||
auto document = hbox({
|
||||
gauge_up,
|
||||
filler(),
|
||||
vbox({
|
||||
gauge_right,
|
||||
filler(),
|
||||
text(data_downloaded) | border | center,
|
||||
filler(),
|
||||
gauge_left,
|
||||
}),
|
||||
filler(),
|
||||
gauge_down,
|
||||
});
|
||||
|
||||
auto screen = Screen(32, 16);
|
||||
Render(screen, document);
|
||||
std::cout << reset_position;
|
||||
screen.Print();
|
||||
reset_position = screen.ResetPosition();
|
||||
|
||||
std::this_thread::sleep_for(0.01s);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// 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,9 +1,10 @@
|
||||
#include <chrono> // for operator""s, chrono_literals
|
||||
#include <cmath> // for sin
|
||||
#include <ftxui/dom/elements.hpp> // for operator|, graph, separator, color, Element, vbox, flex, inverted, Fit, hbox, size, border, GREATER_THAN, HEIGHT
|
||||
#include <ftxui/dom/elements.hpp> // for graph, operator|, separator, color, Element, vbox, flex, inverted, operator|=, Fit, hbox, size, border, GREATER_THAN, HEIGHT
|
||||
#include <ftxui/screen/screen.hpp> // for Full, Screen
|
||||
#include <functional> // for ref, reference_wrapper
|
||||
#include <iostream> // for cout, ostream
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for operator<<, string
|
||||
#include <thread> // for sleep_for
|
||||
#include <vector> // for vector
|
||||
@@ -13,15 +14,15 @@
|
||||
|
||||
class Graph {
|
||||
public:
|
||||
std::vector<int> operator()(int width, int height) {
|
||||
std::vector<int> operator()(int width, int height) const {
|
||||
std::vector<int> output(width);
|
||||
for (int i = 0; i < width; ++i) {
|
||||
float v = 0;
|
||||
v += 0.1f * sin((i + shift) * 0.1f);
|
||||
v += 0.2f * sin((i + shift + 10) * 0.15f);
|
||||
v += 0.1f * sin((i + shift) * 0.03f);
|
||||
v *= height;
|
||||
v += 0.5f * height;
|
||||
v += 0.1f * sin((i + shift) * 0.1f); // NOLINT
|
||||
v += 0.2f * sin((i + shift + 10) * 0.15f); // NOLINT
|
||||
v += 0.1f * sin((i + shift) * 0.03f); // NOLINT
|
||||
v *= height; // NOLINT
|
||||
v += 0.5f * height; // NOLINT
|
||||
output[i] = static_cast<int>(v);
|
||||
}
|
||||
return output;
|
||||
@@ -37,7 +38,7 @@ std::vector<int> triangle(int width, int height) {
|
||||
return output;
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
int main() {
|
||||
using namespace ftxui;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -45,23 +46,26 @@ int main(int argc, const char* argv[]) {
|
||||
|
||||
std::string reset_position;
|
||||
for (int i = 0;; ++i) {
|
||||
auto document =
|
||||
hbox({
|
||||
vbox({
|
||||
graph(std::ref(my_graph)),
|
||||
separator(),
|
||||
graph(triangle) | inverted,
|
||||
}) | flex,
|
||||
auto document = hbox({
|
||||
vbox({
|
||||
graph(std::ref(my_graph)),
|
||||
separator(),
|
||||
vbox({
|
||||
graph(std::ref(my_graph)) | color(Color::BlueLight),
|
||||
separator(),
|
||||
graph(std::ref(my_graph)) | color(Color::RedLight),
|
||||
separator(),
|
||||
graph(std::ref(my_graph)) | color(Color::YellowLight),
|
||||
}) | flex,
|
||||
}) |
|
||||
border | size(HEIGHT, GREATER_THAN, 40);
|
||||
graph(triangle) | inverted,
|
||||
}) | flex,
|
||||
separator(),
|
||||
vbox({
|
||||
graph(std::ref(my_graph)) | color(Color::BlueLight),
|
||||
separator(),
|
||||
graph(std::ref(my_graph)) | color(Color::RedLight),
|
||||
separator(),
|
||||
graph(std::ref(my_graph)) | color(Color::YellowLight),
|
||||
}) | flex,
|
||||
});
|
||||
|
||||
document |= border;
|
||||
|
||||
const int min_width = 40;
|
||||
document |= size(HEIGHT, GREATER_THAN, min_width);
|
||||
|
||||
auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
|
||||
Render(screen, document);
|
||||
@@ -69,7 +73,8 @@ int main(int argc, const char* argv[]) {
|
||||
screen.Print();
|
||||
reset_position = screen.ResetPosition();
|
||||
|
||||
std::this_thread::sleep_for(0.03s);
|
||||
const auto sleep_time = 0.03s;
|
||||
std::this_thread::sleep_for(sleep_time);
|
||||
my_graph.shift++;
|
||||
}
|
||||
|
||||
|
@@ -1,16 +1,15 @@
|
||||
#include <stddef.h> // for size_t
|
||||
#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 allocator, shared_ptr
|
||||
#include <string> // for operator+, to_string, char_traits, string
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for allocator, operator+, to_string, char_traits, string
|
||||
|
||||
#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 make_box = [](size_t dimx, size_t dimy) {
|
||||
auto make_box = [](int dimx, int dimy) {
|
||||
std::string title = std::to_string(dimx) + "x" + std::to_string(dimy);
|
||||
return window(text(title) | hcenter | bold,
|
||||
text("content") | hcenter | dim) |
|
||||
|
@@ -1,11 +1,11 @@
|
||||
#include <chrono> // for operator""s, chrono_literals
|
||||
#include <ftxui/screen/screen.hpp> // for Full, Screen
|
||||
#include <ftxui/screen/screen.hpp> // for Screen
|
||||
#include <iostream> // for cout, ostream
|
||||
#include <memory> // for allocator, shared_ptr
|
||||
#include <string> // for operator<<, string
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for allocator, operator<<, string
|
||||
#include <thread> // for sleep_for
|
||||
|
||||
#include "ftxui/dom/elements.hpp" // for paragraph, text, operator|, Element, border, color, hflow, spinner, vbox, bold, dim, underlined
|
||||
#include "ftxui/dom/elements.hpp" // for paragraph, text, operator|, Element, border, Fit, color, hflow, spinner, vbox, bold, dim, underlined
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/box.hpp" // for ftxui
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Red
|
||||
|
@@ -41,7 +41,7 @@ int main(int argc, const char* argv[]) {
|
||||
|
||||
int remaining_threads = 12;
|
||||
|
||||
int nb_queued = remaining_tasks.size();
|
||||
int nb_queued = (int)remaining_tasks.size();
|
||||
int nb_active = 0;
|
||||
int nb_done = 0;
|
||||
|
||||
|
@@ -1,16 +1,15 @@
|
||||
#include <stddef.h> // for size_t
|
||||
#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 allocator, shared_ptr
|
||||
#include <string> // for operator+, to_string, char_traits, string
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for allocator, operator+, to_string, char_traits, string
|
||||
|
||||
#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 make_box = [](size_t dimx, size_t dimy) {
|
||||
auto make_box = [](int dimx, int dimy) {
|
||||
std::string title = std::to_string(dimx) + "x" + std::to_string(dimy);
|
||||
return window(text(title) | hcenter | bold,
|
||||
text("content") | hcenter | dim) |
|
||||
|
@@ -1,25 +1,32 @@
|
||||
#include <ftxui/dom/elements.hpp> // for operator|, color, Element, bgcolor, graph, border
|
||||
#include <stdlib.h> // for EXIT_SUCCESS
|
||||
#include <ftxui/dom/elements.hpp> // for operator|=, Element, bgcolor, color, graph, border
|
||||
#include <ftxui/screen/screen.hpp> // for Fixed, Screen
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::DarkBlue, Color::Green, Color::Red, ftxui
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::DarkBlue, Color::Red, ftxui
|
||||
|
||||
int main(void) {
|
||||
int main() {
|
||||
using namespace ftxui;
|
||||
Element document = graph([](int x, int y) {
|
||||
std::vector<int> result(x, 0);
|
||||
for (int i{0}; i < x; ++i) {
|
||||
result[i] = ((3 * i) / 2) % y;
|
||||
}
|
||||
return result;
|
||||
}) |
|
||||
color(Color::Red) | border | color(Color::Green) |
|
||||
bgcolor(Color::DarkBlue);
|
||||
std::vector<int> result(x, 0);
|
||||
for (int i{0}; i < x; ++i) {
|
||||
result[i] = ((3 * i) / 2) % y;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
auto screen = Screen::Create(Dimension::Fixed(80), Dimension::Fixed(10));
|
||||
document |= color(Color::Red);
|
||||
document |= bgcolor(Color::DarkBlue);
|
||||
document |= border;
|
||||
|
||||
const int width = 80;
|
||||
const int height = 10;
|
||||
auto screen =
|
||||
Screen::Create(Dimension::Fixed(width), Dimension::Fixed(height));
|
||||
Render(screen, document);
|
||||
screen.Print();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
|
@@ -2,7 +2,8 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>FTXUI examples WebAssembly</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm@4.11.0/lib/xterm.min.js"></script>
|
||||
<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>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@4.11.0/css/xterm.css"></link>
|
||||
</head>
|
||||
<body>
|
||||
@@ -43,12 +44,12 @@
|
||||
});
|
||||
|
||||
let stdin_buffer = [];
|
||||
let stdin = () => {
|
||||
const stdin = () => {
|
||||
return stdin_buffer.shift() || 0;
|
||||
}
|
||||
|
||||
stdout_buffer = [];
|
||||
let stdout = code => {
|
||||
let stdout_buffer = [];
|
||||
const stdout = code => {
|
||||
if (code == 0) {
|
||||
term.write(new Uint8Array(stdout_buffer));
|
||||
stdout_buffer = [];
|
||||
@@ -56,10 +57,19 @@
|
||||
stdout_buffer.push(code)
|
||||
}
|
||||
}
|
||||
let stderr = code => console.log(code);
|
||||
var term = new Terminal();
|
||||
let stderrbuffer = [];
|
||||
const stderr = code => {
|
||||
if (code == 0 || code == 10) {
|
||||
console.error(String.fromCodePoint(...stderrbuffer));
|
||||
stderrbuffer = [];
|
||||
} else {
|
||||
stderrbuffer.push(code)
|
||||
}
|
||||
}
|
||||
const term = new Terminal();
|
||||
term.open(document.querySelector('#terminal'));
|
||||
term.resize(140,43);
|
||||
term.loadAddon(new (WebglAddon.WebglAddon)());
|
||||
const onBinary = e => {
|
||||
for(c of e)
|
||||
stdin_buffer.push(c.charCodeAt(0));
|
||||
@@ -67,7 +77,9 @@
|
||||
term.onBinary(onBinary);
|
||||
term.onData(onBinary)
|
||||
window.Module = {
|
||||
preRun: () => { FS.init(stdin, stdout, stderr); },
|
||||
preRun: () => {
|
||||
FS.init(stdin, stdout, stderr);
|
||||
},
|
||||
postRun: [],
|
||||
onRuntimeInitialized: () => {},
|
||||
};
|
||||
@@ -78,6 +90,7 @@
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
body {
|
||||
background-color:#EEE;
|
||||
padding:20px;
|
||||
|
119
include/ftxui/component/animation.hpp
Normal file
119
include/ftxui/component/animation.hpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#ifndef FTXUI_ANIMATION_HPP
|
||||
#define FTXUI_ANIMATION_HPP
|
||||
|
||||
#include <chrono> // for milliseconds, duration, steady_clock, time_point
|
||||
#include <functional> // for function
|
||||
|
||||
#include "ftxui/component/event.hpp"
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace animation {
|
||||
// Components who haven't completed their animation can call this function to
|
||||
// request a new frame to be drawn later.
|
||||
//
|
||||
// When there is no new events and no animations to complete, no new frame is
|
||||
// drawn.
|
||||
void RequestAnimationFrame();
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using TimePoint = std::chrono::time_point<Clock>;
|
||||
using Duration = std::chrono::duration<double>;
|
||||
|
||||
// Parameter of Component::OnAnimation(param).
|
||||
class Params {
|
||||
public:
|
||||
Params(Duration duration) : duration_(duration) {}
|
||||
|
||||
/// The duration this animation step represents.
|
||||
Duration duration() const { return duration_; }
|
||||
|
||||
private:
|
||||
Duration duration_;
|
||||
};
|
||||
|
||||
namespace easing {
|
||||
using Function = std::function<float(float)>;
|
||||
// Linear interpolation (no easing)
|
||||
float Linear(float p);
|
||||
|
||||
// Quadratic easing; p^2
|
||||
float QuadraticIn(float p);
|
||||
float QuadraticOut(float p);
|
||||
float QuadraticInOut(float p);
|
||||
|
||||
// Cubic easing; p^3
|
||||
float CubicIn(float p);
|
||||
float CubicOut(float p);
|
||||
float CubicInOut(float p);
|
||||
|
||||
// Quartic easing; p^4
|
||||
float QuarticIn(float p);
|
||||
float QuarticOut(float p);
|
||||
float QuarticInOut(float p);
|
||||
|
||||
// Quintic easing; p^5
|
||||
float QuinticIn(float p);
|
||||
float QuinticOut(float p);
|
||||
float QuinticInOut(float p);
|
||||
|
||||
// Sine wave easing; sin(p * PI/2)
|
||||
float SineIn(float p);
|
||||
float SineOut(float p);
|
||||
float SineInOut(float p);
|
||||
|
||||
// Circular easing; sqrt(1 - p^2)
|
||||
float CircularIn(float p);
|
||||
float CircularOut(float p);
|
||||
float CircularInOut(float p);
|
||||
|
||||
// Exponential easing, base 2
|
||||
float ExponentialIn(float p);
|
||||
float ExponentialOut(float p);
|
||||
float ExponentialInOut(float p);
|
||||
|
||||
// Exponentially-damped sine wave easing
|
||||
float ElasticIn(float p);
|
||||
float ElasticOut(float p);
|
||||
float ElasticInOut(float p);
|
||||
|
||||
// Overshooting cubic easing;
|
||||
float BackIn(float p);
|
||||
float BackOut(float p);
|
||||
float BackInOut(float p);
|
||||
|
||||
// Exponentially-decaying bounce easing
|
||||
float BounceIn(float p);
|
||||
float BounceOut(float p);
|
||||
float BounceInOut(float p);
|
||||
} // namespace easing
|
||||
|
||||
class Animator {
|
||||
public:
|
||||
Animator(float* from,
|
||||
float to = 0.f,
|
||||
Duration duration = std::chrono::milliseconds(250),
|
||||
easing::Function easing_function = easing::Linear,
|
||||
Duration delay = std::chrono::milliseconds(0));
|
||||
|
||||
void OnAnimation(Params&);
|
||||
|
||||
float to() const { return to_; }
|
||||
|
||||
private:
|
||||
float* value_;
|
||||
float from_;
|
||||
float to_;
|
||||
Duration duration_;
|
||||
easing::Function easing_function_;
|
||||
Duration current_;
|
||||
};
|
||||
|
||||
} // namespace animation
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_ANIMATION_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.
|
@@ -4,10 +4,11 @@
|
||||
#include <functional> // for function
|
||||
#include <memory> // for make_shared, shared_ptr
|
||||
#include <string> // for wstring
|
||||
#include <utility> // for forward
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/component_base.hpp" // for Component, Components
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption, CheckboxOption, InputOption, MenuOption, RadioboxOption, ToggleOption
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption, CheckboxOption, InputOption (ptr only), MenuEntryOption (ptr only), MenuOption, RadioboxOption (ptr only)
|
||||
#include "ftxui/dom/elements.hpp" // for Element
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringRef, ConstStringListRef, StringRef
|
||||
|
||||
@@ -18,14 +19,21 @@ struct Event;
|
||||
struct InputOption;
|
||||
struct MenuOption;
|
||||
struct RadioboxOption;
|
||||
struct ToggleOption;
|
||||
struct MenuEntryOption;
|
||||
|
||||
template <class T, class... Args>
|
||||
std::shared_ptr<T> Make(Args&&... args) {
|
||||
return std::make_shared<T>(args...);
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Pipe operator to decorate components.
|
||||
using ComponentDecorator = std::function<Component(Component)>;
|
||||
using ElementDecorator = std::function<Element(Element)>;
|
||||
Component operator|(Component component, ComponentDecorator decorator);
|
||||
Component operator|(Component component, ElementDecorator decorator);
|
||||
Component& operator|=(Component& component, ComponentDecorator decorator);
|
||||
Component& operator|=(Component& component, ElementDecorator decorator);
|
||||
|
||||
namespace Container {
|
||||
Component Vertical(Components children);
|
||||
Component Vertical(Components children, int* selector);
|
||||
@@ -37,44 +45,54 @@ Component Tab(Components children, int* selector);
|
||||
|
||||
Component Button(ConstStringRef label,
|
||||
std::function<void()> on_click,
|
||||
Ref<ButtonOption> = {});
|
||||
Ref<ButtonOption> = ButtonOption::Simple());
|
||||
|
||||
Component Checkbox(ConstStringRef label,
|
||||
bool* checked,
|
||||
Ref<CheckboxOption> option = {});
|
||||
Ref<CheckboxOption> option = CheckboxOption::Simple());
|
||||
|
||||
Component Input(StringRef content,
|
||||
ConstStringRef placeholder,
|
||||
Ref<InputOption> option = {});
|
||||
|
||||
Component Menu(ConstStringListRef entries,
|
||||
int* selected_,
|
||||
Ref<MenuOption> = {});
|
||||
Ref<MenuOption> = MenuOption::Vertical());
|
||||
Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> = {});
|
||||
|
||||
Component Dropdown(ConstStringListRef entries, int* selected);
|
||||
|
||||
Component Radiobox(ConstStringListRef entries,
|
||||
int* selected_,
|
||||
Ref<RadioboxOption> option = {});
|
||||
Component Toggle(ConstStringListRef entries,
|
||||
int* selected,
|
||||
Ref<ToggleOption> 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);
|
||||
|
||||
Component ResizableSplitLeft(Component main, Component back, int* main_size);
|
||||
Component ResizableSplitRight(Component main, Component back, int* main_size);
|
||||
Component ResizableSplitTop(Component main, Component back, int* main_size);
|
||||
Component ResizableSplitBottom(Component main, Component back, int* main_size);
|
||||
|
||||
Component Renderer(Component child, std::function<Element()>);
|
||||
Component Renderer(std::function<Element()>);
|
||||
Component Renderer(std::function<Element(bool /* focused */)>);
|
||||
ComponentDecorator Renderer(ElementDecorator);
|
||||
|
||||
Component CatchEvent(Component child, std::function<bool(Event)>);
|
||||
ComponentDecorator CatchEvent(std::function<bool(Event)> on_event);
|
||||
|
||||
Component Maybe(Component, const bool* show);
|
||||
Component Maybe(Component, std::function<bool()>);
|
||||
ComponentDecorator Maybe(const bool* show);
|
||||
ComponentDecorator Maybe(std::function<bool()>);
|
||||
|
||||
Component Collapsible(ConstStringRef label,
|
||||
Component child,
|
||||
Ref<bool> show = false);
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Include component using the old deprecated wstring.
|
||||
#include "ftxui/component/deprecated.hpp"
|
||||
|
||||
#endif /* end of include guard: FTXUI_COMPONENT_HPP */
|
||||
|
||||
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
||||
|
@@ -13,6 +13,10 @@ class Delegate;
|
||||
class Focus;
|
||||
struct Event;
|
||||
|
||||
namespace animation {
|
||||
class Params;
|
||||
} // namespace animation
|
||||
|
||||
class ComponentBase;
|
||||
using Component = std::shared_ptr<ComponentBase>;
|
||||
using Components = std::vector<Component>;
|
||||
@@ -42,6 +46,9 @@ class ComponentBase {
|
||||
// Returns whether the event was handled or not.
|
||||
virtual bool OnEvent(Event);
|
||||
|
||||
// Handle an animation step.
|
||||
virtual void OnAnimation(animation::Params& params);
|
||||
|
||||
// Focus management ----------------------------------------------------------
|
||||
//
|
||||
// If this component contains children, this indicates which one is active,
|
||||
|
@@ -1,58 +1,136 @@
|
||||
#ifndef FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP
|
||||
#define FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP
|
||||
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
#include <ftxui/util/ref.hpp>
|
||||
#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 <functional> // for function
|
||||
#include <optional> // for optional
|
||||
#include <string> // for string
|
||||
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::GrayDark, Color::White
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
/// @brief Option for the Menu component.
|
||||
/// @brief arguments for |ButtonOption::transform|, |CheckboxOption::transform|,
|
||||
/// |Radiobox::transform|, |MenuEntryOption::transform|,
|
||||
/// |MenuOption::transform|.
|
||||
struct EntryState {
|
||||
std::string label; /// < The label to display.
|
||||
bool state; /// < The state of the button/checkbox/radiobox
|
||||
bool active; /// < Whether the entry is the active one.
|
||||
bool focused; /// < Whether the entry is one focused by the user.
|
||||
};
|
||||
|
||||
struct UnderlineOption {
|
||||
bool enabled = false;
|
||||
|
||||
Color color_active = Color::White;
|
||||
Color color_inactive = Color::GrayDark;
|
||||
|
||||
animation::easing::Function leader_function =
|
||||
animation::easing::QuadraticInOut;
|
||||
animation::easing::Function follower_function =
|
||||
animation::easing::QuadraticInOut;
|
||||
|
||||
animation::Duration leader_duration = std::chrono::milliseconds(250);
|
||||
animation::Duration leader_delay = std::chrono::milliseconds(0);
|
||||
animation::Duration follower_duration = std::chrono::milliseconds(250);
|
||||
animation::Duration follower_delay = std::chrono::milliseconds(0);
|
||||
|
||||
void SetAnimation(animation::Duration d, animation::easing::Function f);
|
||||
void SetAnimationDuration(animation::Duration d);
|
||||
void SetAnimationFunction(animation::easing::Function f);
|
||||
void SetAnimationFunction(animation::easing::Function f_leader,
|
||||
animation::easing::Function f_follower);
|
||||
};
|
||||
|
||||
/// @brief Option about a potentially animated color.
|
||||
/// @ingroup component
|
||||
struct MenuOption {
|
||||
Decorator style_normal = nothing; ///< style.
|
||||
Decorator style_focused = inverted; ///< Style when focused.
|
||||
Decorator style_selected = bold; ///< Style when selected.
|
||||
Decorator style_selected_focused =
|
||||
Decorator(inverted) | bold; ///< Style when selected and focused.
|
||||
struct AnimatedColorOption {
|
||||
void Set(
|
||||
Color inactive,
|
||||
Color active,
|
||||
animation::Duration duration = std::chrono::milliseconds(250),
|
||||
animation::easing::Function function = animation::easing::QuadraticInOut);
|
||||
|
||||
/// Called when the selected entry changes.
|
||||
std::function<void()> on_change = [] {};
|
||||
/// Called when the user presses enter.
|
||||
std::function<void()> on_enter = [] {};
|
||||
bool enabled = false;
|
||||
Color inactive;
|
||||
Color active;
|
||||
animation::Duration duration = std::chrono::milliseconds(250);
|
||||
animation::easing::Function function = animation::easing::QuadraticInOut;
|
||||
};
|
||||
|
||||
Ref<int> focused_entry = 0;
|
||||
struct AnimatedColorsOption {
|
||||
AnimatedColorOption background;
|
||||
AnimatedColorOption foreground;
|
||||
};
|
||||
|
||||
/// @brief Option for the MenuEntry component.
|
||||
/// @ingroup component
|
||||
struct MenuEntryOption {
|
||||
Decorator style_normal = nothing; ///< style.
|
||||
Decorator style_focused = inverted; ///< Style when focused.
|
||||
Decorator style_selected = bold; ///< Style when selected.
|
||||
Decorator style_selected_focused =
|
||||
Decorator(inverted) | bold; ///< Style when selected and focused.
|
||||
std::function<Element(const EntryState& state)> transform;
|
||||
AnimatedColorsOption animated_colors;
|
||||
};
|
||||
|
||||
/// @brief Option for the Button component.
|
||||
/// @brief Option for the Menu component.
|
||||
/// @ingroup component
|
||||
struct MenuOption {
|
||||
// Standard constructors:
|
||||
static MenuOption Horizontal();
|
||||
static MenuOption HorizontalAnimated();
|
||||
static MenuOption Vertical();
|
||||
static MenuOption VerticalAnimated();
|
||||
static MenuOption Toggle();
|
||||
|
||||
// Style:
|
||||
UnderlineOption underline;
|
||||
MenuEntryOption entries;
|
||||
enum Direction { Up, Down, Left, Right };
|
||||
Direction direction = Down;
|
||||
std::function<Element()> elements_prefix;
|
||||
std::function<Element()> elements_infix;
|
||||
std::function<Element()> elements_postfix;
|
||||
|
||||
// Observers:
|
||||
std::function<void()> on_change; ///> Called when the seelcted entry changes.
|
||||
std::function<void()> on_enter; ///> Called when the user presses enter.
|
||||
Ref<int> focused_entry = 0;
|
||||
};
|
||||
|
||||
/// @brief Option for the AnimatedButton component.
|
||||
/// @ingroup component
|
||||
struct ButtonOption {
|
||||
/// Whether to show a border around the button.
|
||||
bool border = true;
|
||||
// Standard constructors:
|
||||
static ButtonOption Ascii();
|
||||
static ButtonOption Simple();
|
||||
static ButtonOption Border();
|
||||
static ButtonOption Animated();
|
||||
static ButtonOption Animated(Color color);
|
||||
static ButtonOption Animated(Color background, Color foreground);
|
||||
static ButtonOption Animated(Color background,
|
||||
Color foreground,
|
||||
Color background_active,
|
||||
Color foreground_active);
|
||||
|
||||
// Style:
|
||||
std::function<Element(const EntryState&)> transform;
|
||||
AnimatedColorsOption animated_colors;
|
||||
};
|
||||
|
||||
/// @brief Option for the Checkbox component.
|
||||
/// @ingroup component
|
||||
struct CheckboxOption {
|
||||
std::string style_checked = "▣ "; ///< Prefix for a "checked" state.
|
||||
std::string style_unchecked = "☐ "; ///< Prefix for a "unchecked" state.
|
||||
Decorator style_normal = nothing; ///< style.
|
||||
Decorator style_focused = inverted; ///< Style when focused.
|
||||
Decorator style_selected = bold; ///< Style when selected.
|
||||
Decorator style_selected_focused =
|
||||
Decorator(inverted) | bold; ///< Style when selected and focused.
|
||||
// Standard constructors:
|
||||
static CheckboxOption Simple();
|
||||
|
||||
// Style:
|
||||
std::function<Element(const EntryState&)> transform;
|
||||
|
||||
// Observer:
|
||||
/// Called when the user change the state.
|
||||
std::function<void()> on_change = []() {};
|
||||
std::function<void()> on_change = [] {};
|
||||
};
|
||||
|
||||
/// @brief Option for the Input component.
|
||||
@@ -74,34 +152,15 @@ struct InputOption {
|
||||
/// @brief Option for the Radiobox component.
|
||||
/// @ingroup component
|
||||
struct RadioboxOption {
|
||||
std::string style_checked = "◉ "; ///< Prefix for a "checked" state.
|
||||
std::string style_unchecked = "○ "; ///< Prefix for a "unchecked" state.
|
||||
Decorator style_normal = nothing; ///< style.
|
||||
Decorator style_focused = inverted; ///< Style when focused.
|
||||
Decorator style_selected = bold; ///< Style when selected.
|
||||
Decorator style_selected_focused =
|
||||
Decorator(inverted) | bold; ///< Style when selected and focused.
|
||||
// Standard constructors:
|
||||
static RadioboxOption Simple();
|
||||
|
||||
/// Called when the selected entry changes.
|
||||
std::function<void()> on_change = []() {};
|
||||
|
||||
Ref<int> focused_entry = 0;
|
||||
};
|
||||
|
||||
/// @brief Option for the Toggle component.
|
||||
/// @ingroup component
|
||||
struct ToggleOption {
|
||||
Decorator style_normal = nothing; ///< style.
|
||||
Decorator style_focused = inverted; ///< Style when focused.
|
||||
Decorator style_selected = bold; ///< Style when selected.
|
||||
Decorator style_selected_focused =
|
||||
Decorator(inverted) | bold; ///< Style when selected and focused.
|
||||
// Style:
|
||||
std::function<Element(const EntryState&)> transform;
|
||||
|
||||
// Observers:
|
||||
/// Called when the selected entry changes.
|
||||
std::function<void()> on_change = [] {};
|
||||
/// Called when the user presses enter.
|
||||
std::function<void()> on_enter = [] {};
|
||||
|
||||
Ref<int> focused_entry = 0;
|
||||
};
|
||||
|
||||
|
@@ -1,17 +0,0 @@
|
||||
#ifndef FTXUI_COMPONENT_DEPRECATED_HPP
|
||||
#define FTXUI_COMPONENT_DEPRECATED_HPP
|
||||
|
||||
#include "ftxui/component/component.hpp"
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
Component Input(WideStringRef content,
|
||||
ConstStringRef placeholder,
|
||||
Ref<InputOption> option = {});
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* FTXUI_COMPONENT_DEPRECATED_HPP */
|
||||
|
||||
// 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.
|
@@ -2,7 +2,8 @@
|
||||
#define FTXUI_COMPONENT_EVENT_HPP
|
||||
|
||||
#include <ftxui/component/mouse.hpp> // for Mouse
|
||||
#include <string> // for string, operator==
|
||||
#include <functional>
|
||||
#include <string> // for string, operator==
|
||||
#include <vector>
|
||||
|
||||
namespace ftxui {
|
||||
@@ -53,7 +54,7 @@ struct Event {
|
||||
static const Event PageDown;
|
||||
|
||||
// --- Custom ---
|
||||
static Event Custom;
|
||||
static const Event Custom;
|
||||
|
||||
//--- Method section ---------------------------------------------------------
|
||||
bool is_character() const { return type_ == Type::Character; }
|
||||
|
@@ -7,9 +7,12 @@
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for string
|
||||
#include <thread> // for thread
|
||||
#include <variant> // for variant
|
||||
|
||||
#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/screen/screen.hpp" // for Screen
|
||||
|
||||
namespace ftxui {
|
||||
@@ -17,37 +20,43 @@ class ComponentBase;
|
||||
struct Event;
|
||||
|
||||
using Component = std::shared_ptr<ComponentBase>;
|
||||
class ScreenInteractivePrivate;
|
||||
|
||||
class ScreenInteractive : public Screen {
|
||||
public:
|
||||
using Callback = std::function<void()>;
|
||||
|
||||
// Constructors:
|
||||
static ScreenInteractive FixedSize(int dimx, int dimy);
|
||||
static ScreenInteractive Fullscreen();
|
||||
static ScreenInteractive FitComponent();
|
||||
static ScreenInteractive TerminalOutput();
|
||||
|
||||
void Loop(Component);
|
||||
Callback ExitLoopClosure();
|
||||
// Return the currently active screen, nullptr if none.
|
||||
static ScreenInteractive* Active();
|
||||
|
||||
void Loop(Component);
|
||||
Closure ExitLoopClosure();
|
||||
|
||||
void Post(Task task);
|
||||
void PostEvent(Event event);
|
||||
void RequestAnimationFrame();
|
||||
|
||||
CapturedMouse CaptureMouse();
|
||||
|
||||
// Decorate a function. The outputted one will execute similarly to the
|
||||
// inputted one, but with the currently active screen terminal hooks
|
||||
// temporarily uninstalled.
|
||||
Callback WithRestoredIO(Callback);
|
||||
Closure WithRestoredIO(Closure);
|
||||
|
||||
private:
|
||||
void Install();
|
||||
void Uninstall();
|
||||
|
||||
void Main(Component component);
|
||||
ScreenInteractive* suspended_screen_ = nullptr;
|
||||
|
||||
void Draw(Component component);
|
||||
void EventLoop(Component component);
|
||||
void SigStop();
|
||||
|
||||
ScreenInteractive* suspended_screen_ = nullptr;
|
||||
enum class Dimension {
|
||||
FitComponent,
|
||||
Fixed,
|
||||
@@ -61,20 +70,30 @@ class ScreenInteractive : public Screen {
|
||||
Dimension dimension,
|
||||
bool use_alternative_screen);
|
||||
|
||||
Sender<Event> event_sender_;
|
||||
Receiver<Event> event_receiver_;
|
||||
Sender<Task> task_sender_;
|
||||
Receiver<Task> task_receiver_;
|
||||
|
||||
std::string set_cursor_position;
|
||||
std::string reset_cursor_position;
|
||||
|
||||
std::atomic<bool> quit_ = false;
|
||||
std::thread event_listener_;
|
||||
std::thread animation_listener_;
|
||||
bool animation_requested_ = false;
|
||||
animation::TimePoint previous_animation_time;
|
||||
|
||||
int cursor_x_ = 1;
|
||||
int cursor_y_ = 1;
|
||||
|
||||
bool mouse_captured = false;
|
||||
bool previous_frame_resized_ = false;
|
||||
|
||||
public:
|
||||
class Private {
|
||||
public:
|
||||
static void SigStop(ScreenInteractive& s) { return s.SigStop(); }
|
||||
};
|
||||
friend Private;
|
||||
};
|
||||
|
||||
} // namespace ftxui
|
||||
|
18
include/ftxui/component/task.hpp
Normal file
18
include/ftxui/component/task.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef FTXUI_COMPONENT_ANIMATION_HPP
|
||||
#define FTXUI_COMPONENT_ANIMATION_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <variant>
|
||||
#include "ftxui/component/event.hpp"
|
||||
|
||||
namespace ftxui {
|
||||
class AnimationTask {};
|
||||
using Closure = std::function<void()>;
|
||||
using Task = std::variant<Event, Closure, AnimationTask>;
|
||||
} // namespace ftxui
|
||||
|
||||
#endif // FTXUI_COMPONENT_ANIMATION_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.
|
@@ -1,7 +1,7 @@
|
||||
#ifndef FTXUI_DOM_CANVAS_HPP
|
||||
#define FTXUI_DOM_CANVAS_HPP
|
||||
|
||||
#include <stddef.h> // for size_t
|
||||
#include <cstddef> // for size_t
|
||||
#include <functional> // for function
|
||||
#include <string> // for string
|
||||
#include <unordered_map> // for unordered_map
|
||||
@@ -9,11 +9,17 @@
|
||||
#include "ftxui/screen/color.hpp" // for Color
|
||||
#include "ftxui/screen/screen.hpp" // for Pixel
|
||||
|
||||
#ifdef DrawText
|
||||
// Workaround for WinUsr.h (via Windows.h) defining macros that break things.
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-drawtext
|
||||
#undef DrawText
|
||||
#endif
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
struct Canvas {
|
||||
public:
|
||||
Canvas() {}
|
||||
Canvas() = default;
|
||||
Canvas(int width, int height);
|
||||
|
||||
// Getters:
|
||||
@@ -114,7 +120,8 @@ struct Canvas {
|
||||
|
||||
struct XYHash {
|
||||
size_t operator()(const XY& xy) const {
|
||||
return static_cast<size_t>(xy.x * 1024 + xy.y);
|
||||
constexpr size_t shift = 1024;
|
||||
return size_t(xy.x) * shift + size_t(xy.y);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#ifndef FTXUI_DOM_DEPRECRATED_HPP
|
||||
#define FTXUI_DOM_DEPRECRATED_HPP
|
||||
#ifndef FTXUI_DOM_DEPRECATED_HPP
|
||||
#define FTXUI_DOM_DEPRECATED_HPP
|
||||
|
||||
#include "ftxui/dom/elements.hpp"
|
||||
|
||||
@@ -9,7 +9,7 @@ Element vtext(std::wstring text);
|
||||
Elements paragraph(std::wstring text);
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_DOM_DEPRECRATED_HPP */
|
||||
#endif // FTXUI_DOM_DEPRECATED_HPP
|
||||
|
||||
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
|
@@ -21,19 +21,21 @@ using Decorator = std::function<Element(Element)>;
|
||||
using GraphFunction = std::function<std::vector<int>(int, int)>;
|
||||
|
||||
enum BorderStyle { LIGHT, HEAVY, DOUBLE, ROUNDED, EMPTY };
|
||||
enum class GaugeDirection { Left, Up, Right, Down };
|
||||
|
||||
// Pipe elements into decorator togethers.
|
||||
// For instance the next lines are equivalents:
|
||||
// -> text("ftxui") | bold | underlined
|
||||
// -> underlined(bold(text("FTXUI")))
|
||||
Element operator|(Element, Decorator);
|
||||
Element& operator|=(Element&, Decorator);
|
||||
Elements operator|(Elements, Decorator);
|
||||
Decorator operator|(Decorator, Decorator);
|
||||
|
||||
// --- Widget ---
|
||||
Element text(std::string text);
|
||||
Element vtext(std::string text);
|
||||
Element separator(void);
|
||||
Element separator();
|
||||
Element separatorLight();
|
||||
Element separatorHeavy();
|
||||
Element separatorDouble();
|
||||
@@ -41,7 +43,20 @@ Element separatorEmpty();
|
||||
Element separatorStyled(BorderStyle);
|
||||
Element separator(Pixel);
|
||||
Element separatorCharacter(std::string);
|
||||
Element gauge(float ratio);
|
||||
Element separatorHSelector(float left,
|
||||
float right,
|
||||
Color unselected_color,
|
||||
Color selected_color);
|
||||
Element separatorVSelector(float up,
|
||||
float down,
|
||||
Color unselected_color,
|
||||
Color selected_color);
|
||||
Element gauge(float progress);
|
||||
Element gaugeLeft(float progress);
|
||||
Element gaugeRight(float progress);
|
||||
Element gaugeUp(float progress);
|
||||
Element gaugeDown(float progress);
|
||||
Element gaugeDirection(float progress, GaugeDirection);
|
||||
Element border(Element);
|
||||
Element borderLight(Element);
|
||||
Element borderHeavy(Element);
|
||||
@@ -49,14 +64,14 @@ Element borderDouble(Element);
|
||||
Element borderRounded(Element);
|
||||
Element borderEmpty(Element);
|
||||
Decorator borderStyled(BorderStyle);
|
||||
Decorator borderWith(Pixel);
|
||||
Decorator borderWith(const Pixel&);
|
||||
Element window(Element title, Element content);
|
||||
Element spinner(int charset_index, size_t image_index);
|
||||
Element paragraph(std::string text);
|
||||
Element paragraphAlignLeft(std::string text);
|
||||
Element paragraphAlignRight(std::string text);
|
||||
Element paragraphAlignCenter(std::string text);
|
||||
Element paragraphAlignJustify(std::string text);
|
||||
Element paragraph(const std::string& text);
|
||||
Element paragraphAlignLeft(const std::string& text);
|
||||
Element paragraphAlignRight(const std::string& text);
|
||||
Element paragraphAlignCenter(const std::string& text);
|
||||
Element paragraphAlignJustify(const std::string& text);
|
||||
Element graph(GraphFunction);
|
||||
Element emptyElement();
|
||||
Element canvas(ConstRef<Canvas>);
|
||||
@@ -75,6 +90,7 @@ Element color(Color, Element);
|
||||
Element bgcolor(Color, Element);
|
||||
Decorator focusPosition(int x, int y);
|
||||
Decorator focusPositionRelative(float x, float y);
|
||||
Element automerge(Element child);
|
||||
|
||||
// --- Layout is
|
||||
// Horizontal, Vertical or stacked set of elements.
|
||||
@@ -147,7 +163,7 @@ Dimensions Fit(Element&);
|
||||
|
||||
// Include old definitions using wstring.
|
||||
#include "ftxui/dom/deprecated.hpp"
|
||||
#endif /* end of include guard: FTXUI_DOM_ELEMENTS_HPP */
|
||||
#endif // FTXUI_DOM_ELEMENTS_HPP
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
|
@@ -108,7 +108,7 @@ struct FlexboxConfig {
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_DOM_FLEXBOX_CONFIG_HPP */
|
||||
#endif // FTXUI_DOM_FLEXBOX_CONFIG_HPP
|
||||
|
||||
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
|
@@ -20,6 +20,11 @@ class Node {
|
||||
public:
|
||||
Node();
|
||||
Node(Elements children);
|
||||
Node(const Node&) = delete;
|
||||
Node(const Node&&) = delete;
|
||||
Node& operator=(const Node&) = delete;
|
||||
Node& operator=(const Node&&) = delete;
|
||||
|
||||
virtual ~Node();
|
||||
|
||||
// Step 1: Compute layout requirement. Tell parent what dimensions this
|
||||
@@ -50,12 +55,12 @@ class Node {
|
||||
Box box_;
|
||||
};
|
||||
|
||||
void Render(Screen& screen, const Element& node);
|
||||
void Render(Screen& screen, const Element& element);
|
||||
void Render(Screen& screen, Node* node);
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_DOM_NODE_HPP */
|
||||
#endif // FTXUI_DOM_NODE_HPP
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
|
@@ -28,7 +28,7 @@ struct Requirement {
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_DOM_REQUIREMENT_HPP */
|
||||
#endif // FTXUI_DOM_REQUIREMENT_HPP
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
|
@@ -5,7 +5,7 @@
|
||||
#include <string> // for string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/dom/elements.hpp" // for BorderStyle, LIGHT, Element, Decorator
|
||||
#include "ftxui/dom/elements.hpp" // for Element, BorderStyle, LIGHT, Decorator
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
@@ -52,10 +52,10 @@ class Table {
|
||||
void Initialize(std::vector<std::vector<Element>>);
|
||||
friend TableSelection;
|
||||
std::vector<std::vector<Element>> elements_;
|
||||
int input_dim_x_;
|
||||
int input_dim_y_;
|
||||
int dim_x_;
|
||||
int dim_y_;
|
||||
int input_dim_x_ = 0;
|
||||
int input_dim_y_ = 0;
|
||||
int dim_x_ = 0;
|
||||
int dim_y_ = 0;
|
||||
};
|
||||
|
||||
class TableSelection {
|
||||
|
@@ -1,10 +1,13 @@
|
||||
#ifndef FTXUI_DOM_TAKE_ANY_ARGS_HPP
|
||||
#define FTXUI_DOM_TAKE_ANY_ARGS_HPP
|
||||
|
||||
// IWYU pragma: private, include "ftxui/dom/elements.hpp"
|
||||
#include <type_traits>
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
template <class T>
|
||||
void Merge(Elements&, T) {}
|
||||
void Merge(Elements& /*container*/, T /*element*/) {}
|
||||
|
||||
template <>
|
||||
inline void Merge(Elements& container, Element element) {
|
||||
@@ -38,6 +41,8 @@ TAKE_ANY_ARGS(dbox)
|
||||
TAKE_ANY_ARGS(hflow)
|
||||
} // namespace ftxui
|
||||
|
||||
#endif // FTXUI_DOM_TAKE_ANY_ARGS_HPP
|
||||
|
||||
// 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.
|
||||
|
@@ -9,15 +9,15 @@ struct Box {
|
||||
int y_min = 0;
|
||||
int y_max = 0;
|
||||
|
||||
static Box Intersection(Box a, Box b);
|
||||
bool Contain(int x, int y);
|
||||
static auto Intersection(Box a, Box b) -> Box;
|
||||
bool Contain(int x, int y) const;
|
||||
bool operator==(const Box& other) const;
|
||||
bool operator!=(const Box& other) const;
|
||||
};
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_SCREEN_BOX_HPP */
|
||||
#endif // FTXUI_SCREEN_BOX_HPP
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#ifndef FTXUI_SCREEN_COLOR
|
||||
#define FTXUI_SCREEN_COLOR
|
||||
#ifndef FTXUI_SCREEN_COLOR_HPP
|
||||
#define FTXUI_SCREEN_COLOR_HPP
|
||||
|
||||
#include <stdint.h> // for uint8_t
|
||||
#include <string> // for wstring
|
||||
#include <cstdint> // for uint8_t
|
||||
#include <string> // for wstring
|
||||
|
||||
#ifdef RGB
|
||||
// Workaround for wingdi.h (via Windows.h) defining macros that break things.
|
||||
@@ -27,6 +27,7 @@ class Color {
|
||||
Color(uint8_t red, uint8_t green, uint8_t blue);
|
||||
static Color RGB(uint8_t red, uint8_t green, uint8_t blue);
|
||||
static Color HSV(uint8_t hue, uint8_t saturation, uint8_t value);
|
||||
static Color Interpolate(float t, const Color& a, const Color& b);
|
||||
|
||||
//---------------------------
|
||||
// List of colors:
|
||||
@@ -312,12 +313,8 @@ class Color {
|
||||
Palette256,
|
||||
TrueColor,
|
||||
};
|
||||
|
||||
ColorType type_;
|
||||
union {
|
||||
uint8_t index_ = 0;
|
||||
uint8_t red_;
|
||||
};
|
||||
ColorType type_ = ColorType::Palette1;
|
||||
uint8_t red_ = 0;
|
||||
uint8_t green_ = 0;
|
||||
uint8_t blue_ = 0;
|
||||
};
|
||||
@@ -332,7 +329,7 @@ Color operator""_rgb(unsigned long long int combined);
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_COLOR_H_ */
|
||||
#endif // FTXUI_SCREEN_COLOR_HPP
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#ifndef FTXUI_SCREEN_COLOR_INFO_HPP
|
||||
#define FTXUI_SCREEN_COLOR_INFO_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstdint>
|
||||
#include <ftxui/screen/color.hpp>
|
||||
|
||||
namespace ftxui {
|
||||
@@ -23,7 +23,7 @@ ColorInfo GetColorInfo(Color::Palette16 index);
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_SCREEN_COLOR_INFO_HPP */
|
||||
#endif // FTXUI_SCREEN_COLOR_INFO_HPP
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
|
@@ -8,7 +8,7 @@ int wchar_width(wchar_t);
|
||||
int wstring_width(const std::wstring&);
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_SCREEN_DEPRECATED_HPP */
|
||||
#endif // FTXUI_SCREEN_DEPRECATED_HPP
|
||||
|
||||
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#ifndef FTXUI_SCREEN_SCREEN
|
||||
#define FTXUI_SCREEN_SCREEN
|
||||
#ifndef FTXUI_SCREEN_SCREEN_HPP
|
||||
#define FTXUI_SCREEN_SCREEN_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <string> // for string, allocator, basic_string
|
||||
@@ -14,6 +14,8 @@ namespace ftxui {
|
||||
/// @brief A unicode character and its associated style.
|
||||
/// @ingroup screen
|
||||
struct Pixel {
|
||||
bool operator==(const Pixel& other) const;
|
||||
|
||||
// The graphemes stored into the pixel. To support combining characters,
|
||||
// like: a⃦, this can potentially contains multiple codepoitns.
|
||||
std::string character = " ";
|
||||
@@ -68,13 +70,12 @@ class Screen {
|
||||
int dimy() const { return dimy_; }
|
||||
|
||||
// Move the terminal cursor n-lines up with n = dimy().
|
||||
std::string ResetPosition(bool clear = false);
|
||||
std::string ResetPosition(bool clear = false) const;
|
||||
|
||||
// Fill with space.
|
||||
void Clear();
|
||||
|
||||
void ApplyShader();
|
||||
Box stencil;
|
||||
|
||||
struct Cursor {
|
||||
int x = 0;
|
||||
@@ -83,16 +84,20 @@ class Screen {
|
||||
Cursor cursor() const { return cursor_; }
|
||||
void SetCursor(Cursor cursor) { cursor_ = cursor; }
|
||||
|
||||
Box stencil;
|
||||
|
||||
protected:
|
||||
int dimx_;
|
||||
int dimy_;
|
||||
std::vector<std::vector<Pixel>> pixels_;
|
||||
Cursor cursor_;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_SCREEN_SCREEN */
|
||||
#endif // FTXUI_SCREEN_SCREEN_HPP
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#ifndef FTXUI_CORE_TERMINAL_HPP
|
||||
#define FTXUI_CORE_TERMINAL_HPP
|
||||
#ifndef FTXUI_SCREEN_TERMINAL_HPP
|
||||
#define FTXUI_SCREEN_TERMINAL_HPP
|
||||
|
||||
namespace ftxui {
|
||||
struct Dimensions {
|
||||
@@ -18,11 +18,13 @@ enum Color {
|
||||
TrueColor,
|
||||
};
|
||||
Color ColorSupport();
|
||||
void SetColorSupport(Color color);
|
||||
|
||||
} // namespace Terminal
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_CORE_TERMINAL_HPP */
|
||||
#endif // FTXUI_SCREEN_TERMINAL_HPP
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
|
@@ -27,7 +27,8 @@ template <typename T>
|
||||
class Ref {
|
||||
public:
|
||||
Ref() {}
|
||||
Ref(T t) : owned_(t) {}
|
||||
Ref(const T& t) : owned_(t) {}
|
||||
Ref(T&& t) : owned_(std::forward<T>(t)) {}
|
||||
Ref(T* t) : address_(t) {}
|
||||
T& operator*() { return address_ ? *address_ : owned_; }
|
||||
T& operator()() { return address_ ? *address_ : owned_; }
|
||||
@@ -54,23 +55,6 @@ class StringRef {
|
||||
std::string* address_ = nullptr;
|
||||
};
|
||||
|
||||
/// @brief An adapter. Own or reference a constant string. For convenience, this
|
||||
/// class convert multiple mutable string toward a shared representation.
|
||||
class WideStringRef {
|
||||
public:
|
||||
WideStringRef(std::wstring* ref) : address_(ref) {}
|
||||
WideStringRef(std::wstring ref) : owned_(std::move(ref)) {}
|
||||
WideStringRef(const wchar_t* ref) : WideStringRef(std::wstring(ref)) {}
|
||||
WideStringRef(const char* ref)
|
||||
: WideStringRef(to_wstring(std::string(ref))) {}
|
||||
std::wstring& operator*() { return address_ ? *address_ : owned_; }
|
||||
std::wstring* operator->() { return address_ ? address_ : &owned_; }
|
||||
|
||||
private:
|
||||
std::wstring owned_;
|
||||
std::wstring* address_ = nullptr;
|
||||
};
|
||||
|
||||
/// @brief An adapter. Own or reference a constant string. For convenience, this
|
||||
/// class convert multiple immutable string toward a shared representation.
|
||||
class ConstStringRef {
|
||||
|
293
src/ftxui/component/animation.cpp
Normal file
293
src/ftxui/component/animation.cpp
Normal file
@@ -0,0 +1,293 @@
|
||||
#include <cmath>
|
||||
#include <ratio> // for ratio
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/animation.hpp"
|
||||
|
||||
namespace ftxui::animation {
|
||||
|
||||
namespace easing {
|
||||
|
||||
namespace {
|
||||
constexpr float kPi = 3.14159265358979323846F;
|
||||
constexpr float kPi2 = kPi / 2.F;
|
||||
} // namespace
|
||||
|
||||
// Easing function have been taken out of:
|
||||
// https://github.com/warrenm/AHEasing/blob/master/AHEasing/easing.c
|
||||
//
|
||||
// Corresponding license:
|
||||
// Copyright (c) 2011, Auerhaus Development, LLC
|
||||
//
|
||||
// This program is free software. It comes without any warranty, to
|
||||
// the extent permitted by applicable law. You can redistribute it
|
||||
// and/or modify it under the terms of the Do What The Fuck You Want
|
||||
// To Public License, Version 2, as published by Sam Hocevar. See
|
||||
// http://sam.zoy.org/wtfpl/COPYING for more details.
|
||||
|
||||
// Modeled after the line y = x
|
||||
float Linear(float p) {
|
||||
return p;
|
||||
}
|
||||
|
||||
// Modeled after the parabola y = x^2
|
||||
float QuadraticIn(float p) {
|
||||
return p * p;
|
||||
}
|
||||
|
||||
// Modeled after the parabola y = -x^2 + 2x
|
||||
float QuadraticOut(float p) {
|
||||
return -(p * (p - 2));
|
||||
}
|
||||
|
||||
// Modeled after the piecewise quadratic
|
||||
// y = (1/2)((2x)^2) ; [0, 0.5)
|
||||
// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
|
||||
float QuadraticInOut(float p) {
|
||||
if (p < 0.5F) { // NOLINT
|
||||
return 2 * p * p;
|
||||
} else {
|
||||
return (-2 * p * p) + (4 * p) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the cubic y = x^3
|
||||
float CubicIn(float p) {
|
||||
return p * p * p;
|
||||
}
|
||||
|
||||
// Modeled after the cubic y = (x - 1)^3 + 1
|
||||
float CubicOut(float p) {
|
||||
float f = (p - 1);
|
||||
return f * f * f + 1;
|
||||
}
|
||||
|
||||
// Modeled after the piecewise cubic
|
||||
// y = (1/2)((2x)^3) ; [0, 0.5)
|
||||
// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
|
||||
float CubicInOut(float p) {
|
||||
if (p < 0.5F) { // NOLINT
|
||||
return 4 * p * p * p;
|
||||
} else {
|
||||
float f = ((2 * p) - 2);
|
||||
return 0.5F * f * f * f + 1; // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the quartic x^4
|
||||
float QuarticIn(float p) {
|
||||
return p * p * p * p;
|
||||
}
|
||||
|
||||
// Modeled after the quartic y = 1 - (x - 1)^4
|
||||
float QuarticOut(float p) {
|
||||
float f = (p - 1);
|
||||
return f * f * f * (1 - p) + 1;
|
||||
}
|
||||
|
||||
// Modeled after the piecewise quartic
|
||||
// y = (1/2)((2x)^4) ; [0, 0.5)
|
||||
// y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
|
||||
float QuarticInOut(float p) {
|
||||
if (p < 0.5F) { // NOLINT
|
||||
return 8 * p * p * p * p; // NOLINT
|
||||
} else {
|
||||
float f = (p - 1);
|
||||
return -8 * f * f * f * f + 1; // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the quintic y = x^5
|
||||
float QuinticIn(float p) {
|
||||
return p * p * p * p * p;
|
||||
}
|
||||
|
||||
// Modeled after the quintic y = (x - 1)^5 + 1
|
||||
float QuinticOut(float p) {
|
||||
float f = (p - 1);
|
||||
return f * f * f * f * f + 1;
|
||||
}
|
||||
|
||||
// Modeled after the piecewise quintic
|
||||
// y = (1/2)((2x)^5) ; [0, 0.5)
|
||||
// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
|
||||
float QuinticInOut(float p) {
|
||||
if (p < 0.5F) { // NOLINT
|
||||
return 16 * p * p * p * p * p; // NOLINT
|
||||
} else { // NOLINT
|
||||
float f = ((2 * p) - 2); // NOLINT
|
||||
return 0.5 * f * f * f * f * f + 1; // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after quarter-cycle of sine wave
|
||||
float SineIn(float p) {
|
||||
return std::sin((p - 1) * kPi2) + 1;
|
||||
}
|
||||
|
||||
// Modeled after quarter-cycle of sine wave (different phase)
|
||||
float SineOut(float p) {
|
||||
return std::sin(p * kPi2);
|
||||
}
|
||||
|
||||
// Modeled after half sine wave
|
||||
float SineInOut(float p) {
|
||||
return 0.5F * (1 - std::cos(p * kPi)); // NOLINT
|
||||
}
|
||||
|
||||
// Modeled after shifted quadrant IV of unit circle
|
||||
float CircularIn(float p) {
|
||||
return 1 - std::sqrt(1 - (p * p));
|
||||
}
|
||||
|
||||
// Modeled after shifted quadrant II of unit circle
|
||||
float CircularOut(float p) {
|
||||
return std::sqrt((2 - p) * p);
|
||||
}
|
||||
|
||||
// Modeled after the piecewise circular function
|
||||
// y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5)
|
||||
// y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
|
||||
float CircularInOut(float p) {
|
||||
if (p < 0.5F) { // NOLINT
|
||||
return 0.5F * (1 - std::sqrt(1 - 4 * (p * p))); // NOLINT
|
||||
} else {
|
||||
return 0.5F * (std::sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1); // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the exponential function y = 2^(10(x - 1))
|
||||
float ExponentialIn(float p) {
|
||||
return (p == 0.0) ? p : std::pow(2, 10 * (p - 1)); // NOLINT
|
||||
}
|
||||
|
||||
// Modeled after the exponential function y = -2^(-10x) + 1
|
||||
float ExponentialOut(float p) {
|
||||
return (p == 1.0) ? p : 1 - std::pow(2, -10 * p); // NOLINT
|
||||
}
|
||||
|
||||
// Modeled after the piecewise exponential
|
||||
// y = (1/2)2^(10(2x - 1)) ; [0,0.5)
|
||||
// y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]
|
||||
float ExponentialInOut(float p) {
|
||||
if (p == 0.0 || p == 1.F) {
|
||||
return p;
|
||||
}
|
||||
|
||||
if (p < 0.5F) { // NOLINT
|
||||
return 0.5 * std::pow(2, (20 * p) - 10); // NOLINT
|
||||
} else { // NOLINT
|
||||
return -0.5 * std::pow(2, (-20 * p) + 10) + 1; // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1))
|
||||
float ElasticIn(float p) {
|
||||
return std::sin(13.F * kPi2 * p) * std::pow(2.F, 10.F * (p - 1)); // NOLINT
|
||||
}
|
||||
|
||||
// Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) +
|
||||
// 1
|
||||
float ElasticOut(float p) {
|
||||
// NOLINTNEXTLINE
|
||||
return std::sin(-13.F * kPi2 * (p + 1)) * std::pow(2.F, -10.F * p) + 1;
|
||||
}
|
||||
|
||||
// Modeled after the piecewise exponentially-damped sine wave:
|
||||
// y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
|
||||
// y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
|
||||
float ElasticInOut(float p) {
|
||||
if (p < 0.5F) { // NOLINT
|
||||
return 0.5 * std::sin(13.F * kPi2 * (2 * p)) * // NOLINT
|
||||
std::pow(2, 10 * ((2 * p) - 1)); // NOLINT
|
||||
} else { // NOLINT
|
||||
return 0.5 * (std::sin(-13.F * kPi2 * ((2 * p - 1) + 1)) * // NOLINT
|
||||
std::pow(2, -10 * (2 * p - 1)) + // NOLINT
|
||||
2); // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the overshooting cubic y = x^3-x*sin(x*pi)
|
||||
float BackIn(float p) {
|
||||
return p * p * p - p * std::sin(p * kPi);
|
||||
}
|
||||
|
||||
// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
|
||||
float BackOut(float p) {
|
||||
float f = (1 - p);
|
||||
return 1 - (f * f * f - f * std::sin(f * kPi));
|
||||
}
|
||||
|
||||
// Modeled after the piecewise overshooting cubic function:
|
||||
// y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5)
|
||||
// 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;
|
||||
return 0.5F * (f * f * f - f * std::sin(f * kPi)); // NOLINT
|
||||
} else {
|
||||
float f = (1 - (2 * p - 1)); // NOLINT
|
||||
return 0.5F * (1 - (f * f * f - f * std::sin(f * kPi))) + 0.5; // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
float BounceIn(float p) {
|
||||
return 1 - BounceOut(1 - p);
|
||||
}
|
||||
|
||||
float BounceOut(float p) {
|
||||
if (p < 4 / 11.0) { // NOLINT
|
||||
return (121 * p * p) / 16.0; // NOLINT
|
||||
} else if (p < 8 / 11.0) { // NOLINT
|
||||
return (363 / 40.0 * p * p) - (99 / 10.0 * p) + 17 / 5.0; // NOLINT
|
||||
} else if (p < 9 / 10.0) { // NOLINT
|
||||
return (4356 / 361.0 * p * p) - (35442 / 1805.0 * p) + // NOLINT
|
||||
16061 / 1805.0; // NOLINT
|
||||
} else { // NOLINT
|
||||
return (54 / 5.0 * p * p) - (513 / 25.0 * p) + 268 / 25.0; // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
float BounceInOut(float p) { // NOLINT
|
||||
if (p < 0.5F) { // NOLINT
|
||||
return 0.5F * BounceIn(p * 2); // NOLINT
|
||||
} else { // NOLINT
|
||||
return 0.5F * BounceOut(p * 2 - 1) + 0.5F; // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace easing
|
||||
|
||||
Animator::Animator(float* from,
|
||||
float to,
|
||||
Duration duration,
|
||||
easing::Function easing_function,
|
||||
Duration delay)
|
||||
: value_(from),
|
||||
from_(*from),
|
||||
to_(to),
|
||||
duration_(duration),
|
||||
easing_function_(std::move(easing_function)),
|
||||
current_(-delay) {
|
||||
RequestAnimationFrame();
|
||||
}
|
||||
|
||||
void Animator::OnAnimation(Params& params) {
|
||||
current_ += params.duration();
|
||||
|
||||
if (current_ >= duration_) {
|
||||
*value_ = to_;
|
||||
return;
|
||||
}
|
||||
|
||||
if (current_ <= Duration()) {
|
||||
*value_ = from_;
|
||||
} else {
|
||||
*value_ = from_ +
|
||||
(to_ - from_) * easing_function_(current_ / duration_); // NOLINT
|
||||
}
|
||||
|
||||
RequestAnimationFrame();
|
||||
}
|
||||
|
||||
} // namespace ftxui::animation
|
40
src/ftxui/component/animation_test.cpp
Normal file
40
src/ftxui/component/animation_test.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for SuiteApiResolver, TestPartResult, TestFactoryImpl
|
||||
#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 {
|
||||
|
||||
TEST(AnimationTest, StartAndEnd) {
|
||||
std::vector<animation::easing::Function> functions = {
|
||||
animation::easing::Linear, animation::easing::QuadraticIn,
|
||||
animation::easing::QuadraticOut, animation::easing::QuadraticInOut,
|
||||
animation::easing::CubicIn, animation::easing::CubicOut,
|
||||
animation::easing::CubicInOut, animation::easing::QuarticIn,
|
||||
animation::easing::QuarticOut, animation::easing::QuarticInOut,
|
||||
animation::easing::QuinticIn, animation::easing::QuinticOut,
|
||||
animation::easing::QuinticInOut, animation::easing::SineIn,
|
||||
animation::easing::SineOut, animation::easing::SineInOut,
|
||||
animation::easing::CircularIn, animation::easing::CircularOut,
|
||||
animation::easing::CircularInOut, animation::easing::ExponentialIn,
|
||||
animation::easing::ExponentialOut, animation::easing::ExponentialInOut,
|
||||
animation::easing::ElasticIn, animation::easing::ElasticOut,
|
||||
animation::easing::ElasticInOut, animation::easing::BackIn,
|
||||
animation::easing::BackOut, animation::easing::BackInOut,
|
||||
animation::easing::BounceIn, animation::easing::BounceOut,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
} // 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.
|
@@ -2,65 +2,33 @@
|
||||
#include <memory> // for shared_ptr
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/component.hpp" // for Make, Button
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Return
|
||||
#include "ftxui/component/animation.hpp" // for Animator, Params (ptr only)
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/component.hpp" // for Make, Button
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption, AnimatedColorOption, AnimatedColorsOption, EntryState
|
||||
#include "ftxui/component/event.hpp" // for Event, 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|, Element, nothing, reflect, text, border, inverted
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/util/ref.hpp" // for ConstStringRef, Ref
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Decorator, Element, operator|=, bgcolor, color, reflect, text, bold, border, inverted, nothing
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/color.hpp" // for Color
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringRef
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
class ButtonBase : public ComponentBase {
|
||||
public:
|
||||
ButtonBase(ConstStringRef label,
|
||||
std::function<void()> on_click,
|
||||
Ref<ButtonOption> option)
|
||||
: label_(label), on_click_(on_click), option_(std::move(option)) {}
|
||||
|
||||
// Component implementation:
|
||||
Element Render() override {
|
||||
auto style = Focused() ? inverted : nothing;
|
||||
auto my_border = option_->border ? border : nothing;
|
||||
return text(*label_) | my_border | style | reflect(box_);
|
||||
Element DefaultTransform(EntryState params) { // NOLINT
|
||||
auto element = text(params.label) | border;
|
||||
if (params.active) {
|
||||
element |= bold;
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) {
|
||||
if (!CaptureMouse(event))
|
||||
return false;
|
||||
|
||||
TakeFocus();
|
||||
|
||||
if (event.mouse().button == Mouse::Left &&
|
||||
event.mouse().motion == Mouse::Pressed) {
|
||||
on_click_();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event == Event::Return) {
|
||||
on_click_();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (params.focused) {
|
||||
element |= inverted;
|
||||
}
|
||||
|
||||
bool Focusable() const final { return true; }
|
||||
|
||||
private:
|
||||
ConstStringRef label_;
|
||||
std::function<void()> on_click_;
|
||||
Box box_;
|
||||
Ref<ButtonOption> option_;
|
||||
};
|
||||
return element;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -87,10 +55,135 @@ class ButtonBase : public ComponentBase {
|
||||
/// │Click to quit│
|
||||
/// └─────────────┘
|
||||
/// ```
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
Component Button(ConstStringRef label,
|
||||
std::function<void()> on_click,
|
||||
Ref<ButtonOption> option) {
|
||||
return Make<ButtonBase>(label, std::move(on_click), std::move(option));
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
Impl(ConstStringRef label,
|
||||
std::function<void()> on_click,
|
||||
Ref<ButtonOption> option)
|
||||
: label_(std::move(label)),
|
||||
on_click_(std::move(on_click)),
|
||||
option_(std::move(option)) {}
|
||||
|
||||
// Component implementation:
|
||||
Element Render() override {
|
||||
const bool active = Active();
|
||||
const bool focused = Focused();
|
||||
const bool focused_or_hover = focused || mouse_hover_;
|
||||
|
||||
float target = focused_or_hover ? 1.F : 0.F; // NOLINT
|
||||
if (target != animator_background_.to()) {
|
||||
SetAnimationTarget(target);
|
||||
}
|
||||
|
||||
auto focus_management = focused ? focus : active ? select : nothing;
|
||||
EntryState state = {
|
||||
*label_,
|
||||
false,
|
||||
active,
|
||||
focused_or_hover,
|
||||
};
|
||||
|
||||
auto element =
|
||||
(option_->transform ? option_->transform : DefaultTransform) //
|
||||
(state);
|
||||
return element | AnimatedColorStyle() | focus_management | reflect(box_);
|
||||
}
|
||||
|
||||
Decorator AnimatedColorStyle() {
|
||||
Decorator style = nothing;
|
||||
if (option_->animated_colors.background.enabled) {
|
||||
style = style | bgcolor(Color::Interpolate(
|
||||
animation_foreground_, //
|
||||
option_->animated_colors.background.inactive,
|
||||
option_->animated_colors.background.active));
|
||||
}
|
||||
if (option_->animated_colors.foreground.enabled) {
|
||||
style = style | color(Color::Interpolate(
|
||||
animation_foreground_, //
|
||||
option_->animated_colors.foreground.inactive,
|
||||
option_->animated_colors.foreground.active));
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
void SetAnimationTarget(float target) {
|
||||
if (option_->animated_colors.foreground.enabled) {
|
||||
animator_foreground_ =
|
||||
animation::Animator(&animation_foreground_, target,
|
||||
option_->animated_colors.foreground.duration,
|
||||
option_->animated_colors.foreground.function);
|
||||
}
|
||||
if (option_->animated_colors.background.enabled) {
|
||||
animator_background_ =
|
||||
animation::Animator(&animation_background_, target,
|
||||
option_->animated_colors.background.duration,
|
||||
option_->animated_colors.background.function);
|
||||
}
|
||||
}
|
||||
|
||||
void OnAnimation(animation::Params& p) override {
|
||||
animator_background_.OnAnimation(p);
|
||||
animator_foreground_.OnAnimation(p);
|
||||
}
|
||||
|
||||
void OnClick() {
|
||||
on_click_();
|
||||
animation_background_ = 0.5F; // NOLINT
|
||||
animation_foreground_ = 0.5F; // NOLINT
|
||||
SetAnimationTarget(1.F); // NOLINT
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
if (event.is_mouse()) {
|
||||
return OnMouseEvent(event);
|
||||
}
|
||||
|
||||
if (event == Event::Return) {
|
||||
OnClick();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OnMouseEvent(Event event) {
|
||||
mouse_hover_ =
|
||||
box_.Contain(event.mouse().x, event.mouse().y) && CaptureMouse(event);
|
||||
|
||||
if (!mouse_hover_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.mouse().button == Mouse::Left &&
|
||||
event.mouse().motion == Mouse::Pressed) {
|
||||
TakeFocus();
|
||||
OnClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Focusable() const final { return true; }
|
||||
|
||||
private:
|
||||
ConstStringRef label_;
|
||||
std::function<void()> on_click_;
|
||||
bool mouse_hover_ = false;
|
||||
Box box_;
|
||||
Ref<ButtonOption> option_;
|
||||
float animation_background_ = 0;
|
||||
float animation_foreground_ = 0;
|
||||
animation::Animator animator_background_ =
|
||||
animation::Animator(&animation_background_);
|
||||
animation::Animator animator_foreground_ =
|
||||
animation::Animator(&animation_foreground_);
|
||||
};
|
||||
|
||||
return Make<Impl>(std::move(label), std::move(on_click), std::move(option));
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
197
src/ftxui/component/button_test.cpp
Normal file
197
src/ftxui/component/button_test.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <chrono> // for operator""s, chrono_literals
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
#include <string> // for string
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for Duration, Params
|
||||
#include "ftxui/component/component.hpp" // for Button, Horizontal
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowLeft, Event::ArrowRight
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
|
||||
#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 {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
TEST(ButtonTest, Basic) {
|
||||
int press_count = 0;
|
||||
std::string last_press = "";
|
||||
auto btn1 = Button("btn1", [&] {
|
||||
press_count++;
|
||||
last_press = "btn1";
|
||||
});
|
||||
auto btn2 = Button("btn2", [&] {
|
||||
press_count++;
|
||||
last_press = "btn2";
|
||||
});
|
||||
|
||||
int selected = 0;
|
||||
auto container = Container::Horizontal(
|
||||
{
|
||||
btn1,
|
||||
btn2,
|
||||
},
|
||||
&selected);
|
||||
|
||||
(void)container->Render();
|
||||
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(btn1->Focused());
|
||||
EXPECT_FALSE(btn2->Focused());
|
||||
|
||||
container->OnEvent(Event::ArrowLeft);
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_TRUE(btn1->Focused());
|
||||
EXPECT_FALSE(btn2->Focused());
|
||||
|
||||
container->OnEvent(Event::ArrowRight);
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_FALSE(btn1->Focused());
|
||||
EXPECT_TRUE(btn2->Focused());
|
||||
|
||||
container->OnEvent(Event::ArrowRight);
|
||||
EXPECT_EQ(selected, 1);
|
||||
EXPECT_FALSE(btn1->Focused());
|
||||
EXPECT_TRUE(btn2->Focused());
|
||||
|
||||
EXPECT_EQ(press_count, 0);
|
||||
|
||||
container->OnEvent(Event::Return);
|
||||
EXPECT_EQ(press_count, 1);
|
||||
EXPECT_EQ(last_press, "btn2");
|
||||
|
||||
container->OnEvent(Event::Return);
|
||||
EXPECT_EQ(press_count, 2);
|
||||
EXPECT_EQ(last_press, "btn2");
|
||||
|
||||
container->OnEvent(Event::ArrowLeft);
|
||||
container->OnEvent(Event::Return);
|
||||
EXPECT_EQ(press_count, 3);
|
||||
EXPECT_EQ(last_press, "btn1");
|
||||
|
||||
(void)container->Render();
|
||||
}
|
||||
|
||||
TEST(ButtonTest, Animation) {
|
||||
Terminal::SetColorSupport(Terminal::Color::TrueColor);
|
||||
int press_count = 0;
|
||||
std::string last_press = "";
|
||||
auto option = ButtonOption::Animated();
|
||||
auto btn1 = Button(
|
||||
"btn1",
|
||||
[&] {
|
||||
press_count++;
|
||||
last_press = "btn1";
|
||||
},
|
||||
option);
|
||||
auto btn2 = Button(
|
||||
"btn2",
|
||||
[&] {
|
||||
press_count++;
|
||||
last_press = "btn2";
|
||||
},
|
||||
option);
|
||||
|
||||
int selected = 0;
|
||||
auto container = Container::Horizontal(
|
||||
{
|
||||
btn1,
|
||||
btn2,
|
||||
},
|
||||
&selected);
|
||||
|
||||
{
|
||||
Screen screen(12, 3);
|
||||
Render(screen, container->Render());
|
||||
EXPECT_EQ(screen.ToString(),
|
||||
"\x1B[1m\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m \x1B[22m "
|
||||
" \x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;192;192;192m\x1B[48;2;0;0;"
|
||||
"0m btn1 \x1B[22m btn2 "
|
||||
"\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;192;192;192m\x1B[48;2;0;0;"
|
||||
"0m \x1B[22m \x1B[39m\x1B[49m");
|
||||
}
|
||||
selected = 1;
|
||||
{
|
||||
Screen screen(12, 3);
|
||||
Render(screen, container->Render());
|
||||
EXPECT_EQ(screen.ToString(),
|
||||
"\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m \x1B[1m "
|
||||
"\x1B[22m\x1B[39m\x1B[49m\r\n\x1B[38;2;192;192;192m\x1B[48;2;0;0;"
|
||||
"0m btn1 \x1B[1m btn2 "
|
||||
"\x1B[22m\x1B[39m\x1B[49m\r\n\x1B[38;2;192;192;192m\x1B[48;2;0;0;"
|
||||
"0m \x1B[1m \x1B[22m\x1B[39m\x1B[49m");
|
||||
}
|
||||
animation::Params params(2s);
|
||||
container->OnAnimation(params);
|
||||
{
|
||||
Screen screen(12, 3);
|
||||
Render(screen, container->Render());
|
||||
EXPECT_EQ(
|
||||
screen.ToString(),
|
||||
"\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m "
|
||||
"\x1B[1m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m "
|
||||
"\x1B[22m\x1B[39m\x1B[49m\r\n\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m "
|
||||
"btn1 \x1B[1m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m btn2 "
|
||||
"\x1B[22m\x1B[39m\x1B[49m\r\n\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m "
|
||||
" \x1B[1m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m "
|
||||
"\x1B[22m\x1B[39m\x1B[49m");
|
||||
}
|
||||
EXPECT_EQ(selected, 1);
|
||||
container->OnEvent(MousePressed(3, 1));
|
||||
EXPECT_EQ(selected, 0);
|
||||
{
|
||||
Screen screen(12, 3);
|
||||
Render(screen, container->Render());
|
||||
EXPECT_EQ(
|
||||
screen.ToString(),
|
||||
"\x1B[1m\x1B[38;2;223;223;223m\x1B[48;2;64;64;64m "
|
||||
"\x1B[22m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m "
|
||||
"\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;223;223;223m\x1B[48;2;64;64;64m "
|
||||
"btn1 \x1B[22m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m btn2 "
|
||||
"\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;223;223;223m\x1B[48;2;64;64;64m "
|
||||
" \x1B[22m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m "
|
||||
"\x1B[39m\x1B[49m");
|
||||
}
|
||||
container->OnAnimation(params);
|
||||
{
|
||||
Screen screen(12, 3);
|
||||
Render(screen, container->Render());
|
||||
EXPECT_EQ(
|
||||
screen.ToString(),
|
||||
"\x1B[1m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m "
|
||||
"\x1B[22m\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m "
|
||||
"\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;255;255;255m\x1B[48;2;128;128;"
|
||||
"128m btn1 \x1B[22m\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m btn2 "
|
||||
"\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;255;255;255m\x1B[48;2;128;128;"
|
||||
"128m \x1B[22m\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m "
|
||||
"\x1B[39m\x1B[49m");
|
||||
}
|
||||
}
|
||||
|
||||
} // 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.
|
@@ -11,15 +11,16 @@ namespace ftxui {
|
||||
class CatchEventBase : public ComponentBase {
|
||||
public:
|
||||
// Constructor.
|
||||
CatchEventBase(std::function<bool(Event)> on_event)
|
||||
explicit CatchEventBase(std::function<bool(Event)> on_event)
|
||||
: on_event_(std::move(on_event)) {}
|
||||
|
||||
// Component implementation.
|
||||
bool OnEvent(Event event) override {
|
||||
if (on_event_(event))
|
||||
if (on_event_(event)) {
|
||||
return true;
|
||||
else
|
||||
} else {
|
||||
return ComponentBase::OnEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -39,7 +40,14 @@ class CatchEventBase : public ComponentBase {
|
||||
/// auto renderer = Renderer([] {
|
||||
/// return text("My interface");
|
||||
/// });
|
||||
/// screen.Loop(renderer);
|
||||
/// auto component = CatchEvent(renderer, [&](Event event) {
|
||||
/// if (event == Event::Character('q')) {
|
||||
/// screen.ExitLoopClosure()();
|
||||
/// return true;
|
||||
/// }
|
||||
/// return false;
|
||||
/// });
|
||||
/// screen.Loop(component);
|
||||
/// ```
|
||||
Component CatchEvent(Component child,
|
||||
std::function<bool(Event event)> on_event) {
|
||||
@@ -48,6 +56,33 @@ Component CatchEvent(Component child,
|
||||
return out;
|
||||
}
|
||||
|
||||
/// @brief Decorate a component, using |on_event| to catch events. This function
|
||||
/// must returns true when the event has been handled, false otherwise.
|
||||
/// @param on_event The function drawing the interface.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto screen = ScreenInteractive::TerminalOutput();
|
||||
/// auto renderer = Renderer([] { return text("Hello world"); });
|
||||
/// renderer |= CatchEvent([&](Event event) {
|
||||
/// if (event == Event::Character('q')) {
|
||||
/// screen.ExitLoopClosure()();
|
||||
/// return true;
|
||||
/// }
|
||||
/// return false;
|
||||
/// });
|
||||
/// screen.Loop(renderer);
|
||||
/// ```
|
||||
ComponentDecorator CatchEvent(std::function<bool(Event)> on_event) {
|
||||
return [on_event = std::move(on_event)](Component child) {
|
||||
return CatchEvent(std::move(child), [on_event = on_event](Event event) {
|
||||
return on_event(std::move(event));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
||||
|
@@ -18,40 +18,34 @@ namespace {
|
||||
class CheckboxBase : public ComponentBase {
|
||||
public:
|
||||
CheckboxBase(ConstStringRef label, bool* state, Ref<CheckboxOption> option)
|
||||
: label_(label), state_(state), option_(std::move(option)) {
|
||||
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
|
||||
// Microsoft terminal do not use fonts able to render properly the default
|
||||
// radiobox glyph.
|
||||
if (option_->style_checked == "▣ ")
|
||||
option_->style_checked = "[X]";
|
||||
if (option_->style_unchecked == "☐ ")
|
||||
option_->style_unchecked = "[ ]";
|
||||
#endif
|
||||
}
|
||||
: label_(std::move(label)), state_(state), option_(std::move(option)) {}
|
||||
|
||||
private:
|
||||
// Component implementation.
|
||||
Element Render() override {
|
||||
bool is_focused = Focused();
|
||||
bool is_active = Active();
|
||||
auto style = (is_focused || hovered_) ? option_->style_selected_focused
|
||||
: is_active ? option_->style_selected
|
||||
: option_->style_normal;
|
||||
auto focus_management = is_focused ? focus : is_active ? select : nothing;
|
||||
return hbox({
|
||||
text(*state_ ? option_->style_checked
|
||||
: option_->style_unchecked),
|
||||
text(*label_) | style | focus_management,
|
||||
}) |
|
||||
reflect(box_);
|
||||
auto state = EntryState{
|
||||
*label_,
|
||||
*state_,
|
||||
is_active,
|
||||
is_focused || hovered_,
|
||||
};
|
||||
auto element =
|
||||
(option_->transform ? option_->transform
|
||||
: CheckboxOption::Simple().transform)(state);
|
||||
return element | focus_management | reflect(box_);
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
if (!CaptureMouse(event))
|
||||
if (!CaptureMouse(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.is_mouse())
|
||||
if (event.is_mouse()) {
|
||||
return OnMouseEvent(event);
|
||||
}
|
||||
|
||||
hovered_ = false;
|
||||
if (event == Event::Character(' ') || event == Event::Return) {
|
||||
@@ -66,11 +60,13 @@ class CheckboxBase : public ComponentBase {
|
||||
bool OnMouseEvent(Event event) {
|
||||
hovered_ = box_.Contain(event.mouse().x, event.mouse().y);
|
||||
|
||||
if (!CaptureMouse(event))
|
||||
if (!CaptureMouse(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hovered_)
|
||||
if (!hovered_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.mouse().button == Mouse::Left &&
|
||||
event.mouse().motion == Mouse::Pressed) {
|
||||
@@ -117,7 +113,7 @@ class CheckboxBase : public ComponentBase {
|
||||
Component Checkbox(ConstStringRef label,
|
||||
bool* checked,
|
||||
Ref<CheckboxOption> option) {
|
||||
return Make<CheckboxBase>(label, checked, std::move(option));
|
||||
return Make<CheckboxBase>(std::move(label), checked, std::move(option));
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
@@ -1,10 +1,12 @@
|
||||
#include <string> // for string
|
||||
#include <utility> // for move
|
||||
#include <functional> // for function
|
||||
#include <memory> // for shared_ptr, allocator
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Checkbox, Maybe, Make, Vertical, Collapsible
|
||||
#include "ftxui/component/component_base.hpp" // for Component, ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for CheckboxOption
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringRef
|
||||
#include "ftxui/component/component_options.hpp" // for CheckboxOption, EntryState
|
||||
#include "ftxui/dom/elements.hpp" // for operator|=, text, hbox, Element, bold, inverted
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringRef
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
@@ -28,21 +30,28 @@ namespace ftxui {
|
||||
Component Collapsible(ConstStringRef label, Component child, Ref<bool> show) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
Impl(ConstStringRef label, Component child, Ref<bool> show)
|
||||
: label_(label), show_(std::move(show)) {
|
||||
Impl(ConstStringRef label, Component child, Ref<bool> show) : show_(show) {
|
||||
CheckboxOption opt;
|
||||
opt.style_checked = "▼ ";
|
||||
opt.style_unchecked = "▶ ";
|
||||
opt.transform = [](EntryState s) { // NOLINT
|
||||
auto prefix = text(s.state ? "▼ " : "▶ "); // NOLINT
|
||||
auto t = text(s.label);
|
||||
if (s.active) {
|
||||
t |= bold;
|
||||
}
|
||||
if (s.focused) {
|
||||
t |= inverted;
|
||||
}
|
||||
return hbox({prefix, t});
|
||||
};
|
||||
Add(Container::Vertical({
|
||||
Checkbox(label_, show_.operator->(), opt),
|
||||
Checkbox(std::move(label), show_.operator->(), opt),
|
||||
Maybe(std::move(child), show_.operator->()),
|
||||
}));
|
||||
}
|
||||
ConstStringRef label_;
|
||||
Ref<bool> show_;
|
||||
};
|
||||
|
||||
return Make<Impl>(label, std::move(child), std::move(show));
|
||||
return Make<Impl>(std::move(label), std::move(child), show);
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
55
src/ftxui/component/collapsible_test.cpp
Normal file
55
src/ftxui/component/collapsible_test.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Collapsible, Renderer
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown
|
||||
#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 {
|
||||
|
||||
TEST(CollapsibleTest, Basic) {
|
||||
auto child = Renderer([] { return text("child"); });
|
||||
bool show = false;
|
||||
auto collapsible = Collapsible("parent", child, &show);
|
||||
|
||||
EXPECT_TRUE(collapsible->Focused());
|
||||
EXPECT_FALSE(child->Focused());
|
||||
EXPECT_FALSE(collapsible->OnEvent(Event::ArrowDown));
|
||||
EXPECT_TRUE(collapsible->Focused());
|
||||
EXPECT_FALSE(child->Focused());
|
||||
|
||||
{
|
||||
Screen screen(8, 3);
|
||||
Render(screen, collapsible->Render());
|
||||
EXPECT_EQ(screen.ToString(),
|
||||
"\xE2\x96\xB6 \x1B[1m\x1B[7mparent\x1B[22m\x1B[27m\r\n"
|
||||
" \r\n"
|
||||
" ");
|
||||
}
|
||||
|
||||
collapsible->OnEvent(Event::Return);
|
||||
EXPECT_EQ(show, true);
|
||||
|
||||
{
|
||||
Screen screen(8, 3);
|
||||
Render(screen, collapsible->Render());
|
||||
EXPECT_EQ(screen.ToString(),
|
||||
"\xE2\x96\xBC \x1B[1m\x1B[7mparent\x1B[22m\x1B[27m\r\n"
|
||||
"child \r\n"
|
||||
" ");
|
||||
}
|
||||
|
||||
collapsible->OnEvent(Event::Return);
|
||||
EXPECT_EQ(show, false);
|
||||
}
|
||||
|
||||
} // 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,6 @@
|
||||
#include <stddef.h> // for size_t
|
||||
#include <algorithm> // for find_if
|
||||
#include <cassert> // for assert
|
||||
#include <cstddef> // for size_t
|
||||
#include <iterator> // for begin, end
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector, __alloc_traits<>::value_type
|
||||
@@ -12,6 +12,10 @@
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for text, Element
|
||||
|
||||
namespace ftxui::animation {
|
||||
class Params;
|
||||
} // namespace ftxui::animation
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
@@ -33,7 +37,7 @@ ComponentBase* ComponentBase::Parent() const {
|
||||
/// @brief Access the child at index `i`.
|
||||
/// @ingroup component
|
||||
Component& ComponentBase::ChildAt(size_t i) {
|
||||
assert(i < ChildCount());
|
||||
assert(i < ChildCount()); // NOLINT
|
||||
return children_[i];
|
||||
}
|
||||
|
||||
@@ -57,8 +61,9 @@ void ComponentBase::Add(Component child) {
|
||||
/// @see Parent
|
||||
/// @ingroup component
|
||||
void ComponentBase::Detach() {
|
||||
if (!parent_)
|
||||
if (parent_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto it = std::find_if(std::begin(parent_->children_), //
|
||||
std::end(parent_->children_), //
|
||||
[this](const Component& that) { //
|
||||
@@ -72,8 +77,9 @@ void ComponentBase::Detach() {
|
||||
/// @brief Remove all children.
|
||||
/// @ingroup component
|
||||
void ComponentBase::DetachAllChildren() {
|
||||
while (!children_.empty())
|
||||
while (!children_.empty()) {
|
||||
children_[0]->Detach();
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Draw the component.
|
||||
@@ -81,8 +87,9 @@ void ComponentBase::DetachAllChildren() {
|
||||
/// ftxui::ComponentBase.
|
||||
/// @ingroup component
|
||||
Element ComponentBase::Render() {
|
||||
if (children_.size() == 1)
|
||||
if (children_.size() == 1) {
|
||||
return children_.front()->Render();
|
||||
}
|
||||
|
||||
return text("Not implemented component");
|
||||
}
|
||||
@@ -93,19 +100,35 @@ Element ComponentBase::Render() {
|
||||
/// The default implementation called OnEvent on every child until one return
|
||||
/// true. If none returns true, return false.
|
||||
/// @ingroup component
|
||||
bool ComponentBase::OnEvent(Event event) {
|
||||
for (Component& child : children_) {
|
||||
if (child->OnEvent(event))
|
||||
bool ComponentBase::OnEvent(Event event) { // NOLINT
|
||||
for (Component& child : children_) { // NOLINT
|
||||
if (child->OnEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @brief Called in response to an animation event.
|
||||
/// @param animation_params the parameters of the animation
|
||||
/// The default implementation dispatch the event to every child.
|
||||
/// @ingroup component
|
||||
void ComponentBase::OnAnimation(animation::Params& params) {
|
||||
for (Component& child : children_) {
|
||||
child->OnAnimation(params);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Return the currently Active child.
|
||||
/// @return the currently Active child.
|
||||
/// @ingroup component
|
||||
Component ComponentBase::ActiveChild() {
|
||||
return children_.empty() ? nullptr : children_.front();
|
||||
for (auto& child : children_) {
|
||||
if (child->Focusable()) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// @brief Return true when the component contains focusable elements.
|
||||
@@ -113,9 +136,10 @@ Component ComponentBase::ActiveChild() {
|
||||
/// keyboard.
|
||||
/// @ingroup component
|
||||
bool ComponentBase::Focusable() const {
|
||||
for (const Component& child : children_) {
|
||||
if (child->Focusable())
|
||||
for (const Component& child : children_) { // NOLINT
|
||||
if (child->Focusable()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -123,30 +147,31 @@ bool ComponentBase::Focusable() const {
|
||||
/// @brief Returns if the element if the currently active child of its parent.
|
||||
/// @ingroup component
|
||||
bool ComponentBase::Active() const {
|
||||
return !parent_ || parent_->ActiveChild().get() == this;
|
||||
return parent_ == nullptr || parent_->ActiveChild().get() == this;
|
||||
}
|
||||
|
||||
/// @brief Returns if the elements if focused by the user.
|
||||
/// True when the ComponentBase is focused by the user. An element is Focused
|
||||
/// when it is with all its ancestors the ActiveChild() of their parents.
|
||||
/// when it is with all its ancestors the ActiveChild() of their parents, and it
|
||||
/// Focusable().
|
||||
/// @ingroup component
|
||||
bool ComponentBase::Focused() const {
|
||||
auto current = this;
|
||||
const auto* current = this;
|
||||
while (current && current->Active()) {
|
||||
current = current->parent_;
|
||||
}
|
||||
return !current;
|
||||
return !current && Focusable();
|
||||
}
|
||||
|
||||
/// @brief Make the |child| to be the "active" one.
|
||||
/// @param child the child to become active.
|
||||
/// @ingroup component
|
||||
void ComponentBase::SetActiveChild(ComponentBase*) {}
|
||||
void ComponentBase::SetActiveChild(ComponentBase* /*child*/) {}
|
||||
|
||||
/// @brief Make the |child| to be the "active" one.
|
||||
/// @param child the child to become active.
|
||||
/// @ingroup component
|
||||
void ComponentBase::SetActiveChild(Component child) {
|
||||
void ComponentBase::SetActiveChild(Component child) { // NOLINT
|
||||
SetActiveChild(child.get());
|
||||
}
|
||||
|
||||
@@ -164,9 +189,10 @@ void ComponentBase::TakeFocus() {
|
||||
/// them. It represents a component taking priority over others.
|
||||
/// @param event
|
||||
/// @ingroup component
|
||||
CapturedMouse ComponentBase::CaptureMouse(const Event& event) {
|
||||
if (event.screen_)
|
||||
CapturedMouse ComponentBase::CaptureMouse(const Event& event) { // NOLINT
|
||||
if (event.screen_) {
|
||||
return event.screen_->CaptureMouse();
|
||||
}
|
||||
return std::make_unique<CaptureMouseImpl>();
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "ftxui/component/component.hpp"
|
||||
#include "ftxui/component/terminal_input_parser.hpp"
|
||||
@@ -44,6 +44,63 @@ int GeneratorInt(const char* data, size_t size) {
|
||||
return out;
|
||||
}
|
||||
|
||||
Color GeneratorColor(const char* data, size_t size) {
|
||||
return Color::RGB(GeneratorInt(data, size), GeneratorInt(data, size),
|
||||
GeneratorInt(data, size));
|
||||
}
|
||||
|
||||
AnimatedColorOption GeneratorAnimatedColorOption(const char* data,
|
||||
size_t size) {
|
||||
AnimatedColorOption option;
|
||||
option.enabled = GeneratorBool(data, size);
|
||||
option.inactive = GeneratorColor(data, size);
|
||||
option.active = GeneratorColor(data, size);
|
||||
option.duration = std::chrono::milliseconds(GeneratorInt(data, size));
|
||||
return option;
|
||||
}
|
||||
|
||||
AnimatedColorsOption GeneratorAnimatedColorsOptions(const char* data,
|
||||
size_t size) {
|
||||
AnimatedColorsOption option;
|
||||
option.background = GeneratorAnimatedColorOption(data, size);
|
||||
option.foreground = GeneratorAnimatedColorOption(data, size);
|
||||
return option;
|
||||
}
|
||||
|
||||
ButtonOption GeneratorButtonOption(const char* data, size_t size) {
|
||||
ButtonOption option;
|
||||
option.animated_colors = GeneratorAnimatedColorsOptions(data, size);
|
||||
return option;
|
||||
}
|
||||
|
||||
UnderlineOption GeneratorUnderlineOption(const char* data, size_t size) {
|
||||
UnderlineOption option;
|
||||
option.enabled = GeneratorBool(data, size);
|
||||
option.color_active = GeneratorColor(data, size);
|
||||
option.color_inactive = GeneratorColor(data, size);
|
||||
option.leader_duration = std::chrono::milliseconds(GeneratorInt(data, size));
|
||||
option.follower_duration =
|
||||
std::chrono::milliseconds(GeneratorInt(data, size));
|
||||
option.leader_delay = std::chrono::milliseconds(GeneratorInt(data, size));
|
||||
option.follower_delay = std::chrono::milliseconds(GeneratorInt(data, size));
|
||||
return option;
|
||||
}
|
||||
|
||||
MenuEntryOption GeneratorMenuEntryOption(const char* data, size_t size) {
|
||||
MenuEntryOption option;
|
||||
option.animated_colors = GeneratorAnimatedColorsOptions(data, size);
|
||||
return option;
|
||||
}
|
||||
|
||||
MenuOption GeneratorMenuOption(const char* data, size_t size) {
|
||||
MenuOption option;
|
||||
option.underline = GeneratorUnderlineOption(data, size);
|
||||
option.entries = GeneratorMenuEntryOption(data, size);
|
||||
option.direction =
|
||||
static_cast<MenuOption::Direction>(GeneratorInt(data, size) % 4);
|
||||
return option;
|
||||
}
|
||||
|
||||
bool g_bool;
|
||||
int g_int;
|
||||
std::vector<std::string> g_list;
|
||||
@@ -60,13 +117,15 @@ Component GeneratorComponent(const char*& data, size_t& size, int depth) {
|
||||
value = (value % value_max + value_max) % value_max;
|
||||
switch (value) {
|
||||
case 0:
|
||||
return Button(GeneratorString(data, size), [] {});
|
||||
return Button(
|
||||
GeneratorString(data, size), [] {},
|
||||
GeneratorButtonOption(data, size));
|
||||
case 1:
|
||||
return Checkbox(GeneratorString(data, size), &g_bool);
|
||||
case 2:
|
||||
return Input(GeneratorString(data, size), GeneratorString(data, size));
|
||||
case 3:
|
||||
return Menu(&g_list, &g_int);
|
||||
return Menu(&g_list, &g_int, GeneratorMenuOption(data, size));
|
||||
case 4:
|
||||
return Radiobox(&g_list, &g_int);
|
||||
case 5:
|
||||
@@ -151,16 +210,16 @@ extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) {
|
||||
auto screen =
|
||||
Screen::Create(Dimension::Fixed(width), Dimension::Fixed(height));
|
||||
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
parser.Add(data[i]);
|
||||
}
|
||||
|
||||
Event event;
|
||||
Task event;
|
||||
while (event_receiver->Receive(&event)) {
|
||||
component->OnEvent(event);
|
||||
component->OnEvent(std::get<Event>(event));
|
||||
auto document = component->Render();
|
||||
Render(screen, document);
|
||||
}
|
||||
|
240
src/ftxui/component/component_options.cpp
Normal file
240
src/ftxui/component/component_options.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
#include "ftxui/component/component_options.hpp"
|
||||
|
||||
#include <memory> // for shared_ptr
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for Function, Duration
|
||||
#include "ftxui/dom/elements.hpp" // for operator|=, text, Element, bold, inverted, operator|, dim, hbox, automerge, borderEmpty, borderLight
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
void AnimatedColorOption::Set(Color a_inactive,
|
||||
Color a_active,
|
||||
animation::Duration a_duration,
|
||||
animation::easing::Function a_function) {
|
||||
enabled = true;
|
||||
inactive = a_inactive;
|
||||
active = a_active;
|
||||
duration = a_duration;
|
||||
function = std::move(a_function);
|
||||
}
|
||||
|
||||
void UnderlineOption::SetAnimation(animation::Duration d,
|
||||
animation::easing::Function f) {
|
||||
SetAnimationDuration(d);
|
||||
SetAnimationFunction(std::move(f));
|
||||
}
|
||||
|
||||
void UnderlineOption::SetAnimationDuration(animation::Duration d) {
|
||||
leader_duration = d;
|
||||
follower_duration = d;
|
||||
}
|
||||
|
||||
void UnderlineOption::SetAnimationFunction(animation::easing::Function f) {
|
||||
leader_function = f;
|
||||
follower_function = std::move(f);
|
||||
}
|
||||
|
||||
void UnderlineOption::SetAnimationFunction(
|
||||
animation::easing::Function f_leader,
|
||||
animation::easing::Function f_follower) {
|
||||
leader_function = std::move(f_leader);
|
||||
follower_function = std::move(f_follower);
|
||||
}
|
||||
|
||||
// static
|
||||
MenuOption MenuOption::Horizontal() {
|
||||
MenuOption option;
|
||||
option.direction = Direction::Right;
|
||||
option.entries.transform = [](const EntryState& state) {
|
||||
Element e = text(state.label);
|
||||
if (state.focused) {
|
||||
e |= inverted;
|
||||
}
|
||||
if (state.active) {
|
||||
e |= bold;
|
||||
}
|
||||
if (!state.focused && !state.active) {
|
||||
e |= dim;
|
||||
}
|
||||
return e;
|
||||
};
|
||||
option.elements_infix = [] { return text(" "); };
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
// static
|
||||
MenuOption MenuOption::HorizontalAnimated() {
|
||||
auto option = Horizontal();
|
||||
option.underline.enabled = true;
|
||||
return option;
|
||||
}
|
||||
|
||||
// static
|
||||
MenuOption MenuOption::Vertical() {
|
||||
MenuOption option;
|
||||
option.entries.transform = [](const EntryState& state) {
|
||||
Element e = text((state.active ? "> " : " ") + state.label); // NOLINT
|
||||
if (state.focused) {
|
||||
e |= inverted;
|
||||
}
|
||||
if (state.active) {
|
||||
e |= bold;
|
||||
}
|
||||
if (!state.focused && !state.active) {
|
||||
e |= dim;
|
||||
}
|
||||
return e;
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
// static
|
||||
MenuOption MenuOption::VerticalAnimated() {
|
||||
auto option = MenuOption::Vertical();
|
||||
option.entries.transform = [](const EntryState& state) {
|
||||
Element e = text(state.label);
|
||||
if (state.focused) {
|
||||
e |= inverted;
|
||||
}
|
||||
if (state.active) {
|
||||
e |= bold;
|
||||
}
|
||||
if (!state.focused && !state.active) {
|
||||
e |= dim;
|
||||
}
|
||||
return e;
|
||||
};
|
||||
option.underline.enabled = true;
|
||||
return option;
|
||||
}
|
||||
|
||||
// static
|
||||
MenuOption MenuOption::Toggle() {
|
||||
auto option = MenuOption::Horizontal();
|
||||
option.elements_infix = [] { return text("│") | automerge; };
|
||||
return option;
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, highlighted using [] characters.
|
||||
// static
|
||||
ButtonOption ButtonOption::Ascii() {
|
||||
ButtonOption option;
|
||||
option.transform = [](const EntryState& s) {
|
||||
std::string label = s.focused ? "[" + s.label + "]" //
|
||||
: " " + s.label + " ";
|
||||
return text(label);
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, inverted when focused.
|
||||
// static
|
||||
ButtonOption ButtonOption::Simple() {
|
||||
ButtonOption option;
|
||||
option.transform = [](const EntryState& s) {
|
||||
auto element = text(s.label) | borderLight;
|
||||
if (s.focused) {
|
||||
element |= inverted;
|
||||
}
|
||||
return element;
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, using animated colors.
|
||||
// static
|
||||
ButtonOption ButtonOption::Animated() {
|
||||
return Animated(Color::Black, Color::GrayLight, //
|
||||
Color::GrayDark, Color::White);
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, using animated colors.
|
||||
// static
|
||||
ButtonOption ButtonOption::Animated(Color color) {
|
||||
return ButtonOption::Animated(
|
||||
Color::Interpolate(0.85F, color, Color::Black), // NOLINT
|
||||
Color::Interpolate(0.10F, color, Color::White), // NOLINT
|
||||
Color::Interpolate(0.10F, color, Color::Black), // NOLINT
|
||||
Color::Interpolate(0.85F, color, Color::White)); // NOLINT
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, using animated colors.
|
||||
// static
|
||||
ButtonOption ButtonOption::Animated(Color background, Color foreground) {
|
||||
return ButtonOption::Animated(background, foreground, foreground, background);
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, using animated colors.
|
||||
// static
|
||||
ButtonOption ButtonOption::Animated(Color background,
|
||||
Color foreground,
|
||||
Color background_active,
|
||||
Color foreground_active) {
|
||||
ButtonOption option;
|
||||
option.transform = [](const EntryState& s) {
|
||||
auto element = text(s.label) | borderEmpty;
|
||||
if (s.focused) {
|
||||
element |= bold;
|
||||
}
|
||||
return element;
|
||||
};
|
||||
option.animated_colors.foreground.Set(foreground, foreground_active);
|
||||
option.animated_colors.background.Set(background, background_active);
|
||||
return option;
|
||||
}
|
||||
|
||||
/// @brief Option for standard Checkbox.
|
||||
// static
|
||||
CheckboxOption CheckboxOption::Simple() {
|
||||
auto option = CheckboxOption();
|
||||
option.transform = [](const EntryState& s) {
|
||||
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
|
||||
// Microsoft terminal do not use fonts able to render properly the default
|
||||
// radiobox glyph.
|
||||
auto prefix = text(s.state ? "[X] " : "[ ] "); // NOLINT
|
||||
#else
|
||||
auto prefix = text(s.state ? "▣ " : "☐ "); // NOLINT
|
||||
#endif
|
||||
auto t = text(s.label);
|
||||
if (s.active) {
|
||||
t |= bold;
|
||||
}
|
||||
if (s.focused) {
|
||||
t |= inverted;
|
||||
}
|
||||
return hbox({prefix, t});
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
/// @brief Option for standard Radiobox
|
||||
// static
|
||||
RadioboxOption RadioboxOption::Simple() {
|
||||
auto option = RadioboxOption();
|
||||
option.transform = [](const EntryState& s) {
|
||||
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
|
||||
// Microsoft terminal do not use fonts able to render properly the default
|
||||
// radiobox glyph.
|
||||
auto prefix = text(s.state ? "(*) " : "( ) "); // NOLINT
|
||||
#else
|
||||
auto prefix = text(s.state ? "◉ " : "○ "); // NOLINT
|
||||
#endif
|
||||
auto t = text(s.label);
|
||||
if (s.active) {
|
||||
t |= bold;
|
||||
}
|
||||
if (s.focused) {
|
||||
t |= inverted;
|
||||
}
|
||||
return hbox({prefix, t});
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
} // 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,12 +1,12 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access, allocator, make_shared
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <memory> // for shared_ptr, __shared_ptr_access, allocator, __shared_ptr_access<>::element_type, make_shared
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#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, SuiteApiResolver, TEST, TestFactoryImpl
|
||||
#include "gtest/gtest_pred_impl.h" // for EXPECT_EQ, Test, AssertionResult, TEST, EXPECT_FALSE
|
||||
|
||||
using namespace ftxui;
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
Component Make() {
|
||||
@@ -155,6 +155,24 @@ TEST(ContainerTest, ChildAt) {
|
||||
EXPECT_EQ(parent->ChildAt(0u), child_2);
|
||||
}
|
||||
|
||||
TEST(ComponentTest, NonFocusableAreNotFocused) {
|
||||
class NonFocusable : public ComponentBase {
|
||||
bool Focusable() const override { return false; }
|
||||
};
|
||||
auto root = Make<NonFocusable>();
|
||||
EXPECT_FALSE(root->Focused());
|
||||
EXPECT_EQ(root->ActiveChild(), nullptr);
|
||||
|
||||
auto child = Make<NonFocusable>();
|
||||
root->Add(child);
|
||||
EXPECT_FALSE(root->Focused());
|
||||
EXPECT_FALSE(child->Focused());
|
||||
EXPECT_EQ(root->ActiveChild(), nullptr);
|
||||
EXPECT_EQ(child->ActiveChild(), nullptr);
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// 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,5 +1,5 @@
|
||||
#include <stddef.h> // for size_t
|
||||
#include <algorithm> // for max, min
|
||||
#include <cstddef> // for size_t
|
||||
#include <memory> // for make_shared, __shared_ptr_access, allocator, shared_ptr, allocator_traits<>::value_type
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector, __alloc_traits<>::value_type
|
||||
@@ -17,27 +17,32 @@ class ContainerBase : public ComponentBase {
|
||||
public:
|
||||
ContainerBase(Components children, int* selector)
|
||||
: selector_(selector ? selector : &selected_) {
|
||||
for (Component& child : children)
|
||||
for (Component& child : children) {
|
||||
Add(std::move(child));
|
||||
}
|
||||
}
|
||||
|
||||
// Component override.
|
||||
bool OnEvent(Event event) override {
|
||||
if (event.is_mouse())
|
||||
if (event.is_mouse()) {
|
||||
return OnMouseEvent(event);
|
||||
}
|
||||
|
||||
if (!Focused())
|
||||
if (!Focused()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ActiveChild() && ActiveChild()->OnEvent(event))
|
||||
if (ActiveChild() && ActiveChild()->OnEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return EventHandler(event);
|
||||
}
|
||||
|
||||
Component ActiveChild() override {
|
||||
if (children_.size() == 0)
|
||||
if (children_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return children_[*selector_ % children_.size()];
|
||||
}
|
||||
@@ -45,7 +50,7 @@ class ContainerBase : public ComponentBase {
|
||||
void SetActiveChild(ComponentBase* child) override {
|
||||
for (size_t i = 0; i < children_.size(); ++i) {
|
||||
if (children_[i].get() == child) {
|
||||
*selector_ = i;
|
||||
*selector_ = (int)i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -53,10 +58,10 @@ class ContainerBase : public ComponentBase {
|
||||
|
||||
protected:
|
||||
// Handlers
|
||||
virtual bool EventHandler(Event) { return false; }
|
||||
virtual bool EventHandler(Event /*unused*/) { return false; } // NOLINT
|
||||
|
||||
virtual bool OnMouseEvent(Event event) {
|
||||
return ComponentBase::OnEvent(event);
|
||||
return ComponentBase::OnEvent(std::move(event));
|
||||
}
|
||||
|
||||
int selected_ = 0;
|
||||
@@ -71,11 +76,16 @@ class ContainerBase : public ComponentBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MoveSelectorWrap(int dir) {
|
||||
if (children_.empty()) {
|
||||
return;
|
||||
}
|
||||
for (size_t offset = 1; offset < children_.size(); ++offset) {
|
||||
int i = (*selector_ + offset * dir + children_.size()) % children_.size();
|
||||
size_t i = ((size_t(*selector_ + offset * dir + children_.size())) %
|
||||
children_.size());
|
||||
if (children_[i]->Focusable()) {
|
||||
*selector_ = i;
|
||||
*selector_ = (int)i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -88,60 +98,74 @@ class VerticalContainer : public ContainerBase {
|
||||
|
||||
Element Render() override {
|
||||
Elements elements;
|
||||
for (auto& it : children_)
|
||||
for (auto& it : children_) {
|
||||
elements.push_back(it->Render());
|
||||
if (elements.size() == 0)
|
||||
}
|
||||
if (elements.empty()) {
|
||||
return text("Empty container") | reflect(box_);
|
||||
}
|
||||
return vbox(std::move(elements)) | reflect(box_);
|
||||
}
|
||||
|
||||
bool EventHandler(Event event) override {
|
||||
int old_selected = *selector_;
|
||||
if (event == Event::ArrowUp || event == Event::Character('k'))
|
||||
if (event == Event::ArrowUp || event == Event::Character('k')) {
|
||||
MoveSelector(-1);
|
||||
if (event == Event::ArrowDown || event == Event::Character('j'))
|
||||
}
|
||||
if (event == Event::ArrowDown || event == Event::Character('j')) {
|
||||
MoveSelector(+1);
|
||||
}
|
||||
if (event == Event::PageUp) {
|
||||
for (int i = 0; i < box_.y_max - box_.y_min; ++i)
|
||||
for (int i = 0; i < box_.y_max - box_.y_min; ++i) {
|
||||
MoveSelector(-1);
|
||||
}
|
||||
}
|
||||
if (event == Event::PageDown) {
|
||||
for (int i = 0; i < box_.y_max - box_.y_min; ++i)
|
||||
for (int i = 0; i < box_.y_max - box_.y_min; ++i) {
|
||||
MoveSelector(1);
|
||||
}
|
||||
}
|
||||
if (event == Event::Home) {
|
||||
for (size_t i = 0; i < children_.size(); ++i)
|
||||
for (size_t i = 0; i < children_.size(); ++i) {
|
||||
MoveSelector(-1);
|
||||
}
|
||||
}
|
||||
if (event == Event::End) {
|
||||
for (size_t i = 0; i < children_.size(); ++i)
|
||||
for (size_t i = 0; i < children_.size(); ++i) {
|
||||
MoveSelector(1);
|
||||
}
|
||||
}
|
||||
if (event == Event::Tab && children_.size())
|
||||
if (event == Event::Tab) {
|
||||
MoveSelectorWrap(+1);
|
||||
if (event == Event::TabReverse && children_.size())
|
||||
}
|
||||
if (event == Event::TabReverse) {
|
||||
MoveSelectorWrap(-1);
|
||||
}
|
||||
|
||||
*selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_));
|
||||
return old_selected != *selector_;
|
||||
}
|
||||
|
||||
bool OnMouseEvent(Event event) override {
|
||||
if (ContainerBase::OnMouseEvent(event))
|
||||
if (ContainerBase::OnMouseEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.mouse().button != Mouse::WheelUp &&
|
||||
event.mouse().button != Mouse::WheelDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!box_.Contain(event.mouse().x, event.mouse().y))
|
||||
if (!box_.Contain(event.mouse().x, event.mouse().y)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.mouse().button == Mouse::WheelUp)
|
||||
if (event.mouse().button == Mouse::WheelUp) {
|
||||
MoveSelector(-1);
|
||||
if (event.mouse().button == Mouse::WheelDown)
|
||||
}
|
||||
if (event.mouse().button == Mouse::WheelDown) {
|
||||
MoveSelector(+1);
|
||||
}
|
||||
*selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_));
|
||||
|
||||
return true;
|
||||
@@ -156,23 +180,29 @@ class HorizontalContainer : public ContainerBase {
|
||||
|
||||
Element Render() override {
|
||||
Elements elements;
|
||||
for (auto& it : children_)
|
||||
for (auto& it : children_) {
|
||||
elements.push_back(it->Render());
|
||||
if (elements.size() == 0)
|
||||
}
|
||||
if (elements.empty()) {
|
||||
return text("Empty container");
|
||||
}
|
||||
return hbox(std::move(elements));
|
||||
}
|
||||
|
||||
bool EventHandler(Event event) override {
|
||||
int old_selected = *selector_;
|
||||
if (event == Event::ArrowLeft || event == Event::Character('h'))
|
||||
if (event == Event::ArrowLeft || event == Event::Character('h')) {
|
||||
MoveSelector(-1);
|
||||
if (event == Event::ArrowRight || event == Event::Character('l'))
|
||||
}
|
||||
if (event == Event::ArrowRight || event == Event::Character('l')) {
|
||||
MoveSelector(+1);
|
||||
if (event == Event::Tab && children_.size())
|
||||
}
|
||||
if (event == Event::Tab) {
|
||||
MoveSelectorWrap(+1);
|
||||
if (event == Event::TabReverse && children_.size())
|
||||
}
|
||||
if (event == Event::TabReverse) {
|
||||
MoveSelectorWrap(-1);
|
||||
}
|
||||
|
||||
*selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_));
|
||||
return old_selected != *selector_;
|
||||
@@ -185,11 +215,19 @@ class TabContainer : public ContainerBase {
|
||||
|
||||
Element Render() override {
|
||||
Component active_child = ActiveChild();
|
||||
if (active_child)
|
||||
if (active_child) {
|
||||
return active_child->Render();
|
||||
}
|
||||
return text("Empty container");
|
||||
}
|
||||
|
||||
bool Focusable() const override {
|
||||
if (children_.empty()) {
|
||||
return false;
|
||||
}
|
||||
return children_[*selector_ % children_.size()]->Focusable();
|
||||
}
|
||||
|
||||
bool OnMouseEvent(Event event) override {
|
||||
return ActiveChild()->OnEvent(event);
|
||||
}
|
||||
|
@@ -2,20 +2,21 @@
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Horizontal, Vertical
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#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
|
||||
|
||||
using namespace ftxui;
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
Component Focusable() {
|
||||
return Button("", [] {});
|
||||
}
|
||||
Component NonFocusable() {
|
||||
return Container::Horizontal({});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(ContainerTest, HorizontalEvent) {
|
||||
auto container = Container::Horizontal({});
|
||||
@@ -305,6 +306,36 @@ TEST(ContainerTest, TakeFocus) {
|
||||
EXPECT_FALSE(c23->Active());
|
||||
}
|
||||
|
||||
TEST(ContainerTest, TabFocusable) {
|
||||
int selected = 0;
|
||||
auto c = Container::Tab(
|
||||
{
|
||||
Focusable(),
|
||||
NonFocusable(),
|
||||
Focusable(),
|
||||
NonFocusable(),
|
||||
},
|
||||
&selected);
|
||||
|
||||
selected = 0;
|
||||
EXPECT_TRUE(c->Focusable());
|
||||
EXPECT_TRUE(c->Focused());
|
||||
|
||||
selected = 1;
|
||||
EXPECT_FALSE(c->Focusable());
|
||||
EXPECT_FALSE(c->Focused());
|
||||
|
||||
selected = 2;
|
||||
EXPECT_TRUE(c->Focusable());
|
||||
EXPECT_TRUE(c->Focused());
|
||||
|
||||
selected = 3;
|
||||
EXPECT_FALSE(c->Focusable());
|
||||
EXPECT_FALSE(c->Focused());
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// 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,12 +1,12 @@
|
||||
#include <algorithm> // for max, min
|
||||
#include <memory> // for __shared_ptr_access
|
||||
#include <string> // for string
|
||||
#include <utility> // for move
|
||||
#include <algorithm> // for max, min
|
||||
#include <functional> // for function
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
#include <string> // for string
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Maybe, Checkbox, Make, Radiobox, Vertical, Dropdown
|
||||
#include "ftxui/component/component_base.hpp" // for Component, ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for CheckboxOption
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, border, filler, separator, size, vbox, frame, vscroll_indicator, HEIGHT, LESS_THAN
|
||||
#include "ftxui/component/component_options.hpp" // for CheckboxOption, EntryState
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, border, filler, operator|=, separator, size, text, vbox, frame, vscroll_indicator, hbox, HEIGHT, LESS_THAN, bold, inverted
|
||||
#include "ftxui/util/ref.hpp" // for ConstStringListRef
|
||||
|
||||
namespace ftxui {
|
||||
@@ -15,10 +15,19 @@ Component Dropdown(ConstStringListRef entries, int* selected) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
Impl(ConstStringListRef entries, int* selected)
|
||||
: entries_(std::move(entries)), selected_(selected) {
|
||||
: entries_(entries), selected_(selected) {
|
||||
CheckboxOption option;
|
||||
option.style_checked = "↓│";
|
||||
option.style_unchecked = "→│";
|
||||
option.transform = [](const EntryState& s) {
|
||||
auto prefix = text(s.state ? "↓ " : "→ "); // NOLINT
|
||||
auto t = text(s.label);
|
||||
if (s.active) {
|
||||
t |= bold;
|
||||
}
|
||||
if (s.focused) {
|
||||
t |= inverted;
|
||||
}
|
||||
return hbox({prefix, t});
|
||||
};
|
||||
checkbox_ = Checkbox(&title_, &show_, option),
|
||||
radiobox_ = Radiobox(entries_, selected_);
|
||||
|
||||
@@ -32,11 +41,12 @@ Component Dropdown(ConstStringListRef entries, int* selected) {
|
||||
*selected_ = std::min((int)entries_.size() - 1, std::max(0, *selected_));
|
||||
title_ = entries_[*selected_];
|
||||
if (show_) {
|
||||
const int max_height = 12;
|
||||
return vbox({
|
||||
checkbox_->Render(),
|
||||
separator(),
|
||||
radiobox_->Render() | vscroll_indicator | frame |
|
||||
size(HEIGHT, LESS_THAN, 12),
|
||||
size(HEIGHT, LESS_THAN, max_height),
|
||||
}) |
|
||||
border;
|
||||
}
|
||||
@@ -56,7 +66,7 @@ Component Dropdown(ConstStringListRef entries, int* selected) {
|
||||
Component radiobox_;
|
||||
};
|
||||
|
||||
return Make<Impl>(std::move(entries), selected);
|
||||
return Make<Impl>(entries, selected);
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
@@ -29,7 +29,7 @@ Event Event::Mouse(std::string input, struct Mouse mouse) {
|
||||
Event event;
|
||||
event.input_ = std::move(input);
|
||||
event.type_ = Type::Mouse;
|
||||
event.mouse_ = mouse;
|
||||
event.mouse_ = mouse; // NOLINT
|
||||
return event;
|
||||
}
|
||||
|
||||
@@ -45,44 +45,39 @@ Event Event::CursorReporting(std::string input, int x, int y) {
|
||||
Event event;
|
||||
event.input_ = std::move(input);
|
||||
event.type_ = Type::CursorReporting;
|
||||
event.cursor_.x = x;
|
||||
event.cursor_.y = y;
|
||||
event.cursor_.x = x; // NOLINT
|
||||
event.cursor_.y = y; // NOLINT
|
||||
return event;
|
||||
}
|
||||
|
||||
// --- Arrow ---
|
||||
const Event Event::ArrowLeft = Event::Special("\x1B[D");
|
||||
const Event Event::ArrowRight = Event::Special("\x1B[C");
|
||||
const Event Event::ArrowUp = Event::Special("\x1B[A");
|
||||
const Event Event::ArrowDown = Event::Special("\x1B[B");
|
||||
const Event Event::Backspace = Event::Special({127});
|
||||
const Event Event::Delete = Event::Special("\x1B[3~");
|
||||
const Event Event::Escape = Event::Special("\x1B");
|
||||
#if defined(_WIN32)
|
||||
const Event Event::Return = Event::Special({13});
|
||||
#else
|
||||
const Event Event::Return = Event::Special({10});
|
||||
#endif
|
||||
const Event Event::Tab = Event::Special({9});
|
||||
const Event Event::TabReverse = Event::Special({27, 91, 90});
|
||||
const Event Event::F1 = Event::Special("\x1B[OP");
|
||||
const Event Event::F2 = Event::Special("\x1B[OQ");
|
||||
const Event Event::F3 = Event::Special("\x1B[OR");
|
||||
const Event Event::F4 = Event::Special("\x1B[OS");
|
||||
const Event Event::F5 = Event::Special("\x1B[15~");
|
||||
const Event Event::F6 = Event::Special("\x1B[17~");
|
||||
const Event Event::F7 = Event::Special("\x1B[18~");
|
||||
const Event Event::F8 = Event::Special("\x1B[19~");
|
||||
const Event Event::F9 = Event::Special("\x1B[20~");
|
||||
const Event Event::F10 = Event::Special("\x1B[21~");
|
||||
const Event Event::F11 = Event::Special("\x1B[21~"); // Doesn't exist
|
||||
const Event Event::F12 = Event::Special("\x1B[24~");
|
||||
const Event Event::Home = Event::Special({27, 91, 72});
|
||||
const Event Event::End = Event::Special({27, 91, 70});
|
||||
const Event Event::PageUp = Event::Special({27, 91, 53, 126});
|
||||
const Event Event::PageDown = Event::Special({27, 91, 54, 126});
|
||||
|
||||
Event Event::Custom = Event::Special({0});
|
||||
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::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
|
||||
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::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
|
||||
const Event Event::PageDown = Event::Special({27, 91, 54, 126}); // NOLINT
|
||||
const Event Event::Custom = Event::Special({0}); // NOLINT
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#include <stddef.h> // for size_t
|
||||
#include <algorithm> // for clamp, max, min
|
||||
#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
|
||||
@@ -10,24 +10,25 @@
|
||||
#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/deprecated.hpp" // for Input
|
||||
#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/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/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/string.hpp" // for GlyphPosition, GlyphCount, to_string, CellToGlyphIndex, to_wstring
|
||||
#include "ftxui/util/ref.hpp" // for StringRef, Ref, WideStringRef, ConstStringRef
|
||||
#include "ftxui/screen/util.hpp" // for clamp
|
||||
#include "ftxui/util/ref.hpp" // for StringRef, Ref, ConstStringRef
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string PasswordField(int size) {
|
||||
std::string PasswordField(size_t size) {
|
||||
std::string out;
|
||||
out.reserve(2 * size);
|
||||
while (size--)
|
||||
while (size--) {
|
||||
out += "•";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -37,21 +38,25 @@ class InputBase : public ComponentBase {
|
||||
InputBase(StringRef content,
|
||||
ConstStringRef placeholder,
|
||||
Ref<InputOption> option)
|
||||
: content_(content), placeholder_(placeholder), option_(option) {}
|
||||
: content_(std::move(content)),
|
||||
placeholder_(std::move(placeholder)),
|
||||
option_(std::move(option)) {}
|
||||
|
||||
int cursor_position_internal_ = 0;
|
||||
int& cursor_position() {
|
||||
int& opt = option_->cursor_position();
|
||||
if (opt != -1)
|
||||
if (opt != -1) {
|
||||
return opt;
|
||||
}
|
||||
return cursor_position_internal_;
|
||||
}
|
||||
|
||||
// Component implementation:
|
||||
Element Render() override {
|
||||
std::string password_content;
|
||||
if (option_->password())
|
||||
if (option_->password()) {
|
||||
password_content = PasswordField(content_->size());
|
||||
}
|
||||
std::string& content = option_->password() ? password_content : *content_;
|
||||
|
||||
int size = GlyphCount(content);
|
||||
@@ -64,19 +69,22 @@ class InputBase : public ComponentBase {
|
||||
if (size == 0) {
|
||||
bool hovered = hovered_;
|
||||
Decorator decorator = dim | main_decorator;
|
||||
if (is_focused)
|
||||
decorator = decorator | focus | inverted;
|
||||
if (hovered || is_focused)
|
||||
if (is_focused) {
|
||||
decorator = decorator | focus;
|
||||
}
|
||||
if (hovered || is_focused) {
|
||||
decorator = decorator | inverted;
|
||||
}
|
||||
return text(*placeholder_) | decorator | reflect(box_);
|
||||
}
|
||||
|
||||
// Not focused.
|
||||
if (!is_focused) {
|
||||
if (hovered_)
|
||||
if (hovered_) {
|
||||
return text(content) | main_decorator | inverted | reflect(box_);
|
||||
else
|
||||
} else {
|
||||
return text(content) | main_decorator | reflect(box_);
|
||||
}
|
||||
}
|
||||
|
||||
int index_before_cursor = GlyphPosition(content, cursor_position());
|
||||
@@ -99,17 +107,19 @@ class InputBase : public ComponentBase {
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
cursor_position() =
|
||||
std::max(0, std::min<int>(content_->size(), cursor_position()));
|
||||
std::max(0, std::min<int>((int)content_->size(), cursor_position()));
|
||||
|
||||
if (event.is_mouse())
|
||||
if (event.is_mouse()) {
|
||||
return OnMouseEvent(event);
|
||||
}
|
||||
|
||||
std::string c;
|
||||
|
||||
// Backspace.
|
||||
if (event == Event::Backspace) {
|
||||
if (cursor_position() == 0)
|
||||
if (cursor_position() == 0) {
|
||||
return false;
|
||||
}
|
||||
size_t start = GlyphPosition(*content_, cursor_position() - 1);
|
||||
size_t end = GlyphPosition(*content_, cursor_position());
|
||||
content_->erase(start, end - start);
|
||||
@@ -120,8 +130,9 @@ class InputBase : public ComponentBase {
|
||||
|
||||
// Delete
|
||||
if (event == Event::Delete) {
|
||||
if (cursor_position() == int(content_->size()))
|
||||
if (cursor_position() == int(content_->size())) {
|
||||
return false;
|
||||
}
|
||||
size_t start = GlyphPosition(*content_, cursor_position());
|
||||
size_t end = GlyphPosition(*content_, cursor_position() + 1);
|
||||
content_->erase(start, end - start);
|
||||
@@ -175,8 +186,9 @@ class InputBase : public ComponentBase {
|
||||
bool OnMouseEvent(Event event) {
|
||||
hovered_ =
|
||||
box_.Contain(event.mouse().x, event.mouse().y) && CaptureMouse(event);
|
||||
if (!hovered_)
|
||||
if (!hovered_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.mouse().button != Mouse::Left ||
|
||||
event.mouse().motion != Mouse::Pressed) {
|
||||
@@ -184,25 +196,27 @@ class InputBase : public ComponentBase {
|
||||
}
|
||||
|
||||
TakeFocus();
|
||||
if (content_->size() == 0)
|
||||
if (content_->empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto mapping = CellToGlyphIndex(*content_);
|
||||
int original_glyph = cursor_position();
|
||||
original_glyph = std::clamp(original_glyph, 0, int(mapping.size()));
|
||||
int original_cell = 0;
|
||||
original_glyph = util::clamp(original_glyph, 0, int(mapping.size()));
|
||||
size_t original_cell = 0;
|
||||
for (size_t i = 0; i < mapping.size(); i++) {
|
||||
if (mapping[i] == original_glyph) {
|
||||
original_cell = i;
|
||||
original_cell = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mapping[original_cell] != original_glyph)
|
||||
if (mapping[original_cell] != original_glyph) {
|
||||
original_cell = mapping.size();
|
||||
int target_cell = original_cell + event.mouse().x - cursor_box_.x_min;
|
||||
}
|
||||
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 = std::clamp(target_glyph, 0, GlyphCount(*content_));
|
||||
target_glyph = util::clamp(target_glyph, 0, GlyphCount(*content_));
|
||||
if (cursor_position() != target_glyph) {
|
||||
cursor_position() = target_glyph;
|
||||
option_->on_change();
|
||||
@@ -221,36 +235,6 @@ class InputBase : public ComponentBase {
|
||||
Ref<InputOption> option_;
|
||||
};
|
||||
|
||||
// An input box. The user can type text into it.
|
||||
// For convenience, the std::wstring version of Input simply wrap a
|
||||
// InputBase.
|
||||
class WideInputBase : public InputBase {
|
||||
public:
|
||||
WideInputBase(WideStringRef content,
|
||||
ConstStringRef placeholder,
|
||||
Ref<InputOption> option)
|
||||
: InputBase(&wrapped_content_, std::move(placeholder), std::move(option)),
|
||||
content_(std::move(content)),
|
||||
wrapped_content_(to_string(*content_)) {}
|
||||
|
||||
Element Render() override {
|
||||
wrapped_content_ = to_string(*content_);
|
||||
return InputBase::Render();
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
wrapped_content_ = to_string(*content_);
|
||||
if (InputBase::OnEvent(event)) {
|
||||
*content_ = to_wstring(wrapped_content_);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WideStringRef content_;
|
||||
std::string wrapped_content_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/// @brief An input box for editing text.
|
||||
@@ -278,35 +262,8 @@ class WideInputBase : public InputBase {
|
||||
Component Input(StringRef content,
|
||||
ConstStringRef placeholder,
|
||||
Ref<InputOption> option) {
|
||||
return Make<InputBase>(content, placeholder, std::move(option));
|
||||
}
|
||||
|
||||
/// @brief . An input box for editing text.
|
||||
/// @param content The editable content.
|
||||
/// @param placeholder The text displayed when content is still empty.
|
||||
/// @param option Additional optional parameters.
|
||||
/// @ingroup component
|
||||
/// @see InputBase
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto screen = ScreenInteractive::FitComponent();
|
||||
/// std::string content= "";
|
||||
/// std::string placeholder = "placeholder";
|
||||
/// Component input = Input(&content, &placeholder);
|
||||
/// screen.Loop(input);
|
||||
/// ```
|
||||
///
|
||||
/// ### Output
|
||||
///
|
||||
/// ```bash
|
||||
/// placeholder
|
||||
/// ```
|
||||
Component Input(WideStringRef content,
|
||||
ConstStringRef placeholder,
|
||||
Ref<InputOption> option) {
|
||||
return Make<WideInputBase>(content, placeholder, std::move(option));
|
||||
return Make<InputBase>(std::move(content), std::move(placeholder),
|
||||
std::move(option));
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
@@ -3,7 +3,6 @@
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
#include <string> // for string
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Input
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||
#include "ftxui/component/component_options.hpp" // for InputOption
|
||||
@@ -13,9 +12,9 @@
|
||||
#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 Test, EXPECT_EQ, TEST
|
||||
#include "gtest/gtest_pred_impl.h" // for EXPECT_EQ, Test, TEST
|
||||
|
||||
using namespace ftxui;
|
||||
namespace ftxui {
|
||||
|
||||
TEST(InputTest, Init) {
|
||||
std::string content;
|
||||
@@ -372,6 +371,8 @@ TEST(InputTest, MouseClickComplex) {
|
||||
EXPECT_EQ(option.cursor_position(), 4u);
|
||||
}
|
||||
|
||||
} // 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,38 +1,86 @@
|
||||
#include <functional> // for function
|
||||
#include <memory> // for make_unique, __shared_ptr_access, __shared_ptr_access<>::element_type, shared_ptr
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Make, Maybe
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||
#include "ftxui/component/component.hpp" // for ComponentDecorator, Maybe, Make
|
||||
#include "ftxui/component/component_base.hpp" // for Component, ComponentBase
|
||||
#include "ftxui/component/event.hpp" // for Event
|
||||
#include "ftxui/dom/elements.hpp" // for Element
|
||||
#include "ftxui/dom/node.hpp" // for Node
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
Component Maybe(Component child, const bool* show) {
|
||||
Component Maybe(Component child, std::function<bool()> show) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
Impl(const bool* show) : show_(show) {}
|
||||
explicit Impl(std::function<bool()> show) : show_(std::move(show)) {}
|
||||
|
||||
private:
|
||||
Element Render() override {
|
||||
return *show_ ? ComponentBase::Render() : std::make_unique<Node>();
|
||||
return show_() ? ComponentBase::Render() : std::make_unique<Node>();
|
||||
}
|
||||
bool Focusable() const override {
|
||||
return *show_ && ComponentBase::Focusable();
|
||||
return show_() && ComponentBase::Focusable();
|
||||
}
|
||||
bool OnEvent(Event event) override {
|
||||
return *show_ && ComponentBase::OnEvent(event);
|
||||
return show_() && ComponentBase::OnEvent(event);
|
||||
}
|
||||
|
||||
const bool* show_;
|
||||
std::function<bool()> show_;
|
||||
};
|
||||
|
||||
auto maybe = Make<Impl>(show);
|
||||
auto maybe = Make<Impl>(std::move(show));
|
||||
maybe->Add(std::move(child));
|
||||
return maybe;
|
||||
}
|
||||
|
||||
/// @brief Decorate a component. It is shown only when the |show| function
|
||||
/// returns true.
|
||||
/// @params show a function returning whether the decoratorated component should
|
||||
/// be shown.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto component = Renderer([]{ return "Hello World!"; });
|
||||
/// auto maybe_component = component | Maybe([&]{ return counter == 42; });
|
||||
/// ```
|
||||
ComponentDecorator Maybe(std::function<bool()> show) {
|
||||
return [show = std::move(show)](Component child) mutable {
|
||||
return Maybe(std::move(child), std::move(show));
|
||||
};
|
||||
}
|
||||
|
||||
/// @brief Decorate a component |child|. It is shown only when |show| is true.
|
||||
/// @params child the compoennt to decorate.
|
||||
/// @params show a boolean. |child| is shown when |show| is true.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto component = Renderer([]{ return "Hello World!"; });
|
||||
/// auto maybe_component = Maybe(component, &show);
|
||||
/// ```
|
||||
Component Maybe(Component child, const bool* show) {
|
||||
return Maybe(std::move(child), [show] { return *show; });
|
||||
}
|
||||
|
||||
/// @brief Decorate a component. It is shown only when |show| is true.
|
||||
/// @params show a boolean. |child| is shown when |show| is true.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto component = Renderer([]{ return "Hello World!"; });
|
||||
/// auto maybe_component = component | Maybe(&show);
|
||||
/// ```
|
||||
ComponentDecorator Maybe(const bool* show) {
|
||||
return [show](Component child) { return Maybe(std::move(child), show); };
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
||||
|
@@ -1,91 +1,282 @@
|
||||
#include <algorithm> // for clamp, max
|
||||
#include <algorithm> // for max, reverse
|
||||
#include <chrono> // for milliseconds
|
||||
#include <functional> // for function
|
||||
#include <memory> // for shared_ptr, allocator_traits<>::value_type
|
||||
#include <string> // for operator+, string
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
#include <memory> // for allocator, shared_ptr, allocator_traits<>::value_type, swap
|
||||
#include <string> // for char_traits, operator+, string, basic_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/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/component.hpp" // for Make, Menu, MenuEntry
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp, Event::Return, Event::Tab, Event::TabReverse
|
||||
#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/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
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, reflect, text, nothing, select, vbox, Elements, focus
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/util.hpp"
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, reflect, Decorator, nothing, Elements, bgcolor, color, hbox, separatorHSelector, separatorVSelector, vbox, xflex, yflex, text, bold, focus, inverted, select
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/color.hpp" // for Color
|
||||
#include "ftxui/screen/util.hpp" // for clamp
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef, ConstStringRef
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
|
||||
Element DefaultOptionTransform(const EntryState& state) {
|
||||
std::string label = (state.active ? "> " : " ") + state.label; // NOLINT
|
||||
Element e = text(label);
|
||||
if (state.focused) {
|
||||
e = e | inverted;
|
||||
}
|
||||
if (state.active) {
|
||||
e = e | bold;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
bool IsInverted(MenuOption::Direction direction) {
|
||||
switch (direction) {
|
||||
case MenuOption::Direction::Up:
|
||||
case MenuOption::Direction::Left:
|
||||
return true;
|
||||
case MenuOption::Direction::Down:
|
||||
case MenuOption::Direction::Right:
|
||||
return false;
|
||||
}
|
||||
return false; // NOT_REACHED()
|
||||
}
|
||||
|
||||
bool IsHorizontal(MenuOption::Direction direction) {
|
||||
switch (direction) {
|
||||
case MenuOption::Direction::Left:
|
||||
case MenuOption::Direction::Right:
|
||||
return true;
|
||||
case MenuOption::Direction::Down:
|
||||
case MenuOption::Direction::Up:
|
||||
return false;
|
||||
}
|
||||
return false; // NOT_REACHED()
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// @brief A list of items. The user can navigate through them.
|
||||
/// @ingroup component
|
||||
class MenuBase : public ComponentBase {
|
||||
public:
|
||||
MenuBase(ConstStringListRef entries, int* selected, Ref<MenuOption> option)
|
||||
: entries_(entries), selected_(selected), option_(option) {}
|
||||
: entries_(entries), selected_(selected), option_(std::move(option)) {}
|
||||
|
||||
bool IsHorizontal() { return ftxui::IsHorizontal(option_->direction); }
|
||||
void OnChange() {
|
||||
if (option_->on_change) {
|
||||
option_->on_change();
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnter() {
|
||||
if (option_->on_enter) {
|
||||
option_->on_enter();
|
||||
}
|
||||
}
|
||||
|
||||
void Clamp() {
|
||||
boxes_.resize(size());
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
|
||||
}
|
||||
|
||||
void OnAnimation(animation::Params& params) override {
|
||||
animator_first_.OnAnimation(params);
|
||||
animator_second_.OnAnimation(params);
|
||||
for (auto& animator : animator_background_) {
|
||||
animator.OnAnimation(params);
|
||||
}
|
||||
for (auto& animator : animator_foreground_) {
|
||||
animator.OnAnimation(params);
|
||||
}
|
||||
}
|
||||
|
||||
Element Render() override {
|
||||
Clamp();
|
||||
UpdateAnimationTarget();
|
||||
|
||||
Elements elements;
|
||||
bool is_menu_focused = Focused();
|
||||
if (option_->elements_prefix) {
|
||||
elements.push_back(option_->elements_prefix());
|
||||
}
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
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);
|
||||
|
||||
auto style = is_selected ? (is_focused ? option_->style_selected_focused
|
||||
: option_->style_selected)
|
||||
: (is_focused ? option_->style_focused
|
||||
: option_->style_normal);
|
||||
auto focus_management = !is_selected ? nothing
|
||||
: is_menu_focused ? focus
|
||||
: select;
|
||||
auto icon = is_selected ? "> " : " ";
|
||||
elements.push_back(text(icon + entries_[i]) | style | focus_management |
|
||||
reflect(boxes_[i]));
|
||||
: nothing;
|
||||
EntryState state = {
|
||||
entries_[i],
|
||||
false,
|
||||
is_selected,
|
||||
is_focused,
|
||||
};
|
||||
|
||||
Element element =
|
||||
(option_->entries.transform ? option_->entries.transform
|
||||
: DefaultOptionTransform) //
|
||||
(state);
|
||||
elements.push_back(element | AnimatedColorStyle(i) | reflect(boxes_[i]) |
|
||||
focus_management);
|
||||
}
|
||||
if (option_->elements_postfix) {
|
||||
elements.push_back(option_->elements_postfix());
|
||||
}
|
||||
|
||||
if (IsInverted(option_->direction)) {
|
||||
std::reverse(elements.begin(), elements.end());
|
||||
}
|
||||
|
||||
Element bar =
|
||||
IsHorizontal() ? hbox(std::move(elements)) : vbox(std::move(elements));
|
||||
|
||||
if (!option_->underline.enabled) {
|
||||
return bar | reflect(box_);
|
||||
}
|
||||
|
||||
if (IsHorizontal()) {
|
||||
return vbox({
|
||||
bar | xflex,
|
||||
separatorHSelector(first_, second_, //
|
||||
option_->underline.color_active,
|
||||
option_->underline.color_inactive),
|
||||
}) |
|
||||
reflect(box_);
|
||||
} else {
|
||||
return hbox({
|
||||
separatorVSelector(first_, second_, //
|
||||
option_->underline.color_active,
|
||||
option_->underline.color_inactive),
|
||||
bar | yflex,
|
||||
}) |
|
||||
reflect(box_);
|
||||
}
|
||||
return vbox(std::move(elements)) | reflect(box_);
|
||||
}
|
||||
|
||||
void OnUp() {
|
||||
switch (option_->direction) {
|
||||
case MenuOption::Direction::Up:
|
||||
(*selected_)++;
|
||||
break;
|
||||
case MenuOption::Direction::Down:
|
||||
(*selected_)--;
|
||||
break;
|
||||
case MenuOption::Direction::Left:
|
||||
case MenuOption::Direction::Right:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDown() {
|
||||
switch (option_->direction) {
|
||||
case MenuOption::Direction::Up:
|
||||
(*selected_)--;
|
||||
break;
|
||||
case MenuOption::Direction::Down:
|
||||
(*selected_)++;
|
||||
break;
|
||||
case MenuOption::Direction::Left:
|
||||
case MenuOption::Direction::Right:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnLeft() {
|
||||
switch (option_->direction) {
|
||||
case MenuOption::Direction::Left:
|
||||
(*selected_)++;
|
||||
break;
|
||||
case MenuOption::Direction::Right:
|
||||
(*selected_)--;
|
||||
break;
|
||||
case MenuOption::Direction::Down:
|
||||
case MenuOption::Direction::Up:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnRight() {
|
||||
switch (option_->direction) {
|
||||
case MenuOption::Direction::Left:
|
||||
(*selected_)--;
|
||||
break;
|
||||
case MenuOption::Direction::Right:
|
||||
(*selected_)++;
|
||||
break;
|
||||
case MenuOption::Direction::Down:
|
||||
case MenuOption::Direction::Up:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
bool OnEvent(Event event) override {
|
||||
Clamp();
|
||||
if (!CaptureMouse(event))
|
||||
if (!CaptureMouse(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.is_mouse())
|
||||
if (event.is_mouse()) {
|
||||
return OnMouseEvent(event);
|
||||
}
|
||||
|
||||
if (Focused()) {
|
||||
int old_selected = *selected_;
|
||||
if (event == Event::ArrowUp || event == Event::Character('k'))
|
||||
(*selected_)--;
|
||||
if (event == Event::ArrowDown || event == Event::Character('j'))
|
||||
(*selected_)++;
|
||||
if (event == Event::PageUp)
|
||||
if (event == Event::ArrowUp || event == Event::Character('k')) {
|
||||
OnUp();
|
||||
}
|
||||
if (event == Event::ArrowDown || event == Event::Character('j')) {
|
||||
OnDown();
|
||||
}
|
||||
if (event == Event::ArrowLeft || event == Event::Character('h')) {
|
||||
OnLeft();
|
||||
}
|
||||
if (event == Event::ArrowRight || event == Event::Character('l')) {
|
||||
OnRight();
|
||||
}
|
||||
if (event == Event::PageUp) {
|
||||
(*selected_) -= box_.y_max - box_.y_min;
|
||||
if (event == Event::PageDown)
|
||||
}
|
||||
if (event == Event::PageDown) {
|
||||
(*selected_) += box_.y_max - box_.y_min;
|
||||
if (event == Event::Home)
|
||||
}
|
||||
if (event == Event::Home) {
|
||||
(*selected_) = 0;
|
||||
if (event == Event::End)
|
||||
}
|
||||
if (event == Event::End) {
|
||||
(*selected_) = size() - 1;
|
||||
if (event == Event::Tab && size())
|
||||
}
|
||||
if (event == Event::Tab && size()) {
|
||||
*selected_ = (*selected_ + 1) % size();
|
||||
if (event == Event::TabReverse && size())
|
||||
}
|
||||
if (event == Event::TabReverse && size()) {
|
||||
*selected_ = (*selected_ + size() - 1) % size();
|
||||
}
|
||||
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
|
||||
if (*selected_ != old_selected) {
|
||||
focused_entry() = *selected_;
|
||||
option_->on_change();
|
||||
OnChange();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (event == Event::Return) {
|
||||
option_->on_enter();
|
||||
OnEnter();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -102,11 +293,13 @@ class MenuBase : public ComponentBase {
|
||||
event.mouse().button != Mouse::Left) {
|
||||
return false;
|
||||
}
|
||||
if (!CaptureMouse(event))
|
||||
if (!CaptureMouse(event)) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
if (!boxes_[i].Contain(event.mouse().x, event.mouse().y))
|
||||
if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TakeFocus();
|
||||
focused_entry() = i;
|
||||
@@ -114,7 +307,7 @@ class MenuBase : public ComponentBase {
|
||||
event.mouse().motion == Mouse::Released) {
|
||||
if (*selected_ != i) {
|
||||
*selected_ = i;
|
||||
option_->on_change();
|
||||
OnChange();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -123,39 +316,154 @@ class MenuBase : public ComponentBase {
|
||||
}
|
||||
|
||||
bool OnMouseWheel(Event event) {
|
||||
if (!box_.Contain(event.mouse().x, event.mouse().y))
|
||||
if (!box_.Contain(event.mouse().x, event.mouse().y)) {
|
||||
return false;
|
||||
}
|
||||
int old_selected = *selected_;
|
||||
|
||||
if (event.mouse().button == Mouse::WheelUp)
|
||||
if (event.mouse().button == Mouse::WheelUp) {
|
||||
(*selected_)--;
|
||||
if (event.mouse().button == Mouse::WheelDown)
|
||||
}
|
||||
if (event.mouse().button == Mouse::WheelDown) {
|
||||
(*selected_)++;
|
||||
}
|
||||
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
|
||||
if (*selected_ != old_selected)
|
||||
option_->on_change();
|
||||
if (*selected_ != old_selected) {
|
||||
OnChange();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Clamp() {
|
||||
boxes_.resize(size());
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
|
||||
void UpdateAnimationTarget() {
|
||||
UpdateColorTarget();
|
||||
UpdateUnderlineTarget();
|
||||
}
|
||||
|
||||
void UpdateColorTarget() {
|
||||
if (size() != (int)animation_background_.size()) {
|
||||
animation_background_.resize(size());
|
||||
animation_foreground_.resize(size());
|
||||
animator_background_.clear();
|
||||
animator_foreground_.clear();
|
||||
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
animation_background_[i] = 0.F;
|
||||
animation_foreground_[i] = 0.F;
|
||||
animator_background_.emplace_back(&animation_background_[i], 0.F,
|
||||
std::chrono::milliseconds(0),
|
||||
animation::easing::Linear);
|
||||
animator_foreground_.emplace_back(&animation_foreground_[i], 0.F,
|
||||
std::chrono::milliseconds(0),
|
||||
animation::easing::Linear);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
float target = is_selected ? 1.F : is_focused ? 0.5F : 0.F; // NOLINT
|
||||
if (animator_background_[i].to() != target) {
|
||||
animator_background_[i] = animation::Animator(
|
||||
&animation_background_[i], target,
|
||||
option_->entries.animated_colors.background.duration,
|
||||
option_->entries.animated_colors.background.function);
|
||||
animator_foreground_[i] = animation::Animator(
|
||||
&animation_foreground_[i], target,
|
||||
option_->entries.animated_colors.foreground.duration,
|
||||
option_->entries.animated_colors.foreground.function);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Decorator AnimatedColorStyle(int i) {
|
||||
Decorator style = nothing;
|
||||
if (option_->entries.animated_colors.foreground.enabled) {
|
||||
style = style | color(Color::Interpolate(
|
||||
animation_foreground_[i],
|
||||
option_->entries.animated_colors.foreground.inactive,
|
||||
option_->entries.animated_colors.foreground.active));
|
||||
}
|
||||
|
||||
if (option_->entries.animated_colors.background.enabled) {
|
||||
style = style | bgcolor(Color::Interpolate(
|
||||
animation_background_[i],
|
||||
option_->entries.animated_colors.background.inactive,
|
||||
option_->entries.animated_colors.background.active));
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
void UpdateUnderlineTarget() {
|
||||
if (!option_->underline.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (FirstTarget() == animator_first_.to() &&
|
||||
SecondTarget() == animator_second_.to()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (FirstTarget() >= animator_first_.to()) {
|
||||
animator_first_ = animation::Animator(
|
||||
&first_, FirstTarget(), option_->underline.follower_duration,
|
||||
option_->underline.follower_function,
|
||||
option_->underline.follower_delay);
|
||||
|
||||
animator_second_ = animation::Animator(
|
||||
&second_, SecondTarget(), option_->underline.leader_duration,
|
||||
option_->underline.leader_function, option_->underline.leader_delay);
|
||||
} else {
|
||||
animator_first_ = animation::Animator(
|
||||
&first_, FirstTarget(), option_->underline.leader_duration,
|
||||
option_->underline.leader_function, option_->underline.leader_delay);
|
||||
|
||||
animator_second_ = animation::Animator(
|
||||
&second_, SecondTarget(), option_->underline.follower_duration,
|
||||
option_->underline.follower_function,
|
||||
option_->underline.follower_delay);
|
||||
}
|
||||
}
|
||||
|
||||
bool Focusable() const final { return entries_.size(); }
|
||||
int& focused_entry() { return option_->focused_entry(); }
|
||||
int size() const { return entries_.size(); }
|
||||
int size() const { return int(entries_.size()); }
|
||||
float FirstTarget() {
|
||||
if (boxes_.empty()) {
|
||||
return 0.F;
|
||||
}
|
||||
int value = IsHorizontal() ? boxes_[*selected_].x_min - box_.x_min
|
||||
: boxes_[*selected_].y_min - box_.y_min;
|
||||
return float(value);
|
||||
}
|
||||
float SecondTarget() {
|
||||
if (boxes_.empty()) {
|
||||
return 0.F;
|
||||
}
|
||||
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_ = 0;
|
||||
int* selected_ = nullptr;
|
||||
Ref<MenuOption> option_;
|
||||
|
||||
std::vector<Box> boxes_;
|
||||
Box box_;
|
||||
|
||||
float first_ = 0.F;
|
||||
float second_ = 0.F;
|
||||
animation::Animator animator_first_ = animation::Animator(&first_, 0.F);
|
||||
animation::Animator animator_second_ = animation::Animator(&second_, 0.F);
|
||||
|
||||
std::vector<animation::Animator> animator_background_;
|
||||
std::vector<animation::Animator> animator_foreground_;
|
||||
std::vector<float> animation_background_;
|
||||
std::vector<float> animation_foreground_;
|
||||
};
|
||||
|
||||
/// @brief A list of text. The focused element is selected.
|
||||
@@ -163,7 +471,6 @@ class MenuBase : public ComponentBase {
|
||||
/// @param selected The index of the currently selected element.
|
||||
/// @param option Additional optional parameters.
|
||||
/// @ingroup component
|
||||
/// @see MenuBase
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
@@ -192,6 +499,41 @@ Component Menu(ConstStringListRef entries,
|
||||
return Make<MenuBase>(entries, selected, std::move(option));
|
||||
}
|
||||
|
||||
/// @brief An horizontal list of elements. The user can navigate through them.
|
||||
/// @param entries The list of selectable entries to display.
|
||||
/// @param selected Reference the selected entry.
|
||||
/// @param See also |Menu|.
|
||||
/// @ingroup component
|
||||
Component Toggle(ConstStringListRef entries, int* selected) {
|
||||
return Menu(entries, selected, MenuOption::Toggle());
|
||||
}
|
||||
|
||||
/// @brief A specific menu entry. They can be put into a Container::Vertical to
|
||||
/// form a menu.
|
||||
/// @param label The text drawn representing this element.
|
||||
/// @param option Additional optional parameters.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto screen = ScreenInteractive::TerminalOutput();
|
||||
/// int selected = 0;
|
||||
/// auto menu = Container::Vertical({
|
||||
/// MenuEntry("entry 1"),
|
||||
/// MenuEntry("entry 2"),
|
||||
/// MenuEntry("entry 3"),
|
||||
/// }, &selected);
|
||||
/// screen.Loop(menu);
|
||||
/// ```
|
||||
///
|
||||
/// ### Output
|
||||
///
|
||||
/// ```bash
|
||||
/// > entry 1
|
||||
/// entry 2
|
||||
/// entry 3
|
||||
/// ```
|
||||
Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
@@ -201,24 +543,68 @@ Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
|
||||
private:
|
||||
Element Render() override {
|
||||
bool focused = Focused();
|
||||
auto style =
|
||||
hovered_ ? (focused ? option_->style_selected_focused
|
||||
: option_->style_selected)
|
||||
: (focused ? option_->style_focused : option_->style_normal);
|
||||
UpdateAnimationTarget();
|
||||
|
||||
EntryState state = {
|
||||
*label_,
|
||||
false,
|
||||
hovered_,
|
||||
focused,
|
||||
};
|
||||
|
||||
Element element =
|
||||
(option_->transform ? option_->transform : DefaultOptionTransform) //
|
||||
(state);
|
||||
|
||||
auto focus_management = focused ? select : nothing;
|
||||
auto label = focused ? "> " + (*label_) //
|
||||
: " " + (*label_);
|
||||
return text(label) | style | focus_management | reflect(box_);
|
||||
return element | AnimatedColorStyle() | focus_management | reflect(box_);
|
||||
}
|
||||
|
||||
void UpdateAnimationTarget() {
|
||||
bool focused = Focused();
|
||||
float target = focused ? 1.F : hovered_ ? 0.5F : 0.F; // NOLINT
|
||||
if (target == animator_background_.to()) {
|
||||
return;
|
||||
}
|
||||
animator_background_ =
|
||||
animation::Animator(&animation_background_, target,
|
||||
option_->animated_colors.background.duration,
|
||||
option_->animated_colors.background.function);
|
||||
animator_foreground_ =
|
||||
animation::Animator(&animation_foreground_, target,
|
||||
option_->animated_colors.foreground.duration,
|
||||
option_->animated_colors.foreground.function);
|
||||
}
|
||||
|
||||
Decorator AnimatedColorStyle() {
|
||||
Decorator style = nothing;
|
||||
if (option_->animated_colors.foreground.enabled) {
|
||||
style = style | color(Color::Interpolate(
|
||||
animation_foreground_,
|
||||
option_->animated_colors.foreground.inactive,
|
||||
option_->animated_colors.foreground.active));
|
||||
}
|
||||
|
||||
if (option_->animated_colors.background.enabled) {
|
||||
style = style | bgcolor(Color::Interpolate(
|
||||
animation_background_,
|
||||
option_->animated_colors.background.inactive,
|
||||
option_->animated_colors.background.active));
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
bool Focusable() const override { return true; }
|
||||
bool OnEvent(Event event) override {
|
||||
if (!event.is_mouse())
|
||||
if (!event.is_mouse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hovered_ = box_.Contain(event.mouse().x, event.mouse().y);
|
||||
|
||||
if (!hovered_)
|
||||
if (!hovered_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.mouse().button == Mouse::Left &&
|
||||
event.mouse().motion == Mouse::Released) {
|
||||
@@ -228,10 +614,23 @@ Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void OnAnimation(animation::Params& params) override {
|
||||
animator_background_.OnAnimation(params);
|
||||
animator_foreground_.OnAnimation(params);
|
||||
}
|
||||
|
||||
ConstStringRef label_;
|
||||
Ref<MenuEntryOption> option_;
|
||||
Box box_;
|
||||
bool hovered_ = false;
|
||||
|
||||
float animation_background_ = 0.F;
|
||||
float animation_foreground_ = 0.F;
|
||||
animation::Animator animator_background_ =
|
||||
animation::Animator(&animation_background_, 0.F);
|
||||
animation::Animator animator_foreground_ =
|
||||
animation::Animator(&animation_foreground_, 0.F);
|
||||
};
|
||||
|
||||
return Make<Impl>(std::move(label), std::move(option));
|
||||
|
@@ -1,18 +1,23 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <memory> // for allocator, __shared_ptr_access, shared_ptr
|
||||
#include <string> // for string, basic_string
|
||||
#include <chrono> // for operator""s, chrono_literals
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
#include <string> // for string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/animation.hpp" // for Duration, Params
|
||||
#include "ftxui/component/component.hpp" // for Menu
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for MenuOption
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::Return
|
||||
#include "ftxui/component/component_options.hpp" // for MenuOption, MenuOption::Down, MenuOption::Left, MenuOption::Right, MenuOption::Up
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::Return
|
||||
#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 Test, EXPECT_EQ, TEST
|
||||
#include "gtest/gtest_pred_impl.h" // for EXPECT_EQ, Test, TEST
|
||||
|
||||
using namespace ftxui;
|
||||
namespace ftxui {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
TEST(MenuTest, RemoveEntries) {
|
||||
int focused_entry = 0;
|
||||
@@ -43,6 +48,193 @@ TEST(MenuTest, RemoveEntries) {
|
||||
EXPECT_EQ(focused_entry, 1);
|
||||
}
|
||||
|
||||
TEST(MenuTest, DirectionDown) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
MenuOption option;
|
||||
auto menu = Menu(&entries, &selected, &option);
|
||||
|
||||
selected = 0;
|
||||
option.direction = MenuOption::Down;
|
||||
Screen screen(4, 3);
|
||||
Render(screen, menu->Render());
|
||||
EXPECT_EQ(screen.ToString(),
|
||||
"\x1B[1m\x1B[7m> 1 \x1B[22m\x1B[27m\r\n"
|
||||
" 2 \r\n"
|
||||
" 3 ");
|
||||
|
||||
menu->OnEvent(Event::ArrowUp);
|
||||
EXPECT_EQ(selected, 0);
|
||||
menu->OnEvent(Event::ArrowDown);
|
||||
EXPECT_EQ(selected, 1);
|
||||
menu->OnEvent(Event::ArrowDown);
|
||||
EXPECT_EQ(selected, 2);
|
||||
menu->OnEvent(Event::ArrowDown);
|
||||
EXPECT_EQ(selected, 2);
|
||||
menu->OnEvent(Event::ArrowLeft);
|
||||
EXPECT_EQ(selected, 2);
|
||||
menu->OnEvent(Event::ArrowRight);
|
||||
EXPECT_EQ(selected, 2);
|
||||
}
|
||||
|
||||
TEST(MenuTest, DirectionsUp) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
MenuOption option;
|
||||
auto menu = Menu(&entries, &selected, &option);
|
||||
option.direction = MenuOption::Up;
|
||||
Screen screen(4, 3);
|
||||
Render(screen, menu->Render());
|
||||
EXPECT_EQ(screen.ToString(),
|
||||
" 3 \r\n"
|
||||
" 2 \r\n"
|
||||
"\x1B[1m\x1B[7m> 1 \x1B[22m\x1B[27m");
|
||||
menu->OnEvent(Event::ArrowDown);
|
||||
EXPECT_EQ(selected, 0);
|
||||
menu->OnEvent(Event::ArrowUp);
|
||||
EXPECT_EQ(selected, 1);
|
||||
menu->OnEvent(Event::ArrowUp);
|
||||
EXPECT_EQ(selected, 2);
|
||||
menu->OnEvent(Event::ArrowUp);
|
||||
EXPECT_EQ(selected, 2);
|
||||
menu->OnEvent(Event::ArrowLeft);
|
||||
EXPECT_EQ(selected, 2);
|
||||
menu->OnEvent(Event::ArrowRight);
|
||||
EXPECT_EQ(selected, 2);
|
||||
}
|
||||
|
||||
TEST(MenuTest, DirectionsRight) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
MenuOption option;
|
||||
auto menu = Menu(&entries, &selected, &option);
|
||||
option.direction = MenuOption::Right;
|
||||
Screen screen(10, 1);
|
||||
Render(screen, menu->Render());
|
||||
EXPECT_EQ(screen.ToString(),
|
||||
"\x1B[1m\x1B[7m> 1\x1B[22m\x1B[27m"
|
||||
" 2"
|
||||
" 3 ");
|
||||
menu->OnEvent(Event::ArrowLeft);
|
||||
EXPECT_EQ(selected, 0);
|
||||
menu->OnEvent(Event::ArrowRight);
|
||||
EXPECT_EQ(selected, 1);
|
||||
menu->OnEvent(Event::ArrowRight);
|
||||
EXPECT_EQ(selected, 2);
|
||||
menu->OnEvent(Event::ArrowRight);
|
||||
EXPECT_EQ(selected, 2);
|
||||
menu->OnEvent(Event::ArrowUp);
|
||||
EXPECT_EQ(selected, 2);
|
||||
menu->OnEvent(Event::ArrowDown);
|
||||
EXPECT_EQ(selected, 2);
|
||||
}
|
||||
|
||||
TEST(MenuTest, DirectionsLeft) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
MenuOption option;
|
||||
auto menu = Menu(&entries, &selected, &option);
|
||||
option.direction = MenuOption::Left;
|
||||
Screen screen(10, 1);
|
||||
Render(screen, menu->Render());
|
||||
EXPECT_EQ(screen.ToString(),
|
||||
" 3"
|
||||
" 2"
|
||||
"\x1B[1m\x1B[7m> 1\x1B[22m\x1B[27m ");
|
||||
menu->OnEvent(Event::ArrowRight);
|
||||
EXPECT_EQ(selected, 0);
|
||||
menu->OnEvent(Event::ArrowLeft);
|
||||
EXPECT_EQ(selected, 1);
|
||||
menu->OnEvent(Event::ArrowLeft);
|
||||
EXPECT_EQ(selected, 2);
|
||||
menu->OnEvent(Event::ArrowLeft);
|
||||
EXPECT_EQ(selected, 2);
|
||||
menu->OnEvent(Event::ArrowUp);
|
||||
EXPECT_EQ(selected, 2);
|
||||
menu->OnEvent(Event::ArrowDown);
|
||||
EXPECT_EQ(selected, 2);
|
||||
}
|
||||
|
||||
TEST(MenuTest, AnimationsHorizontal) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
auto option = MenuOption::HorizontalAnimated();
|
||||
auto menu = Menu(&entries, &selected, &option);
|
||||
{
|
||||
Screen screen(4, 3);
|
||||
Render(screen, menu->Render());
|
||||
EXPECT_EQ(
|
||||
screen.ToString(),
|
||||
"\x1B[1m\x1B[7m1\x1B[22m\x1B[27m \x1B[2m2\x1B[22m "
|
||||
"\r\n\x1B[97m\x1B[49m\xE2\x94\x80\x1B[90m\x1B["
|
||||
"49m\xE2\x95\xB6\xE2\x94\x80\xE2\x94\x80\x1B[39m\x1B[49m\r\n ");
|
||||
}
|
||||
selected = 1;
|
||||
{
|
||||
Screen screen(4, 3);
|
||||
Render(screen, menu->Render());
|
||||
EXPECT_EQ(
|
||||
screen.ToString(),
|
||||
"\x1B[7m1\x1B[27m \x1B[1m2\x1B[22m "
|
||||
"\r\n\x1B[97m\x1B[49m\xE2\x94\x80\x1B[90m\x1B["
|
||||
"49m\xE2\x95\xB6\xE2\x94\x80\xE2\x94\x80\x1B[39m\x1B[49m\r\n ");
|
||||
}
|
||||
animation::Params params(2s);
|
||||
menu->OnAnimation(params);
|
||||
{
|
||||
Screen screen(4, 3);
|
||||
Render(screen, menu->Render());
|
||||
EXPECT_EQ(
|
||||
screen.ToString(),
|
||||
"\x1B[7m1\x1B[27m \x1B[1m2\x1B[22m "
|
||||
"\r\n\x1B[90m\x1B[49m\xE2\x94\x80\xE2\x95\xB4\x1B[97m\x1B["
|
||||
"49m\xE2\x94\x80\x1B[90m\x1B[49m\xE2\x95\xB6\x1B[39m\x1B[49m\r\n ");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MenuTest, AnimationsVertical) {
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
auto option = MenuOption::VerticalAnimated();
|
||||
auto menu = Menu(&entries, &selected, &option);
|
||||
{
|
||||
Screen screen(10, 3);
|
||||
Render(screen, menu->Render());
|
||||
EXPECT_EQ(
|
||||
screen.ToString(),
|
||||
"\x1B[90m\x1B[49m\xE2\x94\x82\x1B[1m\x1B[7m\x1B[39m\x1B[49m1\x1B["
|
||||
"22m\x1B[27m "
|
||||
"\r\n\x1B[97m\x1B[49m\xE2\x95\xB7\x1B[2m\x1B[39m\x1B[49m2\x1B[22m "
|
||||
" \r\n\x1B[97m\x1B[49m\xE2\x94\x82\x1B[2m\x1B[39m\x1B[49m3\x1B[22m "
|
||||
" ");
|
||||
}
|
||||
selected = 1;
|
||||
{
|
||||
Screen screen(10, 3);
|
||||
Render(screen, menu->Render());
|
||||
EXPECT_EQ(
|
||||
screen.ToString(),
|
||||
"\x1B[90m\x1B[49m\xE2\x94\x82\x1B[7m\x1B[39m\x1B[49m1\x1B[27m "
|
||||
"\r\n\x1B[97m\x1B[49m\xE2\x95\xB7\x1B[1m\x1B[39m\x1B[49m2\x1B[22m "
|
||||
" \r\n\x1B[97m\x1B[49m\xE2\x94\x82\x1B[2m\x1B[39m\x1B[49m3\x1B[22m "
|
||||
" ");
|
||||
}
|
||||
animation::Params params(2s);
|
||||
menu->OnAnimation(params);
|
||||
{
|
||||
Screen screen(10, 3);
|
||||
Render(screen, menu->Render());
|
||||
EXPECT_EQ(
|
||||
screen.ToString(),
|
||||
"\x1B[97m\x1B[49m\xE2\x95\xB5\x1B[7m\x1B[39m\x1B[49m1\x1B[27m "
|
||||
"\r\n\x1B[90m\x1B[49m\xE2\x94\x82\x1B[1m\x1B[39m\x1B[49m2\x1B[22m "
|
||||
" \r\n\x1B[97m\x1B[49m\xE2\x95\xB7\x1B[2m\x1B[39m\x1B[49m3\x1B[22m "
|
||||
" ");
|
||||
}
|
||||
}
|
||||
|
||||
} // 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,21 +1,20 @@
|
||||
#include <algorithm> // for clamp, max
|
||||
#include <algorithm> // for max
|
||||
#include <functional> // for function
|
||||
#include <memory> // for shared_ptr, allocator_traits<>::value_type
|
||||
#include <string> // for string
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/component.hpp" // for Make, Radiobox
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for RadioboxOption
|
||||
#include "ftxui/component/component_options.hpp" // for RadioboxOption, EntryState
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp, Event::Return, Event::Tab, Event::TabReverse
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::WheelDown, Mouse::WheelUp, Mouse::Left, Mouse::Released
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, reflect, text, Element, hbox, vbox, Elements, focus, nothing, select
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/util.hpp"
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, reflect, Element, vbox, Elements, focus, nothing, select
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/util.hpp" // for clamp
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
@@ -29,14 +28,6 @@ class RadioboxBase : public ComponentBase {
|
||||
int* selected,
|
||||
Ref<RadioboxOption> option)
|
||||
: entries_(entries), selected_(selected), option_(std::move(option)) {
|
||||
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
|
||||
// Microsoft terminal do not use fonts able to render properly the default
|
||||
// radiobox glyph.
|
||||
if (option_->style_checked == "◉ ")
|
||||
option_->style_checked = "(*)";
|
||||
if (option_->style_unchecked == "○ ")
|
||||
option_->style_unchecked = "( )";
|
||||
#endif
|
||||
hovered_ = *selected_;
|
||||
}
|
||||
|
||||
@@ -48,49 +39,61 @@ class RadioboxBase : public ComponentBase {
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
bool is_focused = (focused_entry() == i) && is_menu_focused;
|
||||
bool is_selected = (hovered_ == i);
|
||||
|
||||
auto style = is_selected ? (is_focused ? option_->style_selected_focused
|
||||
: option_->style_selected)
|
||||
: (is_focused ? option_->style_focused
|
||||
: option_->style_normal);
|
||||
auto focus_management = !is_selected ? nothing
|
||||
: is_menu_focused ? focus
|
||||
: select;
|
||||
auto state = EntryState{
|
||||
entries_[i],
|
||||
*selected_ == i,
|
||||
is_selected,
|
||||
is_focused,
|
||||
};
|
||||
auto element =
|
||||
(option_->transform ? option_->transform
|
||||
: RadioboxOption::Simple().transform)(state);
|
||||
|
||||
const std::string& symbol =
|
||||
*selected_ == i ? option_->style_checked : option_->style_unchecked;
|
||||
elements.push_back(hbox(text(symbol), text(entries_[i]) | style) |
|
||||
focus_management | reflect(boxes_[i]));
|
||||
elements.push_back(element | focus_management | reflect(boxes_[i]));
|
||||
}
|
||||
return vbox(std::move(elements)) | reflect(box_);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
bool OnEvent(Event event) override {
|
||||
Clamp();
|
||||
if (!CaptureMouse(event))
|
||||
if (!CaptureMouse(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.is_mouse())
|
||||
if (event.is_mouse()) {
|
||||
return OnMouseEvent(event);
|
||||
}
|
||||
|
||||
if (Focused()) {
|
||||
int old_hovered = hovered_;
|
||||
if (event == Event::ArrowUp || event == Event::Character('k'))
|
||||
if (event == Event::ArrowUp || event == Event::Character('k')) {
|
||||
(hovered_)--;
|
||||
if (event == Event::ArrowDown || event == Event::Character('j'))
|
||||
}
|
||||
if (event == Event::ArrowDown || event == Event::Character('j')) {
|
||||
(hovered_)++;
|
||||
if (event == Event::PageUp)
|
||||
}
|
||||
if (event == Event::PageUp) {
|
||||
(hovered_) -= box_.y_max - box_.y_min;
|
||||
if (event == Event::PageDown)
|
||||
}
|
||||
if (event == Event::PageDown) {
|
||||
(hovered_) += box_.y_max - box_.y_min;
|
||||
if (event == Event::Home)
|
||||
}
|
||||
if (event == Event::Home) {
|
||||
(hovered_) = 0;
|
||||
if (event == Event::End)
|
||||
}
|
||||
if (event == Event::End) {
|
||||
(hovered_) = size() - 1;
|
||||
if (event == Event::Tab && size())
|
||||
}
|
||||
if (event == Event::Tab && size()) {
|
||||
hovered_ = (hovered_ + 1) % size();
|
||||
if (event == Event::TabReverse && size())
|
||||
}
|
||||
if (event == Event::TabReverse && size()) {
|
||||
hovered_ = (hovered_ + size() - 1) % size();
|
||||
}
|
||||
|
||||
hovered_ = util::clamp(hovered_, 0, size() - 1);
|
||||
|
||||
@@ -117,8 +120,9 @@ class RadioboxBase : public ComponentBase {
|
||||
}
|
||||
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
if (!boxes_[i].Contain(event.mouse().x, event.mouse().y))
|
||||
if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TakeFocus();
|
||||
focused_entry() = i;
|
||||
@@ -136,20 +140,24 @@ class RadioboxBase : public ComponentBase {
|
||||
}
|
||||
|
||||
bool OnMouseWheel(Event event) {
|
||||
if (!box_.Contain(event.mouse().x, event.mouse().y))
|
||||
if (!box_.Contain(event.mouse().x, event.mouse().y)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int old_hovered = hovered_;
|
||||
|
||||
if (event.mouse().button == Mouse::WheelUp)
|
||||
if (event.mouse().button == Mouse::WheelUp) {
|
||||
(hovered_)--;
|
||||
if (event.mouse().button == Mouse::WheelDown)
|
||||
}
|
||||
if (event.mouse().button == Mouse::WheelDown) {
|
||||
(hovered_)++;
|
||||
}
|
||||
|
||||
hovered_ = util::clamp(hovered_, 0, size() - 1);
|
||||
|
||||
if (hovered_ != old_hovered)
|
||||
if (hovered_ != old_hovered) {
|
||||
option_->on_change();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -163,7 +171,7 @@ class RadioboxBase : public ComponentBase {
|
||||
|
||||
bool Focusable() const final { return entries_.size(); }
|
||||
int& focused_entry() { return option_->focused_entry(); }
|
||||
int size() const { return entries_.size(); }
|
||||
int size() const { return int(entries_.size()); }
|
||||
|
||||
ConstStringListRef entries_;
|
||||
int* selected_;
|
||||
|
@@ -1,10 +1,9 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
#include <string> // for string, basic_string
|
||||
#include <string> // for string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||
#include "ftxui/component/component.hpp" // for Radiobox
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for RadioboxOption
|
||||
@@ -12,7 +11,7 @@
|
||||
#include "ftxui/util/ref.hpp" // for Ref
|
||||
#include "gtest/gtest_pred_impl.h" // for EXPECT_EQ, Test, TEST
|
||||
|
||||
using namespace ftxui;
|
||||
namespace ftxui {
|
||||
|
||||
TEST(RadioboxTest, Navigation) {
|
||||
int selected = 0;
|
||||
@@ -145,6 +144,8 @@ TEST(RadioboxTest, RemoveEntries) {
|
||||
EXPECT_EQ(focused_entry, 1);
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// 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.
|
||||
|
@@ -6,7 +6,7 @@
|
||||
#include "ftxui/component/receiver.hpp"
|
||||
#include "gtest/gtest_pred_impl.h" // for AssertionResult, Test, EXPECT_EQ
|
||||
|
||||
using namespace ftxui;
|
||||
namespace ftxui {
|
||||
|
||||
TEST(Receiver, Basic) {
|
||||
auto receiver = MakeReceiver<char>();
|
||||
@@ -75,6 +75,8 @@ TEST(Receiver, BasicWithThread) {
|
||||
t23.join();
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// 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.
|
||||
|
@@ -28,7 +28,8 @@ namespace ftxui {
|
||||
Component Renderer(std::function<Element()> render) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
Impl(std::function<Element()> render) : render_(std::move(render)) {}
|
||||
explicit Impl(std::function<Element()> render)
|
||||
: render_(std::move(render)) {}
|
||||
Element Render() override { return render_(); }
|
||||
std::function<Element()> render_;
|
||||
};
|
||||
@@ -82,15 +83,17 @@ Component Renderer(Component child, std::function<Element()> render) {
|
||||
Component Renderer(std::function<Element(bool)> render) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
Impl(std::function<Element(bool)> render) : render_(std::move(render)) {}
|
||||
explicit Impl(std::function<Element(bool)> render)
|
||||
: render_(std::move(render)) {}
|
||||
|
||||
private:
|
||||
Element Render() override { return render_(Focused()) | reflect(box_); }
|
||||
bool Focusable() const override { return true; }
|
||||
bool OnEvent(Event event) override {
|
||||
if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) {
|
||||
if (!CaptureMouse(event))
|
||||
if (!CaptureMouse(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TakeFocus();
|
||||
}
|
||||
@@ -104,6 +107,28 @@ Component Renderer(std::function<Element(bool)> render) {
|
||||
return Make<Impl>(std::move(render));
|
||||
}
|
||||
|
||||
/// @brief Decorate a component, by decorating what it renders.
|
||||
/// @param decorator the function modifying the element it renders.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto screen = ScreenInteractive::TerminalOutput();
|
||||
/// auto renderer =
|
||||
// Renderer([] { return text("Hello");)
|
||||
/// | Renderer(bold)
|
||||
/// | Renderer(inverted);
|
||||
/// screen.Loop(renderer);
|
||||
/// ```
|
||||
ComponentDecorator Renderer(ElementDecorator decorator) { // NOLINT
|
||||
return [decorator](Component component) { // NOLINT
|
||||
return Renderer(component, [component, decorator] {
|
||||
return component->Render() | decorator;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
||||
|
@@ -15,16 +15,19 @@ namespace {
|
||||
class ResizableSplitLeftBase : public ComponentBase {
|
||||
public:
|
||||
ResizableSplitLeftBase(Component main, Component child, int* main_size)
|
||||
: main_(main), child_(child), main_size_(main_size) {
|
||||
: main_(std::move(main)),
|
||||
child_(std::move(child)),
|
||||
main_size_(main_size) {
|
||||
Add(Container::Horizontal({
|
||||
main,
|
||||
child,
|
||||
main_,
|
||||
child_,
|
||||
}));
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) final {
|
||||
if (event.is_mouse())
|
||||
if (event.is_mouse()) {
|
||||
return OnMouseEvent(std::move(event));
|
||||
}
|
||||
return ComponentBase::OnEvent(std::move(event));
|
||||
}
|
||||
|
||||
@@ -71,16 +74,19 @@ class ResizableSplitLeftBase : public ComponentBase {
|
||||
class ResizableSplitRightBase : public ComponentBase {
|
||||
public:
|
||||
ResizableSplitRightBase(Component main, Component child, int* main_size)
|
||||
: main_(main), child_(child), main_size_(main_size) {
|
||||
: main_(std::move(main)),
|
||||
child_(std::move(child)),
|
||||
main_size_(main_size) {
|
||||
Add(Container::Horizontal({
|
||||
child,
|
||||
main,
|
||||
child_,
|
||||
main_,
|
||||
}));
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) final {
|
||||
if (event.is_mouse())
|
||||
if (event.is_mouse()) {
|
||||
return OnMouseEvent(std::move(event));
|
||||
}
|
||||
return ComponentBase::OnEvent(std::move(event));
|
||||
}
|
||||
|
||||
@@ -127,16 +133,19 @@ class ResizableSplitRightBase : public ComponentBase {
|
||||
class ResizableSplitTopBase : public ComponentBase {
|
||||
public:
|
||||
ResizableSplitTopBase(Component main, Component child, int* main_size)
|
||||
: main_(main), child_(child), main_size_(main_size) {
|
||||
: main_(std::move(main)),
|
||||
child_(std::move(child)),
|
||||
main_size_(main_size) {
|
||||
Add(Container::Vertical({
|
||||
main,
|
||||
child,
|
||||
main_,
|
||||
child_,
|
||||
}));
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) final {
|
||||
if (event.is_mouse())
|
||||
if (event.is_mouse()) {
|
||||
return OnMouseEvent(std::move(event));
|
||||
}
|
||||
return ComponentBase::OnEvent(std::move(event));
|
||||
}
|
||||
|
||||
@@ -183,16 +192,19 @@ class ResizableSplitTopBase : public ComponentBase {
|
||||
class ResizableSplitBottomBase : public ComponentBase {
|
||||
public:
|
||||
ResizableSplitBottomBase(Component main, Component child, int* main_size)
|
||||
: main_(main), child_(child), main_size_(main_size) {
|
||||
: main_(std::move(main)),
|
||||
child_(std::move(child)),
|
||||
main_size_(main_size) {
|
||||
Add(Container::Vertical({
|
||||
child,
|
||||
main,
|
||||
child_,
|
||||
main_,
|
||||
}));
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) final {
|
||||
if (event.is_mouse())
|
||||
if (event.is_mouse()) {
|
||||
return OnMouseEvent(std::move(event));
|
||||
}
|
||||
return ComponentBase::OnEvent(std::move(event));
|
||||
}
|
||||
|
||||
|
110
src/ftxui/component/resizable_split_test.cpp
Normal file
110
src/ftxui/component/resizable_split_test.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop
|
||||
#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::Pressed, Mouse::Released
|
||||
#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 {
|
||||
|
||||
namespace {
|
||||
Component BasicComponent() {
|
||||
return Renderer([] { return text(""); });
|
||||
}
|
||||
|
||||
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(ResizableSplit, BasicLeft) {
|
||||
int position = 3;
|
||||
auto component =
|
||||
ResizableSplitLeft(BasicComponent(), BasicComponent(), &position);
|
||||
auto screen = Screen(20, 20);
|
||||
Render(screen, component->Render());
|
||||
EXPECT_EQ(position, 3);
|
||||
EXPECT_TRUE(component->OnEvent(MousePressed(3, 1)));
|
||||
EXPECT_EQ(position, 3);
|
||||
EXPECT_TRUE(component->OnEvent(MousePressed(10, 1)));
|
||||
EXPECT_EQ(position, 10);
|
||||
EXPECT_TRUE(component->OnEvent(MouseReleased(10, 1)));
|
||||
EXPECT_EQ(position, 10);
|
||||
}
|
||||
|
||||
TEST(ResizableSplit, BasicRight) {
|
||||
int position = 3;
|
||||
auto component =
|
||||
ResizableSplitRight(BasicComponent(), BasicComponent(), &position);
|
||||
auto screen = Screen(20, 20);
|
||||
Render(screen, component->Render());
|
||||
EXPECT_EQ(position, 3);
|
||||
EXPECT_TRUE(component->OnEvent(MousePressed(16, 1)));
|
||||
EXPECT_EQ(position, 3);
|
||||
EXPECT_TRUE(component->OnEvent(MousePressed(10, 1)));
|
||||
EXPECT_EQ(position, 9);
|
||||
EXPECT_TRUE(component->OnEvent(MouseReleased(10, 1)));
|
||||
EXPECT_EQ(position, 9);
|
||||
}
|
||||
|
||||
TEST(ResizableSplit, BasicTop) {
|
||||
int position = 3;
|
||||
auto component =
|
||||
ResizableSplitTop(BasicComponent(), BasicComponent(), &position);
|
||||
auto screen = Screen(20, 20);
|
||||
Render(screen, component->Render());
|
||||
EXPECT_EQ(position, 3);
|
||||
EXPECT_TRUE(component->OnEvent(MousePressed(1, 3)));
|
||||
EXPECT_EQ(position, 3);
|
||||
EXPECT_TRUE(component->OnEvent(MousePressed(1, 10)));
|
||||
EXPECT_EQ(position, 10);
|
||||
EXPECT_TRUE(component->OnEvent(MouseReleased(1, 10)));
|
||||
EXPECT_EQ(position, 10);
|
||||
}
|
||||
|
||||
TEST(ResizableSplit, BasicBottom) {
|
||||
int position = 3;
|
||||
auto component =
|
||||
ResizableSplitBottom(BasicComponent(), BasicComponent(), &position);
|
||||
auto screen = Screen(20, 20);
|
||||
Render(screen, component->Render());
|
||||
EXPECT_EQ(position, 3);
|
||||
EXPECT_TRUE(component->OnEvent(MousePressed(1, 16)));
|
||||
EXPECT_EQ(position, 3);
|
||||
EXPECT_TRUE(component->OnEvent(MousePressed(1, 10)));
|
||||
EXPECT_EQ(position, 9);
|
||||
EXPECT_TRUE(component->OnEvent(MouseReleased(1, 10)));
|
||||
EXPECT_EQ(position, 9);
|
||||
}
|
||||
|
||||
} // 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,19 +1,23 @@
|
||||
#include <stdio.h> // for fileno, stdin
|
||||
#include <algorithm> // for copy, max, min
|
||||
#include <csignal> // for signal, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, SIGWINCH
|
||||
#include <cstdlib> // for NULL
|
||||
#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 <functional> // for function
|
||||
#include <initializer_list> // for initializer_list
|
||||
#include <iostream> // for cout, ostream, basic_ostream, operator<<, endl, flush
|
||||
#include <stack> // for stack
|
||||
#include <thread> // for thread
|
||||
#include <utility> // for swap, move
|
||||
#include <vector> // for vector
|
||||
#include <thread> // for thread, sleep_for
|
||||
#include <type_traits> // for decay_t
|
||||
#include <utility> // for move, swap
|
||||
#include <variant> // for visit
|
||||
#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/mouse.hpp" // for Mouse
|
||||
#include "ftxui/component/receiver.hpp" // for ReceiverImpl, MakeReceiver, Sender, SenderImpl, Receiver
|
||||
#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
|
||||
@@ -32,8 +36,8 @@
|
||||
#endif
|
||||
#else
|
||||
#include <sys/select.h> // for select, FD_ISSET, FD_SET, FD_ZERO, fd_set
|
||||
#include <termios.h> // for tcsetattr, tcgetattr, cc_t
|
||||
#include <unistd.h> // for STDIN_FILENO, read
|
||||
#include <termios.h> // for tcsetattr, termios, tcgetattr, TCSANOW, cc_t, ECHO, ICANON, VMIN, VTIME
|
||||
#include <unistd.h> // for STDIN_FILENO, read
|
||||
#endif
|
||||
|
||||
// Quick exit is missing in standard CLang headers
|
||||
@@ -43,8 +47,19 @@
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace animation {
|
||||
void RequestAnimationFrame() {
|
||||
auto* screen = ScreenInteractive::Active();
|
||||
if (screen) {
|
||||
screen->RequestAnimationFrame();
|
||||
}
|
||||
}
|
||||
} // namespace animation
|
||||
|
||||
namespace {
|
||||
|
||||
ScreenInteractive* g_active_screen = nullptr; // NOLINT
|
||||
|
||||
void Flush() {
|
||||
// Emscripten doesn't implement flush. We interpret zero as flush.
|
||||
std::cout << '\0' << std::flush;
|
||||
@@ -54,7 +69,7 @@ constexpr int timeout_milliseconds = 20;
|
||||
constexpr int timeout_microseconds = timeout_milliseconds * 1000;
|
||||
#if defined(_WIN32)
|
||||
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
auto console = GetStdHandle(STD_INPUT_HANDLE);
|
||||
auto parser = TerminalInputParser(out->Clone());
|
||||
while (!*quit) {
|
||||
@@ -104,7 +119,7 @@ void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
|
||||
#include <emscripten.h>
|
||||
|
||||
// Read char from the terminal.
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
(void)timeout_microseconds;
|
||||
auto parser = TerminalInputParser(std::move(out));
|
||||
|
||||
@@ -124,16 +139,14 @@ void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
|
||||
int CheckStdinReady(int usec_timeout) {
|
||||
timeval tv = {0, usec_timeout};
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(STDIN_FILENO, &fds);
|
||||
select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
|
||||
return FD_ISSET(STDIN_FILENO, &fds);
|
||||
FD_ZERO(&fds); // NOLINT
|
||||
FD_SET(STDIN_FILENO, &fds); // NOLINT
|
||||
select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &tv); // NOLINT
|
||||
return FD_ISSET(STDIN_FILENO, &fds); // NOLINT
|
||||
}
|
||||
|
||||
// Read char from the terminal.
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
|
||||
const int buffer_size = 100;
|
||||
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
auto parser = TerminalInputParser(std::move(out));
|
||||
|
||||
while (!*quit) {
|
||||
@@ -142,16 +155,18 @@ void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char buff[buffer_size];
|
||||
int l = read(fileno(stdin), buff, buffer_size);
|
||||
for (int i = 0; i < l; ++i)
|
||||
parser.Add(buff[i]);
|
||||
const size_t buffer_size = 100;
|
||||
std::array<char, buffer_size> buffer; // NOLINT;
|
||||
int l = read(fileno(stdin), buffer.data(), buffer_size); // NOLINT
|
||||
for (int i = 0; i < l; ++i) {
|
||||
parser.Add(buffer[i]); // NOLINT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
const std::string CSI = "\x1b[";
|
||||
const std::string CSI = "\x1b["; // NOLINT
|
||||
|
||||
// DEC: Digital Equipment Corporation
|
||||
enum class DECMode {
|
||||
@@ -172,12 +187,13 @@ enum class DSRMode {
|
||||
kCursor = 6,
|
||||
};
|
||||
|
||||
const std::string Serialize(std::vector<DECMode> parameters) {
|
||||
std::string Serialize(const std::vector<DECMode>& parameters) {
|
||||
bool first = true;
|
||||
std::string out;
|
||||
for (DECMode parameter : parameters) {
|
||||
if (!first)
|
||||
if (!first) {
|
||||
out += ";";
|
||||
}
|
||||
out += std::to_string(int(parameter));
|
||||
first = false;
|
||||
}
|
||||
@@ -185,22 +201,22 @@ const std::string Serialize(std::vector<DECMode> parameters) {
|
||||
}
|
||||
|
||||
// DEC Private Mode Set (DECSET)
|
||||
const std::string Set(std::vector<DECMode> parameters) {
|
||||
std::string Set(const std::vector<DECMode>& parameters) {
|
||||
return CSI + "?" + Serialize(parameters) + "h";
|
||||
}
|
||||
|
||||
// DEC Private Mode Reset (DECRST)
|
||||
const std::string Reset(std::vector<DECMode> parameters) {
|
||||
std::string Reset(const std::vector<DECMode>& parameters) {
|
||||
return CSI + "?" + Serialize(parameters) + "l";
|
||||
}
|
||||
|
||||
// Device Status Report (DSR)
|
||||
const std::string DeviceStatusReport(DSRMode ps) {
|
||||
std::string DeviceStatusReport(DSRMode ps) {
|
||||
return CSI + std::to_string(int(ps)) + "n";
|
||||
}
|
||||
|
||||
using SignalHandler = void(int);
|
||||
std::stack<ScreenInteractive::Callback> on_exit_functions;
|
||||
std::stack<Closure> on_exit_functions; // NOLINT
|
||||
void OnExit(int signal) {
|
||||
(void)signal;
|
||||
while (!on_exit_functions.empty()) {
|
||||
@@ -209,28 +225,44 @@ void OnExit(int signal) {
|
||||
}
|
||||
}
|
||||
|
||||
auto install_signal_handler = [](int sig, SignalHandler handler) {
|
||||
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); });
|
||||
on_exit_functions.push([=] { std::signal(sig, old_signal_handler); });
|
||||
};
|
||||
|
||||
ScreenInteractive::Callback on_resize = [] {};
|
||||
Closure g_on_resize = [] {}; // NOLINT
|
||||
void OnResize(int /* signal */) {
|
||||
on_resize();
|
||||
g_on_resize();
|
||||
}
|
||||
|
||||
void OnSigStop(int /*signal*/) {
|
||||
ScreenInteractive::Private::SigStop(*g_active_screen);
|
||||
}
|
||||
|
||||
class CapturedMouseImpl : public CapturedMouseInterface {
|
||||
public:
|
||||
CapturedMouseImpl(std::function<void(void)> callback) : callback_(callback) {}
|
||||
explicit CapturedMouseImpl(std::function<void(void)> callback)
|
||||
: callback_(std::move(callback)) {}
|
||||
~CapturedMouseImpl() override { callback_(); }
|
||||
CapturedMouseImpl(const CapturedMouseImpl&) = delete;
|
||||
CapturedMouseImpl(CapturedMouseImpl&&) = delete;
|
||||
CapturedMouseImpl& operator=(const CapturedMouseImpl&) = delete;
|
||||
CapturedMouseImpl& operator=(CapturedMouseImpl&&) = delete;
|
||||
|
||||
private:
|
||||
std::function<void(void)> callback_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
void AnimationListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
// Animation at around 60fps.
|
||||
const auto time_delta = std::chrono::milliseconds(15);
|
||||
while (!*quit) {
|
||||
out->Send(AnimationTask());
|
||||
std::this_thread::sleep_for(time_delta);
|
||||
}
|
||||
}
|
||||
|
||||
ScreenInteractive* g_active_screen = nullptr;
|
||||
} // namespace
|
||||
|
||||
ScreenInteractive::ScreenInteractive(int dimx,
|
||||
int dimy,
|
||||
@@ -239,8 +271,7 @@ ScreenInteractive::ScreenInteractive(int dimx,
|
||||
: Screen(dimx, dimy),
|
||||
dimension_(dimension),
|
||||
use_alternative_screen_(use_alternative_screen) {
|
||||
event_receiver_ = MakeReceiver<Event>();
|
||||
event_sender_ = event_receiver_->MakeSender();
|
||||
task_receiver_ = MakeReceiver<Task>();
|
||||
}
|
||||
|
||||
// static
|
||||
@@ -263,21 +294,37 @@ ScreenInteractive ScreenInteractive::FitComponent() {
|
||||
return ScreenInteractive(0, 0, Dimension::FitComponent, false);
|
||||
}
|
||||
|
||||
void ScreenInteractive::Post(Task task) {
|
||||
if (!quit_) {
|
||||
task_sender_->Send(std::move(task));
|
||||
}
|
||||
}
|
||||
void ScreenInteractive::PostEvent(Event event) {
|
||||
if (!quit_)
|
||||
event_sender_->Send(event);
|
||||
Post(event);
|
||||
}
|
||||
|
||||
void ScreenInteractive::RequestAnimationFrame() {
|
||||
if (animation_requested_) {
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
CapturedMouse ScreenInteractive::CaptureMouse() {
|
||||
if (mouse_captured)
|
||||
if (mouse_captured) {
|
||||
return nullptr;
|
||||
}
|
||||
mouse_captured = true;
|
||||
return std::make_unique<CapturedMouseImpl>(
|
||||
[this] { mouse_captured = false; });
|
||||
}
|
||||
|
||||
void ScreenInteractive::Loop(Component component) {
|
||||
|
||||
void ScreenInteractive::Loop(Component component) { // NOLINT
|
||||
// Suspend previously active screen:
|
||||
if (g_active_screen) {
|
||||
std::swap(suspended_screen_, g_active_screen);
|
||||
@@ -291,7 +338,7 @@ void ScreenInteractive::Loop(Component component) {
|
||||
// This screen is now active:
|
||||
g_active_screen = this;
|
||||
g_active_screen->Install();
|
||||
g_active_screen->Main(component);
|
||||
g_active_screen->Main(std::move(component));
|
||||
g_active_screen->Uninstall();
|
||||
g_active_screen = nullptr;
|
||||
|
||||
@@ -315,7 +362,7 @@ void ScreenInteractive::Loop(Component component) {
|
||||
/// @brief Decorate a function. It executes the same way, but with the currently
|
||||
/// active screen terminal hooks temporarilly uninstalled during its execution.
|
||||
/// @param fn The function to decorate.
|
||||
ScreenInteractive::Callback ScreenInteractive::WithRestoredIO(Callback fn) {
|
||||
Closure ScreenInteractive::WithRestoredIO(Closure fn) { // NOLINT
|
||||
return [this, fn] {
|
||||
Uninstall();
|
||||
fn();
|
||||
@@ -323,6 +370,11 @@ ScreenInteractive::Callback ScreenInteractive::WithRestoredIO(Callback fn) {
|
||||
};
|
||||
}
|
||||
|
||||
// static
|
||||
ScreenInteractive* ScreenInteractive::Active() {
|
||||
return g_active_screen;
|
||||
}
|
||||
|
||||
void ScreenInteractive::Install() {
|
||||
// After uninstalling the new configuration, flush it to the terminal to
|
||||
// ensure it is fully applied:
|
||||
@@ -332,10 +384,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})
|
||||
for (int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
|
||||
install_signal_handler(signal, OnExit);
|
||||
}
|
||||
|
||||
// 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);
|
||||
@@ -367,12 +420,12 @@ void ScreenInteractive::Install() {
|
||||
SetConsoleMode(stdin_handle, in_mode);
|
||||
SetConsoleMode(stdout_handle, out_mode);
|
||||
#else
|
||||
struct termios terminal;
|
||||
struct termios terminal; // NOLINT
|
||||
tcgetattr(STDIN_FILENO, &terminal);
|
||||
on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
|
||||
|
||||
terminal.c_lflag &= ~ICANON; // Non canonique terminal.
|
||||
terminal.c_lflag &= ~ECHO; // Do not print after a key press.
|
||||
terminal.c_lflag &= ~ICANON; // NOLINT Non canonique terminal.
|
||||
terminal.c_lflag &= ~ECHO; // NOLINT Do not print after a key press.
|
||||
terminal.c_cc[VMIN] = 0;
|
||||
terminal.c_cc[VTIME] = 0;
|
||||
// auto oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
|
||||
@@ -382,16 +435,19 @@ void ScreenInteractive::Install() {
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
|
||||
|
||||
// Handle resize.
|
||||
on_resize = [&] { event_sender_->Send(Event::Special({0})); };
|
||||
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 = [&](std::vector<DECMode> parameters) {
|
||||
auto enable = [&](const std::vector<DECMode>& parameters) {
|
||||
std::cout << Set(parameters);
|
||||
on_exit_functions.push([=] { std::cout << Reset(parameters); });
|
||||
};
|
||||
|
||||
auto disable = [&](std::vector<DECMode> parameters) {
|
||||
auto disable = [&](const std::vector<DECMode>& parameters) {
|
||||
std::cout << Reset(parameters);
|
||||
on_exit_functions.push([=] { std::cout << Set(parameters); });
|
||||
};
|
||||
@@ -419,46 +475,96 @@ void ScreenInteractive::Install() {
|
||||
Flush();
|
||||
|
||||
quit_ = false;
|
||||
task_sender_ = task_receiver_->MakeSender();
|
||||
event_listener_ =
|
||||
std::thread(&EventListener, &quit_, event_receiver_->MakeSender());
|
||||
std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
|
||||
animation_listener_ =
|
||||
std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
|
||||
}
|
||||
|
||||
void ScreenInteractive::Uninstall() {
|
||||
ExitLoopClosure()();
|
||||
event_listener_.join();
|
||||
animation_listener_.join();
|
||||
|
||||
OnExit(0);
|
||||
}
|
||||
|
||||
// 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 (!event_receiver_->HasPending()) {
|
||||
Draw(component);
|
||||
std::cout << ToString() << set_cursor_position;
|
||||
Flush();
|
||||
Clear();
|
||||
if (attempt_draw && !task_receiver_->HasPending()) {
|
||||
draw();
|
||||
attempt_draw = false;
|
||||
}
|
||||
|
||||
Event event;
|
||||
if (!event_receiver_->Receive(&event))
|
||||
Task task;
|
||||
if (!task_receiver_->Receive(&task)) {
|
||||
break;
|
||||
|
||||
if (event.is_cursor_reporting()) {
|
||||
cursor_x_ = event.cursor_x();
|
||||
cursor_y_ = event.cursor_y();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.is_mouse()) {
|
||||
event.mouse().x -= cursor_x_;
|
||||
event.mouse().y -= cursor_y_;
|
||||
}
|
||||
// clang-format off
|
||||
std::visit([&](auto&& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
|
||||
event.screen_ = this;
|
||||
component->OnEvent(event);
|
||||
// Handle Event.
|
||||
if constexpr (std::is_same_v<T, Event>) {
|
||||
if (arg.is_cursor_reporting()) {
|
||||
cursor_x_ = arg.cursor_x();
|
||||
cursor_y_ = arg.cursor_y();
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg.is_mouse()) {
|
||||
arg.mouse().x -= cursor_x_;
|
||||
arg.mouse().y -= cursor_y_;
|
||||
}
|
||||
|
||||
arg.screen_ = this;
|
||||
component->OnEvent(arg);
|
||||
attempt_draw = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle callback
|
||||
if constexpr (std::is_same_v<T, Closure>) {
|
||||
arg();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Animation
|
||||
if constexpr (std::is_same_v<T, AnimationTask>) {
|
||||
if (!animation_requested_) {
|
||||
return;
|
||||
}
|
||||
|
||||
animation_requested_ = false;
|
||||
animation::TimePoint now = animation::Clock::now();
|
||||
animation::Duration delta = now - previous_animation_time;
|
||||
previous_animation_time = now;
|
||||
|
||||
animation::Params params(delta);
|
||||
component->OnAnimation(params);
|
||||
attempt_draw = true;
|
||||
return;
|
||||
}
|
||||
},
|
||||
task);
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
void ScreenInteractive::Draw(Component component) {
|
||||
auto document = component->Render();
|
||||
int dimx = 0;
|
||||
@@ -509,13 +615,16 @@ void ScreenInteractive::Draw(Component component) {
|
||||
// https://github.com/ArthurSonzogni/FTXUI/issues/136
|
||||
static int i = -3;
|
||||
++i;
|
||||
if (!use_alternative_screen_ && (i % 150 == 0))
|
||||
if (!use_alternative_screen_ && (i % 150 == 0)) { // NOLINT
|
||||
std::cout << DeviceStatusReport(DSRMode::kCursor);
|
||||
}
|
||||
#else
|
||||
static int i = -3;
|
||||
++i;
|
||||
if (!use_alternative_screen_ && (previous_frame_resized_ || i % 40 == 0))
|
||||
if (!use_alternative_screen_ &&
|
||||
(previous_frame_resized_ || i % 40 == 0)) { // NOLINT
|
||||
std::cout << DeviceStatusReport(DSRMode::kCursor);
|
||||
}
|
||||
#endif
|
||||
previous_frame_resized_ = resized;
|
||||
|
||||
@@ -538,13 +647,31 @@ void ScreenInteractive::Draw(Component component) {
|
||||
}
|
||||
}
|
||||
|
||||
ScreenInteractive::Callback ScreenInteractive::ExitLoopClosure() {
|
||||
Closure ScreenInteractive::ExitLoopClosure() {
|
||||
return [this] {
|
||||
quit_ = true;
|
||||
event_sender_.reset();
|
||||
task_sender_.reset();
|
||||
};
|
||||
}
|
||||
|
||||
void ScreenInteractive::SigStop() {
|
||||
#if defined(_WIN32)
|
||||
// Windows do no support SIGTSTP.
|
||||
#else
|
||||
Post([&] {
|
||||
Uninstall();
|
||||
std::cout << reset_cursor_position;
|
||||
reset_cursor_position = "";
|
||||
std::cout << ResetPosition(/*clear=*/true);
|
||||
dimx_ = 0;
|
||||
dimy_ = 0;
|
||||
Flush();
|
||||
std::raise(SIGTSTP);
|
||||
Install();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ftxui.
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
|
@@ -7,7 +7,7 @@
|
||||
#include "ftxui/dom/elements.hpp" // for text, Element
|
||||
#include "gtest/gtest_pred_impl.h" // for Test, TEST, EXPECT_EQ
|
||||
|
||||
using namespace ftxui;
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
bool TestSignal(int signal) {
|
||||
@@ -47,6 +47,8 @@ TEST(ScreenInteractive, Signal_SIGFPE) {
|
||||
TestSignal(SIGFPE);
|
||||
}
|
||||
|
||||
} // 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.
|
||||
|
@@ -18,13 +18,13 @@ template <class T>
|
||||
class SliderBase : public ComponentBase {
|
||||
public:
|
||||
SliderBase(ConstStringRef label, T* value, T min, T max, T increment)
|
||||
: label_(label),
|
||||
: label_(std::move(label)),
|
||||
value_(value),
|
||||
min_(min),
|
||||
max_(max),
|
||||
increment_(increment) {}
|
||||
|
||||
Element Render() {
|
||||
Element Render() override {
|
||||
auto gauge_color =
|
||||
Focused() ? color(Color::GrayLight) : color(Color::GrayDark);
|
||||
float percent = float(*value_ - min_) / float(max_ - min_);
|
||||
@@ -40,8 +40,9 @@ class SliderBase : public ComponentBase {
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) final {
|
||||
if (event.is_mouse())
|
||||
if (event.is_mouse()) {
|
||||
return OnMouseEvent(event);
|
||||
}
|
||||
|
||||
if (event == Event::ArrowLeft || event == Event::Character('h')) {
|
||||
*value_ -= increment_;
|
||||
|
@@ -1,24 +1,27 @@
|
||||
#include "ftxui/component/terminal_input_parser.hpp"
|
||||
|
||||
#include <algorithm> // for max
|
||||
#include <cstdint>
|
||||
#include <cstdint> // for uint32_t
|
||||
#include <memory> // for unique_ptr
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/event.hpp" // for Event
|
||||
#include "ftxui/component/task.hpp" // for Task
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
TerminalInputParser::TerminalInputParser(Sender<Event> out)
|
||||
TerminalInputParser::TerminalInputParser(Sender<Task> out)
|
||||
: out_(std::move(out)) {}
|
||||
|
||||
void TerminalInputParser::Timeout(int time) {
|
||||
timeout_ += time;
|
||||
if (timeout_ < 50)
|
||||
const int timeout_threshold = 50;
|
||||
if (timeout_ < timeout_threshold) {
|
||||
return;
|
||||
}
|
||||
timeout_ = 0;
|
||||
if (pending_.size())
|
||||
if (!pending_.empty()) {
|
||||
Send(SPECIAL);
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalInputParser::Add(char c) {
|
||||
@@ -52,18 +55,27 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) {
|
||||
return;
|
||||
|
||||
case SPECIAL:
|
||||
out_->Send(Event::Special(std::move(pending_)));
|
||||
// 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_)));
|
||||
}
|
||||
pending_.clear();
|
||||
return;
|
||||
|
||||
case MOUSE:
|
||||
out_->Send(Event::Mouse(std::move(pending_), output.mouse));
|
||||
out_->Send(Event::Mouse(std::move(pending_), output.mouse)); // NOLINT
|
||||
pending_.clear();
|
||||
return;
|
||||
|
||||
case CURSOR_REPORTING:
|
||||
out_->Send(Event::CursorReporting(std::move(pending_), output.cursor.x,
|
||||
output.cursor.y));
|
||||
out_->Send(Event::CursorReporting(std::move(pending_), // NOLINT
|
||||
output.cursor.x, // NOLINT
|
||||
output.cursor.y)); // NOLINT
|
||||
pending_.clear();
|
||||
return;
|
||||
}
|
||||
@@ -71,12 +83,13 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) {
|
||||
}
|
||||
|
||||
TerminalInputParser::Output TerminalInputParser::Parse() {
|
||||
if (!Eat())
|
||||
if (!Eat()) {
|
||||
return UNCOMPLETED;
|
||||
}
|
||||
|
||||
switch (Current()) {
|
||||
case 24: // CAN
|
||||
case 26: // SUB
|
||||
case 24: // CAN NOLINT
|
||||
case 26: // SUB NOLINT
|
||||
return DROP;
|
||||
|
||||
case '\x1B':
|
||||
@@ -85,11 +98,13 @@ TerminalInputParser::Output TerminalInputParser::Parse() {
|
||||
break;
|
||||
}
|
||||
|
||||
if (Current() < 32) // C0
|
||||
if (Current() < 32) { // C0 NOLINT
|
||||
return SPECIAL;
|
||||
}
|
||||
|
||||
if (Current() == 127) // Delete
|
||||
if (Current() == 127) { // Delete // NOLINT
|
||||
return SPECIAL;
|
||||
}
|
||||
|
||||
return ParseUTF8();
|
||||
}
|
||||
@@ -111,67 +126,72 @@ TerminalInputParser::Output TerminalInputParser::Parse() {
|
||||
// Then some sequences are illegal if it exist a shorter representation of the
|
||||
// same codepoint.
|
||||
TerminalInputParser::Output TerminalInputParser::ParseUTF8() {
|
||||
unsigned char head = static_cast<unsigned char>(Current());
|
||||
unsigned char selector = 0b1000'0000;
|
||||
auto head = static_cast<unsigned char>(Current());
|
||||
unsigned char selector = 0b1000'0000; // NOLINT
|
||||
|
||||
// The non code-point part of the first byte.
|
||||
unsigned char mask = selector;
|
||||
|
||||
// Find the first zero in the first byte.
|
||||
int first_zero = 8;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
unsigned int first_zero = 8; // NOLINT
|
||||
for (unsigned int i = 0; i < 8; ++i) { // NOLINT
|
||||
mask |= selector;
|
||||
if (head & selector) {
|
||||
selector >>= 1;
|
||||
continue;
|
||||
if (!(head & selector)) {
|
||||
first_zero = i;
|
||||
break;
|
||||
}
|
||||
first_zero = i;
|
||||
break;
|
||||
selector >>= 1U;
|
||||
}
|
||||
|
||||
// Accumulate the value of the first byte.
|
||||
uint32_t value = head & ~mask;
|
||||
auto value = uint32_t(head & ~mask); // NOLINT
|
||||
|
||||
// Invalid UTF8, with more than 5 bytes.
|
||||
if (first_zero == 1 || first_zero >= 5)
|
||||
const unsigned int max_utf8_bytes = 5;
|
||||
if (first_zero == 1 || first_zero >= max_utf8_bytes) {
|
||||
return DROP;
|
||||
}
|
||||
|
||||
// Multi byte UTF-8.
|
||||
for (int i = 2; i <= first_zero; ++i) {
|
||||
if (!Eat())
|
||||
for (unsigned int i = 2; i <= first_zero; ++i) {
|
||||
if (!Eat()) {
|
||||
return UNCOMPLETED;
|
||||
}
|
||||
|
||||
// Invalid continuation byte.
|
||||
head = static_cast<unsigned char>(Current());
|
||||
if ((head & 0b1100'0000) != 0b1000'0000)
|
||||
if ((head & 0b1100'0000) != 0b1000'0000) { // NOLINT
|
||||
return DROP;
|
||||
value <<= 6;
|
||||
value += head & 0b0011'1111;
|
||||
}
|
||||
value <<= 6; // NOLINT
|
||||
value += head & 0b0011'1111; // NOLINT
|
||||
}
|
||||
|
||||
// Check for overlong UTF8 encoding.
|
||||
int extra_byte;
|
||||
if (value <= 0b000'0000'0111'1111) {
|
||||
extra_byte = 0;
|
||||
} else if (value <= 0b000'0111'1111'1111) {
|
||||
extra_byte = 1;
|
||||
} else if (value <= 0b1111'1111'1111'1111) {
|
||||
extra_byte = 2;
|
||||
} else if (value <= 0b1'0000'1111'1111'1111'1111) {
|
||||
extra_byte = 3;
|
||||
} else {
|
||||
int extra_byte = 0;
|
||||
if (value <= 0b000'0000'0111'1111) { // NOLINT
|
||||
extra_byte = 0; // NOLINT
|
||||
} else if (value <= 0b000'0111'1111'1111) { // NOLINT
|
||||
extra_byte = 1; // NOLINT
|
||||
} else if (value <= 0b1111'1111'1111'1111) { // NOLINT
|
||||
extra_byte = 2; // NOLINT
|
||||
} else if (value <= 0b1'0000'1111'1111'1111'1111) { // NOLINT
|
||||
extra_byte = 3; // NOLINT
|
||||
} else { // NOLINT
|
||||
return DROP;
|
||||
}
|
||||
|
||||
if (extra_byte != position_)
|
||||
if (extra_byte != position_) {
|
||||
return DROP;
|
||||
}
|
||||
|
||||
return CHARACTER;
|
||||
}
|
||||
|
||||
TerminalInputParser::Output TerminalInputParser::ParseESC() {
|
||||
if (!Eat())
|
||||
if (!Eat()) {
|
||||
return UNCOMPLETED;
|
||||
}
|
||||
switch (Current()) {
|
||||
case 'P':
|
||||
return ParseDCS();
|
||||
@@ -180,26 +200,32 @@ TerminalInputParser::Output TerminalInputParser::ParseESC() {
|
||||
case ']':
|
||||
return ParseOSC();
|
||||
default:
|
||||
if (!Eat())
|
||||
if (!Eat()) {
|
||||
return UNCOMPLETED;
|
||||
return SPECIAL;
|
||||
} else {
|
||||
return SPECIAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TerminalInputParser::Output TerminalInputParser::ParseDCS() {
|
||||
// Parse until the string terminator ST.
|
||||
while (1) {
|
||||
if (!Eat())
|
||||
while (true) {
|
||||
if (!Eat()) {
|
||||
return UNCOMPLETED;
|
||||
}
|
||||
|
||||
if (Current() != '\x1B')
|
||||
if (Current() != '\x1B') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Eat())
|
||||
if (!Eat()) {
|
||||
return UNCOMPLETED;
|
||||
}
|
||||
|
||||
if (Current() != '\\')
|
||||
if (Current() != '\\') {
|
||||
continue;
|
||||
}
|
||||
|
||||
return SPECIAL;
|
||||
}
|
||||
@@ -210,8 +236,9 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() {
|
||||
int argument = 0;
|
||||
std::vector<int> arguments;
|
||||
while (true) {
|
||||
if (!Eat())
|
||||
if (!Eat()) {
|
||||
return UNCOMPLETED;
|
||||
}
|
||||
|
||||
if (Current() == '<') {
|
||||
altered = true;
|
||||
@@ -219,7 +246,7 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() {
|
||||
}
|
||||
|
||||
if (Current() >= '0' && Current() <= '9') {
|
||||
argument *= 10;
|
||||
argument *= 10; // NOLINT
|
||||
argument += int(Current() - '0');
|
||||
continue;
|
||||
}
|
||||
@@ -232,7 +259,7 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() {
|
||||
|
||||
if (Current() >= ' ' && Current() <= '~' && Current() != '<') {
|
||||
arguments.push_back(argument);
|
||||
argument = 0;
|
||||
argument = 0; // NOLINT
|
||||
switch (Current()) {
|
||||
case 'M':
|
||||
return ParseMouse(altered, true, std::move(arguments));
|
||||
@@ -246,53 +273,61 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() {
|
||||
}
|
||||
|
||||
// Invalid ESC in CSI.
|
||||
if (Current() == '\x1B')
|
||||
if (Current() == '\x1B') {
|
||||
return SPECIAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TerminalInputParser::Output TerminalInputParser::ParseOSC() {
|
||||
// Parse until the string terminator ST.
|
||||
while (true) {
|
||||
if (!Eat())
|
||||
if (!Eat()) {
|
||||
return UNCOMPLETED;
|
||||
if (Current() != '\x1B')
|
||||
}
|
||||
if (Current() != '\x1B') {
|
||||
continue;
|
||||
if (!Eat())
|
||||
}
|
||||
if (!Eat()) {
|
||||
return UNCOMPLETED;
|
||||
if (Current() != '\\')
|
||||
}
|
||||
if (Current() != '\\') {
|
||||
continue;
|
||||
}
|
||||
return SPECIAL;
|
||||
}
|
||||
}
|
||||
|
||||
TerminalInputParser::Output TerminalInputParser::ParseMouse(
|
||||
TerminalInputParser::Output TerminalInputParser::ParseMouse( // NOLINT
|
||||
bool altered,
|
||||
bool pressed,
|
||||
std::vector<int> arguments) {
|
||||
if (arguments.size() != 3)
|
||||
if (arguments.size() != 3) {
|
||||
return SPECIAL;
|
||||
}
|
||||
|
||||
(void)altered;
|
||||
|
||||
Output output(MOUSE);
|
||||
output.mouse.button = Mouse::Button((arguments[0] & 3) + //
|
||||
((arguments[0] & 64) >> 4));
|
||||
output.mouse.motion = Mouse::Motion(pressed);
|
||||
output.mouse.shift = bool(arguments[0] & 4);
|
||||
output.mouse.meta = bool(arguments[0] & 8);
|
||||
output.mouse.x = arguments[1];
|
||||
output.mouse.y = arguments[2];
|
||||
output.mouse.button = Mouse::Button((arguments[0] & 3) + // NOLINT
|
||||
((arguments[0] & 64) >> 4)); // NOLINT
|
||||
output.mouse.motion = Mouse::Motion(pressed); // NOLINT
|
||||
output.mouse.shift = bool(arguments[0] & 4); // NOLINT
|
||||
output.mouse.meta = bool(arguments[0] & 8); // NOLINT
|
||||
output.mouse.x = arguments[1]; // NOLINT
|
||||
output.mouse.y = arguments[2]; // NOLINT
|
||||
return output;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TerminalInputParser::Output TerminalInputParser::ParseCursorReporting(
|
||||
std::vector<int> arguments) {
|
||||
if (arguments.size() != 2)
|
||||
if (arguments.size() != 2) {
|
||||
return SPECIAL;
|
||||
}
|
||||
Output output(CURSOR_REPORTING);
|
||||
output.cursor.y = arguments[0];
|
||||
output.cursor.x = arguments[1];
|
||||
output.cursor.y = arguments[0]; // NOLINT
|
||||
output.cursor.x = arguments[1]; // NOLINT
|
||||
return output;
|
||||
}
|
||||
|
||||
|
@@ -8,6 +8,7 @@
|
||||
#include "ftxui/component/event.hpp" // for Event (ptr only)
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse
|
||||
#include "ftxui/component/receiver.hpp" // for Sender
|
||||
#include "ftxui/component/task.hpp" // for Task
|
||||
|
||||
namespace ftxui {
|
||||
struct Event;
|
||||
@@ -15,7 +16,7 @@ struct Event;
|
||||
// Parse a sequence of |char| accross |time|. Produces |Event|.
|
||||
class TerminalInputParser {
|
||||
public:
|
||||
TerminalInputParser(Sender<Event> out);
|
||||
TerminalInputParser(Sender<Task> out);
|
||||
void Timeout(int time);
|
||||
void Add(char c);
|
||||
|
||||
@@ -47,7 +48,7 @@ class TerminalInputParser {
|
||||
Output(Type t) : type(t) {}
|
||||
};
|
||||
|
||||
void Send(Output type);
|
||||
void Send(Output output);
|
||||
Output Parse();
|
||||
Output ParseUTF8();
|
||||
Output ParseESC();
|
||||
@@ -57,7 +58,7 @@ class TerminalInputParser {
|
||||
Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments);
|
||||
Output ParseCursorReporting(std::vector<int> arguments);
|
||||
|
||||
Sender<Event> out_;
|
||||
Sender<Task> out_;
|
||||
int position_ = -1;
|
||||
int timeout_ = 0;
|
||||
std::string pending_;
|
||||
|
@@ -1,14 +1,16 @@
|
||||
#include <gtest/gtest-message.h> // for Message
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <algorithm> // for max
|
||||
#include <memory> // for unique_ptr, allocator
|
||||
#include <initializer_list> // for initializer_list
|
||||
#include <memory> // for unique_ptr, allocator
|
||||
#include <variant> // for get
|
||||
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Escape
|
||||
#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/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, EXPECT_FALSE, TEST
|
||||
#include "gtest/gtest_pred_impl.h" // for AssertionResult, Test, EXPECT_EQ, EXPECT_TRUE, TEST, EXPECT_FALSE
|
||||
|
||||
using namespace ftxui;
|
||||
namespace ftxui {
|
||||
|
||||
// Test char |c| to are trivially converted into |Event::Character(c)|.
|
||||
TEST(Event, Character) {
|
||||
@@ -18,61 +20,61 @@ TEST(Event, Character) {
|
||||
for (char c = 'A'; c <= 'Z'; ++c)
|
||||
basic_char.push_back(c);
|
||||
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
for (char c : basic_char)
|
||||
parser.Add(c);
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
for (char c : basic_char) {
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(received.is_character());
|
||||
EXPECT_EQ(c, received.character()[0]);
|
||||
EXPECT_TRUE(std::get<Event>(received).is_character());
|
||||
EXPECT_EQ(c, std::get<Event>(received).character()[0]);
|
||||
}
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, EscapeKeyWithoutWaiting) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, EscapeKeyNotEnoughWait) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
parser.Timeout(49);
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, EscapeKeyEnoughWait) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
parser.Timeout(50);
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_EQ(received, Event::Escape);
|
||||
EXPECT_EQ(std::get<Event>(received), Event::Escape);
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, MouseLeftClick) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
TEST(Event, MouseLeftClickPressed) {
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
@@ -88,17 +90,67 @@ TEST(Event, MouseLeftClick) {
|
||||
parser.Add('M');
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(received.is_mouse());
|
||||
EXPECT_EQ(Mouse::Left, received.mouse().button);
|
||||
EXPECT_EQ(12, received.mouse().x);
|
||||
EXPECT_EQ(42, received.mouse().y);
|
||||
EXPECT_TRUE(std::get<Event>(received).is_mouse());
|
||||
EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button);
|
||||
EXPECT_EQ(12, std::get<Event>(received).mouse().x);
|
||||
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
|
||||
EXPECT_EQ(std::get<Event>(received).mouse().motion, Mouse::Pressed);
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, MouseLeftClickReleased) {
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
parser.Add('[');
|
||||
parser.Add('3');
|
||||
parser.Add('2');
|
||||
parser.Add(';');
|
||||
parser.Add('1');
|
||||
parser.Add('2');
|
||||
parser.Add(';');
|
||||
parser.Add('4');
|
||||
parser.Add('2');
|
||||
parser.Add('m');
|
||||
}
|
||||
|
||||
Task received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(std::get<Event>(received).is_mouse());
|
||||
EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button);
|
||||
EXPECT_EQ(12, std::get<Event>(received).mouse().x);
|
||||
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
|
||||
EXPECT_EQ(std::get<Event>(received).mouse().motion, Mouse::Released);
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, MouseReporting) {
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
parser.Add('[');
|
||||
parser.Add('1');
|
||||
parser.Add('2');
|
||||
parser.Add(';');
|
||||
parser.Add('4');
|
||||
parser.Add('2');
|
||||
parser.Add('R');
|
||||
}
|
||||
|
||||
Task received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(std::get<Event>(received).is_cursor_reporting());
|
||||
EXPECT_EQ(42, std::get<Event>(received).cursor_x());
|
||||
EXPECT_EQ(12, std::get<Event>(received).cursor_y());
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, MouseMiddleClick) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
@@ -114,17 +166,17 @@ TEST(Event, MouseMiddleClick) {
|
||||
parser.Add('M');
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(received.is_mouse());
|
||||
EXPECT_EQ(Mouse::Middle, received.mouse().button);
|
||||
EXPECT_EQ(12, received.mouse().x);
|
||||
EXPECT_EQ(42, received.mouse().y);
|
||||
EXPECT_TRUE(std::get<Event>(received).is_mouse());
|
||||
EXPECT_EQ(Mouse::Middle, std::get<Event>(received).mouse().button);
|
||||
EXPECT_EQ(12, std::get<Event>(received).mouse().x);
|
||||
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, MouseRightClick) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
@@ -140,12 +192,12 @@ TEST(Event, MouseRightClick) {
|
||||
parser.Add('M');
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(received.is_mouse());
|
||||
EXPECT_EQ(Mouse::Right, received.mouse().button);
|
||||
EXPECT_EQ(12, received.mouse().x);
|
||||
EXPECT_EQ(42, received.mouse().y);
|
||||
EXPECT_TRUE(std::get<Event>(received).is_mouse());
|
||||
EXPECT_EQ(Mouse::Right, std::get<Event>(received).mouse().button);
|
||||
EXPECT_EQ(12, std::get<Event>(received).mouse().x);
|
||||
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
@@ -216,21 +268,122 @@ TEST(Event, UTF8) {
|
||||
|
||||
};
|
||||
for (auto test : kTestCase) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
for (auto input : test.input)
|
||||
parser.Add(input);
|
||||
}
|
||||
Event received;
|
||||
Task received;
|
||||
if (test.valid) {
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(received.is_character());
|
||||
EXPECT_TRUE(std::get<Event>(received).is_character());
|
||||
}
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Event, NewLine) {
|
||||
for (char newline : {'\r', '\n'}) {
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add(newline);
|
||||
}
|
||||
Task received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(std::get<Event>(received) == Event::Return);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Event, Control) {
|
||||
struct TestCase {
|
||||
char input;
|
||||
bool cancel;
|
||||
};
|
||||
std::vector<TestCase> cases;
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
if (i == 13 || i == 24 || i == 26 || i == 27)
|
||||
continue;
|
||||
cases.push_back({char(i), false});
|
||||
}
|
||||
cases.push_back({char(24), true});
|
||||
cases.push_back({char(26), true});
|
||||
cases.push_back({char(127), false});
|
||||
|
||||
for (auto test : cases) {
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add(test.input);
|
||||
}
|
||||
Task received;
|
||||
if (test.cancel) {
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
} else {
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_EQ(std::get<Event>(received), Event::Special({test.input}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Event, Special) {
|
||||
auto str = [](std::string input) {
|
||||
std::vector<unsigned char> output;
|
||||
for (auto it : input)
|
||||
output.push_back(it);
|
||||
return output;
|
||||
};
|
||||
struct {
|
||||
std::vector<unsigned char> input;
|
||||
Event expected;
|
||||
} kTestCase[] = {
|
||||
{str("\x1B[D"), Event::ArrowLeft},
|
||||
{str("\x1B[C"), Event::ArrowRight},
|
||||
{str("\x1B[A"), Event::ArrowUp},
|
||||
{str("\x1B[B"), Event::ArrowDown},
|
||||
{{127}, 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("\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[24~"), Event::F12},
|
||||
{{27, 91, 72}, Event::Home},
|
||||
{{27, 91, 70}, Event::End},
|
||||
{{27, 91, 53, 126}, Event::PageUp},
|
||||
{{27, 91, 54, 126}, Event::PageDown},
|
||||
{{0}, Event::Custom},
|
||||
};
|
||||
|
||||
for (auto test : kTestCase) {
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
for (auto input : test.input) {
|
||||
parser.Add(input);
|
||||
}
|
||||
}
|
||||
Task received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_EQ(std::get<Event>(received), test.expected);
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// 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.
|
||||
|
@@ -5,14 +5,14 @@
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) {
|
||||
using namespace ftxui;
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
parser.Add(data[i]);
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
while (event_receiver->Receive(&received))
|
||||
;
|
||||
return 0; // Non-zero return values are reserved for future use.
|
||||
|
@@ -1,144 +0,0 @@
|
||||
#include <algorithm> // for clamp, max
|
||||
#include <functional> // for function
|
||||
#include <memory> // for shared_ptr, allocator_traits<>::value_type
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/component.hpp" // for Make, Toggle
|
||||
#include "ftxui/component/component_base.hpp" // for Component, ComponentBase
|
||||
#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/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, Elements, hbox, reflect, separator, text, focus, nothing, select
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/util.hpp"
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
|
||||
/// @brief An horizontal list of elements. The user can navigate through them.
|
||||
/// @ingroup component
|
||||
class ToggleBase : public ComponentBase {
|
||||
public:
|
||||
ToggleBase(ConstStringListRef entries,
|
||||
int* selected,
|
||||
Ref<ToggleOption> option)
|
||||
: entries_(entries), selected_(selected), option_(std::move(option)) {}
|
||||
|
||||
private:
|
||||
Element Render() override {
|
||||
Clamp();
|
||||
Elements children;
|
||||
bool is_toggle_focused = Focused();
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
// Separator.
|
||||
if (i != 0)
|
||||
children.push_back(separator());
|
||||
|
||||
bool is_focused = (focused_entry() == i) && is_toggle_focused;
|
||||
bool is_selected = (*selected_ == i);
|
||||
|
||||
auto style = is_selected ? (is_focused ? option_->style_selected_focused
|
||||
: option_->style_selected)
|
||||
: (is_focused ? option_->style_focused
|
||||
: option_->style_normal);
|
||||
auto focus_management = !is_selected ? nothing
|
||||
: is_toggle_focused ? focus
|
||||
: select;
|
||||
children.push_back(text(entries_[i]) | style | focus_management |
|
||||
reflect(boxes_[i]));
|
||||
}
|
||||
return hbox(std::move(children));
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
Clamp();
|
||||
if (event.is_mouse())
|
||||
return OnMouseEvent(event);
|
||||
|
||||
int old_selected = *selected_;
|
||||
if (event == Event::ArrowLeft || event == Event::Character('h'))
|
||||
(*selected_)--;
|
||||
if (event == Event::ArrowRight || event == Event::Character('l'))
|
||||
(*selected_)++;
|
||||
if (event == Event::Tab && size())
|
||||
*selected_ = (*selected_ + 1) % size();
|
||||
if (event == Event::TabReverse && size())
|
||||
*selected_ = (*selected_ + size() - 1) % size();
|
||||
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
|
||||
if (old_selected != *selected_) {
|
||||
focused_entry() = *selected_;
|
||||
option_->on_change();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event == Event::Return) {
|
||||
option_->on_enter();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OnMouseEvent(Event event) {
|
||||
if (!CaptureMouse(event))
|
||||
return false;
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
if (!boxes_[i].Contain(event.mouse().x, event.mouse().y))
|
||||
continue;
|
||||
|
||||
TakeFocus();
|
||||
focused_entry() = i;
|
||||
if (event.mouse().button == Mouse::Left &&
|
||||
event.mouse().motion == Mouse::Pressed) {
|
||||
TakeFocus();
|
||||
if (*selected_ != i) {
|
||||
*selected_ = i;
|
||||
option_->on_change();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Clamp() {
|
||||
boxes_.resize(size());
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
|
||||
}
|
||||
|
||||
bool Focusable() const final { return size(); }
|
||||
int& focused_entry() { return option_->focused_entry(); }
|
||||
int size() const { return entries_.size(); }
|
||||
|
||||
ConstStringListRef entries_;
|
||||
int* selected_ = 0;
|
||||
|
||||
std::vector<Box> boxes_;
|
||||
Ref<ToggleOption> option_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/// @brief An horizontal list of elements. The user can navigate through them.
|
||||
/// @param entries The list of selectable entries to display.
|
||||
/// @param selected Reference the selected entry.
|
||||
/// @param option Additional optional parameters.
|
||||
/// @ingroup component
|
||||
Component Toggle(ConstStringListRef entries,
|
||||
int* selected,
|
||||
Ref<ToggleOption> option) {
|
||||
return Make<ToggleBase>(entries, selected, std::move(option));
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// 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.
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user