feather-ui 0.4.0

Feather UI library
Documentation
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2025 Fundament Research Institute <https://fundament.institute>

use crate::color::sRGB;
use crate::layout::{Layout, leaf};
use crate::{DAbsPoint, SourceID};
use std::rc::Rc;
use std::sync::Arc;

#[repr(u8)]
pub enum ShapeKind {
    RoundRect,
    Triangle,
    Circle,
    Arc,
}

pub struct Shape<T, const KIND: u8> {
    id: std::sync::Arc<SourceID>,
    props: Rc<T>,
    border: f32,
    blur: f32,
    size: crate::DAbsPoint,
    corners: [f32; 4],
    fill: sRGB,
    outline: sRGB,
}

pub fn round_rect<T: leaf::Padded + 'static>(
    id: std::sync::Arc<SourceID>,
    props: T,
    border: f32,
    blur: f32,
    corners: wide::f32x4,
    fill: sRGB,
    outline: sRGB,
    size: DAbsPoint,
) -> Shape<T, { ShapeKind::RoundRect as u8 }> {
    Shape {
        id,
        props: props.into(),
        border,
        blur,
        corners: corners.to_array(),
        fill,
        outline,
        size,
    }
}

pub fn triangle<T: leaf::Padded + 'static>(
    id: std::sync::Arc<SourceID>,
    props: T,
    border: f32,
    blur: f32,
    corners: [f32; 3],
    offset: f32,
    fill: sRGB,
    outline: sRGB,
    size: DAbsPoint,
) -> Shape<T, { ShapeKind::Triangle as u8 }> {
    Shape {
        id,
        props: props.into(),
        border,
        blur,
        corners: [corners[0], corners[1], corners[2], offset],
        fill,
        outline,
        size,
    }
}

pub fn circle<T: leaf::Padded + 'static>(
    id: std::sync::Arc<SourceID>,
    props: T,
    border: f32,
    blur: f32,
    radii: [f32; 2],
    fill: sRGB,
    outline: sRGB,
    size: DAbsPoint,
) -> Shape<T, { ShapeKind::Circle as u8 }> {
    Shape {
        id,
        props: props.into(),
        border,
        blur,
        corners: [radii[0], radii[1], 0.0, 0.0],
        fill,
        outline,
        size,
    }
}

pub fn arcs<T: leaf::Padded + 'static>(
    id: std::sync::Arc<SourceID>,
    props: T,
    border: f32,
    blur: f32,
    inner_radius: f32,
    arcs: [f32; 2],
    fill: sRGB,
    outline: sRGB,
    size: DAbsPoint,
) -> Shape<T, { ShapeKind::Arc as u8 }> {
    Shape {
        id,
        props: props.into(),
        border,
        blur,
        corners: [arcs[0] + arcs[1] * 0.5, arcs[1] * 0.5, inner_radius, 0.0],
        fill,
        outline,
        size,
    }
}

impl<T: leaf::Padded + 'static, const KIND: u8> Clone for Shape<T, KIND> {
    fn clone(&self) -> Self {
        Self {
            id: self.id.duplicate(),
            props: self.props.clone(),
            border: self.border,
            blur: self.blur,
            corners: self.corners,
            fill: self.fill,
            outline: self.outline,
            size: self.size,
        }
    }
}

impl<T: leaf::Padded + 'static> Shape<T, { ShapeKind::RoundRect as u8 }> {
    pub fn new(
        id: std::sync::Arc<SourceID>,
        props: T,
        border: f32,
        blur: f32,
        corners: wide::f32x4,
        fill: sRGB,
        outline: sRGB,
        size: DAbsPoint,
    ) -> Self {
        Self {
            id,
            props: props.into(),
            border,
            blur,
            corners: corners.to_array(),
            fill,
            outline,
            size,
        }
    }
}

impl<T: leaf::Padded + 'static> Shape<T, { ShapeKind::Triangle as u8 }> {
    pub fn new(
        id: std::sync::Arc<SourceID>,
        props: T,
        border: f32,
        blur: f32,
        corners: [f32; 3],
        offset: f32,
        fill: sRGB,
        outline: sRGB,
        size: DAbsPoint,
    ) -> Self {
        Self {
            id,
            props: props.into(),
            border,
            blur,
            corners: [corners[0], corners[1], corners[2], offset],
            fill,
            outline,
            size,
        }
    }
}

impl<T: leaf::Padded + 'static> Shape<T, { ShapeKind::Circle as u8 }> {
    pub fn new(
        id: std::sync::Arc<SourceID>,
        props: T,
        border: f32,
        blur: f32,
        radii: [f32; 2],
        fill: sRGB,
        outline: sRGB,
        size: DAbsPoint,
    ) -> Self {
        Self {
            id,
            props: props.into(),
            border,
            blur,
            corners: [radii[0], radii[1], 0.0, 0.0],
            fill,
            outline,
            size,
        }
    }
}

impl<T: leaf::Padded + 'static> Shape<T, { ShapeKind::Arc as u8 }> {
    pub fn new(
        id: std::sync::Arc<SourceID>,
        props: T,
        border: f32,
        blur: f32,
        inner_radius: f32,
        arcs: [f32; 2],
        fill: sRGB,
        outline: sRGB,
        size: DAbsPoint,
    ) -> Self {
        Self {
            id,
            props: props.into(),
            border,
            blur,
            corners: [arcs[0] + arcs[1] * 0.5, arcs[1] * 0.5, inner_radius, 0.0],
            fill,
            outline,
            size,
        }
    }
}

impl<T: leaf::Padded + 'static, const KIND: u8> crate::StateMachineChild for Shape<T, KIND> {
    fn id(&self) -> std::sync::Arc<SourceID> {
        self.id.clone()
    }
}

impl<T: leaf::Padded + 'static, const KIND: u8> super::Component for Shape<T, KIND>
where
    for<'a> &'a T: Into<&'a (dyn leaf::Padded + 'static)>,
{
    type Props = T;

    fn layout(
        &self,
        manager: &mut crate::StateManager,
        _: &crate::graphics::Driver,
        window: &Arc<SourceID>,
    ) -> Box<dyn Layout<T>> {
        let dpi = manager
            .get::<super::window::WindowStateMachine>(window)
            .map(|x| x.state.dpi)
            .unwrap_or(crate::BASE_DPI);

        let mut corners = self.corners;
        if KIND == ShapeKind::RoundRect as u8 {
            corners[0] *= dpi.width;
            corners[1] *= dpi.height;
            corners[2] *= dpi.width;
            corners[3] *= dpi.height;
        }

        Box::new(leaf::Sized::<T> {
            props: self.props.clone(),
            id: Arc::downgrade(&self.id),
            size: self.size.resolve(dpi).to_vector().to_size().cast_unit(),
            renderable: Some(Rc::new(crate::render::shape::Instance::<KIND> {
                padding: self.props.padding().as_perimeter(dpi),
                border: self.border,
                blur: self.blur,
                fill: self.fill,
                outline: self.outline,
                corners,
                id: self.id.clone(),
            })),
        })
    }
}