FTXUI 6.1.9
C++ functional terminal UI.
载入中...
搜索中...
未找到
src/ftxui/dom/linear_gradient.cpp
浏览该文件的文档.
1// 版权所有 2023 Arthur Sonzogni。保留所有权利。
2// 本源代码的使用受 MIT 许可证的约束,该许可证可在 LICENSE 文件中找到。
3#include <algorithm> // for max, min, sort, copy
4#include <cmath> // for fmod, cos, sin
5#include <cstddef> // for size_t
6#include <ftxui/dom/linear_gradient.hpp> // for LinearGradient::Stop, LinearGradient
7#include <memory> // for allocator_traits<>::value_type, make_shared
8#include <optional> // for optional, operator!=, operator<
9#include <utility> // for move
10#include <vector> // for vector
11
12#include "ftxui/dom/elements.hpp" // for Element, Decorator, bgcolor, color
13#include "ftxui/dom/node_decorator.hpp" // for NodeDecorator
14#include "ftxui/screen/box.hpp" // for Box
15#include "ftxui/screen/color.hpp" // for Color, Color::Default, Color::Blue
16#include "ftxui/screen/screen.hpp" // for Pixel, Screen
17
18namespace ftxui {
19namespace {
20
21struct LinearGradientNormalized {
22 float angle = 0.F;
23 std::vector<Color> colors;
24 std::vector<float> positions; // Sorted.
25};
26
27// 将 LinearGradient 转换为规范化版本。
28LinearGradientNormalized Normalize(LinearGradient gradient) {
29 // 处理大小为 0 的渐变。
30 if (gradient.stops.empty()) {
31 return LinearGradientNormalized{
32 0.F,
34 {0.F, 1.F},
35 };
36 }
37
38 // 如果未提供,则填充两个范围。
39 if (!gradient.stops.front().position) {
40 gradient.stops.front().position = 0.F;
41 }
42 if (!gradient.stops.back().position) {
43 gradient.stops.back().position = 1.F;
44 }
45
46 // 通过插值位置填充空白。
47 size_t last_checkpoint = 0;
48 for (size_t i = 1; i < gradient.stops.size(); ++i) {
49 if (!gradient.stops[i].position) {
50 continue;
51 }
52
53 if (i - last_checkpoint >= 2) {
54 const float min = gradient.stops[i].position.value(); // NOLINT
55 const float max =
56 gradient.stops[last_checkpoint].position.value(); // NOLINT
57 for (size_t j = last_checkpoint + 1; j < i; ++j) {
58 gradient.stops[j].position = min + (max - min) *
59 float(j - last_checkpoint) /
60 float(i - last_checkpoint);
61 }
62 }
63
64 last_checkpoint = i;
65 }
66
67 // 按位置对停止点进行排序。
68 std::sort(
69 gradient.stops.begin(), gradient.stops.end(),
70 [](const auto& a, const auto& b) { return a.position < b.position; });
71
72 // 如果我们不是从零开始,则在零处添加一个停止点。
73 if (gradient.stops.front().position != 0) {
74 gradient.stops.insert(gradient.stops.begin(),
75 {gradient.stops.front().color, 0.F});
76 }
77 // 如果我们不是以一结束,则在一处添加一个停止点。
78 if (gradient.stops.back().position != 1) {
79 gradient.stops.push_back({gradient.stops.back().color, 1.F});
80 }
81
82 // 规范化角度。
83 LinearGradientNormalized normalized;
84 const float modulo = 360.F;
85 normalized.angle =
86 std::fmod(std::fmod(gradient.angle, modulo) + modulo, modulo);
87 for (auto& stop : gradient.stops) {
88 normalized.colors.push_back(stop.color);
89 // NOLINTNEXTLINE
90 normalized.positions.push_back(stop.position.value());
91 }
92 return normalized;
93}
94
95Color Interpolate(const LinearGradientNormalized& gradient, float t) {
96 // 在渐变停止点中找到正确的颜色。
97 size_t i = 1;
98 while (true) {
99 // 请注意,由于浮点精度,`t` 可能略大于 1.0。
100 // 这就是为什么我们需要处理 `t` 大于最后一个停止点位置的情况。
101 // 参见 https://github.com/ArthurSonzogni/FTXUI/issues/998 if (i >= gradient.positions.size()) {
102 const float half = 0.5F;
103 return Color::Interpolate(half, gradient.colors.back(),
104 gradient.colors.back());
105 }
106 if (t <= gradient.positions[i]) {
107 break;
108 }
109 ++i;
110 }
111
112 const float t0 = gradient.positions[i - 1];
113 const float t1 = gradient.positions[i - 0];
114 const float tt = (t - t0) / (t1 - t0);
115
116 const Color& c0 = gradient.colors[i - 1];
117 const Color& c1 = gradient.colors[i - 0];
118 const Color& cc = Color::Interpolate(tt, c0, c1);
119
120 return cc;
121}
122
123class LinearGradientColor : public NodeDecorator {
124 public:
125 explicit LinearGradientColor(Element child,
126 const LinearGradient& gradient,
127 bool background_color)
128 : NodeDecorator(std::move(child)),
129 gradient_(Normalize(gradient)),
130 background_color_{background_color} {}
131
132 private:
133 void Render(Screen& screen) override {
134 const float degtorad = 0.01745329251F;
135 const float dx = std::cos(gradient_.angle * degtorad);
136 const float dy = std::sin(gradient_.angle * degtorad);
137
138 // 投影每个角以获取渐变的范围。
139 const float p1 = float(box_.x_min) * dx + float(box_.y_min) * dy;
140 const float p2 = float(box_.x_min) * dx + float(box_.y_max) * dy;
141 const float p3 = float(box_.x_max) * dx + float(box_.y_min) * dy;
142 const float p4 = float(box_.x_max) * dx + float(box_.y_max) * dy;
143 const float min = std::min({p1, p2, p3, p4});
144 const float max = std::max({p1, p2, p3, p4});
145
146 // 使用范围和投影几何将投影重新规范化为 [0, 1]。 const float dX = dx / (max - min);
147 const float dY = dy / (max - min);
148 const float dZ = -min / (max - min);
149
150 // 投影每个像素以获取颜色。
151 if (background_color_) {
152 for (int y = box_.y_min; y <= box_.y_max; ++y) {
153 for (int x = box_.x_min; x <= box_.x_max; ++x) {
154 const float t = float(x) * dX + float(y) * dY + dZ;
155 screen.PixelAt(x, y).background_color = Interpolate(gradient_, t);
156 }
157 }
158 } else {
159 for (int y = box_.y_min; y <= box_.y_max; ++y) {
160 for (int x = box_.x_min; x <= box_.x_max; ++x) {
161 const float t = float(x) * dX + float(y) * dY + dZ;
162 screen.PixelAt(x, y).foreground_color = Interpolate(gradient_, t);
163 }
164 }
165 }
166
167 NodeDecorator::Render(screen);
168 }
169
170 LinearGradientNormalized gradient_;
171 bool background_color_;
172};
173
174} // namespace
175
176/// @brief 构建“空”渐变。这通常后跟对 LinearGradient::Angle() 和 LinearGradient::Stop() 的调用。
177/// 示例:
178/// ```cpp
179/// auto gradient =
180/// LinearGradient()
181/// .Angle(45)
182/// .Stop(Color::Red, 0.0)
183/// .Stop(Color::Green, 0.5)
184/// .Stop(Color::Blue, 1.0);;
185/// ```
186LinearGradient::LinearGradient() = default;
187
188/// @brief 构建一个包含两种颜色的渐变。
189/// @param begin 渐变起始颜色。
190/// @param end 渐变结束颜色。
191LinearGradient::LinearGradient(Color begin, Color end)
192 : LinearGradient(0, begin, end) {}
193
194/// @brief 构建一个包含两种颜色和一个角度的渐变。
195/// @param a 渐变角度。
196/// @param begin 渐变起始颜色。
197/// @param end 渐变结束颜色。
198LinearGradient::LinearGradient(float a, Color begin, Color end) : angle(a) {
199 stops.push_back({begin, {}});
200 stops.push_back({end, {}});
201}
202
203/// @brief 设置渐变角度。
204/// @param a 渐变角度。
205/// @return 渐变。
206LinearGradient& LinearGradient::Angle(float a) {
207 angle = a;
208 return *this;
209}
210
211/// @brief 为渐变添加一个颜色停止点。
212/// @param c 停止点颜色。
213/// @param p 停止点位置。
215 stops.push_back({c, p});
216 return *this;
217}
218
219/// @brief 为渐变添加一个颜色停止点。
220/// @param c 停止点颜色。
221/// @return 渐变。
222/// @note 停止点位置从附近的停止点插值。
223LinearGradient& LinearGradient::Stop(Color c) {
224 stops.push_back({c, {}});
225 return *this;
226}
227
228/// @brief 使用线性渐变效果设置元素的前景色。
229/// @param gradient 应用于输出元素的渐变效果。
230/// @param child 输入元素。
231/// @return 已着色的输出元素。
232/// @ingroup dom
233///
234/// ### 示例
235///
236/// ```cpp
237/// color(LinearGradient{0, {Color::Red, Color::Blue}}, text("Hello"))
238/// ```
239Element color(const LinearGradient& gradient, Element child) {
240 return std::make_shared<LinearGradientColor>(std::move(child), gradient,
241 /*background_color*/ false);
242}
243
244/// @brief 使用线性渐变效果设置元素的背景色。
245/// @param gradient 应用于输出元素的渐变效果。
246/// @param child 输入元素。
247/// @return 已着色的输出元素。
248/// @ingroup dom
249///
250/// ### 示例
251///
252/// ```cpp
253/// bgcolor(LinearGradient{0, {Color::Red, Color::Blue}}, text("Hello"))
254/// ```
255Element bgcolor(const LinearGradient& gradient, Element child) {
256 return std::make_shared<LinearGradientColor>(std::move(child), gradient,
257 /*background_color*/ true);
258}
259
260/// @brief 使用线性渐变效果装饰前景色。
261/// @param gradient 应用于输出元素的渐变效果。
262/// @return 应用颜色的装饰器。
263/// @ingroup dom
264///
265/// ### 示例
266///
267/// ```cpp
268/// text("Hello") | color(LinearGradient{0, {Color::Red, Color::Blue}})
269/// ```
270Decorator color(const LinearGradient& gradient) {
271 return
272 [gradient](Element child) { return color(gradient, std::move(child)); };
273}
274
275/// @brief 使用线性渐变效果装饰背景色。
276/// @param gradient 应用于输出元素的渐变效果。
277/// @return 应用颜色的装饰器。
278/// @ingroup dom
279///
280/// ### 示例
281///
282/// ```cpp
283/// text("Hello") | color(LinearGradient{0, {Color::Red, Color::Blue}})
284/// ```
285Decorator bgcolor(const LinearGradient& gradient) {
286 return
287 [gradient](Element child) { return bgcolor(gradient, std::move(child)); };
288}
289
290} // namespace ftxui
friend void Render(Screen &screen, Node *node, Selection &selection)
Element color(const LinearGradient &gradient, Element child)
使用线性渐变效果设置元素的前景色。
Element bgcolor(const LinearGradient &gradient, Element child)
使用线性渐变效果设置元素的背景色。
一个表示线性渐变颜色效果设置的类。
static Color Interpolate(float t, const Color &a, const Color &b)
Color 是一个表示终端用户界面中颜色的类。
Color
Color 是一个表示终端颜色支持的枚举。
#include "ftxui/component/component_base.hpp" // 用于 ComponentBase
std::shared_ptr< Node > Element
std::vector< Color > colors
std::vector< float > positions