1use std::mem;
4
5use zng_ext_image::{IMAGES, ImageCacheMode, ImageRenderArgs};
6use zng_wgt_stack::stack_nodes;
7
8use super::image_properties::{
9 IMAGE_ALIGN_VAR, IMAGE_CACHE_VAR, IMAGE_CROP_VAR, IMAGE_DOWNSCALE_VAR, IMAGE_ERROR_FN_VAR, IMAGE_FIT_VAR, IMAGE_LIMITS_VAR,
10 IMAGE_LOADING_FN_VAR, IMAGE_OFFSET_VAR, IMAGE_RENDERING_VAR, IMAGE_SCALE_DENSITY_VAR, IMAGE_SCALE_FACTOR_VAR, IMAGE_SCALE_VAR,
11 ImageFit, ImgErrorArgs, ImgLoadingArgs,
12};
13use super::*;
14
15context_var! {
16 pub static CONTEXT_IMAGE_VAR: Img = no_context_image();
20}
21fn no_context_image() -> Img {
22 Img::dummy(Some(Txt::from_static("no image source in context")))
23}
24
25pub fn image_source(child: impl IntoUiNode, source: impl IntoVar<ImageSource>) -> UiNode {
36 let source = source.into_var();
37 let ctx_img = var(Img::dummy(None));
38 let child = with_context_var(child, CONTEXT_IMAGE_VAR, ctx_img.read_only());
39 let mut img = var(Img::dummy(None)).read_only();
40 let mut _ctx_binding = None;
41
42 match_node(child, move |child, op| match op {
43 UiNodeOp::Init => {
44 WIDGET.sub_var(&source).sub_var(&IMAGE_CACHE_VAR).sub_var(&IMAGE_DOWNSCALE_VAR);
45
46 let mode = if IMAGE_CACHE_VAR.get() {
47 ImageCacheMode::Cache
48 } else {
49 ImageCacheMode::Ignore
50 };
51 let limits = IMAGE_LIMITS_VAR.get();
52 let downscale = IMAGE_DOWNSCALE_VAR.get();
53
54 let mut source = source.get();
55 if let ImageSource::Render(_, args) = &mut source {
56 *args = Some(ImageRenderArgs::new(WINDOW.id()));
57 }
58 img = IMAGES.image(source, mode, limits, downscale, None);
59
60 ctx_img.set_from(&img);
61 _ctx_binding = Some(img.bind(&ctx_img));
62 }
63 UiNodeOp::Deinit => {
64 child.deinit();
65
66 ctx_img.set(no_context_image());
67 img = var(no_context_image()).read_only();
68 _ctx_binding = None;
69 }
70 UiNodeOp::Update { .. } => {
71 if source.is_new() || IMAGE_DOWNSCALE_VAR.is_new() {
72 let mut source = source.get();
75
76 if let ImageSource::Render(_, args) = &mut source {
77 *args = Some(ImageRenderArgs::new(WINDOW.id()));
78 }
79
80 let mode = if IMAGE_CACHE_VAR.get() {
81 ImageCacheMode::Cache
82 } else {
83 ImageCacheMode::Ignore
84 };
85 let limits = IMAGE_LIMITS_VAR.get();
86 let downscale = IMAGE_DOWNSCALE_VAR.get();
87
88 img = IMAGES.image(source, mode, limits, downscale, None);
89
90 ctx_img.set_from(&img);
91 _ctx_binding = Some(img.bind(&ctx_img));
92 } else if let Some(enabled) = IMAGE_CACHE_VAR.get_new() {
93 let is_cached = ctx_img.with(|img| IMAGES.is_cached(img));
95 if enabled != is_cached {
96 img = if is_cached {
97 let img = mem::replace(&mut img, var(Img::dummy(None)).read_only());
100 IMAGES.detach(img)
101 } else {
102 let source = source.get();
105 let limits = IMAGE_LIMITS_VAR.get();
106 let downscale = IMAGE_DOWNSCALE_VAR.get();
107 IMAGES.image(source, ImageCacheMode::Cache, limits, downscale, None)
108 };
109
110 ctx_img.set_from(&img);
111 _ctx_binding = Some(img.bind(&ctx_img));
112 }
113 }
114 }
115 _ => {}
116 })
117}
118
119context_local! {
120 static IN_ERROR_VIEW: bool = false;
122 static IN_LOADING_VIEW: bool = false;
124}
125
126pub fn image_error_presenter(child: impl IntoUiNode) -> UiNode {
132 let view = CONTEXT_IMAGE_VAR
133 .map(|i| i.error().map(|e| ImgErrorArgs { error: e }))
134 .present_opt(IMAGE_ERROR_FN_VAR.map(|f| {
135 wgt_fn!(f, |e| {
136 if IN_ERROR_VIEW.get_clone() {
137 UiNode::nil()
138 } else {
139 with_context_local(f(e), &IN_ERROR_VIEW, true)
140 }
141 })
142 }));
143
144 stack_nodes(ui_vec![view, child], 1, |constraints, _, img_size| {
145 if img_size == PxSize::zero() {
146 constraints
147 } else {
148 PxConstraints2d::new_fill_size(img_size)
149 }
150 })
151}
152
153pub fn image_loading_presenter(child: impl IntoUiNode) -> UiNode {
159 let view = CONTEXT_IMAGE_VAR
160 .map(|i| if i.is_loading() { Some(ImgLoadingArgs {}) } else { None })
161 .present_opt(IMAGE_LOADING_FN_VAR.map(|f| {
162 wgt_fn!(f, |a| {
163 if IN_LOADING_VIEW.get_clone() {
164 UiNode::nil()
165 } else {
166 with_context_local(f(a), &IN_LOADING_VIEW, true)
167 }
168 })
169 }));
170
171 stack_nodes(ui_vec![view, child], 1, |constraints, _, img_size| {
172 if img_size == PxSize::zero() {
173 constraints
174 } else {
175 PxConstraints2d::new_fill_size(img_size)
176 }
177 })
178}
179
180pub fn image_presenter() -> UiNode {
194 let mut img_size = PxSize::zero();
195 let mut render_clip = PxRect::zero();
196 let mut render_img_size = PxSize::zero();
197 let mut render_tile_size = PxSize::zero();
198 let mut render_tile_spacing = PxSize::zero();
199 let mut render_offset = PxVector::zero();
200 let spatial_id = SpatialFrameId::new_unique();
201
202 match_node_leaf(move |op| match op {
203 UiNodeOp::Init => {
204 WIDGET
205 .sub_var(&CONTEXT_IMAGE_VAR)
206 .sub_var_layout(&IMAGE_CROP_VAR)
207 .sub_var_layout(&IMAGE_SCALE_DENSITY_VAR)
208 .sub_var_layout(&IMAGE_SCALE_FACTOR_VAR)
209 .sub_var_layout(&IMAGE_SCALE_VAR)
210 .sub_var_layout(&IMAGE_FIT_VAR)
211 .sub_var_layout(&IMAGE_ALIGN_VAR)
212 .sub_var_layout(&IMAGE_OFFSET_VAR)
213 .sub_var_layout(&IMAGE_REPEAT_VAR)
214 .sub_var_layout(&IMAGE_REPEAT_SPACING_VAR)
215 .sub_var_render(&IMAGE_RENDERING_VAR);
216
217 img_size = CONTEXT_IMAGE_VAR.with(Img::size);
218 }
219 UiNodeOp::Update { .. } => {
220 if let Some(img) = CONTEXT_IMAGE_VAR.get_new() {
221 let ig_size = img.size();
222 if img_size != ig_size {
223 img_size = ig_size;
224 WIDGET.layout();
225 } else if img.is_loaded() {
226 WIDGET.render();
227 }
228 }
229 }
230 UiNodeOp::Measure { desired_size, .. } => {
231 let metrics = LAYOUT.metrics();
234
235 let mut scale = IMAGE_SCALE_VAR.get();
236 if IMAGE_SCALE_DENSITY_VAR.get() {
237 let screen = metrics.screen_density();
238 let image = CONTEXT_IMAGE_VAR.with(Img::density).unwrap_or(PxDensity2d::splat(screen));
239 scale *= Factor2d::new(screen.ppcm() / image.width.ppcm(), screen.ppcm() / image.height.ppcm());
240 } else if IMAGE_SCALE_FACTOR_VAR.get() {
241 scale *= metrics.scale_factor();
242 }
243
244 let img_rect = PxRect::from_size(img_size);
245 let crop = LAYOUT.with_constraints(PxConstraints2d::new_fill_size(img_size), || {
246 let mut r = IMAGE_CROP_VAR.get();
247 r.replace_default(&img_rect.into());
248 r.layout()
249 });
250 let render_clip = img_rect.intersection(&crop).unwrap_or_default() * scale;
251
252 let min_size = metrics.constraints().clamp_size(render_clip.size);
253 let wgt_ratio = metrics.constraints().with_min_size(min_size).fill_ratio(render_clip.size);
254
255 *desired_size = metrics.constraints().inner().fill_size_or(wgt_ratio);
256 }
257 UiNodeOp::Layout { final_size, .. } => {
258 let metrics = LAYOUT.metrics();
262
263 let mut scale = IMAGE_SCALE_VAR.get();
264 if IMAGE_SCALE_DENSITY_VAR.get() {
265 let screen = metrics.screen_density();
266 let image = CONTEXT_IMAGE_VAR.with(Img::density).unwrap_or(PxDensity2d::splat(screen));
267 scale *= Factor2d::new(screen.ppcm() / image.width.ppcm(), screen.ppcm() / image.height.ppcm());
268 } else if IMAGE_SCALE_FACTOR_VAR.get() {
269 scale *= metrics.scale_factor();
270 }
271
272 let mut r_img_size = img_size * scale;
274
275 let img_rect = PxRect::from_size(img_size);
277 let crop = LAYOUT.with_constraints(PxConstraints2d::new_fill_size(img_size), || {
278 let mut r = IMAGE_CROP_VAR.get();
279 r.replace_default(&img_rect.into());
280 r.layout()
281 });
282 let mut r_clip = img_rect.intersection(&crop).unwrap_or_default() * scale;
283 let mut r_offset = -r_clip.origin.to_vector();
284
285 let mut align = IMAGE_ALIGN_VAR.get();
289
290 let constraints = metrics.constraints();
291 let min_size = constraints.clamp_size(r_clip.size);
292 let wgt_ratio = constraints.with_min_size(min_size).fill_ratio(r_clip.size);
293 let wgt_size = constraints.inner().fill_size_or(wgt_ratio);
294
295 let mut fit = IMAGE_FIT_VAR.get();
296 if let ImageFit::ScaleDown = fit {
297 if r_clip.size.width < wgt_size.width && r_clip.size.height < wgt_size.height {
298 fit = ImageFit::None;
299 } else {
300 fit = ImageFit::Contain;
301 }
302 }
303 match fit {
304 ImageFit::Fill => {
305 align = Align::FILL;
306 }
307 ImageFit::Contain => {
308 let container = wgt_size.to_f32();
309 let content = r_clip.size.to_f32();
310 let scale = (container.width / content.width).min(container.height / content.height).fct();
311 r_clip *= scale;
312 r_img_size *= scale;
313 r_offset *= scale;
314 }
315 ImageFit::Cover => {
316 let container = wgt_size.to_f32();
317 let content = r_clip.size.to_f32();
318 let scale = (container.width / content.width).max(container.height / content.height).fct();
319 r_clip *= scale;
320 r_img_size *= scale;
321 r_offset *= scale;
322 }
323 ImageFit::None => {}
324 ImageFit::ScaleDown => unreachable!(),
325 }
326
327 if align.is_fill_x() {
328 let factor = wgt_size.width.0 as f32 / r_clip.size.width.0 as f32;
329 r_clip.size.width = wgt_size.width;
330 r_clip.origin.x *= factor;
331 r_img_size.width *= factor;
332 r_offset.x = -r_clip.origin.x;
333 } else {
334 let diff = wgt_size.width - r_clip.size.width;
335 let offset = diff * align.x(metrics.direction());
336 r_offset.x += offset;
337 if diff < Px(0) {
338 r_clip.origin.x -= offset;
339 r_clip.size.width += diff;
340 }
341 }
342 if align.is_fill_y() {
343 let factor = wgt_size.height.0 as f32 / r_clip.size.height.0 as f32;
344 r_clip.size.height = wgt_size.height;
345 r_clip.origin.y *= factor;
346 r_img_size.height *= factor;
347 r_offset.y = -r_clip.origin.y;
348 } else {
349 let diff = wgt_size.height - r_clip.size.height;
350 let offset = diff * align.y();
351 r_offset.y += offset;
352 if diff < Px(0) {
353 r_clip.origin.y -= offset;
354 r_clip.size.height += diff;
355 }
356 }
357
358 let offset = LAYOUT.with_constraints(PxConstraints2d::new_fill_size(wgt_size), || IMAGE_OFFSET_VAR.layout());
360 if offset != PxVector::zero() {
361 r_offset += offset;
362
363 let screen_clip = PxRect::new(-r_offset.to_point(), wgt_size);
364 r_clip.origin -= offset;
365 r_clip = r_clip.intersection(&screen_clip).unwrap_or_default();
366 }
367
368 let mut r_tile_size = r_img_size;
370 let mut r_tile_spacing = PxSize::zero();
371 if matches!(IMAGE_REPEAT_VAR.get(), ImageRepeat::Repeat) {
372 r_clip = PxRect::from_size(wgt_size);
373 r_tile_size = r_img_size;
374 r_img_size = wgt_size;
375 r_offset = PxVector::zero();
376
377 let leftover = tile_leftover(r_tile_size, wgt_size);
378 r_tile_spacing = LAYOUT.with_constraints(PxConstraints2d::new_fill_size(r_tile_size), || {
379 LAYOUT.with_leftover(Some(leftover.width), Some(leftover.height), || IMAGE_REPEAT_SPACING_VAR.layout())
380 });
381 }
382
383 if render_clip != r_clip
384 || render_img_size != r_img_size
385 || render_offset != r_offset
386 || render_tile_size != r_tile_size
387 || render_tile_spacing != r_tile_spacing
388 {
389 render_clip = r_clip;
390 render_img_size = r_img_size;
391 render_offset = r_offset;
392 render_tile_size = r_tile_size;
393 render_tile_spacing = r_tile_spacing;
394 WIDGET.render();
395 }
396
397 *final_size = wgt_size;
398 }
399 UiNodeOp::Render { frame } => {
400 CONTEXT_IMAGE_VAR.with(|img| {
401 if img.is_loaded() && !img_size.is_empty() && !render_clip.is_empty() {
402 if render_offset != PxVector::zero() {
403 let transform = PxTransform::from(render_offset);
404 frame.push_reference_frame(spatial_id.into(), FrameValue::Value(transform), true, false, |frame| {
405 frame.push_image(
406 render_clip,
407 render_img_size,
408 render_tile_size,
409 render_tile_spacing,
410 img,
411 IMAGE_RENDERING_VAR.get(),
412 )
413 });
414 } else {
415 frame.push_image(
416 render_clip,
417 render_img_size,
418 render_tile_size,
419 render_tile_spacing,
420 img,
421 IMAGE_RENDERING_VAR.get(),
422 );
423 }
424 }
425 });
426 }
427 _ => {}
428 })
429}
430
431fn tile_leftover(tile_size: PxSize, wgt_size: PxSize) -> PxSize {
432 if tile_size.is_empty() || wgt_size.is_empty() {
433 return PxSize::zero();
434 }
435
436 let full_leftover_x = wgt_size.width % tile_size.width;
437 let full_leftover_y = wgt_size.height % tile_size.height;
438 let full_tiles_x = wgt_size.width / tile_size.width;
439 let full_tiles_y = wgt_size.height / tile_size.height;
440 let spaces_x = full_tiles_x - Px(1);
441 let spaces_y = full_tiles_y - Px(1);
442 PxSize::new(
443 if spaces_x > Px(0) { full_leftover_x / spaces_x } else { Px(0) },
444 if spaces_y > Px(0) { full_leftover_y / spaces_y } else { Px(0) },
445 )
446}