FTXUI 6.1.9
C++ functional terminal UI.
Loading...
Searching...
No Matches
src/ftxui/dom/linear_gradient.cpp
Go to the documentation of this file.
1// Copyright 2023 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
4#include <algorithm> // for max, min, sort, copy
5#include <cmath> // for fmod, cos, sin
6#include <cstddef> // for size_t
7#include <ftxui/dom/linear_gradient.hpp> // for LinearGradient::Stop, LinearGradient
8#include <memory> // for allocator_traits<>::value_type, make_shared
9#include <optional> // for optional, operator!=, operator<
10#include <utility> // for move
11#include <vector> // for vector
12
13#include "ftxui/dom/elements.hpp" // for Element, Decorator, bgcolor, color
14#include "ftxui/dom/node_decorator.hpp" // for NodeDecorator
15#include "ftxui/screen/box.hpp" // for Box
16#include "ftxui/screen/color.hpp" // for Color, Color::Default, Color::Blue
17#include "ftxui/screen/screen.hpp" // for Pixel, Screen
18
19namespace ftxui {
20namespace {
21
22struct LinearGradientNormalized {
23 float angle = 0.F;
24 std::vector<Color> colors;
25 std::vector<float> positions; // Sorted.
26};
27
28// Convert a LinearGradient to a normalized version.
29LinearGradientNormalized Normalize(LinearGradient gradient) {
30 // Handle gradient of size 0.
31 if (gradient.stops.empty()) {
32 return LinearGradientNormalized{
33 0.F,
35 {0.F, 1.F},
36 };
37 }
38
39 // Fill in the two extent, if not provided.
40 if (!gradient.stops.front().position) {
41 gradient.stops.front().position = 0.F;
42 }
43 if (!gradient.stops.back().position) {
44 gradient.stops.back().position = 1.F;
45 }
46
47 // Fill in the blank, by interpolating positions.
48 size_t last_checkpoint = 0;
49 for (size_t i = 1; i < gradient.stops.size(); ++i) {
50 if (!gradient.stops[i].position) {
51 continue;
52 }
53
54 if (i - last_checkpoint >= 2) {
55 const float min = gradient.stops[i].position.value(); // NOLINT
56 const float max =
57 gradient.stops[last_checkpoint].position.value(); // NOLINT
58 for (size_t j = last_checkpoint + 1; j < i; ++j) {
59 gradient.stops[j].position = min + (max - min) *
60 float(j - last_checkpoint) /
61 float(i - last_checkpoint);
62 }
63 }
64
65 last_checkpoint = i;
66 }
67
68 // Sort the stops by position.
69 std::sort(
70 gradient.stops.begin(), gradient.stops.end(),
71 [](const auto& a, const auto& b) { return a.position < b.position; });
72
73 // If we don't being with zero, add a stop at zero.
74 if (gradient.stops.front().position != 0) {
75 gradient.stops.insert(gradient.stops.begin(),
76 {gradient.stops.front().color, 0.F});
77 }
78 // If we don't end with one, add a stop at one.
79 if (gradient.stops.back().position != 1) {
80 gradient.stops.push_back({gradient.stops.back().color, 1.F});
81 }
82
83 // Normalize the angle.
84 LinearGradientNormalized normalized;
85 const float modulo = 360.F;
86 normalized.angle =
87 std::fmod(std::fmod(gradient.angle, modulo) + modulo, modulo);
88 for (auto& stop : gradient.stops) {
89 normalized.colors.push_back(stop.color);
90 // NOLINTNEXTLINE
91 normalized.positions.push_back(stop.position.value());
92 }
93 return normalized;
94}
95
96Color Interpolate(const LinearGradientNormalized& gradient, float t) {
97 // Find the right color in the gradient's stops.
98 size_t i = 1;
99 while (true) {
100 // Note that `t` might be slightly greater than 1.0 due to floating point
101 // precision. This is why we need to handle the case where `t` is greater
102 // than the last stop's position.
103 // See https://github.com/ArthurSonzogni/FTXUI/issues/998
104 if (i >= gradient.positions.size()) {
105 const float half = 0.5F;
106 return Color::Interpolate(half, gradient.colors.back(),
107 gradient.colors.back());
108 }
109 if (t <= gradient.positions[i]) {
110 break;
111 }
112 ++i;
113 }
114
115 const float t0 = gradient.positions[i - 1];
116 const float t1 = gradient.positions[i - 0];
117 const float tt = (t - t0) / (t1 - t0);
118
119 const Color& c0 = gradient.colors[i - 1];
120 const Color& c1 = gradient.colors[i - 0];
121 const Color& cc = Color::Interpolate(tt, c0, c1);
122
123 return cc;
124}
125
126class LinearGradientColor : public NodeDecorator {
127 public:
128 explicit LinearGradientColor(Element child,
129 const LinearGradient& gradient,
130 bool background_color)
131 : NodeDecorator(std::move(child)),
132 gradient_(Normalize(gradient)),
133 background_color_{background_color} {}
134
135 private:
136 void Render(Screen& screen) override {
137 const float degtorad = 0.01745329251F;
138 const float dx = std::cos(gradient_.angle * degtorad);
139 const float dy = std::sin(gradient_.angle * degtorad);
140
141 // Project every corner to get the extent of the gradient.
142 const float p1 = float(box_.x_min) * dx + float(box_.y_min) * dy;
143 const float p2 = float(box_.x_min) * dx + float(box_.y_max) * dy;
144 const float p3 = float(box_.x_max) * dx + float(box_.y_min) * dy;
145 const float p4 = float(box_.x_max) * dx + float(box_.y_max) * dy;
146 const float min = std::min({p1, p2, p3, p4});
147 const float max = std::max({p1, p2, p3, p4});
148
149 // Renormalize the projection to [0, 1] using the extent and projective
150 // geometry.
151 const float dX = dx / (max - min);
152 const float dY = dy / (max - min);
153 const float dZ = -min / (max - min);
154
155 // Project every pixel to get the color.
156 if (background_color_) {
157 for (int y = box_.y_min; y <= box_.y_max; ++y) {
158 for (int x = box_.x_min; x <= box_.x_max; ++x) {
159 const float t = float(x) * dX + float(y) * dY + dZ;
160 screen.PixelAt(x, y).background_color = Interpolate(gradient_, t);
161 }
162 }
163 } else {
164 for (int y = box_.y_min; y <= box_.y_max; ++y) {
165 for (int x = box_.x_min; x <= box_.x_max; ++x) {
166 const float t = float(x) * dX + float(y) * dY + dZ;
167 screen.PixelAt(x, y).foreground_color = Interpolate(gradient_, t);
168 }
169 }
170 }
171
172 NodeDecorator::Render(screen);
173 }
174
175 LinearGradientNormalized gradient_;
176 bool background_color_;
177};
178
179} // namespace
180
181/// @brief Build the "empty" gradient. This is often followed by calls to
182/// LinearGradient::Angle() and LinearGradient::Stop().
183/// Example:
184/// ```cpp
185/// auto gradient =
186/// LinearGradient()
187/// .Angle(45)
188/// .Stop(Color::Red, 0.0)
189/// .Stop(Color::Green, 0.5)
190/// .Stop(Color::Blue, 1.0);;
191/// ```
193
194/// @brief Build a gradient with two colors.
195/// @param begin The color at the beginning of the gradient.
196/// @param end The color at the end of the gradient.
198 : LinearGradient(0, begin, end) {}
199
200/// @brief Build a gradient with two colors and an angle.
201/// @param a The angle of the gradient.
202/// @param begin The color at the beginning of the gradient.
203/// @param end The color at the end of the gradient.
205 stops.push_back({begin, {}});
206 stops.push_back({end, {}});
207}
208
209/// @brief Set the angle of the gradient.
210/// @param a The angle of the gradient.
211/// @return The gradient.
213 angle = a;
214 return *this;
215}
216
217/// @brief Add a color stop to the gradient.
218/// @param c The color of the stop.
219/// @param p The position of the stop.
221 stops.push_back({c, p});
222 return *this;
223}
224
225/// @brief Add a color stop to the gradient.
226/// @param c The color of the stop.
227/// @return The gradient.
228/// @note The position of the stop is interpolated from nearby stops.
230 stops.push_back({c, {}});
231 return *this;
232}
233
234/// @brief Set the foreground color of an element with linear-gradient effect.
235/// @param gradient The gradient effect to be applied on the output element.
236/// @param child The input element.
237/// @return The output element colored.
238/// @ingroup dom
239///
240/// ### Example
241///
242/// ```cpp
243/// color(LinearGradient{0, {Color::Red, Color::Blue}}, text("Hello"))
244/// ```
245Element color(const LinearGradient& gradient, Element child) {
246 return std::make_shared<LinearGradientColor>(std::move(child), gradient,
247 /*background_color*/ false);
248}
249
250/// @brief Set the background color of an element with linear-gradient effect.
251/// @param gradient The gradient effect to be applied on the output element.
252/// @param child The input element.
253/// @return The output element colored.
254/// @ingroup dom
255///
256/// ### Example
257///
258/// ```cpp
259/// bgcolor(LinearGradient{0, {Color::Red, Color::Blue}}, text("Hello"))
260/// ```
261Element bgcolor(const LinearGradient& gradient, Element child) {
262 return std::make_shared<LinearGradientColor>(std::move(child), gradient,
263 /*background_color*/ true);
264}
265
266/// @brief Decorate using a linear-gradient effect on the foreground color.
267/// @param gradient The gradient effect to be applied on the output element.
268/// @return The Decorator applying the color.
269/// @ingroup dom
270///
271/// ### Example
272///
273/// ```cpp
274/// text("Hello") | color(LinearGradient{0, {Color::Red, Color::Blue}})
275/// ```
277 return
278 [gradient](Element child) { return color(gradient, std::move(child)); };
279}
280
281/// @brief Decorate using a linear-gradient effect on the background color.
282/// @param gradient The gradient effect to be applied on the output element.
283/// @return The Decorator applying the color.
284/// @ingroup dom
285///
286/// ### Example
287///
288/// ```cpp
289/// text("Hello") | color(LinearGradient{0, {Color::Red, Color::Blue}})
290/// ```
292 return
293 [gradient](Element child) { return bgcolor(gradient, std::move(child)); };
294}
295
296} // namespace ftxui
LinearGradient & Stop(Color color, float position)
Add a color stop to the gradient.
LinearGradient & Angle(float angle)
Set the angle of the gradient.
LinearGradient()
Build the "empty" gradient. This is often followed by calls to LinearGradient::Angle() and LinearGrad...
std::vector< Stop > stops
friend void Render(Screen &screen, Node *node, Selection &selection)
Definition node.cpp:96
Decorator bgcolor(Color)
Decorate using a background color.
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
Definition node.cpp:84
Decorator color(Color)
Decorate using a foreground color.
A class representing the settings for linear-gradient color effect.
static Color Interpolate(float t, const Color &a, const Color &b)
Color is a class that represents a color in the terminal user interface.
Definition color.hpp:22
Color
Color is an enumeration that represents the color support of the terminal.
Definition terminal.hpp:23
The FTXUI ftxui:: namespace.
Definition animation.hpp:10
std::function< Element(Element)> Decorator
Definition elements.hpp:24
std::shared_ptr< Node > Element
Definition elements.hpp:22
std::vector< Color > colors
std::vector< float > positions