1use zng_ext_image::{
7 IMAGES, ImageCacheMode, ImageDownscaleMode, ImageEntriesMode, ImageLimits, ImageMaskMode, ImageOptions, ImageRenderArgs, ImageSource,
8};
9use zng_wgt::prelude::*;
10
11use crate::ImageFit;
12
13#[property(BORDER-1)]
21pub fn mask_image(child: impl IntoUiNode, source: impl IntoVar<ImageSource>) -> UiNode {
22 let source = source.into_var();
23 let mut img = None;
24 let mut best_reduce = None;
25 let mut img_size = PxSize::zero();
26 let mut rect = PxRect::zero();
27
28 match_node(child, move |c, op| match op {
29 UiNodeOp::Init => {
30 WIDGET
32 .sub_var(&source)
33 .sub_var(&MASK_MODE_VAR)
34 .sub_var(&MASK_IMAGE_CACHE_VAR)
35 .sub_var(&MASK_IMAGE_DOWNSCALE_VAR);
36
37 let mode = if MASK_IMAGE_CACHE_VAR.get() {
38 ImageCacheMode::Cache
39 } else {
40 ImageCacheMode::Ignore
41 };
42 let limits = MASK_IMAGE_LIMITS_VAR.get();
43 let downscale = MASK_IMAGE_DOWNSCALE_VAR.get();
44 let mask_mode = MASK_MODE_VAR.get();
45
46 let mut source = source.get();
47 if let ImageSource::Render(_, args) = &mut source {
48 *args = Some(ImageRenderArgs::new(WINDOW.id()));
49 }
50 let opt = ImageOptions::new(mode, downscale, Some(mask_mode), ImageEntriesMode::PRIMARY);
51 let i = IMAGES.image(source, opt, limits);
52 let s = i.subscribe(UpdateOp::Update, WIDGET.id());
53 img = Some((i, s));
54
55 WIDGET
58 .sub_var_layout(&MASK_FIT_VAR)
59 .sub_var_layout(&MASK_ALIGN_VAR)
60 .sub_var_layout(&MASK_OFFSET_VAR);
61 }
62 UiNodeOp::Deinit => {
63 c.deinit();
64 img = None;
65 best_reduce = None;
66 }
67 UiNodeOp::Update { .. } => {
68 if source.is_new() || MASK_MODE_VAR.is_new() || MASK_IMAGE_DOWNSCALE_VAR.is_new() {
70 let mut source = source.get();
71
72 if let ImageSource::Render(_, args) = &mut source {
73 *args = Some(ImageRenderArgs::new(WINDOW.id()));
74 }
75
76 let mode = if MASK_IMAGE_CACHE_VAR.get() {
77 ImageCacheMode::Cache
78 } else {
79 ImageCacheMode::Ignore
80 };
81 let limits = MASK_IMAGE_LIMITS_VAR.get();
82 let downscale = MASK_IMAGE_DOWNSCALE_VAR.get();
83 let mask_mode = MASK_MODE_VAR.get();
84 let opt = ImageOptions::new(mode, downscale, Some(mask_mode), ImageEntriesMode::PRIMARY);
85 let i = IMAGES.image(source, opt, limits);
86 let s = i.subscribe(UpdateOp::Layout, WIDGET.id());
87 img = Some((i, s));
88
89 WIDGET.layout();
90 } else if let Some(enabled) = MASK_IMAGE_CACHE_VAR.get_new() {
91 let is_cached = img.as_ref().unwrap().0.with(|i| IMAGES.is_cached(i));
93 if enabled != is_cached {
94 let source = source.get();
95 let limits = MASK_IMAGE_LIMITS_VAR.get();
96 let downscale = MASK_IMAGE_DOWNSCALE_VAR.get();
97 let mask_mode = MASK_MODE_VAR.get();
98 let mut opt = ImageOptions::new(ImageCacheMode::Cache, downscale, Some(mask_mode), ImageEntriesMode::PRIMARY);
99
100 if is_cached {
101 let _ = img.take().unwrap().0;
102 if let Some(h) = source.hash128(&opt) {
103 IMAGES.clean(h);
104 }
105
106 opt.cache_mode = ImageCacheMode::Ignore;
107 }
108
109 let i = IMAGES.image(source, opt, limits);
110
111 let s = i.subscribe(UpdateOp::Update, WIDGET.id());
112 img = Some((i, s));
113
114 WIDGET.layout();
115 }
116 } else if let Some(img) = img.as_ref().unwrap().0.get_new() {
117 let s = img.size();
118 if s != img_size {
119 img_size = s;
120 best_reduce = None;
121 WIDGET.layout().render();
122 } else {
123 if img.has_entries() {
124 let b = img.best_reduce(rect.size);
125 let h = b.subscribe(UpdateOp::Render, WIDGET.id());
126 best_reduce = Some((b, h));
127 } else {
128 best_reduce = None;
129 }
130 WIDGET.render();
131 }
132 }
133 }
134 UiNodeOp::Layout { wl, final_size } => {
135 *final_size = c.layout(wl);
136
137 let wgt_size = *final_size;
138 let constraints = PxConstraints2d::new_fill_size(wgt_size);
139 LAYOUT.with_constraints(constraints, || {
140 let mut img_size = img_size;
141 let mut img_origin = PxPoint::zero();
142
143 let mut fit = MASK_FIT_VAR.get();
144 if let ImageFit::ScaleDown = fit {
145 if img_size.width < wgt_size.width && img_size.height < wgt_size.height {
146 fit = ImageFit::None;
147 } else {
148 fit = ImageFit::Contain;
149 }
150 }
151
152 let mut align = MASK_ALIGN_VAR.get();
153 match fit {
154 ImageFit::Fill => {
155 align = Align::FILL;
156 }
157 ImageFit::Contain => {
158 let container = wgt_size.to_f32();
159 let content = img_size.to_f32();
160 let scale = (container.width / content.width).min(container.height / content.height).fct();
161 img_size *= scale;
162 }
163 ImageFit::Cover => {
164 let container = wgt_size.to_f32();
165 let content = img_size.to_f32();
166 let scale = (container.width / content.width).max(container.height / content.height).fct();
167 img_size *= scale;
168 }
169 ImageFit::None => {}
170 ImageFit::ScaleDown => unreachable!(),
171 }
172
173 if align.is_fill_x() {
174 let factor = wgt_size.width.0 as f32 / img_size.width.0 as f32;
175 img_size.width *= factor;
176 } else {
177 let diff = wgt_size.width - img_size.width;
178 let offset = diff * align.x(LAYOUT.direction());
179 img_origin.x += offset;
180 }
181 if align.is_fill_y() {
182 let factor = wgt_size.height.0 as f32 / img_size.height.0 as f32;
183 img_size.height *= factor;
184 } else {
185 let diff = wgt_size.height - img_size.height;
186 let offset = diff * align.y();
187 img_origin.y += offset;
188 }
189
190 img_origin += MASK_OFFSET_VAR.layout();
191
192 let new_rect = PxRect::new(img_origin, img_size);
193 if rect != new_rect {
194 if rect.size != new_rect.size {
195 img.as_ref().unwrap().0.with(|img| {
196 if img.has_entries() {
197 let b = img.best_reduce(new_rect.size);
198 let h = b.subscribe(UpdateOp::Render, WIDGET.id());
199 best_reduce = Some((b, h));
200 } else {
201 best_reduce = None;
202 }
203 });
204 }
205
206 rect = new_rect;
207 WIDGET.render();
208 }
209 });
210 }
211 UiNodeOp::Render { frame } => {
212 if rect.size.is_empty() {
213 return;
214 }
215 best_reduce.as_ref().or(img.as_ref()).unwrap().0.with(|img| {
216 if img.is_loaded() && !img.is_error() {
217 frame.push_mask(img, rect, |frame| c.render(frame));
218 }
219 });
220 }
221 _ => {}
222 })
223}
224
225context_var! {
226 pub static MASK_MODE_VAR: ImageMaskMode = ImageMaskMode::default();
228
229 pub static MASK_IMAGE_CACHE_VAR: bool = true;
231
232 pub static MASK_IMAGE_LIMITS_VAR: Option<ImageLimits> = None;
236
237 pub static MASK_IMAGE_DOWNSCALE_VAR: Option<ImageDownscaleMode> = None;
241
242 pub static MASK_IMAGE_ENTRIES_MODE_VAR: ImageEntriesMode = ImageEntriesMode::PRIMARY;
244
245 pub static MASK_FIT_VAR: ImageFit = ImageFit::Fill;
247
248 pub static MASK_ALIGN_VAR: Align = Align::CENTER;
252
253 pub static MASK_OFFSET_VAR: Vector = Vector::default();
255}
256
257#[property(CONTEXT, default(MASK_MODE_VAR))]
264pub fn mask_mode(child: impl IntoUiNode, mode: impl IntoVar<ImageMaskMode>) -> UiNode {
265 with_context_var(child, MASK_MODE_VAR, mode)
266}
267
268#[property(CONTEXT, default(MASK_IMAGE_CACHE_VAR))]
275pub fn mask_image_cache(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
276 with_context_var(child, MASK_IMAGE_CACHE_VAR, enabled)
277}
278
279#[property(CONTEXT, default(MASK_IMAGE_LIMITS_VAR))]
287pub fn mask_image_limits(child: impl IntoUiNode, limits: impl IntoVar<Option<ImageLimits>>) -> UiNode {
288 with_context_var(child, MASK_IMAGE_LIMITS_VAR, limits)
289}
290
291#[property(CONTEXT, default(MASK_IMAGE_DOWNSCALE_VAR))]
308pub fn mask_image_downscale(child: impl IntoUiNode, downscale: impl IntoVar<Option<ImageDownscaleMode>>) -> UiNode {
309 with_context_var(child, MASK_IMAGE_DOWNSCALE_VAR, downscale)
310}
311
312#[property(CONTEXT, default(MASK_IMAGE_ENTRIES_MODE_VAR))]
326pub fn mask_image_entries_mode(child: impl IntoUiNode, mode: impl IntoVar<ImageEntriesMode>) -> UiNode {
327 with_context_var(child, MASK_IMAGE_ENTRIES_MODE_VAR, mode)
328}
329
330#[property(CONTEXT, default(MASK_FIT_VAR))]
337pub fn mask_fit(child: impl IntoUiNode, fit: impl IntoVar<ImageFit>) -> UiNode {
338 with_context_var(child, MASK_FIT_VAR, fit)
339}
340
341#[property(CONTEXT, default(MASK_ALIGN_VAR))]
348pub fn mask_align(child: impl IntoUiNode, align: impl IntoVar<Align>) -> UiNode {
349 with_context_var(child, MASK_ALIGN_VAR, align)
350}
351
352#[property(CONTEXT, default(MASK_OFFSET_VAR))]
359pub fn mask_offset(child: impl IntoUiNode, offset: impl IntoVar<Vector>) -> UiNode {
360 with_context_var(child, MASK_OFFSET_VAR, offset)
361}