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 - **breaking**: The library prefix is now back to "lib" (the default). This
means non-cmake users should not link against "libftxui-dom" for instance. 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: ### DOM:
- **breaking**: The `inverted` decorator now toggle in the inverted attribute. - **breaking**: The `inverted` decorator now toggle in the inverted attribute.
- Add `gauge` for the 4 directions. Expose the following API: - Add `gauge` for the 4 directions. Expose the following API:
@ -19,29 +46,17 @@ Element gaugeUp(float ratio);
Element gaugeDown(float ratio); Element gaugeDown(float ratio);
Element gaugeDirection(float ratio, GaugeDirection); 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 - Add the `automerge` decorator. This makes separator characters to be merged
with others nearby. with others nearby.
- Fix the `Table` rendering function, to allow automerging characters. - 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)` - Add the `operator|=(Element, Decorator)`
### Component ### Screen:
- Support SIGTSTP. (ctrl+z). - Add: `Color::Interpolate(lambda, color_a, color_b)`.
- 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.
2.0.0 2.0.0
----- -----

View File

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

View File

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

View File

@ -10,6 +10,9 @@ add_subdirectory(component)
add_subdirectory(dom) add_subdirectory(dom)
if (EMSCRIPTEN) 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) get_property(EXAMPLES GLOBAL PROPERTY FTXUI::EXAMPLES)
foreach(file foreach(file
"index.html" "index.html"

View File

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

View File

@ -1,12 +1,11 @@
#include <memory> // for shared_ptr, __shared_ptr_access #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/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer #include "ftxui/component/component.hpp" // for Button, Horizontal, Renderer
#include "ftxui/component/component_base.hpp" // for ComponentBase #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/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; using namespace ftxui;
@ -14,13 +13,9 @@ int main(int argc, const char* argv[]) {
int value = 50; int value = 50;
// The tree of components. This defines how to navigate using the keyboard. // The tree of components. This defines how to navigate using the keyboard.
auto button_option = ButtonOption();
button_option.border = false;
auto buttons = Container::Horizontal({ auto buttons = Container::Horizontal({
Button( Button("Decrease", [&] { value--; }),
"[Decrease]", [&] { value--; }, &button_option), Button("Increase", [&] { value++; }),
Button(
"[Increase]", [&] { value++; }, &button_option),
}); });
// Modify the way to render them on screen: // 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); std::vector<int> ys(100);
for (int x = 0; x < 100; x++) { for (int x = 0; x < 100; x++) {
float dx = x - mouse_x; float dx = float(x - mouse_x);
float dy = 50; float dy = 50.f;
ys[x] = dy + 20 * cos(dx * 0.14) + 10 * sin(dx * 0.42); ys[x] = int(dy + 20 * cos(dx * 0.14) + 10 * sin(dx * 0.42));
} }
for (int x = 1; x < 99; x++) for (int x = 1; x < 99; x++)
c.DrawPointLine(x, ys[x], x + 1, ys[x + 1]); 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"); c.DrawText(0, 0, "A symmetrical graph filled");
std::vector<int> ys(100); std::vector<int> ys(100);
for (int x = 0; x < 100; x++) { for (int x = 0; x < 100; x++) {
ys[x] = 30 + // ys[x] = int(30 + //
10 * cos(x * 0.2 - mouse_x * 0.05) + // 10 * cos(x * 0.2 - mouse_x * 0.05) + //
5 * sin(x * 0.4) + // 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++) { for (int x = 0; x < 100; x++) {
c.DrawPointLine(x, 50 + ys[x], x, 50 - ys[x], Color::Red); 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++) { for (int x = 0; x < size; x++) {
float dx = x - mx; float dx = x - mx;
float dy = y - my; 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++) { 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 <memory> // for shared_ptr, __shared_ptr_access, allocator_traits<>::value_type
#include <string> // for operator+, to_string #include <string> // for operator+, to_string
#include <vector> // for vector #include <vector> // for vector
@ -10,17 +11,13 @@
using namespace ftxui; using namespace ftxui;
struct CheckboxState {
bool checked;
};
int main(int argc, const char* argv[]) { int main(int argc, const char* argv[]) {
std::vector<CheckboxState> states(30); std::array<bool, 30> states;
auto container = Container::Vertical({}); auto container = Container::Vertical({});
for (int i = 0; i < 30; ++i) { for (int i = 0; i < 30; ++i) {
states[i].checked = false; states[i] = false;
container->Add( container->Add(Checkbox("Checkbox" + std::to_string(i), &states[i]));
Checkbox("Checkbox" + std::to_string(i), &states[i].checked));
} }
auto renderer = Renderer(container, [&] { auto renderer = Renderer(container, [&] {

View File

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

View File

@ -9,15 +9,17 @@
#include <utility> // for move #include <utility> // for move
#include <vector> // for vector #include <vector> // for vector
#include "ftxui/component/captured_mouse.hpp" // for ftxui #include "../dom/color_info_sorted_2d.ipp" // for ColorInfoSorted2D
#include "ftxui/component/component.hpp" // for Checkbox, Renderer, Horizontal, Vertical, Input, Menu, Radiobox, ResizableSplitLeft, Tab, Toggle #include "ftxui/component/component.hpp" // for Checkbox, Renderer, Horizontal, Vertical, Input, Menu, Radiobox, ResizableSplitLeft, Tab
#include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/component_base.hpp" // for ComponentBase
#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/event.hpp" // for Event, Event::Custom
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive #include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
#include "ftxui/dom/elements.hpp" // for text, 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/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/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/flexbox_config.hpp" // for FlexboxConfig
#include "ftxui/screen/terminal.hpp" // for Size, Dimensions #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; using namespace ftxui;
@ -27,7 +29,6 @@ int main(int argc, const char* argv[]) {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// HTOP // HTOP
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
int shift = 0; int shift = 0;
auto my_graph = [&shift](int width, int height) { auto my_graph = [&shift](int width, int height) {
@ -95,7 +96,7 @@ int main(int argc, const char* argv[]) {
separator(), separator(),
ram | flex, ram | flex,
}) | }) |
flex | border; flex;
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -255,7 +256,7 @@ int main(int argc, const char* argv[]) {
}) | size(HEIGHT, LESS_THAN, 8), }) | size(HEIGHT, LESS_THAN, 8),
hflow(render_command()) | flex_grow, hflow(render_command()) | flex_grow,
}) | }) |
flex_grow | border; flex_grow;
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -267,61 +268,116 @@ int main(int argc, const char* argv[]) {
entries.push_back(spinner(i, shift / 2) | bold | entries.push_back(spinner(i, shift / 2) | bold |
size(WIDTH, GREATER_THAN, 2) | border); size(WIDTH, GREATER_THAN, 2) | border);
} }
return hflow(std::move(entries)) | border; return hflow(std::move(entries));
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Colors // Colors
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
auto color_tab_renderer = Renderer([] { auto color_tab_renderer = Renderer([] {
return hbox({ auto basic_color_display =
vbox({ vbox({
color(Color::Default, text("Default")), text("16 color palette:"),
color(Color::Black, text("Black")), separator(),
color(Color::GrayDark, text("GrayDark")), hbox({
color(Color::GrayLight, text("GrayLight")), vbox({
color(Color::White, text("White")), color(Color::Default, text("Default")),
color(Color::Blue, text("Blue")), color(Color::Black, text("Black")),
color(Color::BlueLight, text("BlueLight")), color(Color::GrayDark, text("GrayDark")),
color(Color::Cyan, text("Cyan")), color(Color::GrayLight, text("GrayLight")),
color(Color::CyanLight, text("CyanLight")), color(Color::White, text("White")),
color(Color::Green, text("Green")), color(Color::Blue, text("Blue")),
color(Color::GreenLight, text("GreenLight")), color(Color::BlueLight, text("BlueLight")),
color(Color::Magenta, text("Magenta")), color(Color::Cyan, text("Cyan")),
color(Color::MagentaLight, text("MagentaLight")), color(Color::CyanLight, text("CyanLight")),
color(Color::Red, text("Red")), color(Color::Green, text("Green")),
color(Color::RedLight, text("RedLight")), color(Color::GreenLight, text("GreenLight")),
color(Color::Yellow, text("Yellow")), color(Color::Magenta, text("Magenta")),
color(Color::YellowLight, text("YellowLight")), color(Color::MagentaLight, text("MagentaLight")),
}), color(Color::Red, text("Red")),
vbox({ color(Color::RedLight, text("RedLight")),
bgcolor(Color::Default, text("Default")), color(Color::Yellow, text("Yellow")),
bgcolor(Color::Black, text("Black")), color(Color::YellowLight, text("YellowLight")),
bgcolor(Color::GrayDark, text("GrayDark")), }),
bgcolor(Color::GrayLight, text("GrayLight")), vbox({
bgcolor(Color::White, text("White")), bgcolor(Color::Default, text("Default")),
bgcolor(Color::Blue, text("Blue")), bgcolor(Color::Black, text("Black")),
bgcolor(Color::BlueLight, text("BlueLight")), bgcolor(Color::GrayDark, text("GrayDark")),
bgcolor(Color::Cyan, text("Cyan")), bgcolor(Color::GrayLight, text("GrayLight")),
bgcolor(Color::CyanLight, text("CyanLight")), bgcolor(Color::White, text("White")),
bgcolor(Color::Green, text("Green")), bgcolor(Color::Blue, text("Blue")),
bgcolor(Color::GreenLight, text("GreenLight")), bgcolor(Color::BlueLight, text("BlueLight")),
bgcolor(Color::Magenta, text("Magenta")), bgcolor(Color::Cyan, text("Cyan")),
bgcolor(Color::MagentaLight, text("MagentaLight")), bgcolor(Color::CyanLight, text("CyanLight")),
bgcolor(Color::Red, text("Red")), bgcolor(Color::Green, text("Green")),
bgcolor(Color::RedLight, text("RedLight")), bgcolor(Color::GreenLight, text("GreenLight")),
bgcolor(Color::Yellow, text("Yellow")), bgcolor(Color::Magenta, text("Magenta")),
bgcolor(Color::YellowLight, text("YellowLight")), bgcolor(Color::MagentaLight, text("MagentaLight")),
}), bgcolor(Color::Red, text("Red")),
}) | bgcolor(Color::RedLight, text("RedLight")),
hcenter | border; bgcolor(Color::Yellow, text("Yellow")),
bgcolor(Color::YellowLight, text("YellowLight")),
}),
}),
}) |
border;
auto palette_256_color_display = text("256 colors palette:");
{
std::vector<std::vector<ColorInfo>> info_columns = ColorInfoSorted2D();
Elements columns;
for (auto& column : info_columns) {
Elements column_elements;
for (auto& it : column) {
column_elements.push_back(
text(" ") | bgcolor(Color(Color::Palette256(it.index_256))));
}
columns.push_back(hbox(std::move(column_elements)));
}
palette_256_color_display = vbox({
palette_256_color_display,
separator(),
vbox(columns),
}) |
border;
}
// True color display.
auto true_color_display = text("TrueColors: 24bits:");
{
int saturation = 255;
Elements array;
for (int value = 0; value < 255; value += 16) {
Elements line;
for (int hue = 0; hue < 255; hue += 6) {
line.push_back(text("") //
| color(Color::HSV(hue, saturation, value)) //
| bgcolor(Color::HSV(hue, saturation, value + 8)));
}
array.push_back(hbox(std::move(line)));
}
true_color_display = vbox({
true_color_display,
separator(),
vbox(std::move(array)),
}) |
border;
}
return flexbox(
{
basic_color_display,
palette_256_color_display,
true_color_display,
},
FlexboxConfig().SetGap(1, 1));
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Gauges // Gauges
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
auto render_gauge = [&shift](int delta) { auto render_gauge = [&shift](int delta) {
float progress = (shift + delta) % 1000 / 1000.f; float progress = (shift + delta) % 500 / 500.f;
return hbox({ return hbox({
text(std::to_string(int(progress * 100)) + "% ") | text(std::to_string(int(progress * 100)) + "% ") |
size(WIDTH, EQUAL, 5), size(WIDTH, EQUAL, 5),
@ -331,25 +387,24 @@ int main(int argc, const char* argv[]) {
auto gauge_component = Renderer([render_gauge] { auto gauge_component = Renderer([render_gauge] {
return vbox({ return vbox({
render_gauge(0) | color(Color::Black), render_gauge(0) | color(Color::Black),
render_gauge(100) | color(Color::GrayDark), render_gauge(100) | color(Color::GrayDark),
render_gauge(50) | color(Color::GrayLight), render_gauge(50) | color(Color::GrayLight),
render_gauge(6894) | color(Color::White), render_gauge(6894) | color(Color::White),
separator(), separator(),
render_gauge(6841) | color(Color::Blue), render_gauge(6841) | color(Color::Blue),
render_gauge(9813) | color(Color::BlueLight), render_gauge(9813) | color(Color::BlueLight),
render_gauge(98765) | color(Color::Cyan), render_gauge(98765) | color(Color::Cyan),
render_gauge(98) | color(Color::CyanLight), render_gauge(98) | color(Color::CyanLight),
render_gauge(9846) | color(Color::Green), render_gauge(9846) | color(Color::Green),
render_gauge(1122) | color(Color::GreenLight), render_gauge(1122) | color(Color::GreenLight),
render_gauge(84) | color(Color::Magenta), render_gauge(84) | color(Color::Magenta),
render_gauge(645) | color(Color::MagentaLight), render_gauge(645) | color(Color::MagentaLight),
render_gauge(568) | color(Color::Red), render_gauge(568) | color(Color::Red),
render_gauge(2222) | color(Color::RedLight), render_gauge(2222) | color(Color::RedLight),
render_gauge(220) | color(Color::Yellow), render_gauge(220) | color(Color::Yellow),
render_gauge(348) | color(Color::YellowLight), render_gauge(348) | color(Color::YellowLight),
}) | });
border;
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -363,48 +418,36 @@ int main(int argc, const char* argv[]) {
}; };
auto paragraph_renderer_left = Renderer([&] { auto paragraph_renderer_left = Renderer([&] {
auto title_style = bold | bgcolor(Color::Blue) | color(Color::Black);
std::string str = std::string str =
"Lorem Ipsum is simply dummy text of the printing and typesetting " "Lorem Ipsum is simply dummy text of the printing and typesetting "
"industry. Lorem Ipsum has been the industry's standard dummy text " "industry. Lorem Ipsum has been the industry's standard dummy text "
"ever since the 1500s, when an unknown printer took a galley of type " "ever since the 1500s, when an unknown printer took a galley of type "
"and scrambled it to make a type specimen book."; "and scrambled it to make a type specimen book.";
return vbox({ return vbox({
// [ Left ] window(text("Align left:"), paragraphAlignLeft(str)),
text("Align left:") | title_style, window(text("Align center:"), paragraphAlignCenter(str)),
paragraphAlignLeft(str), window(text("Align right:"), paragraphAlignRight(str)),
// [ Center ] window(text("Align justify:"), paragraphAlignJustify(str)),
text("Align center:") | title_style, window(text("Side by side"), hbox({
paragraphAlignCenter(str), paragraph(str),
// [ Right ] separator(),
text("Align right:") | title_style, paragraph(str),
paragraphAlignRight(str), })),
// [ Justify] window(text("Elements with different size:"),
text("Align justify:") | title_style, flexbox({
paragraphAlignJustify(str), make_box(10, 5),
// [ Side by side ] make_box(9, 4),
text("Side by side:") | title_style, make_box(8, 4),
hbox({ make_box(6, 3),
paragraph(str), make_box(10, 5),
separator() | color(Color::Blue), make_box(9, 4),
paragraph(str), make_box(8, 4),
}), make_box(6, 3),
// [ Misc ] make_box(10, 5),
text("Elements with different size:") | title_style, make_box(9, 4),
flexbox({ make_box(8, 4),
make_box(10, 5), make_box(6, 3),
make_box(9, 4), })),
make_box(8, 4),
make_box(6, 3),
make_box(10, 5),
make_box(9, 4),
make_box(8, 4),
make_box(6, 3),
make_box(10, 5),
make_box(9, 4),
make_box(8, 4),
make_box(6, 3),
}),
}) | }) |
vscroll_indicator | yframe | flex; vscroll_indicator | yframe | flex;
}); });
@ -420,7 +463,7 @@ int main(int argc, const char* argv[]) {
&paragraph_renderer_split_position); &paragraph_renderer_split_position);
auto paragraph_renderer_group_renderer = auto paragraph_renderer_group_renderer =
Renderer(paragraph_renderer_group, Renderer(paragraph_renderer_group,
[&] { return paragraph_renderer_group->Render() | border; }); [&] { return paragraph_renderer_group->Render(); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Tabs // Tabs
@ -430,7 +473,8 @@ int main(int argc, const char* argv[]) {
std::vector<std::string> tab_entries = { std::vector<std::string> tab_entries = {
"htop", "color", "spinner", "gauge", "compiler", "paragraph", "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( auto tab_content = Container::Tab(
{ {
htop, htop,
@ -450,7 +494,7 @@ int main(int argc, const char* argv[]) {
auto main_renderer = Renderer(main_container, [&] { auto main_renderer = Renderer(main_container, [&] {
return vbox({ return vbox({
text("FTXUI Demo") | bold | hcenter, text("FTXUI Demo") | bold | hcenter,
tab_selection->Render() | hcenter, tab_selection->Render(),
tab_content->Render() | flex, 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 <iostream> // for basic_ostream::operator<<, operator<<, endl, basic_ostream, basic_ostream<>::__ostream_type, cout, ostream
#include <memory> // for shared_ptr, __shared_ptr_access #include <memory> // for allocator, shared_ptr, __shared_ptr_access
#include <string> // for to_string, allocator #include <string> // for char_traits, to_string, operator+, string, basic_string
#include "ftxui/component/captured_mouse.hpp" // for ftxui #include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for MenuEntry, Renderer, Vertical #include "ftxui/component/component.hpp" // for MenuEntry, Renderer, Vertical
#include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for MenuEntryOption #include "ftxui/component/component_options.hpp" // for MenuEntryOption
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive #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 #include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::Cyan, Color::Green, Color::Red, Color::Yellow
using namespace ftxui; using namespace ftxui;
// Define a special style for some menu entry. // Define a special style for some menu entry.
MenuEntryOption Colored(ftxui::Color c) { MenuEntryOption Colored(ftxui::Color c) {
MenuEntryOption special_style; MenuEntryOption option;
special_style.style_normal = Decorator(color(c)); option.transform = [c](EntryState state) {
special_style.style_focused = Decorator(color(c)) | inverted; state.label = (state.active? "> " : " ") + state.label;
special_style.style_selected = Decorator(color(c)) | bold; Element e = text(state.label) | color(c);
special_style.style_selected_focused = Decorator(color(c)) | inverted | bold; if (state.focused)
return special_style; e = e | inverted;
if (state.active)
e = e | bold;
return e;
};
return option;
} }
int main(int argc, const char* argv[]) { 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 <functional> // for function
#include <memory> // for shared_ptr, __shared_ptr_access, allocator #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 <vector> // for vector
#include "ftxui/component/captured_mouse.hpp" // for ftxui #include "ftxui/component/animation.hpp" // for ElasticOut, Linear
#include "ftxui/component/component.hpp" // for Menu, Horizontal, Renderer #include "ftxui/component/component.hpp" // for Menu, Horizontal, Renderer, Vertical
#include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for MenuOption #include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption, AnimatedColorOption, AnimatedColorsOption, UnderlineOption
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive #include "ftxui/component/mouse.hpp" // for ftxui
#include "ftxui/dom/elements.hpp" // for operator|, color, separator, Decorator, bgcolor, flex, Element, bold, hbox, border, dim #include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::BlueLight, Color::Red, Color::Yellow #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[]) { int main(int argc, const char* argv[]) {
using namespace ftxui;
auto screen = ScreenInteractive::TerminalOutput(); auto screen = ScreenInteractive::TerminalOutput();
std::vector<std::string> entries = { std::vector<std::string> entries = {
"Monkey", "Dog", "Cat", "Bird", "Elephant", "Monkey", "Dog", "Cat", "Bird", "Elephant", "Cat",
}; };
int menu_1_selected_ = 0; std::array<int, 12> selected = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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;
MenuOption option_1; auto vmenu_1_ = VMenu1(&entries, &selected[0]);
option_1.style_focused = bold | color(Color::Blue); auto vmenu_2_ = VMenu2(&entries, &selected[1]);
option_1.style_selected = color(Color::Blue); auto vmenu_3_ = VMenu3(&entries, &selected[2]);
option_1.style_selected_focused = bold | color(Color::Blue); auto vmenu_4_ = VMenu4(&entries, &selected[3]);
option_1.on_enter = screen.ExitLoopClosure(); auto vmenu_5_ = VMenu5(&entries, &selected[4]);
auto menu_1_ = Menu(&entries, &menu_1_selected_, &option_1); auto vmenu_6_ = VMenu6(&entries, &selected[5]);
auto vmenu_7_ = VMenu7(&entries, &selected[6]);
auto vmenu_8_ = VMenu8(&entries, &selected[7]);
MenuOption option_2; auto hmenu_1_ = HMenu1(&entries, &selected[8]);
option_2.style_focused = bold | color(Color::Blue); auto hmenu_2_ = HMenu2(&entries, &selected[9]);
option_2.style_selected = color(Color::Blue); auto hmenu_3_ = HMenu3(&entries, &selected[10]);
option_2.style_selected_focused = bold | color(Color::Blue); auto hmenu_4_ = HMenu4(&entries, &selected[11]);
option_2.on_enter = screen.ExitLoopClosure(); auto hmenu_5_ = HMenu5(&entries, &selected[12]);
auto menu_2_ = Menu(&entries, &menu_2_selected_, &option_2);
MenuOption option_3; auto container = Container::Vertical({
option_3.style_selected = color(Color::Blue); Container::Horizontal({
option_3.style_focused = bgcolor(Color::Blue); vmenu_1_,
option_3.style_selected_focused = bgcolor(Color::Blue); vmenu_2_,
option_3.on_enter = screen.ExitLoopClosure(); vmenu_3_,
auto menu_3_ = Menu(&entries, &menu_3_selected_, &option_3); vmenu_4_,
vmenu_5_,
MenuOption option_4; vmenu_6_,
option_4.style_selected = bgcolor(Color::Blue); vmenu_7_,
option_4.style_focused = bgcolor(Color::BlueLight); vmenu_8_,
option_4.style_selected_focused = bgcolor(Color::BlueLight); }),
option_4.on_enter = screen.ExitLoopClosure(); hmenu_1_,
auto menu_4_ = Menu(&entries, &menu_4_selected_, &option_4); hmenu_2_,
hmenu_3_,
MenuOption option_5; hmenu_4_,
option_5.style_normal = bgcolor(Color::Blue); hmenu_5_,
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_,
}); });
// clang-format off
auto renderer = Renderer(container, [&] { auto renderer = Renderer(container, [&] {
return return //
hbox({ hbox({
menu_1_->Render() | flex, separator(), vbox({
menu_2_->Render() | flex, separator(), hbox({
menu_3_->Render() | flex, separator(), vmenu_1_->Render(),
menu_4_->Render() | flex, separator(), separator(),
menu_5_->Render() | flex, separator(), vmenu_2_->Render(),
menu_6_->Render() | flex, separator(),
}) | border; 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); 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. // Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.

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 <utility> // for move
#include <vector> // for vector #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/elements.hpp" // for text, bgcolor, color, vbox, hbox, separator, operator|, Elements, Element, Fit, border
#include "ftxui/dom/node.hpp" // for Render #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 #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[]) { int main(int argc, const char* argv[]) {
// clang-format off // clang-format off
auto basic_color_display = auto basic_color_display =

View File

@ -1,12 +1,13 @@
#include <cmath>
#include <algorithm> #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. // Acquire the color information for the palette256.
std::vector<ColorInfo> info_gray; std::vector<ftxui::ColorInfo> info_gray;
std::vector<ColorInfo> info_color; std::vector<ftxui::ColorInfo> info_color;
for (int i = 16; i < 256; ++i) { 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) if (info.saturation == 0)
info_gray.push_back(info); info_gray.push_back(info);
else else
@ -16,10 +17,10 @@ std::vector<std::vector<ColorInfo>> ColorInfoSorted2D() {
// Sort info_color by hue. // Sort info_color by hue.
std::sort( std::sort(
info_color.begin(), info_color.end(), 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. // 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; info_columns[0] = info_gray;
for (size_t i = 0; i < info_color.size(); ++i) { for (size_t i = 0; i < info_color.size(); ++i) {
info_columns[1 + 7 * i / info_color.size()].push_back(info_color[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. // Minimize discontinuities for every columns.
for (auto& column : info_columns) { for (auto& column : info_columns) {
std::sort(column.begin(), column.end(), 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; return A.value < B.value;
}); });
for (int i = 0; i < int(column.size()) - 1; ++i) { for (int i = 0; i < int(column.size()) - 1; ++i) {

View File

@ -2,7 +2,8 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>FTXUI examples WebAssembly</title> <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> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@4.11.0/css/xterm.css"></link>
</head> </head>
<body> <body>
@ -56,10 +57,11 @@
stdout_buffer.push(code) stdout_buffer.push(code)
} }
} }
let stderr = code => console.log(code); const stderr = code => console.log(code);
var term = new Terminal(); const term = new Terminal();
term.open(document.querySelector('#terminal')); term.open(document.querySelector('#terminal'));
term.resize(140,43); term.resize(140,43);
term.loadAddon(new (WebglAddon.WebglAddon)());
const onBinary = e => { const onBinary = e => {
for(c of e) for(c of e)
stdin_buffer.push(c.charCodeAt(0)); stdin_buffer.push(c.charCodeAt(0));
@ -78,6 +80,7 @@
</script> </script>
<style> <style>
body { body {
background-color:#EEE; background-color:#EEE;
padding:20px; 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 <vector> // for vector
#include "ftxui/component/component_base.hpp" // for Component, Components #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/dom/elements.hpp" // for Element
#include "ftxui/util/ref.hpp" // for Ref, ConstStringRef, ConstStringListRef, StringRef #include "ftxui/util/ref.hpp" // for Ref, ConstStringRef, ConstStringListRef, StringRef
@ -19,7 +19,6 @@ struct Event;
struct InputOption; struct InputOption;
struct MenuOption; struct MenuOption;
struct RadioboxOption; struct RadioboxOption;
struct ToggleOption;
struct MenuEntryOption; struct MenuEntryOption;
template <class T, class... Args> template <class T, class... Args>
@ -46,11 +45,11 @@ Component Tab(Components children, int* selector);
Component Button(ConstStringRef label, Component Button(ConstStringRef label,
std::function<void()> on_click, std::function<void()> on_click,
Ref<ButtonOption> = {}); Ref<ButtonOption> = ButtonOption::Simple());
Component Checkbox(ConstStringRef label, Component Checkbox(ConstStringRef label,
bool* checked, bool* checked,
Ref<CheckboxOption> option = {}); Ref<CheckboxOption> option = CheckboxOption::Simple());
Component Input(StringRef content, Component Input(StringRef content,
ConstStringRef placeholder, ConstStringRef placeholder,
@ -58,8 +57,7 @@ Component Input(StringRef content,
Component Menu(ConstStringListRef entries, Component Menu(ConstStringListRef entries,
int* selected_, int* selected_,
Ref<MenuOption> = {}); Ref<MenuOption> = MenuOption::Vertical());
Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> = {}); Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> = {});
Component Dropdown(ConstStringListRef entries, int* selected); Component Dropdown(ConstStringListRef entries, int* selected);
@ -67,10 +65,7 @@ Component Dropdown(ConstStringListRef entries, int* selected);
Component Radiobox(ConstStringListRef entries, Component Radiobox(ConstStringListRef entries,
int* selected_, int* selected_,
Ref<RadioboxOption> option = {}); Ref<RadioboxOption> option = {});
Component Toggle(ConstStringListRef entries, int* selected);
Component Toggle(ConstStringListRef entries,
int* selected,
Ref<ToggleOption> option = {});
template <class T> // T = {int, float, long} template <class T> // T = {int, float, long}
Component Slider(ConstStringRef label, T* value, T min, T max, T increment); Component Slider(ConstStringRef label, T* value, T min, T max, T increment);

View File

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

View File

@ -1,58 +1,136 @@
#ifndef FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP #ifndef FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP
#define FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP #define FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP
#include <ftxui/dom/elements.hpp> #include <chrono> // for milliseconds
#include <ftxui/util/ref.hpp> #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 { 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 /// @ingroup component
struct MenuOption { struct AnimatedColorOption {
Decorator style_normal = nothing; ///< style. void Set(
Decorator style_focused = inverted; ///< Style when focused. Color inactive,
Decorator style_selected = bold; ///< Style when selected. Color active,
Decorator style_selected_focused = animation::Duration duration = std::chrono::milliseconds(250),
Decorator(inverted) | bold; ///< Style when selected and focused. animation::easing::Function function = animation::easing::QuadraticInOut);
/// Called when the selected entry changes. bool enabled = false;
std::function<void()> on_change = [] {}; Color inactive;
/// Called when the user presses enter. Color active;
std::function<void()> on_enter = [] {}; 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. /// @brief Option for the MenuEntry component.
/// @ingroup component /// @ingroup component
struct MenuEntryOption { struct MenuEntryOption {
Decorator style_normal = nothing; ///< style. std::function<Element(EntryState state)> transform;
Decorator style_focused = inverted; ///< Style when focused. AnimatedColorsOption animated_colors;
Decorator style_selected = bold; ///< Style when selected.
Decorator style_selected_focused =
Decorator(inverted) | bold; ///< Style when selected and focused.
}; };
/// @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 /// @ingroup component
struct ButtonOption { struct ButtonOption {
/// Whether to show a border around the button. // Standard constructors:
bool border = true; 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. /// @brief Option for the Checkbox component.
/// @ingroup component /// @ingroup component
struct CheckboxOption { struct CheckboxOption {
std::string style_checked = ""; ///< Prefix for a "checked" state. // Standard constructors:
std::string style_unchecked = ""; ///< Prefix for a "unchecked" state. static CheckboxOption Simple();
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;
// Observer:
/// Called when the user change the state. /// Called when the user change the state.
std::function<void()> on_change = []() {}; std::function<void()> on_change = [] {};
}; };
/// @brief Option for the Input component. /// @brief Option for the Input component.
@ -74,34 +152,15 @@ struct InputOption {
/// @brief Option for the Radiobox component. /// @brief Option for the Radiobox component.
/// @ingroup component /// @ingroup component
struct RadioboxOption { struct RadioboxOption {
std::string style_checked = ""; ///< Prefix for a "checked" state. // Standard constructors:
std::string style_unchecked = ""; ///< Prefix for a "unchecked" state. static RadioboxOption Simple();
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.
/// Called when the selected entry changes. // Style:
std::function<void()> on_change = []() {}; std::function<Element(EntryState)> transform;
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.
// Observers:
/// Called when the selected entry changes. /// Called when the selected entry changes.
std::function<void()> on_change = [] {}; std::function<void()> on_change = [] {};
/// Called when the user presses enter.
std::function<void()> on_enter = [] {};
Ref<int> focused_entry = 0; Ref<int> focused_entry = 0;
}; };

View File

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

View File

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

View File

@ -43,6 +43,14 @@ Element separatorEmpty();
Element separatorStyled(BorderStyle); Element separatorStyled(BorderStyle);
Element separator(Pixel); Element separator(Pixel);
Element separatorCharacter(std::string); 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 gauge(float ratio);
Element gaugeLeft(float ratio); Element gaugeLeft(float ratio);
Element gaugeRight(float ratio); Element gaugeRight(float ratio);

View File

@ -27,6 +27,7 @@ class Color {
Color(uint8_t red, uint8_t green, uint8_t blue); Color(uint8_t red, uint8_t green, uint8_t blue);
static Color RGB(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 HSV(uint8_t hue, uint8_t saturation, uint8_t value);
static Color Interpolate(float t, const Color& a, const Color& b);
//--------------------------- //---------------------------
// List of colors: // 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,66 +1,33 @@
#include <functional> // for function #include <functional> // for function
#include <memory> // for shared_ptr #include <memory> // for shared_ptr
#include <string> // for string
#include <utility> // for move #include <utility> // for move
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse #include "ftxui/component/animation.hpp" // for Animator, Params (ptr only)
#include "ftxui/component/component.hpp" // for Make, Button #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
#include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/component.hpp" // for Make, Button
#include "ftxui/component/component_options.hpp" // for ButtonOption #include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/event.hpp" // for Event, Event::Return #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/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
#include "ftxui/component/screen_interactive.hpp" // for Component #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/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 ftxui {
namespace { 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 DefaultTransform(EntryState params) {
Element Render() override { auto element = text(params.label) | border;
auto style = Focused() ? inverted : nothing; if (params.active)
auto my_border = option_->border ? border : nothing; element |= bold;
return text(*label_) | my_border | style | reflect(box_); 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 } // namespace
@ -90,7 +57,122 @@ class ButtonBase : public ComponentBase {
Component Button(ConstStringRef label, Component Button(ConstStringRef label,
std::function<void()> on_click, std::function<void()> on_click,
Ref<ButtonOption> option) { 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 } // namespace ftxui

View File

@ -18,32 +18,24 @@ namespace {
class CheckboxBase : public ComponentBase { class CheckboxBase : public ComponentBase {
public: public:
CheckboxBase(ConstStringRef label, bool* state, Ref<CheckboxOption> option) CheckboxBase(ConstStringRef label, bool* state, Ref<CheckboxOption> option)
: label_(label), state_(state), option_(std::move(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
}
private: private:
// Component implementation. // Component implementation.
Element Render() override { Element Render() override {
bool is_focused = Focused(); bool is_focused = Focused();
bool is_active = Active(); 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; auto focus_management = is_focused ? focus : is_active ? select : nothing;
return hbox({ auto state = EntryState{
text(*state_ ? option_->style_checked *label_,
: option_->style_unchecked), *state_,
text(*label_) | style | focus_management, is_active,
}) | is_focused || hovered_,
reflect(box_); };
auto element = (option_->transform
? option_->transform
: CheckboxOption::Simple().transform)(std::move(state));
return element | focus_management | reflect(box_);
} }
bool OnEvent(Event event) override { 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) Impl(ConstStringRef label, Component child, Ref<bool> show)
: label_(label), show_(std::move(show)) { : label_(label), show_(std::move(show)) {
CheckboxOption opt; CheckboxOption opt;
opt.style_checked = ""; opt.transform = [](EntryState s) {
opt.style_unchecked = ""; 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({ Add(Container::Vertical({
Checkbox(label_, show_.operator->(), opt), Checkbox(label_, show_.operator->(), opt),
Maybe(std::move(child), show_.operator->()), Maybe(std::move(child), show_.operator->()),

View File

@ -12,6 +12,10 @@
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive #include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
#include "ftxui/dom/elements.hpp" // for text, Element #include "ftxui/dom/elements.hpp" // for text, Element
namespace ftxui::animation {
class Params;
} // namespace ftxui::animation
namespace ftxui { namespace ftxui {
namespace { namespace {
@ -101,6 +105,15 @@ bool ComponentBase::OnEvent(Event event) {
return false; 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. /// @brief Return the currently Active child.
/// @return the currently Active child. /// @return the currently Active child.
/// @ingroup component /// @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) Impl(ConstStringListRef entries, int* selected)
: entries_(std::move(entries)), selected_(selected) { : entries_(std::move(entries)), selected_(selected) {
CheckboxOption option; CheckboxOption option;
option.style_checked = ""; option.transform = [](EntryState s) {
option.style_unchecked = ""; 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), checkbox_ = Checkbox(&title_, &show_, option),
radiobox_ = Radiobox(entries_, selected_); radiobox_ = Radiobox(entries_, selected_);

View File

@ -66,7 +66,7 @@ class InputBase : public ComponentBase {
bool hovered = hovered_; bool hovered = hovered_;
Decorator decorator = dim | main_decorator; Decorator decorator = dim | main_decorator;
if (is_focused) if (is_focused)
decorator = decorator | focus | inverted; decorator = decorator | focus;
if (hovered || is_focused) if (hovered || is_focused)
decorator = decorator | inverted; decorator = decorator | inverted;
return text(*placeholder_) | decorator | reflect(box_); 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 <functional> // for function
#include <memory> // for shared_ptr, allocator_traits<>::value_type #include <memory> // for allocator, shared_ptr, allocator_traits<>::value_type, swap
#include <string> // for operator+, string #include <string> // for char_traits, operator+, string, basic_string
#include <utility> // for move #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/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_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption #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::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp, Event::Return, Event::Tab, Event::TabReverse #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/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Released, Mouse::WheelDown, Mouse::WheelUp, Mouse::None
#include "ftxui/component/screen_interactive.hpp" // for Component #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/box.hpp" // for Box
#include "ftxui/screen/util.hpp" // for clamp #include "ftxui/screen/color.hpp" // for Color
#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef, ConstStringRef #include "ftxui/screen/util.hpp" // for clamp
#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef, ConstStringRef
namespace ftxui { 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. /// @brief A list of items. The user can navigate through them.
/// @ingroup component /// @ingroup component
class MenuBase : public ComponentBase { class MenuBase : public ComponentBase {
@ -26,26 +67,148 @@ class MenuBase : public ComponentBase {
MenuBase(ConstStringListRef entries, int* selected, Ref<MenuOption> option) MenuBase(ConstStringListRef entries, int* selected, Ref<MenuOption> option)
: entries_(entries), selected_(selected), option_(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 { Element Render() override {
Clamp(); Clamp();
UpdateAnimationTarget();
Elements elements; Elements elements;
bool is_menu_focused = Focused(); bool is_menu_focused = Focused();
if (option_->elements_prefix)
elements.push_back(option_->elements_prefix());
for (int i = 0; i < size(); ++i) { 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_focused = (focused_entry() == i) && is_menu_focused;
bool is_selected = (*selected_ == i); 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 auto focus_management = !is_selected ? nothing
: is_menu_focused ? focus : is_menu_focused ? focus
: select; : nothing;
auto icon = is_selected ? "> " : " "; EntryState state = {
elements.push_back(text(icon + entries_[i]) | style | focus_management | entries_[i],
reflect(boxes_[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 { bool OnEvent(Event event) override {
@ -59,9 +222,13 @@ class MenuBase : public ComponentBase {
if (Focused()) { if (Focused()) {
int old_selected = *selected_; int old_selected = *selected_;
if (event == Event::ArrowUp || event == Event::Character('k')) if (event == Event::ArrowUp || event == Event::Character('k'))
(*selected_)--; OnUp();
if (event == Event::ArrowDown || event == Event::Character('j')) 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) if (event == Event::PageUp)
(*selected_) -= box_.y_max - box_.y_min; (*selected_) -= box_.y_max - box_.y_min;
if (event == Event::PageDown) if (event == Event::PageDown)
@ -79,13 +246,13 @@ class MenuBase : public ComponentBase {
if (*selected_ != old_selected) { if (*selected_ != old_selected) {
focused_entry() = *selected_; focused_entry() = *selected_;
option_->on_change(); OnChange();
return true; return true;
} }
} }
if (event == Event::Return) { if (event == Event::Return) {
option_->on_enter(); OnEnter();
return true; return true;
} }
@ -114,7 +281,7 @@ class MenuBase : public ComponentBase {
event.mouse().motion == Mouse::Released) { event.mouse().motion == Mouse::Released) {
if (*selected_ != i) { if (*selected_ != i) {
*selected_ = i; *selected_ = i;
option_->on_change(); OnChange();
} }
return true; return true;
} }
@ -135,19 +302,115 @@ class MenuBase : public ComponentBase {
*selected_ = util::clamp(*selected_, 0, size() - 1); *selected_ = util::clamp(*selected_, 0, size() - 1);
if (*selected_ != old_selected) if (*selected_ != old_selected)
option_->on_change(); OnChange();
return true; return true;
} }
void Clamp() { void UpdateAnimationTarget() {
boxes_.resize(size()); UpdateColorTarget();
*selected_ = util::clamp(*selected_, 0, size() - 1); UpdateUnderlineTarget();
focused_entry() = util::clamp(focused_entry(), 0, size() - 1); }
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(); } bool Focusable() const final { return entries_.size(); }
int& focused_entry() { return option_->focused_entry(); } int& focused_entry() { return option_->focused_entry(); }
int size() const { return entries_.size(); } 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: protected:
ConstStringListRef entries_; ConstStringListRef entries_;
@ -156,6 +419,16 @@ class MenuBase : public ComponentBase {
std::vector<Box> boxes_; std::vector<Box> boxes_;
Box box_; 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. /// @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 selected The index of the currently selected element.
/// @param option Additional optional parameters. /// @param option Additional optional parameters.
/// @ingroup component /// @ingroup component
/// @see MenuBase
/// ///
/// ### Example /// ### Example
/// ///
@ -192,6 +464,41 @@ Component Menu(ConstStringListRef entries,
return Make<MenuBase>(entries, selected, std::move(option)); 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) { Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
class Impl : public ComponentBase { class Impl : public ComponentBase {
public: public:
@ -201,15 +508,56 @@ Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
private: private:
Element Render() override { Element Render() override {
bool focused = Focused(); bool focused = Focused();
auto style = UpdateAnimationTarget();
hovered_ ? (focused ? option_->style_selected_focused
: option_->style_selected) EntryState state = {
: (focused ? option_->style_focused : option_->style_normal); *label_,
hovered_,
focused,
false,
};
Element element =
(option_->transform ? option_->transform : DefaultOptionTransform) //
(std::move(state));
auto focus_management = focused ? select : nothing; auto focus_management = focused ? select : nothing;
auto label = focused ? "> " + (*label_) // return element | AnimatedColorStyle() | focus_management | reflect(box_);
: " " + (*label_);
return text(label) | style | 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 Focusable() const override { return true; }
bool OnEvent(Event event) override { bool OnEvent(Event event) override {
if (!event.is_mouse()) if (!event.is_mouse())
@ -228,10 +576,23 @@ Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
return false; return false;
} }
void OnAnimation(animation::Params& params) override {
animator_background_.OnAnimation(params);
animator_foreground_.OnAnimation(params);
}
ConstStringRef label_; ConstStringRef label_;
Ref<MenuEntryOption> option_; Ref<MenuEntryOption> option_;
Box box_; Box box_;
bool hovered_ = false; 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)); return Make<Impl>(std::move(label), std::move(option));

View File

@ -29,14 +29,6 @@ class RadioboxBase : public ComponentBase {
int* selected, int* selected,
Ref<RadioboxOption> option) Ref<RadioboxOption> option)
: entries_(entries), selected_(selected), option_(std::move(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_; hovered_ = *selected_;
} }
@ -48,19 +40,21 @@ class RadioboxBase : public ComponentBase {
for (int i = 0; i < size(); ++i) { for (int i = 0; i < size(); ++i) {
bool is_focused = (focused_entry() == i) && is_menu_focused; bool is_focused = (focused_entry() == i) && is_menu_focused;
bool is_selected = (hovered_ == i); 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 auto focus_management = !is_selected ? nothing
: is_menu_focused ? focus : is_menu_focused ? focus
: select; : 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 = elements.push_back(element | focus_management | reflect(boxes_[i]));
*selected_ == i ? option_->style_checked : option_->style_unchecked;
elements.push_back(hbox(text(symbol), text(entries_[i]) | style) |
focus_management | reflect(boxes_[i]));
} }
return vbox(std::move(elements)) | reflect(box_); return vbox(std::move(elements)) | reflect(box_);
} }

View File

@ -1,21 +1,23 @@
#include <stdio.h> // for fileno, stdin #include <stdio.h> // for fileno, stdin
#include <algorithm> // for copy, max, min #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 <csignal> // for signal, raise, SIGTSTP, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, SIGWINCH
#include <cstdlib> // for NULL #include <cstdlib> // for NULL
#include <functional> // for function #include <functional> // for function
#include <initializer_list> // for initializer_list #include <initializer_list> // for initializer_list
#include <iostream> // for cout, ostream, basic_ostream, operator<<, endl, flush #include <iostream> // for cout, ostream, basic_ostream, operator<<, endl, flush
#include <stack> // for stack #include <stack> // for stack
#include <thread> // for thread #include <thread> // for thread, sleep_for
#include <type_traits> // for decay_t #include <type_traits> // for decay_t
#include <utility> // for swap, move #include <utility> // for swap, move
#include <variant> // for visit #include <variant> // for visit
#include <vector> // for vector #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/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
#include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/event.hpp" // for Event #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/screen_interactive.hpp"
#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser #include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
#include "ftxui/dom/node.hpp" // for Node, Render #include "ftxui/dom/node.hpp" // for Node, Render
@ -45,6 +47,14 @@
namespace ftxui { namespace ftxui {
namespace animation {
void RequestAnimationFrame() {
auto* screen = ScreenInteractive::Active();
if (screen)
screen->RequestAnimationFrame();
}
} // namespace animation
namespace { namespace {
ScreenInteractive* g_active_screen = nullptr; ScreenInteractive* g_active_screen = nullptr;
@ -236,6 +246,13 @@ class CapturedMouseImpl : public CapturedMouseInterface {
std::function<void(void)> callback_; 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 } // namespace
ScreenInteractive::ScreenInteractive(int dimx, ScreenInteractive::ScreenInteractive(int dimx,
@ -276,6 +293,15 @@ void ScreenInteractive::PostEvent(Event event) {
Post(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() { CapturedMouse ScreenInteractive::CaptureMouse() {
if (mouse_captured) if (mouse_captured)
return nullptr; return nullptr;
@ -330,6 +356,11 @@ Closure ScreenInteractive::WithRestoredIO(Closure fn) {
}; };
} }
// static
ScreenInteractive* ScreenInteractive::Active() {
return g_active_screen;
}
void ScreenInteractive::Install() { void ScreenInteractive::Install() {
// After uninstalling the new configuration, flush it to the terminal to // After uninstalling the new configuration, flush it to the terminal to
// ensure it is fully applied: // ensure it is fully applied:
@ -432,54 +463,85 @@ void ScreenInteractive::Install() {
task_sender_ = task_receiver_->MakeSender(); task_sender_ = task_receiver_->MakeSender();
event_listener_ = event_listener_ =
std::thread(&EventListener, &quit_, task_receiver_->MakeSender()); std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
animation_listener_ =
std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
} }
void ScreenInteractive::Uninstall() { void ScreenInteractive::Uninstall() {
ExitLoopClosure()(); ExitLoopClosure()();
event_listener_.join(); event_listener_.join();
animation_listener_.join();
OnExit(0); OnExit(0);
} }
void ScreenInteractive::Main(Component component) { void ScreenInteractive::Main(Component component) {
previous_animation_time = animation::Clock::now();
auto draw = [&] {
Draw(component);
std::cout << ToString() << set_cursor_position;
Flush();
Clear();
};
draw();
while (!quit_) { while (!quit_) {
if (!task_receiver_->HasPending()) { if (!task_receiver_->HasPending())
Draw(component); draw();
std::cout << ToString() << set_cursor_position;
Flush();
Clear();
}
Task task; bool continue_event_loop = true;
if (!task_receiver_->Receive(&task)) while (continue_event_loop) {
break; continue_event_loop = false;
Task task;
if (!task_receiver_->Receive(&task))
break;
std::visit( std::visit(
[&](auto&& arg) { [&](auto&& arg) {
using T = std::decay_t<decltype(arg)>; using T = std::decay_t<decltype(arg)>;
// Handle Event. // Handle Event.
if constexpr (std::is_same_v<T, Event>) { if constexpr (std::is_same_v<T, Event>) {
if (arg.is_cursor_reporting()) { if (arg.is_cursor_reporting()) {
cursor_x_ = arg.cursor_x(); cursor_x_ = arg.cursor_x();
cursor_y_ = arg.cursor_y(); cursor_y_ = arg.cursor_y();
return;
}
if (arg.is_mouse()) {
arg.mouse().x -= cursor_x_;
arg.mouse().y -= cursor_y_;
}
arg.screen_ = this;
component->OnEvent(arg);
}
// Handle callback
if constexpr (std::is_same_v<T, Closure>) {
arg();
return; return;
} }
if (arg.is_mouse()) { // Handle Animation
arg.mouse().x -= cursor_x_; if constexpr (std::is_same_v<T, AnimationTask>) {
arg.mouse().y -= cursor_y_; 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);
} }
},
arg.screen_ = this; task);
component->OnEvent(arg); }
}
// Handle callback
if constexpr (std::is_same_v<T, Closure>)
arg();
},
task);
} }
} }

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

View File

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

View File

@ -1,10 +1,11 @@
#include <memory> // for make_shared #include <memory> // for make_shared, allocator
#include <string> // for string #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/node.hpp" // for Node
#include "ftxui/dom/requirement.hpp" // for Requirement #include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/screen/box.hpp" // for Box #include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/color.hpp" // for Color
#include "ftxui/screen/screen.hpp" // for Pixel, Screen #include "ftxui/screen/screen.hpp" // for Pixel, Screen
namespace ftxui { namespace ftxui {
@ -369,6 +370,142 @@ Element separator(Pixel pixel) {
return std::make_shared<SeparatorWithPixel>(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 } // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved. // 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); 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 { inline namespace literals {
Color operator""_rgb(unsigned long long int combined) { Color operator""_rgb(unsigned long long int combined) {