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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use crate::primitives::Frame;
use crate::types::{StackerCell, StackerDirection};
use pax_lang::api::numeric::Numeric;
use pax_lang::api::{Property, Size, Transform2D};
use pax_lang::*;
use pax_runtime_api::{RuntimeContext, PropertyLiteral};


/// 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.
#[derive(Pax)]
#[custom(Default)]
#[inlined(
    for (cell_spec, i) in self._cell_specs {
        <Frame
            transform={Transform2D::translate((cell_spec.x_px)px, (cell_spec.y_px)px)}
            width={(cell_spec.width_px)px}
            height={(cell_spec.height_px)px}
        >
            slot(i)
        </Frame>
    }

    @handlers {
        will_render: handle_will_render
    }

)]
pub struct Stacker {
    pub cells: Property<Numeric>,
    pub direction: Property<crate::types::StackerDirection>,
    pub _cell_specs: Property<Vec<StackerCell>>,
    pub gutter: Property<Size>,

    /// For for specifying sizes of each cell.  None-values (or array-index out-of-bounds values)
    /// will fall back to computed, equal-sizing
    pub sizes: Property<Vec<Option<Size>>>,
}

impl Default for Stacker {
    fn default() -> Self {
        Self {
            cells: Box::new(PropertyLiteral::new(1.into())),
            direction: Box::new(PropertyLiteral::new(StackerDirection::Horizontal)),
            _cell_specs: Box::new(PropertyLiteral::new(vec![])),
            gutter: Box::new(PropertyLiteral::new(Size::Pixels(Numeric::Integer(0)))),
            sizes: Box::new(PropertyLiteral::new(vec![])),
        }
    }
}

impl Stacker {
    pub fn handle_will_render(&mut self, ctx: RuntimeContext) {
        let cells = self.cells.get().get_as_float();
        let bounds = ctx.bounds_parent;
        let active_bound = match *self.direction.get() {
            StackerDirection::Horizontal => bounds.0,
            StackerDirection::Vertical => bounds.1,
        };

        let gutter_calc = match *self.gutter.get() {
            Size::Pixels(pix) => pix,
            Size::Percent(per) => Numeric::from(active_bound) * (per / Numeric::from(100.0)),
            Size::Combined(pix, per) => {
                pix + (Numeric::from(active_bound) * (per / Numeric::from(100.0)))
            }
        };

        let usable_interior_space = active_bound - (cells - 1.0) * gutter_calc.get_as_float();

        let per_cell_space = usable_interior_space / cells;

        let mut cell_space = vec![per_cell_space; self.cells.get().get_as_float() as usize];
        let sizes = self.sizes.get();

        if sizes.len() > 0 {
            if sizes.len() != (cells as usize) {
                unreachable!(
                    "Sizes is not a valid length. Please specify {} sizes",
                    (cells as usize)
                );
            }
            let mut used_space = 0.0;
            let mut remaining_cells = 0.0;
            for (i, size) in self.sizes.get().iter().enumerate() {
                if let Some(s) = size {
                    let space = match s {
                        Size::Pixels(pix) => *pix,
                        Size::Percent(per) => {
                            Numeric::from(active_bound) * (*per / Numeric::from(100.0))
                        }
                        Size::Combined(pix, per) => {
                            *pix + (Numeric::from(active_bound)
                                * (per.clone() / Numeric::from(100.0)))
                        }
                    }
                    .get_as_float();
                    used_space += space;
                    cell_space[i] = space;
                } else {
                    cell_space[i] = -1.0;
                    remaining_cells += 1.0;
                }
            }
            if used_space > usable_interior_space {
                unreachable!(
                    "Invalid sizes. Usable interior space is {}px",
                    usable_interior_space
                );
            }

            let remaining_per_cell_space = (usable_interior_space - used_space) / remaining_cells;
            for i in &mut cell_space {
                if *i == -1.0 {
                    *i = remaining_per_cell_space;
                }
            }
        }

        let mut used_space = 0.0;
        let new_cell_specs = (0..cells as usize)
            .into_iter()
            .map(|i| {
                let ret = match self.direction.get() {
                    StackerDirection::Horizontal => StackerCell {
                        height_px: bounds.1,
                        width_px: cell_space[i],
                        x_px: ((i) as f64) * (gutter_calc) + used_space,
                        y_px: 0.0,
                    },
                    StackerDirection::Vertical => StackerCell {
                        height_px: cell_space[i],
                        width_px: bounds.0,
                        x_px: 0.0,
                        y_px: ((i) as f64) * (gutter_calc) + used_space,
                    },
                };
                used_space += cell_space[i];
                ret
            })
            .collect();

        self._cell_specs.set(new_cell_specs);
    }
}