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#![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#[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 pub txt_align(align: impl IntoVar<Align>);
60
61 pub line_spacing(spacing: impl IntoVar<Length>);
67 }
68}
69
70#[property(CHILD, capture, default(ui_vec![]), widget_impl(Wrap))]
72pub fn children(children: impl UiNodeList) {}
73
74#[property(LAYOUT, capture, widget_impl(Wrap))]
83pub fn spacing(spacing: impl IntoVar<GridSpacing>) {}
84
85#[property(LAYOUT, capture, widget_impl(Wrap))]
87pub fn children_align(align: impl IntoVar<Align>) {}
88
89pub 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 *final_size = InlineLayout::layout(&mut layout, wl, children.children(), children_align.get(), spacing);
121 }
122 _ => {}
123 })
124}
125
126pub fn lazy_size(children_len: impl IntoVar<usize>, spacing: impl IntoVar<GridSpacing>, child_size: impl IntoVar<Size>) -> impl UiNode {
130 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
148pub 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#[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 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 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#[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_bidi_inline: bool,
311 bidi_layout_fresh: bool,
312 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 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 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 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 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 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_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 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 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 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 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 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; debug_assert_eq!(row_segs_i_start, i);
697 next_row_i += 1;
698 } else {
699 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 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 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 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 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 self.desired_size.width = self.desired_size.width.max(size.width);
845
846 if inline.first_wrapped {
847 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 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 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 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 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; 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 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 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 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 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 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 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 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 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#[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#[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#[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#[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#[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#[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#[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
1148pub trait WidgetInfoWrapExt {
1153 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}