#![allow(dead_code)]
use crate::tui::theme::Theme;
use ratatui::{
layout::Rect,
style::{Modifier, Style},
text::{Line, Span, Text},
widgets::{Block, Borders, Clear, Paragraph, Wrap},
Frame,
};
pub struct ModalButton<'a> {
pub label: &'a str,
pub focused: bool,
}
pub fn render(
frame: &mut Frame,
screen: Rect,
title: &str,
body: &[&str],
buttons: &[ModalButton],
theme: &Theme,
) {
theme.overlay_dim(frame, screen);
let modal = theme.centered_rect(60, 50, screen);
frame.render_widget(Clear, modal);
let mut lines: Vec<Line> = Vec::new();
lines.push(Line::from(""));
for (i, text) in body.iter().enumerate() {
let cor = if i % 2 == 0 {
theme.text_strong
} else {
theme.text_muted
};
lines.push(Line::from(Span::styled(
format!(" {text}"),
Style::default().fg(cor),
)));
}
lines.push(Line::from(""));
let mut btn_spans: Vec<Span> = Vec::new();
for (i, btn) in buttons.iter().enumerate() {
if i > 0 {
btn_spans.push(Span::raw(" "));
}
let estilo = if btn.focused {
Style::default()
.bg(theme.primary)
.fg(theme.on_primary)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(theme.text_muted)
};
btn_spans.push(Span::styled(format!(" {} ", btn.label), estilo));
}
if !btn_spans.is_empty() {
let mut linha_btn = vec![Span::raw(" ")]; linha_btn.extend(btn_spans);
lines.push(Line::from(linha_btn));
}
lines.push(Line::from(""));
let titulo_esq = format!(" {title} ");
let bloco = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(theme.primary))
.title(titulo_esq.as_str())
.title_bottom(Line::from(Span::styled(
" esc ",
Style::default().fg(theme.text_muted),
)));
let paragrafo = Paragraph::new(Text::from(lines))
.block(bloco)
.wrap(Wrap { trim: false });
frame.render_widget(paragrafo, modal);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tui::theme::{self, ColorDepth};
use ratatui::{backend::TestBackend, layout::Rect, Terminal};
fn make_theme() -> Theme {
theme::theme(ColorDepth::TrueColor)
}
fn smoke(width: u16, height: u16, body: &[&str], buttons_cfg: &[(&str, bool)]) {
let backend = TestBackend::new(width, height);
let mut terminal = Terminal::new(backend).unwrap();
let t = make_theme();
terminal
.draw(|f| {
let screen = Rect::new(0, 0, width, height);
let btns: Vec<ModalButton> = buttons_cfg
.iter()
.map(|(label, focused)| ModalButton {
label,
focused: *focused,
})
.collect();
render(f, screen, "Teste", body, &btns, &t);
})
.unwrap();
}
#[test]
fn render_dois_botoes_80x24_sem_panic() {
smoke(
80,
24,
&["Linha de corpo.", "Segunda linha."],
&[("Confirmar", true), ("Cancelar", false)],
);
}
#[test]
fn render_dois_botoes_40x12_sem_panic() {
smoke(40, 12, &["Corpo."], &[("Ok", true), ("Não", false)]);
}
#[test]
fn render_body_vazio_sem_panic() {
smoke(80, 24, &[], &[("Ok", true)]);
}
#[test]
fn render_multiplas_linhas_sem_panic() {
let corpo: Vec<&str> = (0..20)
.map(|_| "linha de texto longa para stress test")
.collect();
smoke(
80,
24,
&corpo,
&[("Sim", true), ("Não", false), ("Cancelar", false)],
);
}
#[test]
fn render_sem_botoes_sem_panic() {
smoke(80, 24, &["Apenas corpo, sem botões."], &[]);
}
#[test]
fn render_terminal_minimo_sem_panic() {
smoke(10, 5, &["x"], &[("Ok", true)]);
}
#[test]
fn render_botao_nao_focado_usa_text_muted() {
smoke(80, 24, &["Corpo"], &[("Confirmar", false)]);
}
#[test]
fn render_botao_focado_usa_primary() {
smoke(80, 24, &["Corpo"], &[("Confirmar", true)]);
}
}