1use bevy::color::palettes::css::BLUE;
4use bevy::color::palettes::css::GREEN;
5use bevy::color::palettes::css::INDIGO;
6use bevy::color::palettes::css::LIME;
7use bevy::color::palettes::css::ORANGE;
8use bevy::color::palettes::css::RED;
9use bevy::color::palettes::css::VIOLET;
10use bevy::color::palettes::css::YELLOW;
11use bevy::prelude::*;
12
13use bevy::ui::ColorStop;
14use std::f32::consts::TAU;
15
16#[derive(Component)]
17struct CurrentColorSpaceLabel;
18
19fn main() {
20 App::new()
21 .add_plugins(DefaultPlugins)
22 .add_systems(Startup, setup)
23 .add_systems(Update, update)
24 .run();
25}
26
27fn setup(mut commands: Commands) {
28 commands.spawn(Camera2d);
29
30 commands
31 .spawn(Node {
32 flex_direction: FlexDirection::Column,
33 row_gap: px(20),
34 margin: UiRect::all(px(20)),
35 ..Default::default()
36 })
37 .with_children(|commands| {
38 for (b, stops) in [
39 (
40 4.,
41 vec![
42 ColorStop::new(Color::WHITE, percent(15)),
43 ColorStop::new(Color::BLACK, percent(85)),
44 ],
45 ),
46 (4., vec![RED.into(), BLUE.into(), LIME.into()]),
47 (
48 0.,
49 vec![
50 RED.into(),
51 ColorStop::new(RED, percent(100. / 7.)),
52 ColorStop::new(ORANGE, percent(100. / 7.)),
53 ColorStop::new(ORANGE, percent(200. / 7.)),
54 ColorStop::new(YELLOW, percent(200. / 7.)),
55 ColorStop::new(YELLOW, percent(300. / 7.)),
56 ColorStop::new(GREEN, percent(300. / 7.)),
57 ColorStop::new(GREEN, percent(400. / 7.)),
58 ColorStop::new(BLUE, percent(400. / 7.)),
59 ColorStop::new(BLUE, percent(500. / 7.)),
60 ColorStop::new(INDIGO, percent(500. / 7.)),
61 ColorStop::new(INDIGO, percent(600. / 7.)),
62 ColorStop::new(VIOLET, percent(600. / 7.)),
63 VIOLET.into(),
64 ],
65 ),
66 ] {
67 commands.spawn(Node::default()).with_children(|commands| {
68 commands
69 .spawn(Node {
70 flex_direction: FlexDirection::Column,
71 row_gap: px(5),
72 ..Default::default()
73 })
74 .with_children(|commands| {
75 for (w, h) in [(70., 70.), (35., 70.), (70., 35.)] {
76 commands
77 .spawn(Node {
78 column_gap: px(10),
79 ..Default::default()
80 })
81 .with_children(|commands| {
82 for angle in (0..8).map(|i| i as f32 * TAU / 8.) {
83 commands.spawn((
84 Node {
85 width: px(w),
86 height: px(h),
87 border: UiRect::all(px(b)),
88 border_radius: BorderRadius::all(px(20)),
89 ..default()
90 },
91 BackgroundGradient::from(LinearGradient {
92 angle,
93 stops: stops.clone(),
94 ..default()
95 }),
96 BorderGradient::from(LinearGradient {
97 angle: 3. * TAU / 8.,
98 stops: vec![
99 YELLOW.into(),
100 Color::WHITE.into(),
101 ORANGE.into(),
102 ],
103 ..default()
104 }),
105 ));
106 }
107 });
108 }
109 });
110
111 commands.spawn(Node::default()).with_children(|commands| {
112 commands.spawn((
113 Node {
114 aspect_ratio: Some(1.),
115 height: percent(100),
116 border: UiRect::all(px(b)),
117 margin: UiRect::left(px(20)),
118 border_radius: BorderRadius::all(px(20)),
119 ..default()
120 },
121 BackgroundGradient::from(LinearGradient {
122 angle: 0.,
123 stops: stops.clone(),
124 ..default()
125 }),
126 BorderGradient::from(LinearGradient {
127 angle: 3. * TAU / 8.,
128 stops: vec![YELLOW.into(), Color::WHITE.into(), ORANGE.into()],
129 ..default()
130 }),
131 AnimateMarker,
132 ));
133
134 commands.spawn((
135 Node {
136 aspect_ratio: Some(1.),
137 height: percent(100),
138 border: UiRect::all(px(b)),
139 margin: UiRect::left(px(20)),
140 border_radius: BorderRadius::all(px(20)),
141 ..default()
142 },
143 BackgroundGradient::from(RadialGradient {
144 stops: stops.clone(),
145 shape: RadialGradientShape::ClosestSide,
146 position: UiPosition::CENTER,
147 ..default()
148 }),
149 BorderGradient::from(LinearGradient {
150 angle: 3. * TAU / 8.,
151 stops: vec![YELLOW.into(), Color::WHITE.into(), ORANGE.into()],
152 ..default()
153 }),
154 AnimateMarker,
155 ));
156 commands.spawn((
157 Node {
158 aspect_ratio: Some(1.),
159 height: percent(100),
160 border: UiRect::all(px(b)),
161 margin: UiRect::left(px(20)),
162 border_radius: BorderRadius::all(px(20)),
163 ..default()
164 },
165 BackgroundGradient::from(ConicGradient {
166 start: 0.,
167 stops: stops
168 .iter()
169 .map(|stop| AngularColorStop::auto(stop.color))
170 .collect(),
171 position: UiPosition::CENTER,
172 ..default()
173 }),
174 BorderGradient::from(LinearGradient {
175 angle: 3. * TAU / 8.,
176 stops: vec![YELLOW.into(), Color::WHITE.into(), ORANGE.into()],
177 ..default()
178 }),
179 AnimateMarker,
180 ));
181 });
182 });
183 }
184
185 let button = commands.spawn((
186 Button,
187 Node {
188 border: UiRect::all(px(2)),
189 padding: UiRect::axes(px(8), px(4)),
190 justify_content: JustifyContent::Center,
192 align_items: AlignItems::Center,
194 border_radius: BorderRadius::MAX,
195 ..default()
196 },
197 BorderColor::all(Color::WHITE),
198 BackgroundColor(Color::BLACK),
199 children![(
200 Text::new("next color space"),
201 TextColor(Color::srgb(0.9, 0.9, 0.9)),
202 TextShadow::default(),
203 )]
204 )).observe(
205 |_event: On<Pointer<Over>>, mut border_query: Query<&mut BorderColor, With<Button>>| {
206 *border_query.single_mut().unwrap() = BorderColor::all(RED);
207
208
209 })
210 .observe(
211 |_event: On<Pointer<Out>>, mut border_query: Query<&mut BorderColor, With<Button>>| {
212 *border_query.single_mut().unwrap() = BorderColor::all(Color::WHITE);
213 })
214 .observe(
215 |_event: On<Pointer<Click>>,
216 mut gradients_query: Query<&mut BackgroundGradient>,
217 mut label_query: Query<
218 &mut Text,
219 With<CurrentColorSpaceLabel>,
220 >| {
221 let mut current_space = InterpolationColorSpace::default();
222 for mut gradients in gradients_query.iter_mut() {
223 for gradient in gradients.0.iter_mut() {
224 let space = match gradient {
225 Gradient::Linear(linear_gradient) => {
226 &mut linear_gradient.color_space
227 }
228 Gradient::Radial(radial_gradient) => {
229 &mut radial_gradient.color_space
230 }
231 Gradient::Conic(conic_gradient) => {
232 &mut conic_gradient.color_space
233 }
234 };
235 *space = match *space {
236 InterpolationColorSpace::Oklaba => {
237 InterpolationColorSpace::Oklcha
238 }
239 InterpolationColorSpace::Oklcha => {
240 InterpolationColorSpace::OklchaLong
241 }
242 InterpolationColorSpace::OklchaLong => {
243 InterpolationColorSpace::Srgba
244 }
245 InterpolationColorSpace::Srgba => {
246 InterpolationColorSpace::LinearRgba
247 }
248 InterpolationColorSpace::LinearRgba => {
249 InterpolationColorSpace::Hsla
250 }
251 InterpolationColorSpace::Hsla => {
252 InterpolationColorSpace::HslaLong
253 }
254 InterpolationColorSpace::HslaLong => {
255 InterpolationColorSpace::Hsva
256 }
257 InterpolationColorSpace::Hsva => {
258 InterpolationColorSpace::HsvaLong
259 }
260 InterpolationColorSpace::HsvaLong => {
261 InterpolationColorSpace::Oklaba
262 }
263 };
264 current_space = *space;
265 }
266 }
267 for mut label in label_query.iter_mut() {
268 label.0 = format!("{current_space:?}");
269 }
270 }
271 ).id();
272
273 commands.spawn(
274 Node {
275 flex_direction: FlexDirection::Column,
276 row_gap: px(10),
277 align_items: AlignItems::Center,
278 ..Default::default()
279 }
280 ).with_children(|commands| {
281 commands.spawn((Text::new(format!("{:?}", InterpolationColorSpace::default())), TextFont { font_size: FontSize::Px(25.), ..default() }, CurrentColorSpaceLabel));
282
283 })
284 .add_child(button);
285 });
286}
287
288#[derive(Component)]
289struct AnimateMarker;
290
291fn update(time: Res<Time>, mut query: Query<&mut BackgroundGradient, With<AnimateMarker>>) {
292 for mut gradients in query.iter_mut() {
293 for gradient in gradients.0.iter_mut() {
294 if let Gradient::Linear(LinearGradient { angle, .. }) = gradient {
295 *angle += 0.5 * time.delta_secs();
296 }
297 }
298 }
299}