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// このソースコードの使用は、LICENSEファイルにあるMITライセンスによって管理されています。
5#include <algorithm> // for max, min, sort, copy
6#include <cmath> // for fmod, cos, sin
7#include <cstddef> // for size_t
8#include <ftxui/dom/linear_gradient.hpp> // for LinearGradient::Stop, LinearGradient
9#include <memory> // for allocator_traits<>::value_type, make_shared
10#include <optional> // for optional, operator!=, operator<
11#include <utility> // for move
12#include <vector> // for vector
13
14#include "ftxui/dom/elements.hpp" // for Element, Decorator, bgcolor, color
15#include "ftxui/dom/node_decorator.hpp" // for NodeDecorator
16#include "ftxui/screen/box.hpp" // for Box
17#include "ftxui/screen/color.hpp" // for Color, Color::Default, Color::Blue
18#include "ftxui/screen/screen.hpp" // for Pixel, Screen
19
20namespace ftxui {
21namespace {
22
23struct LinearGradientNormalized {
24 float angle = 0.F;
25 std::vector<Color> colors;
26 std::vector<float> positions; // Sorted.
27};
28
29// 線形グラデーションを正規化されたバージョンに変換します。
30LinearGradientNormalized Normalize(LinearGradient gradient) {
31 // サイズ0のグラデーションを処理します。
32 if (gradient.stops.empty()) {
33 return LinearGradientNormalized{
34 0.F,
36 {0.F, 1.F},
37 };
38 }
39
40 // 指定されていない場合、2つの範囲を埋めます。
41 if (!gradient.stops.front().position) {
42 gradient.stops.front().position = 0.F;
43 }
44 if (!gradient.stops.back().position) {
45 gradient.stops.back().position = 1.F;
46 }
47
48 // 位置を補間して空白を埋めます。
49 size_t last_checkpoint = 0;
50 for (size_t i = 1; i < gradient.stops.size(); ++i) {
51 if (!gradient.stops[i].position) {
52 continue;
53 }
54
55 if (i - last_checkpoint >= 2) {
56 const float min = gradient.stops[i].position.value(); // NOLINT
57 const float max =
58 gradient.stops[last_checkpoint].position.value(); // NOLINT
59 for (size_t j = last_checkpoint + 1; j < i; ++j) {
60 gradient.stops[j].position = min + (max - min) *
61 float(j - last_checkpoint) /
62 float(i - last_checkpoint);
63 }
64 }
65
66 last_checkpoint = i;
67 }
68
69 // ストップを位置でソートします。
70 std::sort(
71 gradient.stops.begin(), gradient.stops.end(),
72 [](const auto& a, const auto& b) { return a.position < b.position; });
73
74 // ゼロから始まらない場合、ゼロにストップを追加します。
75 if (gradient.stops.front().position != 0) {
76 gradient.stops.insert(gradient.stops.begin(),
77 {gradient.stops.front().color, 0.F});
78 }
79 // 1で終わらない場合、1にストップを追加します。
80 if (gradient.stops.back().position != 1) {
81 gradient.stops.push_back({gradient.stops.back().color, 1.F});
82 }
83
84 // 角度を正規化します。
85 LinearGradientNormalized normalized;
86 const float modulo = 360.F;
87 normalized.angle =
88 std::fmod(std::fmod(gradient.angle, modulo) + modulo, modulo);
89 for (auto& stop : gradient.stops) {
90 normalized.colors.push_back(stop.color);
91 // NOLINTNEXTLINE
92 normalized.positions.push_back(stop.position.value());
93 }
94 return normalized;
95}
96
97Color Interpolate(const LinearGradientNormalized& gradient, float t) {
98 // グラデーションのストップで正しい色を見つけます。
99 size_t i = 1;
100 while (true) {
101 // 浮動小数点精度により `t` が1.0よりわずかに大きい場合があります。
102 // このため、`t` が最後のストップの位置より大きい場合を処理する必要があります。
103 // 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 // グラデーションの範囲を取得するために、すべての角を投影します。
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 // 範囲と射影幾何学を使用して、投影を [0, 1] に再正規化します。
150 const float dX = dx / (max - min);
151 const float dY = dy / (max - min);
152 const float dZ = -min / (max - min);
153
154 // 色を取得するために、すべてのピクセルを投影します。
155 if (background_color_) {
156 for (int y = box_.y_min; y <= box_.y_max; ++y) {
157 for (int x = box_.x_min; x <= box_.x_max; ++x) {
158 const float t = float(x) * dX + float(y) * dY + dZ;
159 screen.PixelAt(x, y).background_color = Interpolate(gradient_, t);
160 }
161 }
162 } else {
163 for (int y = box_.y_min; y <= box_.y_max; ++y) {
164 for (int x = box_.x_min; x <= box_.x_max; ++x) {
165 const float t = float(x) * dX + float(y) * dY + dZ;
166 screen.PixelAt(x, y).foreground_color = Interpolate(gradient_, t);
167 }
168 }
169 }
170
171 NodeDecorator::Render(screen);
172 }
173
174 LinearGradientNormalized gradient_;
175 bool background_color_;
176};
177
178} // namespace
179
180/// @brief 「空の」グラデーションを構築します。これはしばしば
181/// LinearGradient::Angle() および LinearGradient::Stop() の呼び出しが続きます。
182/// 例:
183/// ```cpp
184/// auto gradient =
185/// LinearGradient()
186/// .Angle(45)
187/// .Stop(Color::Red, 0.0)
188/// .Stop(Color::Green, 0.5)
189/// .Stop(Color::Blue, 1.0);;
190/// ```
192
193/// @brief 2色でグラデーションを構築します。
194/// @param begin グラデーションの開始色。
195/// @param end グラデーションの終了色。
197 : LinearGradient(0, begin, end) {}
198
199/// @brief 2色と角度でグラデーションを構築します。
200/// @param a グラデーションの角度。
201/// @param begin グラデーションの開始色。
202/// @param end グラデーションの終了色。
204 stops.push_back({begin, {}});
205 stops.push_back({end, {}});
206}
207
208/// @brief グラデーションの角度を設定します。
209/// @param a グラデーションの角度。
210/// @return グラデーション。
212 angle = a;
213 return *this;
214}
215
216/// @brief グラデーションにカラーストップを追加します。
217/// @param c ストップの色。
218/// @param p ストップの位置。
220 stops.push_back({c, p});
221 return *this;
222}
223
224/// @brief グラデーションにカラーストップを追加します。
225/// @param c ストップの色。
226/// @return グラデーション。
227/// @note ストップの位置は近くのストップから補間されます。
229 stops.push_back({c, {}});
230 return *this;
231}
232
233/// @brief 線形グラデーション効果を持つ要素のフォアグラウンドカラーを設定します。
234/// @param gradient 出力要素に適用されるグラデーション効果。
235/// @param child 入力要素。
236/// @return 色付けされた出力要素。
237/// @ingroup dom
238///
239/// ### 例
240///
241/// ```cpp
242/// color(LinearGradient{0, {Color::Red, Color::Blue}}, text("Hello"))
243/// ```
244Element color(const LinearGradient& gradient, Element child) {
245 return std::make_shared<LinearGradientColor>(std::move(child), gradient,
246 /*background_color*/ false);
247}
248
249/// @brief 線形グラデーション効果を持つ要素の背景色を設定します。
250/// @param gradient 出力要素に適用されるグラデーション効果。
251/// @param child 入力要素。
252/// @return 色付けされた出力要素。
253/// @ingroup dom
254///
255/// ### 例
256///
257/// ```cpp
258/// bgcolor(LinearGradient{0, {Color::Red, Color::Blue}}, text("Hello"))
259/// ```
260Element bgcolor(const LinearGradient& gradient, Element child) {
261 return std::make_shared<LinearGradientColor>(std::move(child), gradient,
262 /*background_color*/ true);
263}
264
265/// @brief フォアグラウンドカラーに線形グラデーション効果を使用して装飾します。
266/// @param gradient 出力要素に適用されるグラデーション効果。
267/// @return 色を適用するデコレーター。
268/// @ingroup dom
269///
270/// ### 例
271///
272/// ```cpp
273/// text("Hello") | color(LinearGradient{0, {Color::Red, Color::Blue}})
274/// ```
276 return
277 [gradient](Element child) { return color(gradient, std::move(child)); };
278}
279
280/// @brief 背景色に線形グラデーション効果を使用して装飾します。
281/// @param gradient 出力要素に適用されるグラデーション効果。
282/// @return 色を適用するデコレーター。
283/// @ingroup dom
284///
285/// ### 例
286///
287/// ```cpp
288/// text("Hello") | color(LinearGradient{0, {Color::Red, Color::Blue}})
289/// ```
291 return
292 [gradient](Element child) { return bgcolor(gradient, std::move(child)); };
293}
294
295} // 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)
Definition node.cpp:96
Decorator bgcolor(Color)
背景色を使用して装飾します。
void Render(Screen &screen, const Element &element)
要素をftxui::Screenに表示します。
Definition node.cpp:84
Decorator color(Color)
前景色を使用して装飾します。
線形グラデーションカラー効果の設定を表すクラスです。
static Color Interpolate(float t, const Color &a, const Color &b)
Colorは、ターミナルユーザーインターフェースにおける色を表すクラスです。
Definition color.hpp:25
Color
Colorは、ターミナルの色サポートを表す列挙型です。
Definition terminal.hpp:21
FTXUI ftxui:: 名前空間
Definition animation.hpp:9
std::function< Element(Element)> Decorator
Definition elements.hpp:23
std::shared_ptr< Node > Element
Definition elements.hpp:21
std::vector< Color > colors
std::vector< float > positions