widgetry/geom/
geom_batch_stack.rs

1use crate::GeomBatch;
2
3#[derive(Clone, Copy, Debug, PartialEq)]
4pub enum Axis {
5    Horizontal,
6    Vertical,
7}
8
9#[derive(Clone, Copy, Debug, PartialEq)]
10pub enum Alignment {
11    Center,
12    Top,
13    Left,
14    // TODO: Bottom, Right
15}
16
17/// Similar to [`Widget::row`]/[`Widget::column`], but for [`GeomBatch`]s instead of [`Widget`]s,
18/// and follows a builder pattern
19///
20/// You can add items incrementally, change `spacing` and `axis`, and call `batch` at the end to
21/// apply these rules to produce an aggeregate `GeomBatch`.
22#[derive(Debug, Clone)]
23pub struct GeomBatchStack {
24    batches: Vec<GeomBatch>,
25    axis: Axis,
26    alignment: Alignment,
27    spacing: f64,
28}
29
30impl Default for GeomBatchStack {
31    fn default() -> Self {
32        GeomBatchStack {
33            batches: vec![],
34            axis: Axis::Horizontal,
35            alignment: Alignment::Center,
36            spacing: 0.0,
37        }
38    }
39}
40
41impl GeomBatchStack {
42    pub fn horizontal(batches: Vec<GeomBatch>) -> Self {
43        Self::from_axis(batches, Axis::Horizontal)
44    }
45
46    pub fn vertical(batches: Vec<GeomBatch>) -> Self {
47        Self::from_axis(batches, Axis::Vertical)
48    }
49
50    pub fn from_axis(batches: Vec<GeomBatch>, axis: Axis) -> Self {
51        GeomBatchStack {
52            batches,
53            axis,
54            ..Default::default()
55        }
56    }
57
58    pub fn get(&self, index: usize) -> Option<&GeomBatch> {
59        self.batches.get(index)
60    }
61
62    pub fn get_mut(&mut self, index: usize) -> Option<&mut GeomBatch> {
63        self.batches.get_mut(index)
64    }
65
66    pub fn push(&mut self, geom_batch: GeomBatch) {
67        self.batches.push(geom_batch);
68    }
69
70    pub fn append(&mut self, geom_batches: &mut Vec<GeomBatch>) {
71        self.batches.append(geom_batches);
72    }
73
74    pub fn set_axis(&mut self, new_value: Axis) {
75        self.axis = new_value;
76    }
77
78    pub fn set_alignment(&mut self, new_value: Alignment) {
79        self.alignment = new_value;
80    }
81
82    pub fn set_spacing(&mut self, spacing: impl Into<f64>) -> &mut Self {
83        self.spacing = spacing.into();
84        self
85    }
86
87    pub fn batch(self) -> GeomBatch {
88        if self.batches.is_empty() {
89            return GeomBatch::new();
90        }
91
92        let max_bound_for_axis = self
93            .batches
94            .iter()
95            .map(GeomBatch::get_bounds)
96            .max_by(|b1, b2| match self.axis {
97                Axis::Vertical => b1.width().partial_cmp(&b2.width()).unwrap(),
98                Axis::Horizontal => b1.height().partial_cmp(&b2.height()).unwrap(),
99            })
100            .unwrap();
101
102        let mut stack_batch = GeomBatch::new();
103        let mut stack_offset = 0.0;
104        for mut batch in self.batches {
105            let bounds = batch.get_bounds();
106            let alignment_inset = match (self.alignment, self.axis) {
107                (Alignment::Center, Axis::Vertical) => {
108                    (max_bound_for_axis.width() - bounds.width()) / 2.0
109                }
110                (Alignment::Center, Axis::Horizontal) => {
111                    (max_bound_for_axis.height() - bounds.height()) / 2.0
112                }
113                (Alignment::Top, Axis::Vertical) => {
114                    panic!("cannot top-align items in a vertical stack")
115                }
116                (Alignment::Top, Axis::Horizontal) => 0.0,
117                (Alignment::Left, Axis::Horizontal) => {
118                    panic!("cannot left-align items in a horizontal stack")
119                }
120                (Alignment::Left, Axis::Vertical) => 0.0,
121            };
122
123            let (dx, dy) = match self.axis {
124                Axis::Vertical => (alignment_inset, stack_offset),
125                Axis::Horizontal => (stack_offset, alignment_inset),
126            };
127            batch = batch.translate(dx, dy);
128            stack_batch.append(batch);
129
130            stack_offset += self.spacing;
131            match self.axis {
132                Axis::Vertical => stack_offset += bounds.height(),
133                Axis::Horizontal => stack_offset += bounds.width(),
134            }
135        }
136        stack_batch
137    }
138}