raui-core 0.21.3

Renderer Agnostic User Interface
Documentation
pub mod default_layout_engine;

use crate::{
    widget::{
        unit::WidgetUnit,
        utils::{Rect, Vec2},
        WidgetId,
    },
    Scalar,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

pub trait LayoutEngine<E> {
    fn layout(&mut self, register_props: &CoordsMapping, tree: &WidgetUnit) -> Result<Layout, E>;
}

#[derive(Default, Clone)]
pub struct Layout {
    pub ui_space: Rect,
    pub items: HashMap<WidgetId, LayoutItem>,
}

impl std::fmt::Debug for Layout {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Layout")
            .field("ui_space", &self.ui_space)
            .field("items", &LayoutSortedItems::new(&self.items))
            .finish()
    }
}

struct LayoutSortedItems<'a>(Vec<(&'a WidgetId, &'a LayoutItem)>);

impl<'a> LayoutSortedItems<'a> {
    fn new(items: &'a HashMap<WidgetId, LayoutItem>) -> Self {
        let mut items = items.iter().collect::<Vec<_>>();
        items.sort_by(|a, b| a.0.path().cmp(b.0.path()));
        Self(items)
    }
}

impl<'a> std::fmt::Debug for LayoutSortedItems<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_map()
            .entries(self.0.iter().map(|&(k, v)| (k, v)))
            .finish()
    }
}

impl Layout {
    pub fn virtual_to_real(&self, mapping: &CoordsMapping) -> Self {
        Self {
            ui_space: mapping.virtual_to_real_rect(self.ui_space),
            items: self
                .items
                .iter()
                .map(|(k, v)| (k.to_owned(), v.virtual_to_real(mapping)))
                .collect::<HashMap<_, _>>(),
        }
    }

    pub fn real_to_virtual(&self, mapping: &CoordsMapping) -> Self {
        Self {
            ui_space: mapping.real_to_virtual_rect(self.ui_space),
            items: self
                .items
                .iter()
                .map(|(k, v)| (k.to_owned(), v.real_to_virtual(mapping)))
                .collect::<HashMap<_, _>>(),
        }
    }
}

#[derive(Debug, Default, Clone)]
pub struct LayoutNode {
    pub id: WidgetId,
    pub local_space: Rect,
    pub children: Vec<LayoutNode>,
}

impl LayoutNode {
    pub fn count(&self) -> usize {
        1 + self.children.iter().map(Self::count).sum::<usize>()
    }
}

#[derive(Debug, Default, Copy, Clone)]
pub struct LayoutItem {
    pub local_space: Rect,
    pub ui_space: Rect,
}

impl LayoutItem {
    pub fn virtual_to_real(&self, mapping: &CoordsMapping) -> Self {
        Self {
            local_space: mapping.virtual_to_real_rect(self.local_space),
            ui_space: mapping.virtual_to_real_rect(self.ui_space),
        }
    }

    pub fn real_to_virtual(&self, mapping: &CoordsMapping) -> Self {
        Self {
            local_space: mapping.real_to_virtual_rect(self.local_space),
            ui_space: mapping.real_to_virtual_rect(self.ui_space),
        }
    }
}

impl LayoutEngine<()> for () {
    fn layout(&mut self, mapping: &CoordsMapping, _: &WidgetUnit) -> Result<Layout, ()> {
        Ok(Layout {
            ui_space: mapping.virtual_area(),
            items: Default::default(),
        })
    }
}

#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum CoordsMappingScaling {
    None,
    Fit(Vec2),
    FitHorizontal(Scalar),
    FitVertical(Scalar),
    FitMinimum(Vec2),
    FitMaximum(Vec2),
}

