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
use crate::GeomBatch;

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Axis {
    Horizontal,
    Vertical,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Alignment {
    Center,
    Top,
    Left,
    // TODO: Bottom, Right
}

/// Similar to [`Widget::row`]/[`Widget::column`], but for [`GeomBatch`]s instead of [`Widget`]s,
/// and follows a builder pattern
///
/// You can add items incrementally, change `spacing` and `axis`, and call `batch` at the end to
/// apply these rules to produce an aggeregate `GeomBatch`.
#[derive(Debug, Clone)]
pub struct GeomBatchStack {
    batches: Vec<GeomBatch>,
    axis: Axis,
    alignment: Alignment,
    spacing: f64,
}

impl Default for GeomBatchStack {
    fn default() -> Self {
        GeomBatchStack {
            batches: vec![],
            axis: Axis::Horizontal,
            alignment: Alignment::Center,
            spacing: 0.0,
        }
    }
}

impl GeomBatchStack {
    pub fn horizontal(batches: Vec<GeomBatch>) -> Self {
        Self::from_axis(batches, Axis::Horizontal)
    }

    pub fn vertical(batches: Vec<GeomBatch>) -> Self {
        Self::from_axis(batches, Axis::Vertical)
    }

    pub fn from_axis(batches: Vec<GeomBatch>, axis: Axis) -> Self {
        GeomBatchStack {
            batches,
            axis,
            ..Default::default()
        }
    }

    pub fn get(&self, index: usize) -> Option<&GeomBatch> {
        self.batches.get(index)
    }

    pub fn get_mut(&mut self, index: usize) -> Option<&mut GeomBatch> {
        self.batches.get_mut(index)
    }

    pub fn push(&mut self, geom_batch: GeomBatch) {
        self.batches.push(geom_batch);
    }

    pub fn append(&mut self, geom_batches: &mut Vec<GeomBatch>) {
        self.batches.append(geom_batches);
    }

    pub fn set_axis(&mut self, new_value: Axis) {
        self.axis = new_value;
    }

    pub fn set_alignment(&mut self, new_value: Alignment) {
        self.alignment = new_value;
    }

    pub fn set_spacing(&mut self, spacing: impl Into<f64>) -> &mut Self {
        self.spacing = spacing.into();
        self
    }

    pub fn batch(self) -> GeomBatch {
        if self.batches.is_empty() {
            return GeomBatch::new();
        }

        let max_bound_for_axis = self
            .batches
            .iter()
            .map(GeomBatch::get_bounds)
            .max_by(|b1, b2| match self.axis {
                Axis::Vertical => b1.width().partial_cmp(&b2.width()).unwrap(),
                Axis::Horizontal => b1.height().partial_cmp(&b2.height()).unwrap(),
            })
            .unwrap();

        let mut stack_batch = GeomBatch::new();
        let mut stack_offset = 0.0;
        for mut batch in self.batches {
            let bounds = batch.get_bounds();
            let alignment_inset = match (self.alignment, self.axis) {
                (Alignment::Center, Axis::Vertical) => {
                    (max_bound_for_axis.width() - bounds.width()) / 2.0
                }
                (Alignment::Center, Axis::Horizontal) => {
                    (max_bound_for_axis.height() - bounds.height()) / 2.0
                }
                (Alignment::Top, Axis::Vertical) => {
                    panic!("cannot top-align items in a vertical stack")
                }
                (Alignment::Top, Axis::Horizontal) => 0.0,
                (Alignment::Left, Axis::Horizontal) => {
                    panic!("cannot left-align items in a horizontal stack")
                }
                (Alignment::Left, Axis::Vertical) => 0.0,
            };

            let (dx, dy) = match self.axis {
                Axis::Vertical => (alignment_inset, stack_offset),
                Axis::Horizontal => (stack_offset, alignment_inset),
            };
            batch = batch.translate(dx, dy);
            stack_batch.append(batch);

            stack_offset += self.spacing;
            match self.axis {
                Axis::Vertical => stack_offset += bounds.height(),
                Axis::Horizontal => stack_offset += bounds.width(),
            }
        }
        stack_batch
    }
}