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