impl Default for CoordsMappingScaling {
    fn default() -> Self {
        Self::None
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CoordsMapping {
    #[serde(default)]
    scale: Scalar,
    #[serde(default)]
    offset: Vec2,
    #[serde(default)]
    real_area: Rect,
    #[serde(default)]
    virtual_area: Rect,
}

impl Default for CoordsMapping {
    fn default() -> Self {
        Self::new(Default::default())
    }
}

impl CoordsMapping {
    pub fn new(real_area: Rect) -> Self {
        Self {
            scale: 1.0,
            offset: Vec2::default(),
            real_area,
            virtual_area: Rect {
                left: 0.0,
                right: real_area.width(),
                top: 0.0,
                bottom: real_area.height(),
            },
        }
    }

    pub fn new_scaling(real_area: Rect, scaling: CoordsMappingScaling) -> Self {
        match scaling {
            CoordsMappingScaling::Fit(size) => {
                let vw = size.x;
                let vh = size.y;
                let rw = real_area.width();
                let rh = real_area.height();
                let va = vw / vh;
                let ra = rw / rh;
                let scale = if va >= ra { rw / vw } else { rh / vh };
                let w = vw * scale;
                let h = vh * scale;
                Self {
                    scale,
                    offset: Vec2 {
                        x: (rw - w) * 0.5,
                        y: (rh - h) * 0.5,
                    },
                    real_area,
                    virtual_area: Rect {
                        left: 0.0,
                        right: vw,
                        top: 0.0,
                        bottom: vh,
                    },
                }
            }
            CoordsMappingScaling::FitHorizontal(vw) => {
                let rw = real_area.width();
                let rh = real_area.height();
                let scale = rw / vw;
                let vh = rh / scale;
                Self {
                    scale,
                    offset: Vec2::default(),
                    real_area,
                    virtual_area: Rect {
                        left: 0.0,
                        right: vw,
                        top: 0.0,
                        bottom: vh,
                    },
                }
            }
            CoordsMappingScaling::FitVertical(vh) => {
                let rw = real_area.width();
                let rh = real_area.height();
                let scale = rh / vh;
                let vw = rw / scale;
                Self {
                    scale,
                    offset: Vec2::default(),
                    real_area,
                    virtual_area: Rect {
                        left: 0.0,
                        right: vw,
                        top: 0.0,
                        bottom: vh,
                    },
                }
            }
            CoordsMappingScaling::FitMinimum(size) => {
                if size.x < size.y {
                    Self::new_scaling(real_area, CoordsMappingScaling::FitHorizontal(size.x))
                } else {
                    Self::new_scaling(real_area, CoordsMappingScaling::FitVertical(size.y))
                }
            }
            CoordsMappingScaling::FitMaximum(size) => {
                if size.x > size.y {
                    Self::new_scaling(real_area, CoordsMappingScaling::FitHorizontal(size.x))
                } else {
                    Self::new_scaling(real_area, CoordsMappingScaling::FitVertical(size.y))
                }
            }
            _ => Self {
                scale: 1.0,
                offset: Vec2::default(),
                real_area,
                virtual_area: Rect {
                    left: 0.0,
                    right: real_area.width(),
                    top: 0.0,
                    bottom: real_area.height(),
                },
            },
        }
    }

    #[inline]
    pub fn scale(&self) -> Scalar {
        self.scale
    }

    #[inline]
    pub fn offset(&self) -> Vec2 {
        self.offset
    }

    #[inline]
    pub fn virtual_area(&self) -> Rect {
        self.virtual_area
    }

    #[inline]
    pub fn virtual_to_real_vec2(&self, coord: Vec2) -> Vec2 {
        Vec2 {
            x: self.offset.x + (coord.x * self.scale),
            y: self.offset.y + (coord.y * self.scale),
        }
    }

    #[inline]
    pub fn real_to_virtual_vec2(&self, coord: Vec2) -> Vec2 {
        Vec2 {
            x: (coord.x - self.offset.x) / self.scale,
            y: (coord.y - self.offset.y) / self.scale,
        }
    }

    #[inline]
    pub fn virtual_to_real_rect(&self, area: Rect) -> Rect {
        Rect {
            left: self.offset.x + (area.left * self.scale),
            right: self.offset.x + (area.right * self.scale),
            top: self.offset.y + (area.top * self.scale),
            bottom: self.offset.y + (area.bottom * self.scale),
        }
    }

    #[inline]
    pub fn real_to_virtual_rect(&self, area: Rect) -> Rect {
        Rect {
            left: (area.left - self.offset.x) / self.scale,
            right: (area.right - self.offset.x) / self.scale,
            top: (area.top - self.offset.y) / self.scale,
            bottom: (area.bottom - self.offset.y) / self.scale,
        }
    }
}