1mod render;
2
3use bevy::app::{App, Plugin};
4use bevy::color::{Color, Srgba};
5use bevy::ecs::component::Component;
6use bevy::math::Vec2;
7use bevy::prelude::ReflectDefault;
8use bevy::utils::default;
9use bevy::{reflect::Reflect, ui::Val};
10use core::{f32, f32::consts::TAU};
11use render::{build_gradients_renderer, finish_gradients_renderer};
12
13fn scale_val(val: Val, scale_factor: f32) -> Val {
14 match val {
15 Val::Px(px) => Val::Px(px * scale_factor),
16 _ => val,
17 }
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
21#[reflect(Default, Debug, PartialEq)]
22pub struct Position {
24 pub anchor: Vec2,
26 pub x: Val,
28 pub y: Val,
30}
31
32impl Default for Position {
33 fn default() -> Self {
34 Self::CENTER
35 }
36}
37
38impl Position {
39 pub const fn anchor(anchor: Vec2) -> Self {
41 Self {
42 anchor,
43 x: Val::ZERO,
44 y: Val::ZERO,
45 }
46 }
47
48 pub const TOP_LEFT: Self = Self::anchor(Vec2::new(-0.5, -0.5));
50
51 pub const LEFT: Self = Self::anchor(Vec2::new(-0.5, 0.0));
53
54 pub const BOTTOM_LEFT: Self = Self::anchor(Vec2::new(-0.5, 0.5));
56
57 pub const TOP: Self = Self::anchor(Vec2::new(0.0, -0.5));
59
60 pub const CENTER: Self = Self::anchor(Vec2::new(0.0, 0.0));
62
63 pub const BOTTOM: Self = Self::anchor(Vec2::new(0.0, 0.5));
65
66 pub const TOP_RIGHT: Self = Self::anchor(Vec2::new(0.5, -0.5));
68
69 pub const RIGHT: Self = Self::anchor(Vec2::new(0.5, 0.0));
71
72 pub const BOTTOM_RIGHT: Self = Self::anchor(Vec2::new(0.5, 0.5));
74
75 pub const fn new(anchor: Vec2, x: Val, y: Val) -> Self {
77 Self { anchor, x, y }
78 }
79
80 pub const fn at(self, x: Val, y: Val) -> Self {
82 Self { x, y, ..self }
83 }
84
85 pub const fn at_x(self, x: Val) -> Self {
87 Self { x, ..self }
88 }
89
90 pub const fn at_y(self, y: Val) -> Self {
92 Self { y, ..self }
93 }
94
95 pub const fn at_px(self, x: f32, y: f32) -> Self {
97 self.at(Val::Px(x), Val::Px(y))
98 }
99
100 pub const fn at_percent(self, x: f32, y: f32) -> Self {
102 self.at(Val::Percent(x), Val::Percent(y))
103 }
104
105 pub const fn with_anchor(self, anchor: Vec2) -> Self {
107 Self { anchor, ..self }
108 }
109
110 pub const fn top_left(x: Val, y: Val) -> Self {
112 Self::TOP_LEFT.at(x, y)
113 }
114
115 pub const fn left(x: Val, y: Val) -> Self {
117 Self::LEFT.at(x, y)
118 }
119
120 pub const fn bottom_left(x: Val, y: Val) -> Self {
122 Self::BOTTOM_LEFT.at(x, y)
123 }
124
125 pub const fn top(x: Val, y: Val) -> Self {
127 Self::TOP.at(x, y)
128 }
129
130 pub const fn center(x: Val, y: Val) -> Self {
132 Self::CENTER.at(x, y)
133 }
134
135 pub const fn bottom(x: Val, y: Val) -> Self {
137 Self::BOTTOM.at(x, y)
138 }
139
140 pub const fn top_right(x: Val, y: Val) -> Self {
142 Self::TOP_RIGHT.at(x, y)
143 }
144
145 pub const fn right(x: Val, y: Val) -> Self {
147 Self::RIGHT.at(x, y)
148 }
149
150 pub const fn bottom_right(x: Val, y: Val) -> Self {
152 Self::BOTTOM_RIGHT.at(x, y)
153 }
154
155 pub fn resolve(
157 self,
158 scale_factor: f32,
159 physical_size: Vec2,
160 physical_target_size: Vec2,
161 ) -> Vec2 {
162 let d = self.anchor.map(|p| if 0. < p { -1. } else { 1. });
163
164 physical_size * self.anchor
165 + d * Vec2::new(
166 scale_val(self.x, scale_factor)
167 .resolve(physical_size.x, physical_target_size)
168 .unwrap_or(0.),
169 scale_val(self.y, scale_factor)
170 .resolve(physical_size.y, physical_target_size)
171 .unwrap_or(0.),
172 )
173 }
174}
175
176impl From<Val> for Position {
177 fn from(x: Val) -> Self {
178 Self { x, ..default() }
179 }
180}
181
182impl From<(Val, Val)> for Position {
183 fn from((x, y): (Val, Val)) -> Self {
184 Self { x, y, ..default() }
185 }
186}
187
188#[derive(Debug, Copy, Clone, PartialEq, Reflect)]
190#[reflect(Default, PartialEq, Debug)]
191pub struct ColorStop {
192 pub color: Color,
194 pub point: Val,
197 pub hint: f32,
199}
200
201impl ColorStop {
202 pub fn new(color: impl Into<Color>, point: Val) -> Self {
204 Self {
205 color: color.into(),
206 point,
207 hint: 0.5,
208 }
209 }
210
211 pub fn auto(color: impl Into<Color>) -> Self {
214 Self {
215 color: color.into(),
216 point: Val::Auto,
217 hint: 0.5,
218 }
219 }
220
221 pub fn with_hint(mut self, hint: f32) -> Self {
223 self.hint = hint;
224 self
225 }
226}
227
228impl From<(Color, Val)> for ColorStop {
229 fn from((color, stop): (Color, Val)) -> Self {
230 Self {
231 color,
232 point: stop,
233 hint: 0.5,
234 }
235 }
236}
237
238impl From<Color> for ColorStop {
239 fn from(color: Color) -> Self {
240 Self {
241 color,
242 point: Val::Auto,
243 hint: 0.5,
244 }
245 }
246}
247
248impl From<Srgba> for ColorStop {
249 fn from(color: Srgba) -> Self {
250 Self {
251 color: color.into(),
252 point: Val::Auto,
253 hint: 0.5,
254 }
255 }
256}
257
258impl Default for ColorStop {
259 fn default() -> Self {
260 Self {
261 color: Color::WHITE,
262 point: Val::Auto,
263 hint: 0.5,
264 }
265 }
266}
267
268#[derive(Default, Debug, Copy, Clone, PartialEq, Reflect)]
270#[reflect(Default, PartialEq, Debug)]
271pub struct AngularColorStop {
272 pub color: Color,
274 pub angle: Option<f32>,
298 pub hint: f32,
300}
301
302impl AngularColorStop {
303 pub fn new(color: impl Into<Color>, angle: f32) -> Self {
305 Self {
306 color: color.into(),
307 angle: Some(angle),
308 hint: 0.5,
309 }
310 }
311
312 pub fn auto(color: impl Into<Color>) -> Self {
315 Self {
316 color: color.into(),
317 angle: None,
318 hint: 0.5,
319 }
320 }
321
322 pub fn with_hint(mut self, hint: f32) -> Self {
324 self.hint = hint;
325 self
326 }
327}
328
329#[derive(Clone, PartialEq, Debug, Reflect)]
333#[reflect(PartialEq)]
334pub struct LinearGradient {
335 pub angle: f32,
338 pub stops: Vec<ColorStop>,
340}
341
342impl LinearGradient {
343 pub const TO_TOP: f32 = 0.;
345 pub const TO_TOP_RIGHT: f32 = TAU / 8.;
347 pub const TO_RIGHT: f32 = 2. * Self::TO_TOP_RIGHT;
349 pub const TO_BOTTOM_RIGHT: f32 = 3. * Self::TO_TOP_RIGHT;
351 pub const TO_BOTTOM: f32 = 4. * Self::TO_TOP_RIGHT;
353 pub const TO_BOTTOM_LEFT: f32 = 5. * Self::TO_TOP_RIGHT;
355 pub const TO_LEFT: f32 = 6. * Self::TO_TOP_RIGHT;
357 pub const TO_TOP_LEFT: f32 = 7. * Self::TO_TOP_RIGHT;
359
360 pub fn new(angle: f32, stops: Vec<ColorStop>) -> Self {
362 Self { angle, stops }
363 }
364
365 pub fn to_top(stops: Vec<ColorStop>) -> Self {
367 Self {
368 angle: Self::TO_TOP,
369 stops,
370 }
371 }
372
373 pub fn to_top_right(stops: Vec<ColorStop>) -> Self {
375 Self {
376 angle: Self::TO_TOP_RIGHT,
377 stops,
378 }
379 }
380
381 pub fn to_right(stops: Vec<ColorStop>) -> Self {
383 Self {
384 angle: Self::TO_RIGHT,
385 stops,
386 }
387 }
388
389 pub fn to_bottom_right(stops: Vec<ColorStop>) -> Self {
391 Self {
392 angle: Self::TO_BOTTOM_RIGHT,
393 stops,
394 }
395 }
396
397 pub fn to_bottom(stops: Vec<ColorStop>) -> Self {
399 Self {
400 angle: Self::TO_BOTTOM,
401 stops,
402 }
403 }
404
405 pub fn to_bottom_left(stops: Vec<ColorStop>) -> Self {
407 Self {
408 angle: Self::TO_BOTTOM_LEFT,
409 stops,
410 }
411 }
412
413 pub fn to_left(stops: Vec<ColorStop>) -> Self {
415 Self {
416 angle: Self::TO_LEFT,
417 stops,
418 }
419 }
420
421 pub fn to_top_left(stops: Vec<ColorStop>) -> Self {
423 Self {
424 angle: Self::TO_TOP_LEFT,
425 stops,
426 }
427 }
428
429 pub fn degrees(degrees: f32, stops: Vec<ColorStop>) -> Self {
431 Self {
432 angle: degrees.to_radians(),
433 stops,
434 }
435 }
436}
437
438#[derive(Clone, PartialEq, Debug, Reflect)]
442#[reflect(PartialEq)]
443pub struct RadialGradient {
444 pub position: Position,
446 pub shape: RadialGradientShape,
448 pub stops: Vec<ColorStop>,
450}
451
452impl RadialGradient {
453 pub fn new(position: Position, shape: RadialGradientShape, stops: Vec<ColorStop>) -> Self {
455 Self {
456 position,
457 shape,
458 stops,
459 }
460 }
461}
462
463impl Default for RadialGradient {
464 fn default() -> Self {
465 Self {
466 position: Position::CENTER,
467 shape: RadialGradientShape::ClosestCorner,
468 stops: Vec::new(),
469 }
470 }
471}
472
473#[derive(Clone, PartialEq, Debug, Reflect)]
477#[reflect(PartialEq)]
478pub struct ConicGradient {
479 pub start: f32,
481 pub position: Position,
483 pub stops: Vec<AngularColorStop>,
485}
486
487impl ConicGradient {
488 pub fn new(stops: Vec<AngularColorStop>) -> Self {
490 Self {
491 start: 0.,
492 position: Position::CENTER,
493 stops,
494 }
495 }
496
497 pub fn with_start(mut self, start: f32) -> Self {
499 self.start = start;
500 self
501 }
502
503 pub fn with_position(mut self, position: Position) -> Self {
505 self.position = position;
506 self
507 }
508}
509
510#[derive(Clone, PartialEq, Debug, Reflect)]
511#[reflect(PartialEq)]
512pub enum Gradient {
513 Linear(LinearGradient),
517 Radial(RadialGradient),
521 Conic(ConicGradient),
525}
526
527impl Gradient {
528 pub fn is_empty(&self) -> bool {
530 match self {
531 Gradient::Linear(gradient) => gradient.stops.is_empty(),
532 Gradient::Radial(gradient) => gradient.stops.is_empty(),
533 Gradient::Conic(gradient) => gradient.stops.is_empty(),
534 }
535 }
536
537 pub fn get_single(&self) -> Option<Color> {
539 match self {
540 Gradient::Linear(gradient) => gradient
541 .stops
542 .first()
543 .and_then(|stop| (gradient.stops.len() == 1).then_some(stop.color)),
544 Gradient::Radial(gradient) => gradient
545 .stops
546 .first()
547 .and_then(|stop| (gradient.stops.len() == 1).then_some(stop.color)),
548 Gradient::Conic(gradient) => gradient
549 .stops
550 .first()
551 .and_then(|stop| (gradient.stops.len() == 1).then_some(stop.color)),
552 }
553 }
554}
555
556impl From<LinearGradient> for Gradient {
557 fn from(value: LinearGradient) -> Self {
558 Self::Linear(value)
559 }
560}
561
562impl From<RadialGradient> for Gradient {
563 fn from(value: RadialGradient) -> Self {
564 Self::Radial(value)
565 }
566}
567
568impl From<ConicGradient> for Gradient {
569 fn from(value: ConicGradient) -> Self {
570 Self::Conic(value)
571 }
572}
573
574#[derive(Default, Component, Clone, PartialEq, Debug, Reflect)]
575#[reflect(PartialEq)]
576pub struct BackgroundGradient(pub Vec<Gradient>);
578
579impl<T: Into<Gradient>> From<T> for BackgroundGradient {
580 fn from(value: T) -> Self {
581 Self(vec![value.into()])
582 }
583}
584
585#[derive(Component, Clone, PartialEq, Debug, Reflect)]
586#[reflect(PartialEq)]
587pub struct BorderGradient(pub Vec<Gradient>);
589
590impl<T: Into<Gradient>> From<T> for BorderGradient {
591 fn from(value: T) -> Self {
592 Self(vec![value.into()])
593 }
594}
595
596#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]
597#[reflect(PartialEq, Default)]
598pub enum RadialGradientShape {
599 ClosestSide,
601 FarthestSide,
603 #[default]
605 ClosestCorner,
606 FarthestCorner,
608 Circle(Val),
610 Ellipse(Val, Val),
612}
613
614fn close_side(p: f32, h: f32) -> f32 {
615 (-h - p).abs().min((h - p).abs())
616}
617
618fn far_side(p: f32, h: f32) -> f32 {
619 (-h - p).abs().max((h - p).abs())
620}
621
622fn close_side2(p: Vec2, h: Vec2) -> f32 {
623 close_side(p.x, h.x).min(close_side(p.y, h.y))
624}
625
626fn far_side2(p: Vec2, h: Vec2) -> f32 {
627 far_side(p.x, h.x).max(far_side(p.y, h.y))
628}
629
630impl RadialGradientShape {
631 pub fn resolve(
633 self,
634 position: Vec2,
635 scale_factor: f32,
636 physical_size: Vec2,
637 physical_target_size: Vec2,
638 ) -> Vec2 {
639 let half_size = 0.5 * physical_size;
640 match self {
641 RadialGradientShape::ClosestSide => Vec2::splat(close_side2(position, half_size)),
642 RadialGradientShape::FarthestSide => Vec2::splat(far_side2(position, half_size)),
643 RadialGradientShape::ClosestCorner => Vec2::new(
644 close_side(position.x, half_size.x),
645 close_side(position.y, half_size.y),
646 ),
647 RadialGradientShape::FarthestCorner => Vec2::new(
648 far_side(position.x, half_size.x),
649 far_side(position.y, half_size.y),
650 ),
651 RadialGradientShape::Circle(radius) => Vec2::splat(
652 scale_val(radius, scale_factor)
653 .resolve(physical_size.x, physical_target_size)
654 .unwrap_or(0.),
655 ),
656 RadialGradientShape::Ellipse(x, y) => Vec2::new(
657 scale_val(x, scale_factor)
658 .resolve(physical_size.x, physical_target_size)
659 .unwrap_or(0.),
660 scale_val(y, scale_factor)
661 .resolve(physical_size.y, physical_target_size)
662 .unwrap_or(0.),
663 ),
664 }
665 }
666}
667
668pub struct UiGradientsPlugin;
669
670impl Plugin for UiGradientsPlugin {
671 fn build(&self, app: &mut App) {
672 build_gradients_renderer(app);
673 }
674
675 fn finish(&self, app: &mut App) {
676 finish_gradients_renderer(app);
677 }
678}