Animation (#355)

This commit is contained in:
Arthur Sonzogni 2022-03-13 18:51:46 +01:00 committed by GitHub
parent 95c766e9e4
commit 4da63b9260
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 2439 additions and 654 deletions

View File

@ -8,6 +8,33 @@ current (development)
- **breaking**: The library prefix is now back to "lib" (the default). This
means non-cmake users should not link against "libftxui-dom" for instance.
### Component
- Animations module! Components can implement the `OnAnimation` method and the
animation::Animator to define some animated properties.
- `Menu` now support animations.
- `Button` now supports animations.
- Support SIGTSTP. (ctrl+z).
- Support task posting. `ScreenInteractive::Post(Task)`.
- `Menu` can now be used in the 4 directions, using `MenuOption.direction`.
- `Menu` can display an animated underline, using
`MenuOption.underline.enabled`.
- **breaking** All the options are now using a transform function.
- **breaking** The `Toggle` component is now implemented using `Menu`.
- **bugfix** Container::Tab implements `Focusable()`.
- **bugfix** Improved default implementations of ComponentBase `Focusable()` and
`ActiveChild()` methods.
- **bugfix** Automatically convert '\r' keys into '\n' for Linux programs that
do not send the correct code for the return key, like the 'bind'.
https://github.com/ArthurSonzogni/FTXUI/issues/337
- Add decorator for components:
- `operator|(Component, ComponentDecorator)`
- `operator|(Component, ElementDecorator)`
- `operator|=(Component, ComponentDecorator)`
- `operator|=(Component, ElementDecorator)`
- Add the `Maybe` decorator.
- Add the `CatchEvent` decorator.
- Add the `Renderer` decorator.
### DOM:
- **breaking**: The `inverted` decorator now toggle in the inverted attribute.
- Add `gauge` for the 4 directions. Expose the following API:
@ -19,29 +46,17 @@ Element gaugeUp(float ratio);
Element gaugeDown(float ratio);
Element gaugeDirection(float ratio, GaugeDirection);
```
- Add `separatorHSelector` and `separatorVSelector` elements. This can be used
to highlight an area.
- Add the `automerge` decorator. This makes separator characters to be merged
with others nearby.
- Fix the `Table` rendering function, to allow automerging characters.
- Bugfix: The `vscroll_indicator` now computes its offset and size correctly.
- **Bugfix**: The `vscroll_indicator` now computes its offset and size
correctly.
- Add the `operator|=(Element, Decorator)`
### Component
- Support SIGTSTP. (ctrl+z).
- Support task posting. `ScreenInteractive::Post(Task)`.
- **bugfix** Container::Tab implements `Focusable()`.
- **bugfix** Improved default implementations of ComponentBase `Focusable()` and
`ActiveChild()` methods.
- **bugfix** Automatically convert '\r' keys into '\n' for Linux programs that
do not send the correct code for the return key, like the 'bind'.
https://github.com/ArthurSonzogni/FTXUI/issues/337
- Add decorator for components:
- `operator|(Component, ComponentDecorator)`
- `operator|=(Component, ComponentDecorator)`
- `operator|(Component, ElementDecorator)`
- `operator|=(Component, ElementDecorator)`
- Add the `Maybe` decorator.
- Add the `CatchEvent` decorator.
- Add the `Renderer` decorator.
### Screen:
- Add: `Color::Interpolate(lambda, color_a, color_b)`.
2.0.0
-----

View File

@ -84,19 +84,23 @@ add_library(dom
)
add_library(component
include/ftxui/component/animation.hpp
include/ftxui/component/captured_mouse.hpp
include/ftxui/component/component.hpp
include/ftxui/component/component_base.hpp
include/ftxui/component/component_options.hpp
include/ftxui/component/event.hpp
include/ftxui/component/mouse.hpp
include/ftxui/component/receiver.hpp
include/ftxui/component/screen_interactive.hpp
include/ftxui/component/task.hpp
src/ftxui/component/animation.cpp
src/ftxui/component/button.cpp
src/ftxui/component/catch_event.cpp
src/ftxui/component/checkbox.cpp
src/ftxui/component/collapsible.cpp
src/ftxui/component/component.cpp
src/ftxui/component/component_options.cpp
src/ftxui/component/container.cpp
src/ftxui/component/dropdown.cpp
src/ftxui/component/event.cpp
@ -111,7 +115,6 @@ add_library(component
src/ftxui/component/slider.cpp
src/ftxui/component/terminal_input_parser.cpp
src/ftxui/component/terminal_input_parser.hpp
src/ftxui/component/toggle.cpp
src/ftxui/component/util.cpp
)

View File

@ -44,8 +44,6 @@ target_link_libraries(tests
target_include_directories(tests
PRIVATE src
)
target_compile_options(tests PRIVATE -fsanitize=address)
target_link_libraries(tests PRIVATE -fsanitize=address)
if (NOT MSVC)
include(cmake/ftxui_benchmark.cmake)

View File

@ -10,6 +10,9 @@ add_subdirectory(component)
add_subdirectory(dom)
if (EMSCRIPTEN)
# 32MB should be enough to run all the examples, in debug mode.
target_link_options(component PUBLIC "SHELL: -s TOTAL_MEMORY=33554432")
get_property(EXAMPLES GLOBAL PROPERTY FTXUI::EXAMPLES)
foreach(file
"index.html"

View File

@ -1,6 +1,8 @@
set(DIRECTORY_LIB component)
example(button)
example(button_animated)
example(button_style)
example(canvas_animated)
example(checkbox)
example(checkbox_in_frame)
@ -16,9 +18,11 @@ example(maybe)
example(menu)
example(menu2)
example(menu_entries)
example(menu_entries_animated)
example(menu_in_frame)
example(menu_multiple)
example(menu_style)
example(menu_underline_animated_gallery)
example(modal_dialog)
example(nested_screen)
example(print_key_press)

View File

@ -1,12 +1,11 @@
#include <memory> // for shared_ptr, __shared_ptr_access
#include <string> // for operator+, to_wstring
#include <string> // for operator+, to_string
#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for ButtonOption
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
#include "ftxui/dom/elements.hpp" // for separator, gauge, Element, operator|, vbox, border
#include "ftxui/dom/elements.hpp" // for separator, gauge, text, Element, operator|, vbox, border
using namespace ftxui;
@ -14,13 +13,9 @@ int main(int argc, const char* argv[]) {
int value = 50;
// The tree of components. This defines how to navigate using the keyboard.
auto button_option = ButtonOption();
button_option.border = false;
auto buttons = Container::Horizontal({
Button(
"[Decrease]", [&] { value--; }, &button_option),
Button(
"[Increase]", [&] { value++; }, &button_option),
Button("Decrease", [&] { value--; }),
Button("Increase", [&] { value++; }),
});
// Modify the way to render them on screen:

View File

@ -0,0 +1,46 @@
#include <memory> // for shared_ptr, __shared_ptr_access
#include <string> // for operator+, to_string
#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for ButtonOption
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
#include "ftxui/dom/elements.hpp" // for gauge, separator, text, vbox, operator|, Element, border
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::Green, Color::Red
using namespace ftxui;
int main(int argc, const char* argv[]) {
int value = 50;
// The tree of components. This defines how to navigate using the keyboard.
auto buttons = Container::Horizontal({
Button(
"Decrease", [&] { value--; }, ButtonOption::Animated(Color::Red)),
Button(
"Reset", [&] { value = 50; }, ButtonOption::Animated(Color::Green)),
Button(
"Increase", [&] { value++; }, ButtonOption::Animated(Color::Blue)),
});
// Modify the way to render them on screen:
auto component = Renderer(buttons, [&] {
return vbox({
vbox({
text("value = " + std::to_string(value)),
separator(),
gauge(value * 0.01f),
}) | border,
buttons->Render(),
});
});
auto screen = ScreenInteractive::FitComponent();
screen.Loop(component);
return 0;
}
// Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

View File

@ -0,0 +1,62 @@
#include <memory> // for shared_ptr, __shared_ptr_access
#include <string> // for operator+, to_string
#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for ButtonOption
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
#include "ftxui/dom/elements.hpp" // for gauge, separator, text, vbox, operator|, Element, border
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::Green, Color::Red
using namespace ftxui;
int main(int argc, const char* argv[]) {
int value = 0;
auto action = [&] { value++; };
auto action_renderer =
Renderer([&] { return text("count = " + std::to_string(value)); });
auto buttons =
Container::Vertical({
action_renderer,
Renderer([] { return separator(); }),
Container::Horizontal({
Container::Vertical({
Button("Ascii 1", action, ButtonOption::Ascii()),
Button("Ascii 2", action, ButtonOption::Ascii()),
Button("Ascii 3", action, ButtonOption::Ascii()),
}),
Renderer([] { return separator(); }),
Container::Vertical({
Button("Simple 1", action, ButtonOption::Simple()),
Button("Simple 2", action, ButtonOption::Simple()),
Button("Simple 3", action, ButtonOption::Simple()),
}),
Renderer([] { return separator(); }),
Container::Vertical({
Button("Animated 1", action, ButtonOption::Animated()),
Button("Animated 2", action, ButtonOption::Animated()),
Button("Animated 3", action, ButtonOption::Animated()),
}),
Renderer([] { return separator(); }),
Container::Vertical({
Button("Animated 4", action,
ButtonOption::Animated(Color::Red)),
Button("Animated 5", action,
ButtonOption::Animated(Color::Green)),
Button("Animated 6", action,
ButtonOption::Animated(Color::Blue)),
}),
}),
}) |
border;
auto screen = ScreenInteractive::FitComponent();
screen.Loop(buttons);
return 0;
}
// Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

View File

@ -126,9 +126,9 @@ int main(int argc, const char* argv[]) {
std::vector<int> ys(100);
for (int x = 0; x < 100; x++) {
float dx = x - mouse_x;
float dy = 50;
ys[x] = dy + 20 * cos(dx * 0.14) + 10 * sin(dx * 0.42);
float dx = float(x - mouse_x);
float dy = 50.f;
ys[x] = int(dy + 20 * cos(dx * 0.14) + 10 * sin(dx * 0.42));
}
for (int x = 1; x < 99; x++)
c.DrawPointLine(x, ys[x], x + 1, ys[x + 1]);
@ -141,10 +141,10 @@ int main(int argc, const char* argv[]) {
c.DrawText(0, 0, "A symmetrical graph filled");
std::vector<int> ys(100);
for (int x = 0; x < 100; x++) {
ys[x] = 30 + //
ys[x] = int(30 + //
10 * cos(x * 0.2 - mouse_x * 0.05) + //
5 * sin(x * 0.4) + //
5 * sin(x * 0.3 - mouse_y * 0.05); //
5 * sin(x * 0.3 - mouse_y * 0.05)); //
}
for (int x = 0; x < 100; x++) {
c.DrawPointLine(x, 50 + ys[x], x, 50 - ys[x], Color::Red);
@ -167,7 +167,7 @@ int main(int argc, const char* argv[]) {
for (int x = 0; x < size; x++) {
float dx = x - mx;
float dy = y - my;
ys[y][x] = -1.5 + 3.0 * std::exp(-0.2f * (dx * dx + dy * dy));
ys[y][x] = (int)(-1.5 + 3.0 * std::exp(-0.2f * (dx * dx + dy * dy)));
}
}
for (int y = 0; y < size; y++) {

View File

@ -1,3 +1,4 @@
#include <array> // for array
#include <memory> // for shared_ptr, __shared_ptr_access, allocator_traits<>::value_type
#include <string> // for operator+, to_string
#include <vector> // for vector
@ -10,17 +11,13 @@
using namespace ftxui;
struct CheckboxState {
bool checked;
};
int main(int argc, const char* argv[]) {
std::vector<CheckboxState> states(30);
std::array<bool, 30> states;
auto container = Container::Vertical({});
for (int i = 0; i < 30; ++i) {
states[i].checked = false;
container->Add(
Checkbox("Checkbox" + std::to_string(i), &states[i].checked));
states[i] = false;
container->Add(Checkbox("Checkbox" + std::to_string(i), &states[i]));
}
auto renderer = Renderer(container, [&] {

View File

@ -4,7 +4,6 @@
#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for ButtonOption
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
#include "ftxui/dom/elements.hpp" // for text, separator, Element, operator|, vbox, border
@ -13,24 +12,17 @@ using namespace ftxui;
// An example of how to compose multiple components into one and maintain their
// interactiveness.
int main(int argc, const char* argv[]) {
auto button_option = ButtonOption();
button_option.border = false;
auto left_count = 0;
auto right_count = 0;
auto left_buttons = Container::Horizontal({
Button(
"[Decrease]", [&] { left_count--; }, &button_option),
Button(
"[Increase]", [&] { left_count++; }, &button_option),
Button("Decrease", [&] { left_count--; }),
Button("Increase", [&] { left_count++; }),
});
auto right_buttons = Container::Horizontal({
Button(
"[Decrease]", [&] { right_count--; }, &button_option),
Button(
"[Increase]", [&] { right_count++; }, &button_option),
Button("Decrease", [&] { right_count--; }),
Button("Increase", [&] { right_count++; }),
});
// Renderer decorates its child with a new rendering function. The way the

View File

@ -9,14 +9,16 @@
#include <utility> // for move
#include <vector> // for vector
#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Checkbox, Renderer, Horizontal, Vertical, Input, Menu, Radiobox, ResizableSplitLeft, Tab, Toggle
#include "../dom/color_info_sorted_2d.ipp" // for ColorInfoSorted2D
#include "ftxui/component/component.hpp" // for Checkbox, Renderer, Horizontal, Vertical, Input, Menu, Radiobox, ResizableSplitLeft, Tab
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for InputOption
#include "ftxui/component/component_options.hpp" // for MenuOption, InputOption
#include "ftxui/component/event.hpp" // for Event, Event::Custom
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
#include "ftxui/dom/elements.hpp" // for text, operator|, color, bgcolor, filler, Element, size, vbox, flex, hbox, separator, graph, EQUAL, paragraph, hcenter, WIDTH, bold, window, border, vscroll_indicator, Elements, HEIGHT, hflow, frame, flex_grow, flexbox, gauge, paragraphAlignCenter, paragraphAlignJustify, paragraphAlignLeft, paragraphAlignRight, dim, spinner, Decorator, LESS_THAN, center, yflex, GREATER_THAN
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::BlueLight, Color::RedLight, Color::Black, Color::Cyan, Color::CyanLight, Color::GrayDark, Color::GrayLight, Color::Green, Color::GreenLight, Color::Magenta, Color::MagentaLight, Color::Red, Color::White, Color::Yellow, Color::YellowLight, Color::Default
#include "ftxui/dom/elements.hpp" // for text, color, operator|, bgcolor, filler, Element, vbox, size, hbox, separator, flex, window, graph, EQUAL, paragraph, WIDTH, hcenter, Elements, bold, vscroll_indicator, HEIGHT, flexbox, hflow, border, frame, flex_grow, gauge, paragraphAlignCenter, paragraphAlignJustify, paragraphAlignLeft, paragraphAlignRight, dim, spinner, LESS_THAN, center, yframe, GREATER_THAN
#include "ftxui/dom/flexbox_config.hpp" // for FlexboxConfig
#include "ftxui/screen/color.hpp" // for Color, Color::BlueLight, Color::RedLight, Color::Black, Color::Blue, Color::Cyan, Color::CyanLight, Color::GrayDark, Color::GrayLight, Color::Green, Color::GreenLight, Color::Magenta, Color::MagentaLight, Color::Red, Color::White, Color::Yellow, Color::YellowLight, Color::Default, Color::Palette256, ftxui
#include "ftxui/screen/color_info.hpp" // for ColorInfo
#include "ftxui/screen/terminal.hpp" // for Size, Dimensions
using namespace ftxui;
@ -27,7 +29,6 @@ int main(int argc, const char* argv[]) {
// ---------------------------------------------------------------------------
// HTOP
// ---------------------------------------------------------------------------
int shift = 0;
auto my_graph = [&shift](int width, int height) {
@ -95,7 +96,7 @@ int main(int argc, const char* argv[]) {
separator(),
ram | flex,
}) |
flex | border;
flex;
});
// ---------------------------------------------------------------------------
@ -255,7 +256,7 @@ int main(int argc, const char* argv[]) {
}) | size(HEIGHT, LESS_THAN, 8),
hflow(render_command()) | flex_grow,
}) |
flex_grow | border;
flex_grow;
});
// ---------------------------------------------------------------------------
@ -267,14 +268,18 @@ int main(int argc, const char* argv[]) {
entries.push_back(spinner(i, shift / 2) | bold |
size(WIDTH, GREATER_THAN, 2) | border);
}
return hflow(std::move(entries)) | border;
return hflow(std::move(entries));
});
// ---------------------------------------------------------------------------
// Colors
// ---------------------------------------------------------------------------
auto color_tab_renderer = Renderer([] {
return hbox({
auto basic_color_display =
vbox({
text("16 color palette:"),
separator(),
hbox({
vbox({
color(Color::Default, text("Default")),
color(Color::Black, text("Black")),
@ -313,15 +318,66 @@ int main(int argc, const char* argv[]) {
bgcolor(Color::Yellow, text("Yellow")),
bgcolor(Color::YellowLight, text("YellowLight")),
}),
}),
}) |
hcenter | border;
border;
auto palette_256_color_display = text("256 colors palette:");
{
std::vector<std::vector<ColorInfo>> info_columns = ColorInfoSorted2D();
Elements columns;
for (auto& column : info_columns) {
Elements column_elements;
for (auto& it : column) {
column_elements.push_back(
text(" ") | bgcolor(Color(Color::Palette256(it.index_256))));
}
columns.push_back(hbox(std::move(column_elements)));
}
palette_256_color_display = vbox({
palette_256_color_display,
separator(),
vbox(columns),
}) |
border;
}
// True color display.
auto true_color_display = text("TrueColors: 24bits:");
{
int saturation = 255;
Elements array;
for (int value = 0; value < 255; value += 16) {
Elements line;
for (int hue = 0; hue < 255; hue += 6) {
line.push_back(text("") //
| color(Color::HSV(hue, saturation, value)) //
| bgcolor(Color::HSV(hue, saturation, value + 8)));
}
array.push_back(hbox(std::move(line)));
}
true_color_display = vbox({
true_color_display,
separator(),
vbox(std::move(array)),
}) |
border;
}
return flexbox(
{
basic_color_display,
palette_256_color_display,
true_color_display,
},
FlexboxConfig().SetGap(1, 1));
});
// ---------------------------------------------------------------------------
// Gauges
// ---------------------------------------------------------------------------
auto render_gauge = [&shift](int delta) {
float progress = (shift + delta) % 1000 / 1000.f;
float progress = (shift + delta) % 500 / 500.f;
return hbox({
text(std::to_string(int(progress * 100)) + "% ") |
size(WIDTH, EQUAL, 5),
@ -348,8 +404,7 @@ int main(int argc, const char* argv[]) {
render_gauge(2222) | color(Color::RedLight),
render_gauge(220) | color(Color::Yellow),
render_gauge(348) | color(Color::YellowLight),
}) |
border;
});
});
// ---------------------------------------------------------------------------
@ -363,34 +418,22 @@ int main(int argc, const char* argv[]) {
};
auto paragraph_renderer_left = Renderer([&] {
auto title_style = bold | bgcolor(Color::Blue) | color(Color::Black);
std::string str =
"Lorem Ipsum is simply dummy text of the printing and typesetting "
"industry. Lorem Ipsum has been the industry's standard dummy text "
"ever since the 1500s, when an unknown printer took a galley of type "
"and scrambled it to make a type specimen book.";
return vbox({
// [ Left ]
text("Align left:") | title_style,
paragraphAlignLeft(str),
// [ Center ]
text("Align center:") | title_style,
paragraphAlignCenter(str),
// [ Right ]
text("Align right:") | title_style,
paragraphAlignRight(str),
// [ Justify]
text("Align justify:") | title_style,
paragraphAlignJustify(str),
// [ Side by side ]
text("Side by side:") | title_style,
hbox({
window(text("Align left:"), paragraphAlignLeft(str)),
window(text("Align center:"), paragraphAlignCenter(str)),
window(text("Align right:"), paragraphAlignRight(str)),
window(text("Align justify:"), paragraphAlignJustify(str)),
window(text("Side by side"), hbox({
paragraph(str),
separator() | color(Color::Blue),
separator(),
paragraph(str),
}),
// [ Misc ]
text("Elements with different size:") | title_style,
})),
window(text("Elements with different size:"),
flexbox({
make_box(10, 5),
make_box(9, 4),
@ -404,7 +447,7 @@ int main(int argc, const char* argv[]) {
make_box(9, 4),
make_box(8, 4),
make_box(6, 3),
}),
})),
}) |
vscroll_indicator | yframe | flex;
});
@ -420,7 +463,7 @@ int main(int argc, const char* argv[]) {
&paragraph_renderer_split_position);
auto paragraph_renderer_group_renderer =
Renderer(paragraph_renderer_group,
[&] { return paragraph_renderer_group->Render() | border; });
[&] { return paragraph_renderer_group->Render(); });
// ---------------------------------------------------------------------------
// Tabs
@ -430,7 +473,8 @@ int main(int argc, const char* argv[]) {
std::vector<std::string> tab_entries = {
"htop", "color", "spinner", "gauge", "compiler", "paragraph",
};
auto tab_selection = Toggle(&tab_entries, &tab_index);
auto tab_selection =
Menu(&tab_entries, &tab_index, MenuOption::HorizontalAnimated());
auto tab_content = Container::Tab(
{
htop,
@ -450,7 +494,7 @@ int main(int argc, const char* argv[]) {
auto main_renderer = Renderer(main_container, [&] {
return vbox({
text("FTXUI Demo") | bold | hcenter,
tab_selection->Render() | hcenter,
tab_selection->Render(),
tab_content->Render() | flex,
});
});

View File

@ -1,25 +1,31 @@
#include <functional> // for function
#include <iostream> // for basic_ostream::operator<<, operator<<, endl, basic_ostream, basic_ostream<>::__ostream_type, cout, ostream
#include <memory> // for shared_ptr, __shared_ptr_access
#include <string> // for to_string, allocator
#include <memory> // for allocator, shared_ptr, __shared_ptr_access
#include <string> // for char_traits, to_string, operator+, string, basic_string
#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for MenuEntry, Renderer, Vertical
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for MenuEntryOption
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
#include "ftxui/dom/elements.hpp" // for operator|, separator, Element, Decorator, color, text, hbox, size, bold, frame, inverted, vbox, HEIGHT, LESS_THAN, border
#include "ftxui/dom/elements.hpp" // for operator|, Element, separator, text, hbox, size, frame, color, vbox, HEIGHT, LESS_THAN, bold, border, inverted
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::Cyan, Color::Green, Color::Red, Color::Yellow
using namespace ftxui;
// Define a special style for some menu entry.
MenuEntryOption Colored(ftxui::Color c) {
MenuEntryOption special_style;
special_style.style_normal = Decorator(color(c));
special_style.style_focused = Decorator(color(c)) | inverted;
special_style.style_selected = Decorator(color(c)) | bold;
special_style.style_selected_focused = Decorator(color(c)) | inverted | bold;
return special_style;
MenuEntryOption option;
option.transform = [c](EntryState state) {
state.label = (state.active? "> " : " ") + state.label;
Element e = text(state.label) | color(c);
if (state.focused)
e = e | inverted;
if (state.active)
e = e | bold;
return e;
};
return option;
}
int main(int argc, const char* argv[]) {

View File

@ -0,0 +1,66 @@
#include <iostream> // for basic_ostream::operator<<, operator<<, endl, basic_ostream, basic_ostream<>::__ostream_type, cout, ostream
#include <memory> // for shared_ptr, __shared_ptr_access
#include <string> // for to_string, allocator
#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for MenuEntryAnimated, Renderer, Vertical
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for MenuEntryAnimated
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
#include "ftxui/dom/elements.hpp" // for operator|, separator, Element, Decorator, color, text, hbox, size, bold, frame, inverted, vbox, HEIGHT, LESS_THAN, border
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::Cyan, Color::Green, Color::Red, Color::Yellow
using namespace ftxui;
// Define a special style for some menu entry.
MenuEntryOption Colored(ftxui::Color c) {
MenuEntryOption option;
option.animated_colors.foreground.enabled = true;
option.animated_colors.background.enabled = true;
option.animated_colors.background.active = c;
option.animated_colors.background.inactive = Color::Black;
option.animated_colors.foreground.active = Color::White;
option.animated_colors.foreground.inactive = c;
return option;
}
int main(int argc, const char* argv[]) {
auto screen = ScreenInteractive::TerminalOutput();
int selected = 0;
auto menu = Container::Vertical(
{
MenuEntry(" 1. rear", Colored(Color::Red)),
MenuEntry(" 2. drown", Colored(Color::Yellow)),
MenuEntry(" 3. nail", Colored(Color::Green)),
MenuEntry(" 4. quit", Colored(Color::Cyan)),
MenuEntry(" 5. decorative", Colored(Color::Blue)),
MenuEntry(" 7. costume"),
MenuEntry(" 8. pick"),
MenuEntry(" 9. oral"),
MenuEntry("11. minister"),
MenuEntry("12. football"),
MenuEntry("13. welcome"),
MenuEntry("14. copper"),
MenuEntry("15. inhabitant"),
},
&selected);
// Display together the menu with a border
auto renderer = Renderer(menu, [&] {
return vbox({
hbox(text("selected = "), text(std::to_string(selected))),
separator(),
menu->Render() | frame,
}) |
border | bgcolor(Color::Black);
});
screen.Loop(renderer);
std::cout << "Selected element = " << selected << std::endl;
}
// Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

View File

@ -1,100 +1,258 @@
#include <array> // for array
#include <chrono> // for milliseconds
#include <functional> // for function
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
#include <string> // for string, basic_string
#include <string> // for string, char_traits, basic_string, operator+
#include <vector> // for vector
#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Menu, Horizontal, Renderer
#include "ftxui/component/animation.hpp" // for ElasticOut, Linear
#include "ftxui/component/component.hpp" // for Menu, Horizontal, Renderer, Vertical
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for MenuOption
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
#include "ftxui/dom/elements.hpp" // for operator|, color, separator, Decorator, bgcolor, flex, Element, bold, hbox, border, dim
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::BlueLight, Color::Red, Color::Yellow
#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption, AnimatedColorOption, AnimatedColorsOption, UnderlineOption
#include "ftxui/component/mouse.hpp" // for ftxui
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
#include "ftxui/dom/elements.hpp" // for separator, operator|, Element, text, bgcolor, hbox, bold, color, filler, border, vbox, borderDouble, dim, flex, hcenter
#include "ftxui/screen/color.hpp" // for Color, Color::Red, Color::Black, Color::Yellow, Color::Blue, Color::Default, Color::White
using namespace ftxui;
Component VMenu1(std::vector<std::string>* entries, int* selected);
Component VMenu2(std::vector<std::string>* entries, int* selected);
Component VMenu3(std::vector<std::string>* entries, int* selected);
Component VMenu4(std::vector<std::string>* entries, int* selected);
Component VMenu5(std::vector<std::string>* entries, int* selected);
Component VMenu6(std::vector<std::string>* entries, int* selected);
Component VMenu7(std::vector<std::string>* entries, int* selected);
Component VMenu8(std::vector<std::string>* entries, int* selected);
Component HMenu1(std::vector<std::string>* entries, int* selected);
Component HMenu2(std::vector<std::string>* entries, int* selected);
Component HMenu3(std::vector<std::string>* entries, int* selected);
Component HMenu4(std::vector<std::string>* entries, int* selected);
Component HMenu5(std::vector<std::string>* entries, int* selected);
int main(int argc, const char* argv[]) {
using namespace ftxui;
auto screen = ScreenInteractive::TerminalOutput();
std::vector<std::string> entries = {
"Monkey", "Dog", "Cat", "Bird", "Elephant",
"Monkey", "Dog", "Cat", "Bird", "Elephant", "Cat",
};
int menu_1_selected_ = 0;
int menu_2_selected_ = 0;
int menu_3_selected_ = 0;
int menu_4_selected_ = 0;
int menu_5_selected_ = 0;
int menu_6_selected_ = 0;
std::array<int, 12> selected = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
MenuOption option_1;
option_1.style_focused = bold | color(Color::Blue);
option_1.style_selected = color(Color::Blue);
option_1.style_selected_focused = bold | color(Color::Blue);
option_1.on_enter = screen.ExitLoopClosure();
auto menu_1_ = Menu(&entries, &menu_1_selected_, &option_1);
auto vmenu_1_ = VMenu1(&entries, &selected[0]);
auto vmenu_2_ = VMenu2(&entries, &selected[1]);
auto vmenu_3_ = VMenu3(&entries, &selected[2]);
auto vmenu_4_ = VMenu4(&entries, &selected[3]);
auto vmenu_5_ = VMenu5(&entries, &selected[4]);
auto vmenu_6_ = VMenu6(&entries, &selected[5]);
auto vmenu_7_ = VMenu7(&entries, &selected[6]);
auto vmenu_8_ = VMenu8(&entries, &selected[7]);
MenuOption option_2;
option_2.style_focused = bold | color(Color::Blue);
option_2.style_selected = color(Color::Blue);
option_2.style_selected_focused = bold | color(Color::Blue);
option_2.on_enter = screen.ExitLoopClosure();
auto menu_2_ = Menu(&entries, &menu_2_selected_, &option_2);
auto hmenu_1_ = HMenu1(&entries, &selected[8]);
auto hmenu_2_ = HMenu2(&entries, &selected[9]);
auto hmenu_3_ = HMenu3(&entries, &selected[10]);
auto hmenu_4_ = HMenu4(&entries, &selected[11]);
auto hmenu_5_ = HMenu5(&entries, &selected[12]);
MenuOption option_3;
option_3.style_selected = color(Color::Blue);
option_3.style_focused = bgcolor(Color::Blue);
option_3.style_selected_focused = bgcolor(Color::Blue);
option_3.on_enter = screen.ExitLoopClosure();
auto menu_3_ = Menu(&entries, &menu_3_selected_, &option_3);
MenuOption option_4;
option_4.style_selected = bgcolor(Color::Blue);
option_4.style_focused = bgcolor(Color::BlueLight);
option_4.style_selected_focused = bgcolor(Color::BlueLight);
option_4.on_enter = screen.ExitLoopClosure();
auto menu_4_ = Menu(&entries, &menu_4_selected_, &option_4);
MenuOption option_5;
option_5.style_normal = bgcolor(Color::Blue);
option_5.style_selected = bgcolor(Color::Yellow);
option_5.style_focused = bgcolor(Color::Red);
option_5.style_selected_focused = bgcolor(Color::Red);
option_5.on_enter = screen.ExitLoopClosure();
auto menu_5_ = Menu(&entries, &menu_5_selected_, &option_5);
MenuOption option_6;
option_6.style_normal = dim | color(Color::Blue);
option_6.style_selected = color(Color::Blue);
option_6.style_focused = bold | color(Color::Blue);
option_6.style_selected_focused = bold | color(Color::Blue);
option_6.on_enter = screen.ExitLoopClosure();
auto menu_6_ = Menu(&entries, &menu_6_selected_, &option_6);
auto container = Container::Horizontal({
menu_1_,
menu_2_,
menu_3_,
menu_4_,
menu_5_,
menu_6_,
auto container = Container::Vertical({
Container::Horizontal({
vmenu_1_,
vmenu_2_,
vmenu_3_,
vmenu_4_,
vmenu_5_,
vmenu_6_,
vmenu_7_,
vmenu_8_,
}),
hmenu_1_,
hmenu_2_,
hmenu_3_,
hmenu_4_,
hmenu_5_,
});
// clang-format off
auto renderer = Renderer(container, [&] {
return
return //
hbox({
menu_1_->Render() | flex, separator(),
menu_2_->Render() | flex, separator(),
menu_3_->Render() | flex, separator(),
menu_4_->Render() | flex, separator(),
menu_5_->Render() | flex, separator(),
menu_6_->Render() | flex,
}) | border;
vbox({
hbox({
vmenu_1_->Render(),
separator(),
vmenu_2_->Render(),
separator(),
vmenu_3_->Render(),
separator(),
vmenu_4_->Render(),
separator(),
vmenu_5_->Render(),
vmenu_6_->Render(),
separator(),
vmenu_7_->Render(),
separator(),
vmenu_8_->Render(),
}),
separator(),
hmenu_1_->Render(),
separator(),
hmenu_2_->Render(),
separator(),
hmenu_3_->Render(),
separator(),
hmenu_4_->Render(),
hmenu_5_->Render(),
}) | border,
filler(),
});
});
// clang-format on
screen.Loop(renderer);
}
Component VMenu1(std::vector<std::string>* entries, int* selected) {
auto option = MenuOption::Vertical();
option.entries.transform = [](EntryState state) {
state.label = (state.active ? "> " : " ") + state.label;
Element e = text(state.label);
if (state.focused)
e = e | bgcolor(Color::Blue);
if (state.active)
e = e | bold;
return e;
};
return Menu(entries, selected, option);
}
Component VMenu2(std::vector<std::string>* entries, int* selected) {
auto option = MenuOption::Vertical();
option.entries.transform = [](EntryState state) {
state.label += (state.active ? " <" : " ");
Element e = hbox(filler(), text(state.label));
if (state.focused)
e = e | bgcolor(Color::Red);
if (state.active)
e = e | bold;
return e;
};
return Menu(entries, selected, option);
}
Component VMenu3(std::vector<std::string>* entries, int* selected) {
auto option = MenuOption::Vertical();
option.entries.transform = [](EntryState state) {
Element e = state.active ? text("[" + state.label + "]")
: text(" " + state.label + " ");
if (state.focused)
e = e | bold;
if (state.focused)
e = e | color(Color::Blue);
if (state.active)
e = e | bold;
return e;
};
return Menu(entries, selected, option);
}
Component VMenu4(std::vector<std::string>* entries, int* selected) {
auto option = MenuOption::Vertical();
option.entries.transform = [](EntryState state) {
if (state.active && state.focused) {
return text(state.label) | color(Color::Yellow) | bgcolor(Color::Black) |
bold;
}
if (state.active) {
return text(state.label) | color(Color::Yellow) | bgcolor(Color::Black);
}
if (state.focused) {
return text(state.label) | color(Color::Black) | bgcolor(Color::Yellow) |
bold;
}
return text(state.label) | color(Color::Black) | bgcolor(Color::Yellow);
};
return Menu(entries, selected, option);
}
Component VMenu5(std::vector<std::string>* entries, int* selected) {
auto option = MenuOption::Vertical();
option.entries.transform = [](EntryState state) {
auto element = text(state.label);
if (state.active && state.focused) {
return element | borderDouble;
}
if (state.active) {
return element | border;
}
if (state.focused) {
return element | bold;
}
return element;
};
return Menu(entries, selected, option);
}
Component VMenu6(std::vector<std::string>* entries, int* selected) {
auto option = MenuOption::VerticalAnimated();
option.underline.color_inactive = Color::Default;
option.underline.color_active = Color::Red;
option.underline.SetAnimationFunction(animation::easing::Linear);
return Menu(entries, selected, option);
}
Component VMenu7(std::vector<std::string>* entries, int* selected) {
auto option = MenuOption::Vertical();
option.entries.animated_colors.foreground.enabled = true;
option.entries.animated_colors.background.enabled = true;
option.entries.animated_colors.background.active = Color::Red;
option.entries.animated_colors.background.inactive = Color::Black;
option.entries.animated_colors.foreground.active = Color::White;
option.entries.animated_colors.foreground.inactive = Color::Red;
return Menu(entries, selected, option);
}
Component VMenu8(std::vector<std::string>* entries, int* selected) {
auto option = MenuOption::Vertical();
option.entries.animated_colors.foreground.Set(Color::Red, Color::White,
std::chrono::milliseconds(500));
return Menu(entries, selected, option);
}
Component HMenu1(std::vector<std::string>* entries, int* selected) {
return Menu(entries, selected, MenuOption::Horizontal());
}
Component HMenu2(std::vector<std::string>* entries, int* selected) {
return Menu(entries, selected, MenuOption::Toggle());
}
Component HMenu3(std::vector<std::string>* entries, int* selected) {
auto option = MenuOption::Toggle();
option.elements_infix = [] { return text(" 🮣🮠 "); };
return Menu(entries, selected, option);
}
Component HMenu4(std::vector<std::string>* entries, int* selected) {
return Menu(entries, selected, MenuOption::HorizontalAnimated());
}
Component HMenu5(std::vector<std::string>* entries, int* selected) {
auto option = MenuOption::HorizontalAnimated();
option.underline.SetAnimation(std::chrono::milliseconds(1500),
animation::easing::ElasticOut);
option.entries.transform = [](EntryState state) {
Element e = text(state.label) | hcenter | flex;
if (state.active && state.focused)
e = e | bold;
if (!state.focused && !state.active)
e = e | dim;
return e;
};
option.underline.color_inactive = Color::Default;
option.underline.color_active = Color::Red;
return Menu(entries, selected, option);
}
// Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

View File

@ -0,0 +1,94 @@
#include <chrono> // for operator""ms, literals
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
#include <string> // for string, basic_string, operator+, to_string
#include <vector> // for vector
#include "ftxui/component/animation.hpp" // for BackOut, Duration
#include "ftxui/component/component.hpp" // for Menu, Renderer, Vertical
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for MenuOption, UnderlineOption
#include "ftxui/component/mouse.hpp" // for ftxui
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
#include "ftxui/dom/elements.hpp" // for text, Element, operator|, borderEmpty, inverted
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::Red
using namespace ftxui;
Component DummyComponent(int id) {
return Renderer([id](bool focused) {
auto t = text("component " + std::to_string(id));
if (focused)
t = t | inverted;
return t;
});
}
Component Text(const std::string& t) {
return Renderer([t] { return text(t) | borderEmpty; });
}
int main(int argc, const char* argv[]) {
using namespace std::literals;
std::vector<std::string> tab_values{
"Tab 1", "Tab 2", "Tab 3", "A very very long tab", "",
};
int tab_selected = 0;
auto container = Container::Vertical({});
int frame_count = 0;
container->Add(Renderer(
[&] { return text("Frame count: " + std::to_string(frame_count++)); }));
{
auto option = MenuOption::HorizontalAnimated();
container->Add(Text("This demonstrate the Menu component"));
container->Add(Menu(&tab_values, &tab_selected, option));
}
{
container->Add(Text("Set underline color to blue"));
auto option = MenuOption::HorizontalAnimated();
option.underline.color_inactive = Color::Blue;
container->Add(Menu(&tab_values, &tab_selected, option));
}
{
container->Add(Text("Set underline active color to red"));
auto option = MenuOption::HorizontalAnimated();
option.underline.color_active = Color::Red;
container->Add(Menu(&tab_values, &tab_selected, option));
}
{
container->Add(Text("Set animation duration to 0ms"));
auto option = MenuOption::HorizontalAnimated();
option.underline.SetAnimationDuration(0ms);
container->Add(Menu(&tab_values, &tab_selected, option));
}
{
container->Add(Text("Set animation easing function to back-out"));
auto option = MenuOption::HorizontalAnimated();
option.underline.SetAnimationFunction(animation::easing::BackOut);
option.underline.SetAnimationDuration(350ms);
container->Add(Menu(&tab_values, &tab_selected, option));
}
// option.underline_animation_follower_delay = 250ms
{
container->Add(Text("Add delay to desynchronize animation"));
auto option = MenuOption::HorizontalAnimated();
option.underline.follower_delay = 250ms;
container->Add(Menu(&tab_values, &tab_selected, option));
}
container->SetActiveChild(container->ChildAt(2));
auto screen = ScreenInteractive::TerminalOutput();
screen.Loop(container);
}
// Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

View File

@ -5,12 +5,13 @@
#include <utility> // for move
#include <vector> // for vector
using namespace ftxui;
#include "./color_info_sorted_2d.ipp" // for ColorInfoSorted2D
#include "ftxui/dom/elements.hpp" // for text, bgcolor, color, vbox, hbox, separator, operator|, Elements, Element, Fit, border
#include "ftxui/dom/node.hpp" // for Render
#include "ftxui/screen/color.hpp" // for Color, Color::Black, Color::Blue, Color::BlueLight, Color::Cyan, Color::CyanLight, Color::Default, Color::GrayDark, Color::GrayLight, Color::Green, Color::GreenLight, Color::Magenta, Color::MagentaLight, Color::Red, Color::RedLight, Color::White, Color::Yellow, Color::YellowLight, Color::Palette256, ftxui
using namespace ftxui;
#include "./color_info_sorted_2d.ipp" // for ColorInfoSorted2D
int main(int argc, const char* argv[]) {
// clang-format off
auto basic_color_display =

View File

@ -1,12 +1,13 @@
#include <cmath>
#include <algorithm>
#include <cmath>
#include <ftxui/screen/color_info.hpp> // for ftxui::ColorInfo
std::vector<std::vector<ColorInfo>> ColorInfoSorted2D() {
std::vector<std::vector<ftxui::ColorInfo>> ColorInfoSorted2D() {
// Acquire the color information for the palette256.
std::vector<ColorInfo> info_gray;
std::vector<ColorInfo> info_color;
std::vector<ftxui::ColorInfo> info_gray;
std::vector<ftxui::ColorInfo> info_color;
for (int i = 16; i < 256; ++i) {
ColorInfo info = GetColorInfo(Color::Palette256(i));
ftxui::ColorInfo info = GetColorInfo(ftxui::Color::Palette256(i));
if (info.saturation == 0)
info_gray.push_back(info);
else
@ -16,10 +17,10 @@ std::vector<std::vector<ColorInfo>> ColorInfoSorted2D() {
// Sort info_color by hue.
std::sort(
info_color.begin(), info_color.end(),
[](const ColorInfo& A, const ColorInfo& B) { return A.hue < B.hue; });
[](const ftxui::ColorInfo& A, const ftxui::ColorInfo& B) { return A.hue < B.hue; });
// Make 8 colums, one gray and seven colored.
std::vector<std::vector<ColorInfo>> info_columns(8);
std::vector<std::vector<ftxui::ColorInfo>> info_columns(8);
info_columns[0] = info_gray;
for (size_t i = 0; i < info_color.size(); ++i) {
info_columns[1 + 7 * i / info_color.size()].push_back(info_color[i]);
@ -28,7 +29,7 @@ std::vector<std::vector<ColorInfo>> ColorInfoSorted2D() {
// Minimize discontinuities for every columns.
for (auto& column : info_columns) {
std::sort(column.begin(), column.end(),
[](const ColorInfo& A, const ColorInfo& B) {
[](const ftxui::ColorInfo& A, const ftxui::ColorInfo& B) {
return A.value < B.value;
});
for (int i = 0; i < int(column.size()) - 1; ++i) {

View File

@ -2,7 +2,8 @@
<head>
<meta charset="utf-8">
<title>FTXUI examples WebAssembly</title>
<script src="https://cdn.jsdelivr.net/npm/xterm@4.11.0/lib/xterm.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm@4.18.0/lib/xterm.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-webgl@0.11.4/lib/xterm-addon-webgl.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@4.11.0/css/xterm.css"></link>
</head>
<body>
@ -56,10 +57,11 @@
stdout_buffer.push(code)
}
}
let stderr = code => console.log(code);
var term = new Terminal();
const stderr = code => console.log(code);
const term = new Terminal();
term.open(document.querySelector('#terminal'));
term.resize(140,43);
term.loadAddon(new (WebglAddon.WebglAddon)());
const onBinary = e => {
for(c of e)
stdin_buffer.push(c.charCodeAt(0));
@ -78,6 +80,7 @@
</script>
<style>
body {
background-color:#EEE;
padding:20px;

View File

@ -0,0 +1,119 @@
#ifndef FTXUI_ANIMATION_HPP
#define FTXUI_ANIMATION_HPP
#include <chrono> // for milliseconds, duration, steady_clock, time_point
#include <functional> // for function
#include "ftxui/component/event.hpp"
namespace ftxui {
namespace animation {
// Components who haven't completed their animation can call this function to
// request a new frame to be drawn later.
//
// When there is no new events and no animations to complete, no new frame is
// drawn.
void RequestAnimationFrame();
using Clock = std::chrono::steady_clock;
using TimePoint = std::chrono::time_point<Clock>;
using Duration = std::chrono::duration<double>;
// Parameter of Component::OnAnimation(param).
class Params {
public:
Params(Duration duration) : duration_(duration) {}
/// The duration this animation step represents.
Duration duration() const { return duration_; }
private:
Duration duration_;
};
namespace easing {
using Function = std::function<float(float)>;
// Linear interpolation (no easing)
float Linear(float p);
// Quadratic easing; p^2
float QuadraticIn(float p);
float QuadraticOut(float p);
float QuadraticInOut(float p);
// Cubic easing; p^3
float CubicIn(float p);
float CubicOut(float p);
float CubicInOut(float p);
// Quartic easing; p^4
float QuarticIn(float p);
float QuarticOut(float p);
float QuarticInOut(float p);
// Quintic easing; p^5
float QuinticIn(float p);
float QuinticOut(float p);
float QuinticInOut(float p);
// Sine wave easing; sin(p * PI/2)
float SineIn(float p);
float SineOut(float p);
float SineInOut(float p);
// Circular easing; sqrt(1 - p^2)
float CircularIn(float p);
float CircularOut(float p);
float CircularInOut(float p);
// Exponential easing, base 2
float ExponentialIn(float p);
float ExponentialOut(float p);
float ExponentialInOut(float p);
// Exponentially-damped sine wave easing
float ElasticIn(float p);
float ElasticOut(float p);
float ElasticInOut(float p);
// Overshooting cubic easing;
float BackIn(float p);
float BackOut(float p);
float BackInOut(float p);
// Exponentially-decaying bounce easing
float BounceIn(float p);
float BounceOut(float p);
float BounceInOut(float p);
} // namespace easing
class Animator {
public:
Animator(float* from,
float to = 0.f,
Duration duration = std::chrono::milliseconds(250),
easing::Function easing_function = easing::Linear,
Duration delay = std::chrono::milliseconds(0));
void OnAnimation(Params&);
float to() const { return to_; }
private:
float* value_;
float from_;
float to_;
Duration duration_;
easing::Function easing_function_;
Duration current_;
};
} // namespace animation
} // namespace ftxui
#endif /* end of include guard: FTXUI_ANIMATION_HPP */
// Copyright 2022 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

View File

@ -8,7 +8,7 @@
#include <vector> // for vector
#include "ftxui/component/component_base.hpp" // for Component, Components
#include "ftxui/component/component_options.hpp" // for ButtonOption, CheckboxOption, InputOption, MenuOption, RadioboxOption, ToggleOption
#include "ftxui/component/component_options.hpp" // for ButtonOption, CheckboxOption (ptr only), InputOption (ptr only), MenuEntryOption (ptr only), MenuOption, RadioboxOption (ptr only)
#include "ftxui/dom/elements.hpp" // for Element
#include "ftxui/util/ref.hpp" // for Ref, ConstStringRef, ConstStringListRef, StringRef
@ -19,7 +19,6 @@ struct Event;
struct InputOption;
struct MenuOption;
struct RadioboxOption;
struct ToggleOption;
struct MenuEntryOption;
template <class T, class... Args>
@ -46,11 +45,11 @@ Component Tab(Components children, int* selector);
Component Button(ConstStringRef label,
std::function<void()> on_click,
Ref<ButtonOption> = {});
Ref<ButtonOption> = ButtonOption::Simple());
Component Checkbox(ConstStringRef label,
bool* checked,
Ref<CheckboxOption> option = {});
Ref<CheckboxOption> option = CheckboxOption::Simple());
Component Input(StringRef content,
ConstStringRef placeholder,
@ -58,8 +57,7 @@ Component Input(StringRef content,
Component Menu(ConstStringListRef entries,
int* selected_,
Ref<MenuOption> = {});
Ref<MenuOption> = MenuOption::Vertical());
Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> = {});
Component Dropdown(ConstStringListRef entries, int* selected);
@ -67,10 +65,7 @@ Component Dropdown(ConstStringListRef entries, int* selected);
Component Radiobox(ConstStringListRef entries,
int* selected_,
Ref<RadioboxOption> option = {});
Component Toggle(ConstStringListRef entries,
int* selected,
Ref<ToggleOption> option = {});
Component Toggle(ConstStringListRef entries, int* selected);
template <class T> // T = {int, float, long}
Component Slider(ConstStringRef label, T* value, T min, T max, T increment);

View File

@ -13,6 +13,10 @@ class Delegate;
class Focus;
struct Event;
namespace animation {
class Params;
} // namespace animation
class ComponentBase;
using Component = std::shared_ptr<ComponentBase>;
using Components = std::vector<Component>;
@ -42,6 +46,9 @@ class ComponentBase {
// Returns whether the event was handled or not.
virtual bool OnEvent(Event);
// Handle an animation step.
virtual void OnAnimation(animation::Params& params);
// Focus management ----------------------------------------------------------
//
// If this component contains children, this indicates which one is active,

View File

@ -1,58 +1,136 @@
#ifndef FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP
#define FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP
#include <ftxui/dom/elements.hpp>
#include <ftxui/util/ref.hpp>
#include <chrono> // for milliseconds
#include <ftxui/component/animation.hpp> // for Duration, QuadraticInOut, Function
#include <ftxui/dom/elements.hpp> // for Decorator, bold, inverted, operator|, Element, nothing
#include <ftxui/util/ref.hpp> // for Ref
#include <functional> // for function
#include <optional> // for optional
#include <string> // for string, allocator
#include "ftxui/screen/color.hpp" // for Color, Color::GrayDark, Color::White
namespace ftxui {
/// @brief Option for the Menu component.
/// @brief arguments for |ButtonOption::transform|, |CheckboxOption::transform|,
/// |Radiobox::transform|, |MenuEntryOption::transform|,
/// |MenuOption::transform|.
struct EntryState {
std::string label; /// < The label to display.
bool state; /// < The state of the button/checkbox/radiobox
bool active; /// < Whether the entry is the active one.
bool focused; /// < Whether the entry is one focused by the user.
};
struct UnderlineOption {
bool enabled = false;
Color color_active = Color::White;
Color color_inactive = Color::GrayDark;
animation::easing::Function leader_function =
animation::easing::QuadraticInOut;
animation::easing::Function follower_function =
animation::easing::QuadraticInOut;
animation::Duration leader_duration = std::chrono::milliseconds(250);
animation::Duration leader_delay = std::chrono::milliseconds(0);
animation::Duration follower_duration = std::chrono::milliseconds(250);
animation::Duration follower_delay = std::chrono::milliseconds(0);
void SetAnimation(animation::Duration d, animation::easing::Function f);
void SetAnimationDuration(animation::Duration d);
void SetAnimationFunction(animation::easing::Function f);
void SetAnimationFunction(animation::easing::Function f_leader,
animation::easing::Function f_follower);
};
/// @brief Option about a potentially animated color.
/// @ingroup component
struct MenuOption {
Decorator style_normal = nothing; ///< style.
Decorator style_focused = inverted; ///< Style when focused.
Decorator style_selected = bold; ///< Style when selected.
Decorator style_selected_focused =
Decorator(inverted) | bold; ///< Style when selected and focused.
struct AnimatedColorOption {
void Set(
Color inactive,
Color active,
animation::Duration duration = std::chrono::milliseconds(250),
animation::easing::Function function = animation::easing::QuadraticInOut);
/// Called when the selected entry changes.
std::function<void()> on_change = [] {};
/// Called when the user presses enter.
std::function<void()> on_enter = [] {};
bool enabled = false;
Color inactive;
Color active;
animation::Duration duration = std::chrono::milliseconds(250);
animation::easing::Function function = animation::easing::QuadraticInOut;
};
Ref<int> focused_entry = 0;
struct AnimatedColorsOption {
AnimatedColorOption background;
AnimatedColorOption foreground;
};
/// @brief Option for the MenuEntry component.
/// @ingroup component
struct MenuEntryOption {
Decorator style_normal = nothing; ///< style.
Decorator style_focused = inverted; ///< Style when focused.
Decorator style_selected = bold; ///< Style when selected.
Decorator style_selected_focused =
Decorator(inverted) | bold; ///< Style when selected and focused.
std::function<Element(EntryState state)> transform;
AnimatedColorsOption animated_colors;
};
/// @brief Option for the Button component.
/// @brief Option for the Menu component.
/// @ingroup component
struct MenuOption {
// Standard constructors:
static MenuOption Horizontal();
static MenuOption HorizontalAnimated();
static MenuOption Vertical();
static MenuOption VerticalAnimated();
static MenuOption Toggle();
// Style:
UnderlineOption underline;
MenuEntryOption entries;
enum Direction { Up, Down, Left, Right };
Direction direction = Down;
std::function<Element()> elements_prefix;
std::function<Element()> elements_infix;
std::function<Element()> elements_postfix;
// Observers:
std::function<void()> on_change; ///> Called when the seelcted entry changes.
std::function<void()> on_enter; ///> Called when the user presses enter.
Ref<int> focused_entry = 0;
};
/// @brief Option for the AnimatedButton component.
/// @ingroup component
struct ButtonOption {
/// Whether to show a border around the button.
bool border = true;
// Standard constructors:
static ButtonOption Ascii();
static ButtonOption Simple();
static ButtonOption Border();
static ButtonOption Animated();
static ButtonOption Animated(Color color);
static ButtonOption Animated(Color background, Color foreground);
static ButtonOption Animated(Color background,
Color foreground,
Color background_active,
Color foreground_active);
// Style:
std::function<Element(EntryState)> transform;
AnimatedColorsOption animated_colors;
};
/// @brief Option for the Checkbox component.
/// @ingroup component
struct CheckboxOption {
std::string style_checked = ""; ///< Prefix for a "checked" state.
std::string style_unchecked = ""; ///< Prefix for a "unchecked" state.
Decorator style_normal = nothing; ///< style.
Decorator style_focused = inverted; ///< Style when focused.
Decorator style_selected = bold; ///< Style when selected.
Decorator style_selected_focused =
Decorator(inverted) | bold; ///< Style when selected and focused.
// Standard constructors:
static CheckboxOption Simple();
// Style:
std::function<Element(EntryState)> transform;
// Observer:
/// Called when the user change the state.
std::function<void()> on_change = []() {};
std::function<void()> on_change = [] {};
};
/// @brief Option for the Input component.
@ -74,34 +152,15 @@ struct InputOption {
/// @brief Option for the Radiobox component.
/// @ingroup component
struct RadioboxOption {
std::string style_checked = ""; ///< Prefix for a "checked" state.
std::string style_unchecked = ""; ///< Prefix for a "unchecked" state.
Decorator style_normal = nothing; ///< style.
Decorator style_focused = inverted; ///< Style when focused.
Decorator style_selected = bold; ///< Style when selected.
Decorator style_selected_focused =
Decorator(inverted) | bold; ///< Style when selected and focused.
// Standard constructors:
static RadioboxOption Simple();
/// Called when the selected entry changes.
std::function<void()> on_change = []() {};
Ref<int> focused_entry = 0;
};
/// @brief Option for the Toggle component.
/// @ingroup component
struct ToggleOption {
Decorator style_normal = nothing; ///< style.
Decorator style_focused = inverted; ///< Style when focused.
Decorator style_selected = bold; ///< Style when selected.
Decorator style_selected_focused =
Decorator(inverted) | bold; ///< Style when selected and focused.
// Style:
std::function<Element(EntryState)> transform;
// Observers:
/// Called when the selected entry changes.
std::function<void()> on_change = [] {};
/// Called when the user presses enter.
std::function<void()> on_enter = [] {};
Ref<int> focused_entry = 0;
};

View File

@ -9,6 +9,7 @@
#include <thread> // for thread
#include <variant> // for variant
#include "ftxui/component/animation.hpp" // for TimePoint
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
#include "ftxui/component/event.hpp" // for Event
#include "ftxui/component/task.hpp" // for Closure, Task
@ -23,16 +24,21 @@ class ScreenInteractivePrivate;
class ScreenInteractive : public Screen {
public:
// Constructors:
static ScreenInteractive FixedSize(int dimx, int dimy);
static ScreenInteractive Fullscreen();
static ScreenInteractive FitComponent();
static ScreenInteractive TerminalOutput();
// Return the currently active screen, nullptr if none.
static ScreenInteractive* Active();
void Loop(Component);
Closure ExitLoopClosure();
void Post(Task task);
void PostEvent(Event event);
void RequestAnimationFrame();
CapturedMouse CaptureMouse();
@ -72,6 +78,9 @@ class ScreenInteractive : public Screen {
std::atomic<bool> quit_ = false;
std::thread event_listener_;
std::thread animation_listener_;
bool animation_requested_ = true;
animation::TimePoint previous_animation_time;
int cursor_x_ = 1;
int cursor_y_ = 1;

View File

@ -1,12 +1,18 @@
#ifndef FTXUI_COMPONENT_ANIMATION_HPP
#define FTXUI_COMPONENT_ANIMATION_HPP
#include <functional>
#include <variant>
#include "ftxui/component/event.hpp"
namespace ftxui {
class AnimationTask {};
using Closure = std::function<void()>;
using Task = std::variant<Event, Closure>;
using Task = std::variant<Event, Closure, AnimationTask>;
} // namespace ftxui
#endif // FTXUI_COMPONENT_ANIMATION_HPP
// Copyright 2022 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

View File

@ -43,6 +43,14 @@ Element separatorEmpty();
Element separatorStyled(BorderStyle);
Element separator(Pixel);
Element separatorCharacter(std::string);
Element separatorHSelector(float left,
float right,
Color background,
Color foreground);
Element separatorVSelector(float up,
float down,
Color background,
Color foreground);
Element gauge(float ratio);
Element gaugeLeft(float ratio);
Element gaugeRight(float ratio);

View File

@ -27,6 +27,7 @@ class Color {
Color(uint8_t red, uint8_t green, uint8_t blue);
static Color RGB(uint8_t red, uint8_t green, uint8_t blue);
static Color HSV(uint8_t hue, uint8_t saturation, uint8_t value);
static Color Interpolate(float t, const Color& a, const Color& b);
//---------------------------
// List of colors:

View File

@ -0,0 +1,285 @@
#define _USE_MATH_DEFINES
#include <cmath> // for sin, pow, sqrt, M_PI_2, M_PI, cos
#include "ftxui/component/animation.hpp"
#include <ratio> // for ratio
namespace ftxui {
namespace animation {
namespace easing {
// Easing function have been taken out of:
// https://github.com/warrenm/AHEasing/blob/master/AHEasing/easing.c
//
// Corresponding license:
// Copyright (c) 2011, Auerhaus Development, LLC
//
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What The Fuck You Want
// To Public License, Version 2, as published by Sam Hocevar. See
// http://sam.zoy.org/wtfpl/COPYING for more details.
// Modeled after the line y = x
float Linear(float p) {
return p;
}
// Modeled after the parabola y = x^2
float QuadraticIn(float p) {
return p * p;
}
// Modeled after the parabola y = -x^2 + 2x
float QuadraticOut(float p) {
return -(p * (p - 2));
}
// Modeled after the piecewise quadratic
// y = (1/2)((2x)^2) ; [0, 0.5)
// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
float QuadraticInOut(float p) {
if (p < 0.5) {
return 2 * p * p;
} else {
return (-2 * p * p) + (4 * p) - 1;
}
}
// Modeled after the cubic y = x^3
float CubicIn(float p) {
return p * p * p;
}
// Modeled after the cubic y = (x - 1)^3 + 1
float CubicOut(float p) {
float f = (p - 1);
return f * f * f + 1;
}
// Modeled after the piecewise cubic
// y = (1/2)((2x)^3) ; [0, 0.5)
// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
float CubicInOut(float p) {
if (p < 0.5) {
return 4 * p * p * p;
} else {
float f = ((2 * p) - 2);
return 0.5 * f * f * f + 1;
}
}
// Modeled after the quartic x^4
float QuarticIn(float p) {
return p * p * p * p;
}
// Modeled after the quartic y = 1 - (x - 1)^4
float QuarticOut(float p) {
float f = (p - 1);
return f * f * f * (1 - p) + 1;
}
// Modeled after the piecewise quartic
// y = (1/2)((2x)^4) ; [0, 0.5)
// y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
float QuarticInOut(float p) {
if (p < 0.5) {
return 8 * p * p * p * p;
} else {
float f = (p - 1);
return -8 * f * f * f * f + 1;
}
}
// Modeled after the quintic y = x^5
float QuinticIn(float p) {
return p * p * p * p * p;
}
// Modeled after the quintic y = (x - 1)^5 + 1
float QuinticOut(float p) {
float f = (p - 1);
return f * f * f * f * f + 1;
}
// Modeled after the piecewise quintic
// y = (1/2)((2x)^5) ; [0, 0.5)
// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
float QuinticInOut(float p) {
if (p < 0.5) {
return 16 * p * p * p * p * p;
} else {
float f = ((2 * p) - 2);
return 0.5 * f * f * f * f * f + 1;
}
}
// Modeled after quarter-cycle of sine wave
float SineIn(float p) {
return sin((p - 1) * M_PI_2) + 1;
}
// Modeled after quarter-cycle of sine wave (different phase)
float SineOut(float p) {
return sin(p * M_PI_2);
}
// Modeled after half sine wave
float SineInOut(float p) {
return 0.5 * (1 - cos(p * M_PI));
}
// Modeled after shifted quadrant IV of unit circle
float CircularIn(float p) {
return 1 - sqrt(1 - (p * p));
}
// Modeled after shifted quadrant II of unit circle
float CircularOut(float p) {
return sqrt((2 - p) * p);
}
// Modeled after the piecewise circular function
// y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5)
// y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
float CircularInOut(float p) {
if (p < 0.5) {
return 0.5 * (1 - sqrt(1 - 4 * (p * p)));
} else {
return 0.5 * (sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1);
}
}
// Modeled after the exponential function y = 2^(10(x - 1))
float ExponentialIn(float p) {
return (p == 0.0) ? p : pow(2, 10 * (p - 1));
}
// Modeled after the exponential function y = -2^(-10x) + 1
float ExponentialOut(float p) {
return (p == 1.0) ? p : 1 - pow(2, -10 * p);
}
// Modeled after the piecewise exponential
// y = (1/2)2^(10(2x - 1)) ; [0,0.5)
// y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]
float ExponentialInOut(float p) {
if (p == 0.0 || p == 1.0)
return p;
if (p < 0.5) {
return 0.5 * pow(2, (20 * p) - 10);
} else {
return -0.5 * pow(2, (-20 * p) + 10) + 1;
}
}
// Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1))
float ElasticIn(float p) {
return sin(13 * M_PI_2 * p) * pow(2, 10 * (p - 1));
}
// Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) +
// 1
float ElasticOut(float p) {
return sin(-13 * M_PI_2 * (p + 1)) * pow(2, -10 * p) + 1;
}
// Modeled after the piecewise exponentially-damped sine wave:
// y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
// y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
float ElasticInOut(float p) {
if (p < 0.5) {
return 0.5 * sin(13 * M_PI_2 * (2 * p)) * pow(2, 10 * ((2 * p) - 1));
} else {
return 0.5 *
(sin(-13 * M_PI_2 * ((2 * p - 1) + 1)) * pow(2, -10 * (2 * p - 1)) +
2);
}
}
// Modeled after the overshooting cubic y = x^3-x*sin(x*pi)
float BackIn(float p) {
return p * p * p - p * sin(p * M_PI);
}
// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
float BackOut(float p) {
float f = (1 - p);
return 1 - (f * f * f - f * sin(f * M_PI));
}
// Modeled after the piecewise overshooting cubic function:
// y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5)
// y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]
float BackInOut(float p) {
if (p < 0.5) {
float f = 2 * p;
return 0.5 * (f * f * f - f * sin(f * M_PI));
} else {
float f = (1 - (2 * p - 1));
return 0.5 * (1 - (f * f * f - f * sin(f * M_PI))) + 0.5;
}
}
float BounceIn(float p) {
return 1 - BounceOut(1 - p);
}
float BounceOut(float p) {
if (p < 4 / 11.0) {
return (121 * p * p) / 16.0;
} else if (p < 8 / 11.0) {
return (363 / 40.0 * p * p) - (99 / 10.0 * p) + 17 / 5.0;
} else if (p < 9 / 10.0) {
return (4356 / 361.0 * p * p) - (35442 / 1805.0 * p) + 16061 / 1805.0;
} else {
return (54 / 5.0 * p * p) - (513 / 25.0 * p) + 268 / 25.0;
}
}
float BounceInOut(float p) {
if (p < 0.5) {
return 0.5 * BounceIn(p * 2);
} else {
return 0.5 * BounceOut(p * 2 - 1) + 0.5;
}
}
} // namespace easing
Animator::Animator(float* from,
float to,
Duration duration,
easing::Function easing_function,
Duration delay)
: value_(from),
from_(*from),
to_(to),
duration_(duration),
easing_function_(easing_function),
current_(-delay) {
RequestAnimationFrame();
}
void Animator::OnAnimation(Params& params) {
current_ += params.duration();
if (current_ >= duration_) {
*value_ = to_;
return;
}
if (current_ <= Duration()) {
*value_ = from_;
} else {
*value_ = from_ + (to_ - from_) * easing_function_(current_ / duration_);
}
RequestAnimationFrame();
}
} // namespace animation
} // namespace ftxui

View File

@ -1,67 +1,34 @@
#include <functional> // for function
#include <memory> // for shared_ptr
#include <string> // for string
#include <utility> // for move
#include "ftxui/component/animation.hpp" // for Animator, Params (ptr only)
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
#include "ftxui/component/component.hpp" // for Make, Button
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for ButtonOption
#include "ftxui/component/component_options.hpp" // for ButtonOption, AnimatedColorOption, AnimatedColorsOption
#include "ftxui/component/event.hpp" // for Event, Event::Return
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
#include "ftxui/component/screen_interactive.hpp" // for Component
#include "ftxui/dom/elements.hpp" // for operator|, Element, nothing, reflect, text, border, inverted
#include "ftxui/dom/elements.hpp" // for operator|, Decorator, Element, bgcolor, color, operator|=, reflect, text, border, inverted, nothing
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/util/ref.hpp" // for ConstStringRef, Ref
#include "ftxui/screen/color.hpp" // for Color
#include "ftxui/util/ref.hpp" // for Ref, ConstStringRef
namespace ftxui {
namespace {
class ButtonBase : public ComponentBase {
public:
ButtonBase(ConstStringRef label,
std::function<void()> on_click,
Ref<ButtonOption> option)
: label_(label), on_click_(on_click), option_(std::move(option)) {}
// Component implementation:
Element Render() override {
auto style = Focused() ? inverted : nothing;
auto my_border = option_->border ? border : nothing;
return text(*label_) | my_border | style | reflect(box_);
Element DefaultTransform(EntryState params) {
auto element = text(params.label) | border;
if (params.active)
element |= bold;
if (params.focused)
element |= inverted;
return element;
}
bool OnEvent(Event event) override {
if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) {
if (!CaptureMouse(event))
return false;
TakeFocus();
if (event.mouse().button == Mouse::Left &&
event.mouse().motion == Mouse::Pressed) {
on_click_();
return true;
}
return false;
}
if (event == Event::Return) {
on_click_();
return true;
}
return false;
}
bool Focusable() const final { return true; }
private:
ConstStringRef label_;
std::function<void()> on_click_;
Box box_;
Ref<ButtonOption> option_;
};
} // namespace
/// @brief Draw a button. Execute a function when clicked.
@ -90,7 +57,122 @@ class ButtonBase : public ComponentBase {
Component Button(ConstStringRef label,
std::function<void()> on_click,
Ref<ButtonOption> option) {
return Make<ButtonBase>(label, std::move(on_click), std::move(option));
class Impl : public ComponentBase {
public:
Impl(ConstStringRef label,
std::function<void()> on_click,
Ref<ButtonOption> option)
: label_(label), on_click_(on_click), option_(std::move(option)) {}
// Component implementation:
Element Render() override {
float target = Focused() ? 1.0 : 0.f;
if (target != animator_background_.to())
SetAnimationTarget(target);
EntryState state = {
*label_,
false,
Active(),
Focused(),
};
auto element =
(option_->transform ? option_->transform : DefaultTransform) //
(state);
return element | AnimatedColorStyle() | reflect(box_);
}
Decorator AnimatedColorStyle() {
Decorator style = nothing;
if (option_->animated_colors.background.enabled) {
style = style | bgcolor(Color::Interpolate(
animation_foreground_, //
option_->animated_colors.background.inactive,
option_->animated_colors.background.active));
}
if (option_->animated_colors.foreground.enabled) {
style = style | color(Color::Interpolate(
animation_foreground_, //
option_->animated_colors.foreground.inactive,
option_->animated_colors.foreground.active));
}
return style;
}
void SetAnimationTarget(float target) {
if (option_->animated_colors.foreground.enabled) {
animator_foreground_ =
animation::Animator(&animation_foreground_, target,
option_->animated_colors.foreground.duration,
option_->animated_colors.foreground.function);
}
if (option_->animated_colors.background.enabled) {
animator_background_ =
animation::Animator(&animation_background_, target,
option_->animated_colors.background.duration,
option_->animated_colors.background.function);
}
}
void OnAnimation(animation::Params& p) override {
animator_background_.OnAnimation(p);
animator_foreground_.OnAnimation(p);
}
void OnClick() {
on_click_();
animation_background_ = 0.5f;
animation_foreground_ = 0.5f;
SetAnimationTarget(1.f);
}
bool OnEvent(Event event) override {
if (event.is_mouse())
return OnMouseEvent(event);
if (event == Event::Return) {
OnClick();
return true;
}
return false;
}
bool OnMouseEvent(Event event) {
mouse_hover_ =
box_.Contain(event.mouse().x, event.mouse().y) && CaptureMouse(event);
if (!mouse_hover_)
return false;
TakeFocus();
if (event.mouse().button == Mouse::Left &&
event.mouse().motion == Mouse::Pressed) {
OnClick();
return true;
}
return false;
}
bool Focusable() const final { return true; }
private:
ConstStringRef label_;
std::function<void()> on_click_;
bool mouse_hover_ = false;
Box box_;
Ref<ButtonOption> option_;
float animation_background_ = 0;
float animation_foreground_ = 0;
animation::Animator animator_background_ =
animation::Animator(&animation_background_);
animation::Animator animator_foreground_ =
animation::Animator(&animation_foreground_);
};
return Make<Impl>(label, std::move(on_click), std::move(option));
}
} // namespace ftxui

View File

@ -18,32 +18,24 @@ namespace {
class CheckboxBase : public ComponentBase {
public:
CheckboxBase(ConstStringRef label, bool* state, Ref<CheckboxOption> option)
: label_(label), state_(state), option_(std::move(option)) {
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
// Microsoft terminal do not use fonts able to render properly the default
// radiobox glyph.
if (option_->style_checked == "")
option_->style_checked = "[X]";
if (option_->style_unchecked == "")
option_->style_unchecked = "[ ]";
#endif
}
: label_(label), state_(state), option_(std::move(option)) {}
private:
// Component implementation.
Element Render() override {
bool is_focused = Focused();
bool is_active = Active();
auto style = (is_focused || hovered_) ? option_->style_selected_focused
: is_active ? option_->style_selected
: option_->style_normal;
auto focus_management = is_focused ? focus : is_active ? select : nothing;
return hbox({
text(*state_ ? option_->style_checked
: option_->style_unchecked),
text(*label_) | style | focus_management,
}) |
reflect(box_);
auto state = EntryState{
*label_,
*state_,
is_active,
is_focused || hovered_,
};
auto element = (option_->transform
? option_->transform
: CheckboxOption::Simple().transform)(std::move(state));
return element | focus_management | reflect(box_);
}
bool OnEvent(Event event) override {

View File

@ -31,8 +31,15 @@ Component Collapsible(ConstStringRef label, Component child, Ref<bool> show) {
Impl(ConstStringRef label, Component child, Ref<bool> show)
: label_(label), show_(std::move(show)) {
CheckboxOption opt;
opt.style_checked = "";
opt.style_unchecked = "";
opt.transform = [](EntryState s) {
auto prefix = text(s.state ? "" : "");
auto t = text(s.label);
if (s.active)
t |= bold;
if (s.focused)
t |= inverted;
return hbox({prefix, t});
};
Add(Container::Vertical({
Checkbox(label_, show_.operator->(), opt),
Maybe(std::move(child), show_.operator->()),

View File

@ -12,6 +12,10 @@
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
#include "ftxui/dom/elements.hpp" // for text, Element
namespace ftxui::animation {
class Params;
} // namespace ftxui::animation
namespace ftxui {
namespace {
@ -101,6 +105,15 @@ bool ComponentBase::OnEvent(Event event) {
return false;
}
/// @brief Called in response to an animation event.
/// @param animation_params the parameters of the animation
/// The default implementation dispatch the event to every child.
/// @ingroup component
void ComponentBase::OnAnimation(animation::Params& params) {
for (Component& child : children_)
child->OnAnimation(params);
}
/// @brief Return the currently Active child.
/// @return the currently Active child.
/// @ingroup component

View File

@ -0,0 +1,228 @@
#include "ftxui/component/component_options.hpp"
#include <memory> // for allocator, shared_ptr
#include "ftxui/component/animation.hpp" // for Function, Duration
#include "ftxui/dom/elements.hpp" // for Element, operator|, text, bold, dim, inverted, automerge
namespace ftxui {
void AnimatedColorOption::Set(Color a_inactive,
Color a_active,
animation::Duration a_duration,
animation::easing::Function a_function) {
enabled = true;
inactive = a_inactive;
active = a_active;
duration = a_duration;
function = a_function;
}
void UnderlineOption::SetAnimation(animation::Duration d,
animation::easing::Function f) {
SetAnimationDuration(d);
SetAnimationFunction(f);
}
void UnderlineOption::SetAnimationDuration(animation::Duration d) {
leader_duration = d;
follower_duration = d;
}
void UnderlineOption::SetAnimationFunction(animation::easing::Function f) {
leader_function = f;
follower_function = f;
}
void UnderlineOption::SetAnimationFunction(
animation::easing::Function f_leader,
animation::easing::Function f_follower) {
leader_function = f_leader;
follower_function = f_follower;
}
// static
MenuOption MenuOption::Horizontal() {
MenuOption option;
option.direction = Direction::Right;
option.entries.transform = [](EntryState state) {
Element e = text(state.label);
if (state.focused)
e |= inverted;
if (state.active)
e |= bold;
if (!state.focused && !state.active)
e |= dim;
return e;
};
option.elements_infix = [] { return text(" "); };
return option;
}
// static
MenuOption MenuOption::HorizontalAnimated() {
auto option = Horizontal();
option.underline.enabled = true;
return option;
}
// static
MenuOption MenuOption::Vertical() {
MenuOption option;
option.entries.transform = [](EntryState state) {
if (state.active)
state.label = "> " + state.label;
else
state.label = " " + state.label;
Element e = text(state.label);
if (state.focused)
e |= inverted;
if (state.active)
e |= bold;
if (!state.focused && !state.active)
e |= dim;
return e;
};
return option;
}
// static
MenuOption MenuOption::VerticalAnimated() {
auto option = MenuOption::Vertical();
option.entries.transform = [](EntryState state) {
Element e = text(state.label);
if (state.focused)
e |= inverted;
if (state.active)
e |= bold;
if (!state.focused && !state.active)
e |= dim;
return e;
};
option.underline.enabled = true;
return option;
}
// static
MenuOption MenuOption::Toggle() {
auto option = MenuOption::Horizontal();
option.elements_infix = [] { return text("") | automerge; };
return option;
}
/// @brief Create a ButtonOption, highlighted using [] characters.
// static
ButtonOption ButtonOption::Ascii() {
ButtonOption option;
option.transform = [](EntryState s) {
s.label = s.focused ? "[" + s.label + "]" //
: " " + s.label + " ";
return text(s.label);
};
return option;
}
/// @brief Create a ButtonOption, inverted when focused.
// static
ButtonOption ButtonOption::Simple() {
ButtonOption option;
option.transform = [](EntryState s) {
auto element = text(s.label) | borderLight;
if (s.focused)
element |= inverted;
return element;
};
return option;
}
/// @brief Create a ButtonOption, using animated colors.
// static
ButtonOption ButtonOption::Animated() {
return Animated(Color::Black, Color::GrayLight, //
Color::GrayDark, Color::White);
}
/// @brief Create a ButtonOption, using animated colors.
// static
ButtonOption ButtonOption::Animated(Color color) {
return ButtonOption::Animated(Color::Interpolate(0.85f, color, Color::Black),
Color::Interpolate(0.10f, color, Color::White),
Color::Interpolate(0.10f, color, Color::Black),
Color::Interpolate(0.85f, color, Color::White));
}
/// @brief Create a ButtonOption, using animated colors.
// static
ButtonOption ButtonOption::Animated(Color background, Color foreground) {
return ButtonOption::Animated(background, foreground, foreground, background);
}
/// @brief Create a ButtonOption, using animated colors.
// static
ButtonOption ButtonOption::Animated(Color background,
Color foreground,
Color background_focused,
Color foreground_focused) {
ButtonOption option;
option.transform = [](EntryState s) {
auto element = text(s.label) | borderEmpty;
if (s.focused)
element |= bold;
return element;
};
option.animated_colors.foreground.Set(foreground, foreground_focused);
option.animated_colors.background.Set(background, background_focused);
return option;
}
/// @brief Option for standard Checkbox.
// static
CheckboxOption CheckboxOption::Simple() {
auto option = CheckboxOption();
option.transform = [](EntryState s) {
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
// Microsoft terminal do not use fonts able to render properly the default
// radiobox glyph.
auto prefix = text(s.state ? "[X] " : "[ ] ");
#else
auto prefix = text(s.state ? "" : "");
#endif
auto t = text(s.label);
if (s.active)
t |= bold;
if (s.focused)
t |= inverted;
return hbox({prefix, t});
};
return option;
}
/// @brief Option for standard Radiobox
// static
RadioboxOption RadioboxOption::Simple() {
auto option = RadioboxOption();
option.transform = [](EntryState s) {
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
// Microsoft terminal do not use fonts able to render properly the default
// radiobox glyph.
auto prefix = text(s.state ? "(*) " : "( ) ");
#else
auto prefix = text(s.state ? "" : "");
#endif
auto t = text(s.label);
if (s.active)
t |= bold;
if (s.focused)
t |= inverted;
return hbox({prefix, t});
};
return option;
}
} // namespace ftxui
// Copyright 2022 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

View File

@ -17,8 +17,15 @@ Component Dropdown(ConstStringListRef entries, int* selected) {
Impl(ConstStringListRef entries, int* selected)
: entries_(std::move(entries)), selected_(selected) {
CheckboxOption option;
option.style_checked = "";
option.style_unchecked = "";
option.transform = [](EntryState s) {
auto prefix = text(s.state ? "" : "");
auto t = text(s.label);
if (s.active)
t |= bold;
if (s.focused)
t |= inverted;
return hbox({prefix, t});
};
checkbox_ = Checkbox(&title_, &show_, option),
radiobox_ = Radiobox(entries_, selected_);

View File

@ -66,7 +66,7 @@ class InputBase : public ComponentBase {
bool hovered = hovered_;
Decorator decorator = dim | main_decorator;
if (is_focused)
decorator = decorator | focus | inverted;
decorator = decorator | focus;
if (hovered || is_focused)
decorator = decorator | inverted;
return text(*placeholder_) | decorator | reflect(box_);

View File

@ -1,24 +1,65 @@
#include <algorithm> // for max
#include <algorithm> // for max, reverse
#include <chrono> // for milliseconds
#include <functional> // for function
#include <memory> // for shared_ptr, allocator_traits<>::value_type
#include <string> // for operator+, string
#include <memory> // for allocator, shared_ptr, allocator_traits<>::value_type, swap
#include <string> // for char_traits, operator+, string, basic_string
#include <utility> // for move
#include <vector> // for vector
#include <vector> // for vector, __alloc_traits<>::value_type
#include "ftxui/component/animation.hpp" // for Animator, Linear, Params (ptr only)
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
#include "ftxui/component/component.hpp" // for Make, Menu, MenuEntry
#include "ftxui/component/component.hpp" // for Make, Menu, MenuEntry, Toggle
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp, Event::Return, Event::Tab, Event::TabReverse
#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption, MenuOption::Direction, UnderlineOption, AnimatedColorOption, AnimatedColorsOption, MenuOption::Down, MenuOption::Left, MenuOption::Right, MenuOption::Up
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp, Event::Return, Event::Tab, Event::TabReverse
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Released, Mouse::WheelDown, Mouse::WheelUp, Mouse::None
#include "ftxui/component/screen_interactive.hpp" // for Component
#include "ftxui/dom/elements.hpp" // for operator|, Element, reflect, text, nothing, select, vbox, Elements, focus
#include "ftxui/dom/elements.hpp" // for operator|, Element, reflect, Decorator, nothing, Elements, bgcolor, color, hbox, separatorHSelector, separatorVSelector, vbox, xflex, yflex, text, bold, focus, inverted, select
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/color.hpp" // for Color
#include "ftxui/screen/util.hpp" // for clamp
#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef, ConstStringRef
namespace ftxui {
namespace {
Element DefaultOptionTransform(EntryState state) {
state.label = (state.active ? "> " : " ") + state.label;
Element e = text(state.label);
if (state.focused)
e = e | inverted;
if (state.active)
e = e | bold;
return e;
}
bool IsInverted(MenuOption::Direction direction) {
switch (direction) {
case MenuOption::Direction::Up:
case MenuOption::Direction::Left:
return true;
case MenuOption::Direction::Down:
case MenuOption::Direction::Right:
return false;
}
return false; // NOT_REACHED()
}
bool IsHorizontal(MenuOption::Direction direction) {
switch (direction) {
case MenuOption::Direction::Left:
case MenuOption::Direction::Right:
return true;
case MenuOption::Direction::Down:
case MenuOption::Direction::Up:
return false;
}
return false; // NOT_REACHED()
}
} // namespace
/// @brief A list of items. The user can navigate through them.
/// @ingroup component
class MenuBase : public ComponentBase {
@ -26,26 +67,148 @@ class MenuBase : public ComponentBase {
MenuBase(ConstStringListRef entries, int* selected, Ref<MenuOption> option)
: entries_(entries), selected_(selected), option_(option) {}
bool IsHorizontal() { return ftxui::IsHorizontal(option_->direction); }
void OnChange() {
if (option_->on_change)
option_->on_change();
}
void OnEnter() {
if (option_->on_enter)
option_->on_enter();
}
void Clamp() {
boxes_.resize(size());
*selected_ = util::clamp(*selected_, 0, size() - 1);
focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
}
void OnAnimation(animation::Params& params) override {
animator_first_.OnAnimation(params);
animator_second_.OnAnimation(params);
for (auto& animator : animator_background_)
animator.OnAnimation(params);
for (auto& animator : animator_foreground_)
animator.OnAnimation(params);
}
Element Render() override {
Clamp();
UpdateAnimationTarget();
Elements elements;
bool is_menu_focused = Focused();
if (option_->elements_prefix)
elements.push_back(option_->elements_prefix());
for (int i = 0; i < size(); ++i) {
if (i != 0 && option_->elements_infix)
elements.push_back(option_->elements_infix());
bool is_focused = (focused_entry() == i) && is_menu_focused;
bool is_selected = (*selected_ == i);
auto style = is_selected ? (is_focused ? option_->style_selected_focused
: option_->style_selected)
: (is_focused ? option_->style_focused
: option_->style_normal);
auto focus_management = !is_selected ? nothing
: is_menu_focused ? focus
: select;
auto icon = is_selected ? "> " : " ";
elements.push_back(text(icon + entries_[i]) | style | focus_management |
reflect(boxes_[i]));
: nothing;
EntryState state = {
entries_[i],
false,
is_selected,
is_focused,
};
Element element =
(option_->entries.transform ? option_->entries.transform
: DefaultOptionTransform) //
(std::move(state));
elements.push_back(element | AnimatedColorStyle(i) | reflect(boxes_[i]) |
focus_management);
}
if (option_->elements_postfix)
elements.push_back(option_->elements_postfix());
if (IsInverted(option_->direction))
std::reverse(elements.begin(), elements.end());
Element bar =
IsHorizontal() ? hbox(std::move(elements)) : vbox(std::move(elements));
if (!option_->underline.enabled)
return bar | reflect(box_);
if (IsHorizontal()) {
return vbox({
bar | xflex,
separatorHSelector(first_, second_, //
option_->underline.color_active,
option_->underline.color_inactive),
}) |
reflect(box_);
} else {
return hbox({
separatorVSelector(first_, second_, //
option_->underline.color_active,
option_->underline.color_inactive),
bar | yflex,
}) |
reflect(box_);
}
}
void OnUp() {
switch (option_->direction) {
case MenuOption::Direction::Up:
(*selected_)++;
break;
case MenuOption::Direction::Down:
(*selected_)--;
break;
case MenuOption::Direction::Left:
case MenuOption::Direction::Right:
break;
}
}
void OnDown() {
switch (option_->direction) {
case MenuOption::Direction::Up:
(*selected_)--;
break;
case MenuOption::Direction::Down:
(*selected_)++;
break;
case MenuOption::Direction::Left:
case MenuOption::Direction::Right:
break;
}
}
void OnLeft() {
switch (option_->direction) {
case MenuOption::Direction::Left:
(*selected_)++;
break;
case MenuOption::Direction::Right:
(*selected_)--;
break;
case MenuOption::Direction::Down:
case MenuOption::Direction::Up:
break;
}
}
void OnRight() {
switch (option_->direction) {
case MenuOption::Direction::Left:
(*selected_)--;
break;
case MenuOption::Direction::Right:
(*selected_)++;
break;
case MenuOption::Direction::Down:
case MenuOption::Direction::Up:
break;
}
return vbox(std::move(elements)) | reflect(box_);
}
bool OnEvent(Event event) override {
@ -59,9 +222,13 @@ class MenuBase : public ComponentBase {
if (Focused()) {
int old_selected = *selected_;
if (event == Event::ArrowUp || event == Event::Character('k'))
(*selected_)--;
OnUp();
if (event == Event::ArrowDown || event == Event::Character('j'))
(*selected_)++;
OnDown();
if (event == Event::ArrowLeft || event == Event::Character('h'))
OnLeft();
if (event == Event::ArrowRight || event == Event::Character('l'))
OnRight();
if (event == Event::PageUp)
(*selected_) -= box_.y_max - box_.y_min;
if (event == Event::PageDown)
@ -79,13 +246,13 @@ class MenuBase : public ComponentBase {
if (*selected_ != old_selected) {
focused_entry() = *selected_;
option_->on_change();
OnChange();
return true;
}
}
if (event == Event::Return) {
option_->on_enter();
OnEnter();
return true;
}
@ -114,7 +281,7 @@ class MenuBase : public ComponentBase {
event.mouse().motion == Mouse::Released) {
if (*selected_ != i) {
*selected_ = i;
option_->on_change();
OnChange();
}
return true;
}
@ -135,19 +302,115 @@ class MenuBase : public ComponentBase {
*selected_ = util::clamp(*selected_, 0, size() - 1);
if (*selected_ != old_selected)
option_->on_change();
OnChange();
return true;
}
void Clamp() {
boxes_.resize(size());
*selected_ = util::clamp(*selected_, 0, size() - 1);
focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
void UpdateAnimationTarget() {
UpdateColorTarget();
UpdateUnderlineTarget();
}
void UpdateColorTarget() {
if (size() != (int)animation_background_.size()) {
animation_background_.resize(size());
animation_foreground_.resize(size());
animator_background_.clear();
animator_foreground_.clear();
for (int i = 0; i < size(); ++i) {
animation_background_[i] = 0.f;
animation_foreground_[i] = 0.f;
animator_background_.emplace_back(&animation_background_[i], 0.f,
std::chrono::milliseconds(0),
animation::easing::Linear);
animator_foreground_.emplace_back(&animation_foreground_[i], 0.f,
std::chrono::milliseconds(0),
animation::easing::Linear);
}
}
bool is_menu_focused = Focused();
for (int i = 0; i < size(); ++i) {
bool is_focused = (focused_entry() == i) && is_menu_focused;
bool is_selected = (*selected_ == i);
float target = is_selected ? 1.f : is_focused ? 0.5f : 0.f;
if (animator_background_[i].to() != target) {
animator_background_[i] = animation::Animator(
&animation_background_[i], target,
option_->entries.animated_colors.background.duration,
option_->entries.animated_colors.background.function);
animator_foreground_[i] = animation::Animator(
&animation_foreground_[i], target,
option_->entries.animated_colors.foreground.duration,
option_->entries.animated_colors.foreground.function);
}
}
}
Decorator AnimatedColorStyle(int i) {
Decorator style = nothing;
if (option_->entries.animated_colors.foreground.enabled) {
style = style | color(Color::Interpolate(
animation_foreground_[i],
option_->entries.animated_colors.foreground.inactive,
option_->entries.animated_colors.foreground.active));
}
if (option_->entries.animated_colors.background.enabled) {
style = style | bgcolor(Color::Interpolate(
animation_background_[i],
option_->entries.animated_colors.background.inactive,
option_->entries.animated_colors.background.active));
}
return style;
}
void UpdateUnderlineTarget() {
if (!option_->underline.enabled)
return;
if (FirstTarget() == animator_first_.to() &&
SecondTarget() == animator_second_.to()) {
return;
}
if (FirstTarget() >= animator_first_.to()) {
animator_first_ = animation::Animator(
&first_, FirstTarget(), option_->underline.follower_duration,
option_->underline.follower_function,
option_->underline.follower_delay);
animator_second_ = animation::Animator(
&second_, SecondTarget(), option_->underline.leader_duration,
option_->underline.leader_function, option_->underline.leader_delay);
} else {
animator_first_ = animation::Animator(
&first_, FirstTarget(), option_->underline.leader_duration,
option_->underline.leader_function, option_->underline.leader_delay);
animator_second_ = animation::Animator(
&second_, SecondTarget(), option_->underline.follower_duration,
option_->underline.follower_function,
option_->underline.follower_delay);
}
}
bool Focusable() const final { return entries_.size(); }
int& focused_entry() { return option_->focused_entry(); }
int size() const { return entries_.size(); }
int FirstTarget() {
if (boxes_.size() == 0)
return 0;
return IsHorizontal() ? boxes_[*selected_].x_min - box_.x_min
: boxes_[*selected_].y_min - box_.y_min;
}
int SecondTarget() {
if (boxes_.size() == 0)
return 0;
return IsHorizontal() ? boxes_[*selected_].x_max - box_.x_min
: boxes_[*selected_].y_max - box_.y_min;
}
protected:
ConstStringListRef entries_;
@ -156,6 +419,16 @@ class MenuBase : public ComponentBase {
std::vector<Box> boxes_;
Box box_;
float first_ = 0.f;
float second_ = 0.f;
animation::Animator animator_first_ = animation::Animator(&first_, 0.f);
animation::Animator animator_second_ = animation::Animator(&second_, 0.f);
std::vector<animation::Animator> animator_background_;
std::vector<animation::Animator> animator_foreground_;
std::vector<float> animation_background_;
std::vector<float> animation_foreground_;
};
/// @brief A list of text. The focused element is selected.
@ -163,7 +436,6 @@ class MenuBase : public ComponentBase {
/// @param selected The index of the currently selected element.
/// @param option Additional optional parameters.
/// @ingroup component
/// @see MenuBase
///
/// ### Example
///
@ -192,6 +464,41 @@ Component Menu(ConstStringListRef entries,
return Make<MenuBase>(entries, selected, std::move(option));
}
/// @brief An horizontal list of elements. The user can navigate through them.
/// @param entries The list of selectable entries to display.
/// @param selected Reference the selected entry.
/// @param See also |Menu|.
/// @ingroup component
Component Toggle(ConstStringListRef entries, int* selected) {
return Menu(entries, selected, MenuOption::Toggle());
}
/// @brief A specific menu entry. They can be put into a Container::Vertical to
/// form a menu.
/// @param label The text drawn representing this element.
/// @param option Additional optional parameters.
/// @ingroup component
///
/// ### Example
///
/// ```cpp
/// auto screen = ScreenInteractive::TerminalOutput();
/// int selected = 0;
/// auto menu = Container::Vertical({
/// MenuEntry("entry 1"),
/// MenuEntry("entry 2"),
/// MenuEntry("entry 3"),
/// }, &selected);
/// screen.Loop(menu);
/// ```
///
/// ### Output
///
/// ```bash
/// > entry 1
/// entry 2
/// entry 3
/// ```
Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
class Impl : public ComponentBase {
public:
@ -201,15 +508,56 @@ Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
private:
Element Render() override {
bool focused = Focused();
auto style =
hovered_ ? (focused ? option_->style_selected_focused
: option_->style_selected)
: (focused ? option_->style_focused : option_->style_normal);
UpdateAnimationTarget();
EntryState state = {
*label_,
hovered_,
focused,
false,
};
Element element =
(option_->transform ? option_->transform : DefaultOptionTransform) //
(std::move(state));
auto focus_management = focused ? select : nothing;
auto label = focused ? "> " + (*label_) //
: " " + (*label_);
return text(label) | style | focus_management | reflect(box_);
return element | AnimatedColorStyle() | focus_management | reflect(box_);
}
void UpdateAnimationTarget() {
bool focused = Focused();
float target = focused ? 1.0f : hovered_ ? 0.5f : 0.0f;
if (target == animator_background_.to())
return;
animator_background_ =
animation::Animator(&animation_background_, target,
option_->animated_colors.background.duration,
option_->animated_colors.background.function);
animator_foreground_ =
animation::Animator(&animation_foreground_, target,
option_->animated_colors.foreground.duration,
option_->animated_colors.foreground.function);
}
Decorator AnimatedColorStyle() {
Decorator style = nothing;
if (option_->animated_colors.foreground.enabled) {
style = style | color(Color::Interpolate(
animation_foreground_,
option_->animated_colors.foreground.inactive,
option_->animated_colors.foreground.active));
}
if (option_->animated_colors.background.enabled) {
style = style | bgcolor(Color::Interpolate(
animation_background_,
option_->animated_colors.background.inactive,
option_->animated_colors.background.active));
}
return style;
}
bool Focusable() const override { return true; }
bool OnEvent(Event event) override {
if (!event.is_mouse())
@ -228,10 +576,23 @@ Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
return false;
}
void OnAnimation(animation::Params& params) override {
animator_background_.OnAnimation(params);
animator_foreground_.OnAnimation(params);
}
ConstStringRef label_;
Ref<MenuEntryOption> option_;
Box box_;
bool hovered_ = false;
float animation_background_ = 0.f;
float animation_foreground_ = 0.f;
animation::Animator animator_background_ =
animation::Animator(&animation_background_, 0.f);
animation::Animator animator_foreground_ =
animation::Animator(&animation_foreground_, 0.f);
};
return Make<Impl>(std::move(label), std::move(option));

View File

@ -29,14 +29,6 @@ class RadioboxBase : public ComponentBase {
int* selected,
Ref<RadioboxOption> option)
: entries_(entries), selected_(selected), option_(std::move(option)) {
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
// Microsoft terminal do not use fonts able to render properly the default
// radiobox glyph.
if (option_->style_checked == "")
option_->style_checked = "(*)";
if (option_->style_unchecked == "")
option_->style_unchecked = "( )";
#endif
hovered_ = *selected_;
}
@ -48,19 +40,21 @@ class RadioboxBase : public ComponentBase {
for (int i = 0; i < size(); ++i) {
bool is_focused = (focused_entry() == i) && is_menu_focused;
bool is_selected = (hovered_ == i);
auto style = is_selected ? (is_focused ? option_->style_selected_focused
: option_->style_selected)
: (is_focused ? option_->style_focused
: option_->style_normal);
auto focus_management = !is_selected ? nothing
: is_menu_focused ? focus
: select;
auto state = EntryState{
entries_[i],
*selected_ == i,
is_selected,
is_focused,
};
auto element =
(option_->transform
? option_->transform
: RadioboxOption::Simple().transform)(std::move(state));
const std::string& symbol =
*selected_ == i ? option_->style_checked : option_->style_unchecked;
elements.push_back(hbox(text(symbol), text(entries_[i]) | style) |
focus_management | reflect(boxes_[i]));
elements.push_back(element | focus_management | reflect(boxes_[i]));
}
return vbox(std::move(elements)) | reflect(box_);
}

View File

@ -1,21 +1,23 @@
#include <stdio.h> // for fileno, stdin
#include <algorithm> // for copy, max, min
#include <chrono> // for operator-, duration, operator>=, milliseconds, time_point, common_type<>::type
#include <csignal> // for signal, raise, SIGTSTP, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, SIGWINCH
#include <cstdlib> // for NULL
#include <functional> // for function
#include <initializer_list> // for initializer_list
#include <iostream> // for cout, ostream, basic_ostream, operator<<, endl, flush
#include <stack> // for stack
#include <thread> // for thread
#include <thread> // for thread, sleep_for
#include <type_traits> // for decay_t
#include <utility> // for swap, move
#include <variant> // for visit
#include <vector> // for vector
#include "ftxui/component/animation.hpp" // for TimePoint, Clock, Duration, Params, RequestAnimationFrame
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/event.hpp" // for Event
#include "ftxui/component/receiver.hpp" // for ReceiverImpl, MakeReceiver, Sender, SenderImpl, Receiver
#include "ftxui/component/receiver.hpp" // for ReceiverImpl, Sender, MakeReceiver, SenderImpl, Receiver
#include "ftxui/component/screen_interactive.hpp"
#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
#include "ftxui/dom/node.hpp" // for Node, Render
@ -45,6 +47,14 @@
namespace ftxui {
namespace animation {
void RequestAnimationFrame() {
auto* screen = ScreenInteractive::Active();
if (screen)
screen->RequestAnimationFrame();
}
} // namespace animation
namespace {
ScreenInteractive* g_active_screen = nullptr;
@ -236,6 +246,13 @@ class CapturedMouseImpl : public CapturedMouseInterface {
std::function<void(void)> callback_;
};
void AnimationListener(std::atomic<bool>* quit, Sender<Task> out) {
while (!*quit) {
out->Send(AnimationTask());
std::this_thread::sleep_for(std::chrono::milliseconds(15));
}
}
} // namespace
ScreenInteractive::ScreenInteractive(int dimx,
@ -276,6 +293,15 @@ void ScreenInteractive::PostEvent(Event event) {
Post(event);
}
void ScreenInteractive::RequestAnimationFrame() {
if (animation_requested_)
return;
animation_requested_ = true;
auto now = animation::Clock::now();
if (now - previous_animation_time >= std::chrono::milliseconds(33))
previous_animation_time = now;
}
CapturedMouse ScreenInteractive::CaptureMouse() {
if (mouse_captured)
return nullptr;
@ -330,6 +356,11 @@ Closure ScreenInteractive::WithRestoredIO(Closure fn) {
};
}
// static
ScreenInteractive* ScreenInteractive::Active() {
return g_active_screen;
}
void ScreenInteractive::Install() {
// After uninstalling the new configuration, flush it to the terminal to
// ensure it is fully applied:
@ -432,24 +463,37 @@ void ScreenInteractive::Install() {
task_sender_ = task_receiver_->MakeSender();
event_listener_ =
std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
animation_listener_ =
std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
}
void ScreenInteractive::Uninstall() {
ExitLoopClosure()();
event_listener_.join();
animation_listener_.join();
OnExit(0);
}
void ScreenInteractive::Main(Component component) {
while (!quit_) {
if (!task_receiver_->HasPending()) {
previous_animation_time = animation::Clock::now();
auto draw = [&] {
Draw(component);
std::cout << ToString() << set_cursor_position;
Flush();
Clear();
}
};
draw();
while (!quit_) {
if (!task_receiver_->HasPending())
draw();
bool continue_event_loop = true;
while (continue_event_loop) {
continue_event_loop = false;
Task task;
if (!task_receiver_->Receive(&task))
break;
@ -476,12 +520,30 @@ void ScreenInteractive::Main(Component component) {
}
// Handle callback
if constexpr (std::is_same_v<T, Closure>)
if constexpr (std::is_same_v<T, Closure>) {
arg();
return;
}
// Handle Animation
if constexpr (std::is_same_v<T, AnimationTask>) {
if (!animation_requested_) {
continue_event_loop = true;
return;
}
animation_requested_ = false;
animation::TimePoint now = animation::Clock::now();
animation::Duration delta = now - previous_animation_time;
previous_animation_time = now;
animation::Params params(delta);
component->OnAnimation(params);
}
},
task);
}
}
}
void ScreenInteractive::Draw(Component component) {
auto document = component->Render();

View File

@ -1,144 +0,0 @@
#include <algorithm> // for max
#include <functional> // for function
#include <memory> // for shared_ptr, allocator_traits<>::value_type
#include <utility> // for move
#include <vector> // for vector
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
#include "ftxui/component/component.hpp" // for Make, Toggle
#include "ftxui/component/component_base.hpp" // for Component, ComponentBase
#include "ftxui/component/component_options.hpp" // for ToggleOption
#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowRight, Event::Return, Event::Tab, Event::TabReverse
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
#include "ftxui/dom/elements.hpp" // for operator|, Element, Elements, hbox, reflect, separator, text, focus, nothing, select
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/util.hpp" // for clamp
#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef
namespace ftxui {
namespace {
/// @brief An horizontal list of elements. The user can navigate through them.
/// @ingroup component
class ToggleBase : public ComponentBase {
public:
ToggleBase(ConstStringListRef entries,
int* selected,
Ref<ToggleOption> option)
: entries_(entries), selected_(selected), option_(std::move(option)) {}
private:
Element Render() override {
Clamp();
Elements children;
bool is_toggle_focused = Focused();
for (int i = 0; i < size(); ++i) {
// Separator.
if (i != 0)
children.push_back(separator());
bool is_focused = (focused_entry() == i) && is_toggle_focused;
bool is_selected = (*selected_ == i);
auto style = is_selected ? (is_focused ? option_->style_selected_focused
: option_->style_selected)
: (is_focused ? option_->style_focused
: option_->style_normal);
auto focus_management = !is_selected ? nothing
: is_toggle_focused ? focus
: select;
children.push_back(text(entries_[i]) | style | focus_management |
reflect(boxes_[i]));
}
return hbox(std::move(children));
}
bool OnEvent(Event event) override {
Clamp();
if (event.is_mouse())
return OnMouseEvent(event);
int old_selected = *selected_;
if (event == Event::ArrowLeft || event == Event::Character('h'))
(*selected_)--;
if (event == Event::ArrowRight || event == Event::Character('l'))
(*selected_)++;
if (event == Event::Tab && size())
*selected_ = (*selected_ + 1) % size();
if (event == Event::TabReverse && size())
*selected_ = (*selected_ + size() - 1) % size();
*selected_ = util::clamp(*selected_, 0, size() - 1);
if (old_selected != *selected_) {
focused_entry() = *selected_;
option_->on_change();
return true;
}
if (event == Event::Return) {
option_->on_enter();
return true;
}
return false;
}
bool OnMouseEvent(Event event) {
if (!CaptureMouse(event))
return false;
for (int i = 0; i < size(); ++i) {
if (!boxes_[i].Contain(event.mouse().x, event.mouse().y))
continue;
TakeFocus();
focused_entry() = i;
if (event.mouse().button == Mouse::Left &&
event.mouse().motion == Mouse::Pressed) {
TakeFocus();
if (*selected_ != i) {
*selected_ = i;
option_->on_change();
}
return true;
}
}
return false;
}
void Clamp() {
boxes_.resize(size());
*selected_ = util::clamp(*selected_, 0, size() - 1);
focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
}
bool Focusable() const final { return size(); }
int& focused_entry() { return option_->focused_entry(); }
int size() const { return entries_.size(); }
ConstStringListRef entries_;
int* selected_ = 0;
std::vector<Box> boxes_;
Ref<ToggleOption> option_;
};
} // namespace
/// @brief An horizontal list of elements. The user can navigate through them.
/// @param entries The list of selectable entries to display.
/// @param selected Reference the selected entry.
/// @param option Additional optional parameters.
/// @ingroup component
Component Toggle(ConstStringListRef entries,
int* selected,
Ref<ToggleOption> option) {
return Make<ToggleBase>(entries, selected, std::move(option));
}
} // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

View File

@ -90,10 +90,10 @@ TEST(ToggleTest, OnChange) {
std::vector<std::string> entries = {"1", "2", "3"};
int selected = 0;
int counter = 0;
auto option = ToggleOption();
auto option = MenuOption::Toggle();
option.on_change = [&] { counter++; };
auto toggle = Toggle(&entries, &selected, &option);
auto toggle = Menu(&entries, &selected, &option);
EXPECT_FALSE(toggle->OnEvent(Event::ArrowLeft)); // Reached far left.
EXPECT_EQ(counter, 0);
@ -120,9 +120,9 @@ TEST(ToggleTest, OnEnter) {
int selected = 0;
int counter = 0;
auto option = ToggleOption();
auto option = MenuOption::Toggle();
option.on_enter = [&] { counter++; };
auto toggle = Toggle(&entries, &selected, &option);
auto toggle = Menu(&entries, &selected, &option);
EXPECT_FALSE(toggle->OnEvent(Event::ArrowLeft)); // Reached far left.
EXPECT_TRUE(toggle->OnEvent(Event::Return));
@ -155,9 +155,9 @@ TEST(ToggleTest, RemoveEntries) {
int focused_entry = 0;
int selected = 0;
std::vector<std::string> entries = {"1", "2", "3"};
ToggleOption option;
auto option = MenuOption::Toggle();
option.focused_entry = &focused_entry;
auto toggle = Toggle(&entries, &selected, option);
auto toggle = Menu(&entries, &selected, option);
EXPECT_EQ(selected, 0);
EXPECT_EQ(focused_entry, 0);

View File

@ -1,9 +1,7 @@
#include <functional> // for function
#include <memory> // for __shared_ptr_access
#include "ftxui/component/component.hpp" // for ElementDecorator, Renderer, ComponentDecorator, operator|, operator|=
#include "ftxui/component/component_base.hpp" // for Component, ComponentBase
#include "ftxui/dom/elements.hpp" // for Element
#include "ftxui/component/component.hpp" // for Renderer, ComponentDecorator, ElementDecorator, operator|, operator|=
#include "ftxui/component/component_base.hpp" // for Component
namespace ftxui {

View File

@ -1,10 +1,11 @@
#include <memory> // for make_shared
#include <memory> // for make_shared, allocator
#include <string> // for string
#include "ftxui/dom/elements.hpp" // for Element, separator
#include "ftxui/dom/elements.hpp" // for Element, BorderStyle, LIGHT, separator, DOUBLE, EMPTY, HEAVY, separatorCharacter, separatorDouble, separatorEmpty, separatorHSelector, separatorHeavy, separatorLight, separatorStyled, separatorVSelector
#include "ftxui/dom/node.hpp" // for Node
#include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/color.hpp" // for Color
#include "ftxui/screen/screen.hpp" // for Pixel, Screen
namespace ftxui {
@ -369,6 +370,142 @@ Element separator(Pixel pixel) {
return std::make_shared<SeparatorWithPixel>(pixel);
}
/// @brief Draw an horizontal bar, with the area in between left/right colored
/// differently.
/// @param left the left limit of the active area.
/// @param right the right limit of the active area.
/// @param selected_color the color of the selected area.
/// @param unselected_color the color of the unselected area.
///
/// ### Example
///
/// ```cpp
/// Element document = separatorHSelector(2,5, Color::White, Color::Blue);
/// ```
Element separatorHSelector(float left,
float right,
Color selected_color,
Color unselected_color) {
class Impl : public Node {
public:
Impl(float left, float right, Color selected_color, Color unselected_color)
: left_(left),
right_(right),
selected_color_(selected_color),
unselected_color_(unselected_color) {}
void ComputeRequirement() override {
requirement_.min_x = 1;
requirement_.min_y = 1;
}
void Render(Screen& screen) override {
if (box_.y_max < box_.y_min)
return;
// This are the two location with an empty demi-cell.
int demi_cell_left = left_ * 2 - 1;
int demi_cell_right = right_ * 2 + 2;
int y = box_.y_min;
for (int x = box_.x_min; x <= box_.x_max; ++x) {
Pixel& pixel = screen.PixelAt(x, y);
int a = (x - box_.x_min) * 2;
int b = a + 1;
bool a_empty = demi_cell_left == a || demi_cell_right == a;
bool b_empty = demi_cell_left == b || demi_cell_right == b;
if (!a_empty && !b_empty) {
pixel.character = "";
pixel.automerge = true;
} else {
pixel.character = a_empty ? "" : "";
pixel.automerge = false;
}
if (demi_cell_left <= a && b <= demi_cell_right)
pixel.foreground_color = selected_color_;
else
pixel.foreground_color = unselected_color_;
}
}
float left_;
float right_;
Color selected_color_;
Color unselected_color_;
};
return std::make_shared<Impl>(left, right, selected_color, unselected_color);
}
/// @brief Draw an vertical bar, with the area in between up/downcolored
/// differently.
/// @param up the left limit of the active area.
/// @param down the right limit of the active area.
/// @param selected_color the color of the selected area.
/// @param unselected_color the color of the unselected area.
///
/// ### Example
///
/// ```cpp
/// Element document = separatorHSelector(2,5, Color::White, Color::Blue);
/// ```
Element separatorVSelector(float up,
float down,
Color selected_color,
Color unselected_color) {
class Impl : public Node {
public:
Impl(float up, float down, Color selected_color, Color unselected_color)
: up_(up),
down_(down),
selected_color_(selected_color),
unselected_color_(unselected_color) {}
void ComputeRequirement() override {
requirement_.min_x = 1;
requirement_.min_y = 1;
}
void Render(Screen& screen) override {
if (box_.x_max < box_.x_min)
return;
// This are the two location with an empty demi-cell.
int demi_cell_up = up_ * 2 - 1;
int demi_cell_down = down_ * 2 + 2;
int x = box_.x_min;
for (int y = box_.y_min; y <= box_.y_max; ++y) {
Pixel& pixel = screen.PixelAt(x, y);
int a = (y - box_.y_min) * 2;
int b = a + 1;
bool a_empty = demi_cell_up == a || demi_cell_down == a;
bool b_empty = demi_cell_up == b || demi_cell_down == b;
if (!a_empty && !b_empty) {
pixel.character = "";
pixel.automerge = true;
} else {
pixel.character = a_empty ? "" : "";
pixel.automerge = false;
}
if (demi_cell_up <= a && b <= demi_cell_down)
pixel.foreground_color = selected_color_;
else
pixel.foreground_color = unselected_color_;
}
}
float up_;
float down_;
Color selected_color_;
Color unselected_color_;
};
return std::make_shared<Impl>(up, down, selected_color, unselected_color);
}
} // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved.

View File

@ -145,6 +145,77 @@ Color Color::HSV(uint8_t h, uint8_t s, uint8_t v) {
return Color(0, 0, 0);
}
// static
Color Color::Interpolate(float t, const Color& a, const Color& b) {
float red;
float green;
float blue;
switch (a.type_) {
case ColorType::Palette1: {
if (t < 0.5)
return a;
else
return b;
}
case ColorType::Palette16: {
ColorInfo info = GetColorInfo(Color::Palette16(a.index_));
red = info.red * (1 - t);
green = info.green * (1 - t);
blue = info.blue * (1 - t);
break;
}
case ColorType::Palette256: {
ColorInfo info = GetColorInfo(Color::Palette256(a.index_));
red = info.red * (1 - t);
green = info.green * (1 - t);
blue = info.blue * (1 - t);
break;
}
case ColorType::TrueColor: {
red = a.red_ * (1 - t);
green = a.green_ * (1 - t);
blue = a.blue_ * (1 - t);
break;
}
}
switch (b.type_) {
case ColorType::Palette1: {
if (t > 0.5)
return a;
else
return b;
}
case ColorType::Palette16: {
ColorInfo info = GetColorInfo(Color::Palette16(b.index_));
red += info.red * t;
green += info.green * t;
blue += info.blue * t;
break;
}
case ColorType::Palette256: {
ColorInfo info = GetColorInfo(Color::Palette256(b.index_));
red += info.red * t;
green += info.green * t;
blue += info.blue * t;
break;
}
case ColorType::TrueColor: {
red += b.red_ * t;
green += b.green_ * t;
blue += b.blue_ * t;
break;
}
}
return Color::RGB(red, green, blue);
}
inline namespace literals {
Color operator""_rgb(unsigned long long int combined) {