zng_wgt_container/
lib.rs

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//!
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 {
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        /// Content overflow clipping.
34        pub clip_to_bounds(clip: impl IntoVar<bool>);
35    }
36}
37
38/// The widget's child.
39///
40/// Can be any type that implements [`UiNode`], any widget.
41///
42/// In `Container!` derived widgets or similar this property is captured and used as the actual child, in other widgets
43/// this property is an alias for [`child_under`](fn@child_under).
44///
45/// [`UiNode`]: zng_app::widget::node::UiNode
46#[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/// Margin space around the content of a widget.
52///
53/// This property is [`margin`](fn@margin) with nest group `CHILD_LAYOUT`.
54#[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/// Aligns the widget *content* within the available space.
60///
61/// This property is [`align`](fn@align) with nest group `CHILD_LAYOUT`.
62#[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/// Placement of a node inserted by the [`child_insert`] property.
68///
69/// [`child_insert`]: fn@child_insert
70#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
71pub enum ChildInsert {
72    /// Insert node above the child.
73    Top,
74    /// Insert node to the right of child.
75    Right,
76    /// Insert node below the child.
77    Bottom,
78    /// Insert node to the left of child.
79    Left,
80
81    /// Insert node to the left of child in [`LayoutDirection::LTR`] contexts and to the right of child
82    /// in [`LayoutDirection::RTL`] contexts.
83    ///
84    /// [`LayoutDirection::LTR`]: zng_wgt::prelude::LayoutDirection::LTR
85    /// [`LayoutDirection::RTL`]: zng_wgt::prelude::LayoutDirection::RTL
86    Start,
87    /// Insert node to the right of child in [`LayoutDirection::LTR`] contexts and to the left of child
88    /// in [`LayoutDirection::RTL`] contexts.
89    ///
90    /// [`LayoutDirection::LTR`]: zng_wgt::prelude::LayoutDirection::LTR
91    /// [`LayoutDirection::RTL`]: zng_wgt::prelude::LayoutDirection::RTL
92    End,
93
94    /// Insert node over the child.
95    ///
96    /// Spacing is ignored for this placement.
97    Over,
98    /// Insert node under the child.
99    ///
100    /// Spacing is ignored for this placement.
101    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    /// Convert [`ChildInsert::Start`] and [`ChildInsert::End`] to the fixed place they represent in the `direction` context.
122    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    /// Inserted node is to the left or right of child.
137    pub fn is_x_axis(self) -> bool {
138        !matches!(self, Self::Top | Self::Bottom)
139    }
140
141    /// Inserted node is above or bellow the child node.
142    pub fn is_y_axis(self) -> bool {
143        matches!(self, Self::Top | Self::Bottom)
144    }
145
146    /// Inserted node is over or under the child node.
147    pub fn is_z_axis(self) -> bool {
148        matches!(self, Self::Over | Self::Under)
149    }
150}
151
152/// Insert `node` in the `placement` relative to the widget's child.
153///
154/// This property disables inline layout for the widget.
155#[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                        // measure to find fill height
226                        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                    // position
272                    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                        // measure fill width
295
296                        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                    // position
339                    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!(), // already resolved
362            };
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/// Insert `node` in the `placement` relative to the widget's child, outside of the child layout.
403///
404/// This is still *inside* the parent widget, but outside of properties like padding.
405///
406/// This property disables inline layout for the widget.
407#[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/// Insert `node` to the left of the widget's child.
418///
419/// This property disables inline layout for the widget. See [`child_insert`] for more details.
420///
421/// [`child_insert`]: fn@child_insert
422#[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/// Insert `node` to the right of the widget's child.
428///
429/// This property disables inline layout for the widget. See [`child_insert`] for more details.
430///
431/// [`child_insert`]: fn@child_insert
432#[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/// Insert `node` above the widget's child.
438///
439/// This property disables inline layout for the widget. See [`child_insert`] for more details.
440///
441/// [`child_insert`]: fn@child_insert
442#[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/// Insert `node` below the widget's child.
448///
449/// This property disables inline layout for the widget. See [`child_insert`] for more details.
450///
451/// [`child_insert`]: fn@child_insert
452#[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/// Insert `node` to the left of the widget's child in LTR contexts or to the right in RTL contexts.
458///
459/// This property disables inline layout for the widget. See [`child_insert`] for more details.
460///
461/// [`child_insert`]: fn@child_insert
462#[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/// 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.
468///
469/// This property disables inline layout for the widget. See [`child_insert`] for more details.
470///
471/// [`child_insert`]: fn@child_insert
472#[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/// Insert `node` over the widget's child.
478///
479/// This property disables inline layout for the widget. See [`child_insert`] for more details.
480///
481/// [`child_insert`]: fn@child_insert
482#[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/// Insert `node` under the widget's child.
488///
489/// This property disables inline layout for the widget. See [`child_insert`] for more details.
490///
491/// [`child_insert`]: fn@child_insert
492#[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/// Insert `node` to the left of the widget's child, outside of the child layout.
498///
499/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
500///
501/// [`child_out_insert`]: fn@child_insert
502#[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/// Insert `node` to the right of the widget's child, outside of the child layout.
508///
509/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
510///
511/// [`child_out_insert`]: fn@child_out_insert
512#[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/// Insert `node` above the widget's child, outside of the child layout.
518///
519/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
520///
521/// [`child_out_insert`]: fn@child_out_insert
522#[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/// Insert `node` below the widget's child, outside of the child layout.
528///
529/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
530///
531/// [`child_out_insert`]: fn@child_out_insert
532#[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/// 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.
538///
539/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
540///
541/// [`child_out_insert`]: fn@child_out_insert
542#[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/// 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.
548///
549/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
550///
551/// [`child_out_insert`]: fn@child_out_insert
552#[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/// Insert `node` over the widget's child, not affected by child layout.
558///
559/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
560///
561/// [`child_out_insert`]: fn@child_out_insert
562#[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/// Insert `node` under the widget's child, not affected by child layout.
568///
569/// This property disables inline layout for the widget. See [`child_out_insert`] for more details.
570///
571/// [`child_out_insert`]: fn@child_out_insert
572#[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}