zng_wgt_image/
border.rs
1use std::mem;
4
5use zng_app::render::RepeatMode;
6use zng_ext_image::{IMAGES, ImageCacheMode, ImageRenderArgs, ImageSource, Img};
7use zng_wgt::prelude::*;
8
9use crate::{IMAGE_CACHE_VAR, IMAGE_LIMITS_VAR, IMAGE_RENDERING_VAR};
10
11#[property(BORDER)]
20pub fn border_img(
21 child: impl UiNode,
22 widths: impl IntoVar<SideOffsets>,
23 source: impl IntoVar<ImageSource>,
24 slices: impl IntoVar<SideOffsets>,
25) -> impl UiNode {
26 let widths = widths.into_var();
27 let source = source.into_var();
28 let slices = slices.into_var();
29
30 let mut img = var(Img::dummy(None)).read_only();
31 let mut _img_sub = VarHandle::dummy();
32 let mut slices_img_size = PxSize::zero();
33 let mut slices_px = PxSideOffsets::zero();
34
35 border_node(
36 child,
37 widths,
38 match_node_leaf(move |op| match op {
39 UiNodeOp::Init => {
40 WIDGET
41 .sub_var(&source)
42 .sub_var(&IMAGE_CACHE_VAR)
43 .sub_var_render(&slices)
44 .sub_var_render(&BORDER_IMG_REPEAT_VAR)
45 .sub_var_render(&IMAGE_RENDERING_VAR)
46 .sub_var_render(&BORDER_IMG_FILL_VAR);
47
48 let mode = if IMAGE_CACHE_VAR.get() {
49 ImageCacheMode::Cache
50 } else {
51 ImageCacheMode::Ignore
52 };
53 let limits = IMAGE_LIMITS_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, None, None);
60 _img_sub = img.subscribe(UpdateOp::Update, WIDGET.id());
61
62 let img = img.get();
63 if img.is_loaded() {
64 slices_img_size = img.size();
65 }
66 }
67 UiNodeOp::Deinit => {
68 img = var(Img::dummy(None)).read_only();
69 _img_sub = VarHandle::dummy();
70 }
71 UiNodeOp::Update { .. } => {
72 if source.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
88 img = IMAGES.image(source, mode, limits, None, None);
89 } else if let Some(enabled) = IMAGE_CACHE_VAR.get_new() {
90 let is_cached = img.with(|img| IMAGES.is_cached(img));
92 if enabled != is_cached {
93 img = if is_cached {
94 let img = mem::replace(&mut img, var(Img::dummy(None)).read_only());
97 IMAGES.detach(img)
98 } else {
99 let source = source.get();
102 let limits = IMAGE_LIMITS_VAR.get();
103 IMAGES.image(source, ImageCacheMode::Cache, limits, None, None)
104 };
105 }
106 }
107
108 if img.is_new() {
109 let img = img.get();
110 if img.is_loaded() {
111 let s = img.size();
112 if s != slices_img_size {
113 slices_img_size = s;
114 WIDGET.layout();
115 }
116 }
117 WIDGET.render();
118 }
119 }
120 UiNodeOp::Measure { desired_size, .. } => {
121 *desired_size = LAYOUT.constraints().fill_size();
122 }
123 UiNodeOp::Layout { final_size, .. } => {
124 *final_size = LAYOUT.constraints().fill_size();
125
126 let metrics = LAYOUT
127 .metrics()
128 .with_constraints(PxConstraints2d::new_exact_size(slices_img_size))
129 .with_scale_factor(1.fct());
130 let s = LAYOUT.with_context(metrics, || slices.layout());
131 if s != slices_px {
132 slices_px = s;
133 WIDGET.render();
134 }
135 }
136 UiNodeOp::Render { frame } => {
137 let img = img.get();
138 if img.is_loaded() {
139 let (rect, offsets) = BORDER.border_layout();
140 if !rect.size.is_empty() {
141 let repeats = BORDER_IMG_REPEAT_VAR.get();
142 frame.push_border_image(
143 rect,
144 offsets,
145 slices_px,
146 BORDER_IMG_FILL_VAR.get(),
147 repeats.top_bottom,
148 repeats.left_right,
149 &img,
150 IMAGE_RENDERING_VAR.get(),
151 );
152 }
153 }
154 }
155 _ => {}
156 }),
157 )
158}
159
160#[property(CONTEXT, default(BORDER_IMG_REPEAT_VAR))]
164pub fn border_img_repeat(child: impl UiNode, repeats: impl IntoVar<BorderRepeats>) -> impl UiNode {
165 with_context_var(child, BORDER_IMG_REPEAT_VAR, repeats)
166}
167
168#[property(CONTEXT, default(BORDER_IMG_FILL_VAR))]
172pub fn border_img_fill(child: impl UiNode, fill: impl IntoVar<bool>) -> impl UiNode {
173 with_context_var(child, BORDER_IMG_FILL_VAR, fill)
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
178pub struct BorderRepeats {
179 pub top_bottom: RepeatMode,
183 pub left_right: RepeatMode,
185}
186impl BorderRepeats {
187 pub fn new<TB: Into<RepeatMode>, LR: Into<RepeatMode>>(top_bottom: TB, left_right: LR) -> Self {
189 BorderRepeats {
190 top_bottom: top_bottom.into(),
191 left_right: left_right.into(),
192 }
193 }
194
195 pub fn new_all<T: Into<RepeatMode>>(all_sides: T) -> Self {
197 let all_sides = all_sides.into();
198 Self::new(all_sides, all_sides)
199 }
200}
201context_var! {
202 pub static BORDER_IMG_REPEAT_VAR: BorderRepeats = BorderRepeats::default();
204
205 pub static BORDER_IMG_FILL_VAR: bool = false;
207}
208
209impl_from_and_into_var! {
210 fn from(repeat: RepeatMode) -> BorderRepeats {
211 BorderRepeats::new_all(repeat)
212 }
213
214 fn from(repeat_or_stretch: bool) -> BorderRepeats {
216 BorderRepeats::new_all(repeat_or_stretch)
217 }
218
219 fn from<TB: Into<RepeatMode>, LR: Into<RepeatMode>>((top_bottom, left_right): (TB, LR)) -> BorderRepeats {
220 BorderRepeats::new(top_bottom, left_right)
221 }
222}