summaryrefslogtreecommitdiffstats
path: root/graphics/src/gradient/linear.rs
blob: 439e848e37ec0ee92afee35d83c680cd4fc55681 (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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! Linear gradient builder & definition.
use crate::gradient::{ColorStop, Gradient};
use crate::{Color, Point, Size};

/// A linear gradient that can be used in the style of [`super::Fill`] or [`super::Stroke`].
#[derive(Debug, Clone, PartialEq)]
pub struct Linear {
    /// The point where the linear gradient begins.
    pub start: Point,
    /// The point where the linear gradient ends.
    pub end: Point,
    /// [`ColorStop`]s along the linear gradient path.
    pub color_stops: Vec<ColorStop>,
}

#[derive(Debug)]
/// The position of the gradient within its bounds.
pub enum Position {
    /// The gradient will be positioned with respect to two points.
    Absolute {
        /// The starting point of the gradient.
        start: Point,
        /// The ending point of the gradient.
        end: Point,
    },
    /// The gradient will be positioned relative to the provided bounds.
    Relative {
        /// The top left position of the bounds.
        top_left: Point,
        /// The width & height of the bounds.
        size: Size,
        /// The start [Location] of the gradient.
        start: Location,
        /// The end [Location] of the gradient.
        end: Location,
    },
}

impl From<(Point, Point)> for Position {
    fn from((start, end): (Point, Point)) -> Self {
        Self::Absolute { start, end }
    }
}

#[derive(Debug)]
/// The location of a relatively-positioned gradient.
pub enum Location {
    /// Top left.
    TopLeft,
    /// Top.
    Top,
    /// Top right.
    TopRight,
    /// Right.
    Right,
    /// Bottom right.
    BottomRight,
    /// Bottom.
    Bottom,
    /// Bottom left.
    BottomLeft,
    /// Left.
    Left,
}

impl Location {
    fn to_absolute(&self, top_left: Point, size: Size) -> Point {
        match self {
            Location::TopLeft => top_left,
            Location::Top => {
                Point::new(top_left.x + size.width / 2.0, top_left.y)
            }
            Location::TopRight => {
                Point::new(top_left.x + size.width, top_left.y)
            }
            Location::Right => Point::new(
                top_left.x + size.width,
                top_left.y + size.height / 2.0,
            ),
            Location::BottomRight => {
                Point::new(top_left.x + size.width, top_left.y + size.height)
            }
            Location::Bottom => Point::new(
                top_left.x + size.width / 2.0,
                top_left.y + size.height,
            ),
            Location::BottomLeft => {
                Point::new(top_left.x, top_left.y + size.height)
            }
            Location::Left => {
                Point::new(top_left.x, top_left.y + size.height / 2.0)
            }
        }
    }
}

/// A [`Linear`] builder.
#[derive(Debug)]
pub struct Builder {
    start: Point,
    end: Point,
    stops: Vec<ColorStop>,
    error: Option<BuilderError>,
}

impl Builder {
    /// Creates a new [`Builder`].
    pub fn new(position: Position) -> Self {
        let (start, end) = match position {
            Position::Absolute { start, end } => (start, end),
            Position::Relative {
                top_left,
                size,
                start,
                end,
            } => (
                start.to_absolute(top_left, size),
                end.to_absolute(top_left, size),
            ),
        };

        Self {
            start,
            end,
            stops: vec![],
            error: None,
        }
    }

    /// Adds a new stop, defined by an offset and a color, to the gradient.
    ///
    /// `offset` must be between `0.0` and `1.0` or the gradient cannot be built.
    ///
    /// Note: when using the [`glow`] backend, any color stop added after the 16th
    /// will not be displayed.
    ///
    /// On the [`wgpu`] backend this limitation does not exist (technical limit is 524,288 stops).
    ///
    /// [`glow`]: https://docs.rs/iced_glow
    /// [`wgpu`]: https://docs.rs/iced_wgpu
    pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
        if offset.is_finite() && (0.0..=1.0).contains(&offset) {
            match self.stops.binary_search_by(|stop| {
                stop.offset.partial_cmp(&offset).unwrap()
            }) {
                Ok(_) => {
                    self.error = Some(BuilderError::DuplicateOffset(offset))
                }
                Err(index) => {
                    self.stops.insert(index, ColorStop { offset, color });
                }
            }
        } else {
            self.error = Some(BuilderError::InvalidOffset(offset))
        };

        self
    }

    /// Builds the linear [`Gradient`] of this [`Builder`].
    ///
    /// Returns `BuilderError` if gradient in invalid.
    pub fn build(self) -> Result<Gradient, BuilderError> {
        if self.stops.is_empty() {
            Err(BuilderError::MissingColorStop)
        } else if let Some(error) = self.error {
            Err(error)
        } else {
            Ok(Gradient::Linear(Linear {
                start: self.start,
                end: self.end,
                color_stops: self.stops,
            }))
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub enum BuilderError {
    #[error("Gradients must contain at least one color stop.")]
    /// Gradients must contain at least one color stop.
    MissingColorStop,
    #[error("Offset {0} must be a unique, finite number.")]
    /// Offsets in a gradient must all be unique & finite.
    DuplicateOffset(f32),
    #[error("Offset {0} must be between 0.0..=1.0.")]
    /// Offsets in a gradient must be between 0.0..=1.0.
    InvalidOffset(f32),
}