use ratatui::{
buffer::Buffer,
layout::{Alignment, Constraint, Layout, Rect},
text::Line,
style::{Color, Style},
prelude::Stylize,
widgets::{
Block, Borders, Clear, Padding, Paragraph, Widget, Wrap,
},
};
use crate::popup::Popup;
use crate::BorderType;
const POPUP_WIDTH: u16 = 44;
const POPUP_HEIGHT: u16 = 9;
#[derive(Clone)]
pub struct ConfirmationPopup<'a> {
title: Option<&'a str>,
border_title: Option<&'a str>,
body: Vec<Line<'a>>,
border_color: Color,
border_type: BorderType,
confirm_bg: Color,
cancel_bg: Color,
confirm_label: &'a str,
cancel_label: &'a str,
position: Option<Rect>,
}
impl<'a> ConfirmationPopup<'a> {
pub fn new() -> Self {
Self {
title: None,
border_title: None,
body: vec![],
border_color: Color::White,
border_type: BorderType::Rounded,
confirm_bg: Color::Green,
cancel_bg: Color::Gray,
confirm_label: "Confirmar",
cancel_label: "Cancelar",
position: None,
}
}
pub fn title(mut self, title: &'a str) -> Self {
self.title = Some(title);
self
}
#[allow(dead_code)]
pub fn border_title(mut self, border_title: &'a str) -> Self {
self.border_title = Some(border_title);
self
}
pub fn body(mut self, body: Vec<Line<'a>>) -> Self {
self.body = body;
self
}
#[allow(dead_code)]
pub fn border_color(mut self, color: Color) -> Self {
self.border_color = color;
self
}
#[allow(dead_code)]
pub fn confirm_bg(mut self, color: Color) -> Self {
self.confirm_bg = color;
self
}
#[allow(dead_code)]
pub fn cancel_bg(mut self, color: Color) -> Self {
self.cancel_bg = color;
self
}
#[allow(dead_code)]
pub fn options(mut self, confirm: &'a str, cancel: &'a str) -> Self {
self.confirm_label = confirm;
self.cancel_label = cancel;
self
}
#[allow(dead_code)]
pub fn border_type(mut self, border_type: BorderType) -> Self {
self.border_type = border_type;
self
}
#[allow(dead_code)]
pub fn position(mut self, position: Rect) -> Self {
self.position = Some(position);
self
}
fn to_ratatui_border(bt: BorderType) -> ratatui::widgets::BorderType {
match bt {
BorderType::Plain => ratatui::widgets::BorderType::Plain,
BorderType::Rounded => ratatui::widgets::BorderType::Rounded,
BorderType::Double => ratatui::widgets::BorderType::Double,
BorderType::Thick => ratatui::widgets::BorderType::Thick,
BorderType::QuadrantInside => ratatui::widgets::BorderType::QuadrantInside,
BorderType::QuadrantOutside => ratatui::widgets::BorderType::QuadrantOutside,
BorderType::None => ratatui::widgets::BorderType::Plain,
}
}
fn centered_rect(area: Rect, width: u16, height: u16) -> Rect {
Rect {
x: (area.width.saturating_sub(width)) / 2,
y: (area.height.saturating_sub(height)) / 2,
width: width.min(area.width),
height: height.min(area.height),
}
}
}
impl Widget for ConfirmationPopup<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let popup_rect = self.position.unwrap_or_else(|| Self::centered_rect(area, POPUP_WIDTH, POPUP_HEIGHT));
Clear.render(popup_rect, buf);
let border_style = Style::new().bold();
let rt_border = Self::to_ratatui_border(self.border_type);
let mut outer_block = Block::bordered()
.border_type(rt_border)
.border_style(border_style.fg(self.border_color));
if let Some(border_title) = self.border_title {
outer_block = outer_block.title(border_title.to_string());
}
let inner = outer_block.inner(popup_rect);
outer_block.render(popup_rect, buf);
let chunks = Layout::vertical([
Constraint::Length(2),
Constraint::Min(0),
Constraint::Length(2),
]).split(inner);
let body_block = Block::new()
.padding(Padding::new(1, 1, 0, 0));
let options_block = Block::new()
.borders(Borders::TOP)
.border_style(border_style.fg(self.border_color))
.padding(Padding::new(1, 1, 0, 0));
let option_item = Block::new()
.padding(Padding::new(1, 1, 0, 0));
let confirm_style = Style::new().bg(self.confirm_bg).fg(Color::Black);
let cancel_style = Style::new().bg(self.cancel_bg).fg(Color::Black);
Popup::render_header(
chunks[0], buf,
self.border_color,
self.title.unwrap_or(""),
);
let body_para = Paragraph::new(self.body.clone())
.block(body_block)
.wrap(Wrap { trim: false });
body_para.render(chunks[1], buf);
let options_inner = options_block.inner(chunks[2]);
let option_chunks = Layout::horizontal([
Constraint::Percentage(48),
Constraint::Length(1),
Constraint::Percentage(48),
]).split(options_inner);
let confirm_para = Paragraph::new(format!("{}", self.confirm_label).dim())
.alignment(Alignment::Center)
.style(confirm_style)
.block(option_item.clone());
confirm_para.render(option_chunks[0], buf);
let cancel_para = Paragraph::new(format!("{}", self.cancel_label).dim())
.alignment(Alignment::Center)
.style(cancel_style)
.block(option_item);
cancel_para.render(option_chunks[2], buf);
options_block.render(chunks[2], buf);
}
}