summaryrefslogtreecommitdiffstats
path: root/core/src/gradient.rs
blob: 54bb86a4f2e6c5d294613349eb36a13831520877 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! For creating a Gradient.
pub use linear::Linear;

use crate::{Color, Radians};

#[derive(Debug, Clone, Copy, PartialEq)]
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
/// or conically (TBD).
///
/// For a gradient which can be used as a fill on a canvas, see [`iced_graphics::Gradient`].
pub enum Gradient {
    /// A linear gradient interpolates colors along a direction at a specific [`Angle`].
    Linear(Linear),
}

impl Gradient {
    /// Creates a new linear [`linear::Builder`].
    ///
    /// This must be defined by an angle (in [`Degrees`] or [`Radians`])
    /// which will use the bounds of the widget as a guide.
    pub fn linear(angle: impl Into<Radians>) -> linear::Builder {
        linear::Builder::new(angle.into())
    }

    /// Adjust the opacity of the gradient by a multiplier applied to each color stop.
    pub fn mul_alpha(mut self, alpha_multiplier: f32) -> Self {
        match &mut self {
            Gradient::Linear(linear) => {
                for stop in linear.color_stops.iter_mut().flatten() {
                    stop.color.a *= alpha_multiplier;
                }
            }
        }

        self
    }
}

#[derive(Debug, Default, Clone, Copy, PartialEq)]
/// A point along the gradient vector where the specified [`color`] is unmixed.
///
/// [`color`]: Self::color
pub struct ColorStop {
    /// Offset along the gradient vector.
    pub offset: f32,

    /// The color of the gradient at the specified [`offset`].
    ///
    /// [`offset`]: Self::offset
    pub color: Color,
}

pub mod linear {
    //! Linear gradient builder & definition.
    use crate::gradient::{ColorStop, Gradient};
    use crate::{Color, Radians};
    use std::cmp::Ordering;

    /// A linear gradient that can be used as a [`Background`].
    #[derive(Debug, Clone, Copy, PartialEq)]
    pub struct Linear {
        /// How the [`Gradient`] is angled within its bounds.
        pub angle: Radians,
        /// [`ColorStop`]s along the linear gradient path.
        pub color_stops: [Option<ColorStop>; 8],
    }

    /// A [`Linear`] builder.
    #[derive(Debug)]
    pub struct Builder {
        angle: Radians,
        stops: [Option<ColorStop>; 8],
    }

    impl Builder {
        /// Creates a new [`Builder`].
        pub fn new(angle: Radians) -> Self {
            Self {
                angle,
                stops: [None; 8],
            }
        }

        /// Adds a new [`ColorStop`], defined by an offset and a color, to the gradient.
        ///
        /// Any `offset` that is not within `0.0..=1.0` will be silently ignored.
        ///
        /// Any stop added after the 8th will be silently ignored.
        pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
            if offset.is_finite() && (0.0..=1.0).contains(&offset) {
                let (Ok(index) | Err(index)) =
                    self.stops.binary_search_by(|stop| match stop {
                        None => Ordering::Greater,
                        Some(stop) => stop.offset.partial_cmp(&offset).unwrap(),
                    });

                if index < 8 {
                    self.stops[index] = Some(ColorStop { offset, color });
                }
            } else {
                log::warn!(
                    "Gradient color stop must be within 0.0..=1.0 range."
                );
            };

            self
        }

        /// Adds multiple [`ColorStop`]s to the gradient.
        ///
        /// Any stop added after the 8th will be silently ignored.
        pub fn add_stops(
            mut self,
            stops: impl IntoIterator<Item = ColorStop>,
        ) -> Self {
            for stop in stops.into_iter() {
                self = self.add_stop(stop.offset, stop.color)
            }

            self
        }

        /// Builds the linear [`Gradient`] of this [`Builder`].
        ///
        /// Returns `BuilderError` if gradient in invalid.
        pub fn build(self) -> Gradient {
            Gradient::Linear(Linear {
                angle: self.angle,
                color_stops: self.stops,
            })
        }
    }
}