diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8b5955e..39ef3c7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,10 @@ current (development)
### Dom
- Feature: Add the dashed style for border and separator.
- Feature: Add colored borders.
+- Feature: Customize with gradient color effect. Add the following decorators:
+ - `colorgrad`
+ - `bgcolorgrad`
+- Improvement: Color::Interpolate() uses gamma correction.
###
- Breaking: Direction enum is renamed WidthOrHeight
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6f28c45..c52c339 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -72,6 +72,7 @@ add_library(dom
src/ftxui/dom/gridbox.cpp
src/ftxui/dom/hbox.cpp
src/ftxui/dom/inverted.cpp
+ src/ftxui/dom/linear_gradient.cpp
src/ftxui/dom/node.cpp
src/ftxui/dom/node_decorator.cpp
src/ftxui/dom/paragraph.cpp
diff --git a/cmake/ftxui_test.cmake b/cmake/ftxui_test.cmake
index 996e5c3..456fcb8 100644
--- a/cmake/ftxui_test.cmake
+++ b/cmake/ftxui_test.cmake
@@ -37,6 +37,7 @@ add_executable(ftxui-tests
src/ftxui/dom/gauge_test.cpp
src/ftxui/dom/gridbox_test.cpp
src/ftxui/dom/hbox_test.cpp
+ src/ftxui/dom/linear_gradient_test.cpp
src/ftxui/dom/scroll_indicator_test.cpp
src/ftxui/dom/separator_test.cpp
src/ftxui/dom/spinner_test.cpp
diff --git a/doc/mainpage.md b/doc/mainpage.md
index 40b9103..6ac8572 100644
--- a/doc/mainpage.md
+++ b/doc/mainpage.md
@@ -442,6 +442,36 @@ ftxui::Color::HSV(uint8_t hue, uint8_t saturation, uint8_t value);
@endhtmlonly
+## LinearGradient #{#dom-linear-gradient}
+
+FTXUI supports linear gradient. Either on the foreground or the background.
+
+```cpp
+Decorator color(const LinearGradient&);
+Decorator bgcolor(const LinearGradient&);
+```
+
+A `ftxui::LinearGradient` is defined by an angle in degree, and a list of color
+stops.
+```cpp
+auto gradient = LinearGradient()
+ .Angle(45)
+ .AddStop(0.0, Color::Red)
+ .AddStop(0.5, Color::Green)
+ .AddStop(1.0, Color::Blue);
+```
+
+You can also use simplified constructors:
+```cpp
+LinearGradient(Color::Red, Color::Blue);
+```
+```cpp
+LinearGradient(45, Color::Red, Color::Blue);
+```
+
+See [demo](https://arthursonzogni.github.io/FTXUI/examples/?file=component/linear_gradient_gallery).
+
+
## Style {#dom-style}
In addition to colored text and colored backgrounds. Many terminals support text
effects such as: `bold`, `dim`, `underlined`, `inverted`, `blink`.
@@ -456,6 +486,8 @@ Element strikethrough(Element);
Element blink(Element);
Decorator color(Color);
Decorator bgcolor(Color);
+Decorator colorgrad(LinearGradient);
+Decorator bgcolorgrad(LinearGradient);
```
[Example](https://arthursonzogni.github.io/FTXUI/examples_2dom_2style_gallery_8cpp-example.html)
@@ -560,8 +592,7 @@ Simple [example](https://github.com/ArthurSonzogni/FTXUI/blob/master/examples/do
Complex [example](https://github.com/ArthurSonzogni/FTXUI/blob/master/examples/component/canvas_animated.cpp):
-![ezgif com-gif-maker (3)](https://user-images.githubusercontent.com/4759106/147250538-783a8246-98e0-4a25-b032-3bd3710549d1.gif)
-
+![ezgif com-gif-maker (3)](https://user-images.githubusercontent.com/4759106/147250538-783a8246-98e0-4a25-b032-3bd3710549d1.gif)
# component {#module-component}
The `ftxui::component` module defines the logic that produces interactive
diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt
index 5b618d2..c080690 100644
--- a/examples/component/CMakeLists.txt
+++ b/examples/component/CMakeLists.txt
@@ -17,6 +17,7 @@ example(focus_cursor)
example(gallery)
example(homescreen)
example(input)
+example(linear_gradient_gallery)
example(maybe)
example(menu)
example(menu2)
diff --git a/examples/component/linear_gradient_gallery.cpp b/examples/component/linear_gradient_gallery.cpp
new file mode 100644
index 0000000..e0368c8
--- /dev/null
+++ b/examples/component/linear_gradient_gallery.cpp
@@ -0,0 +1,56 @@
+#include // for ComponentBase, Component
+#include // for operator|, Element, flex, bgcolor, text, vbox, center
+#include // for LinearGradient
+#include // for Color, Color::Blue, Color::Red
+#include // for __shared_ptr_access, shared_ptr
+#include // for allocator, operator+, char_traits, string, to_string
+
+#include "ftxui/component/captured_mouse.hpp" // for ftxui
+#include "ftxui/component/component.hpp" // for Slider, Renderer, Vertical
+#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
+
+int main(int argc, const char* argv[]) {
+ using namespace ftxui;
+ auto screen = ScreenInteractive::Fullscreen();
+
+ int angle = 180.f;
+ float start = 0.f;
+ float end = 1.f;
+
+ std::string slider_angle_text;
+ std::string slider_start_text;
+ std::string slider_end_text;
+
+ auto slider_angle = Slider(&slider_angle_text, &angle, 0, 360);
+ auto slider_start = Slider(&slider_start_text, &start, 0.f, 1.f);
+ auto slider_end = Slider(&slider_end_text, &end, 0.f, 1.f);
+
+ auto layout = Container::Vertical({
+ slider_angle,
+ slider_start,
+ slider_end,
+ });
+
+ auto renderer = Renderer(layout, [&] {
+ slider_angle_text = "angle = " + std::to_string(angle) + "°";
+ slider_start_text = "start = " + std::to_string(int(start * 100)) + "%";
+ slider_end_text = "end = " + std::to_string(int(end * 100)) + "%";
+
+ auto background = text("Gradient") | center |
+ bgcolor(LinearGradient()
+ .Angle(angle)
+ .Stop(Color::Blue, start)
+ .Stop(Color::Red, end));
+ return vbox({
+ background | flex,
+ layout->Render(),
+ }) |
+ flex;
+ });
+
+ screen.Loop(renderer);
+}
+
+// Copyright 2023 Arthur Sonzogni. All rights reserved.
+// Use of this source code is governed by the MIT license that can be found in
+// the LICENSE file.
diff --git a/examples/dom/CMakeLists.txt b/examples/dom/CMakeLists.txt
index 7923536..ad3285e 100644
--- a/examples/dom/CMakeLists.txt
+++ b/examples/dom/CMakeLists.txt
@@ -3,18 +3,19 @@ set(DIRECTORY_LIB dom)
example(border)
example(border_colored)
example(border_style)
+example(canvas)
example(color_gallery)
example(color_info_palette256)
example(color_truecolor_HSV)
example(color_truecolor_RGB)
example(dbox)
-example(canvas)
example(gauge)
example(gauge_direction)
example(graph)
example(gridbox)
example(hflow)
example(html_like)
+example(linear_gradient)
example(package_manager)
example(paragraph)
example(separator)
diff --git a/examples/dom/linear_gradient.cpp b/examples/dom/linear_gradient.cpp
new file mode 100644
index 0000000..affc370
--- /dev/null
+++ b/examples/dom/linear_gradient.cpp
@@ -0,0 +1,26 @@
+#include // for bgcolor, operator|, operator|=, text, center, Element
+#include // for LinearGradient::Stop, LinearGradient
+#include // for Full, Screen
+#include // for allocator, shared_ptr
+
+#include "ftxui/dom/node.hpp" // for Render
+#include "ftxui/screen/color.hpp" // for Color, Color::DeepPink1, Color::DeepSkyBlue1, Color::Yellow, ftxui
+
+int main(int argc, const char* argv[]) {
+ using namespace ftxui;
+ auto document = text("gradient") | center;
+
+ document |= bgcolor(LinearGradient()
+ .Angle(45)
+ .Stop(Color::DeepPink1)
+ .Stop(Color::DeepSkyBlue1));
+ auto screen = Screen::Create(Dimension::Full(), Dimension::Full());
+ Render(screen, document);
+ screen.Print();
+
+ return 0;
+}
+
+// Copyright 2023 Arthur Sonzogni. All rights reserved.
+// Use of this source code is governed by the MIT license that can be found in
+// the LICENSE file.
diff --git a/examples/dom/style_color.cpp b/examples/dom/style_color.cpp
index 6f42bd1..de4a293 100644
--- a/examples/dom/style_color.cpp
+++ b/examples/dom/style_color.cpp
@@ -1,59 +1,60 @@
-#include // for Full, Screen
-#include // for allocator
+#include // for LinearGradient
+#include // for Full, Screen
+#include // for allocator
-#include "ftxui/dom/elements.hpp" // for text, bgcolor, color, vbox, Fit, filler, hbox
+#include "ftxui/dom/elements.hpp" // for text, bgcolor, color, vbox, filler, Fit, hbox
#include "ftxui/dom/node.hpp" // for Render
-#include "ftxui/screen/box.hpp" // for 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
+#include "ftxui/screen/color.hpp" // for Color, operator""_rgb, Color::Black, Color::Blue, Color::BlueLight, Color::Cyan, Color::CyanLight, Color::DeepSkyBlue4, Color::Default, Color::GrayDark, Color::GrayLight, Color::Green, Color::GreenLight, Color::Magenta, Color::MagentaLight, Color::Red, Color::RedLight, Color::SkyBlue1, Color::White, Color::Yellow, Color::YellowLight, ftxui
int main(int argc, const char* argv[]) {
using namespace ftxui;
- // clang-format off
- auto document =
- hbox(
- vbox(
- color(Color::Default, text("Default")),
- color(Color::Black, text("Black")),
- color(Color::GrayDark, text("GrayDark")),
- color(Color::GrayLight, text("GrayLight")),
- color(Color::White, text("White")),
- color(Color::Blue, text("Blue")),
- color(Color::BlueLight, text("BlueLight")),
- color(Color::Cyan, text("Cyan")),
- color(Color::CyanLight, text("CyanLight")),
- color(Color::Green, text("Green")),
- color(Color::GreenLight, text("GreenLight")),
- color(Color::Magenta, text("Magenta")),
- color(Color::MagentaLight, text("MagentaLight")),
- color(Color::Red, text("Red")),
- color(Color::RedLight, text("RedLight")),
- color(Color::Yellow, text("Yellow")),
- color(Color::YellowLight, text("YellowLight")),
- color(0x66ff66_rgb, text("Phosphor"))
- ),
- vbox(
- bgcolor(Color::Default, text("Default")),
- bgcolor(Color::Black, text("Black")),
- bgcolor(Color::GrayDark, text("GrayDark")),
- bgcolor(Color::GrayLight, text("GrayLight")),
- bgcolor(Color::White, text("White")),
- bgcolor(Color::Blue, text("Blue")),
- bgcolor(Color::BlueLight, text("BlueLight")),
- bgcolor(Color::Cyan, text("Cyan")),
- bgcolor(Color::CyanLight, text("CyanLight")),
- bgcolor(Color::Green, text("Green")),
- bgcolor(Color::GreenLight, text("GreenLight")),
- bgcolor(Color::Magenta, text("Magenta")),
- bgcolor(Color::MagentaLight, text("MagentaLight")),
- bgcolor(Color::Red, text("Red")),
- bgcolor(Color::RedLight, text("RedLight")),
- bgcolor(Color::Yellow, text("Yellow")),
- bgcolor(Color::YellowLight, text("YellowLight")),
- bgcolor(0x66ff66_rgb, text("Phosphor"))
- ),
- filler()
- );
- // clang-format on
+ auto document = hbox({
+ vbox({
+ color(Color::Default, text("Default")),
+ color(Color::Black, text("Black")),
+ color(Color::GrayDark, text("GrayDark")),
+ color(Color::GrayLight, text("GrayLight")),
+ color(Color::White, text("White")),
+ color(Color::Blue, text("Blue")),
+ color(Color::BlueLight, text("BlueLight")),
+ color(Color::Cyan, text("Cyan")),
+ color(Color::CyanLight, text("CyanLight")),
+ color(Color::Green, text("Green")),
+ color(Color::GreenLight, text("GreenLight")),
+ color(Color::Magenta, text("Magenta")),
+ color(Color::MagentaLight, text("MagentaLight")),
+ color(Color::Red, text("Red")),
+ color(Color::RedLight, text("RedLight")),
+ color(Color::Yellow, text("Yellow")),
+ color(Color::YellowLight, text("YellowLight")),
+ color(0x66ff66_rgb, text("Phosphor")),
+ color(LinearGradient(Color::SkyBlue1, Color::DeepSkyBlue4),
+ text("Skyblue to DeepSkyBlue")),
+ }),
+ vbox({
+ bgcolor(Color::Default, text("Default")),
+ bgcolor(Color::Black, text("Black")),
+ bgcolor(Color::GrayDark, text("GrayDark")),
+ bgcolor(Color::GrayLight, text("GrayLight")),
+ bgcolor(Color::White, text("White")),
+ bgcolor(Color::Blue, text("Blue")),
+ bgcolor(Color::BlueLight, text("BlueLight")),
+ bgcolor(Color::Cyan, text("Cyan")),
+ bgcolor(Color::CyanLight, text("CyanLight")),
+ bgcolor(Color::Green, text("Green")),
+ bgcolor(Color::GreenLight, text("GreenLight")),
+ bgcolor(Color::Magenta, text("Magenta")),
+ bgcolor(Color::MagentaLight, text("MagentaLight")),
+ bgcolor(Color::Red, text("Red")),
+ bgcolor(Color::RedLight, text("RedLight")),
+ bgcolor(Color::Yellow, text("Yellow")),
+ bgcolor(Color::YellowLight, text("YellowLight")),
+ bgcolor(0x66ff66_rgb, text("Phosphor")),
+ bgcolor(LinearGradient(Color::SkyBlue1, Color::DeepSkyBlue4),
+ text("Skyblue to DeepSkyBlue")),
+ }),
+ filler(),
+ });
auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
Render(screen, document);
diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp
index 2b615a2..b414c76 100644
--- a/include/ftxui/dom/elements.hpp
+++ b/include/ftxui/dom/elements.hpp
@@ -7,6 +7,7 @@
#include "ftxui/dom/canvas.hpp"
#include "ftxui/dom/direction.hpp"
#include "ftxui/dom/flexbox_config.hpp"
+#include "ftxui/dom/linear_gradient.hpp"
#include "ftxui/dom/node.hpp"
#include "ftxui/screen/box.hpp"
#include "ftxui/screen/color.hpp"
@@ -99,8 +100,12 @@ Element blink(Element);
Element strikethrough(Element);
Decorator color(Color);
Decorator bgcolor(Color);
+Decorator color(const LinearGradient&);
+Decorator bgcolor(const LinearGradient&);
Element color(Color, Element);
Element bgcolor(Color, Element);
+Element color(const LinearGradient&, Element);
+Element bgcolor(const LinearGradient&, Element);
Decorator focusPosition(int x, int y);
Decorator focusPositionRelative(float x, float y);
Element automerge(Element child);
diff --git a/include/ftxui/dom/linear_gradient.hpp b/include/ftxui/dom/linear_gradient.hpp
new file mode 100644
index 0000000..3c6b529
--- /dev/null
+++ b/include/ftxui/dom/linear_gradient.hpp
@@ -0,0 +1,52 @@
+#ifndef FTXUI_DOM_LINEAR_GRADIENT_HPP
+#define FTXUI_DOM_LINEAR_GRADIENT_HPP
+
+#include
+#include
+
+#include "ftxui/screen/color.hpp" // for Colors
+
+namespace ftxui {
+
+/// @brief A class representing the settings for linear-gradient color effect.
+///
+/// Example:
+/// ```cpp
+/// LinearGradient()
+/// .Angle(45)
+/// .Stop(Color::Red, 0.0)
+/// .Stop(Color::Green, 0.5)
+/// .Stop(Color::Blue, 1.0);
+/// ```
+///
+/// There are also shorthand constructors:
+/// ```cpp
+/// LinearGradient(Color::Red, Color::Blue);
+/// LinearGradient(45, Color::Red, Color::Blue);
+/// ```
+struct LinearGradient {
+ float angle = 0.f;
+ struct Stop {
+ Color color = Color::Default;
+ std::optional position;
+ };
+ std::vector stops;
+
+ // Simple constructor
+ LinearGradient();
+ LinearGradient(Color begin, Color end);
+ LinearGradient(float angle, Color begin, Color end);
+
+ // Modifier using the builder pattern.
+ LinearGradient& Angle(float angle);
+ LinearGradient& Stop(Color color, float position);
+ LinearGradient& Stop(Color color);
+};
+
+} // namespace ftxui
+
+#endif // FTXUI_DOM_LINEAR_GRADIENT_HPP
+
+// Copyright 2023 Arthur Sonzogni. All rights reserved.
+// Use of this source code is governed by the MIT license that can be found in
+// the LICENSE file.
diff --git a/include/ftxui/screen/color.hpp b/include/ftxui/screen/color.hpp
index d7d789a..6adf1b3 100644
--- a/include/ftxui/screen/color.hpp
+++ b/include/ftxui/screen/color.hpp
@@ -2,7 +2,8 @@
#define FTXUI_SCREEN_COLOR_HPP
#include // for uint8_t
-#include // for wstring
+#include // for string
+#include // for vector
#ifdef RGB
// Workaround for wingdi.h (via Windows.h) defining macros that break things.
diff --git a/src/ftxui/component/button_test.cpp b/src/ftxui/component/button_test.cpp
index 8f7add6..44c44aa 100644
--- a/src/ftxui/component/button_test.cpp
+++ b/src/ftxui/component/button_test.cpp
@@ -124,10 +124,10 @@ TEST(ButtonTest, Animation) {
Screen screen(12, 3);
Render(screen, container->Render());
EXPECT_EQ(screen.ToString(),
- "\x1B[1m\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m \x1B[22m "
- " \x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;192;192;192m\x1B[48;2;0;0;"
+ "\x1B[1m\x1B[38;2;191;191;191m\x1B[48;2;0;0;0m \x1B[22m "
+ " \x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;191;191;191m\x1B[48;2;0;0;"
"0m btn1 \x1B[22m btn2 "
- "\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;192;192;192m\x1B[48;2;0;0;"
+ "\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;191;191;191m\x1B[48;2;0;0;"
"0m \x1B[22m \x1B[39m\x1B[49m");
}
selected = 1;
@@ -135,10 +135,10 @@ TEST(ButtonTest, Animation) {
Screen screen(12, 3);
Render(screen, container->Render());
EXPECT_EQ(screen.ToString(),
- "\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m \x1B[1m "
- "\x1B[22m\x1B[39m\x1B[49m\r\n\x1B[38;2;192;192;192m\x1B[48;2;0;0;"
+ "\x1B[38;2;191;191;191m\x1B[48;2;0;0;0m \x1B[1m "
+ "\x1B[22m\x1B[39m\x1B[49m\r\n\x1B[38;2;191;191;191m\x1B[48;2;0;0;"
"0m btn1 \x1B[1m btn2 "
- "\x1B[22m\x1B[39m\x1B[49m\r\n\x1B[38;2;192;192;192m\x1B[48;2;0;0;"
+ "\x1B[22m\x1B[39m\x1B[49m\r\n\x1B[38;2;191;191;191m\x1B[48;2;0;0;"
"0m \x1B[1m \x1B[22m\x1B[39m\x1B[49m");
}
animation::Params params(2s);
@@ -148,12 +148,12 @@ TEST(ButtonTest, Animation) {
Render(screen, container->Render());
EXPECT_EQ(
screen.ToString(),
- "\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m "
- "\x1B[1m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m "
- "\x1B[22m\x1B[39m\x1B[49m\r\n\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m "
- "btn1 \x1B[1m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m btn2 "
- "\x1B[22m\x1B[39m\x1B[49m\r\n\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m "
- " \x1B[1m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m "
+ "\x1B[38;2;191;191;191m\x1B[48;2;0;0;0m "
+ "\x1B[1m\x1B[38;2;254;254;254m\x1B[48;2;127;127;127m "
+ "\x1B[22m\x1B[39m\x1B[49m\r\n\x1B[38;2;191;191;191m\x1B[48;2;0;0;0m "
+ "btn1 \x1B[1m\x1B[38;2;254;254;254m\x1B[48;2;127;127;127m btn2 "
+ "\x1B[22m\x1B[39m\x1B[49m\r\n\x1B[38;2;191;191;191m\x1B[48;2;0;0;0m "
+ " \x1B[1m\x1B[38;2;254;254;254m\x1B[48;2;127;127;127m "
"\x1B[22m\x1B[39m\x1B[49m");
}
EXPECT_EQ(selected, 1);
@@ -164,12 +164,12 @@ TEST(ButtonTest, Animation) {
Render(screen, container->Render());
EXPECT_EQ(
screen.ToString(),
- "\x1B[1m\x1B[38;2;223;223;223m\x1B[48;2;64;64;64m "
- "\x1B[22m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m "
- "\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;223;223;223m\x1B[48;2;64;64;64m "
- "btn1 \x1B[22m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m btn2 "
- "\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;223;223;223m\x1B[48;2;64;64;64m "
- " \x1B[22m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m "
+ "\x1B[1m\x1B[38;2;226;226;226m\x1B[48;2;93;93;93m "
+ "\x1B[22m\x1B[38;2;254;254;254m\x1B[48;2;127;127;127m "
+ "\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;226;226;226m\x1B[48;2;93;93;93m "
+ "btn1 \x1B[22m\x1B[38;2;254;254;254m\x1B[48;2;127;127;127m btn2 "
+ "\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;226;226;226m\x1B[48;2;93;93;93m "
+ " \x1B[22m\x1B[38;2;254;254;254m\x1B[48;2;127;127;127m "
"\x1B[39m\x1B[49m");
}
container->OnAnimation(params);
@@ -178,12 +178,12 @@ TEST(ButtonTest, Animation) {
Render(screen, container->Render());
EXPECT_EQ(
screen.ToString(),
- "\x1B[1m\x1B[38;2;255;255;255m\x1B[48;2;128;128;128m "
- "\x1B[22m\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m "
- "\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;255;255;255m\x1B[48;2;128;128;"
- "128m btn1 \x1B[22m\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m btn2 "
- "\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;255;255;255m\x1B[48;2;128;128;"
- "128m \x1B[22m\x1B[38;2;192;192;192m\x1B[48;2;0;0;0m "
+ "\x1B[1m\x1B[38;2;254;254;254m\x1B[48;2;127;127;127m "
+ "\x1B[22m\x1B[38;2;191;191;191m\x1B[48;2;0;0;0m "
+ "\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;254;254;254m\x1B[48;2;127;127;"
+ "127m btn1 \x1B[22m\x1B[38;2;191;191;191m\x1B[48;2;0;0;0m btn2 "
+ "\x1B[39m\x1B[49m\r\n\x1B[1m\x1B[38;2;254;254;254m\x1B[48;2;127;127;"
+ "127m \x1B[22m\x1B[38;2;191;191;191m\x1B[48;2;0;0;0m "
"\x1B[39m\x1B[49m");
}
}
diff --git a/src/ftxui/dom/border.cpp b/src/ftxui/dom/border.cpp
index a90554b..6b2a63b 100644
--- a/src/ftxui/dom/border.cpp
+++ b/src/ftxui/dom/border.cpp
@@ -1,12 +1,13 @@
-#include // for max
-#include // for array
-#include // for allocator, make_shared, __shared_ptr_access
-#include
-#include // for basic_string, string
-#include // for move
-#include // for __alloc_traits<>::value_type
+#include // for max
+#include // for array
+#include // for Color
+#include // for allocator, make_shared, __shared_ptr_access
+#include // for optional, nullopt
+#include // for basic_string, string
+#include // for move
+#include // for __alloc_traits<>::value_type
-#include "ftxui/dom/elements.hpp" // for unpack, Element, Decorator, BorderStyle, ROUNDED, Elements, DOUBLE, EMPTY, HEAVY, LIGHT, border, borderDouble, borderEmpty, borderHeavy, borderLight, borderRounded, borderStyled, borderWith, window
+#include "ftxui/dom/elements.hpp" // for unpack, Element, Decorator, BorderStyle, ROUNDED, borderStyled, Elements, DASHED, DOUBLE, EMPTY, HEAVY, LIGHT, border, borderDashed, borderDouble, borderEmpty, borderHeavy, borderLight, borderRounded, borderWith, window
#include "ftxui/dom/node.hpp" // for Node, Elements
#include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/screen/box.hpp" // for Box
diff --git a/src/ftxui/dom/color_test.cpp b/src/ftxui/dom/color_test.cpp
index 0ff72c3..3ff2d18 100644
--- a/src/ftxui/dom/color_test.cpp
+++ b/src/ftxui/dom/color_test.cpp
@@ -1,9 +1,9 @@
-#include
+#include // for Test, EXPECT_EQ, Message, TestPartResult, TestInfo (ptr only), TEST
#include // for allocator
#include "ftxui/dom/elements.hpp" // for operator|, text, bgcolor, color, Element
#include "ftxui/dom/node.hpp" // for Render
-#include "ftxui/screen/color.hpp" // for Color, Color::Red
+#include "ftxui/screen/color.hpp" // for Color, Color::Red, Color::RedLight
#include "ftxui/screen/screen.hpp" // for Screen, Pixel
namespace ftxui {
diff --git a/src/ftxui/dom/linear_gradient.cpp b/src/ftxui/dom/linear_gradient.cpp
new file mode 100644
index 0000000..39046a3
--- /dev/null
+++ b/src/ftxui/dom/linear_gradient.cpp
@@ -0,0 +1,293 @@
+#include // for size_t
+#include // for max, min, sort, copy
+#include // for fmod, cos, sin
+#include // for LinearGradient::Stop, LinearGradient
+#include // for allocator_traits<>::value_type, make_shared
+#include // for optional, operator!=, operator<
+#include // for move
+#include // for vector
+
+#include "ftxui/dom/elements.hpp" // for Element, Decorator, bgcolor, color
+#include "ftxui/dom/node_decorator.hpp" // for NodeDecorator
+#include "ftxui/screen/box.hpp" // for Box
+#include "ftxui/screen/color.hpp" // for Color, Color::Default, Color::Blue
+#include "ftxui/screen/screen.hpp" // for Pixel, Screen
+
+namespace ftxui {
+namespace {
+
+struct LinearGradientNormalized {
+ float angle = 0.f;
+ std::vector colors;
+ std::vector positions; // Sorted.
+};
+
+// Convert a LinearGradient to a normalized version.
+LinearGradientNormalized Normalize(LinearGradient gradient) {
+ // Handle gradient of size 0.
+ if (gradient.stops.size() == 0) {
+ return LinearGradientNormalized{
+ 0.f, {Color::Default, Color::Default}, {0.f, 1.f}};
+ }
+
+ // Fill in the two extent, if not provided.
+ if (!gradient.stops.front().position) {
+ gradient.stops.front().position = 0;
+ }
+ if (!gradient.stops.back().position) {
+ gradient.stops.back().position = 1.f;
+ }
+
+ // Fill in the blank, by interpolating positions.
+ size_t last_checkpoint = 0;
+ for (size_t i = 1; i < gradient.stops.size(); ++i) {
+ if (!gradient.stops[i].position) {
+ continue;
+ }
+
+ if (i - last_checkpoint >= 2) {
+ const float min = gradient.stops[i].position.value();
+ const float max = gradient.stops[last_checkpoint].position.value();
+ for (size_t j = last_checkpoint + 1; j < i; ++j) {
+ gradient.stops[j].position =
+ min + (max - min) * (j - last_checkpoint) / (i - last_checkpoint);
+ }
+ }
+
+ last_checkpoint = i;
+ }
+
+ // Sort the stops by position.
+ std::sort(
+ gradient.stops.begin(), gradient.stops.end(),
+ [](const auto& a, const auto& b) { return a.position < b.position; });
+
+ // If we don't being with zero, add a stop at zero.
+ if (gradient.stops.front().position != 0) {
+ gradient.stops.insert(gradient.stops.begin(),
+ {gradient.stops.front().color, 0.f});
+ }
+ // If we don't end with one, add a stop at one.
+ if (gradient.stops.back().position != 1) {
+ gradient.stops.push_back({gradient.stops.back().color, 1.f});
+ }
+
+ // Normalize the angle.
+ LinearGradientNormalized normalized;
+ normalized.angle = std::fmod(std::fmod(gradient.angle, 360.f) + 360.f, 360.f);
+ for (auto& stop : gradient.stops) {
+ normalized.colors.push_back(stop.color);
+ normalized.positions.push_back(stop.position.value());
+ }
+ return normalized;
+}
+
+Color Interpolate(const LinearGradientNormalized& gradient, float t) {
+ // Find the right color in the gradient's stops.
+ size_t i = 1;
+ while (true) {
+ if (i > gradient.positions.size()) {
+ return Color::Interpolate(0.5f, gradient.colors.back(),
+ gradient.colors.back());
+ }
+ if (t <= gradient.positions[i]) {
+ break;
+ }
+ ++i;
+ }
+
+ const float t0 = gradient.positions[i - 1];
+ const float t1 = gradient.positions[i - 0];
+ const float tt = (t - t0) / (t1 - t0);
+
+ const Color& c0 = gradient.colors[i - 1];
+ const Color& c1 = gradient.colors[i - 0];
+ const Color& cc = Color::Interpolate(tt, c0, c1);
+
+ return cc;
+}
+
+class LinearGradientColor : public NodeDecorator {
+ public:
+ explicit LinearGradientColor(Element child,
+ const LinearGradient& gradient,
+ bool background_color)
+ : NodeDecorator(std::move(child)),
+ gradient_(Normalize(gradient)),
+ background_color_{background_color} {}
+
+ private:
+ void Render(Screen& screen) override {
+ const float degtorad = 0.01745329251f;
+ const float dx = std::cos(gradient_.angle * degtorad);
+ const float dy = std::sin(gradient_.angle * degtorad);
+
+ // Project every corner to get the extent of the gradient.
+ const float p1 = box_.x_min * dx + box_.y_min * dy;
+ const float p2 = box_.x_min * dx + box_.y_max * dy;
+ const float p3 = box_.x_max * dx + box_.y_min * dy;
+ const float p4 = box_.x_max * dx + box_.y_max * dy;
+ const float min = std::min({p1, p2, p3, p4});
+ const float max = std::max({p1, p2, p3, p4});
+
+ // Renormalize the projection to [0, 1] using the extent and projective
+ // geometry.
+ const float dX = dx / (max - min);
+ const float dY = dy / (max - min);
+ const float dZ = -min / (max - min);
+
+ // Project every pixel to get the color.
+ if (background_color_) {
+ for (int y = box_.y_min; y <= box_.y_max; ++y) {
+ for (int x = box_.x_min; x <= box_.x_max; ++x) {
+ const float t = x * dX + y * dY + dZ;
+ screen.PixelAt(x, y).background_color = Interpolate(gradient_, t);
+ }
+ }
+ } else {
+ for (int y = box_.y_min; y <= box_.y_max; ++y) {
+ for (int x = box_.x_min; x <= box_.x_max; ++x) {
+ const float t = x * dX + y * dY + dZ;
+ screen.PixelAt(x, y).foreground_color = Interpolate(gradient_, t);
+ }
+ }
+ }
+
+ NodeDecorator::Render(screen);
+ }
+
+ LinearGradientNormalized gradient_;
+ bool background_color_;
+};
+
+} // namespace
+
+/// @brief Build the "empty" gradient. This is often followed by calls to
+/// LinearGradient::Angle() and LinearGradient::Stop().
+/// Example:
+/// ```cpp
+/// auto gradient =
+/// LinearGradient()
+/// .Angle(45)
+/// .Stop(Color::Red, 0.0)
+/// .Stop(Color::Green, 0.5)
+/// .Stop(Color::Blue, 1.0);;
+/// ```
+/// @ingroup dom
+LinearGradient::LinearGradient() = default;
+
+/// @brief Build a gradient with two colors.
+/// @param begin The color at the beginning of the gradient.
+/// @param end The color at the end of the gradient.
+/// @ingroup dom
+LinearGradient::LinearGradient(Color begin, Color end) {
+ stops.push_back({begin, {}});
+ stops.push_back({end, {}});
+}
+
+/// @brief Build a gradient with two colors and an angle.
+/// @param a The angle of the gradient.
+/// @param begin The color at the beginning of the gradient.
+/// @param end The color at the end of the gradient.
+/// @ingroup dom
+LinearGradient::LinearGradient(float a, Color begin, Color end) {
+ angle = a;
+ stops.push_back({begin, {}});
+ stops.push_back({end, {}});
+}
+
+/// @brief Set the angle of the gradient.
+/// @param a The angle of the gradient.
+/// @return The gradient.
+/// @ingroup dom
+LinearGradient& LinearGradient::Angle(float a) {
+ angle = a;
+ return *this;
+}
+
+/// @brief Add a color stop to the gradient.
+/// @param c The color of the stop.
+/// @param p The position of the stop.
+/// @return The gradient.
+LinearGradient& LinearGradient::Stop(Color c, float p) {
+ stops.push_back({c, p});
+ return *this;
+}
+
+/// @brief Add a color stop to the gradient.
+/// @param c The color of the stop.
+/// @return The gradient.
+/// @ingroup dom
+/// @note The position of the stop is interpolated from nearby stops.
+LinearGradient& LinearGradient::Stop(Color c) {
+ stops.push_back({c, {}});
+ return *this;
+}
+
+/// @brief Set the foreground color of an element with linear-gradient effect.
+/// @param gradient The gradient effect to be applied on the output element.
+/// @param child The input element.
+/// @return The output element colored.
+/// @ingroup dom
+///
+/// ### Example
+///
+/// ```cpp
+/// color(LinearGradient{0, {Color::Red, Color::Blue}}, text("Hello"))
+/// ```
+Element color(const LinearGradient& gradient, Element child) {
+ return std::make_shared(std::move(child), gradient,
+ /*background_color*/ false);
+}
+
+/// @brief Set the background color of an element with linear-gradient effect.
+/// @param gradient The gradient effect to be applied on the output element.
+/// @param child The input element.
+/// @return The output element colored.
+/// @ingroup dom
+///
+/// ### Example
+///
+/// ```cpp
+/// bgcolor(LinearGradient{0, {Color::Red, Color::Blue}}, text("Hello"))
+/// ```
+Element bgcolor(const LinearGradient& gradient, Element child) {
+ return std::make_shared(std::move(child), gradient,
+ /*background_color*/ true);
+}
+
+/// @brief Decorate using a linear-gradient effect on the foreground color.
+/// @param gradient The gradient effect to be applied on the output element.
+/// @return The Decorator applying the color.
+/// @ingroup dom
+///
+/// ### Example
+///
+/// ```cpp
+/// text("Hello") | color(LinearGradient{0, {Color::Red, Color::Blue}})
+/// ```
+Decorator color(const LinearGradient& gradient) {
+ return
+ [gradient](Element child) { return color(gradient, std::move(child)); };
+}
+
+/// @brief Decorate using a linear-gradient effect on the background color.
+/// @param gradient The gradient effect to be applied on the output element.
+/// @return The Decorator applying the color.
+/// @ingroup dom
+///
+/// ### Example
+///
+/// ```cpp
+/// text("Hello") | color(LinearGradient{0, {Color::Red, Color::Blue}})
+/// ```
+Decorator bgcolor(const LinearGradient& gradient) {
+ return
+ [gradient](Element child) { return bgcolor(gradient, std::move(child)); };
+}
+
+} // namespace ftxui
+
+// Copyright 2023 Arthur Sonzogni. All rights reserved.
+// Use of this source code is governed by the MIT license that can be found in
+// the LICENSE file.
diff --git a/src/ftxui/dom/linear_gradient_test.cpp b/src/ftxui/dom/linear_gradient_test.cpp
new file mode 100644
index 0000000..4f3d102
--- /dev/null
+++ b/src/ftxui/dom/linear_gradient_test.cpp
@@ -0,0 +1,90 @@
+#include // for Test, EXPECT_EQ, Message, TestPartResult, TestInfo (ptr only), TEST
+#include // for LinearGradient::Stop, LinearGradient
+#include // for allocator
+
+#include "ftxui/dom/elements.hpp" // for operator|, text, bgcolor, color, Element
+#include "ftxui/dom/node.hpp" // for Render
+#include "ftxui/screen/color.hpp" // for Color, Color::Red, Color::RedLight
+#include "ftxui/screen/screen.hpp" // for Screen, Pixel
+
+namespace ftxui {
+
+TEST(ColorTest, API_default) {
+ LinearGradient gradient;
+ EXPECT_EQ(gradient.angle, 0);
+ EXPECT_EQ(gradient.stops.size(), 0);
+}
+
+TEST(ColorTest, API_builder) {
+ auto gradient = LinearGradient() //
+ .Angle(45)
+ .Stop(Color::Red)
+ .Stop(Color::RedLight, 0.5)
+ .Stop(Color::RedLight);
+ EXPECT_EQ(gradient.angle, 45);
+ EXPECT_EQ(gradient.stops.size(), 3);
+ EXPECT_EQ(gradient.stops[0].color, Color::Red);
+ EXPECT_EQ(gradient.stops[0].position, std::nullopt);
+ EXPECT_EQ(gradient.stops[1].color, Color::RedLight);
+ EXPECT_EQ(gradient.stops[1].position, 0.5);
+ EXPECT_EQ(gradient.stops[2].color, Color::RedLight);
+ EXPECT_EQ(gradient.stops[2].position, std::nullopt);
+}
+
+TEST(ColorTest, API_constructor_bicolor) {
+ auto gradient = LinearGradient(Color::Red, Color::RedLight);
+ EXPECT_EQ(gradient.angle, 0);
+ EXPECT_EQ(gradient.stops.size(), 2);
+ EXPECT_EQ(gradient.stops[0].color, Color::Red);
+ EXPECT_EQ(gradient.stops[0].position, std::nullopt);
+ EXPECT_EQ(gradient.stops[1].color, Color::RedLight);
+ EXPECT_EQ(gradient.stops[1].position, std::nullopt);
+}
+
+TEST(ColorTest, API_constructor_bicolor_angle) {
+ auto gradient = LinearGradient(45, Color::Red, Color::RedLight);
+ EXPECT_EQ(gradient.angle, 45);
+ EXPECT_EQ(gradient.stops.size(), 2);
+ EXPECT_EQ(gradient.stops[0].color, Color::Red);
+ EXPECT_EQ(gradient.stops[0].position, std::nullopt);
+ EXPECT_EQ(gradient.stops[1].color, Color::RedLight);
+ EXPECT_EQ(gradient.stops[1].position, std::nullopt);
+}
+
+TEST(ColorTest, GradientForeground) {
+ auto element =
+ text("text") | color(LinearGradient(Color::RedLight, Color::Red));
+ Screen screen(5, 1);
+ Render(screen, element);
+
+ Color gradient_begin = Color::Interpolate(0, Color::RedLight, Color::Red);
+ Color gradient_end = Color::Interpolate(1, Color::RedLight, Color::Red);
+
+ EXPECT_EQ(screen.PixelAt(0, 0).foreground_color, gradient_begin);
+ EXPECT_EQ(screen.PixelAt(0, 0).background_color, Color());
+
+ EXPECT_EQ(screen.PixelAt(4, 0).foreground_color, gradient_end);
+ EXPECT_EQ(screen.PixelAt(4, 0).background_color, Color());
+}
+
+TEST(ColorTest, GradientBackground) {
+ auto element =
+ text("text") | bgcolor(LinearGradient(Color::RedLight, Color::Red));
+ Screen screen(5, 1);
+ Render(screen, element);
+
+ Color gradient_begin = Color::Interpolate(0, Color::RedLight, Color::Red);
+ Color gradient_end = Color::Interpolate(1, Color::RedLight, Color::Red);
+
+ EXPECT_EQ(screen.PixelAt(0, 0).foreground_color, Color());
+ EXPECT_EQ(screen.PixelAt(0, 0).background_color, gradient_begin);
+
+ EXPECT_EQ(screen.PixelAt(4, 0).foreground_color, Color());
+ EXPECT_EQ(screen.PixelAt(4, 0).background_color, gradient_end);
+}
+
+} // namespace ftxui
+
+// Copyright 2023 Arthur Sonzogni. All rights reserved.
+// Use of this source code is governed by the MIT license that can be found in
+// the LICENSE file.
diff --git a/src/ftxui/dom/table.cpp b/src/ftxui/dom/table.cpp
index bdaf5fc..4de41ac 100644
--- a/src/ftxui/dom/table.cpp
+++ b/src/ftxui/dom/table.cpp
@@ -15,12 +15,12 @@ bool IsCell(int x, int y) {
// NOLINTNEXTLINE
static std::string charset[6][6] = {
- {"┌", "┐", "└", "┘", "─", "│"}, // LIGHT
- {"┏", "┓", "┗", "┛", "╍", "╏"}, // DASHED
- {"┏", "┓", "┗", "┛", "━", "┃"}, // HEAVY
- {"╔", "╗", "╚", "╝", "═", "║"}, // DOUBLE
- {"╭", "╮", "╰", "╯", "─", "│"}, // ROUNDED
- {" ", " ", " ", " ", " ", " "}, // EMPTY
+ {"┌", "┐", "└", "┘", "─", "│"}, // LIGHT
+ {"┏", "┓", "┗", "┛", "╍", "╏"}, // DASHED
+ {"┏", "┓", "┗", "┛", "━", "┃"}, // HEAVY
+ {"╔", "╗", "╚", "╝", "═", "║"}, // DOUBLE
+ {"╭", "╮", "╰", "╯", "─", "│"}, // ROUNDED
+ {" ", " ", " ", " ", " ", " "}, // EMPTY
};
int Wrap(int input, int modulo) {
diff --git a/src/ftxui/screen/color.cpp b/src/ftxui/screen/color.cpp
index 30caf2c..f52180d 100644
--- a/src/ftxui/screen/color.cpp
+++ b/src/ftxui/screen/color.cpp
@@ -1,6 +1,7 @@
#include "ftxui/screen/color.hpp"
-#include // for array
+#include // for array
+#include
#include // for literals
#include "ftxui/screen/color_info.hpp" // for GetColorInfo, ColorInfo
@@ -220,12 +221,12 @@ Color Color::Interpolate(float t, const Color& a, const Color& b) {
get_color(a, &red_a, &green_a, &blue_a);
get_color(b, &red_b, &green_b, &blue_b);
- return Color::RGB(static_cast(static_cast(red_a) * (1 - t) +
- static_cast(red_b) * t),
- static_cast(static_cast(green_a) * (1 - t) +
- static_cast(green_b) * t),
- static_cast(static_cast(blue_a) * (1 - t) +
- static_cast(blue_b) * t));
+ // Gamma correction:
+ // https://en.wikipedia.org/wiki/Gamma_correction
+ return Color::RGB(
+ pow(pow(red_a, 2.2f) * (1 - t) + pow(red_b, 2.2f) * t, 1 / 2.2f),
+ pow(pow(green_a, 2.2f) * (1 - t) + pow(green_b, 2.2f) * t, 1 / 2.2f),
+ pow(pow(blue_a, 2.2f) * (1 - t) + pow(blue_b, 2.2f) * t, 1 / 2.2f));
}
inline namespace literals {
diff --git a/src/ftxui/screen/color_test.cpp b/src/ftxui/screen/color_test.cpp
index 12dbb28..4dc65df 100644
--- a/src/ftxui/screen/color_test.cpp
+++ b/src/ftxui/screen/color_test.cpp
@@ -57,22 +57,22 @@ TEST(ColorTest, Interpolate) {
Color::RGB(1, 2, 3), //
Color::RGB(244, 244, 123)) //
.Print(false),
- "38;2;73;74;39");
+ "38;2;141;141;71");
EXPECT_EQ(Color::Interpolate(0.7f, //
Color::RGB(1, 2, 3), //
Color::RGB(244, 244, 123)) //
.Print(false),
- "38;2;171;171;87");
+ "38;2;207;207;104");
EXPECT_EQ(Color::Interpolate(0.7f, //
Color(Color::Red), //
Color::RGB(244, 244, 123)) //
.Print(false),
- "38;2;209;170;86");
+ "38;2;216;207;104");
EXPECT_EQ(Color::Interpolate(0.7f, //
Color::RGB(244, 244, 123), //
Color(Color::Plum1)) //
.Print(false),
- "38;2;251;195;215");
+ "38;2;251;198;225");
}
TEST(ColorTest, HSV) {