1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12use std::fmt;
13
14use zng_wgt::{align, clip_to_bounds, margin, prelude::*};
15
16#[widget($crate::Container {
18 ($child:expr) => {
19 child = $child;
20 }
21})]
22pub struct Container(WidgetBase);
23impl Container {
24 fn widget_intrinsic(&mut self) {
25 self.widget_builder().push_build_action(|wgt| {
26 if let Some(child) = wgt.capture_ui_node(property_id!(Self::child)) {
27 wgt.set_child(child);
28 }
29 });
30 }
31
32 widget_impl! {
33 pub clip_to_bounds(clip: impl IntoVar<bool>);
35 }
36}
37
38#[property(CHILD, default(FillUiNode), widget_impl(Container))]
47pub fn child(widget_child: impl UiNode, child: impl UiNode) -> impl UiNode {
48 child_under(widget_child, child)
49}
50
51#[property(CHILD_LAYOUT, default(0), widget_impl(Container))]
55pub fn padding(child: impl UiNode, padding: impl IntoVar<SideOffsets>) -> impl UiNode {
56 margin(child, padding)
57}
58
59#[property(CHILD_LAYOUT, default(Align::FILL), widget_impl(Container))]
63pub fn child_align(child: impl UiNode, alignment: impl IntoVar<Align>) -> impl UiNode {
64 align(child, alignment)
65}
66
67#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
71pub enum ChildInsert {
72 Top,
74 Right,
76 Bottom,
78 Left,
80
81 Start,
87 End,
93
94 Over,
98 Under,
102}
103impl fmt::Debug for ChildInsert {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 if f.alternate() {
106 write!(f, "ChildInsert::")?;
107 }
108 match self {
109 Self::Top => write!(f, "Top"),
110 Self::Right => write!(f, "Right"),
111 Self::Bottom => write!(f, "Bottom"),
112 Self::Left => write!(f, "Left"),
113 Self::Start => write!(f, "Start"),
114 Self::End => write!(f, "End"),
115 Self::Over => write!(f, "Over"),
116 Self::Under => write!(f, "Under"),
117 }
118 }
119}
120impl ChildInsert {
121 pub fn resolve_direction(self, direction: LayoutDirection) -> Self {
123 match self {
124 Self::Start => match direction {
125 LayoutDirection::LTR => Self::Left,
126 LayoutDirection::RTL => Self::Right,
127 },
128 Self::End => match direction {
129 LayoutDirection::LTR => Self::Right,
130 LayoutDirection::RTL => Self::Left,
131 },
132 p => p,
133 }
134 }
135
136 pub fn is_x_axis(self) -> bool {
138 !matches!(self, Self::Top | Self::Bottom)
139 }
140
141 pub fn is_y_axis(self) -> bool {
143 matches!(self, Self::Top | Self::Bottom)
144 }
145
146 pub fn is_z_axis(self) -> bool {
148 matches!(self, Self::Over | Self::Under)
149 }
150}
151
152#[property(CHILD, default(ChildInsert::Start, NilUiNode, 0), widget_impl(Container))]
156pub fn child_insert(
157 child: impl UiNode,
158 placement: impl IntoVar<ChildInsert>,
159 node: impl UiNode,
160 spacing: impl IntoVar<Length>,
161) -> impl UiNode {
162 let placement = placement.into_var();
163 let spacing = spacing.into_var();
164 let offset_key = FrameValueKey::new_unique();
165 let mut offset_child = 0;
166 let mut offset = PxVector::zero();
167
168 match_node_list(ui_vec![child, node], move |children, op| match op {
169 UiNodeOp::Init => {
170 WIDGET.sub_var_layout(&placement).sub_var_layout(&spacing);
171 }
172 UiNodeOp::Measure { wm, desired_size } => {
173 let c = LAYOUT.constraints();
174 let placement = placement.get();
175 *desired_size = if placement.is_x_axis() {
176 let mut spacing = spacing.layout_x();
177 let insert_size = children.with_node(1, |n| {
178 LAYOUT.with_constraints(c.with_new_min(Px(0), Px(0)).with_fill_x(false), || wm.measure_block(n))
179 });
180 if insert_size.width == 0 {
181 spacing = Px(0);
182 }
183 let child_size = children.with_node(0, |n| {
184 LAYOUT.with_constraints(c.with_less_x(insert_size.width + spacing), || wm.measure_block(n))
185 });
186
187 PxSize::new(
188 insert_size.width + spacing + child_size.width,
189 insert_size.height.max(child_size.height),
190 )
191 } else if placement.is_y_axis() {
192 let mut spacing = spacing.layout_y();
193 let insert_size = children.with_node(1, |n| {
194 LAYOUT.with_constraints(c.with_new_min(Px(0), Px(0)).with_fill_y(false), || wm.measure_block(n))
195 });
196 if insert_size.height == 0 {
197 spacing = Px(0);
198 }
199 let child_size = children.with_node(0, |n| {
200 LAYOUT.with_constraints(c.with_less_y(insert_size.height + spacing), || wm.measure_block(n))
201 });
202 if child_size.height == 0 {
203 spacing = Px(0);
204 }
205 PxSize::new(
206 insert_size.width.max(child_size.width),
207 insert_size.height + spacing + child_size.height,
208 )
209 } else {
210 children.with_node(0, |n| wm.measure_block(n))
211 };
212 }
213 UiNodeOp::Layout { wl, final_size } => {
214 wl.require_child_ref_frame();
215
216 let placement = placement.get().resolve_direction(LAYOUT.direction());
217 let c = LAYOUT.constraints();
218
219 *final_size = match placement {
220 ChildInsert::Left | ChildInsert::Right => {
221 let spacing = spacing.layout_x();
222
223 let mut constraints_y = LAYOUT.constraints().y;
224 if constraints_y.fill_or_exact().is_none() {
225 let mut wm = wl.to_measure(None);
227 let wm = &mut wm;
228 let mut spacing = spacing;
229
230 let insert_size = children.with_node(1, |n| {
231 LAYOUT.with_constraints(c.with_new_min(Px(0), Px(0)).with_fill_x(false), || n.measure(wm))
232 });
233 if insert_size.width == 0 {
234 spacing = Px(0);
235 }
236 let child_size = children.with_node(0, |n| {
237 LAYOUT.with_constraints(c.with_less_x(insert_size.width + spacing), || n.measure(wm))
238 });
239
240 constraints_y = constraints_y.with_fill(true).with_max(child_size.height.max(insert_size.height));
241 }
242
243 let mut spacing = spacing;
244 let insert_size = children.with_node(1, |n| {
245 LAYOUT.with_constraints(
246 {
247 let mut c = c;
248 c.y = constraints_y;
249 c.with_new_min(Px(0), Px(0)).with_fill_x(false)
250 },
251 || n.layout(wl),
252 )
253 });
254 if insert_size.width == 0 {
255 spacing = Px(0);
256 }
257 let child_size = children.with_node(0, |n| {
258 LAYOUT.with_constraints(
259 {
260 let mut c = c;
261 c.y = constraints_y;
262 c.with_less_x(insert_size.width + spacing)
263 },
264 || n.layout(wl),
265 )
266 });
267 if child_size.width == 0 {
268 spacing = Px(0);
269 }
270
271 let (child, o) = match placement {
273 ChildInsert::Left => (0, insert_size.width + spacing),
274 ChildInsert::Right => (1, child_size.width + spacing),
275 _ => unreachable!(),
276 };
277 let o = PxVector::new(o, Px(0));
278 if offset != o || offset_child != child {
279 offset_child = child;
280 offset = o;
281 WIDGET.render_update();
282 }
283
284 PxSize::new(
285 insert_size.width + spacing + child_size.width,
286 insert_size.height.max(child_size.height),
287 )
288 }
289 ChildInsert::Top | ChildInsert::Bottom => {
290 let spacing = spacing.layout_y();
291
292 let mut constraints_x = c.x;
293 if constraints_x.fill_or_exact().is_none() {
294 let mut wm = wl.to_measure(None);
297 let wm = &mut wm;
298 let mut spacing = spacing;
299
300 let insert_size = children.with_node(1, |n| {
301 LAYOUT.with_constraints(c.with_new_min(Px(0), Px(0)).with_fill_y(false), || n.measure(wm))
302 });
303 if insert_size.height == 0 {
304 spacing = Px(0);
305 }
306 let child_size = children.with_node(0, |n| {
307 LAYOUT.with_constraints(c.with_less_y(insert_size.height + spacing), || n.measure(wm))
308 });
309
310 constraints_x = constraints_x.with_fill(true).with_max(child_size.width.max(insert_size.width));
311 }
312
313 let mut spacing = spacing;
314 let insert_size = children.with_node(1, |n| {
315 LAYOUT.with_constraints(
316 {
317 let mut c = c;
318 c.x = constraints_x;
319 c.with_new_min(Px(0), Px(0)).with_fill_y(false)
320 },
321 || n.layout(wl),
322 )
323 });
324 if insert_size.height == 0 {
325 spacing = Px(0);
326 }
327 let child_size = children.with_node(0, |n| {
328 LAYOUT.with_constraints(
329 {
330 let mut c = c;
331 c.x = constraints_x;
332 c.with_less_y(insert_size.height + spacing)
333 },
334 || n.layout(wl),
335 )
336 });
337
338 let (child, o) = match placement {
340 ChildInsert::Top => (0, insert_size.height + spacing),
341 ChildInsert::Bottom => (1, child_size.height + spacing),
342 _ => unreachable!(),
343 };
344 let o = PxVector::new(Px(0), o);
345 if offset != o || offset_child != child {
346 offset_child = child;
347 offset = o;
348 WIDGET.render_update();
349 }
350
351 PxSize::new(
352 insert_size.width.max(child_size.width),
353 insert_size.height + spacing + child_size.height,
354 )
355 }
356 ChildInsert::Over | ChildInsert::Under => {
357 let child_size = children.with_node(0, |n| n.layout(wl));
358 let insert_size = children.with_node(1, |n| n.layout(wl));
359 child_size.max(insert_size)
360 }
361 ChildInsert::Start | ChildInsert::End => unreachable!(), };
363 }
364 UiNodeOp::Render { frame } => match placement.get() {
365 ChildInsert::Over => children.render_all(frame),
366 ChildInsert::Under => {
367 children.with_node(1, |n| n.render(frame));
368 children.with_node(0, |n| n.render(frame));
369 }
370 _ => children.for_each(|i, child| {
371 if i as u8 == offset_child {
372 frame.push_reference_frame(offset_key.into(), offset_key.bind(offset.into(), false), true, true, |frame| {
373 child.render(frame);
374 });
375 } else {
376 child.render(frame);
377 }
378 }),
379 },
380 UiNodeOp::RenderUpdate { update } => match placement.get() {
381 ChildInsert::Over => children.render_update_all(update),
382 ChildInsert::Under => {
383 children.with_node(1, |n| n.render_update(update));
384 children.with_node(0, |n| n.render_update(update));
385 }
386 _ => {
387 children.for_each(|i, child| {
388 if i as u8 == offset_child {
389 update.with_transform(offset_key.update(offset.into(), false), true, |update| {
390 child.render_update(update);
391 });
392 } else {
393 child.render_update(update);
394 }
395 });
396 }
397 },
398 _ => {}
399 })
400}
401
402#[property(CHILD_LAYOUT - 1, default(ChildInsert::Start, NilUiNode, 0), widget_impl(Container))]
408pub fn child_out_insert(
409 child: impl UiNode,
410 placement: impl IntoVar<ChildInsert>,
411 node: impl UiNode,
412 spacing: impl IntoVar<Length>,
413) -> impl UiNode {
414 child_insert(child, placement, node, spacing)
415}
416
417#[property(CHILD, default(NilUiNode, 0), widget_impl(Container))]
423pub fn child_left(child: impl UiNode, node: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
424 child_insert(child, ChildInsert::Left, node, spacing)
425}
426
427#[property(CHILD, default(NilUiNode, 0), widget_impl(Container))]
433pub fn child_right(child: impl UiNode, node: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
434 child_insert(child, ChildInsert::Right, node, spacing)
435}
436
437#[property(CHILD, default(NilUiNode, 0), widget_impl(Container))]
443pub fn child_top(child: impl UiNode, node: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
444 child_insert(child, ChildInsert::Top, node, spacing)
445}
446
447#[property(CHILD, default(NilUiNode, 0), widget_impl(Container))]
453pub fn child_bottom(child: impl UiNode, node: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
454 child_insert(child, ChildInsert::Bottom, node, spacing)
455}
456
457#[property(CHILD, default(NilUiNode, 0), widget_impl(Container))]
463pub fn child_start(child: impl UiNode, node: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
464 child_insert(child, ChildInsert::Start, node, spacing)
465}
466
467#[property(CHILD, default(NilUiNode, 0), widget_impl(Container))]
473pub fn child_end(child: impl UiNode, node: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
474 child_insert(child, ChildInsert::End, node, spacing)
475}
476
477#[property(CHILD, default(NilUiNode), widget_impl(Container))]
483pub fn child_over(child: impl UiNode, node: impl UiNode) -> impl UiNode {
484 child_insert(child, ChildInsert::Over, node, 0)
485}
486
487#[property(CHILD, default(NilUiNode), widget_impl(Container))]
493pub fn child_under(child: impl UiNode, node: impl UiNode) -> impl UiNode {
494 child_insert(child, ChildInsert::Under, node, 0)
495}
496
497#[property(CHILD_LAYOUT - 1, default(NilUiNode, 0), widget_impl(Container))]
503pub fn child_out_left(child: impl UiNode, node: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
504 child_out_insert(child, ChildInsert::Left, node, spacing)
505}
506
507#[property(CHILD_LAYOUT - 1, default(NilUiNode, 0), widget_impl(Container))]
513pub fn child_out_right(child: impl UiNode, node: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
514 child_out_insert(child, ChildInsert::Right, node, spacing)
515}
516
517#[property(CHILD_LAYOUT - 1, default(NilUiNode, 0), widget_impl(Container))]
523pub fn child_out_top(child: impl UiNode, node: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
524 child_out_insert(child, ChildInsert::Top, node, spacing)
525}
526
527#[property(CHILD_LAYOUT - 1, default(NilUiNode, 0), widget_impl(Container))]
533pub fn child_out_bottom(child: impl UiNode, node: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
534 child_out_insert(child, ChildInsert::Bottom, node, spacing)
535}
536
537#[property(CHILD_LAYOUT - 1, default(NilUiNode, 0), widget_impl(Container))]
543pub fn child_out_start(child: impl UiNode, node: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
544 child_out_insert(child, ChildInsert::Start, node, spacing)
545}
546
547#[property(CHILD_LAYOUT - 1, default(NilUiNode, 0), widget_impl(Container))]
553pub fn child_out_end(child: impl UiNode, node: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
554 child_out_insert(child, ChildInsert::End, node, spacing)
555}
556
557#[property(CHILD_LAYOUT - 1, default(NilUiNode), widget_impl(Container))]
563pub fn child_out_over(child: impl UiNode, node: impl UiNode) -> impl UiNode {
564 child_out_insert(child, ChildInsert::Over, node, 0)
565}
566
567#[property(CHILD_LAYOUT - 1, default(NilUiNode), widget_impl(Container))]
573pub fn child_out_under(child: impl UiNode, node: impl UiNode) -> impl UiNode {
574 child_out_insert(child, ChildInsert::Under, node, 0)
575}