zng_wgt_container/
lib.rs

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//!
4//! Base container widget and properties.
5//!
6//! # Crate
7//!
8#![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/// Base container.
17#[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        /// Content overflow clipping.
30        pub clip_to_bounds(clip: impl IntoVar<bool>);
31    }
32}
33
34/// The widget's child.
35///
36/// Can be any type that implements [`UiNode`], any widget.
37///
38/// In `Container!` derived widgets or similar this property is captured and used as the actual child, in other widgets
39/// this property is an alias for [`child_under`](fn@child_under).
40///
41/// [`UiNode`]: zng_app::widget::node::UiNode
42#[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/// Margin space around the content of a widget.
48///
49/// This property is [`margin`](fn@margin) with nest group `CHILD_LAYOUT`.
50#[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/// Aligns the widget *content* within the available space.
56///
57/// This property is [`align`](fn@align) with nest group `CHILD_LAYOUT`.
58#[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/// Placement of a node inserted by the [`child_insert`] property.
64///
65/// [`child_insert`]: fn@child_insert
66#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
67pub enum ChildInsert {
68    /// Insert node above the child.
69    Top,
70    /// Insert node to the right of child.
71    Right,
72    /// Insert node below the child.
73    Bottom,
74    /// Insert node to the left of child.
75    Left,
76
77    /// Insert node to the left of child in [`LayoutDirection::LTR`] contexts and to the right of child
78    /// in [`LayoutDirection::RTL`] contexts.
79    ///
80    /// [`LayoutDirection::LTR`]: zng_wgt::prelude::LayoutDirection::LTR
81    /// [`LayoutDirection::RTL`]: zng_wgt::prelude::LayoutDirection::RTL
82    Start,
83    /// Insert node to the right of child in [`LayoutDirection::LTR`] contexts and to the left of child
84    /// in [`LayoutDirection::RTL`] contexts.
85    ///
86    /// [`LayoutDirection::LTR`]: zng_wgt::prelude::LayoutDirection::LTR
87    /// [`LayoutDirection::RTL`]: zng_wgt::prelude::LayoutDirection::RTL
88    End,
89
90    /// Insert node over the child.
91    ///
92    /// Spacing is ignored for this placement.
93    Over,
94    /// Insert node under the child.
95    ///
96    /// Spacing is ignored for this placement.
97    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    /// Convert [`ChildInsert::Start`] and [`ChildInsert::End`] to the fixed place they represent in the `direction` context.
118    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    /// Inserted node is to the left or right of child.
133    pub fn is_x_axis(self) -> bool {
134        !matches!(self, Self::Top | Self::Bottom)
135    }
136
137    /// Inserted node is above or bellow the child node.
138    pub fn is_y_axis(self) -> bool {
139        matches!(self, Self::Top | Self::Bottom)
140    }
141
142    /// Inserted node is over or under the child node.
143    pub fn is_z_axis(self) -> bool {
144        matches!(self, Self::Over | Self::Under)
145    }
146
147    /// Layout the spacing for the direction.
148    ///
149    /// Expects that [`resolve_direction`] was already called.
150    ///
151    /// [`resolve_direction`]: Self::resolve_direction
152    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    /// Identifies the [`child_spacing`] set on the widget.
165    ///
166    /// [`child_spacing`]: fn@child_spacing
167    pub static ref CHILD_SPACING_ID: StateId<Var<SideOffsets>>;
168    /// Identifies the [`child_out_spacing`] set on the widget.
169    ///
170    /// [`child_out_spacing`]: fn@child_out_spacing
171    pub static ref CHILD_OUT_SPACING_ID: StateId<Var<SideOffsets>>;
172}
173
174/// Spacing between [`child`] and one of the [`child_insert`] properties.
175///
176/// The spacing is only applied if the child insert property in the direction is set.
177///
178/// [`child`]: fn@child
179/// [`child_insert`]: fn@child_insert
180#[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/// Spacing between child and child layout nodes and one of the [`child_out_insert`] properties.
197///
198/// The spacing is only applied if the child insert property in the direction is set.
199///
200/// [`child`]: fn@child
201/// [`child_insert`]: fn@child_insert
202#[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/// Insert `node` in the `placement` relative to the widget's child.
219///
220/// The `node` is inserted inside the `CHILD_LAYOUT` scope, meaning inside [`padding`], just like the [`child`].
221/// See also [`child_out_insert`] for inserting a node outside the child layout.
222///
223/// Spacing between the widget's child and node can be configured using [`child_spacing`].
224///
225/// A property for each direction is also provided, see [`child_start`], [`child_end`], [`child_left`],
226/// [`child_right`], [`child_top`], [`child_bottom`], [`child_over`] and [`child_under`].
227///
228/// This property disables inline layout for the widget.
229///
230/// [`padding`]: fn@padding
231/// [`child`]: fn@child
232/// [`child_spacing`]: fn@child_spacing
233/// [`child_out_insert`]: fn@child_out_insert
234/// [`child_start`]: fn@child_start
235/// [`child_end`]: fn@child_end
236/// [`child_left`]: fn@child_left
237/// [`child_right`]: fn@child_right
238/// [`child_top`]: fn@child_top
239/// [`child_bottom`]: fn@child_bottom
240/// [`child_over`]: fn@child_over
241/// [`child_under`]: fn@child_under
242#[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/// Insert `node` in the `placement` relative to the widget's child, outside of the `CHILD_LAYOUT` scope, meaning outside [`padding`], but
253/// still inside the widget.
254///
255/// Spacing between the widget's child layout nodes and the `node` can be configured using [`child_out_spacing`].
256///
257/// A property for each direction is also provided, see [`child_out_start`], [`child_out_end`], [`child_out_left`],
258/// [`child_out_right`], [`child_out_top`], [`child_out_bottom`], [`child_out_over`] and [`child_out_under`].
259///
260/// This property disables inline layout for the widget.
261///
262/// [`padding`]: fn@padding
263/// [`child_out_spacing`]: fn@child_out_spacing
264/// [`child_out_start`]: fn@child_out_start
265/// [`child_out_end`]: fn@child_out_end
266/// [`child_out_left`]: fn@child_out_left
267/// [`child_out_right`]: fn@child_out_right
268/// [`child_out_top`]: fn@child_out_top
269/// [`child_out_bottom`]: fn@child_out_bottom
270/// [`child_out_over`]: fn@child_out_over
271/// [`child_out_under`]: fn@child_out_under
272#[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/// Insert `node` to the left of the widget's child.
283///
284/// This property disables inline layout for the widget. See [`child_insert`] for more details.
285///
286/// [`child_insert`]: fn@child_insert
287#[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/// Insert `node` to the right of the widget's child.
293///
294/// This property disables inline layout for the widget. See [`child_insert`] for more details.
295///
296/// [`child_insert`]: fn@child_insert
297#[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/// Insert `node` above the widget's child.
303///
304/// This property disables inline layout for the widget. See [`child_insert`] for more details.
305///
306/// [`child_insert`]: fn@child_insert
307#[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/// Insert `node` below the widget's child.
313///
314/// This property disables inline layout for the widget. See [`child_insert`] for more details.
315///
316/// [`child_insert`]: fn@child_insert
317#[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/// Insert `node` to the left of the widget's child in LTR contexts or to the right in RTL contexts.
323///
324/// This property disables inline layout for the widget. See [`child_insert`] for more details.
325///
326/// [`child_insert`]: fn@child_insert
327#[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/// Insert `node` to the right of the widget's child in LTR contexts or to the right of the widget's child in RTL contexts.
333///
334/// This property disables inline layout for the widget. See [`child_insert`] for more details.
335///
336/// [`child_insert`]: fn@child_insert
337#[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/// Insert `node` over the widget's child.
343///
344/// This property disables inline layout for the widget. See [`child_insert`] for more details.
345///
346/// [`child_insert`]: fn@child_insert
347#[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/// Insert `node` under the widget's child.
353///
354/// This property disables inline layout for the widget. See [`child_insert`] for more details.
355///
356/// [`child_insert`]: fn@child_insert
357#[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/// Insert `node` to the left of the widget's child, outside of the child layout.
363///
364/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
365///
366/// [`child_out_insert`]: fn@child_insert
367#[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/// Insert `node` to the right of the widget's child, outside of the child layout.
373///
374/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
375///
376/// [`child_out_insert`]: fn@child_out_insert
377#[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/// Insert `node` above the widget's child, outside of the child layout.
383///
384/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
385///
386/// [`child_out_insert`]: fn@child_out_insert
387#[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/// Insert `node` below the widget's child, outside of the child layout.
393///
394/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
395///
396/// [`child_out_insert`]: fn@child_out_insert
397#[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/// Insert `node` to the left of the widget's child in LTR contexts or to the right in RTL contexts, outside of the child layout.
403///
404/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
405///
406/// [`child_out_insert`]: fn@child_out_insert
407#[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/// Insert `node` to the right of the widget's child in LTR contexts or to the right of the widget's child in RTL contexts, outside of the child layout.
413///
414/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
415///
416/// [`child_out_insert`]: fn@child_out_insert
417#[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/// Insert `node` over the widget's child, not affected by child layout.
423///
424/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
425///
426/// [`child_out_insert`]: fn@child_out_insert
427#[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/// Insert `node` under the widget's child, not affected by child layout.
433///
434/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
435///
436/// [`child_out_insert`]: fn@child_out_insert
437#[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                        // measure to find fill height
513                        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                    // position
559                    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                        // measure fill width
580
581                        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                    // position
624                    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!(), // already resolved
647            };
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}