use crate::dialog::DialogData;
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Margin, Rect},
style::{Color, Modifier, Style},
text::Text,
widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap},
Frame,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DialogTheme {
pub background: Color,
pub border: Color,
pub border_active: Color,
pub title: Color,
pub text: Color,
pub button: Color,
pub button_active: Color,
}
impl Default for DialogTheme {
fn default() -> Self {
Self {
background: Color::Reset,
border: Color::DarkGray,
border_active: Color::Cyan,
title: Color::Cyan,
text: Color::Reset,
button: Color::Gray,
button_active: Color::Cyan,
}
}
}
pub fn render_dialog<D>(
f: &mut Frame,
area: Rect,
data: &DialogData<D>,
active_button: usize,
theme: &DialogTheme,
) {
let message_height = data.message.lines().count().max(1) as u16;
let button_row_height = if data.buttons.is_empty() { 0 } else { 3 };
let total_height = (message_height + button_row_height + 4).min(area.height.max(3));
let width = (area.width * 60 / 100).clamp(20, area.width);
let x = area.x + (area.width.saturating_sub(width)) / 2;
let y = area.y + (area.height.saturating_sub(total_height)) / 2;
let dialog_area = Rect::new(x, y, width, total_height);
f.render_widget(Clear, dialog_area);
f.render_widget(
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(theme.border_active))
.title(format!(" {} ", data.title))
.title_style(
Style::default()
.fg(theme.title)
.add_modifier(Modifier::BOLD),
)
.style(Style::default().bg(theme.background)),
dialog_area,
);
let inner = dialog_area.inner(Margin {
horizontal: 2,
vertical: 1,
});
if data.is_loading {
f.render_widget(
Paragraph::new(data.message.as_str())
.style(
Style::default()
.fg(theme.text)
.add_modifier(Modifier::ITALIC),
)
.alignment(Alignment::Center)
.wrap(Wrap { trim: true }),
inner,
);
return;
}
let mut constraints = vec![Constraint::Min(message_height.max(1))];
if button_row_height > 0 {
constraints.push(Constraint::Length(button_row_height));
}
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(constraints)
.split(inner);
f.render_widget(
Paragraph::new(Text::from(data.message.as_str()))
.style(Style::default().fg(theme.text))
.alignment(Alignment::Center)
.wrap(Wrap { trim: true }),
chunks[0],
);
if data.buttons.is_empty() || chunks.len() < 2 {
return;
}
let count = data.buttons.len();
let button_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints(vec![Constraint::Ratio(1, count as u32); count])
.horizontal_margin(1)
.split(chunks[1]);
for (i, label) in data.buttons.iter().enumerate() {
let active = i == active_button;
let (text_style, border_style) = if active {
(
Style::default()
.fg(theme.button_active)
.add_modifier(Modifier::BOLD),
Style::default().fg(theme.border_active),
)
} else {
(
Style::default().fg(theme.button),
Style::default().fg(theme.border),
)
};
f.render_widget(
Paragraph::new(label.as_str())
.alignment(Alignment::Center)
.style(text_style)
.block(
Block::default()
.borders(Borders::ALL)
.border_style(border_style),
),
button_chunks[i],
);
}
}