zng_wgt_wrap/
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//! Wrap panel, properties and nodes.
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::sync::Arc;
13
14use crate_util::RecycleVec;
15use zng_app::widget::node::PanelListRange;
16use zng_ext_font::{BidiLevel, unicode_bidi_levels, unicode_bidi_sort};
17use zng_layout::{
18    context::{InlineConstraints, InlineConstraintsMeasure, InlineSegment, InlineSegmentPos, TextSegmentKind},
19    unit::{GridSpacing, PxGridSpacing},
20};
21use zng_wgt::{
22    node::{with_index_len_node, with_index_node, with_rev_index_node},
23    prelude::*,
24};
25use zng_wgt_text::*;
26
27mod crate_util;
28
29/// Wrapping inline layout.
30#[widget($crate::Wrap { ($children:expr) => { children = $children; }; })]
31pub struct Wrap(WidgetBase);
32impl Wrap {
33    fn widget_intrinsic(&mut self) {
34        self.widget_builder().push_build_action(|wgt| {
35            let child = node(
36                wgt.capture_ui_node_or_nil(property_id!(Self::children)),
37                wgt.capture_var_or_else(property_id!(Self::spacing), || {
38                    LINE_SPACING_VAR.map(|s| GridSpacing {
39                        column: Length::zero(),
40                        row: s.clone(),
41                    })
42                }),
43                wgt.capture_var_or_else(property_id!(Self::children_align), || TEXT_ALIGN_VAR),
44            );
45            wgt.set_child(child);
46        });
47    }
48
49    widget_impl! {
50        /// Alignment of children in this widget and of nested wrap panels and texts.
51        ///
52        /// Note that this is only used for children alignment in this widget if [`children_align`] is not set on it.
53        ///
54        /// [`children_align`]: fn@children_align
55        pub txt_align(align: impl IntoVar<Align>);
56
57        /// Space in between rows of this widget and of nested wrap panels and texts.
58        ///
59        /// Note that this is only used for row spacing in this widget if [`spacing`] is not set on it.
60        ///
61        /// [`spacing`]: fn@spacing
62        pub line_spacing(spacing: impl IntoVar<Length>);
63    }
64}
65
66/// Inlined wrap items.
67#[property(CHILD, default(ui_vec![]), widget_impl(Wrap))]
68pub fn children(wgt: &mut WidgetBuilding, children: impl IntoUiNode) {
69    let _ = children;
70    wgt.expect_property_capture();
71}
72
73/// Space in between items and rows.
74///
75/// Note that column space is limited for bidirectional inline items as it only inserts spacing between
76/// items once and bidirectional text can interleave items, consider using [`word_spacing`] for inline text.
77///
78/// [`LINE_SPACING_VAR`]: zng_wgt_text::LINE_SPACING_VAR
79/// [`line_spacing`]: fn@zng_wgt_text::txt_align
80/// [`word_spacing`]: fn@zng_wgt_text::word_spacing
81#[property(LAYOUT, widget_impl(Wrap))]
82pub fn spacing(wgt: &mut WidgetBuilding, spacing: impl IntoVar<GridSpacing>) {
83    let _ = spacing;
84    wgt.expect_property_capture();
85}
86
87/// Children align.
88#[property(LAYOUT, widget_impl(Wrap))]
89pub fn children_align(wgt: &mut WidgetBuilding, align: impl IntoVar<Align>) {
90    let _ = align;
91    wgt.expect_property_capture();
92}
93
94/// Wrap node.
95///
96/// Can be used directly to inline widgets without declaring a wrap widget info. This node is the child
97/// of the `Wrap!` widget.
98pub fn node(children: impl IntoUiNode, spacing: impl IntoVar<GridSpacing>, children_align: impl IntoVar<Align>) -> UiNode {
99    let children = PanelList::new(children).track_info_range(*PANEL_LIST_ID);
100    let spacing = spacing.into_var();
101    let children_align = children_align.into_var();
102    let mut layout = InlineLayout::default();
103
104    match_node(children, move |children, op| match op {
105        UiNodeOp::Init => {
106            WIDGET.sub_var_layout(&spacing).sub_var_layout(&children_align);
107        }
108        UiNodeOp::Update { updates } => {
109            let mut any = false;
110            children.update_list(updates, &mut any);
111
112            if any {
113                WIDGET.layout();
114            }
115        }
116        UiNodeOp::Measure { wm, desired_size } => {
117            let spacing = spacing.layout();
118            children.delegated();
119            *desired_size = layout.measure(wm, children.node_impl::<PanelList>(), children_align.get(), spacing);
120        }
121        UiNodeOp::Layout { wl, final_size } => {
122            let spacing = spacing.layout();
123            children.delegated();
124            // rust-analyzer does not find `layout` here if called with dot.
125            *final_size = InlineLayout::layout(&mut layout, wl, children.node_impl::<PanelList>(), children_align.get(), spacing);
126        }
127        _ => {}
128    })
129}
130
131/// Create a node that estimates the size of wrap panel children.
132///
133/// The estimation assumes that all items have a size of `child_size`.
134pub fn lazy_size(children_len: impl IntoVar<usize>, spacing: impl IntoVar<GridSpacing>, child_size: impl IntoVar<Size>) -> UiNode {
135    // we don't use `properties::size(UiNode::nil(), child_size)` because that size disables inlining.
136    let size = child_size.into_var();
137    let sample = match_node_leaf(move |op| match op {
138        UiNodeOp::Init => {
139            WIDGET.sub_var_layout(&size);
140        }
141        UiNodeOp::Measure { desired_size, .. } => {
142            *desired_size = size.layout();
143        }
144        UiNodeOp::Layout { final_size, .. } => {
145            *final_size = size.layout();
146        }
147        _ => {}
148    });
149
150    lazy_sample(children_len, spacing, sample)
151}
152
153/// Create a node that estimates the size of wrap panel children.
154///
155/// The estimation assumes that all items have the size of `child_sample`.
156pub fn lazy_sample(children_len: impl IntoVar<usize>, spacing: impl IntoVar<GridSpacing>, child_sample: impl IntoUiNode) -> UiNode {
157    let children_len = children_len.into_var();
158    let spacing = spacing.into_var();
159
160    match_node(child_sample, move |sample, op| match op {
161        UiNodeOp::Init => {
162            WIDGET.sub_var_layout(&children_len).sub_var_layout(&spacing);
163        }
164        UiNodeOp::Measure { wm, desired_size } => {
165            let child_size = sample.measure(wm);
166            *desired_size = InlineLayout::estimate_measure(wm, children_len.get(), child_size, spacing.layout());
167        }
168        UiNodeOp::Layout { wl, final_size } => {
169            let child_size = sample.layout(wl);
170            *final_size = InlineLayout::estimate_layout(wl, children_len.get(), child_size, spacing.layout());
171        }
172        _ => {}
173    })
174}
175
176/// Info about segments of a widget in a row.
177#[derive(Debug, Clone)]
178enum ItemSegsInfo {
179    Block(Px),
180    Built {
181        measure: Arc<Vec<InlineSegment>>,
182        layout: Arc<Vec<InlineSegmentPos>>,
183        x: f32,
184        width: f32,
185    },
186}
187impl ItemSegsInfo {
188    pub fn new_collapsed() -> Self {
189        Self::Block(Px(0))
190    }
191
192    pub fn new_block(width: Px) -> Self {
193        Self::Block(width)
194    }
195
196    pub fn new_inlined(measure: Arc<Vec<InlineSegment>>) -> Self {
197        Self::Built {
198            measure,
199            layout: Arc::new(vec![]),
200            x: 0.0,
201            width: 0.0,
202        }
203    }
204
205    pub fn measure(&self) -> &[InlineSegment] {
206        match self {
207            ItemSegsInfo::Built { measure, .. } => measure,
208            _ => &[],
209        }
210    }
211
212    pub fn layout_mut(&mut self) -> &mut Vec<InlineSegmentPos> {
213        self.build();
214        match self {
215            ItemSegsInfo::Built { measure, layout, .. } => {
216                // Borrow checker limitation does not allow `if let Some(l) = Arc::get_mut(..) { l } else { <insert-return> }`
217
218                if Arc::get_mut(layout).is_none() {
219                    *layout = Arc::new(vec![]);
220                }
221
222                let r = Arc::get_mut(layout).unwrap();
223                r.resize(measure.len(), InlineSegmentPos::new(0.0));
224
225                r
226            }
227            _ => unreachable!(),
228        }
229    }
230
231    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&InlineSegment, &mut InlineSegmentPos)> {
232        self.build();
233        match self {
234            ItemSegsInfo::Built { measure, layout, .. } => {
235                if Arc::get_mut(layout).is_none() {
236                    *layout = Arc::new(vec![]);
237                }
238
239                let r = Arc::get_mut(layout).unwrap();
240                r.resize(measure.len(), InlineSegmentPos::new(0.0));
241
242                measure.iter().zip(r)
243            }
244            _ => unreachable!(),
245        }
246    }
247
248    /// Only valid if has bidi items and is up-to-date.
249    pub fn x_width_segs(&self) -> (Px, Px, Arc<Vec<InlineSegmentPos>>) {
250        match self {
251            ItemSegsInfo::Built { layout, x, width, .. } => (Px(x.floor() as i32), Px(width.ceil() as i32), layout.clone()),
252            _ => unreachable!(),
253        }
254    }
255
256    #[cfg(debug_assertions)]
257    pub fn measure_width(&self) -> f32 {
258        match self {
259            ItemSegsInfo::Block(w) => w.0 as f32,
260            ItemSegsInfo::Built { measure, .. } => measure.iter().map(|s| s.width).sum(),
261        }
262    }
263
264    fn build(&mut self) {
265        match self {
266            ItemSegsInfo::Block(width) => {
267                let width = width.0 as f32;
268                *self = ItemSegsInfo::Built {
269                    measure: Arc::new(vec![InlineSegment::new(width, TextSegmentKind::OtherNeutral)]),
270                    layout: Arc::new(Vec::with_capacity(1)),
271                    x: 0.0,
272                    width,
273                }
274            }
275            ItemSegsInfo::Built { .. } => {}
276        }
277    }
278
279    fn set_x_width(&mut self, new_x: f32, new_width: f32) {
280        match self {
281            ItemSegsInfo::Built { x, width, .. } => {
282                *x = new_x;
283                *width = new_width;
284            }
285            _ => unreachable!(),
286        }
287    }
288}
289
290/// Info about a row managed by wrap.
291#[derive(Default, Debug, Clone)]
292struct RowInfo {
293    size: PxSize,
294    first_child: usize,
295    item_segs: Vec<ItemSegsInfo>,
296}
297impl crate::crate_util::Recycle for RowInfo {
298    fn recycle(&mut self) {
299        self.size = Default::default();
300        self.first_child = Default::default();
301        self.item_segs.clear();
302    }
303}
304
305#[derive(Default)]
306struct InlineLayout {
307    first_wrapped: bool,
308    rows: RecycleVec<RowInfo>,
309    desired_size: PxSize,
310
311    // has segments in the opposite direction, requires bidi sorting and positioning.
312    has_bidi_inline: bool,
313    bidi_layout_fresh: bool,
314    // reused heap alloc
315    bidi_sorted: Vec<usize>,
316    bidi_levels: Vec<BidiLevel>,
317    bidi_default_segs: Arc<Vec<InlineSegmentPos>>,
318}
319impl InlineLayout {
320    pub fn estimate_measure(wm: &mut WidgetMeasure, children_len: usize, child_size: PxSize, spacing: PxGridSpacing) -> PxSize {
321        if children_len == 0 {
322            return PxSize::zero();
323        }
324
325        let metrics = LAYOUT.metrics();
326        let constraints = metrics.constraints();
327
328        if metrics.inline_constraints().is_none()
329            && let Some(known) = constraints.fill_or_exact()
330        {
331            return known;
332        }
333
334        let max_x = constraints.x.max().unwrap_or(Px::MAX).max(child_size.width);
335
336        if let Some(inline) = wm.inline() {
337            let inline_constraints = metrics.inline_constraints().unwrap().measure();
338
339            inline.first_wrapped = inline_constraints.first_max < child_size.width;
340
341            let mut first_max_x = max_x;
342            if !inline.first_wrapped {
343                first_max_x = inline_constraints.first_max;
344            }
345
346            inline.first.height = child_size.height.max(inline_constraints.mid_clear_min);
347
348            let column_len = (first_max_x - child_size.width) / (child_size.width + spacing.column) + Px(1);
349            inline.first.width = (column_len - Px(1)) * (child_size.width + spacing.column) + child_size.width;
350
351            let children_len = Px(children_len as _) - column_len;
352            inline.last_wrapped = children_len.0 > 0;
353
354            let mut size = inline.first;
355
356            if inline.last_wrapped {
357                let column_len = (max_x - child_size.width) / (child_size.width + spacing.column) + Px(1);
358
359                size.width = size
360                    .width
361                    .max((column_len - Px(1)) * (child_size.width + spacing.column) + child_size.width);
362
363                let mid_len = children_len / column_len;
364                if mid_len.0 > 0 {
365                    size.height += (spacing.row + child_size.height) * mid_len;
366                }
367
368                let last_len = children_len % column_len;
369                inline.last.height = child_size.height;
370                if last_len.0 > 0 {
371                    inline.last.width = (last_len - Px(1)) * (child_size.width + spacing.column) + child_size.width;
372
373                    size.height += spacing.row + child_size.height;
374                } else {
375                    inline.last.width = max_x;
376                }
377            } else {
378                inline.last = inline.first;
379            }
380
381            debug_assert_eq!(inline.first.is_empty(), inline.last.is_empty());
382
383            size
384        } else {
385            let column_len = (max_x - child_size.width) / (child_size.width + spacing.column) + Px(1);
386            let row_len = (Px(children_len as i32) / column_len).max(Px(1));
387
388            // spacing in between means space available to divide for pairs (width + column) has 1 less item.
389            let desired_size = PxSize::new(
390                (column_len - Px(1)) * (child_size.width + spacing.column) + child_size.width,
391                (row_len - Px(1)) * (child_size.height + spacing.row) + child_size.height,
392            );
393            constraints.clamp_size(desired_size)
394        }
395    }
396
397    pub fn measure(&mut self, wm: &mut WidgetMeasure, children: &mut PanelList, child_align: Align, spacing: PxGridSpacing) -> PxSize {
398        let metrics = LAYOUT.metrics();
399        let constraints = metrics.constraints();
400
401        if metrics.inline_constraints().is_none()
402            && let Some(known) = constraints.fill_or_exact()
403        {
404            return known;
405        }
406
407        self.measure_rows(wm, &metrics, children, child_align, spacing);
408
409        if let Some(inline) = wm.inline() {
410            inline.first_wrapped = self.first_wrapped;
411            inline.last_wrapped = self.rows.len() > 1;
412
413            if let Some(first) = self.rows.first() {
414                inline.first = first.size;
415                inline.with_first_segs(|i| {
416                    i.extend(first.item_segs.iter().flat_map(|i| i.measure().iter().copied()));
417                });
418            } else {
419                inline.first = PxSize::zero();
420                inline.with_first_segs(|i| i.clear());
421            }
422            if let Some(last) = self.rows.last() {
423                inline.last = last.size;
424                inline.with_last_segs(|i| {
425                    i.extend(last.item_segs.iter().flat_map(|i| i.measure().iter().copied()));
426                })
427            } else {
428                inline.last = PxSize::zero();
429                inline.with_last_segs(|i| i.clear());
430            }
431        }
432
433        constraints.clamp_size(self.desired_size)
434    }
435
436    pub fn estimate_layout(wl: &mut WidgetLayout, children_len: usize, child_size: PxSize, spacing: PxGridSpacing) -> PxSize {
437        let is_inline = wl.inline().is_some();
438        let mut wm = wl.to_measure(if is_inline { Some(Default::default()) } else { None });
439        let size = if let Some(inline) = wl.inline() {
440            let mut size = Self::estimate_measure(&mut wm, children_len, child_size, spacing);
441            if let Some(m_inline) = wm.inline() {
442                inline.invalidate_negative_space();
443                inline.inner_size = size;
444
445                let inline_constraints = LAYOUT.inline_constraints().unwrap().layout();
446
447                let mut mid_height = size.height;
448
449                if !m_inline.first_wrapped {
450                    inline.rows.push(inline_constraints.first);
451                    mid_height -= child_size.height + spacing.row;
452                }
453                if m_inline.last_wrapped {
454                    mid_height -= spacing.row + child_size.height;
455                    inline.rows.push(PxRect::new(
456                        PxPoint::new(Px(0), spacing.row + child_size.height),
457                        PxSize::new(size.width, mid_height),
458                    ));
459                    inline.rows.push(inline_constraints.last);
460                }
461
462                size.height = inline_constraints.last.origin.y + inline_constraints.last.size.height;
463            }
464            size
465        } else {
466            Self::estimate_measure(&mut wm, children_len, child_size, spacing)
467        };
468
469        let width = LAYOUT.constraints().x.fill_or(size.width);
470        PxSize::new(width, size.height)
471    }
472
473    pub fn layout(&mut self, wl: &mut WidgetLayout, children: &mut PanelList, child_align: Align, spacing: PxGridSpacing) -> PxSize {
474        let metrics = LAYOUT.metrics();
475        let inline_constraints = metrics.inline_constraints();
476        let direction = metrics.direction();
477
478        if inline_constraints.is_none() {
479            // if not already measured by parent inline
480            self.measure_rows(&mut wl.to_measure(None), &metrics, children, child_align, spacing);
481        }
482        if self.has_bidi_inline && !self.bidi_layout_fresh {
483            self.layout_bidi(inline_constraints.clone(), direction, spacing.column);
484        }
485
486        let constraints = metrics.constraints();
487        let child_align_x = child_align.x(direction);
488        let child_align_y = child_align.y();
489
490        let panel_width = constraints.x.fill_or(self.desired_size.width);
491
492        let (first, mid, last) = if let Some(s) = inline_constraints.map(|c| c.layout()) {
493            (s.first, s.mid_clear, s.last)
494        } else {
495            // define our own first and last
496            let mut first = PxRect::from_size(self.rows[0].size);
497            let mut last = PxRect::from_size(self.rows.last().unwrap().size);
498
499            #[cfg(debug_assertions)]
500            if self.has_bidi_inline {
501                let segs_max = self.rows[0]
502                    .item_segs
503                    .iter()
504                    .map(|s| {
505                        let (x, width, _) = s.x_width_segs();
506                        x + width
507                    })
508                    .max()
509                    .unwrap_or_default();
510
511                if (first.width() - segs_max).abs() > Px(10) {
512                    tracing::error!("align error, used width: {:?}, but segs max is: {:?}", first.width(), segs_max);
513                }
514            }
515
516            first.origin.x = (panel_width - first.size.width) * child_align_x;
517            last.origin.x = (panel_width - last.size.width) * child_align_x;
518            last.origin.y = self.desired_size.height - last.size.height;
519
520            if let Some(y) = constraints.y.fill_or_exact() {
521                let align_y = (y - self.desired_size.height) * child_align_y;
522                first.origin.y += align_y;
523                last.origin.y += align_y;
524            }
525
526            (first, Px(0), last)
527        };
528        let panel_height = constraints.y.fill_or(last.origin.y - first.origin.y + last.size.height);
529
530        let child_constraints = PxConstraints2d::new_unbounded().with_fill_x(true).with_max_x(panel_width);
531
532        if let Some(inline) = wl.inline() {
533            inline.rows.clear();
534        }
535
536        let fill_width = if child_align.is_fill_x() {
537            Some(panel_width.0 as f32)
538        } else {
539            None
540        };
541
542        LAYOUT.with_constraints(child_constraints, || {
543            let mut row = first;
544            let mut row_segs = &self.rows[0].item_segs;
545            let mut row_advance = Px(0);
546            let mut next_row_i = 1;
547            let mut row_segs_i_start = 0;
548
549            let mut fill_scale = None;
550            if let Some(mut f) = fill_width {
551                if wl.is_inline() {
552                    f = first.width().0 as f32;
553                }
554                fill_scale = Some(f / self.rows[0].size.width.0 as f32);
555            }
556
557            children.for_each_child(|i, child, o| {
558                if next_row_i < self.rows.len() && self.rows[next_row_i].first_child == i {
559                    // new row
560                    if let Some(inline) = wl.inline() {
561                        inline.rows.push(row);
562                    }
563                    if next_row_i == self.rows.len() - 1 {
564                        row = last;
565                    } else {
566                        row.origin.y += row.size.height + spacing.row;
567                        if next_row_i == 1 {
568                            // clear first row
569                            row.origin.y += mid;
570                        }
571
572                        row.size = self.rows[next_row_i].size;
573                        row.origin.x = (panel_width - row.size.width) * child_align_x;
574                    }
575                    row_segs = &self.rows[next_row_i].item_segs;
576                    row_segs_i_start = self.rows[next_row_i].first_child;
577                    next_row_i += 1;
578                    row_advance = Px(0);
579
580                    fill_scale = None;
581                    if let Some(mut f) = fill_width
582                        && (wl.is_inline() || next_row_i < self.rows.len())
583                    {
584                        if wl.is_inline() && next_row_i == self.rows.len() {
585                            f = last.width().0 as f32;
586                        }
587                        // fill row, if it is not the last in a block layout
588                        fill_scale = Some(f / self.rows[next_row_i - 1].size.width.0 as f32);
589                    }
590                }
591
592                let (bidi_x, bidi_width, bidi_segs) = if self.has_bidi_inline {
593                    row_segs[i - row_segs_i_start].x_width_segs()
594                } else {
595                    (Px(0), Px(0), self.bidi_default_segs.clone())
596                };
597
598                if let Some(mut w) = child.as_widget()
599                    && let Some(child_inline) = w.with_context(WidgetUpdateMode::Ignore, || WIDGET.bounds().measure_inline())
600                {
601                    let child_desired_size = w.with_context(WidgetUpdateMode::Ignore, || WIDGET.bounds().measure_outer_size());
602                    if child_desired_size.is_empty() {
603                        // collapsed, continue.
604                        wl.collapse_child(i);
605                        return;
606                    }
607
608                    let mut child_first = PxRect::from_size(child_inline.first);
609                    let mut child_mid = Px(0);
610                    let mut child_last = PxRect::from_size(child_inline.last);
611
612                    if child_inline.last_wrapped {
613                        // child wraps
614                        debug_assert_eq!(self.rows[next_row_i].first_child, i + 1);
615
616                        child_first.origin.x = row.origin.x + row_advance;
617                        if let LayoutDirection::RTL = direction {
618                            child_first.origin.x -= row_advance;
619                        }
620                        child_first.origin.y += (row.size.height - child_first.size.height) * child_align_y;
621                        child_mid = (row.size.height - child_first.size.height).max(Px(0));
622                        child_last.origin.y = child_desired_size.height - child_last.size.height;
623
624                        if self.has_bidi_inline {
625                            child_first.origin.x = row.origin.x + bidi_x;
626                            child_first.size.width = bidi_width;
627                        }
628
629                        let next_row = if next_row_i == self.rows.len() - 1 {
630                            last
631                        } else {
632                            let mut r = row;
633                            r.origin.y += child_last.origin.y;
634                            r.size = self.rows[next_row_i].size;
635                            r.origin.x = (panel_width - r.size.width) * child_align_x;
636                            r
637                        };
638                        child_last.origin.x = next_row.origin.x;
639                        if let LayoutDirection::RTL = direction {
640                            child_last.origin.x += next_row.size.width - child_last.size.width;
641                        }
642                        child_last.origin.y += (next_row.size.height - child_last.size.height) * child_align_y;
643
644                        let (last_bidi_x, last_bidi_width, last_bidi_segs) = if self.has_bidi_inline {
645                            self.rows[next_row_i].item_segs[0].x_width_segs()
646                        } else {
647                            (Px(0), Px(0), self.bidi_default_segs.clone())
648                        };
649
650                        if self.has_bidi_inline {
651                            child_last.origin.x = next_row.origin.x + last_bidi_x;
652                            child_last.size.width = last_bidi_width;
653                        }
654
655                        if let Some(s) = fill_scale {
656                            child_first.size.width *= s;
657
658                            // child wraps, so last is different row
659                            if wl.is_inline() || next_row_i < self.rows.len() - 1 {
660                                let mut f = fill_width.unwrap();
661                                if wl.is_inline() && next_row_i == self.rows.len() - 1 {
662                                    f = last.width().0 as f32;
663                                }
664                                fill_scale = Some(f / next_row.size.width.0 as f32);
665                                let s = fill_scale.unwrap();
666                                child_last.size.width *= s;
667                            } else {
668                                // only fill last row if Wrap! is nested/inlined
669                                fill_scale = None;
670                            }
671                        }
672
673                        let (_, define_ref_frame) =
674                            wl.with_child(|wl| wl.layout_inline(child_first, child_mid, child_last, bidi_segs, last_bidi_segs, child));
675                        o.child_offset = PxVector::new(Px(0), row.origin.y);
676                        o.define_reference_frame = define_ref_frame;
677
678                        // new row
679                        if let Some(inline) = wl.inline() {
680                            inline.rows.push(row);
681                            if let Some(mut w) = child.as_widget() {
682                                w.with_context(WidgetUpdateMode::Ignore, || {
683                                    if let Some(inner) = WIDGET.bounds().inline() {
684                                        if inner.rows.len() >= 3 {
685                                            inline.rows.extend(inner.rows[1..inner.rows.len() - 1].iter().map(|r| {
686                                                let mut r = *r;
687                                                r.origin.y += row.origin.y;
688                                                r
689                                            }));
690                                        }
691                                    } else {
692                                        tracing::error!("child inlined in measure, but not in layout")
693                                    }
694                                });
695                            }
696                        }
697                        row = next_row;
698                        row_advance = child_last.size.width + spacing.column;
699                        row_segs = &self.rows[next_row_i].item_segs;
700                        row_segs_i_start = self.rows[next_row_i].first_child - 1; // next row first item is also this widget
701                        debug_assert_eq!(row_segs_i_start, i);
702                        next_row_i += 1;
703                    } else {
704                        // child inlined, but fits in the row
705
706                        let mut offset = PxVector::new(row_advance, Px(0));
707                        if let LayoutDirection::RTL = direction {
708                            offset.x = row.size.width - child_last.size.width - offset.x;
709                        }
710                        offset.y = (row.size.height - child_inline.first.height) * child_align_y;
711
712                        let mut max_size = child_inline.first;
713
714                        if self.has_bidi_inline {
715                            max_size.width = bidi_width;
716                            child_first.size.width = bidi_width;
717                            child_last.size.width = bidi_width;
718                        }
719
720                        if let Some(s) = fill_scale {
721                            child_first.size.width *= s;
722                            child_last.size.width *= s;
723                            max_size.width *= s;
724                        }
725
726                        let (_, define_ref_frame) = wl.with_child(|wl| {
727                            LAYOUT.with_constraints(child_constraints.with_fill(false, false).with_max_size(max_size), || {
728                                wl.layout_inline(child_first, child_mid, child_last, bidi_segs.clone(), bidi_segs, child)
729                            })
730                        });
731                        o.child_offset = row.origin.to_vector() + offset;
732                        if self.has_bidi_inline {
733                            o.child_offset.x = row.origin.x + bidi_x;
734                        }
735                        o.define_reference_frame = define_ref_frame;
736
737                        row_advance += child_last.size.width + spacing.column;
738                    }
739                } else {
740                    // inline block
741                    let max_width = if self.has_bidi_inline {
742                        bidi_width
743                    } else {
744                        row.size.width - row_advance
745                    };
746                    let (size, define_ref_frame) = LAYOUT.with_constraints(
747                        child_constraints.with_fill(false, false).with_max(max_width, row.size.height),
748                        || wl.with_child(|wl| wl.layout_block(child)),
749                    );
750                    if size.is_empty() {
751                        // collapsed, continue.
752                        o.child_offset = PxVector::zero();
753                        o.define_reference_frame = false;
754                        return;
755                    }
756
757                    let mut offset = PxVector::new(row_advance, Px(0));
758                    if let LayoutDirection::RTL = direction {
759                        offset.x = row.size.width - size.width - offset.x;
760                    }
761                    offset.y = (row.size.height - size.height) * child_align_y;
762                    o.child_offset = row.origin.to_vector() + offset;
763                    if self.has_bidi_inline {
764                        o.child_offset.x = row.origin.x + bidi_x;
765                    }
766                    o.define_reference_frame = define_ref_frame;
767                    row_advance += size.width + spacing.column;
768                }
769            });
770
771            if let Some(inline) = wl.inline() {
772                // last row
773                inline.rows.push(row);
774            }
775        });
776
777        children.commit_data().request_render();
778
779        constraints.clamp_size(PxSize::new(panel_width, panel_height))
780    }
781
782    fn measure_rows(
783        &mut self,
784        wm: &mut WidgetMeasure,
785        metrics: &LayoutMetrics,
786        children: &mut PanelList,
787        child_align: Align,
788        spacing: PxGridSpacing,
789    ) {
790        self.rows.begin_reuse();
791        self.bidi_layout_fresh = false;
792
793        self.first_wrapped = false;
794        self.desired_size = PxSize::zero();
795        self.has_bidi_inline = false;
796
797        let direction = metrics.direction();
798        let constraints = metrics.constraints();
799        let inline_constraints = metrics.inline_constraints();
800        let child_inline_constrain = constraints.x.max_or(Px::MAX);
801        let child_constraints = PxConstraints2d::new_unbounded()
802            .with_fill_x(child_align.is_fill_x())
803            .with_max_x(child_inline_constrain);
804        let mut row = self.rows.new_item();
805        LAYOUT.with_constraints(child_constraints, || {
806            children.for_each_child(|i, child, _| {
807                let mut inline_constrain = child_inline_constrain;
808                let mut wrap_clear_min = Px(0);
809                if self.rows.is_empty()
810                    && !self.first_wrapped
811                    && let Some(InlineConstraints::Measure(InlineConstraintsMeasure {
812                        first_max, mid_clear_min, ..
813                    })) = inline_constraints
814                {
815                    inline_constrain = first_max;
816                    wrap_clear_min = mid_clear_min;
817                }
818                if inline_constrain < Px::MAX {
819                    inline_constrain -= row.size.width;
820                }
821
822                let (inline, size) = wm.measure_inline(inline_constrain, row.size.height - spacing.row, child);
823
824                let can_collapse = size.is_empty()
825                    && match &inline {
826                        Some(i) => i.first_segs.is_empty(),
827                        None => true,
828                    };
829                if can_collapse {
830                    row.item_segs.push(ItemSegsInfo::new_collapsed());
831                    // collapsed, continue.
832                    return;
833                }
834
835                if let Some(inline) = inline {
836                    if !self.has_bidi_inline {
837                        self.has_bidi_inline =
838                            inline
839                                .first_segs
840                                .iter()
841                                .chain(inline.last_segs.iter())
842                                .any(|s| match s.kind.strong_direction() {
843                                    Some(d) => d != direction,
844                                    None => false,
845                                });
846                    }
847
848                    // item mid-rows can be wider
849                    self.desired_size.width = self.desired_size.width.max(size.width);
850
851                    if inline.first_wrapped {
852                        // wrap by us, detected by child
853                        if row.size.is_empty() {
854                            debug_assert!(self.rows.is_empty());
855                            self.first_wrapped = true;
856                        } else {
857                            row.size.width -= spacing.column;
858                            row.size.width = row.size.width.max(Px(0));
859                            self.desired_size.width = self.desired_size.width.max(row.size.width);
860                            self.desired_size.height += row.size.height + spacing.row;
861
862                            self.rows.push_renew(&mut row);
863                        }
864
865                        row.size = inline.first;
866                        row.first_child = i;
867                    } else {
868                        row.size.width += inline.first.width;
869                        row.size.height = row.size.height.max(inline.first.height);
870                    }
871                    row.item_segs.push(ItemSegsInfo::new_inlined(inline.first_segs.clone()));
872
873                    if inline.last_wrapped {
874                        // wrap by child
875                        self.desired_size.width = self.desired_size.width.max(row.size.width);
876                        self.desired_size.height += size.height - inline.first.height;
877
878                        self.rows.push_renew(&mut row);
879                        row.size = inline.last;
880                        row.size.width += spacing.column;
881                        row.first_child = i + 1;
882                        row.item_segs.push(ItemSegsInfo::new_inlined(inline.last_segs));
883                    } else {
884                        // child inlined, but fit in row
885                        row.size.width += spacing.column;
886                    }
887                } else if size.width <= inline_constrain {
888                    row.size.width += size.width + spacing.column;
889                    row.size.height = row.size.height.max(size.height);
890                    row.item_segs.push(ItemSegsInfo::new_block(size.width));
891                } else {
892                    // wrap by us
893                    if row.size.is_empty() {
894                        debug_assert!(self.rows.is_empty());
895                        self.first_wrapped = true;
896                    } else {
897                        row.size.width -= spacing.column;
898                        row.size.width = row.size.width.max(Px(0));
899                        self.desired_size.width = self.desired_size.width.max(row.size.width);
900                        self.desired_size.height += row.size.height.max(wrap_clear_min) + spacing.row;
901                        self.rows.push_renew(&mut row);
902                    }
903
904                    row.size = size;
905                    row.size.width += spacing.column;
906                    row.first_child = i;
907                    row.item_segs.push(ItemSegsInfo::new_block(size.width));
908                }
909            });
910        });
911
912        // last row
913        row.size.width -= spacing.column;
914        row.size.width = row.size.width.max(Px(0));
915        self.desired_size.width = self.desired_size.width.max(row.size.width);
916        self.desired_size.height += row.size.height; // no spacing because it's single line or already added in [^wrap by us]
917        self.rows.push(row);
918
919        self.rows.commit_reuse();
920
921        #[cfg(debug_assertions)]
922        for (i, row) in self.rows.iter().enumerate() {
923            let width = row.size.width;
924            let sum_width = row.item_segs.iter().map(|s| Px(s.measure_width() as i32)).sum::<Px>()
925                + spacing.column * Px(row.item_segs.len().saturating_sub(1) as _);
926
927            if (sum_width - width) > Px(1) {
928                if metrics.inline_constraints().is_some() && (i == 0 || i == self.rows.len() - 1) {
929                    tracing::error!(
930                        "Wrap![{}] panel row {i} inline width is {width}, but sum of segs is {sum_width}",
931                        WIDGET.id()
932                    );
933                    continue;
934                }
935
936                tracing::error!(
937                    "Wrap![{}] panel row {i} computed width {width}, but sum of segs is {sum_width}",
938                    WIDGET.id()
939                );
940            }
941        }
942    }
943
944    fn layout_bidi(&mut self, constraints: Option<InlineConstraints>, direction: LayoutDirection, spacing_x: Px) {
945        let spacing_x = spacing_x.0 as f32;
946        let mut our_rows = 0..self.rows.len();
947
948        if let Some(l) = constraints {
949            let l = l.layout();
950            our_rows = 0..0;
951
952            if !self.rows.is_empty() {
953                if l.first_segs.len() != self.rows[0].item_segs.iter().map(|s| s.measure().len()).sum::<usize>() {
954                    // parent set first_segs empty (not sorted), or wrong
955                    let mut x = 0.0;
956                    for s in self.rows[0].item_segs.iter_mut() {
957                        let mut spacing_x = spacing_x;
958                        for (seg, pos) in s.iter_mut() {
959                            pos.x = x;
960                            x += seg.width + spacing_x;
961                            spacing_x = 0.0;
962                        }
963                    }
964                } else {
965                    // parent set first_segs
966                    for (pos, (_seg, seg_pos)) in l
967                        .first_segs
968                        .iter()
969                        .zip(self.rows[0].item_segs.iter_mut().flat_map(|s| s.iter_mut()))
970                    {
971                        seg_pos.x = pos.x;
972                    }
973                }
974
975                if self.rows.len() > 1 {
976                    // last row not the same as first
977                    let last_i = self.rows.len() - 1;
978                    let last = &mut self.rows[last_i];
979                    if l.last_segs.len() != last.item_segs.iter().map(|s| s.measure().len()).sum::<usize>() {
980                        // parent set last_segs empty (not sorted), or wrong
981                        let mut x = 0.0;
982                        for s in last.item_segs.iter_mut() {
983                            let mut spacing_x = spacing_x;
984                            for (seg, pos) in s.iter_mut() {
985                                pos.x = x;
986                                x += seg.width + spacing_x;
987                                spacing_x = 0.0;
988                            }
989                        }
990                    } else {
991                        // parent set last_segs
992                        for (pos, (_seg, seg_pos)) in l.last_segs.iter().zip(last.item_segs.iter_mut().flat_map(|s| s.iter_mut())) {
993                            seg_pos.x = pos.x;
994                        }
995                    }
996
997                    if self.rows.len() > 2 {
998                        our_rows = 1..self.rows.len() - 1;
999                    }
1000                }
1001            }
1002        }
1003
1004        for row in &mut self.rows[our_rows] {
1005            // rows we sort and set x
1006
1007            unicode_bidi_levels(
1008                direction,
1009                row.item_segs.iter().flat_map(|i| i.measure().iter().map(|i| i.kind)),
1010                &mut self.bidi_levels,
1011            );
1012
1013            unicode_bidi_sort(
1014                direction,
1015                row.item_segs
1016                    .iter()
1017                    .flat_map(|i| i.measure().iter().map(|i| i.kind))
1018                    .zip(self.bidi_levels.iter().copied()),
1019                0,
1020                &mut self.bidi_sorted,
1021            );
1022
1023            let mut x = 0.0;
1024
1025            let mut spacing_count = row.item_segs.len().saturating_sub(1);
1026
1027            let mut last_item_i = usize::MAX;
1028            for &new_i in self.bidi_sorted.iter() {
1029                let mut segs_offset = 0;
1030
1031                // `bidi_sorted` is flatten of `row.segs`
1032                for (i, s) in row.item_segs.iter_mut().enumerate() {
1033                    if segs_offset + s.measure().len() <= new_i {
1034                        segs_offset += s.measure().len();
1035                    } else {
1036                        let new_i = new_i - segs_offset;
1037
1038                        if last_item_i != i {
1039                            last_item_i = i;
1040                            if x > 0.0 && spacing_count > 0 {
1041                                x += spacing_x;
1042                                spacing_count -= 1;
1043                            }
1044                        }
1045
1046                        s.layout_mut()[new_i].x = x;
1047                        x += s.measure()[new_i].width;
1048                        break;
1049                    }
1050                }
1051            }
1052        }
1053
1054        for row in self.rows.iter_mut() {
1055            // update seg.x and seg.width
1056            for seg in &mut row.item_segs {
1057                if seg.measure().is_empty() {
1058                    continue;
1059                }
1060
1061                let mut seg_min = f32::MAX;
1062                let mut seg_max = f32::MIN;
1063                for (m, l) in seg.iter_mut() {
1064                    seg_min = seg_min.min(l.x);
1065                    seg_max = seg_max.max(l.x + m.width);
1066                }
1067                seg.set_x_width(seg_min, seg_max - seg_min);
1068
1069                for (_, l) in seg.iter_mut() {
1070                    l.x -= seg_min;
1071                }
1072            }
1073        }
1074    }
1075}
1076
1077static_id! {
1078    static ref PANEL_LIST_ID: StateId<PanelListRange>;
1079}
1080
1081/// Get the child index in the parent wrap.
1082///
1083/// The child index is zero-based.
1084#[property(CONTEXT)]
1085pub fn get_index(child: impl IntoUiNode, state: impl IntoVar<usize>) -> UiNode {
1086    let state = state.into_var();
1087    with_index_node(child, *PANEL_LIST_ID, move |id| {
1088        state.set(id.unwrap_or(0));
1089    })
1090}
1091
1092/// Get the child index and number of children.
1093#[property(CONTEXT)]
1094pub fn get_index_len(child: impl IntoUiNode, state: impl IntoVar<(usize, usize)>) -> UiNode {
1095    let state = state.into_var();
1096    with_index_len_node(child, *PANEL_LIST_ID, move |id_len| {
1097        state.set(id_len.unwrap_or((0, 0)));
1098    })
1099}
1100
1101/// Get the child index, starting from the last child at `0`.
1102#[property(CONTEXT)]
1103pub fn get_rev_index(child: impl IntoUiNode, state: impl IntoVar<usize>) -> UiNode {
1104    let state = state.into_var();
1105    with_rev_index_node(child, *PANEL_LIST_ID, move |id| {
1106        state.set(id.unwrap_or(0));
1107    })
1108}
1109
1110/// If the child index is even.
1111///
1112/// Child index is zero-based, so the first is even, the next [`is_odd`].
1113///
1114/// [`is_odd`]: fn@is_odd
1115#[property(CONTEXT)]
1116pub fn is_even(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
1117    let state = state.into_var();
1118    with_index_node(child, *PANEL_LIST_ID, move |id| {
1119        state.set(id.map(|i| i % 2 == 0).unwrap_or(false));
1120    })
1121}
1122
1123/// If the child index is odd.
1124///
1125/// Child index is zero-based, so the first [`is_even`], the next one is odd.
1126///
1127/// [`is_even`]: fn@is_even
1128#[property(CONTEXT)]
1129pub fn is_odd(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
1130    let state = state.into_var();
1131    with_index_node(child, *PANEL_LIST_ID, move |id| {
1132        state.set(id.map(|i| i % 2 != 0).unwrap_or(false));
1133    })
1134}
1135
1136/// If the child is the first.
1137#[property(CONTEXT)]
1138pub fn is_first(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
1139    let state = state.into_var();
1140    with_index_node(child, *PANEL_LIST_ID, move |id| {
1141        state.set(id == Some(0));
1142    })
1143}
1144
1145/// If the child is the last.
1146#[property(CONTEXT)]
1147pub fn is_last(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
1148    let state = state.into_var();
1149    with_rev_index_node(child, *PANEL_LIST_ID, move |id| {
1150        state.set(id == Some(0));
1151    })
1152}
1153
1154/// Extension methods for [`WidgetInfo`] that may represent a [`Wrap!`] instance.
1155///
1156/// [`Wrap!`]: struct@Wrap
1157/// [`WidgetInfo`]: zng_app::widget::info::WidgetInfo
1158pub trait WidgetInfoWrapExt {
1159    /// Gets the wrap children, if this widget is a [`Wrap!`] instance.
1160    ///
1161    /// [`Wrap!`]: struct@Wrap
1162    fn wrap_children(&self) -> Option<zng_app::widget::info::iter::Children>;
1163}
1164impl WidgetInfoWrapExt for WidgetInfo {
1165    fn wrap_children(&self) -> Option<zng_app::widget::info::iter::Children> {
1166        PanelListRange::get(self, *PANEL_LIST_ID)
1167    }
1168}