termint 0.8.1

Library for colored printing and Terminal User Interfaces
Documentation
use crate::{prelude::Rect, widgets::Widget};

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct LayoutNode {
    pub area: Rect,
    pub children: Vec<LayoutNode>,
    pub is_dirty: bool,
    pub has_dirty_child: bool,
}

impl LayoutNode {
    /// Constructs new [`LayoutNode`] recursively based on the given [`Widget`]
    /// tree.
    pub fn new<M>(widget: &dyn Widget<M>) -> Self
    where
        M: Clone + 'static,
    {
        let children = widget.children();
        Self {
            area: Rect::default(),
            children: children.iter().map(|c| LayoutNode::new(*c)).collect(),
            is_dirty: true,
            has_dirty_child: true,
        }
    }

    /// Updates the layout tree to match the new widget tree.
    ///
    /// When there's a change compared to the old widget tree, it sets the
    /// node as dirty so that it relayouts itself later.
    pub fn diff<M>(&mut self, old: &dyn Widget<M>, new: &dyn Widget<M>)
    where
        M: Clone + 'static,
    {
        if old.layout_hash() != new.layout_hash() {
            self.is_dirty = true;
        }

        let old_children = old.children();
        let new_children = new.children();
        if old_children.len() != new_children.len() {
            self.children
                .resize(new_children.len(), LayoutNode::default());
            self.is_dirty = true;
        }

        for (i, child) in new_children.iter().enumerate() {
            let Some(old_child) = old_children.get(i) else {
                self.children[i] = LayoutNode::new(*child);
                continue;
            };

            self.children[i].diff(*old_child, *child);
            if self.children[i].is_dirty || self.children[i].has_dirty_child {
                self.has_dirty_child = true;
            }
        }
    }

    /// Wraps the widget layouting, but also adds required checks and sets the
    /// node attributes.
    ///
    /// Widgets should call this when layouting its children, since without the
    /// checks it would layout every time, instead of only when needed.
    pub fn layout<M>(&mut self, widget: &dyn Widget<M>, area: Rect)
    where
        M: Clone + 'static,
    {
        if !self.is_dirty && !self.has_dirty_child && self.area == area {
            return;
        }

        self.area = area;

        widget.layout(self, self.area);

        self.is_dirty = false;
        self.has_dirty_child = false;
    }
}