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// 此原始碼的使用受 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
102 if (i >= gradient.positions.size()) {
103 const float half = 0.5F;
104 return Color::Interpolate(half, gradient.colors.back(),
105 gradient.colors.back());
106 }
107 if (t <= gradient.positions[i]) {
108 break;
109 }
110 ++i;
111 }
112
113 const float t0 = gradient.positions[i - 1];
114 const float t1 = gradient.positions[i - 0];
115 const float tt = (t - t0) / (t1 - t0);
116
117 const Color& c0 = gradient.colors[i - 1];
118 const Color& c1 = gradient.colors[i - 0];
119 const Color& cc = Color::Interpolate(tt, c0, c1);
120
121 return cc;
122}
123
124class LinearGradientColor : public NodeDecorator {
125 public:
126 explicit LinearGradientColor(Element child,
127 const LinearGradient& gradient,
128 bool background_color)
129 : NodeDecorator(std::move(child)),
130 gradient_(Normalize(gradient)),
131 background_color_{background_color} {}
132
133 private:
134 void Render(Screen& screen) override {
135 const float degtorad = 0.01745329251F;
136 const float dx = std::cos(gradient_.angle * degtorad);
137 const float dy = std::sin(gradient_.angle * degtorad);
138
139 // 投影每個角落以獲取漸變的範圍。
140 const float p1 = float(box_.x_min) * dx + float(box_.y_min) * dy;
141 const float p2 = float(box_.x_min) * dx + float(box_.y_max) * dy;
142 const float p3 = float(box_.x_max) * dx + float(box_.y_min) * dy;
143 const float p4 = float(box_.x_max) * dx + float(box_.y_max) * dy;
144 const float min = std::min({p1, p2, p3, p4});
145 const float max = std::max({p1, p2, p3, p4});
146
147 // 使用範圍和投影幾何將投影重新正規化為 [0, 1]。
148 const float dX = dx / (max - min);
149 const float dY = dy / (max - min);
150 const float dZ = -min / (max - min);
151
152 // 投影每個像素以獲取顏色。
153 if (background_color_) {
154 for (int y = box_.y_min; y <= box_.y_max; ++y) {
155 for (int x = box_.x_min; x <= box_.x_max; ++x) {
156 const float t = float(x) * dX + float(y) * dY + dZ;
157 screen.PixelAt(x, y).background_color = Interpolate(gradient_, t);
158 }
159 }
160 } else {
161 for (int y = box_.y_min; y <= box_.y_max; ++y) {
162 for (int x = box_.x_min; x <= box_.x_max; ++x) {
163 const float t = float(x) * dX + float(y) * dY + dZ;
164 screen.PixelAt(x, y).foreground_color = Interpolate(gradient_, t);
165 }
166 }
167 }
168
169 NodeDecorator::Render(screen);
170 }
171
172 LinearGradientNormalized gradient_;
173 bool background_color_;
174};
175
176} // namespace
177
178/// @brief 建立「空」漸變。這通常會接著呼叫 LinearGradient::Angle() 和 LinearGradient::Stop()。
179/// 範例:
180/// ```cpp
181/// auto gradient =
182/// LinearGradient()
183/// .Angle(45)
184/// .Stop(Color::Red, 0.0)
185/// .Stop(Color::Green, 0.5)
186/// .Stop(Color::Blue, 1.0);;
187/// ```
189
190/// @brief 建立一個包含兩種顏色的漸變。
191/// @param begin 漸變開始處的顏色。
192/// @param end 漸變結束處的顏色。
194 : LinearGradient(0, begin, end) {}
195
196/// @brief 建立一個包含兩種顏色和角度的漸變。
197/// @param a 漸變的角度。
198/// @param begin 漸變開始處的顏色。
199/// @param end 漸變結束處的顏色。
201 stops.push_back({begin, {}});
202 stops.push_back({end, {}});
203}
204
205/// @brief 設定漸變的角度。
206/// @param a 漸變的角度。
207/// @return 漸變。
209 angle = a;
210 return *this;
211}
212
213/// @brief 為漸變添加一個顏色停止點。
214/// @param c 停止點的顏色。
215/// @param p 停止點的位置。
217 stops.push_back({c, p});
218 return *this;
219}
220
221/// @brief 為漸變添加一個顏色停止點。
222/// @param c 停止點的顏色。
223/// @return 漸變。
224/// @note 停止點的位置是從附近的停止點內插而來的。
226 stops.push_back({c, {}});
227 return *this;
228}
229
230/// @brief 使用線性漸變效果設定元素的前景色。
231/// @param gradient 要應用於輸出元素的漸變效果。
232/// @param child 輸入元素。
233/// @return 已著色的輸出元素。
234/// @ingroup dom
235///
236/// ### 範例
237///
238/// ```cpp
239/// color(LinearGradient{0, {Color::Red, Color::Blue}}, text("Hello"))
240/// ```
241Element color(const LinearGradient& gradient, Element child) {
242 return std::make_shared<LinearGradientColor>(std::move(child), gradient,
243 /*background_color*/ false);
244}
245
246/// @brief 使用線性漸變效果設定元素的背景顏色。
247/// @param gradient 要應用於輸出元素的漸變效果。
248/// @param child 輸入元素。
249/// @return 已著色的輸出元素。
250/// @ingroup dom
251///
252/// ### 範例
253///
254/// ```cpp
255/// bgcolor(LinearGradient{0, {Color::Red, Color::Blue}}, text("Hello"))
256/// ```
257Element bgcolor(const LinearGradient& gradient, Element child) {
258 return std::make_shared<LinearGradientColor>(std::move(child), gradient,
259 /*background_color*/ true);
260}
261
262/// @brief 使用線性漸變效果裝飾前景色。
263/// @param gradient 要應用於輸出元素的漸變效果。
264/// @return 應用顏色的 Decorator。
265/// @ingroup dom
266///
267/// ### 範例
268///
269/// ```cpp
270/// text("Hello") | color(LinearGradient{0, {Color::Red, Color::Blue}})
271/// ```
273 return
274 [gradient](Element child) { return color(gradient, std::move(child)); };
275}
276
277/// @brief 使用線性漸變效果裝飾背景顏色。
278/// @param gradient 要應用於輸出元素的漸變效果。
279/// @return 應用顏色的 Decorator。
280/// @ingroup dom
281///
282/// ### 範例
283///
284/// ```cpp
285/// text("Hello") | color(LinearGradient{0, {Color::Red, Color::Blue}})
286/// ```
288 return
289 [gradient](Element child) { return bgcolor(gradient, std::move(child)); };
290}
291
292} // namespace ftxui
LinearGradient & Stop(Color color, float position)
為漸變添加一個顏色停止點。
LinearGradient & Angle(float angle)
設定漸變的角度。
LinearGradient()
建立「空」漸變。這通常會接著呼叫 LinearGradient::Angle() 和 LinearGradient::Stop()。 範例:
std::vector< Stop > stops
friend void Render(Screen &screen, Node *node, Selection &selection)
Decorator bgcolor(Color)
使用背景顏色進行裝飾。
Decorator color(Color)
使用前景顏色進行裝飾。
表示線性漸變顏色效果設定的類別。
static Color Interpolate(float t, const Color &a, const Color &b)
Color 是一個在終端使用者介面中表示顏色的類別。
Definition color.hpp:20
Color
Color 是一個列舉,表示終端機的色彩支援
Definition terminal.hpp:22
FTXUI 的 ftxui:: 命名空間
Definition animation.hpp:10
std::function< Element(Element)> Decorator
Definition elements.hpp:24
std::shared_ptr< Node > Element
Definition elements.hpp:22
void Render(Screen &screen, const Element &element)
std::vector< Color > colors
std::vector< float > positions