#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
#![warn(unused_extern_crates)]
#![warn(missing_docs)]
use std::sync::Arc;
use crate_util::RecycleVec;
use zng_app::widget::node::PanelListRange;
use zng_ext_font::{unicode_bidi_levels, unicode_bidi_sort, BidiLevel};
use zng_layout::{
context::{InlineConstraints, InlineConstraintsMeasure, InlineSegment, InlineSegmentPos, TextSegmentKind},
unit::{GridSpacing, PxGridSpacing},
};
use zng_wgt::{
node::{with_index_len_node, with_index_node, with_rev_index_node},
prelude::*,
};
use zng_wgt_text::*;
mod crate_util;
#[widget($crate::Wrap {
($children:expr) => {
children = $children;
};
})]
pub struct Wrap(WidgetBase);
impl Wrap {
fn widget_intrinsic(&mut self) {
self.widget_builder().push_build_action(|wgt| {
let child = node(
wgt.capture_ui_node_list_or_empty(property_id!(Self::children)),
wgt.capture_var_or_else(property_id!(Self::spacing), || {
LINE_SPACING_VAR.map(|s| GridSpacing {
column: Length::zero(),
row: s.clone(),
})
}),
wgt.capture_var_or_else(property_id!(Self::children_align), || TEXT_ALIGN_VAR),
);
wgt.set_child(child);
});
}
widget_impl! {
pub txt_align(align: impl IntoVar<Align>);
pub line_spacing(spacing: impl IntoVar<Length>);
}
}
#[property(CHILD, capture, default(ui_vec![]), widget_impl(Wrap))]
pub fn children(children: impl UiNodeList) {}
#[property(LAYOUT, capture, widget_impl(Wrap))]
pub fn spacing(spacing: impl IntoVar<GridSpacing>) {}
#[property(LAYOUT, capture, widget_impl(Wrap))]
pub fn children_align(align: impl IntoVar<Align>) {}
pub fn node(children: impl UiNodeList, spacing: impl IntoVar<GridSpacing>, children_align: impl IntoVar<Align>) -> impl UiNode {
let children = PanelList::new(children).track_info_range(*PANEL_LIST_ID);
let spacing = spacing.into_var();
let children_align = children_align.into_var();
let mut layout = InlineLayout::default();
match_node_list(children, move |children, op| match op {
UiNodeOp::Init => {
WIDGET.sub_var_layout(&spacing).sub_var_layout(&children_align);
}
UiNodeOp::Update { updates } => {
let mut any = false;
children.update_all(updates, &mut any);
if any {
WIDGET.layout();
}
}
UiNodeOp::Measure { wm, desired_size } => {
let spacing = spacing.layout();
children.delegated();
*desired_size = layout.measure(wm, children.children(), children_align.get(), spacing);
}
UiNodeOp::Layout { wl, final_size } => {
let spacing = spacing.layout();
children.delegated();
*final_size = InlineLayout::layout(&mut layout, wl, children.children(), children_align.get(), spacing);
}
_ => {}
})
}
pub fn lazy_size(children_len: impl IntoVar<usize>, spacing: impl IntoVar<GridSpacing>, child_size: impl IntoVar<Size>) -> impl UiNode {
let size = child_size.into_var();
let sample = match_node_leaf(move |op| match op {
UiNodeOp::Init => {
WIDGET.sub_var_layout(&size);
}
UiNodeOp::Measure { desired_size, .. } => {
*desired_size = size.layout();
}
UiNodeOp::Layout { final_size, .. } => {
*final_size = size.layout();
}
_ => {}
});
lazy_sample(children_len, spacing, sample)
}
pub fn lazy_sample(children_len: impl IntoVar<usize>, spacing: impl IntoVar<GridSpacing>, child_sample: impl UiNode) -> impl UiNode {
let children_len = children_len.into_var();
let spacing = spacing.into_var();
match_node(child_sample, move |sample, op| match op {
UiNodeOp::Init => {
WIDGET.sub_var_layout(&children_len).sub_var_layout(&spacing);
}
UiNodeOp::Measure { wm, desired_size } => {
let child_size = sample.measure(wm);
*desired_size = InlineLayout::estimate_measure(wm, children_len.get(), child_size, spacing.layout());
}
UiNodeOp::Layout { wl, final_size } => {
let child_size = sample.layout(wl);
*final_size = InlineLayout::estimate_layout(wl, children_len.get(), child_size, spacing.layout());
}
_ => {}
})
}
#[derive(Debug, Clone)]
enum ItemSegsInfo {
Block(Px),
Built {
measure: Arc<Vec<InlineSegment>>,
layout: Arc<Vec<InlineSegmentPos>>,
x: f32,
width: f32,
},
}
impl ItemSegsInfo {
pub fn new_collapsed() -> Self {
Self::Block(Px(0))
}
pub fn new_block(width: Px) -> Self {
Self::Block(width)
}
pub fn new_inlined(measure: Arc<Vec<InlineSegment>>) -> Self {
Self::Built {
measure,
layout: Arc::new(vec![]),
x: 0.0,
width: 0.0,
}
}
pub fn measure(&self) -> &[InlineSegment] {
match self {
ItemSegsInfo::Built { measure, .. } => measure,
_ => &[],
}
}
pub fn layout_mut(&mut self) -> &mut Vec<InlineSegmentPos> {
self.build();
match self {
ItemSegsInfo::Built { measure, layout, .. } => {
if Arc::get_mut(layout).is_none() {
*layout = Arc::new(vec![]);
}
let r = Arc::get_mut(layout).unwrap();
r.resize(measure.len(), InlineSegmentPos { x: 0.0 });
r
}
_ => unreachable!(),
}
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&InlineSegment, &mut InlineSegmentPos)> {
self.build();
match self {
ItemSegsInfo::Built { measure, layout, .. } => {
if Arc::get_mut(layout).is_none() {
*layout = Arc::new(vec![]);
}
let r = Arc::get_mut(layout).unwrap();
r.resize(measure.len(), InlineSegmentPos { x: 0.0 });
measure.iter().zip(r)
}
_ => unreachable!(),
}
}
pub fn x_width_segs(&self) -> (Px, Px, Arc<Vec<InlineSegmentPos>>) {
match self {
ItemSegsInfo::Built { layout, x, width, .. } => (Px(x.floor() as i32), Px(width.ceil() as i32), layout.clone()),
_ => unreachable!(),
}
}
#[cfg(debug_assertions)]
pub fn measure_width(&self) -> f32 {
match self {
ItemSegsInfo::Block(w) => w.0 as f32,
ItemSegsInfo::Built { measure, .. } => measure.iter().map(|s| s.width).sum(),
}
}
fn build(&mut self) {
match self {
ItemSegsInfo::Block(width) => {
let width = width.0 as f32;
*self = ItemSegsInfo::Built {
measure: Arc::new(vec![InlineSegment {
width,
kind: TextSegmentKind::OtherNeutral,
}]),
layout: Arc::new(Vec::with_capacity(1)),
x: 0.0,
width,
}
}
ItemSegsInfo::Built { .. } => {}
}
}
fn set_x_width(&mut self, new_x: f32, new_width: f32) {
match self {
ItemSegsInfo::Built { x, width, .. } => {
*x = new_x;
*width = new_width;
}
_ => unreachable!(),
}
}
}
#[derive(Default, Debug, Clone)]
struct RowInfo {
size: PxSize,
first_child: usize,
item_segs: Vec<ItemSegsInfo>,
}
impl crate::crate_util::Recycle for RowInfo {
fn recycle(&mut self) {
self.size = Default::default();
self.first_child = Default::default();
self.item_segs.clear();
}
}
#[derive(Default)]
struct InlineLayout {
first_wrapped: bool,
rows: RecycleVec<RowInfo>,
desired_size: PxSize,
has_bidi_inline: bool,
bidi_layout_fresh: bool,
bidi_sorted: Vec<usize>,
bidi_levels: Vec<BidiLevel>,
bidi_default_segs: Arc<Vec<InlineSegmentPos>>,
}
impl InlineLayout {
pub fn estimate_measure(wm: &mut WidgetMeasure, children_len: usize, child_size: PxSize, spacing: PxGridSpacing) -> PxSize {
if children_len == 0 {
return PxSize::zero();
}
let metrics = LAYOUT.metrics();
let constraints = metrics.constraints();
if let (None, Some(known)) = (metrics.inline_constraints(), constraints.fill_or_exact()) {
return known;
}
let max_x = constraints.x.max().unwrap_or(Px::MAX).max(child_size.width);
if let Some(inline) = wm.inline() {
let inline_constraints = metrics.inline_constraints().unwrap().measure();
inline.first_wrapped = inline_constraints.first_max < child_size.width;
let mut first_max_x = max_x;
if !inline.first_wrapped {
first_max_x = inline_constraints.first_max;
}
inline.first.height = child_size.height.max(inline_constraints.mid_clear_min);
let column_len = (first_max_x - child_size.width) / (child_size.width + spacing.column) + Px(1);
inline.first.width = (column_len - Px(1)) * (child_size.width + spacing.column) + child_size.width;
let children_len = Px(children_len as _) - column_len;
inline.last_wrapped = children_len.0 > 0;
let mut size = inline.first;
if inline.last_wrapped {
let column_len = (max_x - child_size.width) / (child_size.width + spacing.column) + Px(1);
size.width = size
.width
.max((column_len - Px(1)) * (child_size.width + spacing.column) + child_size.width);
let mid_len = children_len / column_len;
if mid_len.0 > 0 {
size.height += (spacing.row + child_size.height) * mid_len;
}
let last_len = children_len % column_len;
inline.last.height = child_size.height;
if last_len.0 > 0 {
inline.last.width = (last_len - Px(1)) * (child_size.width + spacing.column) + child_size.width;
size.height += spacing.row + child_size.height;
} else {
inline.last.width = max_x;
}
} else {
inline.last = inline.first;
}
debug_assert_eq!(inline.first.is_empty(), inline.last.is_empty());
size
} else {
let column_len = (max_x - child_size.width) / (child_size.width + spacing.column) + Px(1);
let row_len = (Px(children_len as i32) / column_len).max(Px(1));
let desired_size = PxSize::new(
(column_len - Px(1)) * (child_size.width + spacing.column) + child_size.width,
(row_len - Px(1)) * (child_size.height + spacing.row) + child_size.height,
);
constraints.clamp_size(desired_size)
}
}
pub fn measure(&mut self, wm: &mut WidgetMeasure, children: &mut PanelList, child_align: Align, spacing: PxGridSpacing) -> PxSize {
let metrics = LAYOUT.metrics();
let constraints = metrics.constraints();
if let (None, Some(known)) = (metrics.inline_constraints(), constraints.fill_or_exact()) {
return known;
}
self.measure_rows(wm, &metrics, children, child_align, spacing);
if let Some(inline) = wm.inline() {
inline.first_wrapped = self.first_wrapped;
inline.last_wrapped = self.rows.len() > 1;
if let Some(first) = self.rows.first() {
inline.first = first.size;
inline.with_first_segs(|i| {
i.extend(first.item_segs.iter().flat_map(|i| i.measure().iter().copied()));
});
} else {
inline.first = PxSize::zero();
inline.with_first_segs(|i| i.clear());
}
if let Some(last) = self.rows.last() {
inline.last = last.size;
inline.with_last_segs(|i| {
i.extend(last.item_segs.iter().flat_map(|i| i.measure().iter().copied()));
})
} else {
inline.last = PxSize::zero();
inline.with_last_segs(|i| i.clear());
}
}
constraints.clamp_size(self.desired_size)
}
pub fn estimate_layout(wl: &mut WidgetLayout, children_len: usize, child_size: PxSize, spacing: PxGridSpacing) -> PxSize {
let is_inline = wl.inline().is_some();
let mut wm = wl.to_measure(if is_inline { Some(Default::default()) } else { None });
let size = if let Some(inline) = wl.inline() {
let mut size = Self::estimate_measure(&mut wm, children_len, child_size, spacing);
if let Some(m_inline) = wm.inline() {
inline.invalidate_negative_space();
inline.inner_size = size;
let inline_constraints = LAYOUT.inline_constraints().unwrap().layout();
let mut mid_height = size.height;
if !m_inline.first_wrapped {
inline.rows.push(inline_constraints.first);
mid_height -= child_size.height + spacing.row;
}
if m_inline.last_wrapped {
mid_height -= spacing.row + child_size.height;
inline.rows.push(PxRect::new(
PxPoint::new(Px(0), spacing.row + child_size.height),
PxSize::new(size.width, mid_height),
));
inline.rows.push(inline_constraints.last);
}
size.height = inline_constraints.last.origin.y + inline_constraints.last.size.height;
}
size
} else {
Self::estimate_measure(&mut wm, children_len, child_size, spacing)
};
let width = LAYOUT.constraints().x.fill_or(size.width);
PxSize::new(width, size.height)
}
pub fn layout(&mut self, wl: &mut WidgetLayout, children: &mut PanelList, child_align: Align, spacing: PxGridSpacing) -> PxSize {
let metrics = LAYOUT.metrics();
let inline_constraints = metrics.inline_constraints();
let direction = metrics.direction();
if inline_constraints.is_none() {
self.measure_rows(&mut wl.to_measure(None), &metrics, children, child_align, spacing);
}
if self.has_bidi_inline && !self.bidi_layout_fresh {
self.layout_bidi(inline_constraints.clone(), direction, spacing.column);
}
let constraints = metrics.constraints();
let child_align_x = child_align.x(direction);
let child_align_y = child_align.y();
let panel_width = constraints.x.fill_or(self.desired_size.width);
let (first, mid, last) = if let Some(s) = inline_constraints.map(|c| c.layout()) {
(s.first, s.mid_clear, s.last)
} else {
let mut first = PxRect::from_size(self.rows[0].size);
let mut last = PxRect::from_size(self.rows.last().unwrap().size);
#[cfg(debug_assertions)]
if self.has_bidi_inline {
let segs_max = self.rows[0]
.item_segs
.iter()
.map(|s| {
let (x, width, _) = s.x_width_segs();
x + width
})
.max()
.unwrap_or_default();
if (first.width() - segs_max).abs() > Px(10) {
tracing::error!("align error, used width: {:?}, but segs max is: {:?}", first.width(), segs_max);
}
}
first.origin.x = (panel_width - first.size.width) * child_align_x;
last.origin.x = (panel_width - last.size.width) * child_align_x;
last.origin.y = self.desired_size.height - last.size.height;
if let Some(y) = constraints.y.fill_or_exact() {
let align_y = (y - self.desired_size.height) * child_align_y;
first.origin.y += align_y;
last.origin.y += align_y;
}
(first, Px(0), last)
};
let panel_height = constraints.y.fill_or(last.origin.y - first.origin.y + last.size.height);
let child_constraints = PxConstraints2d::new_unbounded().with_fill_x(true).with_max_x(panel_width);
if let Some(inline) = wl.inline() {
inline.rows.clear();
}
LAYOUT.with_constraints(child_constraints, || {
let mut row = first;
let mut row_segs = &self.rows[0].item_segs;
let mut row_advance = Px(0);
let mut next_row_i = 1;
let mut row_segs_i_start = 0;
children.for_each(|i, child, o| {
if next_row_i < self.rows.len() && self.rows[next_row_i].first_child == i {
if let Some(inline) = wl.inline() {
inline.rows.push(row);
}
if next_row_i == self.rows.len() - 1 {
row = last;
} else {
row.origin.y += row.size.height + spacing.row;
if next_row_i == 1 {
row.origin.y += mid;
}
row.size = self.rows[next_row_i].size;
row.origin.x = (panel_width - row.size.width) * child_align_x;
}
row_segs = &self.rows[next_row_i].item_segs;
row_segs_i_start = self.rows[next_row_i].first_child;
next_row_i += 1;
row_advance = Px(0);
}
let (bidi_x, bidi_width, bidi_segs) = if self.has_bidi_inline {
row_segs[i - row_segs_i_start].x_width_segs()
} else {
(Px(0), Px(0), self.bidi_default_segs.clone())
};
let child_inline = child
.with_context(WidgetUpdateMode::Ignore, || WIDGET.bounds().measure_inline())
.flatten();
if let Some(child_inline) = child_inline {
let child_desired_size = child
.with_context(WidgetUpdateMode::Ignore, || WIDGET.bounds().measure_outer_size())
.unwrap_or_default();
if child_desired_size.is_empty() {
wl.collapse_child(i);
return;
}
let mut child_first = PxRect::from_size(child_inline.first);
let mut child_mid = Px(0);
let mut child_last = PxRect::from_size(child_inline.last);
if child_inline.last_wrapped {
debug_assert_eq!(self.rows[next_row_i].first_child, i + 1);
child_first.origin.x = row.origin.x + row_advance;
if let LayoutDirection::RTL = direction {
child_first.origin.x -= row_advance;
}
child_first.origin.y += (row.size.height - child_first.size.height) * child_align_y;
child_mid = (row.size.height - child_first.size.height).max(Px(0));
child_last.origin.y = child_desired_size.height - child_last.size.height;
if self.has_bidi_inline {
child_first.origin.x = row.origin.x + bidi_x;
child_first.size.width = bidi_width;
}
let next_row = if next_row_i == self.rows.len() - 1 {
last
} else {
let mut r = row;
r.origin.y += child_last.origin.y;
r.size = self.rows[next_row_i].size;
r.origin.x = (panel_width - r.size.width) * child_align_x;
r
};
child_last.origin.x = next_row.origin.x;
if let LayoutDirection::RTL = direction {
child_last.origin.x += next_row.size.width - child_last.size.width;
}
child_last.origin.y += (next_row.size.height - child_last.size.height) * child_align_y;
let (last_bidi_x, last_bidi_width, last_bidi_segs) = if self.has_bidi_inline {
self.rows[next_row_i].item_segs[0].x_width_segs()
} else {
(Px(0), Px(0), self.bidi_default_segs.clone())
};
if self.has_bidi_inline {
child_last.origin.x = next_row.origin.x + last_bidi_x;
child_last.size.width = last_bidi_width;
}
let (_, define_ref_frame) =
wl.with_child(|wl| wl.layout_inline(child_first, child_mid, child_last, bidi_segs, last_bidi_segs, child));
o.child_offset = PxVector::new(Px(0), row.origin.y);
o.define_reference_frame = define_ref_frame;
if let Some(inline) = wl.inline() {
inline.rows.push(row);
child.with_context(WidgetUpdateMode::Ignore, || {
if let Some(inner) = WIDGET.bounds().inline() {
if inner.rows.len() >= 3 {
inline.rows.extend(inner.rows[1..inner.rows.len() - 1].iter().map(|r| {
let mut r = *r;
r.origin.y += row.origin.y;
r
}));
}
} else {
tracing::error!("child inlined in measure, but not in layout")
}
});
}
row = next_row;
row_advance = child_last.size.width + spacing.column;
row_segs = &self.rows[next_row_i].item_segs;
row_segs_i_start = self.rows[next_row_i].first_child - 1; debug_assert_eq!(row_segs_i_start, i);
next_row_i += 1;
} else {
let mut offset = PxVector::new(row_advance, Px(0));
if let LayoutDirection::RTL = direction {
offset.x = row.size.width - child_last.size.width - offset.x;
}
offset.y = (row.size.height - child_inline.first.height) * child_align_y;
let mut max_size = child_inline.first;
if self.has_bidi_inline {
max_size.width = bidi_width;
child_first.size.width = bidi_width;
child_last.size.width = bidi_width;
}
let (_, define_ref_frame) = wl.with_child(|wl| {
LAYOUT.with_constraints(child_constraints.with_fill(false, false).with_max_size(max_size), || {
wl.layout_inline(child_first, child_mid, child_last, bidi_segs.clone(), bidi_segs, child)
})
});
o.child_offset = row.origin.to_vector() + offset;
if self.has_bidi_inline {
o.child_offset.x = row.origin.x + bidi_x;
}
o.define_reference_frame = define_ref_frame;
row_advance += child_last.size.width + spacing.column;
}
} else {
let max_width = if self.has_bidi_inline {
bidi_width
} else {
row.size.width - row_advance
};
let (size, define_ref_frame) = LAYOUT.with_constraints(
child_constraints.with_fill(false, false).with_max(max_width, row.size.height),
|| wl.with_child(|wl| wl.layout_block(child)),
);
if size.is_empty() {
o.child_offset = PxVector::zero();
o.define_reference_frame = false;
return;
}
let mut offset = PxVector::new(row_advance, Px(0));
if let LayoutDirection::RTL = direction {
offset.x = row.size.width - size.width - offset.x;
}
offset.y = (row.size.height - size.height) * child_align_y;
o.child_offset = row.origin.to_vector() + offset;
if self.has_bidi_inline {
o.child_offset.x = row.origin.x + bidi_x;
}
o.define_reference_frame = define_ref_frame;
row_advance += size.width + spacing.column;
}
});
if let Some(inline) = wl.inline() {
inline.rows.push(row);
}
});
children.commit_data().request_render();
constraints.clamp_size(PxSize::new(panel_width, panel_height))
}
fn measure_rows(
&mut self,
wm: &mut WidgetMeasure,
metrics: &LayoutMetrics,
children: &mut PanelList,
child_align: Align,
spacing: PxGridSpacing,
) {
self.rows.begin_reuse();
self.bidi_layout_fresh = false;
self.first_wrapped = false;
self.desired_size = PxSize::zero();
self.has_bidi_inline = false;
let direction = metrics.direction();
let constraints = metrics.constraints();
let inline_constraints = metrics.inline_constraints();
let child_inline_constrain = constraints.x.max_or(Px::MAX);
let child_constraints = PxConstraints2d::new_unbounded()
.with_fill_x(child_align.is_fill_x())
.with_max_x(child_inline_constrain);
let mut row = self.rows.new_item();
LAYOUT.with_constraints(child_constraints, || {
children.for_each(|i, child, _| {
let mut inline_constrain = child_inline_constrain;
let mut wrap_clear_min = Px(0);
if self.rows.is_empty() && !self.first_wrapped {
if let Some(InlineConstraints::Measure(InlineConstraintsMeasure {
first_max, mid_clear_min, ..
})) = inline_constraints
{
inline_constrain = first_max;
wrap_clear_min = mid_clear_min;
}
}
if inline_constrain < Px::MAX {
inline_constrain -= row.size.width;
}
let (inline, size) = wm.measure_inline(inline_constrain, row.size.height - spacing.row, child);
if size.is_empty() {
row.item_segs.push(ItemSegsInfo::new_collapsed());
return;
}
if let Some(inline) = inline {
if !self.has_bidi_inline {
self.has_bidi_inline =
inline
.first_segs
.iter()
.chain(inline.last_segs.iter())
.any(|s| match s.kind.strong_direction() {
Some(d) => d != direction,
None => false,
});
}
self.desired_size.width = self.desired_size.width.max(size.width);
if inline.first_wrapped {
if row.size.is_empty() {
debug_assert!(self.rows.is_empty());
self.first_wrapped = true;
} else {
row.size.width -= spacing.column;
row.size.width = row.size.width.max(Px(0));
self.desired_size.width = self.desired_size.width.max(row.size.width);
self.desired_size.height += row.size.height + spacing.row;
self.rows.push_renew(&mut row);
}
row.size = inline.first;
row.first_child = i;
} else {
row.size.width += inline.first.width;
row.size.height = row.size.height.max(inline.first.height);
}
row.item_segs.push(ItemSegsInfo::new_inlined(inline.first_segs.clone()));
if inline.last_wrapped {
self.desired_size.width = self.desired_size.width.max(row.size.width);
self.desired_size.height += size.height - inline.first.height;
self.rows.push_renew(&mut row);
row.size = inline.last;
row.size.width += spacing.column;
row.first_child = i + 1;
row.item_segs.push(ItemSegsInfo::new_inlined(inline.last_segs));
} else {
row.size.width += spacing.column;
}
} else if size.width <= inline_constrain {
row.size.width += size.width + spacing.column;
row.size.height = row.size.height.max(size.height);
row.item_segs.push(ItemSegsInfo::new_block(size.width));
} else {
if row.size.is_empty() {
debug_assert!(self.rows.is_empty());
self.first_wrapped = true;
} else {
row.size.width -= spacing.column;
row.size.width = row.size.width.max(Px(0));
self.desired_size.width = self.desired_size.width.max(row.size.width);
self.desired_size.height += row.size.height.max(wrap_clear_min) + spacing.row;
self.rows.push_renew(&mut row);
}
row.size = size;
row.size.width += spacing.column;
row.first_child = i;
row.item_segs.push(ItemSegsInfo::new_block(size.width));
}
});
});
row.size.width -= spacing.column;
row.size.width = row.size.width.max(Px(0));
self.desired_size.width = self.desired_size.width.max(row.size.width);
self.desired_size.height += row.size.height; self.rows.push(row);
self.rows.commit_reuse();
#[cfg(debug_assertions)]
for (i, row) in self.rows.iter().enumerate() {
let width = row.size.width;
let sum_width = row.item_segs.iter().map(|s| Px(s.measure_width() as i32)).sum::<Px>();
if (sum_width - width) > Px(1) {
if metrics.inline_constraints().is_some() && (i == 0 || i == self.rows.len() - 1) {
tracing::error!("Wrap! panel row {i} inline width is {width}, but sum of segs is {sum_width}");
continue;
}
tracing::error!("Wrap! panel row {i} computed width {width}, but sum of segs is {sum_width}");
}
}
}
fn layout_bidi(&mut self, constraints: Option<InlineConstraints>, direction: LayoutDirection, spacing_x: Px) {
let spacing_x = spacing_x.0 as f32;
let mut our_rows = 0..self.rows.len();
if let Some(l) = constraints {
let l = l.layout();
our_rows = 0..0;
if !self.rows.is_empty() {
if l.first_segs.len() != self.rows[0].item_segs.iter().map(|s| s.measure().len()).sum::<usize>() {
let mut x = 0.0;
for s in self.rows[0].item_segs.iter_mut() {
let mut spacing_x = spacing_x;
for (seg, pos) in s.iter_mut() {
pos.x = x;
x += seg.width + spacing_x;
spacing_x = 0.0;
}
}
} else {
for (pos, (_seg, seg_pos)) in l
.first_segs
.iter()
.zip(self.rows[0].item_segs.iter_mut().flat_map(|s| s.iter_mut()))
{
seg_pos.x = pos.x;
}
}
if self.rows.len() > 1 {
let last_i = self.rows.len() - 1;
let last = &mut self.rows[last_i];
if l.last_segs.len() != last.item_segs.iter().map(|s| s.measure().len()).sum::<usize>() {
let mut x = 0.0;
for s in last.item_segs.iter_mut() {
let mut spacing_x = spacing_x;
for (seg, pos) in s.iter_mut() {
pos.x = x;
x += seg.width + spacing_x;
spacing_x = 0.0;
}
}
} else {
for (pos, (_seg, seg_pos)) in l.last_segs.iter().zip(last.item_segs.iter_mut().flat_map(|s| s.iter_mut())) {
seg_pos.x = pos.x;
}
}
if self.rows.len() > 2 {
our_rows = 1..self.rows.len() - 1;
}
}
}
}
for row in &mut self.rows[our_rows] {
unicode_bidi_levels(
direction,
row.item_segs.iter().flat_map(|i| i.measure().iter().map(|i| i.kind)),
&mut self.bidi_levels,
);
unicode_bidi_sort(
direction,
row.item_segs
.iter()
.flat_map(|i| i.measure().iter().map(|i| i.kind))
.zip(self.bidi_levels.iter().copied()),
0,
&mut self.bidi_sorted,
);
let mut x = 0.0;
let mut spacing_count = row.item_segs.len().saturating_sub(1);
let mut last_item_i = usize::MAX;
for &new_i in self.bidi_sorted.iter() {
let mut segs_offset = 0;
for (i, s) in row.item_segs.iter_mut().enumerate() {
if segs_offset + s.measure().len() <= new_i {
segs_offset += s.measure().len();
} else {
let new_i = new_i - segs_offset;
if last_item_i != i {
last_item_i = i;
if x > 0.0 && spacing_count > 0 {
x += spacing_x;
spacing_count -= 1;
}
}
s.layout_mut()[new_i].x = x;
x += s.measure()[new_i].width;
break;
}
}
}
}
for row in self.rows.iter_mut() {
for seg in &mut row.item_segs {
if seg.measure().is_empty() {
continue;
}
let mut seg_min = f32::MAX;
let mut seg_max = f32::MIN;
for (m, l) in seg.iter_mut() {
seg_min = seg_min.min(l.x);
seg_max = seg_max.max(l.x + m.width);
}
seg.set_x_width(seg_min, seg_max - seg_min);
for (_, l) in seg.iter_mut() {
l.x -= seg_min;
}
}
}
}
}
static_id! {
static ref PANEL_LIST_ID: StateId<PanelListRange>;
}
#[property(CONTEXT)]
pub fn get_index(child: impl UiNode, state: impl IntoVar<usize>) -> impl UiNode {
let state = state.into_var();
with_index_node(child, *PANEL_LIST_ID, move |id| {
let _ = state.set(id.unwrap_or(0));
})
}
#[property(CONTEXT)]
pub fn get_index_len(child: impl UiNode, state: impl IntoVar<(usize, usize)>) -> impl UiNode {
let state = state.into_var();
with_index_len_node(child, *PANEL_LIST_ID, move |id_len| {
let _ = state.set(id_len.unwrap_or((0, 0)));
})
}
#[property(CONTEXT)]
pub fn get_rev_index(child: impl UiNode, state: impl IntoVar<usize>) -> impl UiNode {
let state = state.into_var();
with_rev_index_node(child, *PANEL_LIST_ID, move |id| {
let _ = state.set(id.unwrap_or(0));
})
}
#[property(CONTEXT)]
pub fn is_even(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
let state = state.into_var();
with_index_node(child, *PANEL_LIST_ID, move |id| {
let _ = state.set(id.map(|i| i % 2 == 0).unwrap_or(false));
})
}
#[property(CONTEXT)]
pub fn is_odd(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
let state = state.into_var();
with_index_node(child, *PANEL_LIST_ID, move |id| {
let _ = state.set(id.map(|i| i % 2 != 0).unwrap_or(false));
})
}
#[property(CONTEXT)]
pub fn is_first(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
let state = state.into_var();
with_index_node(child, *PANEL_LIST_ID, move |id| {
let _ = state.set(id == Some(0));
})
}
#[property(CONTEXT)]
pub fn is_last(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
let state = state.into_var();
with_rev_index_node(child, *PANEL_LIST_ID, move |id| {
let _ = state.set(id == Some(0));
})
}
pub trait WidgetInfoWrapExt {
fn wrap_children(&self) -> Option<zng_app::widget::info::iter::Children>;
}
impl WidgetInfoWrapExt for WidgetInfo {
fn wrap_children(&self) -> Option<zng_app::widget::info::iter::Children> {
PanelListRange::get(self, *PANEL_LIST_ID)
}
}