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