1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use std::rc::Rc;
use pax::*;
use pax::api::{Size2D, Size, ArgsRender, Property};
use crate::primitives::{Frame, Group};
use crate::types::{StackerDirection, StackerCellProperties};

/// Stacker lays out a series of nodes either
/// vertically or horizontally (i.e. a single row or column) with a specified gutter in between
/// each node.  `Stacker`s can be stacked inside of each other, horizontally
/// and vertically, along with `Transform.align` and `Transform.anchor` to compose any rectilinear 2D layout.
#[pax(
    for (elem, i) in self.computed_layout_spec {
        <Frame transform={translate(elem.x_px, elem.y_px)} size={(elem.width_px, elem.height_px)}>
            slot(i)
        </Frame>
    }
)]
pub struct Stacker {
    pub computed_layout_spec: Property<Vec<Rc<StackerCellProperties>>>,
    pub direction: Property<StackerDirection>,
    pub cells: Property<usize>,
    pub gutter_width: Property<Size>,
    pub overrides_cell_size: Property<Vec<(usize, Size)>>,
    pub overrides_gutter_size: Property<Vec<(usize, Size)>>,
}

impl Stacker {

    #[pax_on(WillRender)]
    pub fn handle_will_render(&mut self, args: ArgsRender) {

        //TODO: dirty check
        let bounds = args.bounds;

        let active_bound = match *self.direction.get() {
            StackerDirection::Horizontal => bounds.0,
            StackerDirection::Vertical => bounds.1
        };

        let gutter_calc = match *self.gutter_width.get() {
            Size::Pixels(px) => px,
            Size::Percent(pct) => active_bound * (pct / 100.0),
        };

        let cells = *self.cells.get() as f64;

        let usable_interior_space = active_bound - (cells - 1.0) * gutter_calc;
        let per_cell_space = usable_interior_space / cells;

        //TODO: account for overrides
        //The two data structures act as "sparse maps," where
        //the first element in the tuple is the index of the cell/gutter to
        //override and the second is the override value.  In the absence
        //of overrides (`vec![]`), cells and gutters will divide space evenly.


        //Manual dirty-check: intended to be supplanted by reactive dirty-check mechanism
        //was needed to stop instance churn that was happening with

        let old = self.computed_layout_spec.get();
        let new : Vec<Rc<StackerCellProperties>> = (0..(cells as usize)).into_iter().map(|i| {
            match self.direction.get() {
                StackerDirection::Horizontal =>
                    Rc::new(StackerCellProperties {
                        height_px: bounds.1,
                        width_px: per_cell_space,
                        x_px: ((i) as f64) * (gutter_calc) + (i as f64) * per_cell_space,
                        y_px: 0.0,
                    }),
                StackerDirection::Vertical =>
                    Rc::new(StackerCellProperties {
                        height_px: per_cell_space,
                        width_px: bounds.0,
                        x_px: 0.0,
                        y_px: ((i) as f64) * (gutter_calc) + (i as f64) * per_cell_space,
                    }),
            }
        }).collect();
        let is_dirty = old.len() != new.len() || old.iter().enumerate().any(|(i,p_old)|{
            let p_new = new.get(i).unwrap();

            p_old.height_px != p_new.height_px ||
                p_old.width_px != p_new.width_px ||
                p_old.x_px != p_new.x_px ||
                p_old.y_px != p_new.y_px
        });

        if is_dirty {
            self.computed_layout_spec.set(new);
        }


    }


}