65 Commits

Author SHA1 Message Date
Miko
4b8c8ea00d Merge branch 'ArthurSonzogni:main' into main 2025-05-11 17:12:31 -04:00
Arthur Sonzogni
08b8a3b28f Add documentation about Bazel (#1045)
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-05-11 08:00:11 +02:00
Miko
9e1120c146 Merge branch 'main' into main 2025-05-10 22:01:04 -04:00
ArthurSonzogni
5cfed50702 v6.1.9
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-05-07 22:43:27 +02:00
Arthur Sonzogni
b307a175ed Bazel: general improvements. (#1043)
* Bazel: general improvements.

Improve the Bazel build. Attempt to fix previous errors recorded while
trying to publish ftxui in the Bazel Central Registry:
- https://github.com/bazelbuild/bazel-central-registry/pull/4485
- https://buildkite.com/bazel/bcr-presubmit/builds/13601#01968b61-f5b2-4d16-94d0-c87a03a1a23b

Test against "recent" platforms
-------------------------------

Previously, I got the error:
```
gcc: error: unrecognized command line option '-std-c++20'; did you mean '-std-c++2a'?
```
This was due to using old distribution like ubuntu 2004. Test against
newer platforms only to avoid GCC version<-9.x.y

Downgrade gtest version.
------------------------

I suspect this caused the Bazel Central Registry error:
```
file:///workdir/modules/googletest/1.15.2/MODULE.bazel:68:20: name 'use_repo_rule' is not defined
```
Specifying using bazelmod fixes the issue. Thanks @robinlinden

Tag gtest as dev_dependency
---------------------------

Presumably, this should avoid dependants to fetch it?

Enable --features-layering_check
--------------------------------

Aka clang `-Wprivate-header`. Fix the encountered errors.

Use clang in the CI
-------------------

The CI was defining clang/gcc in the matrix, but was not using it. Fix
the bug.
2025-05-07 22:41:17 +02:00
Jacob Shing
4604adb502 Adds example project using FTXUI (#1044)
Some checks failed
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-05-05 17:44:13 +02:00
Miko
006ec1cbed Merge branch 'ArthurSonzogni:main' into main 2025-05-04 19:27:15 -04:00
ArthurSonzogni
add5f40d31 Restore dbox behavior from ftxui5.0.0
Some checks failed
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
Bug:https://github.com/eclipse-ecal/ecal/pull/2095
2025-05-02 16:48:00 +02:00
Miko
16c25ae441 Merge branch 'main' into main 2025-05-01 17:09:57 -04:00
ArthurSonzogni
805db9bdea Set Bazel compatibility level
Some checks failed
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-05-01 11:53:18 +02:00
ArthurSonzogni
784f53fd7e Remove attestion for Bazel 2025-05-01 11:30:12 +02:00
ArthurSonzogni
799d8a267e Fix release workflow 2025-05-01 10:52:54 +02:00
ArthurSonzogni
f4513702b0 Fix release workflow 2025-05-01 10:40:53 +02:00
ArthurSonzogni
ba6716c6e1 Fix publish workflow 2025-05-01 10:33:19 +02:00
ArthurSonzogni
694fa6bf5c Fix publish workflow 2025-05-01 10:09:18 +02:00
Arthur Sonzogni
625915b52c Fix publish workflow (#1041) 2025-05-01 10:06:49 +02:00
Arthur Sonzogni
2d4c114008 v6.1.2 (#1040) 2025-05-01 10:01:05 +02:00
Arthur Sonzogni
aa80d8bac9 Generate attestation + refactor workflows (#1039) 2025-05-01 09:59:08 +02:00
Miko
05b4bffe3b Merge branch 'main' into main 2025-04-30 14:39:52 -04:00
Arthur Sonzogni
bcdcf70348 Fix Bazel Central Repository Workflow (#1038)
Some checks are pending
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, windows-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, ubuntu-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Waiting to run
Build / Create release (push) Blocked by required conditions
Build / Build packages (build/ftxui*Darwin*, macos-latest) (push) Blocked by required conditions
Build / Build packages (build/ftxui*Linux*, ubuntu-latest) (push) Blocked by required conditions
Build / Build packages (build/ftxui*Win64*, windows-latest) (push) Blocked by required conditions
Build / Build source package (push) Blocked by required conditions
Build / documentation (push) Waiting to run
2025-04-30 12:04:32 +02:00
Arthur Sonzogni
4231c4903b Release version 6.1.0. (#1037)
Some checks are pending
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, windows-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, ubuntu-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, macos-latest) (push) Waiting to run
Build / Bazel, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Waiting to run
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Waiting to run
Build / Create release (push) Blocked by required conditions
Build / Build packages (build/ftxui*Darwin*, macos-latest) (push) Blocked by required conditions
Build / Build packages (build/ftxui*Linux*, ubuntu-latest) (push) Blocked by required conditions
Build / Build packages (build/ftxui*Win64*, windows-latest) (push) Blocked by required conditions
Build / Build source package (push) Blocked by required conditions
Build / documentation (push) Waiting to run
This highlight support for the Bazel build system.
2025-04-29 16:05:11 +02:00
Arthur Sonzogni
10d73d365f Support Bazel build system (#1033)
Bug:https://github.com/ArthurSonzogni/FTXUI/issues/1032
Fixed:https://github.com/ArthurSonzogni/FTXUI/issues/1032
2025-04-29 15:11:10 +02:00
Toyosatomimi no Miko
ffc6dcd3bf Add ftxui module for all submodules 2025-04-19 09:54:02 -04:00
ArthurSonzogni
f6dceabdc9 tweaks 2025-04-11 15:07:58 +02:00
ArthurSonzogni
cb8ebdeb44 fix workflows 3 2025-04-11 02:03:18 +02:00
ArthurSonzogni
dd37fba100 Fix workflow 2 2025-04-11 01:48:07 +02:00
ArthurSonzogni
4d627c1ffb Fix workflow. 2025-04-11 01:43:23 +02:00
ArthurSonzogni
1dff6a5c35 Compile examples with modules. 2025-04-11 01:39:07 +02:00
ArthurSonzogni
0c67566427 Update 2025-04-10 14:45:45 +02:00
ArthurSonzogni
85c3dc45ca Add Changelog and remove flag. 2025-04-09 12:35:46 +02:00
Miko
84f691e9d3 Merge branch 'ArthurSonzogni:main' into main 2025-04-08 11:45:52 -04:00
Arthur Sonzogni
07fd3e685a Bugfix: Avoid crash with ResizeableSplit. (#1025)
Component
---------
- Bugfix: Fix a crash with ResizeableSplit. See #1023.
  - Clamp screen size to terminal size.
  - Disallow `ResizeableSplit` with negative size.

Dom
---
- Bugfix: Disallow specifying a negative size constraint. See #1023.

Bug: https://github.com/ArthurSonzogni/FTXUI/issues/1023
2025-03-31 18:19:48 +02:00
Miko
2e36aa061a Merge branch 'ArthurSonzogni:main' into main 2025-03-29 22:17:54 -04:00
ArthurSonzogni
09eb2f7fb0 v6.0.2 2025-03-30 01:27:57 +01:00
Arthur Sonzogni
1144e13125 Apply @forworldm code review. (#1022)
See: https://github.com/ArthurSonzogni/FTXUI/pull/1021?notification_referrer_id=NT_kwDOAEieQrMxNTU3OTg4MDA1MDo0NzU5MTA2#discussion_r2019827970
2025-03-30 01:22:17 +01:00
Miko
8ed06a4812 Merge branch 'ArthurSonzogni:main' into main 2025-03-29 12:26:56 -04:00
Toyosatomimi no Miko
571f6dcdcf Trailing newline 2025-03-29 12:26:07 -04:00
Arthur Sonzogni
4ba7dd2c5e Window: Major crash fix. (#1021)
A patch handling focus was recently merged, but a special condition on
Windows was inverted, causing a segfault.

Bug:https://github.com/ArthurSonzogni/FTXUI/issues/1020
2025-03-29 12:51:08 +01:00
Toyosatomimi no Miko
e57c275512 Make modules opt-in (for pre-C++20 builds not to break) 2025-03-28 08:28:05 -04:00
Miko
1c37cdd192 Merge branch 'ArthurSonzogni:main' into main 2025-03-28 08:22:58 -04:00
ArthurSonzogni
ee24bec3ba v6.0.1
Same as v6.0.0.

Due to a problem tag v6.0.0 was replaced. This isn't a good practice and affect
developers that started using it in the short timeframe. Submitting a new
release with the same content is the best way to fix this.

Bug:https://github.com/ArthurSonzogni/FTXUI/issues/1017
Bug:https://github.com/ArthurSonzogni/FTXUI/issues/1019
2025-03-28 12:08:59 +01:00
Miko
772d4ebeed Merge branch 'ArthurSonzogni:main' into main 2025-03-27 23:56:08 -04:00
Miko
5f5bc9019d Delete modules/CMakeLists.txt
No longer needed
2025-03-28 03:54:48 +00:00
Miko
f87b6a4d12 Update CMakeLists.txt
Directly go to `modules/ftxui`
2025-03-28 03:54:25 +00:00
ArthurSonzogni
327f43b175 v6.0.0 2025-03-27 19:19:44 +01:00
Toyosatomimi no Miko
0d50fa25fe I assume the version produced by CMake was supposed to be bumped up to 6.0.0, fixing this 2025-03-25 19:09:35 -04:00
Toyosatomimi no Miko
730ebeed1d Add my own project (using FTXUI modules) for an example 2025-03-25 18:59:58 -04:00
Toyosatomimi no Miko
69928b374e Add modules support 2025-03-25 18:49:18 -04:00
Arthur Sonzogni
5bf8ee819b Update README.md 2025-03-23 23:55:31 +01:00
Arthur Sonzogni
d5b741b2be Update README.md 2025-03-23 19:26:36 +01:00
ArthurSonzogni
b69e0f8b91 v6.0.0 2025-03-23 18:19:57 +01:00
Arthur Sonzogni
67163c2571 Fix errors. (#1010) 2025-03-23 15:29:01 +01:00
KenReneris
2c9a828402 Add support for italics (#1009)
Co-authored-by: Ken Reneris <ms/devops kreneris@microsoft.com>
Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
2025-03-22 18:03:43 +01:00
ArthurSonzogni
bc682d25a6 Fix compiler nits. 2025-03-22 17:31:27 +01:00
Arthur Sonzogni
96e8b8d92e Implement Node::Select for flexbox. (#977) 2025-03-21 16:15:25 +01:00
ArthurSonzogni
f2fb434e31 Quickfix 2025-03-20 19:59:59 +01:00
Ayaan
b0e087ecef Merge dom and component focus (#978)
Instead of two levels of focus with `focus` and `selected`, use a recursive
level. The components set the one "active" and hbox/vbox/dbox 

Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
2025-03-19 15:33:05 +01:00
Emmanuel Ogie
8519e9b0f3 Add terminal-rain and keywords to README.md (#1003)
Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
2025-03-13 12:24:57 +01:00
Frames
36c669c194 Add a new example in README.md (#1005)
Add project to README.

Add https://github.com/Cyxuan0311/FTB.git
2025-03-13 12:17:48 +01:00
Arthur Sonzogni
d75108e960 Fix linear_gradient float precision bug.
This was reported by:
https://github.com/ArthurSonzogni/FTXUI/issues/998

Indeed, the `t` interpolation factor, which is itself interpolated might become
slightly larger than 1.0. This is due to the float precision.
This was supposedly handled, but there was an off-by-one error in the check.

Along the way, fix a bug found by a fuzzer.

Bug: https://github.com/ArthurSonzogni/FTXUI/issues/998
Fixed: https://github.com/ArthurSonzogni/FTXUI/issues/998
2025-02-10 23:10:27 +01:00
Yazid
15587dad01 Adding BestEdrOfTheMarket in examples (#995) 2025-01-27 18:21:59 +01:00
s1dd
c58a234f05 [DOCS] Add inLimbo as example project (#988) 2025-01-20 15:29:54 +01:00
Jan Stranik
c89569f5a7 Update menu.cpp - remove unused variable (#982) 2025-01-03 18:20:11 +01:00
Arthur Sonzogni
f6a690a942 Fix dev warning. (#980) 2024-12-29 10:24:17 +01:00
Clément Roblot
6fafa2dfed Feature: Selection
Add support for selection content in the dom.
2024-12-27 09:45:13 +01:00
131 changed files with 3786 additions and 628 deletions

7
.bazelrc Normal file
View File

@@ -0,0 +1,7 @@
build --features=layering_check
build --enable_bzlmod
build --enable_platform_specific_config
build:linux --cxxopt=-std=c++20
build:macos --cxxopt=-std=c++20
build:windows --cxxopt=-std:c++20

9
.bcr/README.md Normal file
View File

@@ -0,0 +1,9 @@
# Bazel Central Registry
When the ruleset is released, we want it to be published to the
Bazel Central Registry automatically:
<https://registry.bazel.build>
This folder contains configuration files to automate the publish step.
See <https://github.com/bazel-contrib/publish-to-bcr/blob/main/templates/README.md>
for authoritative documentation about these files.

View File

@@ -0,0 +1,16 @@
{
"homepage": "https://github.com/ArthurSonzogni/FTXUI",
"maintainers": [
{
"name": "Arthur Sonzogni",
"email": "sonzogniarthur@gmail.com",
"github": "ArthurSonzogni",
"github_user_id": 4759106
}
],
"repository": [
"github:ArthurSonzogni/FTXUI"
],
"versions": [],
"yanked_versions": {}
}

36
.bcr/presubmit.yml Normal file
View File

@@ -0,0 +1,36 @@
matrix:
bazel:
- 7.x
- 8.x
- rolling
unix_platform:
- debian11
- ubuntu2204
- macos
- macos_arm64
win_platform:
- windows
tasks:
unix_test:
name: Verify build targets on Unix
platform: ${{ unix_platform }}
bazel: ${{ bazel }}
build_flags:
- --cxxopt=-std=c++20
build_targets:
- '@ftxui//:dom'
- '@ftxui//:component'
- '@ftxui//:screen'
windows_test:
name: Verify build targets
platform: ${{ win_platform }}
bazel: ${{ bazel }}
build_flags:
- --cxxopt=/std:c++20
build_targets:
- '@ftxui//:dom'
- '@ftxui//:component'
- '@ftxui//:screen'

View File

@@ -0,0 +1,5 @@
{
"integrity": "",
"strip_prefix": "",
"url": "https://github.com/ArthurSonzogni/FTXUI/releases/download/{TAG}/source.tar.gz"
}

View File

@@ -1,17 +1,63 @@
name: Build name: Build
on: on:
create: # On new commits to main:
push: push:
branches: branches:
- main - main
# On pull requests:
pull_request: pull_request:
branches: branches:
- main - main
jobs: jobs:
test:
name: "Tests" test_bazel:
name: "Bazel, ${{ matrix.cxx }}, ${{ matrix.os }}"
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
cxx: g++
cc: gcc
- os: ubuntu-latest
cxx: clang++
cc: clang
- os: macos-latest
cxx: g++
cc: gcc
- os: macos-latest
cxx: clang++
cc: clang
- os: windows-latest
cxx: cl
cc: cl
runs-on: ${{ matrix.os }}
steps:
- name: "Checkout repository"
uses: actions/checkout@v3
- name: "Build with Bazel"
env:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
run: bazel build ...
- name: "Tests with Bazel"
env:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
run: bazel test --test_output=all ...
test_cmake:
name: "CMake, ${{ matrix.compiler }}, ${{ matrix.os }}"
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -19,18 +65,16 @@ jobs:
- name: Linux GCC - name: Linux GCC
os: ubuntu-latest os: ubuntu-latest
compiler: gcc compiler: gcc
gcov_executable: gcov
- name: Linux Clang - name: Linux Clang
os: ubuntu-latest os: ubuntu-latest
compiler: llvm compiler: llvm
gcov_executable: "llvm-cov gcov" gcov_executable: "llvm-cov gcov"
# https://github.com/aminya/setup-cpp/issues/246 - name: MacOS clang
#- name: MacOS clang os: macos-latest
#os: macos-latest compiler: llvm
#compiler: llvm gcov_executable: "llvm-cov gcov"
#gcov_executable: "llvm-cov gcov"
- name: Windows MSVC - name: Windows MSVC
os: windows-latest os: windows-latest
@@ -85,7 +129,7 @@ jobs:
ctest -C Debug --rerun-failed --output-on-failure; ctest -C Debug --rerun-failed --output-on-failure;
- name: Unix - coverage - name: Unix - coverage
if: runner.os != 'Windows' if: matrix.gcov_executable != ''
working-directory: ./build working-directory: ./build
run: > run: >
gcovr gcovr
@@ -122,9 +166,55 @@ jobs:
name: ${{ runner.os }}-coverage name: ${{ runner.os }}-coverage
files: ./build/coverage.xml files: ./build/coverage.xml
test_modules:
name: "Test modules"
strategy:
matrix:
include:
- os: ubuntu-latest
compiler: llvm
# TODO add gcc / msvc
runs-on: ${{ matrix.os }}
steps:
- name: "Checkout repository"
uses: actions/checkout@v3
- name: "Setup Cpp"
uses: aminya/setup-cpp@v1
with:
compiler: ${{ matrix.compiler }}
vcvarsall: ${{ contains(matrix.os, 'windows' )}}
cmake: true
ninja: true
clangtidy: false
cppcheck: false
opencppcoverage: false
- name: "Generate ./examples_modules"
run: >
./tools/generate_examples_modules.sh
- name: "Build modules"
run: >
mkdir build;
cd build;
cmake ..
-DCMAKE_GENERATOR=Ninja
-DFTXUI_BUILD_MODULES=ON
-DFTXUI_BUILD_EXAMPLES=ON
-DCMAKE_BUILD_TYPE=Debug
-DFTXUI_BUILD_DOCS=OFF
-DFTXUI_BUILD_TESTS=OFF
-DFTXUI_BUILD_TESTS_FUZZER=OFF
-DFTXUI_ENABLE_INSTALL=ON
-DFTXUI_DEV_WARNINGS=ON ;
cmake --build .
# Create a release on new v* tags # Create a release on new v* tags
release: release:
needs: test needs:
- test_cmake
- test_bazel
if: ${{ github.event_name == 'create' && startsWith(github.ref, 'refs/tags/v') }} if: ${{ github.event_name == 'create' && startsWith(github.ref, 'refs/tags/v') }}
name: "Create release" name: "Create release"
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -140,7 +230,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Build artifact for the release # Build artifact for the release
package: package_compiled:
name: "Build packages" name: "Build packages"
needs: release needs: release
strategy: strategy:
@@ -184,6 +274,29 @@ jobs:
upload_url: ${{ needs.release.outputs.upload_url }} upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: ${{ matrix.asset_path }} asset_path: ${{ matrix.asset_path }}
overwrite: true overwrite: true
# Build "source" artifact for the release. This is the same as the github
# "source" archive, but with a stable URL. This is useful for the Bazel
# Central Repository.
package_source:
name: "Build source package"
needs: release
runs-on: ubuntu-latest
steps:
- name: "Checkout repository"
uses: actions/checkout@v3
- name: "Create source package"
run: >
git archive --format=tar.gz -o source.tar.gz HEAD
- name: "Upload source package"
uses: shogo82148/actions-upload-release-asset@v1
with:
upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: source.tar.gz
overwrite: true
documentation: documentation:
if: github.ref == 'refs/heads/main' if: github.ref == 'refs/heads/main'

View File

@@ -1,76 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '45 22 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'cpp' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

60
.github/workflows/documentation.yaml vendored Normal file
View File

@@ -0,0 +1,60 @@
name: Documentation
on:
# On new commits to main:
push:
branches:
- main
jobs:
documentation:
runs-on: ubuntu-latest
steps:
- name: "Checkout repository"
uses: actions/checkout@v3
- name: "Install cmake"
uses: lukka/get-cmake@latest
- name: "Install emsdk"
uses: mymindstorm/setup-emsdk@v7
- name: "Install Doxygen/Graphviz"
run: >
sudo apt-get update;
sudo apt-get install doxygen graphviz;
- name: "Build documentation"
run: >
mkdir build;
cd build;
emcmake cmake ..
-DCMAKE_BUILD_TYPE=Release
-DFTXUI_BUILD_DOCS=ON
-DFTXUI_BUILD_EXAMPLES=ON
-DFTXUI_BUILD_TESTS=OFF
-DFTXUI_BUILD_TESTS_FUZZER=OFF
-DFTXUI_ENABLE_INSTALL=OFF
-DFTXUI_DEV_WARNINGS=ON ;
cmake --build . --target doc;
cmake --build . ;
rsync -amv
--include='*/'
--include='*.html'
--include='*.css'
--include='*.mjs'
--include='*.js'
--include='*.wasm'
--exclude='*'
examples
doc/doxygen/html;
- name: "Deploy"
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: build/doc/doxygen/html/
enable_jekyll: false
allow_empty_commit: false
force_orphan: true
publish_branch: gh-pages

24
.github/workflows/publish.yaml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: "Publish to Bazel Central Registry"
on:
# Manual kick-off (you type the tag)
workflow_dispatch:
inputs:
tag_name:
description: "Tag to publish"
required: true
type: string
permissions:
contents: write
jobs:
publish:
uses: bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml@v0.0.4
with:
tag_name: ${{ github.event.inputs.tag_name }}
registry_fork: ArthurSonzogni/bazel-central-registry
attest: false
secrets:
publish_token: ${{ secrets.PUBLISH_TOKEN }}

100
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,100 @@
name: Release
on:
# On push to a tag:
push:
tags:
- 'v*'
# On manual trigger:
workflow_dispatch:
permissions:
# Needed to mint attestations
id-token: write
attestations: write
# Needed to upload release assets
contents: write
jobs:
release:
name: "Create release"
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: "Create release"
uses: softprops/action-gh-release@v1
id: create_release
with:
draft: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Build artifact for the release
package_compiled:
name: "Build packages"
needs: release
strategy:
matrix:
include:
- os: ubuntu-latest
asset_path: build/ftxui*Linux*
- os: macos-latest
asset_path: build/ftxui*Darwin*
- os: windows-latest
asset_path: build/ftxui*Win64*
runs-on: ${{ matrix.os }}
steps:
- name: Get number of CPU cores
uses: SimenB/github-actions-cpu-cores@v1
id: cpu-cores
- name: "Checkout repository"
uses: actions/checkout@v3
- name: "Install cmake"
uses: lukka/get-cmake@latest
- name: "Build packages"
run: >
mkdir build;
cd build;
cmake ..
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_BUILD_PARALLEL_LEVEL=${{ steps.cpu-cores.outputs.count }}
-DFTXUI_BUILD_DOCS=OFF
-DFTXUI_BUILD_EXAMPLES=OFF
-DFTXUI_BUILD_TESTS=OFF
-DFTXUI_BUILD_TESTS_FUZZER=OFF
-DFTXUI_ENABLE_INSTALL=ON
-DFTXUI_DEV_WARNINGS=ON ;
cmake --build . --target package;
- uses: shogo82148/actions-upload-release-asset@v1
with:
upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: ${{ matrix.asset_path }}
overwrite: true
# Build "source" artifact for the release. This is the same as the github
# "source" archive, but with a stable URL. This is useful for the Bazel
# Central Repository.
package_source:
name: "Build source package"
needs: release
runs-on: ubuntu-latest
steps:
- name: "Checkout repository"
uses: actions/checkout@v3
- name: "Create source package"
run: >
git archive --format=tar.gz -o source.tar.gz HEAD
- name: "Upload source package"
uses: shogo82148/actions-upload-release-asset@v1
with:
upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: source.tar.gz
overwrite: true

10
.gitignore vendored
View File

@@ -20,6 +20,10 @@ out/
!flake.nix !flake.nix
!ftxui.pc.in !ftxui.pc.in
!iwyu.imp !iwyu.imp
!WORKSPACE.bazel
!BUILD.bazel
!MODULE.bazel
!.bazelrc
# .github directory: # .github directory:
!.github/**/*.yaml !.github/**/*.yaml
@@ -29,6 +33,10 @@ out/
!cmake/**/*.in !cmake/**/*.in
!cmake/**/*.cmake !cmake/**/*.cmake
# bazel directory:
!bazel/**/*.bzl
!.bcr/*
# doc directory: # doc directory:
!doc/**/Doxyfile.in !doc/**/Doxyfile.in
!doc/**/*.txt !doc/**/*.txt
@@ -54,8 +62,10 @@ out/
!include/ftxui/**/*.cpp !include/ftxui/**/*.cpp
# src directory: # src directory:
!src/ftxui/*.cppm
!src/ftxui/**/*.hpp !src/ftxui/**/*.hpp
!src/ftxui/**/*.cpp !src/ftxui/**/*.cpp
!src/ftxui/**/*.cppm
# tools directory: # tools directory:
!tools/**/*.sh !tools/**/*.sh

270
BUILD.bazel Normal file
View File

@@ -0,0 +1,270 @@
# Copyright 2025 Arthur Sonzogni. All rights reserved.
# Use of this source code is governed by the MIT license that can be found in
# the LICENSE file.
# TODO:
# - Build benchmark.
# - Build fuzzers.
# - Build documentation.
# - Enable the two tests timing out.
# - Support WebAssembly
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
load(":bazel/ftxui.bzl", "ftxui_cc_library")
load(":bazel/ftxui.bzl", "generate_examples")
load(":bazel/ftxui.bzl", "windows_copts")
load(":bazel/ftxui.bzl", "pthread_linkopts")
# A meta target depending on all of the ftxui submodules.
# Note that component depends on dom and screen, so ftxui is just an alias for
# component.
# ┌component──┐
# │┌dom──────┐│
# ││┌screen─┐││
# └┴┴───────┴┴┘
alias(name = "ftxui", actual = ":component")
# @ftxui:screen is a module that provides a screen buffer and color management
# for terminal applications. A screen is a 2D array of cells, each cell can
# contain a glyph, a color, and other attributes. The library also provides
# functions to manipulate the screen.
ftxui_cc_library(
name = "screen",
srcs = [
"src/ftxui/screen/box.cpp",
"src/ftxui/screen/color.cpp",
"src/ftxui/screen/color_info.cpp",
"src/ftxui/screen/image.cpp",
"src/ftxui/screen/screen.cpp",
"src/ftxui/screen/string.cpp",
"src/ftxui/screen/string_internal.hpp",
"src/ftxui/screen/terminal.cpp",
"src/ftxui/screen/util.hpp",
],
hdrs = [
"include/ftxui/screen/box.hpp",
"include/ftxui/screen/color.hpp",
"include/ftxui/screen/color_info.hpp",
"include/ftxui/screen/deprecated.hpp",
"include/ftxui/screen/image.hpp",
"include/ftxui/screen/pixel.hpp",
"include/ftxui/screen/screen.hpp",
"include/ftxui/screen/string.hpp",
"include/ftxui/screen/terminal.hpp",
"include/ftxui/util/autoreset.hpp",
"include/ftxui/util/ref.hpp",
],
)
# @ftxui:dom is a library that provides a way to create and manipulate a
# "document" that can be rendered to a screen. The document is a tree of nodes.
# Nodes can be text, layouts, or various decorators. Users needs to compose
# nodes to create a document. A document is responsive to the size of the
# screen.
ftxui_cc_library(
name = "dom",
srcs = [
"src/ftxui/dom/automerge.cpp",
"src/ftxui/dom/blink.cpp",
"src/ftxui/dom/bold.cpp",
"src/ftxui/dom/border.cpp",
"src/ftxui/dom/box_helper.cpp",
"src/ftxui/dom/box_helper.hpp",
"src/ftxui/dom/canvas.cpp",
"src/ftxui/dom/clear_under.cpp",
"src/ftxui/dom/color.cpp",
"src/ftxui/dom/composite_decorator.cpp",
"src/ftxui/dom/dbox.cpp",
"src/ftxui/dom/dim.cpp",
"src/ftxui/dom/flex.cpp",
"src/ftxui/dom/flexbox.cpp",
"src/ftxui/dom/flexbox_config.cpp",
"src/ftxui/dom/flexbox_helper.cpp",
"src/ftxui/dom/flexbox_helper.hpp",
"src/ftxui/dom/focus.cpp",
"src/ftxui/dom/frame.cpp",
"src/ftxui/dom/gauge.cpp",
"src/ftxui/dom/graph.cpp",
"src/ftxui/dom/gridbox.cpp",
"src/ftxui/dom/hbox.cpp",
"src/ftxui/dom/hyperlink.cpp",
"src/ftxui/dom/inverted.cpp",
"src/ftxui/dom/italic.cpp",
"src/ftxui/dom/linear_gradient.cpp",
"src/ftxui/dom/node.cpp",
"src/ftxui/dom/node_decorator.cpp",
"src/ftxui/dom/node_decorator.hpp",
"src/ftxui/dom/paragraph.cpp",
"src/ftxui/dom/reflect.cpp",
"src/ftxui/dom/scroll_indicator.cpp",
"src/ftxui/dom/selection.cpp",
"src/ftxui/dom/selection_style.cpp",
"src/ftxui/dom/separator.cpp",
"src/ftxui/dom/size.cpp",
"src/ftxui/dom/spinner.cpp",
"src/ftxui/dom/strikethrough.cpp",
"src/ftxui/dom/table.cpp",
"src/ftxui/dom/text.cpp",
"src/ftxui/dom/underlined.cpp",
"src/ftxui/dom/underlined_double.cpp",
"src/ftxui/dom/util.cpp",
"src/ftxui/dom/vbox.cpp",
],
hdrs = [
"include/ftxui/dom/canvas.hpp",
"include/ftxui/dom/deprecated.hpp",
"include/ftxui/dom/direction.hpp",
"include/ftxui/dom/elements.hpp",
"include/ftxui/dom/flexbox_config.hpp",
"include/ftxui/dom/linear_gradient.hpp",
"include/ftxui/dom/node.hpp",
"include/ftxui/dom/requirement.hpp",
"include/ftxui/dom/selection.hpp",
"include/ftxui/dom/table.hpp",
"include/ftxui/dom/take_any_args.hpp",
],
deps = [":screen"],
)
# @ftxui:component is a library to create "dynamic" component renderering and
# updating a ftxui::dom document on the screen. It is a higher level API than
# ftxui:dom.
#
# The module is required if your program needs to respond to user input. It
# defines a set of ftxui::Component. These components can be utilized to
# navigate using the arrow keys and/or cursor. There are several builtin widgets
# like checkbox/inputbox/etc to interact with. You can combine them, or even
# define your own custom components.
ftxui_cc_library(
name = "component",
srcs = [
"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",
"src/ftxui/component/hoverable.cpp",
"src/ftxui/component/input.cpp",
"src/ftxui/component/loop.cpp",
"src/ftxui/component/maybe.cpp",
"src/ftxui/component/menu.cpp",
"src/ftxui/component/modal.cpp",
"src/ftxui/component/radiobox.cpp",
"src/ftxui/component/renderer.cpp",
"src/ftxui/component/resizable_split.cpp",
"src/ftxui/component/screen_interactive.cpp",
"src/ftxui/component/slider.cpp",
"src/ftxui/component/terminal_input_parser.cpp",
"src/ftxui/component/terminal_input_parser.hpp",
"src/ftxui/component/util.cpp",
"src/ftxui/component/window.cpp",
# Private header from ftxui:dom.
"src/ftxui/dom/node_decorator.hpp",
# Private header from ftxui:screen.
"src/ftxui/screen/string_internal.hpp",
"src/ftxui/screen/util.hpp",
],
hdrs = [
"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/loop.hpp",
"include/ftxui/component/mouse.hpp",
"include/ftxui/component/receiver.hpp",
"include/ftxui/component/screen_interactive.hpp",
"include/ftxui/component/task.hpp",
],
linkopts = pthread_linkopts(),
deps = [
":dom",
":screen",
],
)
# FTXUI's tests
cc_test(
name = "tests",
testonly = True,
srcs = [
"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/container_test.cpp",
"src/ftxui/component/dropdown_test.cpp",
"src/ftxui/component/hoverable_test.cpp",
"src/ftxui/component/input_test.cpp",
"src/ftxui/component/menu_test.cpp",
"src/ftxui/component/modal_test.cpp",
"src/ftxui/component/radiobox_test.cpp",
"src/ftxui/component/receiver_test.cpp",
"src/ftxui/component/resizable_split_test.cpp",
"src/ftxui/component/slider_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/hyperlink_test.cpp",
"src/ftxui/dom/italic_test.cpp",
"src/ftxui/dom/linear_gradient_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",
"src/ftxui/util/ref_test.cpp",
# Private header from ftxui:screen for string_test.cpp.
"src/ftxui/screen/string_internal.hpp",
# Private header from ftxui::component for
# terminal_input_parser_test.cpp.
"src/ftxui/component/terminal_input_parser.hpp",
# Private header from ftxui::dom for
# flexbox_helper_test.cpp.
"src/ftxui/dom/flexbox_helper.hpp",
# TODO: Enable the two tests timing out with Bazel:
# - "src/ftxui/component/screen_interactive_test.cpp",
# - "src/ftxui/dom/selection_test.cpp",
],
includes = [
"include",
"src",
],
copts = windows_copts(),
deps = [
":screen",
":dom",
":component",
"@googletest//:gtest",
"@googletest//:gtest_main",
],
)
generate_examples()

View File

@@ -1,8 +1,93 @@
Changelog Changelog
========= =========
current (development) Development
--------------------- -----------
### Build
- Feature: Support C++20 modules.
This requires:
- Using the Ninja or MSVC generator
- A recent Clang/GCC/MSVC compiler.
- Cmake 3.28 or higher.
Usage:
```cpp
import ftxui;
import ftxui.component;
import ftxui.dom;
import ftxui.screen;
```
Thanks @mikomikotaishi for PR #1015.
Future release
=======
6.1.9 (2025-05-07
### Build
If all goes well (pending), ftxui should appear in the Bazel central repository.
It can be imported into your project using the following lines:
**MODULE.bazel**
```bazel
bazel_dep(name = "ftxui", version = "6.1.9")
```
Thanks @robinlinden and @kcc for the reviews.
### dom
- Bugfix: Restore the `dbox` behavior from ftxui 5.0.0. To apply bgcolor
blending between the two layers, a new `dboxBlend` will be added.
6.1.8 (2025-05-01)
------------------
### Build
- Feature: Support `bazel` build system. See #1032.
Proposed by Kostya Serebryany @kcc
**BUILD.bazel**
```bazel
deps = [
// Depend on the whole library:
"@ftxui//:ftxui",
// Choose a specific submodule:
"@ftxui//:component",
"@ftxui//:dom",
"@ftxui//:screen",
]
```
### Component
- Bugfix: Fix a crash with ResizeableSplit. See #1023.
- Clamp screen size to terminal size.
- Disallow `ResizeableSplit` with negative size.
### Dom
- Bugfix: Disallow specifying a negative size constraint. See #1023.
6.0.2 (2025-03-30)
-----
### Component
- BugFix: Fix major crash on Windows affecting all components. See #1020
- BugFix: Fix focusRelative.
6.0.1 (2025-03-28)
-----
Same as v6.0.0.
Due to a problem tag v6.0.0 was replaced. This isn't a good practice and affect
developers that started using it in the short timeframe. Submitting a new
release with the same content is the best way to fix this.
See #1017 and #1019.
6.0.0 (2025-03-23)
-----
### Component ### Component
- Feature: Add support for raw input. Allowing more keys to be detected. - Feature: Add support for raw input. Allowing more keys to be detected.
@@ -16,6 +101,9 @@ current (development)
- Feature: Add support for `Input`'s insert mode. Add `InputOption::insert` - Feature: Add support for `Input`'s insert mode. Add `InputOption::insert`
option. Added by @mingsheng13. option. Added by @mingsheng13.
- Feature: Add `DropdownOption` to configure the dropdown. See #826. - Feature: Add `DropdownOption` to configure the dropdown. See #826.
- Feature: Add support for Selection. Thanks @clement-roblot. See #926.
- See `ScreenInteractive::GetSelection()`.
- See `ScreenInteractive::SelectionChange(...)` listener.
- Bugfix/Breaking change: `Mouse transition`: - Bugfix/Breaking change: `Mouse transition`:
- Detect when the mouse move, as opposed to being pressed. - Detect when the mouse move, as opposed to being pressed.
The Mouse::Moved motion was added. The Mouse::Moved motion was added.
@@ -41,14 +129,37 @@ current (development)
See #932 See #932
- Feature: Add `SliderOption::on_change`. This allows to set a callback when the - Feature: Add `SliderOption::on_change`. This allows to set a callback when the
slider value changes. See #938. slider value changes. See #938.
- Bugfix: Handle `Dropdown` with no entries.
- Bugfix: Fix crash in `LinearGradient` due to float precision and an off-by-one
mistake. See #998.
### Dom ### Dom
- Feature: Add `italic` decorator. For instance:
```cpp
auto italic_text = text("Italic text") | italic;
```
```cpp
auto italic_text = italic(text("Italic text"));
```
Proposed by @kenReneris in #1009.
- Feature: Add `hscroll_indicator`. It display an horizontal indicator - Feature: Add `hscroll_indicator`. It display an horizontal indicator
reflecting the current scroll position. Proposed by @ibrahimnasson in reflecting the current scroll position. Proposed by @ibrahimnasson in
[issue 752](https://github.com/ArthurSonzogni/FTXUI/issues/752) [issue 752](https://github.com/ArthurSonzogni/FTXUI/issues/752)
- Feature: Add `extend_beyond_screen` option to `Dimension::Fit(..)`, allowing - Feature: Add `extend_beyond_screen` option to `Dimension::Fit(..)`, allowing
the element to be larger than the screen. Proposed by @LordWhiro. See #572 and the element to be larger than the screen. Proposed by @LordWhiro. See #572 and
#949. #949.
- Feature: Add support for Selection. Thanks @clement-roblot. See #926.
- See `selectionColor` decorator.
- See `selectionBackgroundColor` decorator.
- See `selectionForegroundColor` decorator.
- See `selectionStyle(style)` decorator.
- See `selectionStyleReset` decorator.
- Breaking change: Change how "focus"/"select" are handled. This fixes the
behavior.
- Breaking change: `Component::OnRender()` becomes the method to override to
render a component. This replaces `Component::Render()` that is still in use
to call the rendering method on the children. This change allows to fix a
couple of issues around focus handling.
### Screen ### Screen
- Feature: Add `Box::IsEmpty()`. - Feature: Add `Box::IsEmpty()`.

View File

@@ -1,20 +1,26 @@
cmake_minimum_required(VERSION 3.12) option(FTXUI_BUILD_DOCS "Set to ON to build docs" OFF)
option(FTXUI_BUILD_EXAMPLES "Set to ON to build examples" OFF)
option(FTXUI_BUILD_MODULES "Build the C++20 modules" OFF)
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_CLANG_TIDY "Execute clang-tidy" OFF)
option(FTXUI_DEV_WARNINGS "Enable more compiler warnings and warnings as errors" OFF)
option(FTXUI_ENABLE_COVERAGE "Execute code coverage" OFF)
option(FTXUI_ENABLE_INSTALL "Generate the install target" ON)
option(FTXUI_QUIET "Set to ON for FTXUI to be quiet" OFF)
if (FTXUI_BUILD_MODULES)
cmake_minimum_required(VERSION 3.28.2)
else()
cmake_minimum_required(VERSION 3.12)
endif()
project(ftxui project(ftxui
LANGUAGES CXX LANGUAGES CXX
VERSION 5.0.0 VERSION 6.1.9
DESCRIPTION "C++ Functional Terminal User Interface." DESCRIPTION "C++ Functional Terminal User Interface."
) )
option(FTXUI_QUIET "Set to ON for FTXUI to be quiet" OFF)
option(FTXUI_BUILD_EXAMPLES "Set to ON to build examples" OFF)
option(FTXUI_BUILD_DOCS "Set to ON to build docs" OFF)
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)
option(FTXUI_DEV_WARNINGS "Enable more compiler warnings and warnings as errors" OFF)
set(FTXUI_MICROSOFT_TERMINAL_FALLBACK_HELP_TEXT "On windows, assume the \ 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 \ terminal used will be one of Microsoft and use a set of reasonnable fallback \
@@ -56,11 +62,12 @@ add_library(dom
include/ftxui/dom/flexbox_config.hpp include/ftxui/dom/flexbox_config.hpp
include/ftxui/dom/node.hpp include/ftxui/dom/node.hpp
include/ftxui/dom/requirement.hpp include/ftxui/dom/requirement.hpp
include/ftxui/dom/selection.hpp
include/ftxui/dom/take_any_args.hpp include/ftxui/dom/take_any_args.hpp
src/ftxui/dom/automerge.cpp src/ftxui/dom/automerge.cpp
src/ftxui/dom/selection_style.cpp
src/ftxui/dom/blink.cpp src/ftxui/dom/blink.cpp
src/ftxui/dom/bold.cpp src/ftxui/dom/bold.cpp
src/ftxui/dom/hyperlink.cpp
src/ftxui/dom/border.cpp src/ftxui/dom/border.cpp
src/ftxui/dom/box_helper.cpp src/ftxui/dom/box_helper.cpp
src/ftxui/dom/box_helper.hpp src/ftxui/dom/box_helper.hpp
@@ -81,13 +88,16 @@ add_library(dom
src/ftxui/dom/graph.cpp src/ftxui/dom/graph.cpp
src/ftxui/dom/gridbox.cpp src/ftxui/dom/gridbox.cpp
src/ftxui/dom/hbox.cpp src/ftxui/dom/hbox.cpp
src/ftxui/dom/hyperlink.cpp
src/ftxui/dom/inverted.cpp src/ftxui/dom/inverted.cpp
src/ftxui/dom/italic.cpp
src/ftxui/dom/linear_gradient.cpp src/ftxui/dom/linear_gradient.cpp
src/ftxui/dom/node.cpp src/ftxui/dom/node.cpp
src/ftxui/dom/node_decorator.cpp src/ftxui/dom/node_decorator.cpp
src/ftxui/dom/paragraph.cpp src/ftxui/dom/paragraph.cpp
src/ftxui/dom/reflect.cpp src/ftxui/dom/reflect.cpp
src/ftxui/dom/scroll_indicator.cpp src/ftxui/dom/scroll_indicator.cpp
src/ftxui/dom/selection.cpp
src/ftxui/dom/separator.cpp src/ftxui/dom/separator.cpp
src/ftxui/dom/size.cpp src/ftxui/dom/size.cpp
src/ftxui/dom/spinner.cpp src/ftxui/dom/spinner.cpp
@@ -172,6 +182,13 @@ include(cmake/iwyu.cmake)
include(cmake/ftxui_export.cmake) include(cmake/ftxui_export.cmake)
include(cmake/ftxui_install.cmake) include(cmake/ftxui_install.cmake)
include(cmake/ftxui_package.cmake) include(cmake/ftxui_package.cmake)
include(cmake/ftxui_modules.cmake)
add_subdirectory(examples)
add_subdirectory(doc) add_subdirectory(doc)
add_subdirectory(examples)
# You can generate ./examples_modules/ by running
# ./tools/generate_examples_modules.sh
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/examples_modules/CMakeLists.txt")
add_subdirectory(examples_modules)
endif()

13
MODULE.bazel Normal file
View File

@@ -0,0 +1,13 @@
# FTXUI module.
module(
name = "ftxui",
version = "6.1.9",
compatibility_level = 6,
)
# Build dependencies.
bazel_dep(name = "rules_cc", version = "0.1.1")
bazel_dep(name = "platforms", version = "0.0.10")
# Test dependencies.
bazel_dep(name = "googletest", version = "1.14.0.bcr.1", dev_dependency = True)

View File

@@ -40,17 +40,27 @@ A simple cross-platform C++ library for terminal based user interfaces!
* Support for animations. [Demo 1](https://arthursonzogni.github.io/FTXUI/examples/?file=component/menu_underline_animated_gallery), [Demo 2](https://arthursonzogni.github.io/FTXUI/examples/?file=component/button_style) * Support for animations. [Demo 1](https://arthursonzogni.github.io/FTXUI/examples/?file=component/menu_underline_animated_gallery), [Demo 2](https://arthursonzogni.github.io/FTXUI/examples/?file=component/button_style)
* Support for drawing. [Demo](https://arthursonzogni.github.io/FTXUI/examples/?file=component/canvas_animated) * Support for drawing. [Demo](https://arthursonzogni.github.io/FTXUI/examples/?file=component/canvas_animated)
* No dependencies * No dependencies
* Module support
* **Cross platform**: Linux/MacOS (main target), WebAssembly, Windows (Thanks to contributors!). * **Cross platform**: Linux/MacOS (main target), WebAssembly, Windows (Thanks to contributors!).
* Learn by [examples](#documentation), and [tutorials](#documentation) * Learn by [examples](#documentation), and [tutorials](#documentation)
* Multiple packages: CMake [FetchContent]([https://bewagner.net/programming/2020/05/02/cmake-fetchcontent/](https://cmake.org/cmake/help/latest/module/FetchContent.html)) (preferred), vcpkg, pkgbuild, conan. * Multiple packages:
- CMake [FetchContent]([https://bewagner.net/programming/2020/05/02/cmake-fetchcontent/](https://cmake.org/cmake/help/latest/module/FetchContent.html)) (preferred)
- [Bazel](https://registry.bazel.build/modules/ftxui)
- [vcpkg](https://vcpkg.link/ports/ftxui)
- [Conan](https://conan.io/center/recipes/ftxui) [Debian package](https://tracker.debian.org/pkg/ftxui)
- [Ubuntu package](https://launchpad.net/ubuntu/+source/ftxui)
- [Arch Linux](https://aur.archlinux.org/packages/ftxui/)
- [OpenSUSE](https://build.opensuse.org/package/show/devel:libraries:c_c++/ftxui)
* Good practices: documentation, tests, fuzzers, performance tests, automated CI, automated packaging, etc... * Good practices: documentation, tests, fuzzers, performance tests, automated CI, automated packaging, etc...
## Documentation ## Documentation
- [Starter example project](https://github.com/ArthurSonzogni/ftxui-starter) - [Starter CMake](https://github.com/ArthurSonzogni/ftxui-starter)
- [Starter Bazel](https://github.com/ArthurSonzogni/ftxui-bazel)
- [Documentation](https://arthursonzogni.github.io/FTXUI/) - [Documentation](https://arthursonzogni.github.io/FTXUI/)
- [Examples (WebAssembly)](https://arthursonzogni.github.io/FTXUI/examples/) - [Examples (WebAssembly)](https://arthursonzogni.github.io/FTXUI/examples/)
- [Build using CMake](https://arthursonzogni.github.io/FTXUI/#build-cmake) - [Build using CMake](https://arthursonzogni.github.io/FTXUI/#build-cmake)
- [Build using Bazel](https://arthursonzogni.github.io/FTXUI/#build-bazel)
## Example ## Example
~~~cpp ~~~cpp
@@ -109,6 +119,7 @@ Element can become flexible using the the `flex` decorator.
An element can be decorated using the functions: An element can be decorated using the functions:
- `bold` - `bold`
- `italic`
- `dim` - `dim`
- `inverted` - `inverted`
- `underlined` - `underlined`
@@ -342,6 +353,13 @@ Feel free to add your projects here:
- [Fallout terminal hacking](https://github.com/gshigin/yet-another-fallout-terminal-hacking-game) - [Fallout terminal hacking](https://github.com/gshigin/yet-another-fallout-terminal-hacking-game)
- [Lazylist](https://github.com/zhuyongqi9/lazylist) - [Lazylist](https://github.com/zhuyongqi9/lazylist)
- [TUISIC](https://github.com/Dark-Kernel/tuisic) - [TUISIC](https://github.com/Dark-Kernel/tuisic)
- [inLimbo](https://github.com/nots1dd/inLimbo)
- [BestEdrOfTheMarket](https://github.com/Xacone/BestEdrOfTheMarket)
- [terminal-rain](https://github.com/Oakamoore/terminal-rain)
- [keywords](https://github.com/Oakamoore/keywords) ([Play web version :heart:](https://oakamoore.itch.io/keywords))
- [FTB - tertminal file browser](https://github.com/Cyxuan0311/FTB)
- [openJuice](https://github.com/mikomikotaishi/openJuice)
- [SHOOT!](https://github.com/ShingZhanho/ENGG1340-Project-25Spring)
### [cpp-best-practices/game_jam](https://github.com/cpp-best-practices/game_jam) ### [cpp-best-practices/game_jam](https://github.com/cpp-best-practices/game_jam)
@@ -358,38 +376,68 @@ Several games using the FTXUI have been made during the Game Jam:
- [smoothlife](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/smoothlife.md) - [smoothlife](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/smoothlife.md)
- [Consu](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/consu.md) - [Consu](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/consu.md)
## Utilization ## Build using CMake
It is **highly** recommended to use CMake FetchContent to depend on FTXUI so you may specify which commit you would like to depend on. It is **highly** recommended to use CMake FetchContent to depend on FTXUI so you may specify which commit you would like to depend on.
```cmake ```cmake
include(FetchContent) include(FetchContent)
FetchContent_Declare(ftxui FetchContent_Declare(ftxui
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
GIT_TAG v5.0.0 GIT_TAG v6.1.9
) )
FetchContent_MakeAvailable(ftxui)
FetchContent_GetProperties(ftxui) target_link_libraries(your_target PRIVATE
if(NOT ftxui_POPULATED) # Chose a submodule
FetchContent_Populate(ftxui) ftxui::component
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL) ftxui::dom
endif() ftxui::screen
)
``` ```
# Build using Bazel
**MODULE.bazel**
```starlark
bazel_dep(
name = "ftxui",
version = "v6.1.9",
)
```
**BUILD.bazel**
```starlark
cc_binary(
name = "your_target",
srcs = ["your_source.cc"],
deps = [
"@ftxui//:ftxui_component",
"@ftxui//:ftxui_dom",
"@ftxui//:ftxui_screen",
],
)
```
# Build with something else:
If you don't, FTXUI may be used from the following packages: If you don't, FTXUI may be used from the following packages:
- [vcpkg](https://vcpkgx.com/details.html?package=ftxui) - CMake [FetchContent]([https://bewagner.net/programming/2020/05/02/cmake-fetchcontent/](https://cmake.org/cmake/help/latest/module/FetchContent.html)) (preferred),
- [Arch Linux PKGBUILD](https://aur.archlinux.org/packages/ftxui-git/). - [Bazel](https://registry.bazel.build/modules/ftxui),
- [conan.io](https://conan.io/center/ftxui) - [vcpkg](https://vcpkg.link/ports/ftxui),
- [openSUSE](https://build.opensuse.org/package/show/devel:libraries:c_c++/ftxui) - [Conan](https://conan.io/center/recipes/ftxui)
- - [Debian package](https://tracker.debian.org/pkg/ftxui),
[![Packaging status](https://repology.org/badge/vertical-allrepos/ftxui.svg)](https://repology.org/project/ftxui/versions) - [Ubuntu package](https://launchpad.net/ubuntu/+source/ftxui),
- [Arch Linux](https://aur.archlinux.org/packages/ftxui/),
- [OpenSUSE](https://build.opensuse.org/package/show/devel:libraries:c_c++/ftxui),
[![Packaging status](https://repology.org/badge/vertical-allrepos/libftxui.svg)](https://repology.org/project/libftxui/versions)
If you choose to build and link FTXUI yourself, `ftxui-component` must be first in the linking order relative to the other FTXUI libraries, i.e. If you choose to build and link FTXUI yourself, `ftxui-component` must be first in the linking order relative to the other FTXUI libraries, i.e.
```bash ```bash
g++ . . . -lftxui-component -lftxui-dom -lftxui-screen . . . g++ . . . -lftxui-component -lftxui-dom -lftxui-screen . . .
``` ```
To build FTXUI with modules, ensure that you are using a generator like Ninja or Visual Studio that supports modules, and pass the flag `FTXUI_BUILD_MODULES`.
## Contributors ## Contributors

4
WORKSPACE.bazel Normal file
View File

@@ -0,0 +1,4 @@
# Copyright 2025 Arthur Sonzogni. All rights reserved.
# Use of this source code is governed by the MIT license that can be found in
# the LICENSE file.
workspace(name = "ftxui")

104
bazel/ftxui.bzl Normal file
View File

@@ -0,0 +1,104 @@
# ftxui_common.bzl
load("@rules_cc//cc:defs.bzl", "cc_library")
load("@rules_cc//cc:defs.bzl", "cc_binary")
# Microsoft terminal is a bit buggy ¯\_(ツ)_/¯ and MSVC uses bad defaults.
def windows_copts():
MSVC_COPTS = [
# Microsoft Visual Studio must decode sources files as UTF-8.
"/utf-8",
# Microsoft Visual Studio must interpret the codepoint using unicode.
"/DUNICODE",
"/D_UNICODE",
# Fallback for Microsoft Terminal.
# This
# - Replace missing font symbols by others.
# - Reduce screen position pooling frequency to deals against a Microsoft
# race condition. This was fixed in 2020, but clients never not updated.
# - https://github.com/microsoft/terminal/pull/7583
# - https://github.com/ArthurSonzogni/FTXUI/issues/136
"/DFTXUI_MICROSOFT_TERMINAL_FALLBACK",
]
WINDOWS_COPTS = [
# Fallback for Microsoft Terminal.
# This
# - Replace missing font symbols by others.
# - Reduce screen position pooling frequency to deals against a Microsoft
# race condition. This was fixed in 2020, but clients are still using
# old versions.
# - https://github.com/microsoft/terminal/pull/7583
# - https://github.com/ArthurSonzogni/FTXUI/issues/136
"-DFTXUI_MICROSOFT_TERMINAL_FALLBACK",
];
return select({
# MSVC:
"@rules_cc//cc/compiler:msvc-cl": MSVC_COPTS,
"@rules_cc//cc/compiler:clang-cl": MSVC_COPTS,
"@platforms//os:windows": WINDOWS_COPTS,
"//conditions:default": [],
})
def pthread_linkopts():
return select({
# With MSVC, threading is already built-in (you don't need -pthread.
"@rules_cc//cc/compiler:msvc-cl": [],
"@rules_cc//cc/compiler:clang-cl": [],
"@rules_cc//cc/compiler:clang": ["-pthread"],
"@rules_cc//cc/compiler:gcc": ["-pthread"],
"//conditions:default": ["-pthread"],
})
def ftxui_cc_library(
name,
srcs = [],
hdrs = [],
linkopts = [],
deps = []):
cc_library(
name = name,
srcs = srcs,
hdrs = hdrs,
linkopts = linkopts,
deps = deps,
strip_include_prefix = "",
include_prefix = "",
includes = [
"include",
"src",
],
copts = windows_copts(),
visibility = ["//visibility:public"],
)
# Compile all the examples in the examples/ directory.
# This is useful to check the Bazel is always synchronized against CMake
# definitions.
def generate_examples():
cpp_files = native.glob(["examples/**/*.cpp"])
for src in cpp_files:
# Skip failing examples due to the color_info_sorted_2d.ipp dependency.
if src == "examples/component/homescreen.cpp" or \
src == "examples/dom/color_info_palette256.cpp" or \
src == "examples/dom/color_gallery.cpp":
continue
# Turn "examples/component/button.cpp" → "example_component_button"
name = src.replace("/", "_").replace(".cpp", "")
cc_binary(
name = name,
srcs = [src],
deps = [
":component",
":dom",
":screen",
],
copts = windows_copts(),
)

View File

@@ -5,13 +5,14 @@ function(ftxui_message msg)
endfunction() endfunction()
ftxui_message("┌─ FTXUI options ─────────────────────") ftxui_message("┌─ FTXUI options ─────────────────────")
ftxui_message("│ FTXUI_ENABLE_INSTALL : ${FTXUI_ENABLE_INSTALL}")
ftxui_message("│ FTXUI_BUILD_EXAMPLES : ${FTXUI_BUILD_EXAMPLES}")
ftxui_message("│ FTXUI_QUIET : ${FTXUI_QUIET}")
ftxui_message("│ FTXUI_BUILD_DOCS : ${FTXUI_BUILD_DOCS}") ftxui_message("│ FTXUI_BUILD_DOCS : ${FTXUI_BUILD_DOCS}")
ftxui_message("│ FTXUI_BUILD_EXAMPLES : ${FTXUI_BUILD_EXAMPLES}")
ftxui_message("│ FTXUI_BUILD_MODULES : ${FTXUI_BUILD_MODULES}")
ftxui_message("│ FTXUI_BUILD_TESTS : ${FTXUI_BUILD_TESTS}") ftxui_message("│ FTXUI_BUILD_TESTS : ${FTXUI_BUILD_TESTS}")
ftxui_message("│ FTXUI_BUILD_TESTS_FUZZER : ${FTXUI_BUILD_TESTS_FUZZER}") ftxui_message("│ FTXUI_BUILD_TESTS_FUZZER : ${FTXUI_BUILD_TESTS_FUZZER}")
ftxui_message("│ FTXUI_ENABLE_COVERAGE : ${FTXUI_ENABLE_COVERAGE}")
ftxui_message("│ FTXUI_DEV_WARNINGS : ${FTXUI_DEV_WARNINGS}")
ftxui_message("│ FTXUI_CLANG_TIDY : ${FTXUI_CLANG_TIDY}") ftxui_message("│ FTXUI_CLANG_TIDY : ${FTXUI_CLANG_TIDY}")
ftxui_message("│ FTXUI_DEV_WARNINGS : ${FTXUI_DEV_WARNINGS}")
ftxui_message("│ FTXUI_ENABLE_COVERAGE : ${FTXUI_ENABLE_COVERAGE}")
ftxui_message("│ FTXUI_ENABLE_INSTALL : ${FTXUI_ENABLE_INSTALL}")
ftxui_message("│ FTXUI_QUIET : ${FTXUI_QUIET}")
ftxui_message("└─────────────────────────────────────") ftxui_message("└─────────────────────────────────────")

81
cmake/ftxui_modules.cmake Normal file
View File

@@ -0,0 +1,81 @@
if (NOT FTXUI_BUILD_MODULES)
return()
endif()
add_library(ftxui-modules)
target_sources(ftxui-modules
PUBLIC FILE_SET CXX_MODULES FILES
src/ftxui/component.cppm
src/ftxui/component/Animation.cppm
src/ftxui/component/CapturedMouse.cppm
src/ftxui/component/Component.cppm
src/ftxui/component/ComponentBase.cppm
src/ftxui/component/ComponentOptions.cppm
src/ftxui/component/Event.cppm
src/ftxui/component/Loop.cppm
src/ftxui/component/Mouse.cppm
src/ftxui/component/Receiver.cppm
src/ftxui/component/ScreenInteractive.cppm
src/ftxui/component/Task.cppm
src/ftxui/dom.cppm
src/ftxui/dom/Canvas.cppm
src/ftxui/dom/Deprecated.cppm
src/ftxui/dom/Direction.cppm
src/ftxui/dom/Elements.cppm
src/ftxui/dom/FlexboxConfig.cppm
src/ftxui/dom/LinearGradient.cppm
src/ftxui/dom/Node.cppm
src/ftxui/dom/Requirement.cppm
src/ftxui/dom/Selection.cppm
src/ftxui/dom/Table.cppm
src/ftxui/screen.cppm
src/ftxui/screen/Box.cppm
src/ftxui/screen/Color.cppm
src/ftxui/screen/ColorInfo.cppm
src/ftxui/screen/Deprecated.cppm
src/ftxui/screen/Image.cppm
src/ftxui/screen/Pixel.cppm
src/ftxui/screen/Screen.cppm
src/ftxui/screen/String.cppm
src/ftxui/screen/Terminal.cppm
src/ftxui/util.cppm
src/ftxui/util/AutoReset.cppm
src/ftxui/util/Ref.cppm
)
target_link_libraries(ftxui-modules
PUBLIC
ftxui::screen
ftxui::dom
ftxui::component
)
target_compile_features(ftxui-modules PUBLIC cxx_std_20)
if (CMAKE_COMPILER_IS_GNUCXX)
target_compile_options(${name} PUBLIC -fmodules-ts)
endif ()
add_library(ftxui::modules ALIAS ftxui-modules)
if(FTXUI_ENABLE_INSTALL)
include(GNUInstallDirs)
install(TARGETS ftxui-modules
EXPORT ftxui-targets
FILE_SET CXX_MODULES
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ftxui
FILE_SET HEADERS
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ftxui
INCLUDES
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ftxui
)
install(EXPORT ftxui-targets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ftxui
CXX_MODULES_DIRECTORY ${CMAKE_INSTALL_LIBDIR}/cmake/ftxui
)
install(FILES my_package-config.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ftxui
)
endif()

View File

@@ -83,10 +83,6 @@ function(ftxui_set_options library)
target_compile_options(${library} PRIVATE "-Wpedantic") target_compile_options(${library} PRIVATE "-Wpedantic")
target_compile_options(${library} PRIVATE "-Wshadow") target_compile_options(${library} PRIVATE "-Wshadow")
target_compile_options(${library} PRIVATE "-Wunused") target_compile_options(${library} PRIVATE "-Wunused")
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(${library} PRIVATE "-Wuseless-cast")
endif()
endif() endif()
endif() endif()

View File

@@ -13,6 +13,7 @@ add_executable(ftxui-tests
src/ftxui/component/component_test.cpp src/ftxui/component/component_test.cpp
src/ftxui/component/component_test.cpp src/ftxui/component/component_test.cpp
src/ftxui/component/container_test.cpp src/ftxui/component/container_test.cpp
src/ftxui/component/dropdown_test.cpp
src/ftxui/component/hoverable_test.cpp src/ftxui/component/hoverable_test.cpp
src/ftxui/component/input_test.cpp src/ftxui/component/input_test.cpp
src/ftxui/component/menu_test.cpp src/ftxui/component/menu_test.cpp
@@ -38,8 +39,10 @@ add_executable(ftxui-tests
src/ftxui/dom/gridbox_test.cpp src/ftxui/dom/gridbox_test.cpp
src/ftxui/dom/hbox_test.cpp src/ftxui/dom/hbox_test.cpp
src/ftxui/dom/hyperlink_test.cpp src/ftxui/dom/hyperlink_test.cpp
src/ftxui/dom/italic_test.cpp
src/ftxui/dom/linear_gradient_test.cpp src/ftxui/dom/linear_gradient_test.cpp
src/ftxui/dom/scroll_indicator_test.cpp src/ftxui/dom/scroll_indicator_test.cpp
src/ftxui/dom/selection_test.cpp
src/ftxui/dom/separator_test.cpp src/ftxui/dom/separator_test.cpp
src/ftxui/dom/spinner_test.cpp src/ftxui/dom/spinner_test.cpp
src/ftxui/dom/table_test.cpp src/ftxui/dom/table_test.cpp

View File

@@ -81,7 +81,7 @@ include(FetchContent)
set(FETCHCONTENT_UPDATES_DISCONNECTED TRUE) set(FETCHCONTENT_UPDATES_DISCONNECTED TRUE)
FetchContent_Declare(ftxui FetchContent_Declare(ftxui
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
GIT_TAG main # Important: Specify a version or a commit hash here. GIT_TAG 6.1.9
) )
FetchContent_MakeAvailable(ftxui) FetchContent_MakeAvailable(ftxui)
@@ -94,13 +94,33 @@ target_link_libraries(ftxui-starter
) )
``` ```
## Build ### Using Bazel {#build-bazel}
```bash See [ftxui module](https://registry.bazel.build/modules/ftxui) from the Bazel
mkdir build && cd build Central Registry.
cmake ..
make See also this [starter](https://github.com/ArthurSonzogni/ftxui-bazel) project.
./main
**Module.bazel**
```starlark
bazel_dep(
name = "ftxui",
version = "6.1.9",
)
```
**BUILD.bazel**
```starlark
cc_binary(
name = "main",
srcs = ["main.cpp"],
deps = [
# Choose one of the following:
"@ftxui//:dom",
"@ftxui//:screen",
"@ftxui//:component",
],
)
``` ```
# List of modules. {#modules} # List of modules. {#modules}
@@ -123,8 +143,8 @@ The project is comprised of 3 modules:
This is the visual element of the program. It defines a `ftxui::Screen`, which This is the visual element of the program. It defines a `ftxui::Screen`, which
is a grid of `ftxui::Pixel`. A Pixel represents a Unicode character and its is a grid of `ftxui::Pixel`. A Pixel represents a Unicode character and its
associated style (bold, colors, etc.). The screen can be printed as a string associated style (bold, italic, colors, etc.). The screen can be printed as a
using `ftxui::Screen::ToString()`. The following example highlights this string using `ftxui::Screen::ToString()`. The following example highlights this
process: process:
```cpp ```cpp
@@ -476,10 +496,11 @@ See [demo](https://arthursonzogni.github.io/FTXUI/examples/?file=component/linea
## Style {#dom-style} ## Style {#dom-style}
In addition to colored text and colored backgrounds. Many terminals support text In addition to colored text and colored backgrounds. Many terminals support text
effects such as: `bold`, `dim`, `underlined`, `inverted`, `blink`. effects such as: `bold`, `italic`, `dim`, `underlined`, `inverted`, `blink`.
```cpp ```cpp
Element bold(Element); Element bold(Element);
Element italic(Element);
Element dim(Element); Element dim(Element);
Element inverted(Element); Element inverted(Element);
Element underlined(Element); Element underlined(Element);

View File

@@ -18,6 +18,7 @@ example(focus_cursor)
example(gallery) example(gallery)
example(homescreen) example(homescreen)
example(input) example(input)
example(input_in_frame)
example(input_style) example(input_style)
example(linear_gradient_gallery) example(linear_gradient_gallery)
example(maybe) example(maybe)
@@ -39,6 +40,7 @@ example(radiobox_in_frame)
example(renderer) example(renderer)
example(resizable_split) example(resizable_split)
example(scrollbar) example(scrollbar)
example(selection)
example(slider) example(slider)
example(slider_direction) example(slider_direction)
example(slider_rgb) example(slider_rgb)

View File

@@ -18,7 +18,7 @@ using namespace ftxui;
// We are using `center` to center the text inside the button, then `border` to // We are using `center` to center the text inside the button, then `border` to
// add a border around the button, and finally `flex` to make the button fill // add a border around the button, and finally `flex` to make the button fill
// the available space. // the available space.
ButtonOption Style() { ButtonOption ButtonStyle() {
auto option = ButtonOption::Animated(); auto option = ButtonOption::Animated();
option.transform = [](const EntryState& s) { option.transform = [](const EntryState& s) {
auto element = text(s.label); auto element = text(s.label);
@@ -33,19 +33,20 @@ ButtonOption Style() {
int main() { int main() {
int value = 50; int value = 50;
// clang-format off
auto btn_dec_01 = Button("-1", [&] { value -= 1; }, Style());
auto btn_inc_01 = Button("+1", [&] { value += 1; }, Style());
auto btn_dec_10 = Button("-10", [&] { value -= 10; }, Style());
auto btn_inc_10 = Button("+10", [&] { value += 10; }, Style());
// clang-format on
// The tree of components. This defines how to navigate using the keyboard. // The tree of components. This defines how to navigate using the keyboard.
// The selected `row` is shared to get a grid layout.
int row = 0;
auto buttons = Container::Vertical({ auto buttons = Container::Vertical({
Container::Horizontal({btn_dec_01, btn_inc_01}, &row) | flex, Container::Horizontal({
Container::Horizontal({btn_dec_10, btn_inc_10}, &row) | flex, Button(
"-1", [&] { value--; }, ButtonStyle()),
Button(
"+1", [&] { value++; }, ButtonStyle()),
}) | flex,
Container::Horizontal({
Button(
"-10", [&] { value -= 10; }, ButtonStyle()),
Button(
"+10", [&] { value += 10; }, ButtonStyle()),
}) | flex,
}); });
// Modify the way to render them on screen: // Modify the way to render them on screen:
@@ -58,7 +59,7 @@ int main() {
flex | border; flex | border;
}); });
auto screen = ScreenInteractive::FitComponent(); auto screen = ScreenInteractive::Fullscreen();
screen.Loop(component); screen.Loop(component);
return 0; return 0;
} }

View File

@@ -1,30 +1,38 @@
// Copyright 2021 Arthur Sonzogni. All rights reserved. // Copyright 2021 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.
#include <memory> // for allocator, __shared_ptr_access #include <array> // for array
#include <string> // for string, basic_string, operator+, to_string #include <iostream>
#include <vector> // for vector #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/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Input, Renderer, Vertical #include "ftxui/component/component.hpp" // for Checkbox, Renderer, Vertical
#include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive #include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
#include "ftxui/dom/elements.hpp" // for operator|, Element, size, border, frame, vscroll_indicator, HEIGHT, LESS_THAN #include "ftxui/dom/elements.hpp" // for operator|, Element, size, border, frame, vscroll_indicator, HEIGHT, LESS_THAN
using namespace ftxui;
int main() { int main() {
using namespace ftxui; bool download = false;
bool upload = false;
bool ping = false;
Component input_list = Container::Vertical({}); auto container = Container::Vertical({
std::vector<std::string> items(100, ""); Checkbox("Download", &download),
for (size_t i = 0; i < items.size(); ++i) { Checkbox("Upload", &upload),
input_list->Add(Input(&(items[i]), "placeholder " + std::to_string(i))); Checkbox("Ping", &ping),
}
auto renderer = Renderer(input_list, [&] {
return input_list->Render() | vscroll_indicator | frame | border |
size(HEIGHT, LESS_THAN, 10);
}); });
auto screen = ScreenInteractive::TerminalOutput(); auto screen = ScreenInteractive::FitComponent();
screen.Loop(renderer); screen.Loop(container);
std::cout << "---" << std::endl;
std::cout << "Download: " << download << std::endl;
std::cout << "Upload: " << upload << std::endl;
std::cout << "Ping: " << ping << std::endl;
std::cout << "---" << std::endl;
return 0;
} }

View File

@@ -97,7 +97,25 @@ int main() {
}); });
sliders = Wrap("Slider", sliders); sliders = Wrap("Slider", sliders);
// -- Layout ----------------------------------------------------------------- // A large text:
auto lorel_ipsum = Renderer([] {
return vbox({
text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. "),
text("Sed do eiusmod tempor incididunt ut labore et dolore magna "
"aliqua. "),
text("Ut enim ad minim veniam, quis nostrud exercitation ullamco "
"laboris nisi ut aliquip ex ea commodo consequat. "),
text("Duis aute irure dolor in reprehenderit in voluptate velit esse "
"cillum dolore eu fugiat nulla pariatur. "),
text("Excepteur sint occaecat cupidatat non proident, sunt in culpa "
"qui officia deserunt mollit anim id est laborum. "),
});
});
lorel_ipsum = Wrap("Lorel Ipsum", lorel_ipsum);
// -- Layout
// -----------------------------------------------------------------
auto layout = Container::Vertical({ auto layout = Container::Vertical({
menu, menu,
toggle, toggle,
@@ -106,6 +124,7 @@ int main() {
input, input,
sliders, sliders,
button, button,
lorel_ipsum,
}); });
auto component = Renderer(layout, [&] { auto component = Renderer(layout, [&] {
@@ -123,6 +142,8 @@ int main() {
sliders->Render(), sliders->Render(),
separator(), separator(),
button->Render(), button->Render(),
separator(),
lorel_ipsum->Render(),
}) | }) |
xflex | size(WIDTH, GREATER_THAN, 40) | border; xflex | size(WIDTH, GREATER_THAN, 40) | border;
}); });

View File

@@ -424,7 +424,7 @@ int main() {
auto paragraph_renderer_left = Renderer([&] { auto paragraph_renderer_left = Renderer([&] {
std::string str = std::string str =
"Lorem Ipsum is simply dummy text of the printing and typesetting " "Lorem Ipsum is simply dummy text of the printing and typesetting "
"industry. Lorem Ipsum has been the industry's standard dummy text " "industry.\nLorem Ipsum has been the industry's standard dummy text "
"ever since the 1500s, when an unknown printer took a galley of type " "ever since the 1500s, when an unknown printer took a galley of type "
"and scrambled it to make a type specimen book."; "and scrambled it to make a type specimen book.";
return vbox({ return vbox({

View File

@@ -0,0 +1,30 @@
// 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.
#include <memory> // for allocator, __shared_ptr_access
#include <string> // for string, basic_string, operator+, to_string
#include <vector> // for vector
#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Input, 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 operator|, Element, size, border, frame, vscroll_indicator, HEIGHT, LESS_THAN
int main() {
using namespace ftxui;
Component input_list = Container::Vertical({});
std::vector<std::string> items(100, "");
for (size_t i = 0; i < items.size(); ++i) {
input_list->Add(Input(&(items[i]), "placeholder " + std::to_string(i)));
}
auto renderer = Renderer(input_list, [&] {
return input_list->Render() | vscroll_indicator | frame | border |
size(HEIGHT, LESS_THAN, 10);
});
auto screen = ScreenInteractive::TerminalOutput();
screen.Loop(renderer);
}

View File

@@ -0,0 +1,87 @@
// Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#include <string> // for char_traits, operator+, string, basic_string
#include "ftxui/component/component.hpp" // for Input, Renderer, Vertical
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for InputOption
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
#include "ftxui/dom/elements.hpp" // for text, hbox, separator, Element, operator|, vbox, border
#include "ftxui/util/ref.hpp" // for Ref
using namespace ftxui;
Element LoremIpsum() {
return vbox({
text("FTXUI: A powerful library for building user interfaces."),
text("Enjoy a rich set of components and a declarative style."),
text("Create beautiful and responsive UIs with minimal effort."),
text("Join the community and experience the power of FTXUI."),
});
}
int main() {
auto screen = ScreenInteractive::TerminalOutput();
auto quit =
Button("Quit", screen.ExitLoopClosure(), ButtonOption::Animated());
int selection_change_counter = 0;
std::string selection_content = "";
screen.SelectionChange([&] {
selection_change_counter++;
selection_content = screen.GetSelection();
});
// The components:
auto renderer = Renderer(quit, [&] {
return vbox({
text("Select changed: " + std::to_string(selection_change_counter) +
" times"),
text("Currently selected: "),
paragraph(selection_content) | vscroll_indicator | frame | border |
size(HEIGHT, EQUAL, 10),
window(text("Horizontal split"), hbox({
LoremIpsum(),
separator(),
LoremIpsum(),
separator(),
LoremIpsum(),
})),
window(text("Vertical split"), vbox({
LoremIpsum(),
separator(),
LoremIpsum(),
separator(),
LoremIpsum(),
})),
window(text("Grid split with different style"),
vbox({
hbox({
LoremIpsum(),
separator(),
LoremIpsum() //
| selectionBackgroundColor(Color::Yellow) //
| selectionColor(Color::Black) //
| selectionStyleReset,
separator(),
LoremIpsum() | selectionColor(Color::Blue),
}),
separator(),
hbox({
LoremIpsum() | selectionColor(Color::Red),
separator(),
LoremIpsum() | selectionStyle([](Pixel& pixel) {
pixel.underlined_double = true;
}),
separator(),
LoremIpsum(),
}),
})),
quit->Render(),
});
});
screen.Loop(renderer);
}

View File

@@ -19,7 +19,7 @@ using namespace ftxui;
int main() { int main() {
auto screen = ScreenInteractive::TerminalOutput(); auto screen = ScreenInteractive::TerminalOutput();
std::array<int, 30> values; std::array<int, 30> values;
for (int i = 0; i < values.size(); ++i) { for (size_t i = 0; i < values.size(); ++i) {
values[i] = 50 + 20 * std::sin(i * 0.3); values[i] = 50 + 20 * std::sin(i * 0.3);
} }

View File

@@ -29,6 +29,7 @@ example(style_dim)
example(style_gallery) example(style_gallery)
example(style_hyperlink) example(style_hyperlink)
example(style_inverted) example(style_inverted)
example(style_italic)
example(style_strikethrough) example(style_strikethrough)
example(style_underlined) example(style_underlined)
example(style_underlined_double) example(style_underlined_double)

View File

@@ -12,7 +12,6 @@
int main() { int main() {
using namespace ftxui; using namespace ftxui;
int saturation = 255;
Elements red_line; Elements red_line;
Elements green_line; Elements green_line;
Elements blue_line; Elements blue_line;

View File

@@ -10,6 +10,7 @@
#include <memory> // for shared_ptr #include <memory> // for shared_ptr
#include <string> // for operator<<, string #include <string> // for operator<<, string
#include <thread> // for sleep_for #include <thread> // for sleep_for
#include <utility> // for ignore
#include <vector> // for vector #include <vector> // for vector
#include "ftxui/dom/node.hpp" // for Render #include "ftxui/dom/node.hpp" // for Render
@@ -49,6 +50,7 @@ int main() {
std::string reset_position; std::string reset_position;
for (int i = 0;; ++i) { for (int i = 0;; ++i) {
std::ignore = i;
auto document = hbox({ auto document = hbox({
vbox({ vbox({
graph(std::ref(my_graph)), graph(std::ref(my_graph)),

View File

@@ -15,6 +15,7 @@ int main() {
hbox({ hbox({
text("normal") , text(" ") , text("normal") , text(" ") ,
text("bold") | bold , text(" ") , text("bold") | bold , text(" ") ,
text("italic") | italic , text(" ") ,
text("dim") | dim , text(" ") , text("dim") | dim , text(" ") ,
text("inverted") | inverted , text(" ") , text("inverted") | inverted , text(" ") ,
text("underlined") | underlined , text(" ") , text("underlined") | underlined , text(" ") ,

View File

@@ -0,0 +1,23 @@
// Copyright 2025 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#include <ftxui/dom/elements.hpp> // for text, operator|, inverted, Fit, hbox, Element
#include <ftxui/screen/screen.hpp> // for Full, Screen
#include <memory> // for allocator
#include "ftxui/dom/node.hpp" // for Render
#include "ftxui/screen/color.hpp" // for ftxui
int main() {
using namespace ftxui;
auto document = hbox({
text("This text is "),
text("italic") | italic,
text(". Do you like it?"),
});
auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
Render(screen, document);
screen.Print();
return 0;
}

View File

@@ -50,7 +50,10 @@ class ComponentBase {
void DetachAllChildren(); void DetachAllChildren();
// Renders the component. // Renders the component.
virtual Element Render(); Element Render();
// Override this function modify how `Render` works.
virtual Element OnRender();
// Handles an event. // Handles an event.
// By default, reduce on children with a lazy OR. // By default, reduce on children with a lazy OR.
@@ -94,6 +97,7 @@ class ComponentBase {
private: private:
ComponentBase* parent_ = nullptr; ComponentBase* parent_ = nullptr;
bool in_render = false;
}; };
} // namespace ftxui } // namespace ftxui

View File

@@ -16,6 +16,7 @@
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
#include "ftxui/component/event.hpp" // for Event #include "ftxui/component/event.hpp" // for Event
#include "ftxui/component/task.hpp" // for Task, Closure #include "ftxui/component/task.hpp" // for Task, Closure
#include "ftxui/dom/selection.hpp" // for SelectionOption
#include "ftxui/screen/screen.hpp" // for Screen #include "ftxui/screen/screen.hpp" // for Screen
namespace ftxui { namespace ftxui {
@@ -68,6 +69,10 @@ class ScreenInteractive : public Screen {
void ForceHandleCtrlC(bool force); void ForceHandleCtrlC(bool force);
void ForceHandleCtrlZ(bool force); void ForceHandleCtrlZ(bool force);
// Selection API.
std::string GetSelection();
void SelectionChange(std::function<void()> callback);
private: private:
void ExitNow(); void ExitNow();
@@ -82,6 +87,8 @@ class ScreenInteractive : public Screen {
void RunOnceBlocking(Component component); void RunOnceBlocking(Component component);
void HandleTask(Component component, Task& task); void HandleTask(Component component, Task& task);
bool HandleSelection(bool handled, Event event);
void RefreshSelection();
void Draw(Component component); void Draw(Component component);
void ResetCursorPosition(); void ResetCursorPosition();
@@ -129,6 +136,22 @@ class ScreenInteractive : public Screen {
// The style of the cursor to restore on exit. // The style of the cursor to restore on exit.
int cursor_reset_shape_ = 1; int cursor_reset_shape_ = 1;
// Selection API:
CapturedMouse selection_pending_;
struct SelectionData {
int start_x = -1;
int start_y = -1;
int end_x = -2;
int end_y = -2;
bool empty = true;
bool operator==(const SelectionData& other) const;
bool operator!=(const SelectionData& other) const;
};
SelectionData selection_data_;
SelectionData selection_data_previous_;
std::unique_ptr<Selection> selection_;
std::function<void()> selection_on_change_;
friend class Loop; friend class Loop;
public: public:

View File

@@ -95,6 +95,7 @@ Element canvas(std::function<void(Canvas&)>);
// -- Decorator --- // -- Decorator ---
Element bold(Element); Element bold(Element);
Element dim(Element); Element dim(Element);
Element italic(Element);
Element inverted(Element); Element inverted(Element);
Element underlined(Element); Element underlined(Element);
Element underlinedDouble(Element); Element underlinedDouble(Element);
@@ -113,6 +114,11 @@ Decorator focusPositionRelative(float x, float y);
Element automerge(Element child); Element automerge(Element child);
Decorator hyperlink(std::string link); Decorator hyperlink(std::string link);
Element hyperlink(std::string link, Element child); Element hyperlink(std::string link, Element child);
Element selectionStyleReset(Element);
Decorator selectionColor(Color foreground);
Decorator selectionBackgroundColor(Color foreground);
Decorator selectionForegroundColor(Color foreground);
Decorator selectionStyle(std::function<void(Pixel&)> style);
// --- Layout is // --- Layout is
// Horizontal, Vertical or stacked set of elements. // Horizontal, Vertical or stacked set of elements.
@@ -156,7 +162,7 @@ Element frame(Element);
Element xframe(Element); Element xframe(Element);
Element yframe(Element); Element yframe(Element);
Element focus(Element); Element focus(Element);
Element select(Element); Element select(Element e); // Deprecated - Alias for focus.
// --- Cursor --- // --- Cursor ---
// Those are similar to `focus`, but also change the shape of the cursor. // Those are similar to `focus`, but also change the shape of the cursor.

View File

@@ -8,6 +8,7 @@
#include <vector> // for vector #include <vector> // for vector
#include "ftxui/dom/requirement.hpp" // for Requirement #include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/dom/selection.hpp" // for Selection
#include "ftxui/screen/box.hpp" // for Box #include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/screen.hpp" #include "ftxui/screen/screen.hpp"
@@ -40,9 +41,15 @@ class Node {
// Propagated from Parents to Children. // Propagated from Parents to Children.
virtual void SetBox(Box box); virtual void SetBox(Box box);
// Step 3: Draw this element. // Step 3: (optional) Selection
// Propagated from Parents to Children.
virtual void Select(Selection& selection);
// Step 4: Draw this element.
virtual void Render(Screen& screen); virtual void Render(Screen& screen);
virtual std::string GetSelectedContent(Selection& selection);
// Layout may not resolve within a single iteration for some elements. This // Layout may not resolve within a single iteration for some elements. This
// allows them to request additionnal iterations. This signal must be // allows them to request additionnal iterations. This signal must be
// forwarded to children at least once. // forwarded to children at least once.
@@ -52,6 +59,8 @@ class Node {
}; };
virtual void Check(Status* status); virtual void Check(Status* status);
friend void Render(Screen& screen, Node* node, Selection& selection);
protected: protected:
Elements children_; Elements children_;
Requirement requirement_; Requirement requirement_;
@@ -60,6 +69,10 @@ class Node {
void Render(Screen& screen, const Element& element); void Render(Screen& screen, const Element& element);
void Render(Screen& screen, Node* node); void Render(Screen& screen, Node* node);
void Render(Screen& screen, Node* node, Selection& selection);
std::string GetNodeSelectedContent(Screen& screen,
Node* node,
Selection& selection);
} // namespace ftxui } // namespace ftxui

View File

@@ -5,8 +5,10 @@
#define FTXUI_DOM_REQUIREMENT_HPP #define FTXUI_DOM_REQUIREMENT_HPP
#include "ftxui/screen/box.hpp" #include "ftxui/screen/box.hpp"
#include "ftxui/screen/screen.hpp"
namespace ftxui { namespace ftxui {
class Node;
struct Requirement { struct Requirement {
// The required size to fully draw the element. // The required size to fully draw the element.
@@ -20,13 +22,28 @@ struct Requirement {
int flex_shrink_y = 0; int flex_shrink_y = 0;
// Focus management to support the frame/focus/select element. // Focus management to support the frame/focus/select element.
enum Selection { struct Focused {
NORMAL = 0, bool enabled = false;
SELECTED = 1, Box box;
FOCUSED = 2, Node* node = nullptr;
Screen::Cursor::Shape cursor_shape = Screen::Cursor::Shape::Hidden;
// Internal for interactions with components.
bool component_active = false;
// Return whether this requirement should be preferred over the other.
bool Prefer(const Focused& other) const {
if (!other.enabled) {
return false;
}
if (!enabled) {
return true;
}
return other.component_active && !component_active;
}
}; };
Selection selection = NORMAL; Focused focused;
Box selected_box;
}; };
} // namespace ftxui } // namespace ftxui

View File

@@ -0,0 +1,50 @@
// Copyright 2024 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#ifndef FTXUI_DOM_SELECTION_HPP
#define FTXUI_DOM_SELECTION_HPP
#include <functional>
#include <sstream>
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/pixel.hpp" // for Pixel
namespace ftxui {
/// @brief Represent a selection in the terminal.
class Selection {
public:
Selection(); // Empty selection.
Selection(int start_x, int start_y, int end_x, int end_y);
const Box& GetBox() const;
Selection SaturateHorizontal(Box box);
Selection SaturateVertical(Box box);
bool IsEmpty() const { return empty_; }
void AddPart(const std::string& part, int y, int left, int right);
std::string GetParts() { return parts_.str(); }
private:
Selection(int start_x, int start_y, int end_x, int end_y, Selection* parent);
const int start_x_ = 0;
const int start_y_ = 0;
const int end_x_ = 0;
const int end_y_ = 0;
const Box box_ = {};
Selection* const parent_ = this;
const bool empty_ = true;
std::stringstream parts_;
// The position of the last inserted part.
int x_ = 0;
int y_ = 0;
};
} // namespace ftxui
#endif /* end of include guard: FTXUI_DOM_SELECTION_HPP */

View File

@@ -14,6 +14,7 @@ struct Box {
static auto Intersection(Box a, Box b) -> Box; static auto Intersection(Box a, Box b) -> Box;
static auto Union(Box a, Box b) -> Box; static auto Union(Box a, Box b) -> Box;
void Shift(int x, int y);
bool Contain(int x, int y) const; bool Contain(int x, int y) const;
bool IsEmpty() const; bool IsEmpty() const;
bool operator==(const Box& other) const; bool operator==(const Box& other) const;

View File

@@ -17,6 +17,7 @@ struct Pixel {
: blink(false), : blink(false),
bold(false), bold(false),
dim(false), dim(false),
italic(false),
inverted(false), inverted(false),
underlined(false), underlined(false),
underlined_double(false), underlined_double(false),
@@ -27,6 +28,7 @@ struct Pixel {
bool blink : 1; bool blink : 1;
bool bold : 1; bool bold : 1;
bool dim : 1; bool dim : 1;
bool italic : 1;
bool inverted : 1; bool inverted : 1;
bool underlined : 1; bool underlined : 1;
bool underlined_double : 1; bool underlined_double : 1;

View File

@@ -4,12 +4,14 @@
#ifndef FTXUI_SCREEN_SCREEN_HPP #ifndef FTXUI_SCREEN_SCREEN_HPP
#define FTXUI_SCREEN_SCREEN_HPP #define FTXUI_SCREEN_SCREEN_HPP
#include <cstdint> // for uint8_t #include <cstdint> // for uint8_t
#include <string> // for string, basic_string, allocator #include <functional> // for function
#include <vector> // for vector #include <string> // for string, basic_string, allocator
#include <vector> // for vector
#include "ftxui/screen/image.hpp" // for Pixel, Image #include "ftxui/screen/image.hpp" // for Pixel, Image
#include "ftxui/screen/terminal.hpp" // for Dimensions #include "ftxui/screen/terminal.hpp" // for Dimensions
#include "ftxui/util/autoreset.hpp" // for AutoReset
namespace ftxui { namespace ftxui {
@@ -67,9 +69,18 @@ class Screen : public Image {
uint8_t RegisterHyperlink(const std::string& link); uint8_t RegisterHyperlink(const std::string& link);
const std::string& Hyperlink(uint8_t id) const; const std::string& Hyperlink(uint8_t id) const;
using SelectionStyle = std::function<void(Pixel&)>;
const SelectionStyle& GetSelectionStyle() const;
void SetSelectionStyle(SelectionStyle decorator);
protected: protected:
Cursor cursor_; Cursor cursor_;
std::vector<std::string> hyperlinks_ = {""}; std::vector<std::string> hyperlinks_ = {""};
// The current selection style. This is overridden by various dom elements.
SelectionStyle selection_style_ = [](Pixel& pixel) {
pixel.inverted ^= true;
};
}; };
} // namespace ftxui } // namespace ftxui

18
src/ftxui/component.cppm Normal file
View File

@@ -0,0 +1,18 @@
/**
* @file component.cppm
* @brief Module file for FTXUI component operations.
*/
export module ftxui.component;
export import ftxui.component.Animation;
export import ftxui.component.CapturedMouse;
export import ftxui.component.Component;
export import ftxui.component.ComponentBase;
export import ftxui.component.ComponentOptions;
export import ftxui.component.Event;
export import ftxui.component.Loop;
export import ftxui.component.Mouse;
export import ftxui.component.Receiver;
export import ftxui.component.ScreenInteractive;
export import ftxui.component.Task;

View File

@@ -0,0 +1,66 @@
/**
* @file Animation.cppm
* @brief Module file for the Animation namespace of the Component module
*/
module;
#include <ftxui/component/animation.hpp>
export module ftxui.component.Animation;
/**
* @namespace ftxui::animation
* @brief The FTXUI ftxui::animation:: namespace
*/
export namespace ftxui::animation {
using ftxui::animation::RequestAnimationFrame;
using ftxui::animation::Clock;
using ftxui::animation::TimePoint;
using ftxui::animation::Duration;
using ftxui::animation::Params;
/**
* @namespace easing
* @brief The FTXUI sf::animation::easing:: namespace
*/
namespace easing {
using ftxui::animation::easing::Function;
using ftxui::animation::easing::Linear;
using ftxui::animation::easing::QuadraticIn;
using ftxui::animation::easing::QuadraticOut;
using ftxui::animation::easing::QuadraticInOut;
using ftxui::animation::easing::CubicIn;
using ftxui::animation::easing::CubicOut;
using ftxui::animation::easing::CubicInOut;
using ftxui::animation::easing::QuarticIn;
using ftxui::animation::easing::QuarticOut;
using ftxui::animation::easing::QuarticInOut;
using ftxui::animation::easing::QuinticIn;
using ftxui::animation::easing::QuinticOut;
using ftxui::animation::easing::QuinticInOut;
using ftxui::animation::easing::SineIn;
using ftxui::animation::easing::SineOut;
using ftxui::animation::easing::SineInOut;
using ftxui::animation::easing::CircularIn;
using ftxui::animation::easing::CircularOut;
using ftxui::animation::easing::CircularInOut;
using ftxui::animation::easing::ExponentialIn;
using ftxui::animation::easing::ExponentialOut;
using ftxui::animation::easing::ExponentialInOut;
using ftxui::animation::easing::ElasticIn;
using ftxui::animation::easing::ElasticOut;
using ftxui::animation::easing::ElasticInOut;
using ftxui::animation::easing::BackIn;
using ftxui::animation::easing::BackOut;
using ftxui::animation::easing::BackInOut;
using ftxui::animation::easing::BounceIn;
using ftxui::animation::easing::BounceOut;
using ftxui::animation::easing::BounceInOut;
}
using ftxui::animation::Animator;
}

View File

@@ -0,0 +1,18 @@
/**
* @file CapturedMouse.cppm
* @brief Module file for the CapturedMouseInterface class of the Component module
*/
module;
#include <ftxui/component/captured_mouse.hpp>
export module ftxui.component.CapturedMouse;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::CapturedMouseInterface;
}

View File

@@ -0,0 +1,61 @@
/**
* @file Component.cppm
* @brief Module file for the Component classes of the Component module
*/
module;
#include <ftxui/component/component.hpp>
export module ftxui.component.Component;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::ButtonOption;
using ftxui::CheckboxOption;
using ftxui::Event;
using ftxui::InputOption;
using ftxui::MenuOption;
using ftxui::RadioboxOption;
using ftxui::MenuEntryOption;
using ftxui::Make;
using ftxui::ComponentDecorator;
using ftxui::ElementDecorator;
using ftxui::operator|;
using ftxui::operator|=;
namespace Container {
using ftxui::Container::Vertical;
using ftxui::Container::Horizontal;
using ftxui::Container::Tab;
using ftxui::Container::Stacked;
}
using ftxui::Button;
using ftxui::Checkbox;
using ftxui::Input;
using ftxui::Menu;
using ftxui::MenuEntry;
using ftxui::Radiobox;
using ftxui::Dropdown;
using ftxui::Toggle;
using ftxui::Slider;
using ftxui::ResizableSplit;
using ftxui::ResizableSplitLeft;
using ftxui::ResizableSplitRight;
using ftxui::ResizableSplitTop;
using ftxui::ResizableSplitBottom;
using ftxui::Renderer;
using ftxui::CatchEvent;
using ftxui::Maybe;
using ftxui::Modal;
using ftxui::Collapsible;
using ftxui::Hoverable;
using ftxui::Window;
}

View File

@@ -0,0 +1,28 @@
/**
* @file ComponentBase.cppm
* @brief Module file for the ComponentBase class of the Component module
*/
module;
#include <ftxui/component/component_base.hpp>
export module ftxui.component.ComponentBase;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::Delegate;
using ftxui::Focus;
using ftxui::Event;
namespace animation {
using ftxui::animation::Params;
}
using ftxui::ComponentBase;
using ftxui::Component;
using ftxui::Components;
}

View File

@@ -0,0 +1,33 @@
/**
* @file ComponentOptions.cppm
* @brief Module file for options for the Component class of the Component module
*/
module;
#include <ftxui/component/component_options.hpp>
export module ftxui.component.ComponentOptions;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::EntryState;
using ftxui::UnderlineOption;
using ftxui::AnimatedColorOption;
using ftxui::AnimatedColorsOption;
using ftxui::MenuEntryOption;
using ftxui::MenuOption;
using ftxui::ButtonOption;
using ftxui::CheckboxOption;
using ftxui::InputState;
using ftxui::InputOption;
using ftxui::RadioboxOption;
using ftxui::ResizableSplitOption;
using ftxui::SliderOption;
using ftxui::WindowRenderState;
using ftxui::WindowOptions;
using ftxui::DropdownOption;
}

View File

@@ -0,0 +1,21 @@
/**
* @file Event.cppm
* @brief Module file for the Event struct of the Component module
*/
module;
#include <ftxui/component/event.hpp>
export module ftxui.component.Event;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::ScreenInteractive;
using ftxui::ComponentBase;
using ftxui::Event;
}

View File

@@ -0,0 +1,22 @@
/**
* @file Loop.cppm
* @brief Module file for the Loop class of the Component module
*/
module;
#include <ftxui/component/loop.hpp>
export module ftxui.component.Loop;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::ComponentBase;
using ftxui::Component;
using ftxui::ScreenInteractive;
using ftxui::Loop;
}

View File

@@ -0,0 +1,18 @@
/**
* @file Mouse.cppm
* @brief Module file for the Mouse struct of the Component module
*/
module;
#include <ftxui/component/mouse.hpp>
export module ftxui.component.Mouse;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::Mouse;
}

View File

@@ -0,0 +1,22 @@
/**
* @file Receiver.cppm
* @brief Module file for the Receiver class of the Component module
*/
module;
#include <ftxui/component/receiver.hpp>
export module ftxui.component.Receiver;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::SenderImpl;
using ftxui::ReceiverImpl;
using ftxui::Sender;
using ftxui::Receiver;
using ftxui::MakeReceiver;
}

View File

@@ -0,0 +1,25 @@
/**
* @file ScreenInteractive.cppm
* @brief Module file for the ScreenInteractive class of the Component module
*/
module;
#include <ftxui/component/screen_interactive.hpp>
export module ftxui.component.ScreenInteractive;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::ComponentBase;
using ftxui::Loop;
using ftxui::Event;
using ftxui::Component;
using ftxui::Screen;
using ftxui::ScreenInteractivePrivate;
using ftxui::ScreenInteractive;
}

View File

@@ -0,0 +1,20 @@
/**
* @file Task.cppm
* @brief Module file for the Task class of the Component module
*/
module;
#include <ftxui/component/task.hpp>
export module ftxui.component.Task;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::AnimationTask;
using ftxui::Closure;
using ftxui::Task;
}

View File

@@ -37,7 +37,7 @@ class ButtonBase : public ComponentBase, public ButtonOption {
explicit ButtonBase(ButtonOption option) : ButtonOption(std::move(option)) {} explicit ButtonBase(ButtonOption option) : ButtonOption(std::move(option)) {}
// Component implementation: // Component implementation:
Element Render() override { Element OnRender() override {
const bool active = Active(); const bool active = Active();
const bool focused = Focused(); const bool focused = Focused();
const bool focused_or_hover = focused || mouse_hover_; const bool focused_or_hover = focused || mouse_hover_;
@@ -47,14 +47,16 @@ class ButtonBase : public ComponentBase, public ButtonOption {
SetAnimationTarget(target); SetAnimationTarget(target);
} }
auto focus_management = focused ? focus : active ? select : nothing;
const EntryState state{ const EntryState state{
*label, false, active, focused_or_hover, Index(), *label, false, active, focused_or_hover, Index(),
}; };
auto element = (transform ? transform : DefaultTransform) // auto element = (transform ? transform : DefaultTransform) //
(state); (state);
return element | AnimatedColorStyle() | focus_management | reflect(box_); element |= AnimatedColorStyle();
element |= focus;
element |= reflect(box_);
return element;
} }
Decorator AnimatedColorStyle() { Decorator AnimatedColorStyle() {

View File

@@ -23,16 +23,17 @@ class CheckboxBase : public ComponentBase, public CheckboxOption {
private: private:
// Component implementation. // Component implementation.
Element Render() override { Element OnRender() override {
const bool is_focused = Focused(); const bool is_focused = Focused();
const bool is_active = Active(); const bool is_active = Active();
auto focus_management = is_focused ? focus : is_active ? select : nothing;
auto entry_state = EntryState{ auto entry_state = EntryState{
*label, *checked, is_active, is_focused || hovered_, -1, *label, *checked, is_active, is_focused || hovered_, -1,
}; };
auto element = (transform ? transform : CheckboxOption::Simple().transform)( auto element = (transform ? transform : CheckboxOption::Simple().transform)(
entry_state); entry_state);
return element | focus_management | reflect(box_); element |= focus;
element |= reflect(box_);
return element;
} }
bool OnEvent(Event event) override { bool OnEvent(Event event) override {
@@ -69,7 +70,6 @@ class CheckboxBase : public ComponentBase, public CheckboxOption {
event.mouse().motion == Mouse::Pressed) { event.mouse().motion == Mouse::Pressed) {
*checked = !*checked; *checked = !*checked;
on_change(); on_change();
TakeFocus();
return true; return true;
} }

View File

@@ -15,6 +15,8 @@
#include "ftxui/component/event.hpp" // for Event #include "ftxui/component/event.hpp" // for Event
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive #include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
#include "ftxui/dom/elements.hpp" // for text, Element #include "ftxui/dom/elements.hpp" // for text, Element
#include "ftxui/dom/node.hpp" // for Node, Elements
#include "ftxui/screen/box.hpp" // for Box
namespace ftxui::animation { namespace ftxui::animation {
class Params; class Params;
@@ -103,10 +105,46 @@ void ComponentBase::DetachAllChildren() {
} }
/// @brief Draw the component. /// @brief Draw the component.
/// Build a ftxui::Element to be drawn on the ftxi::Screen representing this /// Build a ftxui::Element to be drawn on the ftxui::Screen representing this
/// ftxui::ComponentBase. /// ftxui::ComponentBase. Please override OnRender() to modify the rendering.
/// @ingroup component /// @ingroup component
Element ComponentBase::Render() { Element ComponentBase::Render() {
// Some users might call `ComponentBase::Render()` from
// `T::OnRender()`. To avoid infinite recursion, we use a flag.
if (in_render) {
return ComponentBase::OnRender();
}
in_render = true;
Element element = OnRender();
in_render = false;
class Wrapper : public Node {
public:
bool active_ = false;
Wrapper(Element child, bool active)
: Node({std::move(child)}), active_(active) {}
void SetBox(Box box) override {
Node::SetBox(box);
children_[0]->SetBox(box);
}
void ComputeRequirement() override {
Node::ComputeRequirement();
requirement_.focused.component_active = active_;
}
};
return std::make_shared<Wrapper>(std::move(element), Active());
}
/// @brief Draw the component.
/// Build a ftxui::Element to be drawn on the ftxi::Screen representing this
/// ftxui::ComponentBase. This function is means to be overridden.
/// @ingroup component
Element ComponentBase::OnRender() {
if (children_.size() == 1) { if (children_.size() == 1) {
return children_.front()->Render(); return children_.front()->Render();
} }

View File

@@ -98,7 +98,7 @@ class VerticalContainer : public ContainerBase {
public: public:
using ContainerBase::ContainerBase; using ContainerBase::ContainerBase;
Element Render() override { Element OnRender() override {
Elements elements; Elements elements;
elements.reserve(children_.size()); elements.reserve(children_.size());
for (auto& it : children_) { for (auto& it : children_) {
@@ -163,6 +163,7 @@ class VerticalContainer : public ContainerBase {
return false; return false;
} }
const int old_selected = *selector_;
if (event.mouse().button == Mouse::WheelUp) { if (event.mouse().button == Mouse::WheelUp) {
MoveSelector(-1); MoveSelector(-1);
} }
@@ -171,7 +172,7 @@ class VerticalContainer : public ContainerBase {
} }
*selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_)); *selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_));
return true; return old_selected != *selector_;
} }
Box box_; Box box_;
@@ -181,7 +182,7 @@ class HorizontalContainer : public ContainerBase {
public: public:
using ContainerBase::ContainerBase; using ContainerBase::ContainerBase;
Element Render() override { Element OnRender() override {
Elements elements; Elements elements;
elements.reserve(children_.size()); elements.reserve(children_.size());
for (auto& it : children_) { for (auto& it : children_) {
@@ -217,7 +218,7 @@ class TabContainer : public ContainerBase {
public: public:
using ContainerBase::ContainerBase; using ContainerBase::ContainerBase;
Element Render() override { Element OnRender() override {
const Component active_child = ActiveChild(); const Component active_child = ActiveChild();
if (active_child) { if (active_child) {
return active_child->Render(); return active_child->Render();
@@ -243,7 +244,7 @@ class StackedContainer : public ContainerBase {
: ContainerBase(std::move(children), nullptr) {} : ContainerBase(std::move(children), nullptr) {}
private: private:
Element Render() final { Element OnRender() final {
Elements elements; Elements elements;
for (auto& child : children_) { for (auto& child : children_) {
elements.push_back(child->Render()); elements.push_back(child->Render());
@@ -333,7 +334,7 @@ Component Vertical(Components children) {
/// children_2, /// children_2,
/// children_3, /// children_3,
/// children_4, /// children_4,
/// }); /// }, &selected_children);
/// ``` /// ```
Component Vertical(Components children, int* selector) { Component Vertical(Components children, int* selector) {
return std::make_shared<VerticalContainer>(std::move(children), selector); return std::make_shared<VerticalContainer>(std::move(children), selector);
@@ -354,7 +355,7 @@ Component Vertical(Components children, int* selector) {
/// children_2, /// children_2,
/// children_3, /// children_3,
/// children_4, /// children_4,
/// }, &selected_children); /// });
/// ``` /// ```
Component Horizontal(Components children) { Component Horizontal(Components children) {
return Horizontal(std::move(children), nullptr); return Horizontal(std::move(children), nullptr);

View File

@@ -44,10 +44,14 @@ Component Dropdown(DropdownOption option) {
})); }));
} }
Element Render() override { Element OnRender() override {
radiobox.selected = selected_ =
util::clamp(radiobox.selected(), 0, int(radiobox.entries.size()) - 1); util::clamp(radiobox.selected(), 0, int(radiobox.entries.size()) - 1);
title_ = radiobox.entries[selected_()]; selected_ = util::clamp(selected_(), 0, int(radiobox.entries.size()) - 1);
if (selected_() >= 0 && selected_() < int(radiobox.entries.size())) {
title_ = radiobox.entries[selected_()];
}
return transform(*open_, checkbox_->Render(), radiobox_->Render()); return transform(*open_, checkbox_->Render(), radiobox_->Render());
} }
@@ -66,10 +70,13 @@ Component Dropdown(DropdownOption option) {
// Auto-close the dropdown when the user selects an item, even if the item // Auto-close the dropdown when the user selects an item, even if the item
// it the same as the previous one. // it the same as the previous one.
if (open_old && open_()) { if (open_old && open_()) {
const bool should_close = (selected_() != selected_old) || // const bool should_close =
(event == Event::Return) || // (selected_() != selected_old) || //
(event == Event::Character(' ')) || // (event == Event::Return) || //
(event == Event::Escape); // (event == Event::Character(' ')) || //
(event == Event::Escape) || //
(event.is_mouse() && event.mouse().button == Mouse::Left &&
event.mouse().motion == Mouse::Pressed);
if (should_close) { if (should_close) {
checkbox_->TakeFocus(); checkbox_->TakeFocus();

View File

@@ -0,0 +1,34 @@
// Copyright 2025 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#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.h" // for AssertionResult, Message, TestPartResult, EXPECT_EQ, EXPECT_FALSE, Test, EXPECT_TRUE, TEST
namespace ftxui {
TEST(DropdownTest, Empty) {
std::vector<std::string> list = {};
int index = 0;
auto dropdown = Dropdown(list, &index);
dropdown->OnEvent(Event::Return);
auto screen = Screen(8, 8);
auto document = dropdown->Render();
Render(screen, document);
EXPECT_EQ(screen.ToString(),
"╭──────╮\r\n"
"│↓ │\r\n"
"├──────┤\r\n"
"│ │\r\n"
"│ │\r\n"
"│ │\r\n"
"│ │\r\n"
"╰──────╯");
}
} // namespace ftxui

View File

@@ -49,8 +49,8 @@ Component Hoverable(Component component, bool* hover) {
} }
private: private:
Element Render() override { Element OnRender() override {
return ComponentBase::Render() | reflect(box_); return ComponentBase::OnRender() | reflect(box_);
} }
bool OnEvent(Event event) override { bool OnEvent(Event event) override {
@@ -98,8 +98,8 @@ Component Hoverable(Component component,
} }
private: private:
Element Render() override { Element OnRender() override {
return ComponentBase::Render() | reflect(box_); return ComponentBase::OnRender() | reflect(box_);
} }
bool OnEvent(Event event) override { bool OnEvent(Event event) override {

View File

@@ -96,9 +96,9 @@ class InputBase : public ComponentBase, public InputOption {
private: private:
// Component implementation: // Component implementation:
Element Render() override { Element OnRender() override {
const bool is_focused = Focused(); const bool is_focused = Focused();
const auto focused = (!is_focused && !hovered_) ? select const auto focused = (!is_focused && !hovered_) ? nothing
: insert() ? focusCursorBarBlinking : insert() ? focusCursorBarBlinking
: focusCursorBlockBlinking; : focusCursorBlockBlinking;
@@ -108,15 +108,12 @@ class InputBase : public ComponentBase, public InputOption {
// placeholder. // placeholder.
if (content->empty()) { if (content->empty()) {
auto element = text(placeholder()) | xflex | frame; auto element = text(placeholder()) | xflex | frame;
if (is_focused) {
element |= focus;
}
return transform_func({ return transform_func({
std::move(element), hovered_, is_focused, std::move(element), hovered_, is_focused,
true // placeholder true // placeholder
}) | }) |
reflect(box_); focus | reflect(box_);
} }
Elements elements; Elements elements;
@@ -176,7 +173,7 @@ class InputBase : public ComponentBase, public InputOption {
elements.push_back(element); elements.push_back(element);
} }
auto element = vbox(std::move(elements)) | frame; auto element = vbox(std::move(elements), cursor_line) | frame;
return transform_func({ return transform_func({
std::move(element), hovered_, is_focused, std::move(element), hovered_, is_focused,
false // placeholder false // placeholder

View File

@@ -24,8 +24,8 @@ Component Maybe(Component child, std::function<bool()> show) {
explicit Impl(std::function<bool()> show) : show_(std::move(show)) {} explicit Impl(std::function<bool()> show) : show_(std::move(show)) {}
private: private:
Element Render() override { Element OnRender() override {
return show_() ? ComponentBase::Render() : std::make_unique<Node>(); return show_() ? ComponentBase::OnRender() : std::make_unique<Node>();
} }
bool Focusable() const override { bool Focusable() const override {
return show_() && ComponentBase::Focusable(); return show_() && ComponentBase::Focusable();

View File

@@ -105,7 +105,7 @@ class MenuBase : public ComponentBase, public MenuOption {
} }
} }
Element Render() override { Element OnRender() override {
Clamp(); Clamp();
UpdateAnimationTarget(); UpdateAnimationTarget();
@@ -126,16 +126,15 @@ class MenuBase : public ComponentBase, public MenuOption {
entries[i], false, is_selected, is_focused, i, entries[i], false, is_selected, is_focused, i,
}; };
auto focus_management = (selected_focus_ != i) ? nothing Element element = (entries_option.transform ? entries_option.transform
: is_menu_focused ? focus : DefaultOptionTransform) //
: select;
const Element element =
(entries_option.transform ? entries_option.transform
: DefaultOptionTransform) //
(state); (state);
elements.push_back(element | AnimatedColorStyle(i) | reflect(boxes_[i]) | if (selected_focus_ == i) {
focus_management); element |= focus;
}
element |= AnimatedColorStyle(i);
element |= reflect(boxes_[i]);
elements.push_back(element);
} }
if (elements_postfix) { if (elements_postfix) {
elements.push_back(elements_postfix()); elements.push_back(elements_postfix());
@@ -145,8 +144,9 @@ class MenuBase : public ComponentBase, public MenuOption {
std::reverse(elements.begin(), elements.end()); std::reverse(elements.begin(), elements.end());
} }
const Element bar = const Element bar = IsHorizontal()
IsHorizontal() ? hbox(std::move(elements)) : vbox(std::move(elements)); ? hbox(std::move(elements), selected_focus_)
: vbox(std::move(elements), selected_focus_);
if (!underline.enabled) { if (!underline.enabled) {
return bar | reflect(box_); return bar | reflect(box_);
@@ -618,20 +618,22 @@ Component MenuEntry(MenuEntryOption option) {
: MenuEntryOption(std::move(option)) {} : MenuEntryOption(std::move(option)) {}
private: private:
Element Render() override { Element OnRender() override {
const bool focused = Focused(); const bool is_focused = Focused();
UpdateAnimationTarget(); UpdateAnimationTarget();
const EntryState state{ const EntryState state{
label(), false, hovered_, focused, Index(), label(), false, hovered_, is_focused, Index(),
}; };
const Element element = Element element = (transform ? transform : DefaultOptionTransform) //
(transform ? transform : DefaultOptionTransform) //
(state); (state);
auto focus_management = focused ? select : nothing; if (is_focused) {
return element | AnimatedColorStyle() | focus_management | reflect(box_); element |= focus;
}
return element | AnimatedColorStyle() | reflect(box_);
} }
void UpdateAnimationTarget() { void UpdateAnimationTarget() {
@@ -692,7 +694,6 @@ Component MenuEntry(MenuEntryOption option) {
animator_foreground_.OnAnimation(params); animator_foreground_.OnAnimation(params);
} }
MenuEntryOption option_;
Box box_; Box box_;
bool hovered_ = false; bool hovered_ = false;

View File

@@ -266,7 +266,7 @@ TEST(MenuTest, MenuEntryIndex) {
menu->OnEvent(Event::ArrowDown); menu->OnEvent(Event::ArrowDown);
menu->OnEvent(Event::ArrowDown); menu->OnEvent(Event::ArrowDown);
menu->OnEvent(Event::Return); menu->OnEvent(Event::Return);
for (int index = 0; index < menu->ChildCount(); index++) { for (size_t index = 0; index < menu->ChildCount(); index++) {
EXPECT_EQ(menu->ChildAt(index)->Index(), index); EXPECT_EQ(menu->ChildAt(index)->Index(), index);
} }
} }

View File

@@ -26,7 +26,7 @@ Component Modal(Component main, Component modal, const bool* show_modal) {
} }
private: private:
Element Render() override { Element OnRender() override {
selector_ = *show_modal_; selector_ = *show_modal_;
auto document = main_->Render(); auto document = main_->Render();
if (*show_modal_) { if (*show_modal_) {

View File

@@ -28,7 +28,7 @@ class RadioboxBase : public ComponentBase, public RadioboxOption {
: RadioboxOption(option) {} : RadioboxOption(option) {}
private: private:
Element Render() override { Element OnRender() override {
Clamp(); Clamp();
Elements elements; Elements elements;
const bool is_menu_focused = Focused(); const bool is_menu_focused = Focused();
@@ -36,18 +36,17 @@ class RadioboxBase : public ComponentBase, public RadioboxOption {
for (int i = 0; i < size(); ++i) { for (int i = 0; i < size(); ++i) {
const bool is_focused = (focused_entry() == i) && is_menu_focused; const bool is_focused = (focused_entry() == i) && is_menu_focused;
const bool is_selected = (hovered_ == i); const bool is_selected = (hovered_ == i);
auto focus_management = !is_selected ? nothing
: is_menu_focused ? focus
: select;
auto state = EntryState{ auto state = EntryState{
entries[i], selected() == i, is_selected, is_focused, i, entries[i], selected() == i, is_selected, is_focused, i,
}; };
auto element = auto element =
(transform ? transform : RadioboxOption::Simple().transform)(state); (transform ? transform : RadioboxOption::Simple().transform)(state);
if (is_selected) {
elements.push_back(element | focus_management | reflect(boxes_[i])); element |= focus;
}
elements.push_back(element | reflect(boxes_[i]));
} }
return vbox(std::move(elements)) | reflect(box_); return vbox(std::move(elements), hovered_) | reflect(box_);
} }
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)

View File

@@ -31,7 +31,7 @@ Component Renderer(std::function<Element()> render) {
public: public:
explicit Impl(std::function<Element()> render) explicit Impl(std::function<Element()> render)
: render_(std::move(render)) {} : render_(std::move(render)) {}
Element Render() override { return render_(); } Element OnRender() override { return render_(); }
std::function<Element()> render_; std::function<Element()> render_;
}; };
@@ -88,7 +88,7 @@ Component Renderer(std::function<Element(bool)> render) {
: render_(std::move(render)) {} : render_(std::move(render)) {}
private: private:
Element Render() override { return render_(Focused()) | reflect(box_); } Element OnRender() override { return render_(Focused()) | reflect(box_); }
bool Focusable() const override { return true; } bool Focusable() const override { return true; }
bool OnEvent(Event event) override { bool OnEvent(Event event) override {
if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) { if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) {

View File

@@ -77,16 +77,16 @@ class ResizableSplitBase : public ComponentBase {
switch (options_->direction()) { switch (options_->direction()) {
case Direction::Left: case Direction::Left:
options_->main_size() = event.mouse().x - box_.x_min; options_->main_size() = std::max(0, event.mouse().x - box_.x_min);
return true; return true;
case Direction::Right: case Direction::Right:
options_->main_size() = box_.x_max - event.mouse().x; options_->main_size() = std::max(0, box_.x_max - event.mouse().x);
return true; return true;
case Direction::Up: case Direction::Up:
options_->main_size() = event.mouse().y - box_.y_min; options_->main_size() = std::max(0, event.mouse().y - box_.y_min);
return true; return true;
case Direction::Down: case Direction::Down:
options_->main_size() = box_.y_max - event.mouse().y; options_->main_size() = std::max(0, box_.y_max - event.mouse().y);
return true; return true;
} }
@@ -94,7 +94,7 @@ class ResizableSplitBase : public ComponentBase {
return false; return false;
} }
Element Render() final { Element OnRender() final {
switch (options_->direction()) { switch (options_->direction()) {
case Direction::Left: case Direction::Left:
return RenderLeft(); return RenderLeft();

View File

@@ -34,6 +34,7 @@
#include "ftxui/dom/requirement.hpp" // for Requirement #include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/screen/pixel.hpp" // for Pixel #include "ftxui/screen/pixel.hpp" // for Pixel
#include "ftxui/screen/terminal.hpp" // for Dimensions, Size #include "ftxui/screen/terminal.hpp" // for Dimensions, Size
#include "ftxui/screen/util.hpp" // for util::clamp
#if defined(_WIN32) #if defined(_WIN32)
#define DEFINE_CONSOLEV2_PROPERTIES #define DEFINE_CONSOLEV2_PROPERTIES
@@ -576,6 +577,18 @@ void ScreenInteractive::ForceHandleCtrlZ(bool force) {
force_handle_ctrl_z_ = force; force_handle_ctrl_z_ = force;
} }
/// @brief Returns the content of the current selection
std::string ScreenInteractive::GetSelection() {
if (!selection_) {
return "";
}
return selection_->GetParts();
}
void ScreenInteractive::SelectionChange(std::function<void()> callback) {
selection_on_change_ = std::move(callback);
}
/// @brief Return the currently active screen, or null if none. /// @brief Return the currently active screen, or null if none.
// static // static
ScreenInteractive* ScreenInteractive::Active() { ScreenInteractive* ScreenInteractive::Active() {
@@ -751,6 +764,14 @@ void ScreenInteractive::RunOnce(Component component) {
ExecuteSignalHandlers(); ExecuteSignalHandlers();
} }
Draw(std::move(component)); Draw(std::move(component));
if (selection_data_previous_ != selection_data_) {
selection_data_previous_ = selection_data_;
if (selection_on_change_) {
selection_on_change_();
Post(Event::Custom);
}
}
} }
// private // private
@@ -781,7 +802,9 @@ void ScreenInteractive::HandleTask(Component component, Task& task) {
arg.screen_ = this; arg.screen_ = this;
const bool handled = component->OnEvent(arg); bool handled = component->OnEvent(arg);
handled = HandleSelection(handled, arg);
if (arg == Event::CtrlC && (!handled || force_handle_ctrl_c_)) { if (arg == Event::CtrlC && (!handled || force_handle_ctrl_c_)) {
RecordSignal(SIGABRT); RecordSignal(SIGABRT);
@@ -824,6 +847,59 @@ void ScreenInteractive::HandleTask(Component component, Task& task) {
// clang-format on // clang-format on
} }
// private
bool ScreenInteractive::HandleSelection(bool handled, Event event) {
if (handled) {
selection_pending_ = nullptr;
selection_data_.empty = true;
selection_ = nullptr;
return true;
}
if (!event.is_mouse()) {
return false;
}
auto& mouse = event.mouse();
if (mouse.button != Mouse::Left) {
return false;
}
if (mouse.motion == Mouse::Pressed) {
selection_pending_ = CaptureMouse();
selection_data_.start_x = mouse.x;
selection_data_.start_y = mouse.y;
selection_data_.end_x = mouse.x;
selection_data_.end_y = mouse.y;
return false;
}
if (!selection_pending_) {
return false;
}
if (mouse.motion == Mouse::Moved) {
if ((mouse.x != selection_data_.end_x) ||
(mouse.y != selection_data_.end_y)) {
selection_data_.end_x = mouse.x;
selection_data_.end_y = mouse.y;
selection_data_.empty = false;
}
return true;
}
if (mouse.motion == Mouse::Released) {
selection_pending_ = nullptr;
selection_data_.end_x = mouse.x;
selection_data_.end_y = mouse.y;
selection_data_.empty = false;
return true;
}
return false;
}
// private // private
// NOLINTNEXTLINE // NOLINTNEXTLINE
void ScreenInteractive::Draw(Component component) { void ScreenInteractive::Draw(Component component) {
@@ -842,15 +918,15 @@ void ScreenInteractive::Draw(Component component) {
break; break;
case Dimension::TerminalOutput: case Dimension::TerminalOutput:
dimx = terminal.dimx; dimx = terminal.dimx;
dimy = document->requirement().min_y; dimy = util::clamp(document->requirement().min_y, 0, terminal.dimy);
break; break;
case Dimension::Fullscreen: case Dimension::Fullscreen:
dimx = terminal.dimx; dimx = terminal.dimx;
dimy = terminal.dimy; dimy = terminal.dimy;
break; break;
case Dimension::FitComponent: case Dimension::FitComponent:
dimx = std::min(document->requirement().min_x, terminal.dimx); dimx = util::clamp(document->requirement().min_x, 0, terminal.dimx);
dimy = std::min(document->requirement().min_y, terminal.dimy); dimy = util::clamp(document->requirement().min_y, 0, terminal.dimy);
break; break;
} }
@@ -899,7 +975,12 @@ void ScreenInteractive::Draw(Component component) {
#endif #endif
previous_frame_resized_ = resized; previous_frame_resized_ = resized;
Render(*this, document); selection_ = selection_data_.empty
? std::make_unique<Selection>()
: std::make_unique<Selection>(
selection_data_.start_x, selection_data_.start_y, //
selection_data_.end_x, selection_data_.end_y);
Render(*this, document.get(), *selection_);
// Set cursor position for user using tools to insert CJK characters. // Set cursor position for user using tools to insert CJK characters.
{ {
@@ -988,4 +1069,21 @@ void ScreenInteractive::Signal(int signal) {
#endif #endif
} }
bool ScreenInteractive::SelectionData::operator==(
const ScreenInteractive::SelectionData& other) const {
if (empty && other.empty) {
return true;
}
if (empty || other.empty) {
return false;
}
return start_x == other.start_x && start_y == other.start_y &&
end_x == other.end_x && end_y == other.end_y;
}
bool ScreenInteractive::SelectionData::operator!=(
const ScreenInteractive::SelectionData& other) const {
return !(*this == other);
}
} // namespace ftxui. } // namespace ftxui.

View File

@@ -16,7 +16,6 @@
#include "ftxui/dom/elements.hpp" // for operator|, text, Element, xflex, hbox, color, underlined, reflect, Decorator, dim, vcenter, focus, nothing, select, yflex, gaugeDirection #include "ftxui/dom/elements.hpp" // for operator|, text, Element, xflex, hbox, color, underlined, reflect, Decorator, dim, vcenter, focus, nothing, select, yflex, gaugeDirection
#include "ftxui/screen/box.hpp" // for Box #include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/color.hpp" // for Color, Color::GrayDark, Color::White #include "ftxui/screen/color.hpp" // for Color, Color::GrayDark, Color::White
#include "ftxui/screen/util.hpp" // for clamp
#include "ftxui/util/ref.hpp" // for ConstRef, Ref, ConstStringRef #include "ftxui/util/ref.hpp" // for ConstRef, Ref, ConstStringRef
namespace ftxui { namespace ftxui {
@@ -39,7 +38,7 @@ class SliderBase : public SliderOption<T>, public ComponentBase {
public: public:
explicit SliderBase(SliderOption<T> options) : SliderOption<T>(options) {} explicit SliderBase(SliderOption<T> options) : SliderOption<T>(options) {}
Element Render() override { Element OnRender() override {
auto gauge_color = auto gauge_color =
Focused() ? color(this->color_active) : color(this->color_inactive); Focused() ? color(this->color_active) : color(this->color_inactive);
const float percent = const float percent =
@@ -134,53 +133,52 @@ class SliderBase : public SliderOption<T>, public ComponentBase {
return ComponentBase::OnEvent(event); return ComponentBase::OnEvent(event);
} }
bool OnCapturedMouseEvent(Event event) {
if (event.mouse().motion == Mouse::Released) {
captured_mouse_ = nullptr;
return true;
}
T old_value = this->value();
switch (this->direction) {
case Direction::Right: {
this->value() = this->min() + (event.mouse().x - gauge_box_.x_min) *
(this->max() - this->min()) /
(gauge_box_.x_max - gauge_box_.x_min);
break;
}
case Direction::Left: {
this->value() = this->max() - (event.mouse().x - gauge_box_.x_min) *
(this->max() - this->min()) /
(gauge_box_.x_max - gauge_box_.x_min);
break;
}
case Direction::Down: {
this->value() = this->min() + (event.mouse().y - gauge_box_.y_min) *
(this->max() - this->min()) /
(gauge_box_.y_max - gauge_box_.y_min);
break;
}
case Direction::Up: {
this->value() = this->max() - (event.mouse().y - gauge_box_.y_min) *
(this->max() - this->min()) /
(gauge_box_.y_max - gauge_box_.y_min);
break;
}
}
this->value() = std::max(this->min(), std::min(this->max(), this->value()));
if (old_value != this->value() && this->on_change) {
this->on_change();
}
return true;
}
bool OnMouseEvent(Event event) { bool OnMouseEvent(Event event) {
if (captured_mouse_) { if (captured_mouse_) {
if (event.mouse().motion == Mouse::Released) { return OnCapturedMouseEvent(event);
captured_mouse_ = nullptr;
return true;
}
T old_value = this->value();
switch (this->direction) {
case Direction::Right: {
this->value() =
this->min() + (event.mouse().x - gauge_box_.x_min) *
(this->max() - this->min()) /
(gauge_box_.x_max - gauge_box_.x_min);
break;
}
case Direction::Left: {
this->value() =
this->max() - (event.mouse().x - gauge_box_.x_min) *
(this->max() - this->min()) /
(gauge_box_.x_max - gauge_box_.x_min);
break;
}
case Direction::Down: {
this->value() =
this->min() + (event.mouse().y - gauge_box_.y_min) *
(this->max() - this->min()) /
(gauge_box_.y_max - gauge_box_.y_min);
break;
}
case Direction::Up: {
this->value() =
this->max() - (event.mouse().y - gauge_box_.y_min) *
(this->max() - this->min()) /
(gauge_box_.y_max - gauge_box_.y_min);
break;
}
}
this->value() =
std::max(this->min(), std::min(this->max(), this->value()));
if (old_value != this->value() && this->on_change) {
this->on_change();
}
return true;
} }
if (event.mouse().button != Mouse::Left) { if (event.mouse().button != Mouse::Left) {
@@ -198,7 +196,7 @@ class SliderBase : public SliderOption<T>, public ComponentBase {
if (captured_mouse_) { if (captured_mouse_) {
TakeFocus(); TakeFocus();
return true; return OnCapturedMouseEvent(event);
} }
return false; return false;
@@ -242,19 +240,21 @@ class SliderWithLabel : public ComponentBase {
return true; return true;
} }
Element Render() override { Element OnRender() override {
auto focus_management = Focused() ? focus : Active() ? select : nothing;
auto gauge_color = (Focused() || mouse_hover_) ? color(Color::White) auto gauge_color = (Focused() || mouse_hover_) ? color(Color::White)
: color(Color::GrayDark); : color(Color::GrayDark);
return hbox({ auto element = hbox({
text(label_()) | dim | vcenter, text(label_()) | dim | vcenter,
hbox({ hbox({
text("["), text("["),
ComponentBase::Render() | underlined, ComponentBase::Render() | underlined,
text("]"), text("]"),
}) | xflex, }) | xflex,
}) | }) |
gauge_color | xflex | reflect(box_) | focus_management; gauge_color | xflex | reflect(box_);
element |= focus;
return element;
} }
ConstStringRef label_; ConstStringRef label_;

View File

@@ -60,17 +60,17 @@ TEST(SliderTest, Right) {
EXPECT_EQ(value, 50); EXPECT_EQ(value, 50);
EXPECT_EQ(updated, 0); EXPECT_EQ(updated, 0);
EXPECT_TRUE(slider->OnEvent(MousePressed(3, 0))); EXPECT_TRUE(slider->OnEvent(MousePressed(3, 0)));
EXPECT_EQ(value, 50); EXPECT_EQ(value, 30);
EXPECT_EQ(updated, 0); EXPECT_EQ(updated, 1);
EXPECT_TRUE(slider->OnEvent(MousePressed(9, 0))); EXPECT_TRUE(slider->OnEvent(MousePressed(9, 0)));
EXPECT_EQ(value, 90); EXPECT_EQ(value, 90);
EXPECT_EQ(updated, 1); EXPECT_EQ(updated, 2);
EXPECT_TRUE(slider->OnEvent(MousePressed(9, 2))); EXPECT_TRUE(slider->OnEvent(MousePressed(9, 2)));
EXPECT_EQ(value, 90); EXPECT_EQ(value, 90);
EXPECT_EQ(updated, 1); EXPECT_EQ(updated, 2);
EXPECT_TRUE(slider->OnEvent(MousePressed(5, 2))); EXPECT_TRUE(slider->OnEvent(MousePressed(5, 2)));
EXPECT_EQ(value, 50); EXPECT_EQ(value, 50);
EXPECT_EQ(updated, 2); EXPECT_EQ(updated, 3);
EXPECT_TRUE(slider->OnEvent(MouseReleased(5, 2))); EXPECT_TRUE(slider->OnEvent(MouseReleased(5, 2)));
EXPECT_FALSE(slider->OnEvent(MousePressed(5, 2))); EXPECT_FALSE(slider->OnEvent(MousePressed(5, 2)));
EXPECT_EQ(value, 50); EXPECT_EQ(value, 50);
@@ -92,17 +92,17 @@ TEST(SliderTest, Left) {
EXPECT_EQ(value, 50); EXPECT_EQ(value, 50);
EXPECT_EQ(updated, 0); EXPECT_EQ(updated, 0);
EXPECT_TRUE(slider->OnEvent(MousePressed(3, 0))); EXPECT_TRUE(slider->OnEvent(MousePressed(3, 0)));
EXPECT_EQ(value, 50); EXPECT_EQ(value, 70);
EXPECT_EQ(updated, 0); EXPECT_EQ(updated, 1);
EXPECT_TRUE(slider->OnEvent(MousePressed(9, 0))); EXPECT_TRUE(slider->OnEvent(MousePressed(9, 0)));
EXPECT_EQ(value, 10); EXPECT_EQ(value, 10);
EXPECT_EQ(updated, 1); EXPECT_EQ(updated, 2);
EXPECT_TRUE(slider->OnEvent(MousePressed(9, 2))); EXPECT_TRUE(slider->OnEvent(MousePressed(9, 2)));
EXPECT_EQ(value, 10); EXPECT_EQ(value, 10);
EXPECT_EQ(updated, 1); EXPECT_EQ(updated, 2);
EXPECT_TRUE(slider->OnEvent(MousePressed(5, 2))); EXPECT_TRUE(slider->OnEvent(MousePressed(5, 2)));
EXPECT_EQ(value, 50); EXPECT_EQ(value, 50);
EXPECT_EQ(updated, 2); EXPECT_EQ(updated, 3);
EXPECT_TRUE(slider->OnEvent(MouseReleased(5, 2))); EXPECT_TRUE(slider->OnEvent(MouseReleased(5, 2)));
EXPECT_FALSE(slider->OnEvent(MousePressed(5, 2))); EXPECT_FALSE(slider->OnEvent(MousePressed(5, 2)));
EXPECT_EQ(value, 50); EXPECT_EQ(value, 50);
@@ -124,21 +124,21 @@ TEST(SliderTest, Down) {
EXPECT_EQ(value, 50); EXPECT_EQ(value, 50);
EXPECT_EQ(updated, 0); EXPECT_EQ(updated, 0);
EXPECT_TRUE(slider->OnEvent(MousePressed(0, 3))); EXPECT_TRUE(slider->OnEvent(MousePressed(0, 3)));
EXPECT_EQ(value, 50); EXPECT_EQ(value, 30);
EXPECT_EQ(updated, 0); EXPECT_EQ(updated, 1);
EXPECT_TRUE(slider->OnEvent(MousePressed(0, 9))); EXPECT_TRUE(slider->OnEvent(MousePressed(0, 9)));
EXPECT_EQ(value, 90); EXPECT_EQ(value, 90);
EXPECT_EQ(updated, 1); EXPECT_EQ(updated, 2);
EXPECT_TRUE(slider->OnEvent(MousePressed(2, 9))); EXPECT_TRUE(slider->OnEvent(MousePressed(2, 9)));
EXPECT_EQ(value, 90); EXPECT_EQ(value, 90);
EXPECT_EQ(updated, 1); EXPECT_EQ(updated, 2);
EXPECT_TRUE(slider->OnEvent(MousePressed(2, 5))); EXPECT_TRUE(slider->OnEvent(MousePressed(2, 5)));
EXPECT_EQ(value, 50); EXPECT_EQ(value, 50);
EXPECT_EQ(updated, 2); EXPECT_EQ(updated, 3);
EXPECT_TRUE(slider->OnEvent(MouseReleased(2, 5))); EXPECT_TRUE(slider->OnEvent(MouseReleased(2, 5)));
EXPECT_FALSE(slider->OnEvent(MousePressed(2, 5))); EXPECT_FALSE(slider->OnEvent(MousePressed(2, 5)));
EXPECT_EQ(value, 50); EXPECT_EQ(value, 50);
EXPECT_EQ(updated, 2); EXPECT_EQ(updated, 3);
} }
TEST(SliderTest, Up) { TEST(SliderTest, Up) {
@@ -157,17 +157,17 @@ TEST(SliderTest, Up) {
EXPECT_EQ(value, 50); EXPECT_EQ(value, 50);
EXPECT_EQ(updated, 0); EXPECT_EQ(updated, 0);
EXPECT_TRUE(slider->OnEvent(MousePressed(0, 3))); EXPECT_TRUE(slider->OnEvent(MousePressed(0, 3)));
EXPECT_EQ(value, 50); EXPECT_EQ(value, 70);
EXPECT_EQ(updated, 0); EXPECT_EQ(updated, 1);
EXPECT_TRUE(slider->OnEvent(MousePressed(0, 9))); EXPECT_TRUE(slider->OnEvent(MousePressed(0, 9)));
EXPECT_EQ(value, 10); EXPECT_EQ(value, 10);
EXPECT_EQ(updated, 1); EXPECT_EQ(updated, 2);
EXPECT_TRUE(slider->OnEvent(MousePressed(2, 9))); EXPECT_TRUE(slider->OnEvent(MousePressed(2, 9)));
EXPECT_EQ(value, 10); EXPECT_EQ(value, 10);
EXPECT_EQ(updated, 1); EXPECT_EQ(updated, 2);
EXPECT_TRUE(slider->OnEvent(MousePressed(2, 5))); EXPECT_TRUE(slider->OnEvent(MousePressed(2, 5)));
EXPECT_EQ(value, 50); EXPECT_EQ(value, 50);
EXPECT_EQ(updated, 2); EXPECT_EQ(updated, 3);
EXPECT_TRUE(slider->OnEvent(MouseReleased(2, 5))); EXPECT_TRUE(slider->OnEvent(MouseReleased(2, 5)));
EXPECT_FALSE(slider->OnEvent(MousePressed(2, 5))); EXPECT_FALSE(slider->OnEvent(MousePressed(2, 5)));
EXPECT_EQ(value, 50); EXPECT_EQ(value, 50);

View File

@@ -124,7 +124,7 @@ class WindowImpl : public ComponentBase, public WindowOptions {
} }
private: private:
Element Render() final { Element OnRender() final {
auto element = ComponentBase::Render(); auto element = ComponentBase::Render();
const bool captureable = const bool captureable =

17
src/ftxui/dom.cppm Normal file
View File

@@ -0,0 +1,17 @@
/**
* @file dom.cppm
* @brief Module file for FTXUI main operations.
*/
export module ftxui.dom;
export import ftxui.dom.Canvas;
export import ftxui.dom.Deprecated;
export import ftxui.dom.Direction;
export import ftxui.dom.Elements;
export import ftxui.dom.FlexboxConfig;
export import ftxui.dom.LinearGradient;
export import ftxui.dom.Node;
export import ftxui.dom.Requirement;
export import ftxui.dom.Selection;
export import ftxui.dom.Table;

18
src/ftxui/dom/Canvas.cppm Normal file
View File

@@ -0,0 +1,18 @@
/**
* @file Canvas.cppm
* @brief Module file for the Canvas struct of the Dom module
*/
module;
#include <ftxui/dom/canvas.hpp>
export module ftxui.dom.Canvas;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::Canvas;
}

View File

@@ -0,0 +1,20 @@
/**
* @file Deprecated.cppm
* @brief Module file for deprecated parts of the Dom module
*/
module;
#include <ftxui/dom/deprecated.hpp>
export module ftxui.dom.Deprecated;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::text;
using ftxui::vtext;
using ftxui::paragraph;
}

View File

@@ -0,0 +1,18 @@
/**
* @file Direction.cppm
* @brief Module file for the Direction enum of the Dom module
*/
module;
#include <ftxui/dom/direction.hpp>
export module ftxui.dom.Direction;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::Direction;
}

130
src/ftxui/dom/Elements.cppm Normal file
View File

@@ -0,0 +1,130 @@
/**
* @file Canvas.cppm
* @brief Module file for the Element classes and functions of the Dom module
*/
module;
#include <ftxui/dom/elements.hpp>
export module ftxui.dom.Elements;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::Node;
using ftxui::Element;
using ftxui::Elements;
using ftxui::Decorator;
using ftxui::GraphFunction;
using ftxui::BorderStyle;
using ftxui::operator|;
using ftxui::operator|=;
using ftxui::text;
using ftxui::vtext;
using ftxui::separator;
using ftxui::separatorLight;
using ftxui::separatorDashed;
using ftxui::separatorHeavy;
using ftxui::separatorDouble;
using ftxui::separatorEmpty;
using ftxui::separatorStyled;
using ftxui::separatorCharacter;
using ftxui::separatorHSelector;
using ftxui::separatorVSelector;
using ftxui::gauge;
using ftxui::gaugeLeft;
using ftxui::gaugeRight;
using ftxui::gaugeUp;
using ftxui::gaugeDown;
using ftxui::gaugeDirection;
using ftxui::border;
using ftxui::borderLight;
using ftxui::borderDashed;
using ftxui::borderHeavy;
using ftxui::borderDouble;
using ftxui::borderRounded;
using ftxui::borderEmpty;
using ftxui::borderStyled;
using ftxui::borderWith;
using ftxui::window;
using ftxui::spinner;
using ftxui::paragraph;
using ftxui::paragraphAlignLeft;
using ftxui::paragraphAlignRight;
using ftxui::paragraphAlignCenter;
using ftxui::paragraphAlignJustify;
using ftxui::graph;
using ftxui::emptyElement;
using ftxui::canvas;
using ftxui::bold;
using ftxui::dim;
using ftxui::inverted;
using ftxui::underlined;
using ftxui::underlinedDouble;
using ftxui::blink;
using ftxui::strikethrough;
using ftxui::color;
using ftxui::bgcolor;
using ftxui::focusPosition;
using ftxui::focusPositionRelative;
using ftxui::automerge;
using ftxui::hyperlink;
using ftxui::selectionStyleReset;
using ftxui::selectionColor;
using ftxui::selectionBackgroundColor;
using ftxui::selectionForegroundColor;
using ftxui::selectionStyle;
using ftxui::hbox;
using ftxui::vbox;
using ftxui::dbox;
using ftxui::flexbox;
using ftxui::gridbox;
using ftxui::hflow;
using ftxui::vflow;
using ftxui::flex;
using ftxui::flex_grow;
using ftxui::flex_shrink;
using ftxui::xflex;
using ftxui::xflex_grow;
using ftxui::xflex_shrink;
using ftxui::yflex;
using ftxui::yflex_grow;
using ftxui::yflex_shrink;
using ftxui::notflex;
using ftxui::filler;
using ftxui::WidthOrHeight;
using ftxui::Constraint;
using ftxui::size;
using ftxui::focusCursorBlock;
using ftxui::focusCursorBlockBlinking;
using ftxui::focusCursorBar;
using ftxui::focusCursorBarBlinking;
using ftxui::focusCursorUnderline;
using ftxui::focusCursorUnderlineBlinking;
using ftxui::vscroll_indicator;
using ftxui::hscroll_indicator;
using ftxui::reflect;
using ftxui::clear_under;
using ftxui::hcenter;
using ftxui::vcenter;
using ftxui::center;
using ftxui::align_right;
using ftxui::nothing;
namespace Dimension {
using ftxui::Dimension::Fit;
}
}

View File

@@ -0,0 +1,18 @@
/**
* @file FlexboxConfig.cppm
* @brief Module file for the FlexboxConfig struct of the Dom module
*/
module;
#include <ftxui/dom/flexbox_config.hpp>
export module ftxui.dom.FlexboxConfig;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::FlexboxConfig;
}

View File

@@ -0,0 +1,18 @@
/**
* @file LinearGradient.cppm
* @brief Module file for the LinearGradient struct of the Dom module
*/
module;
#include <ftxui/dom/linear_gradient.hpp>
export module ftxui.dom.LinearGradient;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::LinearGradient;
}

25
src/ftxui/dom/Node.cppm Normal file
View File

@@ -0,0 +1,25 @@
/**
* @file Node.cppm
* @brief Module file for the Node class of the Dom module
*/
module;
#include <ftxui/dom/node.hpp>
export module ftxui.dom.Node;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::Node;
using ftxui::Screen;
using ftxui::Element;
using ftxui::Elements;
using ftxui::Render;
using ftxui::GetNodeSelectedContent;
}

View File

@@ -0,0 +1,18 @@
/**
* @file Requirement.cppm
* @brief Module file for the Requirement struct of the Dom module
*/
module;
#include <ftxui/dom/requirement.hpp>
export module ftxui.dom.Requirement;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::Requirement;
}

View File

@@ -0,0 +1,18 @@
/**
* @file Selection.cppm
* @brief Module file for the Selection class of the Dom module
*/
module;
#include <ftxui/dom/selection.hpp>
export module ftxui.dom.Selection;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::Selection;
}

19
src/ftxui/dom/Table.cppm Normal file
View File

@@ -0,0 +1,19 @@
/**
* @file Table.cppm
* @brief Module file for the Table class of the Dom module
*/
module;
#include <ftxui/dom/table.hpp>
export module ftxui.dom.Table;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
using ftxui::Table;
using ftxui::TableSelection;
}

View File

@@ -54,10 +54,10 @@ class Border : public Node {
requirement_.min_x = requirement_.min_x =
std::max(requirement_.min_x, children_[1]->requirement().min_x + 2); std::max(requirement_.min_x, children_[1]->requirement().min_x + 2);
} }
requirement_.selected_box.x_min++; requirement_.focused.box.x_min++;
requirement_.selected_box.x_max++; requirement_.focused.box.x_max++;
requirement_.selected_box.y_min++; requirement_.focused.box.y_min++;
requirement_.selected_box.y_max++; requirement_.focused.box.y_max++;
} }
void SetBox(Box box) override { void SetBox(Box box) override {
@@ -65,7 +65,8 @@ class Border : public Node {
if (children_.size() == 2) { if (children_.size() == 2) {
Box title_box; Box title_box;
title_box.x_min = box.x_min + 1; title_box.x_min = box.x_min + 1;
title_box.x_max = std::min(box.x_max - 1, box.x_min + children_[1]->requirement().min_x); title_box.x_max = std::min(box.x_max - 1,
box.x_min + children_[1]->requirement().min_x);
title_box.y_min = box.y_min; title_box.y_min = box.y_min;
title_box.y_max = box.y_min; title_box.y_max = box.y_min;
children_[1]->SetBox(title_box); children_[1]->SetBox(title_box);
@@ -145,10 +146,8 @@ class BorderPixel : public Node {
requirement_.min_x = requirement_.min_x =
std::max(requirement_.min_x, children_[1]->requirement().min_x + 2); std::max(requirement_.min_x, children_[1]->requirement().min_x + 2);
} }
requirement_.selected_box.x_min++;
requirement_.selected_box.x_max++; requirement_.focused.box.Shift(1, 1);
requirement_.selected_box.y_min++;
requirement_.selected_box.y_max++;
} }
void SetBox(Box box) override { void SetBox(Box box) override {

View File

@@ -5,6 +5,7 @@
#define FTXUI_DOM_BOX_HELPER_HPP #define FTXUI_DOM_BOX_HELPER_HPP
#include <vector> #include <vector>
#include "ftxui/dom/requirement.hpp"
namespace ftxui::box_helper { namespace ftxui::box_helper {
@@ -19,7 +20,6 @@ struct Element {
}; };
void Compute(std::vector<Element>* elements, int target_size); void Compute(std::vector<Element>* elements, int target_size);
} // namespace ftxui::box_helper } // namespace ftxui::box_helper
#endif /* end of include guard: FTXUI_DOM_BOX_HELPER_HPP */ #endif /* end of include guard: FTXUI_DOM_BOX_HELPER_HPP */

View File

@@ -21,24 +21,20 @@ class DBox : public Node {
explicit DBox(Elements children) : Node(std::move(children)) {} explicit DBox(Elements children) : Node(std::move(children)) {}
void ComputeRequirement() override { void ComputeRequirement() override {
requirement_.min_x = 0; requirement_ = Requirement{};
requirement_.min_y = 0;
requirement_.flex_grow_x = 0;
requirement_.flex_grow_y = 0;
requirement_.flex_shrink_x = 0;
requirement_.flex_shrink_y = 0;
requirement_.selection = Requirement::NORMAL;
for (auto& child : children_) { for (auto& child : children_) {
child->ComputeRequirement(); child->ComputeRequirement();
// Propagate the focused requirement.
if (requirement_.focused.Prefer(child->requirement().focused)) {
requirement_.focused = child->requirement().focused;
}
// Extend the min_x and min_y to contain all the children
requirement_.min_x = requirement_.min_x =
std::max(requirement_.min_x, child->requirement().min_x); std::max(requirement_.min_x, child->requirement().min_x);
requirement_.min_y = requirement_.min_y =
std::max(requirement_.min_y, child->requirement().min_y); std::max(requirement_.min_y, child->requirement().min_y);
if (requirement_.selection < child->requirement().selection) {
requirement_.selection = child->requirement().selection;
requirement_.selected_box = child->requirement().selected_box;
}
} }
} }
@@ -49,58 +45,6 @@ class DBox : public Node {
child->SetBox(box); child->SetBox(box);
} }
} }
void Render(Screen& screen) override {
if (children_.size() <= 1) {
Node::Render(screen);
return;
}
const int width = box_.x_max - box_.x_min + 1;
const int height = box_.y_max - box_.y_min + 1;
std::vector<Pixel> pixels(std::size_t(width * height));
for (auto& child : children_) {
child->Render(screen);
// Accumulate the pixels
Pixel* acc = pixels.data();
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
auto& pixel = screen.PixelAt(x + box_.x_min, y + box_.y_min);
acc->background_color =
Color::Blend(acc->background_color, pixel.background_color);
acc->automerge = pixel.automerge || acc->automerge;
if (pixel.character.empty()) {
acc->foreground_color =
Color::Blend(acc->foreground_color, pixel.background_color);
} else {
acc->blink = pixel.blink;
acc->bold = pixel.bold;
acc->dim = pixel.dim;
acc->inverted = pixel.inverted;
acc->underlined = pixel.underlined;
acc->underlined_double = pixel.underlined_double;
acc->strikethrough = pixel.strikethrough;
acc->hyperlink = pixel.hyperlink;
acc->character = pixel.character;
acc->foreground_color = pixel.foreground_color;
}
++acc; // NOLINT
pixel = Pixel();
}
}
}
// Render the accumulated pixels:
Pixel* acc = pixels.data();
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
screen.PixelAt(x + box_.x_min, y + box_.y_min) = *acc++; // NOLINT
}
}
}
}; };
} // namespace } // namespace

View File

@@ -80,6 +80,7 @@ class Flex : public Node {
} }
void SetBox(Box box) override { void SetBox(Box box) override {
Node::SetBox(box);
if (children_.empty()) { if (children_.empty()) {
return; return;
} }

View File

@@ -4,6 +4,7 @@
#include <algorithm> // for min, max #include <algorithm> // for min, max
#include <cstddef> // for size_t #include <cstddef> // for size_t
#include <memory> // for __shared_ptr_access, shared_ptr, allocator_traits<>::value_type, make_shared #include <memory> // for __shared_ptr_access, shared_ptr, allocator_traits<>::value_type, make_shared
#include <tuple> // for ignore
#include <utility> // for move, swap #include <utility> // for move, swap
#include <vector> // for vector #include <vector> // for vector
@@ -12,6 +13,7 @@
#include "ftxui/dom/flexbox_helper.hpp" // for Block, Global, Compute #include "ftxui/dom/flexbox_helper.hpp" // for Block, Global, Compute
#include "ftxui/dom/node.hpp" // for Node, Elements, Node::Status #include "ftxui/dom/node.hpp" // for Node, Elements, Node::Status
#include "ftxui/dom/requirement.hpp" // for Requirement #include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/dom/selection.hpp" // for Selection
#include "ftxui/screen/box.hpp" // for Box #include "ftxui/screen/box.hpp" // for Box
namespace ftxui { namespace ftxui {
@@ -89,37 +91,32 @@ class Flexbox : public Node {
} }
void ComputeRequirement() override { void ComputeRequirement() override {
requirement_ = Requirement{};
for (auto& child : children_) { for (auto& child : children_) {
child->ComputeRequirement(); child->ComputeRequirement();
} }
flexbox_helper::Global global; global_ = flexbox_helper::Global();
global.config = config_normalized_; global_.config = config_normalized_;
if (IsColumnOriented()) { if (IsColumnOriented()) {
global.size_x = 100000; // NOLINT global_.size_x = 100000; // NOLINT
global.size_y = asked_; global_.size_y = asked_;
} else { } else {
global.size_x = asked_; global_.size_x = asked_;
global.size_y = 100000; // NOLINT global_.size_y = 100000; // NOLINT
} }
Layout(global, true); Layout(global_, true);
// Reset: if (global_.blocks.empty()) {
requirement_.selection = Requirement::Selection::NORMAL;
requirement_.selected_box = Box();
requirement_.min_x = 0;
requirement_.min_y = 0;
if (global.blocks.empty()) {
return; return;
} }
// Compute the union of all the blocks: // Compute the union of all the blocks:
Box box; Box box;
box.x_min = global.blocks[0].x; box.x_min = global_.blocks[0].x;
box.y_min = global.blocks[0].y; box.y_min = global_.blocks[0].y;
box.x_max = global.blocks[0].x + global.blocks[0].dim_x; box.x_max = global_.blocks[0].x + global_.blocks[0].dim_x;
box.y_max = global.blocks[0].y + global.blocks[0].dim_y; box.y_max = global_.blocks[0].y + global_.blocks[0].dim_y;
for (auto& b : global.blocks) { for (auto& b : global_.blocks) {
box.x_min = std::min(box.x_min, b.x); box.x_min = std::min(box.x_min, b.x);
box.y_min = std::min(box.y_min, b.y); box.y_min = std::min(box.y_min, b.y);
box.x_max = std::max(box.x_max, b.x + b.dim_x); box.x_max = std::max(box.x_max, b.x + b.dim_x);
@@ -130,19 +127,14 @@ class Flexbox : public Node {
// Find the selection: // Find the selection:
for (size_t i = 0; i < children_.size(); ++i) { for (size_t i = 0; i < children_.size(); ++i) {
if (requirement_.selection >= children_[i]->requirement().selection) { if (requirement_.focused.Prefer(children_[i]->requirement().focused)) {
continue; requirement_.focused = children_[i]->requirement().focused;
// Shift |focused.box| according to its position inside this component:
auto& b = global_.blocks[i];
requirement_.focused.box.Shift(b.x, b.y);
requirement_.focused.box =
Box::Intersection(requirement_.focused.box, box);
} }
requirement_.selection = children_[i]->requirement().selection;
Box selected_box = children_[i]->requirement().selected_box;
// Shift |selected_box| according to its position inside this component:
auto& b = global.blocks[i];
selected_box.x_min += b.x;
selected_box.y_min += b.y;
selected_box.x_max += b.x;
selected_box.y_max += b.y;
requirement_.selected_box = Box::Intersection(selected_box, box);
} }
} }
@@ -177,6 +169,43 @@ class Flexbox : public Node {
} }
} }
void Select(Selection& selection) override {
// If this Node box_ doesn't intersect with the selection, then no
// selection.
if (Box::Intersection(selection.GetBox(), box_).IsEmpty()) {
return;
}
Selection selection_lines = IsColumnOriented()
? selection.SaturateVertical(box_)
: selection.SaturateHorizontal(box_);
size_t i = 0;
for (auto& line : global_.lines) {
Box box;
box.x_min = box_.x_min + line.x;
box.x_max = box_.x_min + line.x + line.dim_x - 1;
box.y_min = box_.y_min + line.y;
box.y_max = box_.y_min + line.y + line.dim_y - 1;
// If the line box doesn't intersect with the selection, then no
// selection.
if (Box::Intersection(selection.GetBox(), box).IsEmpty()) {
continue;
}
Selection selection_line = IsColumnOriented()
? selection_lines.SaturateHorizontal(box)
: selection_lines.SaturateVertical(box);
for (auto& block : line.blocks) {
std::ignore = block;
children_[i]->Select(selection_line);
i++;
}
}
}
void Check(Status* status) override { void Check(Status* status) override {
for (auto& child : children_) { for (auto& child : children_) {
child->Check(status); child->Check(status);
@@ -194,6 +223,7 @@ class Flexbox : public Node {
bool need_iteration_ = true; bool need_iteration_ = true;
const FlexboxConfig config_; const FlexboxConfig config_;
const FlexboxConfig config_normalized_; const FlexboxConfig config_normalized_;
flexbox_helper::Global global_;
}; };
} // namespace } // namespace

View File

@@ -68,6 +68,10 @@ void SymmetryXY(Global& g) {
std::swap(b.x, b.y); std::swap(b.x, b.y);
std::swap(b.dim_x, b.dim_y); std::swap(b.dim_x, b.dim_y);
} }
for (auto& l : g.lines) {
std::swap(l.x, l.y);
std::swap(l.dim_x, l.dim_y);
}
} }
void SymmetryX(Global& g) { void SymmetryX(Global& g) {
@@ -75,6 +79,9 @@ void SymmetryX(Global& g) {
for (auto& b : g.blocks) { for (auto& b : g.blocks) {
b.x = g.size_x - b.x - b.dim_x; b.x = g.size_x - b.x - b.dim_x;
} }
for (auto& l : g.lines) {
l.x = g.size_x - l.x - l.dim_x;
}
} }
void SymmetryY(Global& g) { void SymmetryY(Global& g) {
@@ -82,14 +89,13 @@ void SymmetryY(Global& g) {
for (auto& b : g.blocks) { for (auto& b : g.blocks) {
b.y = g.size_y - b.y - b.dim_y; b.y = g.size_y - b.y - b.dim_y;
} }
for (auto& l : g.lines) {
l.y = g.size_y - l.y - l.dim_y;
}
} }
struct Line { void SetX(Global& global) {
std::vector<Block*> blocks; for (auto& line : global.lines) {
};
void SetX(Global& global, std::vector<Line> lines) {
for (auto& line : lines) {
std::vector<box_helper::Element> elements; std::vector<box_helper::Element> elements;
elements.reserve(line.blocks.size()); elements.reserve(line.blocks.size());
for (auto* block : line.blocks) { for (auto* block : line.blocks) {
@@ -110,19 +116,24 @@ void SetX(Global& global, std::vector<Line> lines) {
int x = 0; int x = 0;
for (size_t i = 0; i < line.blocks.size(); ++i) { for (size_t i = 0; i < line.blocks.size(); ++i) {
line.blocks[i]->dim_x = elements[i].size;
line.blocks[i]->x = x; line.blocks[i]->x = x;
line.blocks[i]->dim_x = elements[i].size;
x += elements[i].size; x += elements[i].size;
x += global.config.gap_x; x += global.config.gap_x;
} }
} }
for (auto& line : global.lines) {
line.x = 0;
line.dim_x = global.size_x;
}
} }
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)
void SetY(Global& g, std::vector<Line> lines) { void SetY(Global& g) {
std::vector<box_helper::Element> elements; std::vector<box_helper::Element> elements;
elements.reserve(lines.size()); elements.reserve(g.lines.size());
for (auto& line : lines) { for (auto& line : g.lines) {
box_helper::Element element; box_helper::Element element;
element.flex_shrink = line.blocks.front()->flex_shrink_y; element.flex_shrink = line.blocks.front()->flex_shrink_y;
element.flex_grow = line.blocks.front()->flex_grow_y; element.flex_grow = line.blocks.front()->flex_grow_y;
@@ -202,9 +213,9 @@ void SetY(Global& g, std::vector<Line> lines) {
} }
// [Align items] // [Align items]
for (size_t i = 0; i < lines.size(); ++i) { for (size_t i = 0; i < g.lines.size(); ++i) {
auto& element = elements[i]; auto& element = elements[i];
for (auto* block : lines[i].blocks) { for (auto* block : g.lines[i].blocks) {
const bool stretch = const bool stretch =
block->flex_grow_y != 0 || block->flex_grow_y != 0 ||
g.config.align_content == FlexboxConfig::AlignContent::Stretch; g.config.align_content == FlexboxConfig::AlignContent::Stretch;
@@ -237,10 +248,16 @@ void SetY(Global& g, std::vector<Line> lines) {
} }
} }
} }
ys.push_back(g.size_y);
for (size_t i = 0; i < g.lines.size(); ++i) {
g.lines[i].y = ys[i];
g.lines[i].dim_y = ys[i + 1] - ys[i];
}
} }
void JustifyContent(Global& g, std::vector<Line> lines) { void JustifyContent(Global& g) {
for (auto& line : lines) { for (auto& line : g.lines) {
Block* last = line.blocks.back(); Block* last = line.blocks.back();
int remaining_space = g.size_x - last->x - last->dim_x; int remaining_space = g.size_x - last->x - last->dim_x;
switch (g.config.justify_content) { switch (g.config.justify_content) {
@@ -315,38 +332,36 @@ void Compute2(Global& global) {
void Compute3(Global& global) { void Compute3(Global& global) {
// Step 1: Lay out every elements into rows: // Step 1: Lay out every elements into rows:
std::vector<Line> lines;
{ {
Line line; Line line;
int x = 0; int x = 0;
line.blocks.reserve(global.blocks.size());
for (auto& block : global.blocks) { for (auto& block : global.blocks) {
// Does it fit the end of the row? // Does it fit the end of the row?
// No? Then we need to start a new one: // No? Then we need to start a new one:
if (x + block.min_size_x > global.size_x) { if (x + block.min_size_x > global.size_x) {
x = 0; x = 0;
if (!line.blocks.empty()) { if (!line.blocks.empty()) {
lines.push_back(std::move(line)); global.lines.push_back(std::move(line));
} }
line = Line(); line = Line();
} }
block.line = static_cast<int>(lines.size()); block.line = static_cast<int>(global.lines.size());
block.line_position = static_cast<int>(line.blocks.size()); block.line_position = static_cast<int>(line.blocks.size());
line.blocks.push_back(&block); line.blocks.push_back(&block);
x += block.min_size_x + global.config.gap_x; x += block.min_size_x + global.config.gap_x;
} }
if (!line.blocks.empty()) { if (!line.blocks.empty()) {
lines.push_back(std::move(line)); global.lines.push_back(std::move(line));
} }
} }
// Step 2: Set positions on the X axis. // Step 2: Set positions on the X axis.
SetX(global, lines); SetX(global);
JustifyContent(global, lines); // Distribute remaining space. JustifyContent(global); // Distribute remaining space.
// Step 3: Set positions on the Y axis. // Step 3: Set positions on the Y axis.
SetY(global, lines); SetY(global);
} }
} // namespace } // namespace

View File

@@ -9,6 +9,7 @@
namespace ftxui::flexbox_helper { namespace ftxui::flexbox_helper {
// A block is a rectangle in the flexbox.
struct Block { struct Block {
// Input: // Input:
int min_size_x = 0; int min_size_x = 0;
@@ -28,8 +29,18 @@ struct Block {
bool overflow = false; bool overflow = false;
}; };
// A line is a row of blocks.
struct Line {
std::vector<Block*> blocks;
int x = 0;
int y = 0;
int dim_x = 0;
int dim_y = 0;
};
struct Global { struct Global {
std::vector<Block> blocks; std::vector<Block> blocks;
std::vector<Line> lines;
FlexboxConfig config; FlexboxConfig config;
int size_x; int size_x;
int size_y; int size_y;

View File

@@ -36,13 +36,12 @@ Decorator focusPositionRelative(float x, float y) {
void ComputeRequirement() override { void ComputeRequirement() override {
NodeDecorator::ComputeRequirement(); NodeDecorator::ComputeRequirement();
requirement_.selection = Requirement::Selection::NORMAL; requirement_.focused.enabled = true;
requirement_.focused.node = this;
Box& box = requirement_.selected_box; requirement_.focused.box.x_min = int(float(requirement_.min_x) * x_);
box.x_min = int(float(requirement_.min_x) * x_); requirement_.focused.box.y_min = int(float(requirement_.min_y) * y_);
box.y_min = int(float(requirement_.min_y) * y_); requirement_.focused.box.x_max = int(float(requirement_.min_x) * x_);
box.x_max = int(float(requirement_.min_x) * x_); requirement_.focused.box.y_max = int(float(requirement_.min_y) * y_);
box.y_max = int(float(requirement_.min_y) * y_);
} }
private: private:
@@ -75,9 +74,9 @@ Decorator focusPosition(int x, int y) {
void ComputeRequirement() override { void ComputeRequirement() override {
NodeDecorator::ComputeRequirement(); NodeDecorator::ComputeRequirement();
requirement_.selection = Requirement::Selection::NORMAL; requirement_.focused.enabled = false;
Box& box = requirement_.selected_box; Box& box = requirement_.focused.box;
box.x_min = x_; box.x_min = x_;
box.y_min = y_; box.y_min = y_;
box.x_max = x_; box.x_max = x_;

View File

@@ -6,28 +6,28 @@
#include <utility> // for move #include <utility> // for move
#include "ftxui/dom/elements.hpp" // for Element, unpack, Elements, focus, frame, select, xframe, yframe #include "ftxui/dom/elements.hpp" // for Element, unpack, Elements, focus, frame, select, xframe, yframe
#include "ftxui/dom/node.hpp" // for Node, Elements #include "ftxui/dom/node.hpp" // for Node, Elements
#include "ftxui/dom/requirement.hpp" // for Requirement, Requirement::FOCUSED, Requirement::SELECTED #include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/screen/box.hpp" // for Box #include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/screen.hpp" // for Screen, Screen::Cursor #include "ftxui/screen/screen.hpp" // for Screen, Screen::Cursor
#include "ftxui/util/autoreset.hpp" // for AutoReset #include "ftxui/util/autoreset.hpp" // for AutoReset
namespace ftxui { namespace ftxui {
namespace { namespace {
class Select : public Node { class Focus : public Node {
public: public:
explicit Select(Elements children) : Node(std::move(children)) {} explicit Focus(Elements children) : Node(std::move(children)) {}
void ComputeRequirement() override { void ComputeRequirement() override {
Node::ComputeRequirement(); Node::ComputeRequirement();
requirement_ = children_[0]->requirement(); requirement_ = children_[0]->requirement();
auto& selected_box = requirement_.selected_box; requirement_.focused.enabled = true;
selected_box.x_min = 0; requirement_.focused.node = this;
selected_box.y_min = 0; requirement_.focused.box.x_min = 0;
selected_box.x_max = requirement_.min_x - 1; requirement_.focused.box.y_min = 0;
selected_box.y_max = requirement_.min_y - 1; requirement_.focused.box.x_max = requirement_.min_x - 1;
requirement_.selection = Requirement::SELECTED; requirement_.focused.box.y_max = requirement_.min_y - 1;
} }
void SetBox(Box box) override { void SetBox(Box box) override {
@@ -36,65 +36,21 @@ class Select : public Node {
} }
}; };
class Focus : public Select {
public:
using Select::Select;
void ComputeRequirement() override {
Select::ComputeRequirement();
requirement_.selection = Requirement::FOCUSED;
}
void Render(Screen& screen) override {
Select::Render(screen);
// Setting the cursor to the right position allow folks using CJK (China,
// Japanese, Korean, ...) characters to see their [input method editor]
// displayed at the right location. See [issue].
//
// [input method editor]:
// https://en.wikipedia.org/wiki/Input_method
//
// [issue]:
// https://github.com/ArthurSonzogni/FTXUI/issues/2#issuecomment-505282355
//
// Unfortunately, Microsoft terminal do not handle properly hidding the
// cursor. Instead the character under the cursor is hidden, which is a big
// problem. As a result, we can't enable setting cursor to the right
// location. It will be displayed at the bottom right corner.
// See:
// https://github.com/microsoft/terminal/issues/1203
// https://github.com/microsoft/terminal/issues/3093
#if !defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
screen.SetCursor(Screen::Cursor{
box_.x_min,
box_.y_min,
Screen::Cursor::Shape::Hidden,
});
#endif
}
};
class Frame : public Node { class Frame : public Node {
public: public:
Frame(Elements children, bool x_frame, bool y_frame) Frame(Elements children, bool x_frame, bool y_frame)
: Node(std::move(children)), x_frame_(x_frame), y_frame_(y_frame) {} : Node(std::move(children)), x_frame_(x_frame), y_frame_(y_frame) {}
void ComputeRequirement() override {
Node::ComputeRequirement();
requirement_ = children_[0]->requirement();
}
void SetBox(Box box) override { void SetBox(Box box) override {
Node::SetBox(box); Node::SetBox(box);
auto& selected_box = requirement_.selected_box; auto& focused_box = requirement_.focused.box;
Box children_box = box; Box children_box = box;
if (x_frame_) { if (x_frame_) {
const int external_dimx = box.x_max - box.x_min; const int external_dimx = box.x_max - box.x_min;
const int internal_dimx = std::max(requirement_.min_x, external_dimx); const int internal_dimx = std::max(requirement_.min_x, external_dimx);
const int focused_dimx = selected_box.x_max - selected_box.x_min; const int focused_dimx = focused_box.x_max - focused_box.x_min;
int dx = selected_box.x_min - external_dimx / 2 + focused_dimx / 2; int dx = focused_box.x_min - external_dimx / 2 + focused_dimx / 2;
dx = std::max(0, std::min(internal_dimx - external_dimx - 1, dx)); dx = std::max(0, std::min(internal_dimx - external_dimx - 1, dx));
children_box.x_min = box.x_min - dx; children_box.x_min = box.x_min - dx;
children_box.x_max = box.x_min + internal_dimx - dx; children_box.x_max = box.x_min + internal_dimx - dx;
@@ -103,8 +59,8 @@ class Frame : public Node {
if (y_frame_) { if (y_frame_) {
const int external_dimy = box.y_max - box.y_min; const int external_dimy = box.y_max - box.y_min;
const int internal_dimy = std::max(requirement_.min_y, external_dimy); const int internal_dimy = std::max(requirement_.min_y, external_dimy);
const int focused_dimy = selected_box.y_max - selected_box.y_min; const int focused_dimy = focused_box.y_max - focused_box.y_min;
int dy = selected_box.y_min - external_dimy / 2 + focused_dimy / 2; int dy = focused_box.y_min - external_dimy / 2 + focused_dimy / 2;
dy = std::max(0, std::min(internal_dimy - external_dimy - 1, dy)); dy = std::max(0, std::min(internal_dimy - external_dimy - 1, dy));
children_box.y_min = box.y_min - dy; children_box.y_min = box.y_min - dy;
children_box.y_max = box.y_min + internal_dimy - dy; children_box.y_max = box.y_min + internal_dimy - dy;
@@ -130,33 +86,29 @@ class FocusCursor : public Focus {
: Focus(std::move(children)), shape_(shape) {} : Focus(std::move(children)), shape_(shape) {}
private: private:
void Render(Screen& screen) override { void ComputeRequirement() override {
Select::Render(screen); // NOLINT Focus::ComputeRequirement(); // NOLINT
screen.SetCursor(Screen::Cursor{ requirement_.focused.cursor_shape = shape_;
box_.x_min,
box_.y_min,
shape_,
});
} }
Screen::Cursor::Shape shape_; Screen::Cursor::Shape shape_;
}; };
} // namespace } // namespace
/// @brief Set the `child` to be the one selected among its siblings. /// @brief Set the `child` to be the one focused among its siblings.
/// @param child The element to be selected.
/// @ingroup dom
Element select(Element child) {
return std::make_shared<Select>(unpack(std::move(child)));
}
/// @brief Set the `child` to be the one in focus globally.
/// @param child The element to be focused. /// @param child The element to be focused.
/// @ingroup dom /// @ingroup dom
Element focus(Element child) { Element focus(Element child) {
return std::make_shared<Focus>(unpack(std::move(child))); return std::make_shared<Focus>(unpack(std::move(child)));
} }
/// This is deprecated. Use `focus` instead.
/// @brief Set the `child` to be the one focused among its siblings.
/// @param child The element to be focused.
Element select(Element child) {
return focus(std::move(child));
}
/// @brief Allow an element to be displayed inside a 'virtual' area. It size can /// @brief Allow an element to be displayed inside a 'virtual' area. It size can
/// be larger than its container. In this case only a smaller portion is /// be larger than its container. In this case only a smaller portion is
/// displayed. The view is scrollable to make the focused element visible. /// displayed. The view is scrollable to make the focused element visible.

View File

@@ -49,13 +49,7 @@ class GridBox : public Node {
} }
void ComputeRequirement() override { void ComputeRequirement() override {
requirement_.min_x = 0; requirement_ = Requirement{};
requirement_.min_y = 0;
requirement_.flex_grow_x = 0;
requirement_.flex_grow_y = 0;
requirement_.flex_shrink_x = 0;
requirement_.flex_shrink_y = 0;
for (auto& line : lines_) { for (auto& line : lines_) {
for (auto& cell : line) { for (auto& cell : line) {
cell->ComputeRequirement(); cell->ComputeRequirement();
@@ -75,19 +69,15 @@ class GridBox : public Node {
requirement_.min_x = Integrate(size_x); requirement_.min_x = Integrate(size_x);
requirement_.min_y = Integrate(size_y); requirement_.min_y = Integrate(size_y);
// Forward the selected/focused child state: // Forward the focused/focused child state:
requirement_.selection = Requirement::NORMAL;
for (int x = 0; x < x_size; ++x) { for (int x = 0; x < x_size; ++x) {
for (int y = 0; y < y_size; ++y) { for (int y = 0; y < y_size; ++y) {
if (requirement_.selection >= lines_[y][x]->requirement().selection) { if (requirement_.focused.enabled ||
!lines_[y][x]->requirement().focused.enabled) {
continue; continue;
} }
requirement_.selection = lines_[y][x]->requirement().selection; requirement_.focused = lines_[y][x]->requirement().focused;
requirement_.selected_box = lines_[y][x]->requirement().selected_box; requirement_.focused.box.Shift(size_x[x], size_y[y]);
requirement_.selected_box.x_min += size_x[x];
requirement_.selected_box.x_max += size_x[x];
requirement_.selected_box.y_min += size_y[y];
requirement_.selected_box.y_max += size_y[y];
} }
} }
} }

View File

@@ -7,7 +7,7 @@
#include <string> // for allocator, basic_string, string #include <string> // for allocator, basic_string, string
#include <vector> // for vector #include <vector> // for vector
#include "ftxui/dom/elements.hpp" // for text, operator|, Element, flex, Elements, flex_grow, flex_shrink, vtext, gridbox, vbox, focus, operator|=, border, frame #include "ftxui/dom/elements.hpp" // for text, operator|, Element, flex, Elements, flex_grow, flex_shrink, vtext, gridbox, vbox, select, operator|=, border, frame
#include "ftxui/dom/node.hpp" // for Render #include "ftxui/dom/node.hpp" // for Render
#include "ftxui/screen/screen.hpp" // for Screen #include "ftxui/screen/screen.hpp" // for Screen

View File

@@ -11,8 +11,8 @@
#include "ftxui/dom/elements.hpp" // for Element, Elements, hbox #include "ftxui/dom/elements.hpp" // for Element, Elements, hbox
#include "ftxui/dom/node.hpp" // for Node, Elements #include "ftxui/dom/node.hpp" // for Node, Elements
#include "ftxui/dom/requirement.hpp" // for Requirement #include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/dom/selection.hpp" // for Selection
#include "ftxui/screen/box.hpp" // for Box #include "ftxui/screen/box.hpp" // for Box
namespace ftxui { namespace ftxui {
namespace { namespace {
@@ -20,22 +20,20 @@ class HBox : public Node {
public: public:
explicit HBox(Elements children) : Node(std::move(children)) {} explicit HBox(Elements children) : Node(std::move(children)) {}
private:
void ComputeRequirement() override { void ComputeRequirement() override {
requirement_.min_x = 0; requirement_ = Requirement{};
requirement_.min_y = 0;
requirement_.flex_grow_x = 0;
requirement_.flex_grow_y = 0;
requirement_.flex_shrink_x = 0;
requirement_.flex_shrink_y = 0;
requirement_.selection = Requirement::NORMAL;
for (auto& child : children_) { for (auto& child : children_) {
child->ComputeRequirement(); child->ComputeRequirement();
if (requirement_.selection < child->requirement().selection) {
requirement_.selection = child->requirement().selection; // Propagate the focused requirement.
requirement_.selected_box = child->requirement().selected_box; if (requirement_.focused.Prefer(child->requirement().focused)) {
requirement_.selected_box.x_min += requirement_.min_x; requirement_.focused = child->requirement().focused;
requirement_.selected_box.x_max += requirement_.min_x; requirement_.focused.box.Shift(requirement_.min_x, 0);
} }
// Extend the min_x and min_y to contain all the children
requirement_.min_x += child->requirement().min_x; requirement_.min_x += child->requirement().min_x;
requirement_.min_y = requirement_.min_y =
std::max(requirement_.min_y, child->requirement().min_y); std::max(requirement_.min_y, child->requirement().min_y);
@@ -64,6 +62,19 @@ class HBox : public Node {
x = box.x_max + 1; x = box.x_max + 1;
} }
} }
void Select(Selection& selection) override {
// If this Node box_ doesn't intersect with the selection, then no
// selection.
if (Box::Intersection(selection.GetBox(), box_).IsEmpty()) {
return;
}
Selection selection_saturated = selection.SaturateHorizontal(box_);
for (auto& child : children_) {
child->Select(selection_saturated);
}
}
}; };
} // namespace } // namespace

35
src/ftxui/dom/italic.cpp Normal file
View File

@@ -0,0 +1,35 @@
// Copyright 2025 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#include <memory> // for make_shared
#include <utility> // for move
#include "ftxui/dom/elements.hpp" // for Element, underlinedDouble
#include "ftxui/dom/node.hpp" // for Node
#include "ftxui/dom/node_decorator.hpp" // for NodeDecorator
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/screen.hpp" // for Pixel, Screen
namespace ftxui {
/// @brief Apply a underlinedDouble to text.
/// @ingroup dom
Element italic(Element child) {
class Impl : public NodeDecorator {
public:
using NodeDecorator::NodeDecorator;
void Render(Screen& screen) override {
for (int y = box_.y_min; y <= box_.y_max; ++y) {
for (int x = box_.x_min; x <= box_.x_max; ++x) {
screen.PixelAt(x, y).italic = true;
}
}
Node::Render(screen);
}
};
return std::make_shared<Impl>(std::move(child));
}
} // namespace ftxui

Some files were not shown because too many files have changed in this diff Show More