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 std::sync::Arc;

use ratatui::widgets::Block;

use crate::backend::ratatui_backend::common::{
    apply_effect_style_clipped, border_horizontal_char, border_tabs_title_line,
    calculate_visible_borders, clear_fg_preserve_bg_clipped, fill_rect_clipped_style,
    style_paints_bg, style_uses_backdrop_bg, to_ratatui_border_set, to_ratatui_border_type,
    to_ratatui_style_with_terminal_bg,
};
use crate::style::{Color, Rect, RichText, Style};
use crate::widgets::GridProps;
use crate::widgets::TabVariant;
use crate::widgets::internal::StackProps;

pub(crate) struct VStackRenderCtx<'a> {
    pub active_tab_style: &'a Style,
    pub inactive_tab_style: &'a Style,
    pub tab_variant: &'a TabVariant,
    pub title_prefix: &'a Option<Arc<str>>,
    pub clip_rect: Option<Rect>,
    pub terminal_bg: Option<Color>,
}

pub(crate) fn render_vstack(
    f: &mut ratatui::Frame<'_>,
    props: &StackProps,
    tab_titles: &[RichText],
    active_tab: usize,
    rect: Rect,
    rrect: ratatui::layout::Rect,
    ctx: VStackRenderCtx<'_>,
) {
    let VStackRenderCtx {
        active_tab_style,
        inactive_tab_style,
        tab_variant,
        title_prefix,
        clip_rect,
        terminal_bg,
    } = ctx;
    let needs_bg = style_paints_bg(props.style);
    if needs_bg || props.border {
        if style_uses_backdrop_bg(props.style) {
            clear_fg_preserve_bg_clipped(f, rect, clip_rect);
        } else if needs_bg {
            fill_rect_clipped_style(f, rect, props.style, clip_rect, terminal_bg);
        }

        if props.border {
            let borders = calculate_visible_borders(rect, clip_rect);
            let mut block = Block::default()
                .borders(borders)
                .border_type(to_ratatui_border_type(props.border_style))
                .style(to_ratatui_style_with_terminal_bg(props.style, terminal_bg));

            if let Some(set) = to_ratatui_border_set(props.border_style) {
                block = block.border_set(set);
            }

            if !tab_titles.is_empty() {
                let active_tab_style = props.style.patch(*active_tab_style);
                let inactive_tab_style = props.style.patch(*inactive_tab_style);
                let mut line = border_tabs_title_line(
                    tab_titles,
                    active_tab,
                    active_tab_style,
                    inactive_tab_style,
                    *tab_variant,
                    props.style,
                    props.style,
                );

                if let Some(prefix) = title_prefix {
                    use ratatui::text::Span;
                    let prefix_style =
                        to_ratatui_style_with_terminal_bg(active_tab_style, terminal_bg);
                    let prefix_span = Span::styled(prefix.to_string(), prefix_style);

                    let h_char = border_horizontal_char(props.border_style);
                    let sep_span = Span::styled(
                        h_char.to_string(),
                        to_ratatui_style_with_terminal_bg(props.style, terminal_bg),
                    );

                    line.spans.insert(0, sep_span);
                    line.spans.insert(0, prefix_span);
                }

                block = block.title(line);
            }
            f.render_widget(block, rrect);
        }
    }
}

pub(crate) struct GridRenderCtx {
    pub clip_rect: Option<Rect>,
    pub terminal_bg: Option<Color>,
}

pub(crate) fn render_grid(
    f: &mut ratatui::Frame<'_>,
    props: &GridProps,
    rect: Rect,
    rrect: ratatui::layout::Rect,
    ctx: GridRenderCtx,
) {
    let GridRenderCtx {
        clip_rect,
        terminal_bg,
    } = ctx;
    let needs_bg = style_paints_bg(props.style);
    if needs_bg || props.border {
        if style_uses_backdrop_bg(props.style) {
            clear_fg_preserve_bg_clipped(f, rect, clip_rect);
        } else if needs_bg {
            fill_rect_clipped_style(f, rect, props.style, clip_rect, terminal_bg);
        }

        if props.border {
            let borders = calculate_visible_borders(rect, clip_rect);
            let mut block = Block::default()
                .borders(borders)
                .border_type(to_ratatui_border_type(props.border_style))
                .style(to_ratatui_style_with_terminal_bg(props.style, terminal_bg));

            if let Some(set) = to_ratatui_border_set(props.border_style) {
                block = block.border_set(set);
            }
            f.render_widget(block, rrect);
        }
    }
}

pub(crate) fn render_hstack(
    f: &mut ratatui::Frame<'_>,
    props: &StackProps,
    rect: Rect,
    rrect: ratatui::layout::Rect,
    clip_rect: Option<Rect>,
    terminal_bg: Option<Color>,
) {
    let needs_bg = style_paints_bg(props.style);
    if needs_bg || props.border {
        if style_uses_backdrop_bg(props.style) {
            clear_fg_preserve_bg_clipped(f, rect, clip_rect);
        } else if needs_bg {
            fill_rect_clipped_style(f, rect, props.style, clip_rect, terminal_bg);
        }

        if props.border {
            let borders = calculate_visible_borders(rect, clip_rect);
            let mut block = Block::default()
                .borders(borders)
                .border_type(to_ratatui_border_type(props.border_style))
                .style(to_ratatui_style_with_terminal_bg(props.style, terminal_bg));

            if let Some(set) = to_ratatui_border_set(props.border_style) {
                block = block.border_set(set);
            }
            f.render_widget(block, rrect);
        }
    }
}

pub(crate) fn render_zstack_center(
    f: &mut ratatui::Frame<'_>,
    style: &Style,
    rect: Rect,
    _rrect: ratatui::layout::Rect,
    clip_rect: Option<Rect>,
    terminal_bg: Option<ratatui::style::Color>,
) {
    if style_uses_backdrop_bg(*style) {
        clear_fg_preserve_bg_clipped(f, rect, clip_rect);
    } else if style_paints_bg(*style) {
        fill_rect_clipped_style(
            f,
            rect,
            *style,
            clip_rect,
            terminal_bg.map(crate::backend::ratatui_backend::common::from_ratatui_color),
        );
    }

    apply_effect_style_clipped(f, rect, *style, clip_rect, terminal_bg);
}