1use zng_ext_image::{IMAGES, ImageCacheMode, ImageOptions, ImageRenderArgs};
4use zng_wgt_stack::stack_nodes;
5
6use super::image_properties::{
7 IMAGE_ALIGN_VAR, IMAGE_AUTO_SCALE_VAR, IMAGE_CACHE_VAR, IMAGE_CROP_VAR, IMAGE_DOWNSCALE_VAR, IMAGE_ERROR_FN_VAR, IMAGE_FIT_VAR,
8 IMAGE_LIMITS_VAR, IMAGE_LOADING_FN_VAR, IMAGE_OFFSET_VAR, IMAGE_RENDERING_VAR, IMAGE_SCALE_VAR, ImageFit, ImgErrorArgs, ImgLoadingArgs,
9};
10use super::*;
11
12context_var! {
13 pub static CONTEXT_IMAGE_VAR: ImageEntry = no_context_image();
17}
18fn no_context_image() -> ImageEntry {
19 ImageEntry::new_error(Txt::from_static("no image source in context"))
20}
21
22pub fn image_source(child: impl IntoUiNode, source: impl IntoVar<ImageSource>) -> UiNode {
33 let source = source.into_var();
34 let ctx_img = var(ImageEntry::new_loading());
35 let child = with_context_var(child, CONTEXT_IMAGE_VAR, ctx_img.read_only());
36 let mut img = var(ImageEntry::new_loading()).read_only();
37 let mut _ctx_binding = None;
38
39 match_node(child, move |child, op| match op {
40 UiNodeOp::Init => {
41 WIDGET
42 .sub_var(&source)
43 .sub_var(&IMAGE_CACHE_VAR)
44 .sub_var(&IMAGE_DOWNSCALE_VAR)
45 .sub_var(&IMAGE_ENTRIES_MODE_VAR);
46
47 let mode = if IMAGE_CACHE_VAR.get() {
48 ImageCacheMode::Cache
49 } else {
50 ImageCacheMode::Ignore
51 };
52
53 let mut source = source.get();
54 if let ImageSource::Render(_, args) = &mut source {
55 *args = Some(ImageRenderArgs::new(WINDOW.id()));
56 }
57 let opt = ImageOptions::new(mode, IMAGE_DOWNSCALE_VAR.get(), None, IMAGE_ENTRIES_MODE_VAR.get());
58 img = IMAGES.image(source, opt, IMAGE_LIMITS_VAR.get());
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() || IMAGE_ENTRIES_MODE_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 opt = ImageOptions::new(mode, IMAGE_DOWNSCALE_VAR.get(), None, IMAGE_ENTRIES_MODE_VAR.get());
86 img = IMAGES.image(source, opt, IMAGE_LIMITS_VAR.get());
87
88 ctx_img.set_from(&img);
89 _ctx_binding = Some(img.bind(&ctx_img));
90 } else if let Some(enabled) = IMAGE_CACHE_VAR.get_new() {
91 let is_cached = ctx_img.with(|img| IMAGES.is_cached(img));
93 if enabled != is_cached {
94 let source = source.get();
95 let mut opt = ImageOptions::new(ImageCacheMode::Cache, IMAGE_DOWNSCALE_VAR.get(), None, IMAGE_ENTRIES_MODE_VAR.get());
96
97 if is_cached {
98 img = const_var(ImageEntry::new_loading());
99 if let Some(h) = source.hash128(&opt) {
100 IMAGES.clean(h);
101 }
102 opt.cache_mode = ImageCacheMode::Ignore;
103 }
104 img = IMAGES.image(source, opt, IMAGE_LIMITS_VAR.get());
105
106 ctx_img.set_from(&img);
107 _ctx_binding = Some(img.bind(&ctx_img));
108 }
109 }
110 }
111 _ => {}
112 })
113}
114
115context_local! {
116 static IN_ERROR_VIEW: bool = false;
118 static IN_LOADING_VIEW: bool = false;
120}
121
122pub fn image_error_presenter(child: impl IntoUiNode) -> UiNode {
128 let view = CONTEXT_IMAGE_VAR
129 .map(|i| i.error().map(|e| ImgErrorArgs { error: e }))
130 .present_opt(IMAGE_ERROR_FN_VAR.map(|f| {
131 wgt_fn!(f, |e| {
132 if IN_ERROR_VIEW.get_clone() {
133 UiNode::nil()
134 } else {
135 with_context_local(f(e), &IN_ERROR_VIEW, true)
136 }
137 })
138 }));
139
140 stack_nodes(ui_vec![view, child], 1, |constraints, _, img_size| {
141 if img_size == PxSize::zero() {
142 constraints
143 } else {
144 PxConstraints2d::new_fill_size(img_size)
145 }
146 })
147}
148
149pub fn image_loading_presenter(child: impl IntoUiNode) -> UiNode {
155 let view = CONTEXT_IMAGE_VAR
156 .map(|i| if i.is_loading() { Some(ImgLoadingArgs {}) } else { None })
157 .present_opt(IMAGE_LOADING_FN_VAR.map(|f| {
158 wgt_fn!(f, |a| {
159 if IN_LOADING_VIEW.get_clone() {
160 UiNode::nil()
161 } else {
162 with_context_local(f(a), &IN_LOADING_VIEW, true)
163 }
164 })
165 }));
166
167 stack_nodes(ui_vec![view, child], 1, |constraints, _, img_size| {
168 if img_size == PxSize::zero() {
169 constraints
170 } else {
171 PxConstraints2d::new_fill_size(img_size)
172 }
173 })
174}
175
176pub fn image_presenter() -> UiNode {
189 let mut img_size = PxSize::zero();
190 let mut render_clip = PxRect::zero();
191 let mut render_img_size = PxSize::zero();
192 let mut render_tile_size = PxSize::zero();
193 let mut render_tile_spacing = PxSize::zero();
194 let mut render_offset = PxVector::zero();
195 let spatial_id = SpatialFrameId::new_unique();
196
197 match_node_leaf(move |op| match op {
198 UiNodeOp::Init => {
199 WIDGET
200 .sub_var(&CONTEXT_IMAGE_VAR)
201 .sub_var_layout(&IMAGE_CROP_VAR)
202 .sub_var_layout(&IMAGE_AUTO_SCALE_VAR)
203 .sub_var_layout(&IMAGE_SCALE_VAR)
204 .sub_var_layout(&IMAGE_FIT_VAR)
205 .sub_var_layout(&IMAGE_ALIGN_VAR)
206 .sub_var_layout(&IMAGE_OFFSET_VAR)
207 .sub_var_layout(&IMAGE_REPEAT_VAR)
208 .sub_var_layout(&IMAGE_REPEAT_SPACING_VAR)
209 .sub_var_render(&IMAGE_RENDERING_VAR);
210
211 img_size = CONTEXT_IMAGE_VAR.with(ImageEntry::size);
212 }
213 UiNodeOp::Update { .. } => {
214 if let Some(img) = CONTEXT_IMAGE_VAR.get_new() {
215 let ig_size = img.size();
216 if img_size != ig_size {
217 img_size = ig_size;
218 WIDGET.layout();
219 } else if img.is_loaded() {
220 WIDGET.render();
221 }
222 }
223 }
224 UiNodeOp::Measure { desired_size, .. } => {
225 let metrics = LAYOUT.metrics();
228
229 let mut scale = IMAGE_SCALE_VAR.get();
230 match IMAGE_AUTO_SCALE_VAR.get() {
231 ImageAutoScale::Pixel => {}
232 ImageAutoScale::Factor => {
233 scale *= metrics.scale_factor();
234 }
235 ImageAutoScale::Density => {
236 let screen = metrics.screen_density();
237 let image = CONTEXT_IMAGE_VAR.with(ImageEntry::density).unwrap_or(PxDensity2d::splat(screen));
238 scale *= Factor2d::new(screen.ppcm() / image.width.ppcm(), screen.ppcm() / image.height.ppcm());
239 }
240 }
241
242 let img_rect = PxRect::from_size(img_size);
243 let crop = LAYOUT.with_constraints(PxConstraints2d::new_fill_size(img_size), || {
244 let mut r = IMAGE_CROP_VAR.get();
245 r.replace_default(&img_rect.into());
246 r.layout()
247 });
248 let render_clip = img_rect.intersection(&crop).unwrap_or_default() * scale;
249
250 let min_size = metrics.constraints().clamp_size(render_clip.size);
251 let wgt_ratio = metrics.constraints().with_min_size(min_size).fill_ratio(render_clip.size);
252
253 *desired_size = metrics.constraints().inner().fill_size_or(wgt_ratio);
254 }
255 UiNodeOp::Layout { final_size, .. } => {
256 let metrics = LAYOUT.metrics();
260
261 let mut scale = IMAGE_SCALE_VAR.get();
262 match IMAGE_AUTO_SCALE_VAR.get() {
263 ImageAutoScale::Pixel => {}
264 ImageAutoScale::Factor => {
265 scale *= metrics.scale_factor();
266 }
267 ImageAutoScale::Density => {
268 let screen = metrics.screen_density();
269 let image = CONTEXT_IMAGE_VAR.with(ImageEntry::density).unwrap_or(PxDensity2d::splat(screen));
270 scale *= Factor2d::new(screen.ppcm() / image.width.ppcm(), screen.ppcm() / image.height.ppcm());
271 }
272 }
273
274 let mut r_img_size = img_size * scale;
276
277 let img_rect = PxRect::from_size(img_size);
279 let crop = LAYOUT.with_constraints(PxConstraints2d::new_fill_size(img_size), || {
280 let mut r = IMAGE_CROP_VAR.get();
281 r.replace_default(&img_rect.into());
282 r.layout()
283 });
284 let mut r_clip = img_rect.intersection(&crop).unwrap_or_default() * scale;
285 let mut r_offset = -r_clip.origin.to_vector();
286
287 let mut align = IMAGE_ALIGN_VAR.get();
291
292 let constraints = metrics.constraints();
293 let min_size = constraints.clamp_size(r_clip.size);
294 let wgt_ratio = constraints.with_min_size(min_size).fill_ratio(r_clip.size);
295 let wgt_size = constraints.inner().fill_size_or(wgt_ratio);
296
297 let mut fit = IMAGE_FIT_VAR.get();
298 if let ImageFit::ScaleDown = fit {
299 if r_clip.size.width < wgt_size.width && r_clip.size.height < wgt_size.height {
300 fit = ImageFit::None;
301 } else {
302 fit = ImageFit::Contain;
303 }
304 }
305 match fit {
306 ImageFit::Fill => {
307 align = Align::FILL;
308 }
309 ImageFit::Contain => {
310 let container = wgt_size.to_f32();
311 let content = r_clip.size.to_f32();
312 let scale = (container.width / content.width).min(container.height / content.height).fct();
313 r_clip *= scale;
314 r_img_size *= scale;
315 r_offset *= scale;
316 }
317 ImageFit::Cover => {
318 let container = wgt_size.to_f32();
319 let content = r_clip.size.to_f32();
320 let scale = (container.width / content.width).max(container.height / content.height).fct();
321 r_clip *= scale;
322 r_img_size *= scale;
323 r_offset *= scale;
324 }
325 ImageFit::None => {}
326 ImageFit::ScaleDown => unreachable!(),
327 }
328
329 if align.is_fill_x() {
330 let factor = wgt_size.width.0 as f32 / r_clip.size.width.0 as f32;
331 r_clip.size.width = wgt_size.width;
332 r_clip.origin.x *= factor;
333 r_img_size.width *= factor;
334 r_offset.x = -r_clip.origin.x;
335 } else {
336 let diff = wgt_size.width - r_clip.size.width;
337 let offset = diff * align.x(metrics.direction());
338 r_offset.x += offset;
339 if diff < Px(0) {
340 r_clip.origin.x -= offset;
341 r_clip.size.width += diff;
342 }
343 }
344 if align.is_fill_y() {
345 let factor = wgt_size.height.0 as f32 / r_clip.size.height.0 as f32;
346 r_clip.size.height = wgt_size.height;
347 r_clip.origin.y *= factor;
348 r_img_size.height *= factor;
349 r_offset.y = -r_clip.origin.y;
350 } else {
351 let diff = wgt_size.height - r_clip.size.height;
352 let offset = diff * align.y();
353 r_offset.y += offset;
354 if diff < Px(0) {
355 r_clip.origin.y -= offset;
356 r_clip.size.height += diff;
357 }
358 }
359
360 let offset = LAYOUT.with_constraints(PxConstraints2d::new_fill_size(wgt_size), || IMAGE_OFFSET_VAR.layout());
362 if offset != PxVector::zero() {
363 r_offset += offset;
364
365 let screen_clip = PxRect::new(-r_offset.to_point(), wgt_size);
366 r_clip.origin -= offset;
367 r_clip = r_clip.intersection(&screen_clip).unwrap_or_default();
368 }
369
370 let mut r_tile_size = r_img_size;
372 let mut r_tile_spacing = PxSize::zero();
373 if matches!(IMAGE_REPEAT_VAR.get(), ImageRepeat::Repeat) {
374 r_clip = PxRect::from_size(wgt_size);
375 r_tile_size = r_img_size;
376 r_img_size = wgt_size;
377 r_offset = PxVector::zero();
378
379 let leftover = tile_leftover(r_tile_size, wgt_size);
380 r_tile_spacing = LAYOUT.with_constraints(PxConstraints2d::new_fill_size(r_tile_size), || {
381 LAYOUT.with_leftover(Some(leftover.width), Some(leftover.height), || IMAGE_REPEAT_SPACING_VAR.layout())
382 });
383 }
384
385 if render_clip != r_clip
386 || render_img_size != r_img_size
387 || render_offset != r_offset
388 || render_tile_size != r_tile_size
389 || render_tile_spacing != r_tile_spacing
390 {
391 render_clip = r_clip;
392 render_img_size = r_img_size;
393 render_offset = r_offset;
394 render_tile_size = r_tile_size;
395 render_tile_spacing = r_tile_spacing;
396 WIDGET.render();
397 }
398
399 *final_size = wgt_size;
400 }
401 UiNodeOp::Render { frame } => {
402 if render_clip.is_empty() {
403 return;
404 }
405 CONTEXT_IMAGE_VAR.with(|img| {
406 img.with_best_reduce(render_tile_size, |img| {
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}