schemaui 0.7.2

A Rust library for generating TUI and Web UIs from JSON Schemas for configuration management.
Documentation
use ratatui::{
    Frame,
    layout::{Constraint, Direction, Layout},
    style::{Color, Modifier, Style},
    widgets::{Block, Borders, Clear, Paragraph, Wrap},
};

use crate::tui::state::FormState;

use super::super::frame::CompositeOverlay;
use super::{body::render_body, layout::popup_rect, tabstrip::render_tab_strip};

pub fn render_composite_overlay(
    frame: &mut Frame<'_>,
    overlay: &CompositeOverlay,
    overlay_form: &mut FormState,
) {
    let base = frame.area();
    let width = base.width.saturating_sub(base.width / 4).max(40);
    let height = base.height.saturating_sub(base.height / 5).max(12);
    let area = popup_rect(base, width, height);
    frame.render_widget(Clear, area);

    let mut block_title = format!("Overlay {}{}", overlay.level, overlay.title);
    if overlay.dirty {
        block_title.push_str("  • DIRTY");
    }
    let block = Block::default()
        .title(block_title)
        .borders(Borders::ALL)
        .style(
            Style::default()
                .fg(Color::Cyan)
                .add_modifier(Modifier::BOLD),
        );
    frame.render_widget(block.clone(), area);
    let inner = block.inner(area);

    let mut constraints = Vec::new();
    if overlay.list_entries.is_some() {
        constraints.push(Constraint::Length(3));
    }
    constraints.push(Constraint::Min(5));
    constraints.push(Constraint::Length(2));

    let layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints(constraints)
        .split(inner);

    let mut next = 0usize;
    if let Some(entries) = &overlay.list_entries {
        let label = overlay.entry_label.as_deref().unwrap_or("Entries");
        render_tab_strip(
            frame,
            layout[next],
            entries,
            overlay.list_selected.unwrap_or(0),
            label,
        );
        next += 1;
    }

    let body_area = layout[next];
    let layout_desc = layout_section_description(overlay_form);
    let combined_description = if overlay.description.is_some() || layout_desc.is_some() {
        let mut text = String::new();
        if let Some(desc) = &overlay.description {
            text.push_str(desc);
        }
        if let Some(layout) = layout_desc {
            if !text.is_empty() {
                text.push_str("\n\n");
            }
            text.push_str(&layout);
        }
        Some(text)
    } else {
        None
    };

    if let Some(description) = combined_description {
        let sub = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Length(4), Constraint::Min(1)])
            .split(body_area);
        let desc = Paragraph::new(description)
            .wrap(Wrap { trim: true })
            .style(Style::default().fg(Color::Gray));
        frame.render_widget(desc, sub[0]);
        render_body(frame, sub[1], overlay_form, true);
    } else {
        render_body(frame, body_area, overlay_form, true);
    }

    let footer_area = layout.last().copied().unwrap_or(body_area);
    let footer = Paragraph::new(overlay.instructions.clone())
        .wrap(Wrap { trim: true })
        .style(Style::default().fg(Color::Yellow))
        .block(
            Block::default()
                .borders(Borders::TOP)
                .title("Overlay Controls"),
        );
    frame.render_widget(footer, footer_area);
}

pub(crate) fn layout_section_description(form: &FormState) -> Option<String> {
    let nav = form.layout_nav()?;
    let pointer = form
        .focused_field()
        .map(|field| field.schema.pointer.clone())?;

    for root in &nav.roots {
        for section in &root.sections {
            if section.pointers.iter().any(|p| p == &pointer) {
                return Some(format!("Section: {} - {}", root.title, section.title));
            }
        }
    }

    None
}