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}