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 img_size = PxSize::zero();
25 let mut rect = PxRect::zero();
26
27 match_node(child, move |c, op| match op {
28 UiNodeOp::Init => {
29 WIDGET
31 .sub_var(&source)
32 .sub_var(&MASK_MODE_VAR)
33 .sub_var(&MASK_IMAGE_CACHE_VAR)
34 .sub_var(&MASK_IMAGE_DOWNSCALE_VAR);
35
36 let mode = if MASK_IMAGE_CACHE_VAR.get() {
37 ImageCacheMode::Cache
38 } else {
39 ImageCacheMode::Ignore
40 };
41 let limits = MASK_IMAGE_LIMITS_VAR.get();
42 let downscale = MASK_IMAGE_DOWNSCALE_VAR.get();
43 let mask_mode = MASK_MODE_VAR.get();
44
45 let mut source = source.get();
46 if let ImageSource::Render(_, args) = &mut source {
47 *args = Some(ImageRenderArgs::new(WINDOW.id()));
48 }
49 let opt = ImageOptions::new(mode, downscale, Some(mask_mode), ImageEntriesMode::PRIMARY);
50 let i = IMAGES.image(source, opt, limits);
51 let s = i.subscribe(UpdateOp::Update, WIDGET.id());
52 img = Some((i, s));
53
54 WIDGET
57 .sub_var_layout(&MASK_FIT_VAR)
58 .sub_var_layout(&MASK_ALIGN_VAR)
59 .sub_var_layout(&MASK_OFFSET_VAR);
60 }
61 UiNodeOp::Deinit => {
62 c.deinit();
63 img = None;
64 }
65 UiNodeOp::Update { .. } => {
66 if source.is_new() || MASK_MODE_VAR.is_new() || MASK_IMAGE_DOWNSCALE_VAR.is_new() {
68 let mut source = source.get();
69
70 if let ImageSource::Render(_, args) = &mut source {
71 *args = Some(ImageRenderArgs::new(WINDOW.id()));
72 }
73
74 let mode = if MASK_IMAGE_CACHE_VAR.get() {
75 ImageCacheMode::Cache
76 } else {
77 ImageCacheMode::Ignore
78 };
79 let limits = MASK_IMAGE_LIMITS_VAR.get();
80 let downscale = MASK_IMAGE_DOWNSCALE_VAR.get();
81 let mask_mode = MASK_MODE_VAR.get();
82 let opt = ImageOptions::new(mode, downscale, Some(mask_mode), ImageEntriesMode::PRIMARY);
83 let i = IMAGES.image(source, opt, limits);
84 let s = i.subscribe(UpdateOp::Layout, WIDGET.id());
85 img = Some((i, s));
86
87 WIDGET.layout();
88 } else if let Some(enabled) = MASK_IMAGE_CACHE_VAR.get_new() {
89 let is_cached = img.as_ref().unwrap().0.with(|i| IMAGES.is_cached(i));
91 if enabled != is_cached {
92 let source = source.get();
93 let limits = MASK_IMAGE_LIMITS_VAR.get();
94 let downscale = MASK_IMAGE_DOWNSCALE_VAR.get();
95 let mask_mode = MASK_MODE_VAR.get();
96 let mut opt = ImageOptions::new(ImageCacheMode::Cache, downscale, Some(mask_mode), ImageEntriesMode::PRIMARY);
97
98 if is_cached {
99 let _ = img.take().unwrap().0;
100 if let Some(h) = source.hash128(&opt) {
101 IMAGES.clean(h);
102 }
103
104 opt.cache_mode = ImageCacheMode::Ignore;
105 }
106
107 let i = IMAGES.image(source, opt, limits);
108
109 let s = i.subscribe(UpdateOp::Update, WIDGET.id());
110 img = Some((i, s));
111
112 WIDGET.layout();
113 }
114 } else if let Some(img) = img.as_ref().unwrap().0.get_new() {
115 let s = img.size();
116 if s != img_size {
117 img_size = s;
118 WIDGET.layout().render();
119 } else {
120 WIDGET.render();
121 }
122 }
123 }
124 UiNodeOp::Layout { wl, final_size } => {
125 *final_size = c.layout(wl);
126
127 let wgt_size = *final_size;
128 let constraints = PxConstraints2d::new_fill_size(wgt_size);
129 LAYOUT.with_constraints(constraints, || {
130 let mut img_size = img_size;
131 let mut img_origin = PxPoint::zero();
132
133 let mut fit = MASK_FIT_VAR.get();
134 if let ImageFit::ScaleDown = fit {
135 if img_size.width < wgt_size.width && img_size.height < wgt_size.height {
136 fit = ImageFit::None;
137 } else {
138 fit = ImageFit::Contain;
139 }
140 }
141
142 let mut align = MASK_ALIGN_VAR.get();
143 match fit {
144 ImageFit::Fill => {
145 align = Align::FILL;
146 }
147 ImageFit::Contain => {
148 let container = wgt_size.to_f32();
149 let content = img_size.to_f32();
150 let scale = (container.width / content.width).min(container.height / content.height).fct();
151 img_size *= scale;
152 }
153 ImageFit::Cover => {
154 let container = wgt_size.to_f32();
155 let content = img_size.to_f32();
156 let scale = (container.width / content.width).max(container.height / content.height).fct();
157 img_size *= scale;
158 }
159 ImageFit::None => {}
160 ImageFit::ScaleDown => unreachable!(),
161 }
162
163 if align.is_fill_x() {
164 let factor = wgt_size.width.0 as f32 / img_size.width.0 as f32;
165 img_size.width *= factor;
166 } else {
167 let diff = wgt_size.width - img_size.width;
168 let offset = diff * align.x(LAYOUT.direction());
169 img_origin.x += offset;
170 }
171 if align.is_fill_y() {
172 let factor = wgt_size.height.0 as f32 / img_size.height.0 as f32;
173 img_size.height *= factor;
174 } else {
175 let diff = wgt_size.height - img_size.height;
176 let offset = diff * align.y();
177 img_origin.y += offset;
178 }
179
180 img_origin += MASK_OFFSET_VAR.layout();
181
182 let new_rect = PxRect::new(img_origin, img_size);
183 if rect != new_rect {
184 rect = new_rect;
185 WIDGET.render();
186 }
187 });
188 }
189 UiNodeOp::Render { frame } => {
190 if rect.size.is_empty() {
191 return;
192 }
193 img.as_ref().unwrap().0.with(|img| {
194 img.with_best_reduce(rect.size, |img| {
195 frame.push_mask(img, rect, |frame| c.render(frame));
196 });
197 });
198 }
199 _ => {}
200 })
201}
202
203context_var! {
204 pub static MASK_MODE_VAR: ImageMaskMode = ImageMaskMode::default();
206
207 pub static MASK_IMAGE_CACHE_VAR: bool = true;
209
210 pub static MASK_IMAGE_LIMITS_VAR: Option<ImageLimits> = None;
214
215 pub static MASK_IMAGE_DOWNSCALE_VAR: Option<ImageDownscaleMode> = None;
219
220 pub static MASK_IMAGE_ENTRIES_MODE_VAR: ImageEntriesMode = ImageEntriesMode::PRIMARY;
222
223 pub static MASK_FIT_VAR: ImageFit = ImageFit::Fill;
225
226 pub static MASK_ALIGN_VAR: Align = Align::CENTER;
230
231 pub static MASK_OFFSET_VAR: Vector = Vector::default();
233}
234
235#[property(CONTEXT, default(MASK_MODE_VAR))]
242pub fn mask_mode(child: impl IntoUiNode, mode: impl IntoVar<ImageMaskMode>) -> UiNode {
243 with_context_var(child, MASK_MODE_VAR, mode)
244}
245
246#[property(CONTEXT, default(MASK_IMAGE_CACHE_VAR))]
253pub fn mask_image_cache(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
254 with_context_var(child, MASK_IMAGE_CACHE_VAR, enabled)
255}
256
257#[property(CONTEXT, default(MASK_IMAGE_LIMITS_VAR))]
265pub fn mask_image_limits(child: impl IntoUiNode, limits: impl IntoVar<Option<ImageLimits>>) -> UiNode {
266 with_context_var(child, MASK_IMAGE_LIMITS_VAR, limits)
267}
268
269#[property(CONTEXT, default(MASK_IMAGE_DOWNSCALE_VAR))]
286pub fn mask_image_downscale(child: impl IntoUiNode, downscale: impl IntoVar<Option<ImageDownscaleMode>>) -> UiNode {
287 with_context_var(child, MASK_IMAGE_DOWNSCALE_VAR, downscale)
288}
289
290#[property(CONTEXT, default(MASK_IMAGE_ENTRIES_MODE_VAR))]
304pub fn mask_image_entries_mode(child: impl IntoUiNode, mode: impl IntoVar<ImageEntriesMode>) -> UiNode {
305 with_context_var(child, MASK_IMAGE_ENTRIES_MODE_VAR, mode)
306}
307
308#[property(CONTEXT, default(MASK_FIT_VAR))]
315pub fn mask_fit(child: impl IntoUiNode, fit: impl IntoVar<ImageFit>) -> UiNode {
316 with_context_var(child, MASK_FIT_VAR, fit)
317}
318
319#[property(CONTEXT, default(MASK_ALIGN_VAR))]
326pub fn mask_align(child: impl IntoUiNode, align: impl IntoVar<Align>) -> UiNode {
327 with_context_var(child, MASK_ALIGN_VAR, align)
328}
329
330#[property(CONTEXT, default(MASK_OFFSET_VAR))]
337pub fn mask_offset(child: impl IntoUiNode, offset: impl IntoVar<Vector>) -> UiNode {
338 with_context_var(child, MASK_OFFSET_VAR, offset)
339}