zng_wgt_image/
mask.rs
1use zng_ext_image::{IMAGES, ImageCacheMode, ImageDownscale, ImageLimits, ImageMaskMode, ImageRenderArgs, ImageSource};
7use zng_wgt::prelude::*;
8
9use crate::ImageFit;
10
11#[property(FILL-1)]
19pub fn mask_image(child: impl UiNode, source: impl IntoVar<ImageSource>) -> impl UiNode {
20 let source = source.into_var();
21 let mut img = None;
22 let mut img_size = PxSize::zero();
23 let mut rect = PxRect::zero();
24
25 match_node(child, move |c, op| match op {
26 UiNodeOp::Init => {
27 WIDGET
29 .sub_var(&source)
30 .sub_var(&MASK_MODE_VAR)
31 .sub_var(&MASK_IMAGE_CACHE_VAR)
32 .sub_var(&MASK_IMAGE_DOWNSCALE_VAR);
33
34 let mode = if MASK_IMAGE_CACHE_VAR.get() {
35 ImageCacheMode::Cache
36 } else {
37 ImageCacheMode::Ignore
38 };
39 let limits = MASK_IMAGE_LIMITS_VAR.get();
40 let downscale = MASK_IMAGE_DOWNSCALE_VAR.get();
41 let mask_mode = MASK_MODE_VAR.get();
42
43 let mut source = source.get();
44 if let ImageSource::Render(_, args) = &mut source {
45 *args = Some(ImageRenderArgs { parent: Some(WINDOW.id()) });
46 }
47 let i = IMAGES.image(source, mode, limits, downscale, Some(mask_mode));
48 let s = i.subscribe(UpdateOp::Update, WIDGET.id());
49 img = Some((i, s));
50
51 WIDGET
54 .sub_var_layout(&MASK_FIT_VAR)
55 .sub_var_layout(&MASK_ALIGN_VAR)
56 .sub_var_layout(&MASK_OFFSET_VAR);
57 }
58 UiNodeOp::Deinit => {
59 c.deinit();
60 img = None;
61 }
62 UiNodeOp::Update { .. } => {
63 if source.is_new() || MASK_MODE_VAR.is_new() || MASK_IMAGE_DOWNSCALE_VAR.is_new() {
65 let mut source = source.get();
66
67 if let ImageSource::Render(_, args) = &mut source {
68 *args = Some(ImageRenderArgs { parent: Some(WINDOW.id()) });
69 }
70
71 let mode = if MASK_IMAGE_CACHE_VAR.get() {
72 ImageCacheMode::Cache
73 } else {
74 ImageCacheMode::Ignore
75 };
76 let limits = MASK_IMAGE_LIMITS_VAR.get();
77 let downscale = MASK_IMAGE_DOWNSCALE_VAR.get();
78 let mask_mode = MASK_MODE_VAR.get();
79
80 let i = IMAGES.image(source, mode, limits, downscale, Some(mask_mode));
81 let s = i.subscribe(UpdateOp::Layout, WIDGET.id());
82 img = Some((i, s));
83
84 WIDGET.layout();
85 } else if let Some(enabled) = MASK_IMAGE_CACHE_VAR.get_new() {
86 let is_cached = img.as_ref().unwrap().0.with(|i| IMAGES.is_cached(i));
88 if enabled != is_cached {
89 let i = if is_cached {
90 let img = img.take().unwrap().0;
93 IMAGES.detach(img)
94 } else {
95 let source = source.get();
98 let limits = MASK_IMAGE_LIMITS_VAR.get();
99 let downscale = MASK_IMAGE_DOWNSCALE_VAR.get();
100 let mask_mode = MASK_MODE_VAR.get();
101 IMAGES.image(source, ImageCacheMode::Cache, limits, downscale, Some(mask_mode))
102 };
103
104 let s = i.subscribe(UpdateOp::Update, WIDGET.id());
105 img = Some((i, s));
106
107 WIDGET.layout();
108 }
109 } else if let Some(img) = img.as_ref().unwrap().0.get_new() {
110 let s = img.size();
111 if s != img_size {
112 img_size = s;
113 WIDGET.layout().render();
114 } else {
115 WIDGET.render();
116 }
117 }
118 }
119 UiNodeOp::Layout { wl, final_size } => {
120 *final_size = c.layout(wl);
121
122 let wgt_size = *final_size;
123 let constraints = PxConstraints2d::new_fill_size(wgt_size);
124 LAYOUT.with_constraints(constraints, || {
125 let mut img_size = img_size;
126 let mut img_origin = PxPoint::zero();
127
128 let mut fit = MASK_FIT_VAR.get();
129 if let ImageFit::ScaleDown = fit {
130 if img_size.width < wgt_size.width && img_size.height < wgt_size.height {
131 fit = ImageFit::None;
132 } else {
133 fit = ImageFit::Contain;
134 }
135 }
136
137 let mut align = MASK_ALIGN_VAR.get();
138 match fit {
139 ImageFit::Fill => {
140 align = Align::FILL;
141 }
142 ImageFit::Contain => {
143 let container = wgt_size.to_f32();
144 let content = img_size.to_f32();
145 let scale = (container.width / content.width).min(container.height / content.height).fct();
146 img_size *= scale;
147 }
148 ImageFit::Cover => {
149 let container = wgt_size.to_f32();
150 let content = img_size.to_f32();
151 let scale = (container.width / content.width).max(container.height / content.height).fct();
152 img_size *= scale;
153 }
154 ImageFit::None => {}
155 ImageFit::ScaleDown => unreachable!(),
156 }
157
158 if align.is_fill_x() {
159 let factor = wgt_size.width.0 as f32 / img_size.width.0 as f32;
160 img_size.width *= factor;
161 } else {
162 let diff = wgt_size.width - img_size.width;
163 let offset = diff * align.x(LAYOUT.direction());
164 img_origin.x += offset;
165 }
166 if align.is_fill_y() {
167 let factor = wgt_size.height.0 as f32 / img_size.height.0 as f32;
168 img_size.height *= factor;
169 } else {
170 let diff = wgt_size.height - img_size.height;
171 let offset = diff * align.y();
172 img_origin.y += offset;
173 }
174
175 img_origin += MASK_OFFSET_VAR.layout();
176
177 let new_rect = PxRect::new(img_origin, img_size);
178 if rect != new_rect {
179 rect = new_rect;
180 WIDGET.render();
181 }
182 });
183 }
184 UiNodeOp::Render { frame } => {
185 img.as_ref().unwrap().0.with(|img| {
186 if img.is_loaded() && !rect.size.is_empty() {
187 frame.push_mask(img, rect, |frame| c.render(frame));
188 }
189 });
190 }
191 _ => {}
192 })
193}
194
195context_var! {
196 pub static MASK_MODE_VAR: ImageMaskMode = ImageMaskMode::default();
198
199 pub static MASK_IMAGE_CACHE_VAR: bool = true;
201
202 pub static MASK_IMAGE_LIMITS_VAR: Option<ImageLimits> = None;
206
207 pub static MASK_IMAGE_DOWNSCALE_VAR: Option<ImageDownscale> = None;
211
212 pub static MASK_FIT_VAR: ImageFit = ImageFit::Fill;
214
215 pub static MASK_ALIGN_VAR: Align = Align::CENTER;
219
220 pub static MASK_OFFSET_VAR: Vector = Vector::default();
222}
223
224#[property(CONTEXT, default(MASK_MODE_VAR))]
231pub fn mask_mode(child: impl UiNode, mode: impl IntoVar<ImageMaskMode>) -> impl UiNode {
232 with_context_var(child, MASK_MODE_VAR, mode)
233}
234
235#[property(CONTEXT, default(MASK_IMAGE_CACHE_VAR))]
242pub fn mask_image_cache(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode {
243 with_context_var(child, MASK_IMAGE_CACHE_VAR, enabled)
244}
245
246#[property(CONTEXT, default(MASK_IMAGE_LIMITS_VAR))]
252pub fn mask_image_limits(child: impl UiNode, limits: impl IntoVar<Option<ImageLimits>>) -> impl UiNode {
253 with_context_var(child, MASK_IMAGE_LIMITS_VAR, limits)
254}
255
256#[property(CONTEXT, default(MASK_IMAGE_DOWNSCALE_VAR))]
270pub fn mask_image_downscale(child: impl UiNode, downscale: impl IntoVar<Option<ImageDownscale>>) -> impl UiNode {
271 with_context_var(child, MASK_IMAGE_DOWNSCALE_VAR, downscale)
272}
273
274#[property(CONTEXT, default(MASK_FIT_VAR))]
281pub fn mask_fit(child: impl UiNode, fit: impl IntoVar<ImageFit>) -> impl UiNode {
282 with_context_var(child, MASK_FIT_VAR, fit)
283}
284
285#[property(CONTEXT, default(MASK_ALIGN_VAR))]
292pub fn mask_align(child: impl UiNode, align: impl IntoVar<Align>) -> impl UiNode {
293 with_context_var(child, MASK_ALIGN_VAR, align)
294}
295
296#[property(CONTEXT, default(MASK_OFFSET_VAR))]
303pub fn mask_offset(child: impl UiNode, offset: impl IntoVar<Vector>) -> impl UiNode {
304 with_context_var(child, MASK_OFFSET_VAR, offset)
305}