Skip to main content

sequoia_view/
image.rs

1use std::sync::Arc;
2
3use sequoia_assets::AssetRegistry;
4use sequoia_core::{ImageSource, Length, Rect, Size};
5use sequoia_layout::{Indents, LayoutCtx, LayoutStyle};
6
7use crate::view::{PaintCtx, View};
8use crate::views::Unit;
9
10pub struct Image {
11    source: ImageSource,
12    style: LayoutStyle,
13}
14
15impl Image {
16    pub fn file(path: impl Into<String>) -> Self {
17        Self {
18            source: ImageSource::File(path.into()),
19            style: LayoutStyle {
20                width: Length::Fill(1.0),
21                height: Length::Fill(1.0),
22                ..LayoutStyle::default()
23            },
24        }
25    }
26
27    pub fn rgba(id: u64, width: u32, height: u32, data: Vec<u8>) -> Self {
28        Self {
29            source: ImageSource::Rgba {
30                id,
31                width: width.max(1),
32                height: height.max(1),
33                data: Arc::new(data),
34            },
35            style: LayoutStyle {
36                width: Length::Fill(1.0),
37                height: Length::Fill(1.0),
38                ..LayoutStyle::default()
39            },
40        }
41    }
42
43    pub fn width(mut self, width: impl Into<Length>) -> Self {
44        self.style = self.style.width(width.into());
45        self
46    }
47
48    pub fn height(mut self, height: impl Into<Length>) -> Self {
49        self.style = self.style.height(height.into());
50        self
51    }
52
53    pub fn min_width(mut self, v: f32) -> Self {
54        self.style = self.style.min_width(v);
55        self
56    }
57
58    pub fn max_width(mut self, v: f32) -> Self {
59        self.style = self.style.max_width(v);
60        self
61    }
62
63    pub fn min_height(mut self, v: f32) -> Self {
64        self.style = self.style.min_height(v);
65        self
66    }
67
68    pub fn max_height(mut self, v: f32) -> Self {
69        self.style = self.style.max_height(v);
70        self
71    }
72
73    pub fn flex(mut self, weight: f32) -> Self {
74        self.style = self.style.flex(weight);
75        self
76    }
77
78    pub fn expand(self) -> Self {
79        self.flex(1.0)
80    }
81
82    pub fn fill_width(mut self) -> Self {
83        self.style = self.style.width(Length::Fill(1.0));
84        self
85    }
86
87    pub fn fill_height(mut self) -> Self {
88        self.style = self.style.height(Length::Fill(1.0));
89        self
90    }
91
92    pub fn fill_size(self) -> Self {
93        self.fill_width().fill_height()
94    }
95
96    pub fn margin(mut self, margin: impl Into<sequoia_core::Edges>) -> Self {
97        self.style = self.style.margin(margin);
98        self
99    }
100
101    pub fn margin_xy(mut self, x: f32, y: f32) -> Self {
102        self.style = self.style.margin_xy(x, y);
103        self
104    }
105
106    pub fn margin_x(mut self, x: f32) -> Self {
107        self.style = self.style.margin_x(x);
108        self
109    }
110
111    pub fn margin_y(mut self, y: f32) -> Self {
112        self.style = self.style.margin_y(y);
113        self
114    }
115
116    pub fn margin_left(mut self, v: f32) -> Self {
117        self.style = self.style.margin_left(v);
118        self
119    }
120
121    pub fn margin_right(mut self, v: f32) -> Self {
122        self.style = self.style.margin_right(v);
123        self
124    }
125
126    pub fn margin_top(mut self, v: f32) -> Self {
127        self.style = self.style.margin_top(v);
128        self
129    }
130
131    pub fn margin_bottom(mut self, v: f32) -> Self {
132        self.style = self.style.margin_bottom(v);
133        self
134    }
135
136    pub fn indent_top(mut self, v: f32) -> Self {
137        self.style = self.style.indent_top(v);
138        self
139    }
140
141    pub fn indent_bottom(mut self, v: f32) -> Self {
142        self.style = self.style.indent_bottom(v);
143        self
144    }
145
146    pub fn indent_left(mut self, v: f32) -> Self {
147        self.style = self.style.indent_left(v);
148        self
149    }
150
151    pub fn indent_right(mut self, v: f32) -> Self {
152        self.style = self.style.indent_right(v);
153        self
154    }
155
156    pub fn clear_indents(mut self) -> Self {
157        self.style = self.style.clear_indents();
158        self
159    }
160
161    pub fn indent(mut self, indents: Indents) -> Self {
162        self.style = self.style.indent(indents);
163        self
164    }
165
166    pub fn indent_all(mut self, v: f32) -> Self {
167        self.style = self.style.indent_all(v);
168        self
169    }
170
171    pub fn indent_xy(mut self, x: f32, y: f32) -> Self {
172        self.style = self.style.indent_xy(x, y);
173        self
174    }
175
176    pub fn indent_axes(mut self, horizontal: f32, vertical: f32) -> Self {
177        self.style = self.style.indent_axes(horizontal, vertical);
178        self
179    }
180
181    pub fn dynamic(id: u64, width: u32, height: u32, data: Vec<u8>, version: u64) -> Self {
182        Self {
183            source: ImageSource::Dynamic {
184                id,
185                width: width.max(1),
186                height: height.max(1),
187                data: Arc::new(data),
188                version,
189            },
190            style: LayoutStyle {
191                width: Length::Fill(1.0),
192                height: Length::Fill(1.0),
193                ..LayoutStyle::default()
194            },
195        }
196    }
197
198    #[allow(unreachable_patterns)]
199    fn aspect(&self, ctx: &PaintCtx) -> Option<(f32, f32)> {
200        match &self.source {
201            ImageSource::Rgba { width, height, .. }
202            | ImageSource::Dynamic { width, height, .. } => {
203                if *width > 0 && *height > 0 {
204                    Some((*width as f32, *height as f32))
205                } else {
206                    None
207                }
208            }
209            ImageSource::File(path) => ctx
210                .environment
211                .get::<AssetRegistry>()
212                .and_then(|assets| assets.photo_size(path).ok())
213                .filter(|(w, h)| *w > 0 && *h > 0)
214                .map(|(w, h)| (w as f32, h as f32)),
215
216            _ => None,
217        }
218    }
219}
220
221fn contain_fit(outer: Rect, aspect: (f32, f32)) -> Rect {
222    let (aw, ah) = aspect;
223    if aw <= 0.0 || ah <= 0.0 || outer.size.width <= 0.0 || outer.size.height <= 0.0 {
224        return outer;
225    }
226    let scale = (outer.size.width / aw).min(outer.size.height / ah);
227    let w = aw * scale;
228    let h = ah * scale;
229    Rect::from_xywh(
230        outer.left() + (outer.size.width - w) * 0.5,
231        outer.top() + (outer.size.height - h) * 0.5,
232        w,
233        h,
234    )
235}
236
237impl View for Image {
238    fn body(&self) -> impl View + 'static {
239        Unit
240    }
241    fn is_leaf(&self) -> bool {
242        true
243    }
244    fn layout_style(&self) -> LayoutStyle {
245        self.style
246    }
247
248    #[allow(unreachable_patterns)]
249    fn intrinsic_size(&self, _ctx: &mut LayoutCtx) -> Option<Size> {
250        match &self.source {
251            ImageSource::Rgba { width, height, .. }
252            | ImageSource::Dynamic { width, height, .. } => {
253                Some(Size::new(*width as f32, *height as f32))
254            }
255            ImageSource::File(_) => None,
256
257            _ => None,
258        }
259    }
260    fn paint_self(&self, ctx: &mut PaintCtx) {
261        let rect = ctx.rect;
262        if rect.size.width <= 0.0 || rect.size.height <= 0.0 {
263            return;
264        }
265        let dst = self
266            .aspect(ctx)
267            .map(|a| contain_fit(rect, a))
268            .unwrap_or(rect);
269        if dst.is_empty() {
270            return;
271        }
272        ctx.image(dst, [0.0, 0.0, 1.0, 1.0], self.source.clone());
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn contain_fit_centers_without_distortion() {
282        let outer = Rect::from_xywh(0.0, 0.0, 100.0, 100.0);
283
284        let r = contain_fit(outer, (40.0, 30.0));
285        assert_eq!((r.size.width, r.size.height), (100.0, 75.0));
286        assert_eq!((r.left(), r.top()), (0.0, 12.5));
287
288        let r = contain_fit(outer, (30.0, 40.0));
289        assert_eq!((r.size.width, r.size.height), (75.0, 100.0));
290        assert_eq!((r.left(), r.top()), (12.5, 0.0));
291    }
292
293    #[test]
294    fn contain_fit_degenerate_falls_back_to_outer() {
295        let outer = Rect::from_xywh(1.0, 2.0, 10.0, 10.0);
296        assert_eq!(contain_fit(outer, (0.0, 5.0)), outer);
297        assert_eq!(contain_fit(Rect::ZERO, (4.0, 3.0)), Rect::ZERO);
298    }
299}