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::component::ChildOf;
use crate::component::text::Text;
use crate::layout::{Desc, Layout, base, flex, leaf};
use crate::persist::{FnPersist, VectorMap};
use crate::{SourceID, UNSIZED_AXIS, gen_id, layout};
use core::f32;
use derive_where::derive_where;
use std::rc::Rc;
use std::sync::Arc;

#[derive(feather_macro::StateMachineChild)]
#[derive_where(Clone)]
pub struct Paragraph<T> {
    pub id: Arc<SourceID>,
    props: Rc<T>,
    children: im::Vector<Option<Box<ChildOf<dyn flex::Prop>>>>,
}

#[derive(Clone, Copy, Default, PartialEq, PartialOrd)]
struct MinimalFlexChild {
    grow: f32,
}

impl flex::Child for MinimalFlexChild {
    fn grow(&self) -> f32 {
        self.grow
    }

    fn shrink(&self) -> f32 {
        0.0
    }

    fn basis(&self) -> crate::DValue {
        crate::DValue {
            dp: 0.0,
            px: 0.0,
            rel: UNSIZED_AXIS,
        }
    }
}

impl base::Area for MinimalFlexChild {
    fn area(&self) -> &crate::DRect {
        &crate::AUTO_DRECT
    }
}

impl base::Anchor for MinimalFlexChild {}
impl base::Order for MinimalFlexChild {}
impl base::Margin for MinimalFlexChild {}
impl base::RLimits for MinimalFlexChild {}
impl base::Limits for MinimalFlexChild {}
impl base::Padding for MinimalFlexChild {}
impl leaf::Prop for MinimalFlexChild {}
impl leaf::Padded for MinimalFlexChild {}

impl<T: flex::Prop + 'static> Paragraph<T> {
    pub fn new(id: Arc<SourceID>, props: T) -> Self {
        Self {
            id,
            props: props.into(),
            children: im::Vector::new(),
        }
    }

    pub fn append(&mut self, child: Box<ChildOf<dyn flex::Prop>>) {
        self.children.push_back(Some(child));
    }

    pub fn prepend(&mut self, child: Box<ChildOf<dyn flex::Prop>>) {
        self.children.push_front(Some(child));
    }

    #[allow(clippy::too_many_arguments)]
    pub fn set_text(
        &mut self,
        text: &str,
        font_size: f32,
        line_height: f32,
        font: cosmic_text::FamilyOwned,
        color: sRGB,
        weight: cosmic_text::Weight,
        style: cosmic_text::Style,
        fullwidth: bool,
    ) {
        self.children.clear();
        for (i, word) in text.split_ascii_whitespace().enumerate() {
            let text = Text::<MinimalFlexChild>::new(
                gen_id!(gen_id!(self.id), i),
                MinimalFlexChild {
                    grow: if fullwidth { 1.0 } else { 0.0 },
                },
                font_size,
                line_height,
                word.to_owned() + " ",
                font.clone(),
                color,
                weight,
                style,
                cosmic_text::Wrap::None,
                None, // paragraph does it's own alignment so we don't set any here
            );
            self.children.push_back(Some(Box::new(text)));
        }
    }
}

impl<T: flex::Prop + 'static> super::Component for Paragraph<T> {
    type Props = T;

    fn layout(
        &self,
        manager: &mut crate::StateManager,
        driver: &crate::graphics::Driver,
        window: &Arc<SourceID>,
    ) -> Box<dyn Layout<T>> {
        let mut map = VectorMap::new(crate::persist::Persist::new(
            |child: &Option<Box<ChildOf<dyn flex::Prop>>>| -> Option<Box<dyn Layout<<dyn flex::Prop as Desc>::Child>>> {
                Some(child.as_ref()?.layout(manager, driver, window))
            })
        );

        let (_, children) = map.call(Default::default(), &self.children);
        Box::new(layout::Node::<T, dyn flex::Prop> {
            props: self.props.clone(),
            children,
            id: Arc::downgrade(&self.id),
            renderable: None,
            layer: None,
        })
    }
}