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::component::FocusContext;
use crate::core::node::{NodeId, NodeKind, NodeTree};
use crate::layout::reconcile::{ElementReconcile, OverlayState, ReconcileCtx, reconcile_element};
use crate::overlay::OverlayScope;
use crate::style::Rect;
use crate::widgets::popover::layout::resolve_popover_rect;
use crate::widgets::popover::node::PopoverNode;

pub(crate) fn reconcile_popover(
    tree: &mut NodeTree,
    epoch: u32,
    id: NodeId,
    popover: &super::Popover,
    rect: Rect,
    focus: Option<&FocusContext>,
    overlay_state: &mut OverlayState,
) -> NodeId {
    let old_children = {
        let node = tree.node_mut(id);
        node.rect = rect;
        node.kind = NodeKind::Popover(PopoverNode {
            trigger: Box::new(NodeId::INVALID),
            content: Box::new(NodeId::INVALID),
            on_close: popover.on_close.clone(),
            open: popover.open,
            scope: popover.scope,
        });
        std::mem::take(&mut node.children)
    };

    let reuse_trigger = old_children.first().copied();
    let reuse_content = old_children.get(1).copied();

    let mut new_children = Vec::with_capacity(2);

    let trigger_id = reconcile_element(
        &mut ReconcileCtx {
            tree,
            epoch,
            focus,
            overlay_state,
        },
        ElementReconcile {
            reuse: reuse_trigger,
            parent: Some(id),
            el: &popover.trigger,
            rect,
        },
    );
    new_children.push(trigger_id);

    let mut content_rect = None;
    if popover.open {
        let trigger_rect = tree.node(trigger_id).rect;
        let bounds = tree.node(tree.root).rect;
        let resolved = resolve_popover_rect(popover, trigger_rect, bounds);

        let content_id = reconcile_element(
            &mut ReconcileCtx {
                tree,
                epoch,
                focus,
                overlay_state,
            },
            ElementReconcile {
                reuse: reuse_content,
                parent: Some(id),
                el: &popover.content,
                rect: resolved,
            },
        );
        new_children.push(content_id);
        if matches!(popover.scope, OverlayScope::Local) {
            content_rect = Some(resolved);
        }
    }

    let trigger_rect = tree.node(new_children[0]).rect;
    let mut popover_rect = trigger_rect;
    if let Some(content_rect) = content_rect {
        let x1 = trigger_rect.x.min(content_rect.x);
        let y1 = trigger_rect.y.min(content_rect.y);
        let x2 = (trigger_rect.x.saturating_add(trigger_rect.w as i16))
            .max(content_rect.x.saturating_add(content_rect.w as i16));
        let y2 = (trigger_rect.y.saturating_add(trigger_rect.h as i16))
            .max(content_rect.y.saturating_add(content_rect.h as i16));
        popover_rect = Rect {
            x: x1,
            y: y1,
            w: (x2.saturating_sub(x1)) as u16,
            h: (y2.saturating_sub(y1)) as u16,
        };
    }

    let node = tree.node_mut(id);
    node.children = new_children.clone();
    if let NodeKind::Popover(popover_node) = &mut node.kind {
        *popover_node.trigger = new_children[0];
        if popover.open && new_children.len() > 1 {
            *popover_node.content = new_children[1];
        }
    }

    node.rect = popover_rect;

    id
}