1use crate::{Control, ControlImpl, Panel, PanelImpl};
6use ferroui_base::input::{INavigableContainer, InputElement, InputElementImpl, NavigationDirection};
7use ferroui_base::interactivity::InteractiveImpl;
8use ferroui_base::layout::{LayoutHelper, Layoutable, LayoutableImpl, Orientation};
9use ferroui_base::utilities::math_utilities::max;
10use ferroui_base::utilities::MathUtilities;
11use ferroui_base::{
12 ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroProperty,
13 Rect, Ref, Size, StyledElementImpl, StyledProperty, VisualImpl,
14};
15
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
18#[repr(i32)]
19pub enum WrapPanelItemsAlignment {
20 #[default]
23 Start = 0,
24 Center = 1,
27 End = 2,
30 Justify = 3,
33 Stretch = 4,
36 StretchAll = 5,
39}
40
41#[repr(C)]
46pub struct WrapPanel {
47 base: Panel,
48}
49
50ferro_class!(WrapPanel: Panel);
51ferroui_base::ferro_class_info!(WrapPanel { new: WrapPanel::new });
52ferro_impl_classes!(
53 WrapPanel: StyledElementImpl,
54 VisualImpl,
55 InteractiveImpl,
56 InputElementImpl,
57 ControlImpl,
58 PanelImpl
59);
60
61ferroui_base::ferro_impl_classes!(WrapPanel: FerroObjectImpl);
62
63impl LayoutableImpl for WrapPanel {
64 fn measure_override(this: &Self, constraint: Size) -> Size {
65 let item_width = this.item_width();
66 let item_height = this.item_height();
67 let item_spacing = this.item_spacing();
68 let line_spacing = this.line_spacing();
69 let orientation = this.orientation();
70 let children = this.children().snapshot();
71 let mut cur_line_size = UVSize::new(orientation);
72 let mut panel_size = UVSize::new(orientation);
73 let uv_constraint = UVSize::with_size(orientation, constraint.width, constraint.height);
74 let item_width_set = !item_width.is_nan();
75 let item_height_set = !item_height.is_nan();
76 let mut item_exists = false;
77 let mut line_exists = false;
78 let mut items_alignment = this.items_alignment();
79 let use_layout_rounding = this.use_layout_rounding();
80 if uv_constraint.u == f64::INFINITY {
83 items_alignment = WrapPanelItemsAlignment::Start;
84 }
85 if matches!(items_alignment, WrapPanelItemsAlignment::Justify | WrapPanelItemsAlignment::StretchAll) {
88 panel_size.u = uv_constraint.u;
89 }
90
91 let child_constraint = Size::new(
92 if item_width_set { item_width } else { constraint.width },
93 if item_height_set { item_height } else { constraint.height },
94 );
95
96 for child in children.iter() {
97 child.measure(child_constraint);
99
100 let child_size = UVSize::with_size(
101 orientation,
102 if item_width_set { item_width } else { child.desired_size().width },
103 if item_height_set { item_height } else { child.desired_size().height },
104 );
105
106 let next_spacing = if item_exists && child.is_visible() { item_spacing } else { 0.0 };
107 if greater_than(use_layout_rounding, cur_line_size.u + child_size.u + next_spacing, uv_constraint.u) {
108 panel_size.u = max(cur_line_size.u, panel_size.u);
110 panel_size.v += cur_line_size.v + (if line_exists { line_spacing } else { 0.0 });
111 cur_line_size = child_size;
112
113 item_exists = child.is_visible();
114 line_exists = true;
115 } else {
116 cur_line_size.u += child_size.u + next_spacing;
118 cur_line_size.v = max(child_size.v, cur_line_size.v);
119
120 item_exists |= child.is_visible(); }
122 }
123
124 if line_exists && items_alignment == WrapPanelItemsAlignment::Stretch {
127 panel_size.u = uv_constraint.u;
128 }
129
130 panel_size.u = max(cur_line_size.u, panel_size.u);
132 panel_size.v += cur_line_size.v + (if line_exists { line_spacing } else { 0.0 });
133
134 Size::new(panel_size.width(), panel_size.height())
135 }
136
137 fn arrange_override(this: &Self, final_size: Size) -> Size {
138 let item_width = this.item_width();
139 let item_height = this.item_height();
140 let item_spacing = this.item_spacing();
141 let line_spacing = this.line_spacing();
142 let orientation = this.orientation();
143 let is_horizontal = orientation == Orientation::Horizontal;
144 let children = this.children().snapshot();
145 let mut first_in_line = 0;
146 let mut accumulated_v = 0.0;
147 let mut cur_line_size = UVSize::new(orientation);
148 let uv_final_size = UVSize::with_size(orientation, final_size.width, final_size.height);
149 let item_width_set = !item_width.is_nan();
150 let item_height_set = !item_height.is_nan();
151 let mut item_exists = false;
152 let mut line_exists = false;
153 let mut items_alignment = this.items_alignment();
154 let use_layout_rounding = this.use_layout_rounding();
155 if uv_final_size.u == f64::INFINITY {
158 items_alignment = WrapPanelItemsAlignment::Start;
159 }
160
161 let line = LineArrangement {
162 children: &children,
163 is_horizontal,
164 use_item_u: if is_horizontal { item_width_set } else { item_height_set },
165 item_u: if is_horizontal { item_width } else { item_height },
166 item_spacing,
167 final_u: uv_final_size.u,
168 items_alignment,
169 };
170
171 for (i, child) in children.iter().enumerate() {
172 let child_size = UVSize::with_size(
173 orientation,
174 if item_width_set { item_width } else { child.desired_size().width },
175 if item_height_set { item_height } else { child.desired_size().height },
176 );
177
178 let next_spacing = if item_exists && child.is_visible() { item_spacing } else { 0.0 };
179 if greater_than(use_layout_rounding, cur_line_size.u + child_size.u + next_spacing, uv_final_size.u) {
180 accumulated_v += if line_exists { line_spacing } else { 0.0 }; line.arrange(accumulated_v, cur_line_size.v, first_in_line, i);
183 accumulated_v += cur_line_size.v; cur_line_size = child_size;
185
186 first_in_line = i;
187
188 item_exists = child.is_visible();
189 line_exists = true;
190 } else {
191 cur_line_size.u += child_size.u + next_spacing;
193 cur_line_size.v = max(child_size.v, cur_line_size.v);
194
195 item_exists |= child.is_visible(); }
197 }
198
199 if first_in_line < children.len() {
201 accumulated_v += if line_exists { line_spacing } else { 0.0 }; line.arrange(accumulated_v, cur_line_size.v, first_in_line, children.len());
203 }
204
205 final_size
206 }
207}
208
209struct LineArrangement<'a> {
211 children: &'a [Ref<Control>],
212 is_horizontal: bool,
213 use_item_u: bool,
214 item_u: f64,
215 item_spacing: f64,
216 final_u: f64,
217 items_alignment: WrapPanelItemsAlignment,
218}
219
220impl LineArrangement<'_> {
221 fn child_u(&self, child: &Control) -> f64 {
222 if self.use_item_u {
223 self.item_u
224 } else if self.is_horizontal {
225 child.desired_size().width
226 } else {
227 child.desired_size().height
228 }
229 }
230
231 fn arrange(&self, accumulated_v: f64, line_v: f64, start: usize, end_excluded: usize) {
232 let children = self.children;
233 let mut u = 0.0;
234 let mut spacing = self.item_spacing;
235 let mut spacing_count = -1;
237 let mut total_u = 0.0;
238 let mut stretch_ratio = 1.0;
239 let mut line_items_alignment = self.items_alignment;
240 if self.items_alignment == WrapPanelItemsAlignment::Stretch && end_excluded == children.len() {
241 line_items_alignment = WrapPanelItemsAlignment::Start;
243 }
244
245 if line_items_alignment != WrapPanelItemsAlignment::Start {
246 for child in &children[start..end_excluded] {
247 total_u += self.child_u(child);
248 if child.is_visible() {
249 spacing_count += 1;
250 }
251 }
252 }
253
254 match line_items_alignment {
255 WrapPanelItemsAlignment::Start => {}
256 WrapPanelItemsAlignment::Center => {
257 total_u += spacing * spacing_count as f64;
258 u = (self.final_u - total_u) / 2.0;
259 }
260 WrapPanelItemsAlignment::End => {
261 total_u += spacing * spacing_count as f64;
262 u = self.final_u - total_u;
263 }
264 WrapPanelItemsAlignment::Justify => {
265 let total_spacing = max(self.final_u - total_u, 0.0);
266 if spacing_count > 0 {
267 spacing = total_spacing / spacing_count as f64;
268 }
269 }
270 WrapPanelItemsAlignment::Stretch | WrapPanelItemsAlignment::StretchAll => {
271 if !MathUtilities::is_zero(total_u) {
272 let final_u_without_spacing = max(self.final_u - spacing * spacing_count as f64, 0.0);
273 stretch_ratio = final_u_without_spacing / total_u;
274 }
275 }
276 }
277
278 for child in &children[start..end_excluded] {
279 let layout_slot_u = self.child_u(child) * stretch_ratio;
280 child.arrange(if self.is_horizontal {
281 Rect::new(u, accumulated_v, layout_slot_u, line_v)
282 } else {
283 Rect::new(accumulated_v, u, line_v, layout_slot_u)
284 });
285 u += layout_slot_u + (if child.is_visible() { spacing } else { 0.0 });
286 }
287 }
288}
289
290fn greater_than(use_layout_rounding: bool, value1: f64, value2: f64) -> bool {
291 if use_layout_rounding {
292 value1 > value2 && value1 - value2 > LayoutHelper::LAYOUT_EPSILON
293 } else {
294 MathUtilities::greater_than(value1, value2)
295 }
296}
297
298#[derive(Clone, Copy)]
300struct UVSize {
301 u: f64,
302 v: f64,
303 orientation: Orientation,
304}
305
306impl UVSize {
307 fn with_size(orientation: Orientation, width: f64, height: f64) -> Self {
308 if orientation == Orientation::Horizontal {
309 Self { u: width, v: height, orientation }
310 } else {
311 Self { u: height, v: width, orientation }
312 }
313 }
314
315 fn new(orientation: Orientation) -> Self {
316 Self { u: 0.0, v: 0.0, orientation }
317 }
318
319 fn width(&self) -> f64 {
320 if self.orientation == Orientation::Horizontal {
321 self.u
322 } else {
323 self.v
324 }
325 }
326
327 fn height(&self) -> f64 {
328 if self.orientation == Orientation::Horizontal {
329 self.v
330 } else {
331 self.u
332 }
333 }
334}
335
336ferroui_base::ferro_properties! { impl WrapPanel {
337 ferro_property!(
338 pub fn item_spacing_property() -> StyledProperty<f64> {
340 FerroProperty::register::<WrapPanel, _>("ItemSpacing", 0.0)
341 }
342 );
343
344 ferro_property!(
345 pub fn line_spacing_property() -> StyledProperty<f64> {
347 FerroProperty::register::<WrapPanel, _>("LineSpacing", 0.0)
348 }
349 );
350
351 ferro_property!(
352 pub fn orientation_property() -> StyledProperty<Orientation> {
354 FerroProperty::register::<WrapPanel, _>("Orientation", Orientation::Horizontal)
355 }
356 );
357
358 ferro_property!(
359 pub fn items_alignment_property() -> StyledProperty<WrapPanelItemsAlignment> {
361 FerroProperty::register::<WrapPanel, _>("ItemsAlignment", WrapPanelItemsAlignment::Start)
362 }
363 );
364
365 ferro_property!(
366 pub fn item_width_property() -> StyledProperty<f64> {
368 FerroProperty::register::<WrapPanel, _>("ItemWidth", f64::NAN)
369 }
370 );
371
372 ferro_property!(
373 pub fn item_height_property() -> StyledProperty<f64> {
375 FerroProperty::register::<WrapPanel, _>("ItemHeight", f64::NAN)
376 }
377 );
378} }
379
380impl WrapPanel {
381 fn static_constructor() {
382 Layoutable::affects_measure::<WrapPanel>(&[
383 Self::item_spacing_property().as_property(),
384 Self::line_spacing_property().as_property(),
385 Self::orientation_property().as_property(),
386 Self::item_width_property().as_property(),
387 Self::item_height_property().as_property(),
388 ]);
389 Layoutable::affects_arrange::<WrapPanel>(&[Self::items_alignment_property().as_property()]);
390 }
391
392 pub fn construct() -> Self {
394 Self { base: Panel::construct() }
395 }
396
397 pub fn new() -> Ref<Self> {
398 instantiate(Self::construct())
399 }
400
401 pub fn item_spacing(&self) -> f64 {
403 self.get_value(Self::item_spacing_property())
404 }
405
406 pub fn set_item_spacing(&self, value: f64) {
407 self.set_value(Self::item_spacing_property(), value)
408 }
409
410 pub fn line_spacing(&self) -> f64 {
412 self.get_value(Self::line_spacing_property())
413 }
414
415 pub fn set_line_spacing(&self, value: f64) {
416 self.set_value(Self::line_spacing_property(), value)
417 }
418
419 pub fn orientation(&self) -> Orientation {
421 self.get_value(Self::orientation_property())
422 }
423
424 pub fn set_orientation(&self, value: Orientation) {
425 self.set_value(Self::orientation_property(), value)
426 }
427
428 pub fn items_alignment(&self) -> WrapPanelItemsAlignment {
430 self.get_value(Self::items_alignment_property())
431 }
432
433 pub fn set_items_alignment(&self, value: WrapPanelItemsAlignment) {
434 self.set_value(Self::items_alignment_property(), value)
435 }
436
437 pub fn item_width(&self) -> f64 {
439 self.get_value(Self::item_width_property())
440 }
441
442 pub fn set_item_width(&self, value: f64) {
443 self.set_value(Self::item_width_property(), value)
444 }
445
446 pub fn item_height(&self) -> f64 {
448 self.get_value(Self::item_height_property())
449 }
450
451 pub fn set_item_height(&self, value: f64) {
452 self.set_value(Self::item_height_property(), value)
453 }
454}
455
456impl INavigableContainer for WrapPanel {
457 fn get_control(
459 &self,
460 direction: NavigationDirection,
461 from: Option<&Ref<InputElement>>,
462 _wrap: bool,
463 ) -> Option<Ref<InputElement>> {
464 let children = self.children();
465 let horiz = self.orientation() == Orientation::Horizontal;
466 let mut index: i64 = from
467 .and_then(|from| from.cast::<Control>())
468 .and_then(|from| children.index_of(&from))
469 .map_or(-1, |index| index as i64);
470
471 match direction {
472 NavigationDirection::First => index = 0,
473 NavigationDirection::Last => index = children.count() as i64 - 1,
474 NavigationDirection::Next => index += 1,
475 NavigationDirection::Previous => index -= 1,
476 NavigationDirection::Left => index = if horiz { index - 1 } else { -1 },
477 NavigationDirection::Right => index = if horiz { index + 1 } else { -1 },
478 NavigationDirection::Up => index = if horiz { -1 } else { index - 1 },
479 NavigationDirection::Down => index = if horiz { -1 } else { index + 1 },
480 _ => {}
481 }
482
483 if index >= 0 && (index as usize) < children.count() {
484 return Some(children.get(index as usize).upcast());
485 }
486
487 None
488 }
489}