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