sge_ui 1.2.0

UI library for SGE
Documentation
use sge_api::shapes_2d::Orientation;
use sge_input::{cursor, mouse_pressed, mouse_released};
use sge_window::{use_ew_resize_cursor_icon, use_ns_resize_cursor_icon};

use super::*;

#[derive(Debug)]
pub struct Dock {
    a: Child,
    b: Child,
    border: BorderStyle,
    hovered_border: BorderStyle,
    orientation: Orientation,
    default_split: Split,
    state: State<DockState>,
}

#[derive(Clone, Copy, Debug)]
pub enum Split {
    FixedSizeA(f32),
    FixedSizeB(f32),
    Proportion(f32),
}

impl Split {
    pub fn split_area(&self, area: Area, orientation: Orientation) -> (Area, Area) {
        let (a, _) = self.sizes(orientation.main(area.size));
        area.split_at(a, orientation)
    }

    pub fn sizes(self, width: f32) -> (f32, f32) {
        match self {
            Self::FixedSizeA(size_a) => {
                if width > size_a {
                    (size_a, width - size_a)
                } else {
                    (width, 0.0)
                }
            }
            Self::FixedSizeB(size_b) => {
                if width > size_b {
                    (width - size_b, size_b)
                } else {
                    (0.0, width)
                }
            }
            Self::Proportion(proportion) => {
                debug_assert!((0.0..=1.0).contains(&proportion));
                let p_a = proportion;
                let p_b = 1.0 - proportion;
                (width * p_a, width * p_b)
            }
        }
    }
}

#[derive(Debug)]
pub struct DockState {
    split: Split,
    captured: bool,
}

impl Dock {
    pub fn new(
        border: BorderStyle,
        hovered_border: BorderStyle,
        orientation: Orientation,
        default_split: Split,
        id: usize,
        a: Child,
        b: Child,
    ) -> UiRef {
        Self {
            a,
            b,
            border,
            hovered_border,
            orientation,
            default_split,
            state: State::from_id(id),
        }
        .to_ref()
    }
}

impl UiNode for Dock {
    fn preferred_dimensions(&self) -> Vec2 {
        Vec2::INFINITY
    }

    fn size(&self, area: Area) -> Vec2 {
        area.size
    }

    fn draw(&self, area: Area, ui: &UiState) -> Vec2 {
        let state = self.state.get_or_create_mut(|| DockState {
            split: self.default_split,
            captured: false,
        });

        if mouse_released(MouseButton::Left) {
            state.captured = false;
        }

        if state.captured {
            if let Some(cursor_pos) = cursor() {
                let total = self.orientation.main(area.size);
                let relative = self.orientation.main(cursor_pos - area.top_left);

                match state.split {
                    Split::FixedSizeA(_) => {
                        let size_a = relative.clamp(10.0, total - 10.0);
                        state.split = Split::FixedSizeA(size_a);
                    }
                    Split::FixedSizeB(_) => {
                        let size_b = (total - relative).clamp(10.0, total - 10.0);
                        state.split = Split::FixedSizeB(size_b);
                    }
                    Split::Proportion(_) => {
                        let proportion = (relative / total).clamp(0.05, 0.95);
                        state.split = Split::Proportion(proportion);
                    }
                }
            }
        }

        let (area_a, area_b) = state.split.split_area(area, self.orientation);

        let border_thickness = self.border.thickness.max(10.0);
        let border_area = match self.orientation {
            Orientation::Horizontal => Area {
                top_left: Vec2::new(area_a.right() - border_thickness / 2.0, area.top_left.y),
                size: Vec2::new(border_thickness, area.size.y),
            },
            Orientation::Vertical => Area {
                top_left: Vec2::new(area.top_left.x, area_a.bottom() - border_thickness / 2.0),
                size: Vec2::new(area.size.x, border_thickness),
            },
        };

        let is_hovered = ui.is_hovered(border_area);

        if is_hovered || state.captured {
            match self.orientation {
                Orientation::Horizontal => {
                    use_ew_resize_cursor_icon();
                }
                Orientation::Vertical => {
                    use_ns_resize_cursor_icon();
                }
            }
        }

        if mouse_pressed(MouseButton::Left) && is_hovered {
            state.captured = true;
        }

        let border = if is_hovered || state.captured {
            &self.hovered_border
        } else {
            &self.border
        };

        match self.orientation {
            Orientation::Horizontal => {
                let x = area_a.right();
                let top = Vec2::new(x, area.top_left.y);
                let bottom = Vec2::new(x, area.bottom_right().y);
                border.draw(top, bottom, Side::Right);
            }
            Orientation::Vertical => {
                let y = area_a.bottom();
                let left = Vec2::new(area.top_left.x, y);
                let right = Vec2::new(area.bottom_right().x, y);
                border.draw(left, right, Side::Bottom);
            }
        }

        self.a.draw(area_a, ui);
        self.b.draw(area_b, ui);

        area.size
    }
}