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#![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 { ($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 pub txt_align(align: impl IntoVar<Align>);
56
57 pub line_spacing(spacing: impl IntoVar<Length>);
63 }
64}
65
66#[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#[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#[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
94pub 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 *final_size = InlineLayout::layout(&mut layout, wl, children.node_impl::<PanelList>(), children_align.get(), spacing);
126 }
127 _ => {}
128 })
129}
130
131pub fn lazy_size(children_len: impl IntoVar<usize>, spacing: impl IntoVar<GridSpacing>, child_size: impl IntoVar<Size>) -> UiNode {
135 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
153pub 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#[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 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 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#[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_bidi_inline: bool,
313 bidi_layout_fresh: bool,
314 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 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 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 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 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 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_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 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 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 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 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 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; debug_assert_eq!(row_segs_i_start, i);
702 next_row_i += 1;
703 } else {
704 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 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 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 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 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 self.desired_size.width = self.desired_size.width.max(size.width);
850
851 if inline.first_wrapped {
852 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 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 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 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 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; 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 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 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 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 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 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 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 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 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#[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#[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#[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#[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#[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#[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#[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
1154pub trait WidgetInfoWrapExt {
1159 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}