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 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
use crate::primitives::*;
use crate::types::{StackerCell, StackerDirection};
use pax_engine::api::Numeric;
use pax_engine::api::{Property, Size, Transform2D};
use pax_engine::*;
use pax_runtime::api::{NodeContext, StringBox};
/// 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 percentage-based positioning and `Transform2D.anchor` to compose any rectilinear 2D layout.
#[pax]
#[custom(Default)]
#[inlined(
for (cell_spec, i) in self._cell_specs {
<Group
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)
</Group>
}
@settings {
@mount: on_mount
}
)]
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: Property::new(1.into()),
direction: Property::new(StackerDirection::Horizontal),
_cell_specs: Property::new(vec![]),
gutter: Property::new(Size::Pixels(Numeric::Integer(0))),
sizes: Property::new(vec![]),
}
}
}
impl Stacker {
pub fn on_mount(&mut self, ctx: &NodeContext) {
let cells = self.cells.clone();
let sizes = self.sizes.clone();
let bound = ctx.bounds_self.clone();
let gutter = self.gutter.clone();
let direction = self.direction.clone();
let deps = [
cells.untyped(),
bound.untyped(),
direction.untyped(),
sizes.untyped(),
gutter.untyped(),
];
//NOTE: replace with is needed since the for loop already has a connection to the prop
self._cell_specs.replace_with(Property::computed_with_name(
move || {
let cells: f64 = cells.get().to_float();
let bounds = bound.get();
let active_bound = match direction.get() {
StackerDirection::Horizontal => bounds.0,
StackerDirection::Vertical => bounds.1,
};
let gutter_calc = match 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.to_float();
let per_cell_space = usable_interior_space / cells;
let mut cell_space = vec![per_cell_space; cells as usize];
let sizes = 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 sizes.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 / Numeric::from(100.0)))
}
}
.to_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 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();
new_cell_specs
},
&deps,
"stacker _cell_specs",
));
}
}