1use super::{
2 Flex, FlexAlignContent, FlexAlignItems, FlexBasisKind, FlexDirection, FlexJustifyContent, FlexWrap,
3};
4use crate::{Control, ControlImpl, Panel, PanelImpl, PanelImplExt};
5use ferroui_base::collections::NotifyCollectionChangedEventArgs;
6use ferroui_base::input::InputElementImpl;
7use ferroui_base::interactivity::InteractiveImpl;
8use ferroui_base::layout::{HorizontalAlignment, Layoutable, LayoutableImpl, VerticalAlignment};
9use ferroui_base::reactive::{IDisposable, ObservableExt};
10use ferroui_base::utilities::math_utilities::max as math_max;
11use ferroui_base::utilities::MathUtilities;
12use ferroui_base::{
13 ferro_class, ferro_impl_classes, instantiate, FerroObject, FerroObjectExtensions, FerroObjectImpl, FerroProperty, Point, Rect,
14 Ref, Size, StyledElementImpl, StyledProperty, Visual, VisualImpl,
15};
16use std::cell::{Cell, RefCell};
17use std::collections::HashMap;
18use std::rc::Rc;
19
20#[repr(C)]
27pub struct FlexPanel {
28 base: Panel,
29 state: FlexLayoutState,
30 child_visibility_subscriptions: RefCell<HashMap<Ref<Control>, Rc<dyn IDisposable>>>,
31 visible_children: RefCell<Option<Rc<[Ref<Control>]>>>,
32}
33
34ferro_class!(FlexPanel: Panel);
35ferroui_base::ferro_class_info!(FlexPanel { new: FlexPanel::new });
36ferro_impl_classes!(
37 FlexPanel: StyledElementImpl,
38 VisualImpl,
39 InteractiveImpl,
40 InputElementImpl,
41 ControlImpl
42);
43
44ferroui_base::ferro_impl_classes!(FlexPanel: FerroObjectImpl);
45
46ferroui_base::ferro_properties! {
47 impl FlexPanel {
48 pub fn direction_property() -> StyledProperty<FlexDirection> {
50 FerroProperty::register::<FlexPanel, _>("Direction", FlexDirection::Row)
51 }
52
53 pub fn justify_content_property() -> StyledProperty<FlexJustifyContent> {
55 FerroProperty::register::<FlexPanel, _>("JustifyContent", FlexJustifyContent::FlexStart)
56 }
57
58 pub fn align_items_property() -> StyledProperty<FlexAlignItems> {
60 FerroProperty::register::<FlexPanel, _>("AlignItems", FlexAlignItems::Stretch)
61 }
62
63 pub fn align_content_property() -> StyledProperty<FlexAlignContent> {
65 FerroProperty::register::<FlexPanel, _>("AlignContent", FlexAlignContent::Stretch)
66 }
67
68 pub fn wrap_property() -> StyledProperty<FlexWrap> {
70 FerroProperty::register::<FlexPanel, _>("Wrap", FlexWrap::NoWrap)
71 }
72
73 pub fn column_spacing_property() -> StyledProperty<f64> {
75 FerroProperty::register::<FlexPanel, _>("ColumnSpacing", 0.0)
76 }
77
78 pub fn row_spacing_property() -> StyledProperty<f64> {
80 FerroProperty::register::<FlexPanel, _>("RowSpacing", 0.0)
81 }
82 }
83}
84
85impl FlexPanel {
86 fn static_constructor() {
87 Layoutable::affects_measure::<FlexPanel>(&[
88 Self::direction_property().as_property(),
89 Self::justify_content_property().as_property(),
90 Self::wrap_property().as_property(),
91 Self::column_spacing_property().as_property(),
92 Self::row_spacing_property().as_property(),
93 ]);
94
95 Layoutable::affects_arrange::<FlexPanel>(&[
96 Self::align_items_property().as_property(),
97 Self::align_content_property().as_property(),
98 ]);
99
100 Panel::affects_parent_measure::<FlexPanel>(&[
101 Layoutable::horizontal_alignment_property().as_property(),
102 Layoutable::vertical_alignment_property().as_property(),
103 Flex::order_property().as_property(),
104 Flex::basis_property().as_property(),
105 Flex::shrink_property().as_property(),
106 Flex::grow_property().as_property(),
107 ]);
108
109 Panel::affects_parent_arrange::<FlexPanel>(&[Flex::align_self_property().as_property()]);
110 }
111
112 pub fn construct() -> Self {
114 Self {
115 base: Panel::construct(),
116 state: FlexLayoutState::default(),
117 child_visibility_subscriptions: RefCell::new(HashMap::new()),
118 visible_children: RefCell::new(None),
119 }
120 }
121
122 pub fn new() -> Ref<Self> {
123 instantiate(Self::construct())
124 }
125
126 pub fn direction(&self) -> FlexDirection {
132 self.get_value(Self::direction_property())
133 }
134
135 pub fn set_direction(&self, value: FlexDirection) {
136 self.set_value(Self::direction_property(), value)
137 }
138
139 pub fn justify_content(&self) -> FlexJustifyContent {
146 self.get_value(Self::justify_content_property())
147 }
148
149 pub fn set_justify_content(&self, value: FlexJustifyContent) {
150 self.set_value(Self::justify_content_property(), value)
151 }
152
153 pub fn align_items(&self) -> FlexAlignItems {
160 self.get_value(Self::align_items_property())
161 }
162
163 pub fn set_align_items(&self, value: FlexAlignItems) {
164 self.set_value(Self::align_items_property(), value)
165 }
166
167 pub fn align_content(&self) -> FlexAlignContent {
175 self.get_value(Self::align_content_property())
176 }
177
178 pub fn set_align_content(&self, value: FlexAlignContent) {
179 self.set_value(Self::align_content_property(), value)
180 }
181
182 pub fn wrap(&self) -> FlexWrap {
189 self.get_value(Self::wrap_property())
190 }
191
192 pub fn set_wrap(&self, value: FlexWrap) {
193 self.set_value(Self::wrap_property(), value)
194 }
195
196 pub fn column_spacing(&self) -> f64 {
201 self.get_value(Self::column_spacing_property())
202 }
203
204 pub fn set_column_spacing(&self, value: f64) {
205 self.set_value(Self::column_spacing_property(), value)
206 }
207
208 pub fn row_spacing(&self) -> f64 {
213 self.get_value(Self::row_spacing_property())
214 }
215
216 pub fn set_row_spacing(&self, value: f64) {
217 self.set_value(Self::row_spacing_property(), value)
218 }
219
220 fn non_empty_visible_children(&self) -> Option<Rc<[Ref<Control>]>> {
222 self.visible_children.borrow().clone().filter(|children| !children.is_empty())
223 }
224
225 fn measure_child(element: &Control, max: Uv, is_column: bool) -> Uv {
226 let basis = Flex::get_basis(element);
227 let flex_constraint = match basis.kind() {
228 FlexBasisKind::Auto => max.u,
229 FlexBasisKind::Absolute => basis.value(),
230 FlexBasisKind::Relative => max.u * basis.value() / 100.0,
231 };
232 element.measure(Uv::to_size(max.with_u(flex_constraint), is_column));
233
234 let size = Uv::from_size(element.desired_size(), is_column);
235
236 let flex_length = match basis.kind() {
237 FlexBasisKind::Auto => size.u,
238 FlexBasisKind::Absolute | FlexBasisKind::Relative => math_max(size.u, flex_constraint),
239 };
240 let size = size.with_u(flex_length);
241
242 Flex::set_base_length(element, flex_length);
243 Flex::set_current_length(element, flex_length);
244 size
245 }
246
247 fn determine_align_content(
248 current_align_content: FlexAlignContent,
249 free_v: f64,
250 lines_count: usize,
251 ) -> FlexAlignContent {
252 match current_align_content {
254 FlexAlignContent::Stretch if free_v > 0.0 => FlexAlignContent::Stretch,
256 FlexAlignContent::SpaceBetween if free_v > 0.0 && lines_count > 1 => FlexAlignContent::SpaceBetween,
257 FlexAlignContent::SpaceAround if free_v > 0.0 && lines_count > 0 => FlexAlignContent::SpaceAround,
258 FlexAlignContent::SpaceEvenly if free_v > 0.0 && lines_count > 0 => FlexAlignContent::SpaceEvenly,
259
260 FlexAlignContent::Stretch => FlexAlignContent::FlexStart,
262 FlexAlignContent::SpaceBetween => FlexAlignContent::FlexStart,
263 FlexAlignContent::SpaceAround => FlexAlignContent::Center,
264 FlexAlignContent::SpaceEvenly => FlexAlignContent::Center,
265 FlexAlignContent::FlexStart | FlexAlignContent::Center | FlexAlignContent::FlexEnd => {
266 current_align_content
267 }
268 }
269 }
270
271 fn get_cross_axis_pos_and_spacing(
272 align_content: FlexAlignContent,
273 spacing: Uv,
274 free_v: f64,
275 lines_count: usize,
276 ) -> (f64, f64) {
277 let lines = lines_count as f64;
278 match align_content {
279 FlexAlignContent::FlexStart => (0.0, spacing.v),
280 FlexAlignContent::FlexEnd => (free_v, spacing.v),
281 FlexAlignContent::Center => (free_v / 2.0, spacing.v),
282 FlexAlignContent::Stretch => (0.0, spacing.v),
283
284 FlexAlignContent::SpaceBetween if lines_count > 1 => (0.0, spacing.v + free_v / (lines - 1.0)),
285 FlexAlignContent::SpaceBetween => (0.0, spacing.v),
286
287 FlexAlignContent::SpaceAround if lines_count > 0 => (free_v / lines / 2.0, spacing.v + free_v / lines),
288 FlexAlignContent::SpaceAround => (free_v / 2.0, spacing.v),
289
290 FlexAlignContent::SpaceEvenly => (free_v / (lines + 1.0), spacing.v + free_v / (lines + 1.0)),
291 }
292 }
293
294 fn get_main_axis_pos_and_spacing(
295 justify_content: FlexJustifyContent,
296 line: &FlexLine,
297 spacing: Uv,
298 remaining_free_u: f64,
299 items_count: usize,
300 ) -> (f64, f64) {
301 if line.grow > 0.0 {
302 return (0.0, spacing.u);
303 }
304
305 let items = items_count as f64;
306 match justify_content {
307 FlexJustifyContent::FlexStart => (0.0, spacing.u),
308 FlexJustifyContent::FlexEnd => (remaining_free_u, spacing.u),
309 FlexJustifyContent::Center => (remaining_free_u / 2.0, spacing.u),
310
311 FlexJustifyContent::SpaceBetween if items_count > 1 => {
312 (0.0, spacing.u + remaining_free_u / (items - 1.0))
313 }
314 FlexJustifyContent::SpaceBetween => (0.0, spacing.u),
315
316 FlexJustifyContent::SpaceAround if items_count > 0 => {
317 (remaining_free_u / items / 2.0, spacing.u + remaining_free_u / items)
318 }
319 FlexJustifyContent::SpaceAround => (remaining_free_u / 2.0, spacing.u),
320
321 FlexJustifyContent::SpaceEvenly if items_count > 0 => {
322 (remaining_free_u / (items + 1.0), spacing.u + remaining_free_u / (items + 1.0))
323 }
324 FlexJustifyContent::SpaceEvenly => (remaining_free_u / 2.0, spacing.u),
325 }
326 }
327
328 fn get_line_measure_u(line: &FlexLine, panel_size_u: f64, spacing_u: f64) -> (usize, f64) {
330 let items_count = line.count();
331 let total_spacing_u = (items_count as f64 - 1.0) * spacing_u;
332 let total_u = line.u + total_spacing_u;
333 let free_u = panel_size_u - total_u;
334 (items_count, free_u)
335 }
336
337 fn get_line_mult_info(line: &FlexLine, free_u: f64) -> (f64, i32, f64) {
340 let line_mult = if free_u < 0.0 {
341 line.shrink
342 } else if free_u > 0.0 {
343 line.grow
344 } else {
345 0.0
346 };
347 if line_mult > 0.0 && line_mult < 1.0 {
350 (line_mult, line.auto_margins, free_u * line_mult)
351 } else {
352 (line_mult, line.auto_margins, free_u)
353 }
354 }
355
356 fn get_item_mult(element: &Layoutable, free_u: f64) -> f64 {
357 if free_u < 0.0 {
358 Flex::get_shrink(element)
359 } else if free_u > 0.0 {
360 Flex::get_grow(element)
361 } else {
362 0.0
363 }
364 }
365
366 fn get_item_auto_margins(element: &Layoutable, is_column: bool) -> i32 {
367 if is_column {
368 match element.vertical_alignment() {
369 VerticalAlignment::Stretch => 0,
370 VerticalAlignment::Top | VerticalAlignment::Bottom => 1,
371 VerticalAlignment::Center => 2,
372 }
373 } else {
374 match element.horizontal_alignment() {
375 HorizontalAlignment::Stretch => 0,
376 HorizontalAlignment::Left | HorizontalAlignment::Right => 1,
377 HorizontalAlignment::Center => 2,
378 }
379 }
380 }
381
382 fn on_child_visibility_changed(&self, _is_visible: bool) {
383 self.update_visible_children();
384 }
385
386 fn update_visible_children(&self) {
387 let mut visible_children: Vec<Ref<Control>> =
388 self.children().snapshot().iter().filter(|child| child.is_visible()).cloned().collect();
389 visible_children.sort_by_key(|child| Flex::get_order(child));
391 *self.visible_children.borrow_mut() = Some(visible_children.into());
392
393 self.invalidate_measure();
394 }
395}
396
397impl LayoutableImpl for FlexPanel {
398 fn measure_override(this: &Self, available_size: Size) -> Size {
399 let Some(children) = this.non_empty_visible_children() else {
400 this.state.lines.borrow_mut().clear();
401 this.state.measure_count.set(this.state.measure_count.get().wrapping_add(1));
402 return Size::default();
403 };
404
405 let direction = this.direction();
406 let is_column = matches!(direction, FlexDirection::Column | FlexDirection::ColumnReverse);
407 let wrap = this.wrap();
408
409 let max = Uv::from_size(available_size, is_column);
410 let spacing = Uv::from_width_height(this.column_spacing(), this.row_spacing(), is_column);
411
412 let mut line_data = LineData::default();
413 let (mut child_index, mut first_child_index, mut item_index) = (0usize, 0usize, 0usize);
414
415 let mut lines = std::mem::take(&mut *this.state.lines.borrow_mut());
418 lines.clear();
419
420 let mut panel_size_u = 0.0;
421 let max_selector = |l: &FlexLine| l.u + (l.count() as f64 - 1.0) * spacing.u;
422
423 for element in children.iter() {
424 let size = Self::measure_child(element, max, is_column);
425
426 if wrap != FlexWrap::NoWrap
427 && MathUtilities::greater_than_or_close(line_data.u + size.u + item_index as f64 * spacing.u, max.u)
428 {
429 let line = FlexLine::new(first_child_index as isize, child_index as isize - 1, line_data);
432 panel_size_u = math_max(panel_size_u, max_selector(&line));
433 lines.push(line);
434 line_data = LineData::default();
435 first_child_index = child_index;
436 item_index = 0;
437 }
438
439 line_data.u += size.u;
440 line_data.v = math_max(line_data.v, size.v);
441 line_data.shrink += Flex::get_shrink(element);
442 line_data.grow += Flex::get_grow(element);
443 line_data.auto_margins += Self::get_item_auto_margins(element, is_column);
444 item_index += 1;
445 child_index += 1;
446 }
447
448 if item_index != 0 {
449 let line =
450 FlexLine::new(first_child_index as isize, (first_child_index + item_index) as isize - 1, line_data);
451 panel_size_u = math_max(panel_size_u, max_selector(&line));
452 lines.push(line);
453 }
454
455 let total_spacing_v = (lines.len() as f64 - 1.0) * spacing.v;
456
457 for flex_line in lines.iter_mut() {
459 let (_items_count, free_u) = Self::get_line_measure_u(flex_line, max.u, spacing.u);
460 let (line_mult, _auto_margins, remaining_free_u) = Self::get_line_mult_info(flex_line, free_u);
461
462 if max.u.is_finite() && line_mult != 0.0 && remaining_free_u != 0.0 {
463 let mut max_v: f64 = 0.0;
464 for element in &children[flex_line.range()] {
465 let base_length = Flex::get_base_length(element);
466 let mult = Self::get_item_mult(element, free_u);
467 let length = math_max(0.0, base_length + remaining_free_u * mult / line_mult);
468 element.measure(Uv::to_size(max.with_u(length), is_column));
469
470 max_v = math_max(Uv::from_size(element.desired_size(), is_column).v, max_v);
471 }
472
473 flex_line.v = max_v;
474 }
475 }
476
477 if wrap == FlexWrap::WrapReverse {
478 lines.reverse();
479 }
480
481 let total_line_v: f64 = lines.iter().map(|l| l.v).sum();
482 let panel_size =
483 if lines.is_empty() { Uv::default() } else { Uv::new(panel_size_u, total_line_v + total_spacing_v) };
484
485 *this.state.lines.borrow_mut() = lines;
486 this.state.measure_count.set(this.state.measure_count.get().wrapping_add(1));
487
488 Uv::to_size(panel_size, is_column)
489 }
490
491 fn arrange_override(this: &Self, final_size: Size) -> Size {
492 let Some(children) = this.non_empty_visible_children() else {
493 return final_size;
494 };
495 if this.state.lines.borrow().is_empty() {
496 return final_size;
497 }
498
499 let direction = this.direction();
500 let is_column = matches!(direction, FlexDirection::Column | FlexDirection::ColumnReverse);
501 let is_reverse = matches!(direction, FlexDirection::RowReverse | FlexDirection::ColumnReverse);
502
503 let panel_size = Uv::from_size(final_size, is_column);
504 let spacing = Uv::from_width_height(this.column_spacing(), this.row_spacing(), is_column);
505 let justify_content = this.justify_content();
506 let align_items = this.align_items();
507
508 let lines = std::mem::take(&mut *this.state.lines.borrow_mut());
511 let measure_count = this.state.measure_count.get();
512
513 let lines_count = lines.len();
514 let total_line_v: f64 = lines.iter().map(|s| s.v).sum();
515 let total_spacing_v = (lines_count as f64 - 1.0) * spacing.v;
516 let total_v = total_line_v + total_spacing_v;
517 let free_v = panel_size.v - total_v;
518
519 let align_content = Self::determine_align_content(this.align_content(), free_v, lines_count);
520
521 let (mut v, spacing_v) = Self::get_cross_axis_pos_and_spacing(align_content, spacing, free_v, lines_count);
522
523 let scale_v = if align_content == FlexAlignContent::Stretch && total_line_v != 0.0 {
524 (panel_size.v - total_spacing_v) / total_line_v
525 } else {
526 1.0
527 };
528
529 for line in &lines {
530 let line_children = &children[line.range()];
531 let line_v = scale_v * line.v;
532 let (items_count, free_u) = Self::get_line_measure_u(line, panel_size.u, spacing.u);
533 let (line_mult, line_auto_margins, mut remaining_free_u) = Self::get_line_mult_info(line, free_u);
534
535 let mut current_free_u = remaining_free_u;
536 if line_mult != 0.0 && remaining_free_u != 0.0 {
537 for element in line_children {
538 let base_length = Flex::get_base_length(element);
539 let mult = Self::get_item_mult(element, free_u);
540 if mult != 0.0 {
541 let length = math_max(0.0, base_length + remaining_free_u * mult / line_mult);
542 Flex::set_current_length(element, length);
543 current_free_u -= length - base_length;
544 }
545 }
546 }
547 remaining_free_u = current_free_u;
548
549 if line_auto_margins != 0 && remaining_free_u != 0.0 {
550 for element in line_children {
551 let base_length = Flex::get_current_length(element);
552 let auto_margins = Self::get_item_auto_margins(element, is_column);
553 if auto_margins != 0 {
554 let length = math_max(
555 0.0,
556 base_length + remaining_free_u * auto_margins as f64 / line_auto_margins as f64,
557 );
558 Flex::set_current_length(element, length);
559 current_free_u -= length - base_length;
560 }
561 }
562 }
563 remaining_free_u = current_free_u;
564
565 let (mut u, spacing_u) =
566 Self::get_main_axis_pos_and_spacing(justify_content, line, spacing, remaining_free_u, items_count);
567
568 for element in line_children {
569 let size = Uv::from_size(element.desired_size(), is_column).with_u(Flex::get_current_length(element));
570 let align = Flex::get_align_self(element).unwrap_or(align_items);
571
572 let position_v = match align {
573 FlexAlignItems::FlexStart => v,
574 FlexAlignItems::FlexEnd => v + line_v - size.v,
575 FlexAlignItems::Center => v + (line_v - size.v) / 2.0,
576 FlexAlignItems::Stretch => v,
577 };
578
579 let size = size.with_v(if align == FlexAlignItems::Stretch { line_v } else { size.v });
580 let position = Uv::new(if is_reverse { panel_size.u - size.u - u } else { u }, position_v);
581 let origin = Uv::to_point(position, is_column);
582 let extent = Uv::to_size(size, is_column);
583 element.arrange(Rect::new(origin.x, origin.y, extent.width, extent.height));
584
585 u += size.u + spacing_u;
586 }
587
588 v += line_v + spacing_v;
589 }
590
591 if this.state.measure_count.get() == measure_count {
594 *this.state.lines.borrow_mut() = lines;
595 }
596
597 final_size
598 }
599}
600
601impl PanelImpl for FlexPanel {
602 fn children_changed(this: &Self, e: &NotifyCollectionChangedEventArgs<'_, Ref<Control>>) {
603 Self::parent_children_changed(this, e);
604
605 for old in e.old_items {
606 let disposable = this.child_visibility_subscriptions.borrow_mut().remove(old);
607 if let Some(disposable) = disposable {
608 disposable.dispose();
609 }
610 }
611
612 for new_item in e.new_items {
613 let weak = this.to_ref().downgrade();
614 let object: &FerroObject = new_item;
615 let subscription = FerroObjectExtensions::get_observable(object, Visual::is_visible_property())
616 .subscribe_fn(move |is_visible| {
617 if let Some(this) = weak.upgrade() {
618 this.on_child_visibility_changed(is_visible);
619 }
620 });
621 let previous = this.child_visibility_subscriptions.borrow_mut().insert(new_item.clone(), subscription);
622 if previous.is_some() {
623 panic!("An item with the same key has already been added.");
624 }
625 }
626
627 this.update_visible_children();
628 }
629}
630
631#[derive(Default)]
632struct FlexLayoutState {
633 lines: RefCell<Vec<FlexLine>>,
634 measure_count: Cell<u64>,
637}
638
639#[derive(Clone, Copy, Default)]
640struct LineData {
641 u: f64,
642 v: f64,
643 shrink: f64,
644 grow: f64,
645 auto_margins: i32,
646}
647
648#[derive(Clone, Copy)]
649struct FlexLine {
650 first: isize,
652 last: isize,
654 u: f64,
656 v: f64,
658 shrink: f64,
660 grow: f64,
662 auto_margins: i32,
664}
665
666impl FlexLine {
667 fn new(first: isize, last: isize, l: LineData) -> Self {
668 Self { first, last, u: l.u, v: l.v, shrink: l.shrink, grow: l.grow, auto_margins: l.auto_margins }
669 }
670
671 fn count(&self) -> usize {
673 (self.last - self.first + 1).max(0) as usize
674 }
675
676 fn range(&self) -> std::ops::Range<usize> {
678 let first = self.first.max(0) as usize;
679 first..first + self.count()
680 }
681}
682
683#[derive(Clone, Copy, Default)]
684struct Uv {
685 u: f64,
686 v: f64,
687}
688
689impl Uv {
690 fn new(u: f64, v: f64) -> Self {
691 Self { u, v }
692 }
693
694 fn from_width_height(width: f64, height: f64, swap: bool) -> Self {
695 Self::new(if swap { height } else { width }, if swap { width } else { height })
696 }
697
698 fn from_size(size: Size, swap: bool) -> Self {
699 Self::from_width_height(size.width, size.height, swap)
700 }
701
702 fn to_point(uv: Uv, swap: bool) -> Point {
703 Point::new(if swap { uv.v } else { uv.u }, if swap { uv.u } else { uv.v })
704 }
705
706 fn to_size(uv: Uv, swap: bool) -> Size {
707 Size::new(if swap { uv.v } else { uv.u }, if swap { uv.u } else { uv.v })
708 }
709
710 fn with_u(self, u: f64) -> Self {
711 Self::new(u, self.v)
712 }
713
714 fn with_v(self, v: f64) -> Self {
715 Self::new(self.u, v)
716 }
717}