tui-lipan 0.1.0

Opinionated, component-based TUI framework for Rust - declarative components, reconciliation, layout engine, focus, overlays, and rich widgets on top of ratatui.
Documentation
use crate::core::node::{NodeId, NodeKind};
use crate::layout::measure::min_size_constrained;
use crate::layout::reconcile::{
    ElementReconcile, ReconcileCtx, apply_constraints, reconcile_element,
};
use crate::layout::tag::can_reuse;
use crate::style::{LayoutConstraints, Length, Rect};

use super::{Divider, DividerNode, Orientation, measure_divider};

pub(crate) struct DividerReconcile<'a> {
    pub id: NodeId,
    pub divider: &'a Divider,
    pub rect: Rect,
    pub constraints: &'a LayoutConstraints,
}

pub fn reconcile_divider(ctx: &mut ReconcileCtx<'_>, args: DividerReconcile<'_>) -> NodeId {
    let DividerReconcile {
        id,
        divider,
        rect,
        constraints,
    } = args;
    let (w, h) = measure_divider(divider);

    let avail_w = rect.w;
    let avail_h = rect.h;
    let mut rect = rect;
    if matches!(divider.width, Length::Auto) {
        rect.w = w.min(rect.w);
    }
    if matches!(divider.height, Length::Auto) {
        rect.h = h.min(rect.h);
    }
    apply_constraints(&mut rect, constraints, avail_w, avail_h);

    let old_children = {
        let node = ctx.tree.node_mut(id);
        node.rect = rect;
        node.kind = NodeKind::Divider(DividerNode::from(divider.clone()));
        std::mem::take(&mut node.children)
    };

    let reuse_child = if divider.orientation == Orientation::Horizontal {
        divider.label.as_deref().and_then(|label| {
            old_children
                .iter()
                .copied()
                .find(|id| ctx.tree.is_valid(*id) && can_reuse(ctx.tree.node(*id), label))
        })
    } else {
        None
    };

    let mut new_children = old_children;
    new_children.clear();

    if divider.orientation == Orientation::Horizontal
        && let Some(label) = divider.label.as_deref()
    {
        let padding = divider.label_padding;
        let max_label_w = rect.w.saturating_sub(padding.saturating_mul(2));
        if max_label_w > 0 && rect.h > 0 {
            let (min_label_w, _) = min_size_constrained(label, Some(max_label_w), Some(1));
            let label_w = match divider.label_alignment {
                crate::style::Align::Stretch => max_label_w,
                _ => min_label_w,
            };

            if label_w > 0 {
                let label_x = match divider.label_alignment {
                    crate::style::Align::Start | crate::style::Align::Stretch => {
                        rect.x.saturating_add(padding as i16)
                    }
                    crate::style::Align::Center => rect
                        .x
                        .saturating_add((rect.w.saturating_sub(label_w) / 2) as i16),
                    crate::style::Align::End => rect
                        .x
                        .saturating_add(rect.w.saturating_sub(label_w) as i16)
                        .saturating_sub(padding as i16),
                };

                let child_rect = Rect {
                    x: label_x,
                    y: rect.y,
                    w: label_w,
                    h: 1,
                };

                let child_id = reconcile_element(
                    ctx,
                    ElementReconcile {
                        reuse: reuse_child,
                        parent: Some(id),
                        el: label,
                        rect: child_rect,
                    },
                );
                new_children.push(child_id);
            }
        }
    }

    let node = ctx.tree.node_mut(id);
    node.children = new_children;

    id
}