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::reconcile::{
    ElementReconcile, ReconcileCtx, reconcile_element, resolve_rect_with_auto,
};
use crate::style::{LayoutConstraints, Rect};
use crate::widgets::containers::reconcile::stack_reuse_plan;

use super::layout::{layout_splitter, measure_splitter};
use super::{Splitter, SplitterNode, resolve_weights};

pub(crate) struct SplitterReconcile<'a> {
    pub parent: NodeId,
    pub old_children: Vec<NodeId>,
    pub splitter: &'a Splitter,
    pub bounds: Rect,
    pub constraints: &'a LayoutConstraints,
}

pub(crate) fn reconcile_splitter(
    ctx: &mut ReconcileCtx<'_>,
    args: SplitterReconcile<'_>,
) -> Vec<NodeId> {
    let SplitterReconcile {
        parent,
        mut old_children,
        splitter,
        bounds,
        constraints,
    } = args;
    let plan = stack_reuse_plan(ctx.tree, &old_children, &splitter.children);

    old_children.clear();

    let resolved_max_w = constraints.max_w.and_then(|l| l.resolve_as_max(bounds.w));
    let resolved_max_h = constraints.max_h.and_then(|l| l.resolve_as_max(bounds.h));
    let (w, h) = measure_splitter(splitter, resolved_max_w, resolved_max_h);
    let rect = resolve_rect_with_auto(bounds, constraints, splitter.width, splitter.height, w, h);

    let (prev_weights, active_handle, prev_weights_nonce) = {
        let node = ctx.tree.node(parent);
        if let NodeKind::Splitter(existing) = &node.kind {
            (
                existing.weights.clone(),
                existing.active_handle,
                existing.weights_nonce,
            )
        } else {
            (Vec::new(), None, 0)
        }
    };

    let len = splitter.children.len();
    let explicit = &splitter.weights;
    let weights = if active_handle.is_some() {
        resolve_weights(explicit, &prev_weights, len)
    } else if splitter.weights_nonce > prev_weights_nonce
        && explicit.len() == len
        && !explicit.is_empty()
    {
        resolve_weights(explicit, &[], len)
    } else {
        resolve_weights(explicit, &prev_weights, len)
    };
    let layout = layout_splitter(splitter, &weights, rect);

    for (child, (reuse_id, child_rect)) in splitter
        .children
        .iter()
        .zip(plan.into_iter().zip(layout.pane_rects.iter().copied()))
    {
        let child_id = reconcile_element(
            ctx,
            ElementReconcile {
                reuse: reuse_id,
                parent: Some(parent),
                el: child,
                rect: child_rect,
            },
        );
        old_children.push(child_id);
    }

    let node = ctx.tree.node_mut(parent);
    node.rect = rect;
    node.kind = NodeKind::Splitter(SplitterNode {
        orientation: splitter.orientation,
        weights,
        weights_nonce: splitter.weights_nonce,
        split_id: splitter.split_id.clone(),
        on_resize: splitter.on_resize.clone(),
        min_size: splitter.min_size,
        join_frame: splitter.join_frame,
        handle_symbol: splitter.handle_symbol,
        handle_style: splitter.handle_style,
        handle_hover_style: splitter.handle_hover_style,
        handle_active_style: splitter.handle_active_style,
        handle_rects: layout.handle_rects,
        pane_sizes: layout.pane_sizes,
        active_handle,
    });

    old_children
}