1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/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 { ($child:expr) => { child = $child; } })]
18pub struct Container(WidgetBase);
19impl Container {
20 fn widget_intrinsic(&mut self) {
21 self.widget_builder().push_build_action(|wgt| {
22 if let Some(child) = wgt.capture_ui_node(property_id!(Self::child)) {
23 wgt.set_child(child);
24 }
25 });
26 }
27
28 widget_impl! {
29 pub clip_to_bounds(clip: impl IntoVar<bool>);
31 }
32}
33
34#[property(CHILD, default(FillUiNode), widget_impl(Container))]
43pub fn child(widget_child: impl IntoUiNode, child: impl IntoUiNode) -> UiNode {
44 child_under(widget_child, child)
45}
46
47#[property(CHILD_LAYOUT, default(0), widget_impl(Container))]
51pub fn padding(child: impl IntoUiNode, padding: impl IntoVar<SideOffsets>) -> UiNode {
52 margin(child, padding)
53}
54
55#[property(CHILD_LAYOUT, default(Align::FILL), widget_impl(Container))]
59pub fn child_align(child: impl IntoUiNode, alignment: impl IntoVar<Align>) -> UiNode {
60 align(child, alignment)
61}
62
63#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
67pub enum ChildInsert {
68 Top,
70 Right,
72 Bottom,
74 Left,
76
77 Start,
83 End,
89
90 Over,
94 Under,
98}
99impl fmt::Debug for ChildInsert {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 if f.alternate() {
102 write!(f, "ChildInsert::")?;
103 }
104 match self {
105 Self::Top => write!(f, "Top"),
106 Self::Right => write!(f, "Right"),
107 Self::Bottom => write!(f, "Bottom"),
108 Self::Left => write!(f, "Left"),
109 Self::Start => write!(f, "Start"),
110 Self::End => write!(f, "End"),
111 Self::Over => write!(f, "Over"),
112 Self::Under => write!(f, "Under"),
113 }
114 }
115}
116impl ChildInsert {
117 pub fn resolve_direction(self, direction: LayoutDirection) -> Self {
119 match self {
120 Self::Start => match direction {
121 LayoutDirection::LTR => Self::Left,
122 LayoutDirection::RTL => Self::Right,
123 },
124 Self::End => match direction {
125 LayoutDirection::LTR => Self::Right,
126 LayoutDirection::RTL => Self::Left,
127 },
128 p => p,
129 }
130 }
131
132 pub fn is_x_axis(self) -> bool {
134 !matches!(self, Self::Top | Self::Bottom)
135 }
136
137 pub fn is_y_axis(self) -> bool {
139 matches!(self, Self::Top | Self::Bottom)
140 }
141
142 pub fn is_z_axis(self) -> bool {
144 matches!(self, Self::Over | Self::Under)
145 }
146
147 pub fn spacing(self, spacing: &Var<SideOffsets>) -> Px {
153 spacing.with(|s| match self {
154 ChildInsert::Top => s.top.layout_y(),
155 ChildInsert::Right => s.right.layout_x(),
156 ChildInsert::Bottom => s.bottom.layout_y(),
157 ChildInsert::Left => s.left.layout_x(),
158 _ => Px(0),
159 })
160 }
161}
162
163static_id! {
164 pub static ref CHILD_SPACING_ID: StateId<Var<SideOffsets>>;
168 pub static ref CHILD_OUT_SPACING_ID: StateId<Var<SideOffsets>>;
172}
173
174#[property(CONTEXT, default(0), widget_impl(Container))]
181pub fn child_spacing(child: impl IntoUiNode, spacing: impl IntoVar<SideOffsets>) -> UiNode {
182 let spacing = spacing.into_var();
183 match_node(child, move |c, op| match op {
184 UiNodeOp::Init => {
185 WIDGET.sub_var_layout(&spacing);
186 WIDGET.set_state(*CHILD_SPACING_ID, spacing.clone());
187 }
188 UiNodeOp::Deinit => {
189 c.deinit();
190 WIDGET.set_state(*CHILD_SPACING_ID, const_var(SideOffsets::zero()));
191 }
192 _ => {}
193 })
194}
195
196#[property(CONTEXT, default(0), widget_impl(Container))]
203pub fn child_out_spacing(child: impl IntoUiNode, spacing: impl IntoVar<SideOffsets>) -> UiNode {
204 let spacing = spacing.into_var();
205 match_node(child, move |c, op| match op {
206 UiNodeOp::Init => {
207 WIDGET.sub_var_layout(&spacing);
208 WIDGET.set_state(*CHILD_OUT_SPACING_ID, spacing.clone());
209 }
210 UiNodeOp::Deinit => {
211 c.deinit();
212 WIDGET.set_state(*CHILD_OUT_SPACING_ID, const_var(SideOffsets::zero()));
213 }
214 _ => {}
215 })
216}
217
218#[property(CHILD, default(ChildInsert::Start, UiNode::nil()), widget_impl(Container))]
243pub fn child_insert(child: impl IntoUiNode, placement: impl IntoVar<ChildInsert>, node: impl IntoUiNode) -> UiNode {
244 fn init_spacing(s: &mut Var<SideOffsets>) {
245 if let Some(v) = WIDGET.get_state(*CHILD_SPACING_ID) {
246 *s = v;
247 }
248 }
249 child_insert_node(child.into_node(), placement.into_var(), node.into_node(), init_spacing)
250}
251
252#[property(CHILD_LAYOUT - 1, default(ChildInsert::Start, UiNode::nil()), widget_impl(Container))]
273pub fn child_out_insert(child: impl IntoUiNode, placement: impl IntoVar<ChildInsert>, node: impl IntoUiNode) -> UiNode {
274 fn init_spacing(s: &mut Var<SideOffsets>) {
275 if let Some(v) = WIDGET.get_state(*CHILD_OUT_SPACING_ID) {
276 *s = v;
277 }
278 }
279 child_insert_node(child.into_node(), placement.into_var(), node.into_node(), init_spacing)
280}
281
282#[property(CHILD, default(UiNode::nil()), widget_impl(Container))]
288pub fn child_left(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
289 child_insert(child, ChildInsert::Left, node)
290}
291
292#[property(CHILD, default(UiNode::nil()), widget_impl(Container))]
298pub fn child_right(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
299 child_insert(child, ChildInsert::Right, node)
300}
301
302#[property(CHILD, default(UiNode::nil()), widget_impl(Container))]
308pub fn child_top(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
309 child_insert(child, ChildInsert::Top, node)
310}
311
312#[property(CHILD, default(UiNode::nil()), widget_impl(Container))]
318pub fn child_bottom(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
319 child_insert(child, ChildInsert::Bottom, node)
320}
321
322#[property(CHILD, default(UiNode::nil()), widget_impl(Container))]
328pub fn child_start(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
329 child_insert(child, ChildInsert::Start, node)
330}
331
332#[property(CHILD, default(UiNode::nil()), widget_impl(Container))]
338pub fn child_end(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
339 child_insert(child, ChildInsert::End, node)
340}
341
342#[property(CHILD, default(UiNode::nil()), widget_impl(Container))]
348pub fn child_over(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
349 child_insert(child, ChildInsert::Over, node)
350}
351
352#[property(CHILD, default(UiNode::nil()), widget_impl(Container))]
358pub fn child_under(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
359 child_insert(child, ChildInsert::Under, node)
360}
361
362#[property(CHILD_LAYOUT - 1, default(UiNode::nil()), widget_impl(Container))]
368pub fn child_out_left(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
369 child_out_insert(child, ChildInsert::Left, node)
370}
371
372#[property(CHILD_LAYOUT - 1, default(UiNode::nil()), widget_impl(Container))]
378pub fn child_out_right(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
379 child_out_insert(child, ChildInsert::Right, node)
380}
381
382#[property(CHILD_LAYOUT - 1, default(UiNode::nil()), widget_impl(Container))]
388pub fn child_out_top(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
389 child_out_insert(child, ChildInsert::Top, node)
390}
391
392#[property(CHILD_LAYOUT - 1, default(UiNode::nil()), widget_impl(Container))]
398pub fn child_out_bottom(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
399 child_out_insert(child, ChildInsert::Bottom, node)
400}
401
402#[property(CHILD_LAYOUT - 1, default(UiNode::nil()), widget_impl(Container))]
408pub fn child_out_start(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
409 child_out_insert(child, ChildInsert::Start, node)
410}
411
412#[property(CHILD_LAYOUT - 1, default(UiNode::nil()), widget_impl(Container))]
418pub fn child_out_end(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
419 child_out_insert(child, ChildInsert::End, node)
420}
421
422#[property(CHILD_LAYOUT - 1, default(UiNode::nil()), widget_impl(Container))]
428pub fn child_out_over(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
429 child_out_insert(child, ChildInsert::Over, node)
430}
431
432#[property(CHILD_LAYOUT - 1, default(UiNode::nil()), widget_impl(Container))]
438pub fn child_out_under(child: impl IntoUiNode, node: impl IntoUiNode) -> UiNode {
439 child_out_insert(child, ChildInsert::Under, node)
440}
441
442fn child_insert_node(child: UiNode, placement: Var<ChildInsert>, node: UiNode, init_spacing: fn(&mut Var<SideOffsets>)) -> UiNode {
443 let placement = placement.into_var();
444 let mut spacing = const_var(SideOffsets::zero());
445 let offset_key = FrameValueKey::new_unique();
446 let mut offset_child = 0;
447 let mut offset = PxVector::zero();
448
449 match_node(ui_vec![child, node], move |children, op| match op {
450 UiNodeOp::Init => {
451 WIDGET.sub_var_layout(&placement);
452 init_spacing(&mut spacing);
453 }
454 UiNodeOp::Deinit => {
455 spacing = const_var(SideOffsets::zero());
456 }
457 UiNodeOp::Measure { wm, desired_size } => {
458 children.delegated();
459
460 let c = LAYOUT.constraints();
461 let placement = placement.get().resolve_direction(LAYOUT.direction());
462 let mut spacing = placement.spacing(&spacing);
463 *desired_size = if placement.is_x_axis() {
464 let insert_size = children.node().with_child(1, |n| {
465 LAYOUT.with_constraints(c.with_new_min(Px(0), Px(0)).with_fill_x(false), || wm.measure_block(n))
466 });
467 if insert_size.width == 0 {
468 spacing = Px(0);
469 }
470 let child_size = children.node().with_child(0, |n| {
471 LAYOUT.with_constraints(c.with_less_x(insert_size.width + spacing), || wm.measure_block(n))
472 });
473
474 PxSize::new(
475 insert_size.width + spacing + child_size.width,
476 insert_size.height.max(child_size.height),
477 )
478 } else if placement.is_y_axis() {
479 let insert_size = children.node().with_child(1, |n| {
480 LAYOUT.with_constraints(c.with_new_min(Px(0), Px(0)).with_fill_y(false), || wm.measure_block(n))
481 });
482 if insert_size.height == 0 {
483 spacing = Px(0);
484 }
485 let child_size = children.node().with_child(0, |n| {
486 LAYOUT.with_constraints(c.with_less_y(insert_size.height + spacing), || wm.measure_block(n))
487 });
488 if child_size.height == 0 {
489 spacing = Px(0);
490 }
491 PxSize::new(
492 insert_size.width.max(child_size.width),
493 insert_size.height + spacing + child_size.height,
494 )
495 } else {
496 children.node().with_child(0, |n| wm.measure_block(n))
497 };
498 }
499 UiNodeOp::Layout { wl, final_size } => {
500 children.delegated();
501 wl.require_child_ref_frame();
502
503 let placement = placement.get().resolve_direction(LAYOUT.direction());
504 let spacing = placement.spacing(&spacing);
505
506 let c = LAYOUT.constraints();
507
508 *final_size = match placement {
509 ChildInsert::Left | ChildInsert::Right => {
510 let mut constraints_y = LAYOUT.constraints().y;
511 if constraints_y.fill_or_exact().is_none() {
512 let mut wm = wl.to_measure(None);
514 let wm = &mut wm;
515 let mut spacing = spacing;
516
517 let insert_size = children.node().with_child(1, |n| {
518 LAYOUT.with_constraints(c.with_new_min(Px(0), Px(0)).with_fill_x(false), || n.measure(wm))
519 });
520 if insert_size.width == 0 {
521 spacing = Px(0);
522 }
523 let child_size = children.node().with_child(0, |n| {
524 LAYOUT.with_constraints(c.with_less_x(insert_size.width + spacing), || n.measure(wm))
525 });
526
527 constraints_y = constraints_y.with_fill(true).with_max(child_size.height.max(insert_size.height));
528 }
529
530 let mut spacing = spacing;
531 let insert_size = children.node().with_child(1, |n| {
532 LAYOUT.with_constraints(
533 {
534 let mut c = c;
535 c.y = constraints_y;
536 c.with_new_min(Px(0), Px(0)).with_fill_x(false)
537 },
538 || n.layout(wl),
539 )
540 });
541 if insert_size.width == 0 {
542 spacing = Px(0);
543 }
544 let child_size = children.node().with_child(0, |n| {
545 LAYOUT.with_constraints(
546 {
547 let mut c = c;
548 c.y = constraints_y;
549 c.with_less_x(insert_size.width + spacing)
550 },
551 || n.layout(wl),
552 )
553 });
554 if child_size.width == 0 {
555 spacing = Px(0);
556 }
557
558 let (child, o) = match placement {
560 ChildInsert::Left => (0, insert_size.width + spacing),
561 ChildInsert::Right => (1, child_size.width + spacing),
562 _ => unreachable!(),
563 };
564 let o = PxVector::new(o, Px(0));
565 if offset != o || offset_child != child {
566 offset_child = child;
567 offset = o;
568 WIDGET.render_update();
569 }
570
571 PxSize::new(
572 insert_size.width + spacing + child_size.width,
573 insert_size.height.max(child_size.height),
574 )
575 }
576 ChildInsert::Top | ChildInsert::Bottom => {
577 let mut constraints_x = c.x;
578 if constraints_x.fill_or_exact().is_none() {
579 let mut wm = wl.to_measure(None);
582 let wm = &mut wm;
583 let mut spacing = spacing;
584
585 let insert_size = children.node().with_child(1, |n| {
586 LAYOUT.with_constraints(c.with_new_min(Px(0), Px(0)).with_fill_y(false), || n.measure(wm))
587 });
588 if insert_size.height == 0 {
589 spacing = Px(0);
590 }
591 let child_size = children.node().with_child(0, |n| {
592 LAYOUT.with_constraints(c.with_less_y(insert_size.height + spacing), || n.measure(wm))
593 });
594
595 constraints_x = constraints_x.with_fill(true).with_max(child_size.width.max(insert_size.width));
596 }
597
598 let mut spacing = spacing;
599 let insert_size = children.node().with_child(1, |n| {
600 LAYOUT.with_constraints(
601 {
602 let mut c = c;
603 c.x = constraints_x;
604 c.with_new_min(Px(0), Px(0)).with_fill_y(false)
605 },
606 || n.layout(wl),
607 )
608 });
609 if insert_size.height == 0 {
610 spacing = Px(0);
611 }
612 let child_size = children.node().with_child(0, |n| {
613 LAYOUT.with_constraints(
614 {
615 let mut c = c;
616 c.x = constraints_x;
617 c.with_less_y(insert_size.height + spacing)
618 },
619 || n.layout(wl),
620 )
621 });
622
623 let (child, o) = match placement {
625 ChildInsert::Top => (0, insert_size.height + spacing),
626 ChildInsert::Bottom => (1, child_size.height + spacing),
627 _ => unreachable!(),
628 };
629 let o = PxVector::new(Px(0), o);
630 if offset != o || offset_child != child {
631 offset_child = child;
632 offset = o;
633 WIDGET.render_update();
634 }
635
636 PxSize::new(
637 insert_size.width.max(child_size.width),
638 insert_size.height + spacing + child_size.height,
639 )
640 }
641 ChildInsert::Over | ChildInsert::Under => {
642 let child_size = children.node().with_child(0, |n| n.layout(wl));
643 let insert_size = children.node().with_child(1, |n| n.layout(wl));
644 child_size.max(insert_size)
645 }
646 ChildInsert::Start | ChildInsert::End => unreachable!(), };
648 }
649 UiNodeOp::Render { frame } => match placement.get() {
650 ChildInsert::Over => children.render(frame),
651 ChildInsert::Under => {
652 children.delegated();
653 children.node().with_child(1, |n| n.render(frame));
654 children.node().with_child(0, |n| n.render(frame));
655 }
656 _ => {
657 children.delegated();
658 children.node().for_each_child(|i, child| {
659 if i as u8 == offset_child {
660 frame.push_reference_frame(offset_key.into(), offset_key.bind(offset.into(), false), true, true, |frame| {
661 child.render(frame);
662 });
663 } else {
664 child.render(frame);
665 }
666 })
667 }
668 },
669 UiNodeOp::RenderUpdate { update } => match placement.get() {
670 ChildInsert::Over => children.render_update(update),
671 ChildInsert::Under => {
672 children.delegated();
673 children.node().with_child(1, |n| n.render_update(update));
674 children.node().with_child(0, |n| n.render_update(update));
675 }
676 _ => {
677 children.delegated();
678 children.node().for_each_child(|i, child| {
679 if i as u8 == offset_child {
680 update.with_transform(offset_key.update(offset.into(), false), true, |update| {
681 child.render_update(update);
682 });
683 } else {
684 child.render_update(update);
685 }
686 });
687 }
688 },
689 _ => {}
690 })
691}