Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ui/src/gradients.rs
6598 views
1
use crate::{UiPosition, Val};
2
use bevy_color::{Color, Srgba};
3
use bevy_ecs::{component::Component, reflect::ReflectComponent};
4
use bevy_math::Vec2;
5
use bevy_reflect::prelude::*;
6
use bevy_utils::default;
7
use core::{f32, f32::consts::TAU};
8
9
/// A color stop for a gradient
10
#[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
)]
17
pub struct ColorStop {
18
/// Color
19
pub color: Color,
20
/// Logical position along the gradient line.
21
/// Stop positions are relative to the start of the gradient and not other stops.
22
pub point: Val,
23
/// Normalized position between this and the following stop of the interpolation midpoint.
24
pub hint: f32,
25
}
26
27
impl ColorStop {
28
/// Create a new color stop
29
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
/// An automatic color stop.
38
/// The positions of automatic stops are interpolated evenly between explicit stops.
39
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
/// A color stop with its position in logical pixels.
48
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
/// A color stop with a percentage position.
57
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
// Set the interpolation midpoint between this and the following stop
66
pub const fn with_hint(mut self, hint: f32) -> Self {
67
self.hint = hint;
68
self
69
}
70
}
71
72
impl 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
82
impl 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
92
impl 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
102
impl 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
/// An angular color stop for a conic gradient
113
#[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
)]
120
pub struct AngularColorStop {
121
/// Color of the stop
122
pub color: Color,
123
/// The angle of the stop.
124
/// Angles are relative to the start of the gradient and not other stops.
125
/// If set to `None` the angle of the stop will be interpolated between the explicit stops or 0 and 2 PI degrees if there no explicit stops.
126
/// Given angles are clamped to between `0.`, and [`TAU`].
127
/// This means that a list of stops:
128
/// ```
129
/// # use std::f32::consts::TAU;
130
/// # use bevy_ui::AngularColorStop;
131
/// # use bevy_color::{Color, palettes::css::{RED, BLUE}};
132
/// let stops = [
133
/// AngularColorStop::new(Color::WHITE, 0.),
134
/// AngularColorStop::new(Color::BLACK, -1.),
135
/// AngularColorStop::new(RED, 2. * TAU),
136
/// AngularColorStop::new(BLUE, TAU),
137
/// ];
138
/// ```
139
/// is equivalent to:
140
/// ```
141
/// # use std::f32::consts::TAU;
142
/// # use bevy_ui::AngularColorStop;
143
/// # use bevy_color::{Color, palettes::css::{RED, BLUE}};
144
/// let stops = [
145
/// AngularColorStop::new(Color::WHITE, 0.),
146
/// AngularColorStop::new(Color::BLACK, 0.),
147
/// AngularColorStop::new(RED, TAU),
148
/// AngularColorStop::new(BLUE, TAU),
149
/// ];
150
/// ```
151
/// Resulting in a black to red gradient, not white to blue.
152
pub angle: Option<f32>,
153
/// Normalized angle between this and the following stop of the interpolation midpoint.
154
pub hint: f32,
155
}
156
157
impl AngularColorStop {
158
// Create a new color stop
159
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
/// An angular stop without an explicit angle. The angles of automatic stops
168
/// are interpolated evenly between explicit stops.
169
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
// Set the interpolation midpoint between this and the following stop
178
pub const fn with_hint(mut self, hint: f32) -> Self {
179
self.hint = hint;
180
self
181
}
182
}
183
184
impl 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
194
impl 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
204
impl 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
214
impl 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
/// A linear gradient
225
///
226
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient>
227
#[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
)]
234
pub struct LinearGradient {
235
/// The color space used for interpolation.
236
pub color_space: InterpolationColorSpace,
237
/// The direction of the gradient in radians.
238
/// An angle of `0.` points upward, with the value increasing in the clockwise direction.
239
pub angle: f32,
240
/// The list of color stops
241
pub stops: Vec<ColorStop>,
242
}
243
244
impl LinearGradient {
245
/// Angle of a linear gradient transitioning from bottom to top
246
pub const TO_TOP: f32 = 0.;
247
/// Angle of a linear gradient transitioning from bottom-left to top-right
248
pub const TO_TOP_RIGHT: f32 = TAU / 8.;
249
/// Angle of a linear gradient transitioning from left to right
250
pub const TO_RIGHT: f32 = 2. * Self::TO_TOP_RIGHT;
251
/// Angle of a linear gradient transitioning from top-left to bottom-right
252
pub const TO_BOTTOM_RIGHT: f32 = 3. * Self::TO_TOP_RIGHT;
253
/// Angle of a linear gradient transitioning from top to bottom
254
pub const TO_BOTTOM: f32 = 4. * Self::TO_TOP_RIGHT;
255
/// Angle of a linear gradient transitioning from top-right to bottom-left
256
pub const TO_BOTTOM_LEFT: f32 = 5. * Self::TO_TOP_RIGHT;
257
/// Angle of a linear gradient transitioning from right to left
258
pub const TO_LEFT: f32 = 6. * Self::TO_TOP_RIGHT;
259
/// Angle of a linear gradient transitioning from bottom-right to top-left
260
pub const TO_TOP_LEFT: f32 = 7. * Self::TO_TOP_RIGHT;
261
262
/// Create a new linear gradient
263
pub fn new(angle: f32, stops: Vec<ColorStop>) -> Self {
264
Self {
265
angle,
266
stops,
267
color_space: InterpolationColorSpace::default(),
268
}
269
}
270
271
/// A linear gradient transitioning from bottom to top
272
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
/// A linear gradient transitioning from bottom-left to top-right
281
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
/// A linear gradient transitioning from left to right
290
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
/// A linear gradient transitioning from top-left to bottom-right
299
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
/// A linear gradient transitioning from top to bottom
308
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
/// A linear gradient transitioning from top-right to bottom-left
317
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
/// A linear gradient transitioning from right to left
326
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
/// A linear gradient transitioning from bottom-right to top-left
335
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
/// A linear gradient with the given angle in degrees
344
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
/// A radial gradient
359
///
360
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/radial-gradient>
361
#[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
)]
368
pub struct RadialGradient {
369
/// The color space used for interpolation.
370
pub color_space: InterpolationColorSpace,
371
/// The center of the radial gradient
372
pub position: UiPosition,
373
/// Defines the end shape of the radial gradient
374
pub shape: RadialGradientShape,
375
/// The list of color stops
376
pub stops: Vec<ColorStop>,
377
}
378
379
impl RadialGradient {
380
/// Create a new radial gradient
381
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
396
impl 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
/// A conic gradient
408
///
409
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient>
410
#[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
)]
417
pub struct ConicGradient {
418
/// The color space used for interpolation.
419
pub color_space: InterpolationColorSpace,
420
/// The starting angle of the gradient in radians
421
pub start: f32,
422
/// The center of the conic gradient
423
pub position: UiPosition,
424
/// The list of color stops
425
pub stops: Vec<AngularColorStop>,
426
}
427
428
impl ConicGradient {
429
/// Create a new conic gradient
430
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
/// Sets the starting angle of the gradient in radians
440
pub const fn with_start(mut self, start: f32) -> Self {
441
self.start = start;
442
self
443
}
444
445
/// Sets the position of the gradient
446
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
)]
464
pub enum Gradient {
465
/// A linear gradient
466
///
467
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient>
468
Linear(LinearGradient),
469
/// A radial gradient
470
///
471
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient>
472
Radial(RadialGradient),
473
/// A conic gradient
474
///
475
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/radial-gradient>
476
Conic(ConicGradient),
477
}
478
479
impl Gradient {
480
/// Returns true if the gradient has no stops.
481
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
/// If the gradient has only a single color stop, `get_single` returns its color.
490
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
508
impl From<LinearGradient> for Gradient {
509
fn from(value: LinearGradient) -> Self {
510
Self::Linear(value)
511
}
512
}
513
514
impl From<RadialGradient> for Gradient {
515
fn from(value: RadialGradient) -> Self {
516
Self::Radial(value)
517
}
518
}
519
520
impl 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
)]
533
/// A UI node that displays a gradient
534
pub struct BackgroundGradient(pub Vec<Gradient>);
535
536
impl<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
)]
549
/// A UI node border that displays a gradient
550
pub struct BorderGradient(pub Vec<Gradient>);
551
552
impl<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
)]
565
pub enum RadialGradientShape {
566
/// A circle with radius equal to the distance from its center to the closest side
567
ClosestSide,
568
/// A circle with radius equal to the distance from its center to the farthest side
569
FarthestSide,
570
/// An ellipse with extents equal to the distance from its center to the nearest corner
571
#[default]
572
ClosestCorner,
573
/// An ellipse with extents equal to the distance from its center to the farthest corner
574
FarthestCorner,
575
/// A circle
576
Circle(Val),
577
/// An ellipse
578
Ellipse(Val, Val),
579
}
580
581
const fn close_side(p: f32, h: f32) -> f32 {
582
(-h - p).abs().min((h - p).abs())
583
}
584
585
const fn far_side(p: f32, h: f32) -> f32 {
586
(-h - p).abs().max((h - p).abs())
587
}
588
589
const fn close_side2(p: Vec2, h: Vec2) -> f32 {
590
close_side(p.x, h.x).min(close_side(p.y, h.y))
591
}
592
593
const fn far_side2(p: Vec2, h: Vec2) -> f32 {
594
far_side(p.x, h.x).max(far_side(p.y, h.y))
595
}
596
597
impl RadialGradientShape {
598
/// Resolve the physical dimensions of the end shape of the radial gradient
599
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
/// The color space used for interpolation.
634
#[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
)]
640
pub enum InterpolationColorSpace {
641
/// Interpolates in OKLABA space.
642
#[default]
643
Oklaba,
644
/// Interpolates in OKLCHA space, taking the shortest hue path.
645
Oklcha,
646
/// Interpolates in OKLCHA space, taking the longest hue path.
647
OklchaLong,
648
/// Interpolates in sRGBA space.
649
Srgba,
650
/// Interpolates in linear sRGBA space.
651
LinearRgba,
652
/// Interpolates in HSLA space, taking the shortest hue path.
653
Hsla,
654
/// Interpolates in HSLA space, taking the longest hue path.
655
HslaLong,
656
/// Interpolates in HSVA space, taking the shortest hue path.
657
Hsva,
658
/// Interpolates in HSVA space, taking the longest hue path.
659
HsvaLong,
660
}
661
662
/// Set the color space used for interpolation.
663
pub trait InColorSpace: Sized {
664
/// Interpolate in the given `color_space`.
665
fn in_color_space(self, color_space: InterpolationColorSpace) -> Self;
666
667
/// Interpolate in `OKLab` space.
668
fn in_oklaba(self) -> Self {
669
self.in_color_space(InterpolationColorSpace::Oklaba)
670
}
671
672
/// Interpolate in OKLCH space (short hue path).
673
fn in_oklch(self) -> Self {
674
self.in_color_space(InterpolationColorSpace::Oklcha)
675
}
676
677
/// Interpolate in OKLCH space (long hue path).
678
fn in_oklch_long(self) -> Self {
679
self.in_color_space(InterpolationColorSpace::OklchaLong)
680
}
681
682
/// Interpolate in sRGB space.
683
fn in_srgb(self) -> Self {
684
self.in_color_space(InterpolationColorSpace::Srgba)
685
}
686
687
/// Interpolate in linear sRGB space.
688
fn in_linear_rgb(self) -> Self {
689
self.in_color_space(InterpolationColorSpace::LinearRgba)
690
}
691
}
692
693
impl InColorSpace for LinearGradient {
694
/// Interpolate in the given `color_space`.
695
fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
696
self.color_space = color_space;
697
self
698
}
699
}
700
701
impl InColorSpace for RadialGradient {
702
/// Interpolate in the given `color_space`.
703
fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
704
self.color_space = color_space;
705
self
706
}
707
}
708
709
impl InColorSpace for ConicGradient {
710
/// Interpolate in the given `color_space`.
711
fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
712
self.color_space = color_space;
713
self
714
}
715
}
716
717