FTXUI 7.0.3
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 Cell, Screen
18
19namespace ftxui {
20namespace {
21
22struct LinearGradientNormalized {
23 float angle = 0.F;
24 std::vector<Color> colors;
25 std::vector<float> positions; // ソート済み。
26};
27
28// LinearGradientを正規化されたバージョンに変換する。
29LinearGradientNormalized Normalize(LinearGradient gradient) {
30 // サイズ0のグラデーションを処理する。
31 if (gradient.stops.empty()) {
32 return LinearGradientNormalized{
33 0.F,
34 {Color::Default, Color::Default},
35 {0.F, 1.F},
36 };
37 }
38
39 // 提供されていない場合、2つの端を埋める。
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 // 位置を補間して空白を埋める。
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 // ストップを位置でソートする。
69 std::sort(
70 gradient.stops.begin(), gradient.stops.end(),
71 [](const auto& a, const auto& b) { return a.position < b.position; });
72
73 // ゼロから始まっていない場合、ゼロにストップを追加する。
74 if (gradient.stops.front().position != 0) {
75 gradient.stops.insert(gradient.stops.begin(),
76 {gradient.stops.front().color, 0.F});
77 }
78 // 1で終わっていない場合、1にストップを追加する。
79 if (gradient.stops.back().position != 1) {
80 gradient.stops.push_back({gradient.stops.back().color, 1.F});
81 }
82
83 // 角度を正規化する。
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 // グラデーションのストップ内で正しい色を見つける。
98 size_t i = 1;
99 while (true) {
100 // 浮動小数点精度により `t` が1.0よりわずかに大きい場合があります。
101 // このため、`t` が最後のストップの位置より大きい場合を処理する必要があります。
102 // https://github.com/ArthurSonzogni/FTXUI/issues/998 を参照してください。
103 if (i >= gradient.positions.size()) {
104 const float half = 0.5F;
105 return Color::Interpolate(half, gradient.colors.back(),
106 gradient.colors.back());
107 }
108 if (t <= gradient.positions[i]) {
109 break;
110 }
111 ++i;
112 }
113
114 const float t0 = gradient.positions[i - 1];
115 const float t1 = gradient.positions[i - 0];
116 const float tt = (t - t0) / (t1 - t0);
117
118 const Color& c0 = gradient.colors[i - 1];
119 const Color& c1 = gradient.colors[i - 0];
120 const Color& cc = Color::Interpolate(tt, c0, c1);
121
122 return cc;
123}
124
125class LinearGradientColor : public NodeDecorator {
126 public:
127 explicit LinearGradientColor(Element child,
128 const LinearGradient& gradient,
129 bool background_color)
130 : NodeDecorator(std::move(child)),
131 gradient_(Normalize(gradient)),
132 background_color_{background_color} {}
133
134 private:
135 void Render(Screen& screen) override {
136 const float degtorad = 0.01745329251F;
137 const float dx = std::cos(gradient_.angle * degtorad);
138 const float dy = std::sin(gradient_.angle * degtorad);
139
140 // 各角を投影してグラデーションの範囲を得る。
141 const float p1 = float(box_.x_min) * dx + float(box_.y_min) * dy;
142 const float p2 = float(box_.x_min) * dx + float(box_.y_max) * dy;
143 const float p3 = float(box_.x_max) * dx + float(box_.y_min) * dy;
144 const float p4 = float(box_.x_max) * dx + float(box_.y_max) * dy;
145 const float min = std::min({p1, p2, p3, p4});
146 const float max = std::max({p1, p2, p3, p4});
147
148 // 範囲と射影幾何を使って投影を[0, 1]に再正規化する。
149 const float dX = dx / (max - min);
150 const float dY = dy / (max - min);
151 const float dZ = -min / (max - min);
152
153 // 各ピクセルを投影して色を得る。
154 if (background_color_) {
155 for (int y = box_.y_min; y <= box_.y_max; ++y) {
156 for (int x = box_.x_min; x <= box_.x_max; ++x) {
157 const float t = float(x) * dX + float(y) * dY + dZ;
158 screen.CellAt(x, y).background_color = Interpolate(gradient_, t);
159 }
160 }
161 } else {
162 for (int y = box_.y_min; y <= box_.y_max; ++y) {
163 for (int x = box_.x_min; x <= box_.x_max; ++x) {
164 const float t = float(x) * dX + float(y) * dY + dZ;
165 screen.CellAt(x, y).foreground_color = Interpolate(gradient_, t);
166 }
167 }
168 }
169
170 NodeDecorator::Render(screen);
171 }
172
173 LinearGradientNormalized gradient_;
174 bool background_color_;
175};
176
177} // namespace
178
179/// @brief 「空の」グラデーションを構築します。これはしばしば
180/// LinearGradient::Angle() および LinearGradient::Stop() の呼び出しが続きます。
181/// 例:
182/// ```cpp
183/// auto gradient =
184/// LinearGradient()
185/// .Angle(45)
186/// .Stop(Color::Red, 0.0)
187/// .Stop(Color::Green, 0.5)
188/// .Stop(Color::Blue, 1.0);;
189/// ```
190LinearGradient::LinearGradient() = default;
191
192/// @brief 2色でグラデーションを構築します。
193/// @param begin グラデーションの開始色。
194/// @param end グラデーションの終了色。
195LinearGradient::LinearGradient(Color begin, Color end)
196 : LinearGradient(0, begin, end) {}
197
198/// @brief 2色と角度でグラデーションを構築します。
199/// @param a グラデーションの角度。
200/// @param begin グラデーションの開始色。
201/// @param end グラデーションの終了色。
202LinearGradient::LinearGradient(float a, Color begin, Color end) : angle(a) {
203 stops.push_back({begin, {}});
204 stops.push_back({end, {}});
205}
206
207/// @brief グラデーションの角度を設定します。
208/// @param a グラデーションの角度。
209/// @return グラデーション。
210LinearGradient& LinearGradient::Angle(float a) {
211 angle = a;
212 return *this;
213}
214
215/// @brief グラデーションにカラーストップを追加します。
216/// @param c ストップの色。
217/// @param p ストップの位置。
218LinearGradient& LinearGradient::Stop(Color c, float p) {
219 stops.push_back({c, p});
220 return *this;
221}
222
223/// @brief グラデーションにカラーストップを追加します。
224/// @param c ストップの色。
225/// @return グラデーション。
226/// @note ストップの位置は近くのストップから補間されます。
227LinearGradient& LinearGradient::Stop(Color c) {
228 stops.push_back({c, {}});
229 return *this;
230}
231
232/// @brief 線形グラデーション効果を持つ要素のフォアグラウンドカラーを設定します。
233/// @param gradient 出力要素に適用されるグラデーション効果。
234/// @param child 入力要素。
235/// @return 色付けされた出力要素。
236/// @ingroup dom
237///
238/// ### 例
239///
240/// ```cpp
241/// color(LinearGradient{0, {Color::Red, Color::Blue}}, text("Hello"))
242/// ```
243Element color(const LinearGradient& gradient, Element child) {
244 return std::make_shared<LinearGradientColor>(std::move(child), gradient,
245 /*background_color*/ false);
246}
247
248/// @brief 線形グラデーション効果を持つ要素の背景色を設定します。
249/// @param gradient 出力要素に適用されるグラデーション効果。
250/// @param child 入力要素。
251/// @return 色付けされた出力要素。
252/// @ingroup dom
253///
254/// ### 例
255///
256/// ```cpp
257/// bgcolor(LinearGradient{0, {Color::Red, Color::Blue}}, text("Hello"))
258/// ```
259Element bgcolor(const LinearGradient& gradient, Element child) {
260 return std::make_shared<LinearGradientColor>(std::move(child), gradient,
261 /*background_color*/ true);
262}
263
264/// @brief フォアグラウンドカラーに線形グラデーション効果を使用して装飾します。
265/// @param gradient 出力要素に適用されるグラデーション効果。
266/// @return 色を適用するデコレーター。
267/// @ingroup dom
268///
269/// ### 例
270///
271/// ```cpp
272/// text("Hello") | color(LinearGradient{0, {Color::Red, Color::Blue}})
273/// ```
274Decorator color(const LinearGradient& gradient) {
275 return
276 [gradient](Element child) { return color(gradient, std::move(child)); };
277}
278
279/// @brief 背景色に線形グラデーション効果を使用して装飾します。
280/// @param gradient 出力要素に適用されるグラデーション効果。
281/// @return 色を適用するデコレーター。
282/// @ingroup dom
283///
284/// ### 例
285///
286/// ```cpp
287/// text("Hello") | color(LinearGradient{0, {Color::Red, Color::Blue}})
288/// ```
289Decorator bgcolor(const LinearGradient& gradient) {
290 return
291 [gradient](Element child) { return bgcolor(gradient, std::move(child)); };
292}
293
294} // namespace ftxui
Element bgcolor(Color color, Element child)
要素の背景色を設定します。
Definition dom/color.cpp:96
Element color(Color color, Element child)
要素の前景色を設定します。
Definition dom/color.cpp:81
FTXUI ftxui::名前空間
Definition animation.hpp:11
std::shared_ptr< Node > Element
Definition elements.hpp:25
std::function< Element(Element)> Decorator
Definition elements.hpp:27
int y
Definition elements.hpp:131
void Render(Screen &screen, Node *node, Selection &selection)
Definition node.cpp:105
std::vector< Color > colors
std::vector< float > positions