void_app_api/
lib.rs

1pub use egui::Key;
2
3#[derive(Clone, Copy)]
4pub enum HorizontalAlignment {
5    Centered,
6    Left,
7    Right,
8}
9
10#[derive(Clone, Copy)]
11pub enum VerticalAlignment {
12    Centered,
13    Top,
14    Bottom,
15}
16
17pub mod color {
18
19    pub enum ColorError {
20        HueOutOfRange,
21        SaturationOutOfRange,
22        LightnessOutOfRange,
23    }
24
25    pub const BLACK: Color = Color::rgb(0, 0, 0);
26    pub const WHITE: Color = Color::rgb(255, 255, 255);
27
28    pub struct Color {
29        r: u8,
30        g: u8,
31        b: u8,
32    }
33
34    impl Color {
35        pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
36            Self { r, g, b }
37        }
38
39        pub fn hsl(h: u16, s: f32, l: f32) -> Result<Self, ColorError> {
40            if h > 360 {
41                return Err(ColorError::HueOutOfRange);
42            }
43
44            if !(0. ..=1.).contains(&s) {
45                return Err(ColorError::SaturationOutOfRange);
46            }
47
48            if !(0. ..=1.).contains(&l) {
49                return Err(ColorError::LightnessOutOfRange);
50            }
51
52            let (r, g, b) = if s == 0. {
53                (l, l, l)
54            } else {
55                let q = if l < 0.5 {
56                    l * (1_f32 + s)
57                } else {
58                    l + s - l * s
59                };
60                let p = 2. * l - q;
61                (
62                    Color::hue_to_rgb(p, q, f32::from(h) + 1. / 3.),
63                    Color::hue_to_rgb(p, q, f32::from(h)),
64                    Color::hue_to_rgb(p, q, f32::from(h) - 1. / 3.),
65                )
66            };
67
68            Ok(Self {
69                r: (r * 255.) as u8,
70                g: (g * 255.) as u8,
71                b: (b * 255.) as u8,
72            })
73        }
74
75        fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
76            if t < 0. {
77                t += 1.;
78            }
79
80            if t > 1. {
81                t -= 1.;
82            }
83
84            if t < 1. / 6. {
85                return p + (q - p) * 6. * t;
86            }
87
88            if t < 0.5 {
89                return q;
90            }
91
92            if t < 2. / 3. {
93                return p + (q - p) * (2. / 3. - t) * 6.;
94            }
95
96            p
97        }
98
99        pub fn red(&self) -> u8 {
100            self.r
101        }
102
103        pub fn blue(&self) -> u8 {
104            self.b
105        }
106
107        pub fn green(&self) -> u8 {
108            self.g
109        }
110    }
111}
112
113pub struct Text {
114    color: color::Color,
115    text: String,
116    layout: Layout,
117}
118
119impl Text {
120    pub fn new(text: &str) -> Self {
121        Self {
122            text: text.to_owned(),
123            color: color::BLACK,
124            layout: Layout::Cell(TextCell::centered()),
125        }
126    }
127
128    pub const fn color(mut self, color: color::Color) -> Self {
129        self.color = color;
130        self
131    }
132
133    pub fn text(&self) -> &str {
134        &self.text
135    }
136}
137
138impl Component for Text {
139    fn layout(&self) -> &Layout {
140        &self.layout
141    }
142}
143
144pub struct TextCell {
145    horizontal_alignment: HorizontalAlignment,
146    vertical_alignment: VerticalAlignment,
147    horizontal_offset: i32,
148    vertical_offset: i32,
149}
150
151impl TextCell {
152    pub fn centered() -> Self {
153        Self {
154            horizontal_alignment: HorizontalAlignment::Centered,
155            vertical_alignment: VerticalAlignment::Centered,
156            horizontal_offset: 0,
157            vertical_offset: 0,
158        }
159    }
160
161    pub fn align_horizontally(mut self, horizontal_alignment: HorizontalAlignment) -> Self {
162        self.horizontal_alignment = horizontal_alignment;
163        self
164    }
165
166    pub fn align_vertically(mut self, vertical_alignment: VerticalAlignment) -> Self {
167        self.vertical_alignment = vertical_alignment;
168        self
169    }
170
171    pub fn offset_horizontally(mut self, horizontal_offset: i32) -> Self {
172        self.horizontal_offset = horizontal_offset;
173        self
174    }
175
176    pub fn offset_vertically(mut self, vertical_offset: i32) -> Self {
177        self.vertical_offset = vertical_offset;
178        self
179    }
180
181    pub fn horizontal_offset(&self) -> i32 {
182        self.horizontal_offset
183    }
184
185    pub fn vertical_offset(&self) -> i32 {
186        self.vertical_offset
187    }
188
189    pub fn horizontal_alignment(&self) -> HorizontalAlignment {
190        self.horizontal_alignment
191    }
192
193    pub fn vertical_alignment(&self) -> VerticalAlignment {
194        self.vertical_alignment
195    }
196}
197
198/// A `Layout` tells a component how to place it's children.
199pub enum Layout {
200    Column(Column),
201    Row(Row),
202    Grid(Grid),
203
204    /// The "text cell" layout. A component with the "text cell" layout means that it has no child components, only text.
205    Cell(TextCell),
206
207    Empty,
208}
209
210pub struct Grid {
211    components: Vec<Vec<Option<Box<dyn Component>>>>,
212}
213
214impl Grid {
215    pub fn of_size(width: u32, height: u32) -> Self {
216        let mut components = Vec::new();
217        for _column in 0..width {
218            let mut row_vector = Vec::new();
219            for _row in 0..height {
220                row_vector.push(None)
221            }
222            components.push(row_vector);
223        }
224
225        Self { components }
226    }
227
228    pub fn set(&mut self, row: u32, column: u32, component: Box<dyn Component>) {
229        self.components[row as usize][column as usize] = Some(component);
230    }
231}
232
233impl From<Grid> for Layout {
234    fn from(value: Grid) -> Self {
235        Self::Grid(value)
236    }
237}
238
239pub struct Row {
240    components: Vec<Box<dyn Component>>,
241}
242
243#[derive(Default)]
244pub struct Column {
245    components: Vec<Box<dyn Component>>,
246}
247
248impl Column {
249    pub fn add_to_bottom(&mut self, component: Box<dyn Component>) {
250        self.components.push(component);
251    }
252
253    pub fn add_to_top(&mut self, component: Box<dyn Component>) {
254        let mut components = vec![component];
255        components.append(&mut self.components);
256        self.components = components;
257    }
258
259    pub fn height(&self) -> usize {
260        self.components.len()
261    }
262
263    pub fn into_layout(self) -> Layout {
264        self.into()
265    }
266}
267
268impl From<Column> for Layout {
269    fn from(value: Column) -> Self {
270        Self::Column(value)
271    }
272}
273
274pub trait Component {
275    fn layout(&self) -> &Layout;
276
277    // Event listeners
278
279    fn update(&mut self) {}
280    fn on_mouse_down(&mut self) {}
281    fn on_mouse_up(&mut self) {}
282    fn on_key_down(&mut self, key: Key) {}
283    fn on_key_up(&mut self) {}
284    fn on_right_mouse_down(&mut self) {}
285    fn on_right_mouse_up(&mut self) {}
286}
287
288pub trait Parent {
289    fn children(&self) -> Vec<&dyn Component>;
290}
291
292impl<T: Component + ?Sized> Parent for &T {
293    fn children(&self) -> Vec<&dyn Component> {
294        match self.layout() {
295            Layout::Column(column) => column
296                .components
297                .iter()
298                .map(|component| component.as_ref())
299                .collect(),
300            Layout::Row(row) => row
301                .components
302                .iter()
303                .map(|component| component.as_ref())
304                .collect(),
305            Layout::Grid(grid) => grid
306                .components
307                .iter()
308                .flatten()
309                .filter(|component| component.is_some())
310                .map(|component| component.as_ref().unwrap().as_ref())
311                .collect(),
312            Layout::Empty | Layout::Cell(_) => Vec::new(),
313        }
314    }
315}
316
317pub enum Length {
318    Percent(i32),
319    Pixels(i32),
320    FitContent,
321}
322
323pub trait Unit {
324    fn percent(&self) -> Length;
325    fn pixels(&self) -> Length;
326}
327
328impl<T: Into<i32> + Clone> Unit for T {
329    fn percent(&self) -> Length {
330        Length::Percent(self.clone().into())
331    }
332
333    fn pixels(&self) -> Length {
334        Length::Pixels(self.clone().into())
335    }
336}