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