1use zng_app::render::RepeatMode;
4use zng_ext_image::{IMAGES, ImageCacheMode, ImageEntry, ImageOptions, ImageRenderArgs, ImageSource};
5use zng_wgt::prelude::*;
6
7use crate::{IMAGE_CACHE_VAR, IMAGE_LIMITS_VAR, IMAGE_RENDERING_VAR};
8
9#[property(BORDER)]
18pub fn border_img(
19 child: impl IntoUiNode,
20 widths: impl IntoVar<SideOffsets>,
21 source: impl IntoVar<ImageSource>,
22 slices: impl IntoVar<SideOffsets>,
23) -> UiNode {
24 let widths = widths.into_var();
25 let source = source.into_var();
26 let slices = slices.into_var();
27
28 let mut img = var(ImageEntry::new_loading()).read_only();
29 let mut _img_sub = VarHandle::dummy();
30 let mut slices_img_size = PxSize::zero();
31 let mut slices_px = PxSideOffsets::zero();
32
33 border_node(
34 child,
35 widths,
36 match_node_leaf(move |op| match op {
37 UiNodeOp::Init => {
38 WIDGET
39 .sub_var(&source)
40 .sub_var(&IMAGE_CACHE_VAR)
41 .sub_var_render(&slices)
42 .sub_var_render(&BORDER_IMG_REPEAT_VAR)
43 .sub_var_render(&IMAGE_RENDERING_VAR)
44 .sub_var_render(&BORDER_IMG_FILL_VAR);
45
46 let mode = if IMAGE_CACHE_VAR.get() {
47 ImageCacheMode::Cache
48 } else {
49 ImageCacheMode::Ignore
50 };
51 let limits = IMAGE_LIMITS_VAR.get();
52
53 let mut source = source.get();
54 if let ImageSource::Render(_, args) = &mut source {
55 *args = Some(ImageRenderArgs::new(WINDOW.id()));
56 }
57
58 let mut opt = ImageOptions::cache();
59 opt.cache_mode = mode;
60 img = IMAGES.image(source, opt, limits);
61 _img_sub = img.subscribe(UpdateOp::Update, WIDGET.id());
62
63 let img = img.get();
64 if img.is_loaded() {
65 slices_img_size = img.size();
66 }
67 }
68 UiNodeOp::Deinit => {
69 img = var(ImageEntry::new_loading()).read_only();
70 _img_sub = VarHandle::dummy();
71 }
72 UiNodeOp::Update { .. } => {
73 if source.is_new() {
74 let mut source = source.get();
77
78 if let ImageSource::Render(_, args) = &mut source {
79 *args = Some(ImageRenderArgs::new(WINDOW.id()));
80 }
81
82 let mode = if IMAGE_CACHE_VAR.get() {
83 ImageCacheMode::Cache
84 } else {
85 ImageCacheMode::Ignore
86 };
87 let limits = IMAGE_LIMITS_VAR.get();
88 let mut opt = ImageOptions::cache();
89 opt.cache_mode = mode;
90 img = IMAGES.image(source, opt, limits);
91 } else if let Some(enabled) = IMAGE_CACHE_VAR.get_new() {
92 let is_cached = img.with(|img| IMAGES.is_cached(img));
94 if enabled != is_cached {
95 let source = source.get();
96 let limits = IMAGE_LIMITS_VAR.get();
97
98 let mut opt = ImageOptions::cache();
99
100 if is_cached {
101 img = const_var(ImageEntry::new_loading()); if let Some(h) = source.hash128(&opt) {
105 IMAGES.clean(h);
106 }
107 opt.cache_mode = ImageCacheMode::Ignore;
108 }
109 img = IMAGES.image(source, opt, limits);
110 }
111 }
112
113 if img.is_new() {
114 let img = img.get();
115 if img.is_loaded() {
116 let s = img.size();
117 if s != slices_img_size {
118 slices_img_size = s;
119 WIDGET.layout();
120 }
121 }
122 WIDGET.render();
123 }
124 }
125 UiNodeOp::Measure { desired_size, .. } => {
126 *desired_size = LAYOUT.constraints().fill_size();
127 }
128 UiNodeOp::Layout { final_size, .. } => {
129 *final_size = LAYOUT.constraints().fill_size();
130
131 let metrics = LAYOUT
132 .metrics()
133 .with_constraints(PxConstraints2d::new_exact_size(slices_img_size))
134 .with_scale_factor(1.fct());
135 let s = LAYOUT.with_context(metrics, || slices.layout());
136 if s != slices_px {
137 slices_px = s;
138 WIDGET.render();
139 }
140 }
141 UiNodeOp::Render { frame } => {
142 let img = img.get();
143 if img.is_loaded() {
144 let (rect, offsets) = BORDER.border_layout();
145 if !rect.size.is_empty() {
146 let repeats = BORDER_IMG_REPEAT_VAR.get();
147 frame.push_border_image(
148 rect,
149 offsets,
150 slices_px,
151 BORDER_IMG_FILL_VAR.get(),
152 repeats.top_bottom,
153 repeats.left_right,
154 &img,
155 IMAGE_RENDERING_VAR.get(),
156 );
157 }
158 }
159 }
160 _ => {}
161 }),
162 )
163}
164
165#[property(CONTEXT, default(BORDER_IMG_REPEAT_VAR))]
169pub fn border_img_repeat(child: impl IntoUiNode, repeats: impl IntoVar<BorderRepeats>) -> UiNode {
170 with_context_var(child, BORDER_IMG_REPEAT_VAR, repeats)
171}
172
173#[property(CONTEXT, default(BORDER_IMG_FILL_VAR))]
177pub fn border_img_fill(child: impl IntoUiNode, fill: impl IntoVar<bool>) -> UiNode {
178 with_context_var(child, BORDER_IMG_FILL_VAR, fill)
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
183pub struct BorderRepeats {
184 pub top_bottom: RepeatMode,
188 pub left_right: RepeatMode,
190}
191impl BorderRepeats {
192 pub fn new<TB: Into<RepeatMode>, LR: Into<RepeatMode>>(top_bottom: TB, left_right: LR) -> Self {
194 BorderRepeats {
195 top_bottom: top_bottom.into(),
196 left_right: left_right.into(),
197 }
198 }
199
200 pub fn new_all<T: Into<RepeatMode>>(all_sides: T) -> Self {
202 let all_sides = all_sides.into();
203 Self::new(all_sides, all_sides)
204 }
205}
206context_var! {
207 pub static BORDER_IMG_REPEAT_VAR: BorderRepeats = BorderRepeats::default();
209
210 pub static BORDER_IMG_FILL_VAR: bool = false;
212}
213
214impl_from_and_into_var! {
215 fn from(repeat: RepeatMode) -> BorderRepeats {
216 BorderRepeats::new_all(repeat)
217 }
218
219 fn from(repeat_or_stretch: bool) -> BorderRepeats {
221 BorderRepeats::new_all(repeat_or_stretch)
222 }
223
224 fn from<TB: Into<RepeatMode>, LR: Into<RepeatMode>>((top_bottom, left_right): (TB, LR)) -> BorderRepeats {
225 BorderRepeats::new(top_bottom, left_right)
226 }
227}