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