rosace_layout/widgets/
wrap.rs1use rosace_core::child_container::ChildContainer;
4use rosace_core::element::{Element, NativeElement};
5#[cfg(debug_assertions)]
6use rosace_core::render_object::AxisBound;
7use rosace_core::types::{Point, Size};
8#[cfg(debug_assertions)]
9use rosace_trace::{
10 event::{ComponentId, RosaceTrace, TraceConstraints},
11 trace,
12};
13
14use crate::constraints::Constraints;
15use crate::layout_result::LayoutResult;
16
17#[derive(Debug, Clone)]
23pub struct Wrap {
24 children: Vec<Element>,
25 spacing: f32,
26 run_spacing: f32,
27}
28
29impl Wrap {
30 pub fn new() -> Self {
32 Self {
33 children: Vec::new(),
34 spacing: 0.0,
35 run_spacing: 0.0,
36 }
37 }
38
39 pub fn spacing(mut self, s: f32) -> Self {
41 self.spacing = s;
42 self
43 }
44
45 pub fn run_spacing(mut self, s: f32) -> Self {
47 self.run_spacing = s;
48 self
49 }
50
51 pub fn layout(&self, constraints: Constraints, child_sizes: &[Size]) -> LayoutResult {
58 #[cfg(debug_assertions)]
59 let start = std::time::Instant::now();
60
61 #[cfg(debug_assertions)]
62 trace!(RosaceTrace::LayoutStart {
63 component: ComponentId(0),
64 constraints: TraceConstraints {
65 min_width: constraints.min_width,
66 max_width: match &constraints.max_width {
67 AxisBound::Bounded(v) => Some(*v),
68 _ => None,
69 },
70 min_height: constraints.min_height,
71 max_height: match &constraints.max_height {
72 AxisBound::Bounded(v) => Some(*v),
73 _ => None,
74 },
75 },
76 });
77
78 let max_w = constraints.max_width_f32();
79 let n = child_sizes.len();
80 let mut positions = Vec::with_capacity(n);
81
82 let mut cursor_x = 0.0_f32;
84 let mut cursor_y = 0.0_f32;
86 let mut row_height = 0.0_f32;
88 let mut is_first_in_row = true;
90
91 for size in child_sizes {
92 let x_with_gap = if is_first_in_row {
94 0.0
95 } else {
96 cursor_x + self.spacing
97 };
98
99 if !is_first_in_row && x_with_gap + size.width > max_w {
102 cursor_y += row_height + self.run_spacing;
103 cursor_x = 0.0;
104 row_height = 0.0;
105 is_first_in_row = true;
106 }
107
108 let pos_x = if is_first_in_row {
109 0.0
110 } else {
111 cursor_x + self.spacing
112 };
113
114 positions.push(Point { x: pos_x, y: cursor_y });
115 cursor_x = pos_x + size.width;
116 row_height = row_height.max(size.height);
117 is_first_in_row = false;
118 }
119
120 let total_height = cursor_y + row_height;
121 let total_width = if max_w.is_finite() { max_w } else { cursor_x };
123 let size = constraints.constrain(Size {
124 width: total_width,
125 height: total_height,
126 });
127
128 let result = LayoutResult {
129 size,
130 child_positions: positions,
131 };
132
133 #[cfg(debug_assertions)]
134 trace!(RosaceTrace::LayoutEnd {
135 component: ComponentId(0),
136 size: result.size,
137 duration: start.elapsed(),
138 });
139
140 result
141 }
142}
143
144impl Default for Wrap {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150impl ChildContainer for Wrap {
151 fn child(mut self, element: impl Into<Element>) -> Self {
152 self.children.push(element.into());
153 self
154 }
155
156 fn children<E: Into<Element>>(mut self, elements: Vec<E>) -> Self {
157 self.children
158 .extend(elements.into_iter().map(Into::into));
159 self
160 }
161
162 fn prepend(mut self, element: impl Into<Element>) -> Self {
163 self.children.insert(0, element.into());
164 self
165 }
166}
167
168impl From<Wrap> for Element {
169 fn from(w: Wrap) -> Self {
170 Element::Native(NativeElement {
171 tag: "Wrap",
172 payload: None,
173 children: w.children,
174 key: None,
175 })
176 }
177}