9 Commits
v0.9 ... v0.10

Author SHA1 Message Date
ArthurSonzogni
75cd2b0fca Fix workflow 2021-09-30 21:29:41 +02:00
ArthurSonzogni
0a5e9f2a2f Add workflow creating releases. 2021-09-30 21:25:47 +02:00
ArthurSonzogni
66cdf9b2a5 Fix: box drawing comparison.
There was a bug. Converting the bitfield to int using the union wasn't
working correctly.
2021-09-30 20:55:47 +02:00
ArthurSonzogni
84287eb217 Use vscroll_indicator in examples. 2021-09-30 20:55:47 +02:00
Arthur Sonzogni
535974d291 Update README.md 2021-09-30 20:55:47 +02:00
ArthurSonzogni
753502998c Add a C++ badge. 2021-09-30 20:55:47 +02:00
ArthurSonzogni
31b5fac9c5 Add fuzzer to Maybe & Dropdown. 2021-09-30 20:55:47 +02:00
ArthurSonzogni
76b2f17488 fix: vscroll_indicator. Reserve one cell. 2021-09-30 20:55:47 +02:00
Arthur Sonzogni
c5ef0c7fb5 feat: Dropdown select menu. (#214)
Dom
 - `vscroll_indicator`. Show a scrollback indicator on the right.

Component
 - `Maybe`: Display an component conditionnally based on a boolean.
 - `Dropdown`: A dropdown select list.

This address:
https://github.com/ArthurSonzogni/FTXUI/issues/204
2021-09-30 20:55:47 +02:00
21 changed files with 420 additions and 58 deletions

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

@@ -0,0 +1,39 @@
name: Release
on:
create:
tags:
-v*
jobs:
build:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: seanmiddleditch/gha-setup-ninja@master
- name: Build
run: >
mkdir build;
cd build;
cmake ..
-DCMAKE_CXX_COMPILER=clang++
-DFTXUI_BUILD_DOCS=OFF
-DFTXUI_BUILD_EXAMPLES=OFF
-DFTXUI_BUILD_TESTS=OFF
-DFTXUI_BUILD_TESTS_FUZZER=OFF
-DFTXUI_ENABLE_INSTALL=ON;
cmake --build . --config Release;
make package;
- name: Upload
uses: softprops/action-gh-release@v1
with:
files: build/ftxui-*
draft: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,5 +1,19 @@
# Changelog
## Current
## 0.10 (2021-09-30)
## Bug
- Fix the automated merge of borders.
### Dom
- `vscroll_indicator`. Show a scrollbar indicator on the right.
### Component
- `Maybe`: Display an component conditionnally based on a boolean.
- `Dropdown`: A dropdown select list.
## 0.9 (2021-09-26)
The initial release where changelog where written.

View File

@@ -4,7 +4,7 @@ include(cmake/ftxui_git_version.cmake)
project(ftxui
LANGUAGES CXX
VERSION 0.9.${git_version}
VERSION 0.10.${git_version}
)
option(FTXUI_BUILD_DOCS "Set to ON to build tests" ON)
@@ -57,14 +57,15 @@ add_library(dom STATIC
src/ftxui/dom/frame.cpp
src/ftxui/dom/gauge.cpp
src/ftxui/dom/graph.cpp
src/ftxui/dom/hbox.cpp
src/ftxui/dom/gridbox.cpp
src/ftxui/dom/hbox.cpp
src/ftxui/dom/hflow.cpp
src/ftxui/dom/inverted.cpp
src/ftxui/dom/node.cpp
src/ftxui/dom/node_decorator.cpp
src/ftxui/dom/paragraph.cpp
src/ftxui/dom/reflect.cpp
src/ftxui/dom/scroll_indicator.cpp
src/ftxui/dom/separator.cpp
src/ftxui/dom/size.cpp
src/ftxui/dom/spinner.cpp
@@ -87,8 +88,10 @@ add_library(component STATIC
src/ftxui/component/checkbox.cpp
src/ftxui/component/component.cpp
src/ftxui/component/container.cpp
src/ftxui/component/dropdown.cpp
src/ftxui/component/event.cpp
src/ftxui/component/input.cpp
src/ftxui/component/maybe.cpp
src/ftxui/component/menu.cpp
src/ftxui/component/radiobox.cpp
src/ftxui/component/radiobox.cpp
@@ -120,10 +123,6 @@ if (FTXUI_BUILD_TESTS AND ${CMAKE_VERSION} VERSION_GREATER "3.11.4")
include(cmake/ftxui_test.cmake)
endif()
if(FTXUI_ENABLE_INSTALL)
include(cmake/ftxui_install.cmake)
endif()
if(FTXUI_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
@@ -134,3 +133,9 @@ endif()
include(cmake/iwyu.cmake)
include(cmake/ftxui_export.cmake)
if(FTXUI_ENABLE_INSTALL)
include(cmake/ftxui_install.cmake)
include(cmake/ftxui_package.cmake)
endif()

View File

@@ -1,24 +1,29 @@
# FTXUI
<p align="center">
<img src="./examples/component/homescreen.gif" alt="Demo image"></img>
<br/>
<a href="#"><img src="https://img.shields.io/badge/c++-%2300599C.svg?style=flat&logo=c%2B%2B&logoColor=white"></img></a>
<a href="http://opensource.org/licenses/MIT"><img src="https://img.shields.io/github/license/arthursonzogni/FTXUI?color=black"></img></a>
<a href="#"><img src="https://img.shields.io/github/stars/ArthurSonzogni/FTXUI"></img></a>
<a href="#"><img src="https://img.shields.io/github/forks/ArthurSonzogni/FTXUI"></img></a>
<a href="#"><img src="https://img.shields.io/github/repo-size/ArthurSonzogni/FTXUI"></img></a>
<a href="https://github.com/ArthurSonzogni/FTXUI/issues"><img src="https://img.shields.io/github/issues/ArthurSonzogni/FTXUI"></img></a>
<a href="https://github.com/ArthurSonzogni/FTXUI/graphs/contributors"><img src="https://img.shields.io/github/contributors/arthursonzogni/FTXUI?color=blue"></img></a>
[![issues][badge.issues]][issues]
[![license][badge.license]][license]
[![contributors][badge.contributors]][contributors]
<br/>
<a href="https://github.com/ArthurSonzogni/FTXUI/wiki">Documentation</a> ·
<a href="https://github.com/ArthurSonzogni/FTXUI/issues">Report Bug</a> ·
<a href="https://arthursonzogni.github.io/FTXUI/examples.html">Examples</a> .
<a href="https://github.com/ArthurSonzogni/FTXUI/issues">Request Feature</a> ·
<a href="https://github.com/ArthurSonzogni/FTXUI/pulls">Send a Pull Request</a>
[badge.issues]: https://img.shields.io/github/issues-raw/arthursonzogni/FTXUI
[badge.license]: https://img.shields.io/github/license/arthursonzogni/FTXUI?color=black
[badge.contributors]: https://img.shields.io/github/contributors/arthursonzogni/FTXUI?color=blue
</p>
[issues]: https://github.com/ArthurSonzogni/FTXUI/issues
[license]: http://opensource.org/licenses/MIT
[contributors]: https://github.com/ArthurSonzogni/FTXUI/graphs/contributors
## FTXUI
**Functional Terminal (X) User interface**
<i>Functional Terminal (X) User interface</i>
A simple C++ library for terminal based user interface.
## Demo:
![Demo image](./examples/component/homescreen.gif)
## Feature
* Functional style. Inspired by
[[1]](https://hackernoon.com/building-reactive-terminal-interfaces-in-c-d392ce34e649?gi=d9fb9ce35901)
@@ -48,7 +53,7 @@ A simple C++ library for terminal based user interface.
[link.windows-msvc]: https://github.com/ArthurSonzogni/FTXUI/actions/workflows/windows-msvc.yaml
[link.mac-clang]: https://github.com/ArthurSonzogni/FTXUI/actions/workflows/mac-clang.yaml
## Example:
## Example
~~~cpp
vbox({
hbox({
@@ -69,7 +74,7 @@ A simple C++ library for terminal based user interface.
└────────────────────────────────────────────────────────────────────────────┘
~~~
# Documentation:
# Documentation
- [Starter example project](https://github.com/ArthurSonzogni/ftxui-starter)
- [Documentation](https://arthursonzogni.github.io/FTXUI/)
@@ -94,11 +99,11 @@ Feel free to add your projects here:
- [todoman](https://github.com/aaleino/todoman)
- [TimeAccumulator](https://github.com/asari555/TimeAccumulator)
## Hosted on:
## Hosted on
* [github](https://github.com/ArthurSonzogni/ftxui)
* [gitlab](https://gitlab.com/ArthurSonzogni/ftxui)
## External package:
## External package
It is **highly** recommanded to use cmake FetchContent to depends on FTXUI. This
way you can specify which commit you would like to depends on.

16
cmake/ftxui_package.cmake Normal file
View File

@@ -0,0 +1,16 @@
set(CPACK_GENERATOR "DEB;External;RPM;STGZ;TBZ2;TGZ;TXZ;TZ;TZST;ZIP")
set(CPACK_DEBIAN_PACKAGE_DEPENDS " ")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE_URL "https://github.com/ArthurSonzogni/FTXUI/")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Arthur Sonzogni")
set(CPACK_DEBIAN_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A simple C++ Terminal UI library")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/ArthurSonzogni/FTXUI/")
set(CPACK_PACKAGE_NAME "ftxui")
set(CPACK_PACKAGE_VENDOR "Arthur Sonzogni")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
include(CPack)

View File

@@ -2,19 +2,21 @@ set(DIRECTORY_LIB component)
example(button)
example(checkbox)
example(nested_screen)
example(checkbox_in_frame)
example(composition)
example(dropdown)
example(gallery)
example(homescreen)
example(input)
example(maybe)
example(menu)
example(menu_in_frame)
example(menu2)
example(menu_multiple)
example(menu_entries)
example(menu_in_frame)
example(menu_multiple)
example(menu_style)
example(modal_dialog)
example(nested_screen)
example(print_key_press)
example(radiobox)
example(radiobox_in_frame)

View File

@@ -24,7 +24,8 @@ int main(int argc, const char* argv[]) {
}
auto renderer = Renderer(container, [&] {
return container->Render() | frame | size(HEIGHT, LESS_THAN, 10) | border;
return container->Render() | vscroll_indicator | frame |
size(HEIGHT, LESS_THAN, 10) | border;
});
auto screen = ScreenInteractive::FitComponent();

View File

@@ -0,0 +1,48 @@
#include <functional> // for function
#include <iostream> // for basic_ostream::operator<<, operator<<, endl, basic_ostream, basic_ostream<>::__ostream_type, cout, ostream
#include <string> // for string, basic_string, allocator
#include <vector> // for vector
#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Menu
#include "ftxui/component/component_options.hpp" // for MenuOption
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
int main(int argc, const char* argv[]) {
using namespace ftxui;
std::vector<std::string> entries = {
"tribute", "clearance", "ally", "bend", "electronics",
"module", "era", "cultural", "sniff", "nationalism",
"negotiation", "deliver", "figure", "east",
"tribute", "clearance", "ally", "bend", "electronics",
"module", "era", "cultural", "sniff", "nationalism",
"negotiation", "deliver", "figure", "east",
"tribute", "clearance", "ally", "bend", "electronics",
"module", "era", "cultural", "sniff", "nationalism",
"negotiation", "deliver", "figure", "east",
};
int selected_1 = 0;
int selected_2 = 0;
int selected_3 = 0;
int selected_4 = 0;
auto layout = Container::Vertical({
Container::Horizontal({
Dropdown(&entries, &selected_1),
Dropdown(&entries, &selected_2),
}),
Container::Horizontal({
Dropdown(&entries, &selected_3),
Dropdown(&entries, &selected_4),
}),
});
auto screen = ScreenInteractive::FitComponent();
screen.Loop(layout);
}
// 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.

View File

@@ -214,12 +214,13 @@ int main(int argc, const char* argv[]) {
};
auto compiler_renderer = Renderer(compiler_component, [&] {
auto compiler_win = window(text("Compiler"), compiler->Render() | frame);
auto flags_win = window(text("Flags"), flags->Render() | frame);
auto compiler_win = window(text("Compiler"),
compiler->Render() | vscroll_indicator | frame);
auto flags_win =
window(text("Flags"), flags->Render() | vscroll_indicator | frame);
auto executable_win = window(text("Executable:"), executable_->Render());
auto input_win =
window(text("Input"),
hbox({
window(text("Input"), hbox({
vbox({
hbox({
text("Add: "),
@@ -229,7 +230,8 @@ int main(int argc, const char* argv[]) {
filler(),
}),
separator(),
input->Render() | frame | size(HEIGHT, EQUAL, 3) | flex,
input->Render() | vscroll_indicator | frame |
size(HEIGHT, EQUAL, 3) | flex,
}));
return vbox({
hbox({
@@ -240,7 +242,7 @@ int main(int argc, const char* argv[]) {
input_win | size(WIDTH, EQUAL, 60),
}),
filler(),
}) | size(HEIGHT, LESS_THAN, 6),
}) | size(HEIGHT, LESS_THAN, 8),
hflow(render_command()) | flex_grow,
}) |
flex_grow | border;

View File

@@ -0,0 +1,47 @@
#include <functional> // for function
#include <iostream> // for basic_ostream::operator<<, operator<<, endl, basic_ostream, basic_ostream<>::__ostream_type, cout, ostream
#include <string> // for string, basic_string, allocator
#include <vector> // for vector
#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Menu
#include "ftxui/component/component_options.hpp" // for MenuOption
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
using namespace ftxui;
Component Border(Component child) {
return Renderer(child, [child] { return child->Render() | border; });
}
int main(int argc, const char* argv[]) {
std::vector<std::string> entries = {
"entry 1",
"entry 2",
"entry 3",
};
int menu_1_selected = 0;
int menu_2_selected = 0;
auto menu_1 = Radiobox(&entries, &menu_1_selected);
auto menu_2 = Radiobox(&entries, &menu_2_selected);
menu_1 = Border(menu_1);
menu_2 = Border(menu_2);
bool menu_1_show = false;
bool menu_2_show = false;
auto layout = Container::Vertical({
Checkbox("Show menu_1", &menu_1_show),
Maybe(menu_1, &menu_1_show),
Checkbox("Show menu_2", &menu_2_show),
Maybe(menu_2, &menu_2_show),
});
auto screen = ScreenInteractive::TerminalOutput();
screen.Loop(layout);
}
// 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.

View File

@@ -18,7 +18,8 @@ int main(int argc, const char* argv[]) {
entries.push_back("Entry " + std::to_string(i));
auto radiobox = Menu(&entries, &selected);
auto renderer = Renderer(radiobox, [&] {
return radiobox->Render() | frame | size(HEIGHT, LESS_THAN, 10) | border;
return radiobox->Render() | vscroll_indicator | frame |
size(HEIGHT, LESS_THAN, 10) | border;
});
auto screen = ScreenInteractive::FitComponent();

View File

@@ -18,7 +18,8 @@ int main(int argc, const char* argv[]) {
entries.push_back("RadioBox " + std::to_string(i));
auto radiobox = Radiobox(&entries, &selected);
auto renderer = Renderer(radiobox, [&] {
return radiobox->Render() | frame | size(HEIGHT, LESS_THAN, 10) | border;
return radiobox->Render() | vscroll_indicator | frame |
size(HEIGHT, LESS_THAN, 10) | border;
});
auto screen = ScreenInteractive::FitComponent();

View File

@@ -39,6 +39,7 @@ Component Menu(ConstStringListRef entries,
int* selected_,
Ref<MenuOption> = {});
Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> = {});
Component Dropdown(ConstStringListRef entries, int* selected);
Component Radiobox(ConstStringListRef entries,
int* selected_,
Ref<RadioboxOption> option = {});
@@ -55,6 +56,7 @@ Component Renderer(Component child, std::function<Element()>);
Component Renderer(std::function<Element()>);
Component Renderer(std::function<Element(bool /* focused */)>);
Component CatchEvent(Component child, std::function<bool(Event)>);
Component Maybe(Component, bool* show);
namespace Container {
Component Vertical(Components children);

View File

@@ -74,6 +74,7 @@ struct Event {
bool operator!=(const Event& other) const { return !operator==(other); }
//--- State section ----------------------------------------------------------
ScreenInteractive* screen_ = nullptr;
private:
friend ComponentBase;
friend ScreenInteractive;
@@ -95,8 +96,6 @@ struct Event {
struct Cursor cursor_;
};
std::string input_;
ScreenInteractive* screen_ = nullptr;
};
} // namespace ftxui

View File

@@ -104,6 +104,8 @@ Element yframe(Element);
Element focus(Element);
Element select(Element);
Element vscroll_indicator(Element);
// --- Util --------------------------------------------------------------------
Element hcenter(Element);
Element vcenter(Element);

View File

@@ -57,7 +57,7 @@ Component GeneratorComponent(const char*& data, size_t& size, int depth) {
if (depth <= 0)
return Button(GeneratorString(data, size), [] {});
switch (value % 16) {
switch (value % 18) {
case 1:
return Checkbox(GeneratorString(data, size), &g_bool);
case 2:
@@ -102,6 +102,10 @@ Component GeneratorComponent(const char*& data, size_t& size, int depth) {
&g_int);
case 15:
return Container::Tab(GeneratorComponents(data, size, depth - 1), &g_int);
case 16:
return Maybe(GeneratorComponent(data, size, depth - 1), &g_bool);
case 17:
return Dropdown(&g_list, &g_int);
default:
return Button(GeneratorString(data, size), [] {});
}

View File

@@ -0,0 +1,54 @@
#include "ftxui/component/component.hpp"
#include "ftxui/component/component_base.hpp"
#include "ftxui/component/event.hpp"
namespace ftxui {
Component Dropdown(ConstStringListRef entries, int* selected) {
class Impl : public ComponentBase {
public:
Impl(ConstStringListRef entries, int* selected)
: entries_(std::move(entries)), selected_(selected) {
CheckboxOption option;
option.style_checked = "↓│";
option.style_unchecked = "→│";
checkbox_ = Checkbox(&title_, &show_, option),
radiobox_ = Radiobox(entries_, selected_);
Add(Container::Vertical({
checkbox_,
Maybe(radiobox_, &show_),
}));
}
Element Render() override {
title_ = entries_[*selected_];
if (show_) {
return vbox({
checkbox_->Render(),
separator(),
radiobox_->Render() | vscroll_indicator | frame |
size(HEIGHT, LESS_THAN, 12),
}) |
border;
}
return vbox({
checkbox_->Render() | border,
filler(),
});
}
private:
ConstStringListRef entries_;
bool show_ = false;
int* selected_;
std::string title_;
Component checkbox_;
Component radiobox_;
};
return Make<Impl>(std::move(entries), selected);
}
} // namespace ftxui

View File

@@ -0,0 +1,31 @@
#include "ftxui/component/component.hpp"
#include "ftxui/component/component_base.hpp"
#include "ftxui/component/event.hpp"
namespace ftxui {
Component Maybe(Component child, bool* show) {
class Impl : public ComponentBase {
public:
Impl(bool* show): show_(show) {}
private:
Element Render() override {
return *show_ ? ComponentBase::Render() : std::make_unique<Node>();
}
bool Focusable() const override {
return *show_ && ComponentBase::Focusable();
}
bool OnEvent(Event event) override {
return *show_ && ComponentBase::OnEvent(event);
}
bool* show_;
};
auto maybe = Make<Impl>(show);
maybe->Add(std::move(child));
return maybe;
}
} // namespace ftxui

View File

@@ -0,0 +1,26 @@
#include "ftxui/component/component_base.hpp"
Component Maybe(Component child, bool* show) {
class Impl : public ComponentBase {
public:
Impl(Component child, bool* show) : ComponentBase(child), show_(show) {}
private:
Element Render() override {
if (*show_)
return ComponentBase::Render();
else
return text("");
}
bool Focusable() const override {
return *show_ && ComponentBase::Focusable();
}
bool OnEvent(Event event) override {
if (*show_)
return false return ComponentBase::OnEvent(event);
}
bool* show_;
};
return Make<Impl>(std::move(child), show);
}

View File

@@ -0,0 +1,58 @@
#include "ftxui/dom/elements.hpp"
#include "ftxui/dom/node.hpp"
#include "ftxui/dom/node_decorator.hpp" // for NodeDecorator
#include "ftxui/screen/box.hpp"
#include "ftxui/screen/screen.hpp"
namespace ftxui {
/// @brief Add a filter that will invert the foreground and the background
/// colors.
/// @ingroup dom
Element vscroll_indicator(Element child) {
class Impl : public NodeDecorator {
using NodeDecorator::NodeDecorator;
void ComputeRequirement() override {
Node::ComputeRequirement();
requirement_ = children_[0]->requirement();
requirement_.min_x++;
}
void SetBox(Box box) override {
Node::SetBox(box);
if (box_.x_min > box_.x_max)
box_.x_max--;
children_[0]->SetBox(box);
}
void Render(Screen& screen) final {
Node::Render(screen);
const Box& stencil = screen.stencil;
int size_inner = box_.y_max - box_.y_min;
int size_outter = stencil.y_max - stencil.y_min;
if (size_outter >= size_inner)
return;
int start_y = 2 * stencil.y_min + 2 * float(stencil.y_min - box_.y_min) *
(size_outter - 1) / size_inner;
int size = 2 * float(size_outter) * (size_outter - 1) / size_inner + 2;
size = std::max(size, 1);
const int x = stencil.x_max;
for (int y = stencil.y_min; y <= stencil.y_max; ++y) {
bool up = (2 * y + -1 >= start_y) && (2 * y - 1 <= start_y + size);
bool down = (2 * y - 0 >= start_y) && (2 * y - 0 <= start_y + size);
const char* c = up ? (down ? "" : "") : (down ? "" : " ");
screen.PixelAt(x, y).character = c;
screen.PixelAt(x, y).inverted = true;
}
};
};
return std::make_shared<Impl>(std::move(child));
}
} // namespace ftxui

View File

@@ -98,16 +98,21 @@ struct TileEncoding {
unsigned int down : 2;
unsigned int round : 1;
// clang-format off
bool operator<(const TileEncoding& other) const {
union Converter {
TileEncoding input;
uint16_t output = 0;
};
Converter a, b;
a.input = *this;
b.input = other;
return a.output < b.output;
if (left < other.left) return true;
if (left > other.left) return false;
if (top < other.top) return true;
if (top > other.top) return false;
if (right < other.right) return true;
if (right > other.right) return false;
if (down < other.down) return true;
if (down > other.down) return false;
if (round < other.round) return true;
if (round > other.round) return false;
return false;
}
// clang-format on
};
// clang-format off