torin 0.4.0-rc.2

UI layout Library designed for Freya.
Documentation
use std::{
    fmt::Debug,
    hash::Hash,
    sync::Arc,
};

pub use euclid::Rect;

use crate::{
    geometry::Length,
    measure::Phase,
    scaled::Scaled,
};

pub struct SizeFnContext {
    pub parent: f32,
    pub available_parent: f32,
    pub parent_margin: f32,
    pub root: f32,
    pub phase: Phase,
}

#[cfg(feature = "serde")]
pub use serde::*;

#[derive(Clone)]
pub struct SizeFn(Arc<dyn Fn(SizeFnContext) -> Option<f32> + Sync + Send>, u64);

#[cfg(feature = "serde")]
impl Serialize for SizeFn {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str("Fn")
    }
}

#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for SizeFn {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct FnVisitor;
        use serde::de::Visitor;

        impl Visitor<'_> for FnVisitor {
            type Value = SizeFn;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter.write_str("\"Fn\"")
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                if v == "Fn" {
                    Ok(SizeFn(Arc::new(|_ctx| None), 0))
                } else {
                    Err(E::custom(format!("expected \"Fn\", got {v}")))
                }
            }
        }

        deserializer.deserialize_str(FnVisitor)
    }
}

impl SizeFn {
    pub fn new(func: impl Fn(SizeFnContext) -> Option<f32> + 'static + Sync + Send) -> Self {
        Self(Arc::new(func), 0)
    }

    pub fn new_data<D: Hash>(
        func: impl Fn(SizeFnContext) -> Option<f32> + 'static + Sync + Send,
        data: &D,
    ) -> Self {
        use std::hash::Hasher;
        let mut hasher = std::hash::DefaultHasher::default();
        data.hash(&mut hasher);
        Self(Arc::new(func), hasher.finish())
    }

    pub fn call(&self, context: SizeFnContext) -> Option<f32> {
        (self.0)(context)
    }
}

impl Debug for SizeFn {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("SizeFn")
    }
}

impl PartialEq for SizeFn {
    fn eq(&self, other: &Self) -> bool {
        self.1 == other.1
    }
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Clone, Debug)]
pub enum Size {
    Inner,
    Fill,
    FillMinimum,
    Percentage(Length),
    Pixels(Length),
    RootPercentage(Length),
    Fn(Box<SizeFn>),
    Flex(Length),
}

impl Default for Size {
    fn default() -> Self {
        Self::Inner
    }
}

impl Size {
    pub(crate) fn flex_grow(&self) -> Option<Length> {
        match self {
            Self::Flex(f) => Some(*f),
            _ => None,
        }
    }

    pub(crate) fn is_flex(&self) -> bool {
        matches!(self, Self::Flex(_))
    }

    pub(crate) fn inner_sized(&self) -> bool {
        matches!(self, Self::Inner | Self::FillMinimum)
    }

    pub fn pretty(&self) -> String {
        match self {
            Self::Inner => "auto".to_string(),
            Self::Pixels(s) => format!("{}", s.get()),
            Self::Fn(_) => "Fn".to_string(),
            Self::Percentage(p) => format!("{}%", p.get()),
            Self::Fill => "fill".to_string(),
            Self::FillMinimum => "fill-min".to_string(),
            Self::RootPercentage(p) => format!("{}% of root", p.get()),
            Self::Flex(f) => format!("flex({})", f.get()),
        }
    }

    pub(crate) fn eval(
        &self,
        parent: f32,
        available_parent: f32,
        parent_margin: f32,
        root: f32,
        phase: Phase,
    ) -> Option<f32> {
        match self {
            Self::Pixels(px) => Some(px.get() + parent_margin),
            Self::Percentage(per) => Some(parent / 100.0 * per.get()),
            Self::Fill => Some(available_parent),
            Self::RootPercentage(per) => Some(root / 100.0 * per.get()),
            Self::Flex(_) | Self::FillMinimum if phase == Phase::Final => Some(available_parent),
            Self::Fn(f) => f.call(SizeFnContext {
                parent,
                available_parent,
                parent_margin,
                root,
                phase,
            }),
            _ => None,
        }
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) fn min_max(
        &self,
        value: f32,
        parent_value: f32,
        available_parent_value: f32,
        single_margin: f32,
        margin: f32,
        minimum: &Self,
        maximum: &Self,
        root_value: f32,
        phase: Phase,
    ) -> f32 {
        let value = self
            .eval(
                parent_value,
                available_parent_value,
                margin,
                root_value,
                phase,
            )
            .unwrap_or(value + margin);

        let minimum_value = minimum
            .eval(
                parent_value,
                available_parent_value,
                margin,
                root_value,
                phase,
            )
            .map(|v| v + single_margin);
        let maximum_value = maximum.eval(
            parent_value,
            available_parent_value,
            margin,
            root_value,
            phase,
        );

        let mut final_value = value;

        if let Some(minimum_value) = minimum_value
            && minimum_value > final_value
        {
            final_value = minimum_value;
        }

        if let Some(maximum_value) = maximum_value
            && final_value > maximum_value
        {
            final_value = maximum_value;
        }

        final_value
    }

    pub(crate) fn most_fitting_size<'a>(&self, size: &'a f32, available_size: &'a f32) -> &'a f32 {
        match self {
            Self::Inner => available_size,
            _ => size,
        }
    }
}

impl Scaled for Size {
    fn scale(&mut self, scale_factor: f32) {
        if let Self::Pixels(s) = self {
            *s *= scale_factor;
        }
    }
}