void-app-api 0.0.10

The API for creating Void modules.
Documentation
pub use egui::Key;

#[derive(Clone, Copy)]
pub enum HorizontalAlignment {
    Centered,
    Left,
    Right,
}

#[derive(Clone, Copy)]
pub enum VerticalAlignment {
    Centered,
    Top,
    Bottom,
}

pub mod color {

    pub enum ColorError {
        HueOutOfRange,
        SaturationOutOfRange,
        LightnessOutOfRange,
    }

    pub const BLACK: Color = Color::rgb(0, 0, 0);
    pub const WHITE: Color = Color::rgb(255, 255, 255);

    pub struct Color {
        r: u8,
        g: u8,
        b: u8,
    }

    impl Color {
        pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
            Self { r, g, b }
        }

        pub fn hsl(h: u16, s: f32, l: f32) -> Result<Self, ColorError> {
            if h > 360 {
                return Err(ColorError::HueOutOfRange);
            }

            if !(0. ..=1.).contains(&s) {
                return Err(ColorError::SaturationOutOfRange);
            }

            if !(0. ..=1.).contains(&l) {
                return Err(ColorError::LightnessOutOfRange);
            }

            let (r, g, b) = if s == 0. {
                (l, l, l)
            } else {
                let q = if l < 0.5 {
                    l * (1_f32 + s)
                } else {
                    l + s - l * s
                };
                let p = 2. * l - q;
                (
                    Color::hue_to_rgb(p, q, f32::from(h) + 1. / 3.),
                    Color::hue_to_rgb(p, q, f32::from(h)),
                    Color::hue_to_rgb(p, q, f32::from(h) - 1. / 3.),
                )
            };

            Ok(Self {
                r: (r * 255.) as u8,
                g: (g * 255.) as u8,
                b: (b * 255.) as u8,
            })
        }

        fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
            if t < 0. {
                t += 1.;
            }

            if t > 1. {
                t -= 1.;
            }

            if t < 1. / 6. {
                return p + (q - p) * 6. * t;
            }

            if t < 0.5 {
                return q;
            }

            if t < 2. / 3. {
                return p + (q - p) * (2. / 3. - t) * 6.;
            }

            p
        }

        pub fn red(&self) -> u8 {
            self.r
        }

        pub fn blue(&self) -> u8 {
            self.b
        }

        pub fn green(&self) -> u8 {
            self.g
        }
    }
}

pub struct Text {
    color: color::Color,
    text: String,
    layout: Layout,
}

impl Text {
    pub fn new(text: &str) -> Self {
        Self {
            text: text.to_owned(),
            color: color::BLACK,
            layout: Layout::Cell(TextCell::centered()),
        }
    }

    pub const fn color(mut self, color: color::Color) -> Self {
        self.color = color;
        self
    }

    pub fn text(&self) -> &str {
        &self.text
    }
}

impl Component for Text {
    fn layout(&self) -> &Layout {
        &self.layout
    }
}

pub struct TextCell {
    horizontal_alignment: HorizontalAlignment,
    vertical_alignment: VerticalAlignment,
    horizontal_offset: i32,
    vertical_offset: i32,
}

impl TextCell {
    pub fn centered() -> Self {
        Self {
            horizontal_alignment: HorizontalAlignment::Centered,
            vertical_alignment: VerticalAlignment::Centered,
            horizontal_offset: 0,
            vertical_offset: 0,
        }
    }

    pub fn align_horizontally(mut self, horizontal_alignment: HorizontalAlignment) -> Self {
        self.horizontal_alignment = horizontal_alignment;
        self
    }

    pub fn align_vertically(mut self, vertical_alignment: VerticalAlignment) -> Self {
        self.vertical_alignment = vertical_alignment;
        self
    }

    pub fn offset_horizontally(mut self, horizontal_offset: i32) -> Self {
        self.horizontal_offset = horizontal_offset;
        self
    }

    pub fn offset_vertically(mut self, vertical_offset: i32) -> Self {
        self.vertical_offset = vertical_offset;
        self
    }

    pub fn horizontal_offset(&self) -> i32 {
        self.horizontal_offset
    }

    pub fn vertical_offset(&self) -> i32 {
        self.vertical_offset
    }

    pub fn horizontal_alignment(&self) -> HorizontalAlignment {
        self.horizontal_alignment
    }

    pub fn vertical_alignment(&self) -> VerticalAlignment {
        self.vertical_alignment
    }
}

/// A `Layout` tells a component how to place it's children.
pub enum Layout {
    Column(Column),
    Row(Row),
    Grid(Grid),

    /// The "text cell" layout. A component with the "text cell" layout means that it has no child components, only text.
    Cell(TextCell),

    Empty,
}

pub struct Grid {
    components: Vec<Vec<Option<Box<dyn Component>>>>,
}

impl Grid {
    pub fn of_size(width: u32, height: u32) -> Self {
        let mut components = Vec::new();
        for _column in 0..width {
            let mut row_vector = Vec::new();
            for _row in 0..height {
                row_vector.push(None)
            }
            components.push(row_vector);
        }

        Self { components }
    }

    pub fn set(&mut self, row: u32, column: u32, component: Box<dyn Component>) {
        self.components[row as usize][column as usize] = Some(component);
    }
}

impl From<Grid> for Layout {
    fn from(value: Grid) -> Self {
        Self::Grid(value)
    }
}

pub struct Row {
    components: Vec<Box<dyn Component>>,
}

#[derive(Default)]
pub struct Column {
    components: Vec<Box<dyn Component>>,
}

impl Column {
    pub fn add_to_bottom(&mut self, component: Box<dyn Component>) {
        self.components.push(component);
    }

    pub fn add_to_top(&mut self, component: Box<dyn Component>) {
        let mut components = vec![component];
        components.append(&mut self.components);
        self.components = components;
    }

    pub fn height(&self) -> usize {
        self.components.len()
    }

    pub fn into_layout(self) -> Layout {
        self.into()
    }
}

impl From<Column> for Layout {
    fn from(value: Column) -> Self {
        Self::Column(value)
    }
}

pub trait Component {
    fn layout(&self) -> &Layout;

    // Event listeners

    fn update(&mut self) {}
    fn on_mouse_down(&mut self) {}
    fn on_mouse_up(&mut self) {}
    fn on_key_down(&mut self, key: Key) {}
    fn on_key_up(&mut self) {}
    fn on_right_mouse_down(&mut self) {}
    fn on_right_mouse_up(&mut self) {}
}

pub trait Parent {
    fn children(&self) -> Vec<&dyn Component>;
}

impl<T: Component + ?Sized> Parent for &T {
    fn children(&self) -> Vec<&dyn Component> {
        match self.layout() {
            Layout::Column(column) => column
                .components
                .iter()
                .map(|component| component.as_ref())
                .collect(),
            Layout::Row(row) => row
                .components
                .iter()
                .map(|component| component.as_ref())
                .collect(),
            Layout::Grid(grid) => grid
                .components
                .iter()
                .flatten()
                .filter(|component| component.is_some())
                .map(|component| component.as_ref().unwrap().as_ref())
                .collect(),
            Layout::Empty | Layout::Cell(_) => Vec::new(),
        }
    }
}

pub enum Length {
    Percent(i32),
    Pixels(i32),
    FitContent,
}

pub trait Unit {
    fn percent(&self) -> Length;
    fn pixels(&self) -> Length;
}

impl<T: Into<i32> + Clone> Unit for T {
    fn percent(&self) -> Length {
        Length::Percent(self.clone().into())
    }

    fn pixels(&self) -> Length {
        Length::Pixels(self.clone().into())
    }
}