1use inset_embedder::{Offset, Size, TextDirection};
11use inset_foundation::App;
12use inset_painting::{Alignment, BoxFit, ImageInfo, ImagePaintStyle, ImageRepeat, paint_image};
13
14use crate::box_::{BoxConstraints, RenderBox, RenderBoxData};
15use crate::object::{AnyRenderObject, RenderHandle, RenderObject, RenderObjectData};
16use crate::painting_context::PaintingContext;
17use inset_painting::ClipContext;
18
19pub struct RenderImage {
21 render_object: RenderObjectData,
22 render_box: RenderBoxData,
23 image: Option<ImageInfo>,
24 width: Option<f64>,
25 height: Option<f64>,
26 opacity: f64,
27 fit: Option<BoxFit>,
28 alignment: Alignment,
29 repeat: ImageRepeat,
30 match_text_direction: bool,
31 text_direction: Option<TextDirection>,
32}
33
34impl RenderImage {
35 pub fn new(app: &mut App) -> RenderHandle<RenderImage> {
37 RenderHandle::new_box(
38 app,
39 RenderImage {
40 render_object: RenderObjectData::new(),
41 render_box: RenderBoxData::new(),
42 image: None,
43 width: None,
44 height: None,
45 opacity: 1.0,
46 fit: None,
47 alignment: Alignment::CENTER,
48 repeat: ImageRepeat::NoRepeat,
49 match_text_direction: false,
50 text_direction: None,
51 },
52 )
53 }
54
55 pub fn image(self: RenderHandle<Self>, app: &App) -> Option<ImageInfo> {
57 self.get(app).image.clone()
58 }
59
60 pub fn set_image(self: RenderHandle<Self>, app: &mut App, value: Option<ImageInfo>) {
65 if self.get(app).image == value {
66 return;
67 }
68 let follows_the_image = self.get(app).width.is_none() || self.get(app).height.is_none();
69 self.get_mut(app).image = value;
70 self.mark_needs_paint(app);
71 if follows_the_image {
72 self.mark_needs_layout(app);
73 }
74 }
75
76 pub fn width(self: RenderHandle<Self>, app: &App) -> Option<f64> {
78 self.get(app).width
79 }
80
81 pub fn set_width(self: RenderHandle<Self>, app: &mut App, value: Option<f64>) {
83 if self.get(app).width == value {
84 return;
85 }
86 self.get_mut(app).width = value;
87 self.mark_needs_layout(app);
88 }
89
90 pub fn height(self: RenderHandle<Self>, app: &App) -> Option<f64> {
92 self.get(app).height
93 }
94
95 pub fn set_height(self: RenderHandle<Self>, app: &mut App, value: Option<f64>) {
97 if self.get(app).height == value {
98 return;
99 }
100 self.get_mut(app).height = value;
101 self.mark_needs_layout(app);
102 }
103
104 pub fn opacity(self: RenderHandle<Self>, app: &App) -> f64 {
106 self.get(app).opacity
107 }
108
109 pub fn set_opacity(self: RenderHandle<Self>, app: &mut App, value: f64) {
111 if self.get(app).opacity == value {
112 return;
113 }
114 self.get_mut(app).opacity = value;
115 self.mark_needs_paint(app);
116 }
117
118 pub fn fit(self: RenderHandle<Self>, app: &App) -> Option<BoxFit> {
120 self.get(app).fit
121 }
122
123 pub fn set_fit(self: RenderHandle<Self>, app: &mut App, value: Option<BoxFit>) {
125 if self.get(app).fit == value {
126 return;
127 }
128 self.get_mut(app).fit = value;
129 self.mark_needs_paint(app);
130 }
131
132 pub fn alignment(self: RenderHandle<Self>, app: &App) -> Alignment {
134 self.get(app).alignment
135 }
136
137 pub fn set_alignment(self: RenderHandle<Self>, app: &mut App, value: Alignment) {
139 if self.get(app).alignment == value {
140 return;
141 }
142 self.get_mut(app).alignment = value;
143 self.mark_needs_paint(app);
144 }
145
146 pub fn repeat(self: RenderHandle<Self>, app: &App) -> ImageRepeat {
148 self.get(app).repeat
149 }
150
151 pub fn set_repeat(self: RenderHandle<Self>, app: &mut App, value: ImageRepeat) {
153 if self.get(app).repeat == value {
154 return;
155 }
156 self.get_mut(app).repeat = value;
157 self.mark_needs_paint(app);
158 }
159
160 pub fn match_text_direction(self: RenderHandle<Self>, app: &App) -> bool {
162 self.get(app).match_text_direction
163 }
164
165 pub fn set_match_text_direction(self: RenderHandle<Self>, app: &mut App, value: bool) {
167 if self.get(app).match_text_direction == value {
168 return;
169 }
170 self.get_mut(app).match_text_direction = value;
171 self.mark_needs_paint(app);
172 }
173
174 pub fn text_direction(self: RenderHandle<Self>, app: &App) -> Option<TextDirection> {
177 self.get(app).text_direction
178 }
179
180 pub fn set_text_direction(
182 self: RenderHandle<Self>,
183 app: &mut App,
184 value: Option<TextDirection>,
185 ) {
186 if self.get(app).text_direction == value {
187 return;
188 }
189 self.get_mut(app).text_direction = value;
190 self.mark_needs_paint(app);
191 }
192
193 fn size_for_constraints(
199 self: RenderHandle<Self>,
200 app: &App,
201 constraints: BoxConstraints,
202 ) -> Size {
203 let held = self.get(app);
204 let constraints = BoxConstraints::tight_for(held.width, held.height).enforce(constraints);
205 let Some(image) = &held.image else {
206 return constraints.smallest();
207 };
208 constraints.constrain_size_and_attempt_to_preserve_aspect_ratio(image.size())
209 }
210
211 fn paint_style(self: RenderHandle<Self>, app: &App) -> ImagePaintStyle {
213 let held = self.get(app);
214 let scale = held.image.as_ref().map_or(1.0, |image| image.scale);
215 ImagePaintStyle {
216 scale,
217 opacity: held.opacity,
218 fit: held.fit,
219 alignment: held.alignment,
220 repeat: held.repeat,
221 flip_horizontally: held.match_text_direction
222 && held.text_direction == Some(TextDirection::Rtl),
223 }
224 }
225}
226
227impl RenderObject for RenderImage {
228 crate::render_object_accessors!();
229
230 fn visit_children(
231 self: RenderHandle<Self>,
232 _app: &App,
233 _visitor: &mut dyn FnMut(AnyRenderObject),
234 ) {
235 }
237
238 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
239 let constraints = self.constraints(app);
240 let size = self.size_for_constraints(app, constraints);
241 self.set_size(app, size);
242 }
243
244 fn paint(
245 self: RenderHandle<Self>,
246 app: &mut App,
247 context: &mut PaintingContext,
248 offset: Offset,
249 ) {
250 let Some(image) = self.get(app).image.clone() else {
251 return;
252 };
253 let size = self.size(app);
254 let style = self.paint_style(app);
255 paint_image(context.canvas(), offset & size, &image.image, &style);
256 }
257}
258
259impl RenderBox for RenderImage {
260 crate::render_box_accessors!();
261
262 fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
265 self.compute_max_intrinsic_width(app, height)
266 }
267
268 fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
269 debug_assert!(height >= 0.0);
270 if self.get(app).width.is_none() && self.get(app).height.is_none() {
271 return 0.0;
272 }
273 self.size_for_constraints(app, BoxConstraints::tight_for(None, Some(height)))
274 .width()
275 }
276
277 fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
278 self.compute_max_intrinsic_height(app, width)
279 }
280
281 fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
282 debug_assert!(width >= 0.0);
283 if self.get(app).width.is_none() && self.get(app).height.is_none() {
284 return 0.0;
285 }
286 self.size_for_constraints(app, BoxConstraints::tight_for(Some(width), None))
287 .height()
288 }
289
290 fn compute_dry_layout(
291 self: RenderHandle<Self>,
292 app: &mut App,
293 constraints: BoxConstraints,
294 ) -> Size {
295 self.size_for_constraints(app, constraints)
296 }
297
298 fn hit_test_self(self: RenderHandle<Self>, _app: &App, _position: Offset) -> bool {
300 true
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use inset_embedder::test_support::solid_image;
307 use inset_foundation::AppCell;
308
309 use super::*;
310
311 fn wide_image(scale: f64) -> ImageInfo {
313 ImageInfo::new(solid_image([40, 20], [255, 0, 0, 255])).scale(scale)
314 }
315
316 fn loose(width: f64, height: f64) -> BoxConstraints {
317 BoxConstraints::loose(Size::new(width, height))
318 }
319
320 #[test]
321 fn an_image_that_has_not_arrived_takes_no_room() {
322 let cell = AppCell::new();
323 let mut app = cell.borrow_mut();
324 let render = RenderImage::new(&mut app);
325 assert_eq!(
326 render.compute_dry_layout(&mut app, loose(100.0, 100.0)),
327 Size::new(0.0, 0.0)
328 );
329 }
330
331 #[test]
332 fn an_image_with_no_size_given_takes_its_own() {
333 let cell = AppCell::new();
334 let mut app = cell.borrow_mut();
335 let render = RenderImage::new(&mut app);
336 render.set_image(&mut app, Some(wide_image(1.0)));
337 assert_eq!(
338 render.compute_dry_layout(&mut app, loose(1000.0, 1000.0)),
339 Size::new(40.0, 20.0)
340 );
341 }
342
343 #[test]
344 fn a_scaled_image_takes_fewer_points_than_it_has_pixels() {
345 let cell = AppCell::new();
346 let mut app = cell.borrow_mut();
347 let render = RenderImage::new(&mut app);
348 render.set_image(&mut app, Some(wide_image(2.0)));
350 assert_eq!(
351 render.compute_dry_layout(&mut app, loose(1000.0, 1000.0)),
352 Size::new(20.0, 10.0)
353 );
354 }
355
356 #[test]
357 fn one_given_dimension_derives_the_other_from_the_proportions() {
358 let cell = AppCell::new();
359 let mut app = cell.borrow_mut();
360 let render = RenderImage::new(&mut app);
361 render.set_image(&mut app, Some(wide_image(1.0)));
362
363 render.set_width(&mut app, Some(80.0));
364 assert_eq!(
365 render.compute_dry_layout(&mut app, loose(1000.0, 1000.0)),
366 Size::new(80.0, 40.0)
367 );
368
369 render.set_width(&mut app, None);
370 render.set_height(&mut app, Some(5.0));
371 assert_eq!(
372 render.compute_dry_layout(&mut app, loose(1000.0, 1000.0)),
373 Size::new(10.0, 5.0)
374 );
375 }
376
377 #[test]
378 fn both_dimensions_given_make_the_size_exact() {
379 let cell = AppCell::new();
380 let mut app = cell.borrow_mut();
381 let render = RenderImage::new(&mut app);
382 render.set_image(&mut app, Some(wide_image(1.0)));
383 render.set_width(&mut app, Some(7.0));
384 render.set_height(&mut app, Some(9.0));
385 assert_eq!(
386 render.compute_dry_layout(&mut app, loose(1000.0, 1000.0)),
387 Size::new(7.0, 9.0)
388 );
389 }
390
391 #[test]
392 fn a_box_too_small_for_the_image_constrains_it() {
393 let cell = AppCell::new();
394 let mut app = cell.borrow_mut();
395 let render = RenderImage::new(&mut app);
396 render.set_image(&mut app, Some(wide_image(1.0)));
397 assert_eq!(
399 render.compute_dry_layout(&mut app, loose(20.0, 1000.0)),
400 Size::new(20.0, 10.0)
401 );
402 }
403
404 #[test]
405 fn an_image_with_no_dimension_given_reports_no_intrinsic_size() {
406 let cell = AppCell::new();
407 let mut app = cell.borrow_mut();
408 let render = RenderImage::new(&mut app);
409 render.set_image(&mut app, Some(wide_image(1.0)));
410 assert_eq!(render.compute_max_intrinsic_width(&mut app, 100.0), 0.0);
412 assert_eq!(render.compute_max_intrinsic_height(&mut app, 100.0), 0.0);
413
414 render.set_width(&mut app, Some(80.0));
415 assert_eq!(render.compute_max_intrinsic_height(&mut app, 80.0), 40.0);
416 }
417}