1use crate::{UiPosition, Val};
2use bevy_color::{Color, Srgba};
3use bevy_ecs::{component::Component, reflect::ReflectComponent};
4use bevy_math::Vec2;
5use bevy_reflect::prelude::*;
6use bevy_utils::default;
7use core::{f32, f32::consts::TAU};
8
9#[derive(Debug, Copy, Clone, PartialEq, Reflect)]
11#[reflect(Default, PartialEq, Debug)]
12#[cfg_attr(
13 feature = "serialize",
14 derive(serde::Serialize, serde::Deserialize),
15 reflect(Serialize, Deserialize)
16)]
17pub struct ColorStop {
18 pub color: Color,
20 pub point: Val,
23 pub hint: f32,
25}
26
27impl ColorStop {
28 pub fn new(color: impl Into<Color>, point: Val) -> Self {
30 Self {
31 color: color.into(),
32 point,
33 hint: 0.5,
34 }
35 }
36
37 pub fn auto(color: impl Into<Color>) -> Self {
40 Self {
41 color: color.into(),
42 point: Val::Auto,
43 hint: 0.5,
44 }
45 }
46
47 pub fn px(color: impl Into<Color>, px: f32) -> Self {
49 Self {
50 color: color.into(),
51 point: Val::Px(px),
52 hint: 0.5,
53 }
54 }
55
56 pub fn percent(color: impl Into<Color>, percent: f32) -> Self {
58 Self {
59 color: color.into(),
60 point: Val::Percent(percent),
61 hint: 0.5,
62 }
63 }
64
65 pub const fn with_hint(mut self, hint: f32) -> Self {
67 self.hint = hint;
68 self
69 }
70}
71
72impl From<(Color, Val)> for ColorStop {
73 fn from((color, stop): (Color, Val)) -> Self {
74 Self {
75 color,
76 point: stop,
77 hint: 0.5,
78 }
79 }
80}
81
82impl From<Color> for ColorStop {
83 fn from(color: Color) -> Self {
84 Self {
85 color,
86 point: Val::Auto,
87 hint: 0.5,
88 }
89 }
90}
91
92impl From<Srgba> for ColorStop {
93 fn from(color: Srgba) -> Self {
94 Self {
95 color: color.into(),
96 point: Val::Auto,
97 hint: 0.5,
98 }
99 }
100}
101
102impl Default for ColorStop {
103 fn default() -> Self {
104 Self {
105 color: Color::WHITE,
106 point: Val::Auto,
107 hint: 0.5,
108 }
109 }
110}
111
112#[derive(Debug, Copy, Clone, PartialEq, Reflect)]
114#[reflect(Default, PartialEq, Debug)]
115#[cfg_attr(
116 feature = "serialize",
117 derive(serde::Serialize, serde::Deserialize),
118 reflect(Serialize, Deserialize)
119)]
120pub struct AngularColorStop {
121 pub color: Color,
123 pub angle: Option<f32>,
153 pub hint: f32,
155}
156
157impl AngularColorStop {
158 pub fn new(color: impl Into<Color>, angle: f32) -> Self {
160 Self {
161 color: color.into(),
162 angle: Some(angle),
163 hint: 0.5,
164 }
165 }
166
167 pub fn auto(color: impl Into<Color>) -> Self {
170 Self {
171 color: color.into(),
172 angle: None,
173 hint: 0.5,
174 }
175 }
176
177 pub const fn with_hint(mut self, hint: f32) -> Self {
179 self.hint = hint;
180 self
181 }
182}
183
184impl From<(Color, f32)> for AngularColorStop {
185 fn from((color, angle): (Color, f32)) -> Self {
186 Self {
187 color,
188 angle: Some(angle),
189 hint: 0.5,
190 }
191 }
192}
193
194impl From<Color> for AngularColorStop {
195 fn from(color: Color) -> Self {
196 Self {
197 color,
198 angle: None,
199 hint: 0.5,
200 }
201 }
202}
203
204impl From<Srgba> for AngularColorStop {
205 fn from(color: Srgba) -> Self {
206 Self {
207 color: color.into(),
208 angle: None,
209 hint: 0.5,
210 }
211 }
212}
213
214impl Default for AngularColorStop {
215 fn default() -> Self {
216 Self {
217 color: Color::WHITE,
218 angle: None,
219 hint: 0.5,
220 }
221 }
222}
223
224#[derive(Default, Clone, PartialEq, Debug, Reflect)]
228#[reflect(PartialEq)]
229#[cfg_attr(
230 feature = "serialize",
231 derive(serde::Serialize, serde::Deserialize),
232 reflect(Serialize, Deserialize)
233)]
234pub struct LinearGradient {
235 pub color_space: InterpolationColorSpace,
237 pub angle: f32,
240 pub stops: Vec<ColorStop>,
242}
243
244impl LinearGradient {
245 pub const TO_TOP: f32 = 0.;
247 pub const TO_TOP_RIGHT: f32 = TAU / 8.;
249 pub const TO_RIGHT: f32 = 2. * Self::TO_TOP_RIGHT;
251 pub const TO_BOTTOM_RIGHT: f32 = 3. * Self::TO_TOP_RIGHT;
253 pub const TO_BOTTOM: f32 = 4. * Self::TO_TOP_RIGHT;
255 pub const TO_BOTTOM_LEFT: f32 = 5. * Self::TO_TOP_RIGHT;
257 pub const TO_LEFT: f32 = 6. * Self::TO_TOP_RIGHT;
259 pub const TO_TOP_LEFT: f32 = 7. * Self::TO_TOP_RIGHT;
261
262 pub fn new(angle: f32, stops: Vec<ColorStop>) -> Self {
264 Self {
265 angle,
266 stops,
267 color_space: InterpolationColorSpace::default(),
268 }
269 }
270
271 pub fn to_top(stops: Vec<ColorStop>) -> Self {
273 Self {
274 angle: Self::TO_TOP,
275 stops,
276 color_space: InterpolationColorSpace::default(),
277 }
278 }
279
280 pub fn to_top_right(stops: Vec<ColorStop>) -> Self {
282 Self {
283 angle: Self::TO_TOP_RIGHT,
284 stops,
285 color_space: InterpolationColorSpace::default(),
286 }
287 }
288
289 pub fn to_right(stops: Vec<ColorStop>) -> Self {
291 Self {
292 angle: Self::TO_RIGHT,
293 stops,
294 color_space: InterpolationColorSpace::default(),
295 }
296 }
297
298 pub fn to_bottom_right(stops: Vec<ColorStop>) -> Self {
300 Self {
301 angle: Self::TO_BOTTOM_RIGHT,
302 stops,
303 color_space: InterpolationColorSpace::default(),
304 }
305 }
306
307 pub fn to_bottom(stops: Vec<ColorStop>) -> Self {
309 Self {
310 angle: Self::TO_BOTTOM,
311 stops,
312 color_space: InterpolationColorSpace::default(),
313 }
314 }
315
316 pub fn to_bottom_left(stops: Vec<ColorStop>) -> Self {
318 Self {
319 angle: Self::TO_BOTTOM_LEFT,
320 stops,
321 color_space: InterpolationColorSpace::default(),
322 }
323 }
324
325 pub fn to_left(stops: Vec<ColorStop>) -> Self {
327 Self {
328 angle: Self::TO_LEFT,
329 stops,
330 color_space: InterpolationColorSpace::default(),
331 }
332 }
333
334 pub fn to_top_left(stops: Vec<ColorStop>) -> Self {
336 Self {
337 angle: Self::TO_TOP_LEFT,
338 stops,
339 color_space: InterpolationColorSpace::default(),
340 }
341 }
342
343 pub fn degrees(degrees: f32, stops: Vec<ColorStop>) -> Self {
345 Self {
346 angle: degrees.to_radians(),
347 stops,
348 color_space: InterpolationColorSpace::default(),
349 }
350 }
351
352 pub fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
353 self.color_space = color_space;
354 self
355 }
356}
357
358#[derive(Clone, PartialEq, Debug, Reflect)]
362#[reflect(PartialEq)]
363#[cfg_attr(
364 feature = "serialize",
365 derive(serde::Serialize, serde::Deserialize),
366 reflect(Serialize, Deserialize)
367)]
368pub struct RadialGradient {
369 pub color_space: InterpolationColorSpace,
371 pub position: UiPosition,
373 pub shape: RadialGradientShape,
375 pub stops: Vec<ColorStop>,
377}
378
379impl RadialGradient {
380 pub fn new(position: UiPosition, shape: RadialGradientShape, stops: Vec<ColorStop>) -> Self {
382 Self {
383 color_space: default(),
384 position,
385 shape,
386 stops,
387 }
388 }
389
390 pub const fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
391 self.color_space = color_space;
392 self
393 }
394}
395
396impl Default for RadialGradient {
397 fn default() -> Self {
398 Self {
399 position: UiPosition::CENTER,
400 shape: RadialGradientShape::ClosestCorner,
401 stops: Vec::new(),
402 color_space: default(),
403 }
404 }
405}
406
407#[derive(Default, Clone, PartialEq, Debug, Reflect)]
411#[reflect(PartialEq)]
412#[cfg_attr(
413 feature = "serialize",
414 derive(serde::Serialize, serde::Deserialize),
415 reflect(Serialize, Deserialize)
416)]
417pub struct ConicGradient {
418 pub color_space: InterpolationColorSpace,
420 pub start: f32,
422 pub position: UiPosition,
424 pub stops: Vec<AngularColorStop>,
426}
427
428impl ConicGradient {
429 pub fn new(position: UiPosition, stops: Vec<AngularColorStop>) -> Self {
431 Self {
432 color_space: default(),
433 start: 0.,
434 position,
435 stops,
436 }
437 }
438
439 pub const fn with_start(mut self, start: f32) -> Self {
441 self.start = start;
442 self
443 }
444
445 pub const fn with_position(mut self, position: UiPosition) -> Self {
447 self.position = position;
448 self
449 }
450
451 pub const fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
452 self.color_space = color_space;
453 self
454 }
455}
456
457#[derive(Clone, PartialEq, Debug, Reflect)]
458#[reflect(PartialEq)]
459#[cfg_attr(
460 feature = "serialize",
461 derive(serde::Serialize, serde::Deserialize),
462 reflect(Serialize, Deserialize)
463)]
464pub enum Gradient {
465 Linear(LinearGradient),
469 Radial(RadialGradient),
473 Conic(ConicGradient),
477}
478
479impl Gradient {
480 pub const fn is_empty(&self) -> bool {
482 match self {
483 Gradient::Linear(gradient) => gradient.stops.is_empty(),
484 Gradient::Radial(gradient) => gradient.stops.is_empty(),
485 Gradient::Conic(gradient) => gradient.stops.is_empty(),
486 }
487 }
488
489 pub fn get_single(&self) -> Option<Color> {
491 match self {
492 Gradient::Linear(gradient) => gradient
493 .stops
494 .first()
495 .and_then(|stop| (gradient.stops.len() == 1).then_some(stop.color)),
496 Gradient::Radial(gradient) => gradient
497 .stops
498 .first()
499 .and_then(|stop| (gradient.stops.len() == 1).then_some(stop.color)),
500 Gradient::Conic(gradient) => gradient
501 .stops
502 .first()
503 .and_then(|stop| (gradient.stops.len() == 1).then_some(stop.color)),
504 }
505 }
506}
507
508impl From<LinearGradient> for Gradient {
509 fn from(value: LinearGradient) -> Self {
510 Self::Linear(value)
511 }
512}
513
514impl From<RadialGradient> for Gradient {
515 fn from(value: RadialGradient) -> Self {
516 Self::Radial(value)
517 }
518}
519
520impl From<ConicGradient> for Gradient {
521 fn from(value: ConicGradient) -> Self {
522 Self::Conic(value)
523 }
524}
525
526#[derive(Component, Clone, PartialEq, Debug, Default, Reflect)]
527#[reflect(Component, Default, PartialEq, Debug, Clone)]
528#[cfg_attr(
529 feature = "serialize",
530 derive(serde::Serialize, serde::Deserialize),
531 reflect(Serialize, Deserialize)
532)]
533pub struct BackgroundGradient(pub Vec<Gradient>);
535
536impl<T: Into<Gradient>> From<T> for BackgroundGradient {
537 fn from(value: T) -> Self {
538 Self(vec![value.into()])
539 }
540}
541
542#[derive(Component, Clone, PartialEq, Debug, Default, Reflect)]
543#[reflect(Component, Default, PartialEq, Debug, Clone)]
544#[cfg_attr(
545 feature = "serialize",
546 derive(serde::Serialize, serde::Deserialize),
547 reflect(Serialize, Deserialize)
548)]
549pub struct BorderGradient(pub Vec<Gradient>);
551
552impl<T: Into<Gradient>> From<T> for BorderGradient {
553 fn from(value: T) -> Self {
554 Self(vec![value.into()])
555 }
556}
557
558#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]
559#[reflect(PartialEq, Default)]
560#[cfg_attr(
561 feature = "serialize",
562 derive(serde::Serialize, serde::Deserialize),
563 reflect(Serialize, Deserialize)
564)]
565pub enum RadialGradientShape {
566 ClosestSide,
568 FarthestSide,
570 #[default]
572 ClosestCorner,
573 FarthestCorner,
575 Circle(Val),
577 Ellipse(Val, Val),
579}
580
581const fn close_side(p: f32, h: f32) -> f32 {
582 (-h - p).abs().min((h - p).abs())
583}
584
585const fn far_side(p: f32, h: f32) -> f32 {
586 (-h - p).abs().max((h - p).abs())
587}
588
589const fn close_side2(p: Vec2, h: Vec2) -> f32 {
590 close_side(p.x, h.x).min(close_side(p.y, h.y))
591}
592
593const fn far_side2(p: Vec2, h: Vec2) -> f32 {
594 far_side(p.x, h.x).max(far_side(p.y, h.y))
595}
596
597impl RadialGradientShape {
598 pub fn resolve(
600 self,
601 position: Vec2,
602 scale_factor: f32,
603 physical_size: Vec2,
604 physical_target_size: Vec2,
605 ) -> Vec2 {
606 let half_size = 0.5 * physical_size;
607 match self {
608 RadialGradientShape::ClosestSide => Vec2::splat(close_side2(position, half_size)),
609 RadialGradientShape::FarthestSide => Vec2::splat(far_side2(position, half_size)),
610 RadialGradientShape::ClosestCorner => Vec2::new(
611 close_side(position.x, half_size.x),
612 close_side(position.y, half_size.y),
613 ),
614 RadialGradientShape::FarthestCorner => Vec2::new(
615 far_side(position.x, half_size.x),
616 far_side(position.y, half_size.y),
617 ),
618 RadialGradientShape::Circle(radius) => Vec2::splat(
619 radius
620 .resolve(scale_factor, physical_size.x, physical_target_size)
621 .unwrap_or(0.),
622 ),
623 RadialGradientShape::Ellipse(x, y) => Vec2::new(
624 x.resolve(scale_factor, physical_size.x, physical_target_size)
625 .unwrap_or(0.),
626 y.resolve(scale_factor, physical_size.y, physical_target_size)
627 .unwrap_or(0.),
628 ),
629 }
630 }
631}
632
633#[derive(Default, Copy, Clone, Hash, Debug, PartialEq, Eq, Reflect)]
635#[cfg_attr(
636 feature = "serialize",
637 derive(serde::Serialize, serde::Deserialize),
638 reflect(Serialize, Deserialize)
639)]
640pub enum InterpolationColorSpace {
641 #[default]
643 Oklaba,
644 Oklcha,
646 OklchaLong,
648 Srgba,
650 LinearRgba,
652 Hsla,
654 HslaLong,
656 Hsva,
658 HsvaLong,
660}
661
662pub trait InColorSpace: Sized {
664 fn in_color_space(self, color_space: InterpolationColorSpace) -> Self;
666
667 fn in_oklaba(self) -> Self {
669 self.in_color_space(InterpolationColorSpace::Oklaba)
670 }
671
672 fn in_oklch(self) -> Self {
674 self.in_color_space(InterpolationColorSpace::Oklcha)
675 }
676
677 fn in_oklch_long(self) -> Self {
679 self.in_color_space(InterpolationColorSpace::OklchaLong)
680 }
681
682 fn in_srgb(self) -> Self {
684 self.in_color_space(InterpolationColorSpace::Srgba)
685 }
686
687 fn in_linear_rgb(self) -> Self {
689 self.in_color_space(InterpolationColorSpace::LinearRgba)
690 }
691}
692
693impl InColorSpace for LinearGradient {
694 fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
696 self.color_space = color_space;
697 self
698 }
699}
700
701impl InColorSpace for RadialGradient {
702 fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
704 self.color_space = color_space;
705 self
706 }
707}
708
709impl InColorSpace for ConicGradient {
710 fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
712 self.color_space = color_space;
713 self
714 }
715}