hefesto-widgets 0.7.0

Ratatui widgets for the Hefesto TUI toolkit: popups, scrollable lists, trees, text input and spinners
Documentation
use ratatui::{
    buffer::Buffer,
    layout::{Alignment, Constraint, Layout, Rect},
    text::{Line, Span},
    style::{Color, Style},
    prelude::Stylize,
    widgets::{
        Block, Borders, Padding, Paragraph, Widget, Wrap,
    },
};

use crate::popup::Popup;
use crate::{BorderType, DotPattern, PopupSize};

#[derive(Clone)]
pub struct ConfirmationPopup<'a> {
    popup: Popup<'a>,
    body: Vec<Line<'a>>,
    confirm_style: Style,
    cancel_style: Style,
    confirm_label: &'a str,
    cancel_label: &'a str,
}

impl<'a> ConfirmationPopup<'a> {
    pub fn new() -> Self {
        Self {
            popup: Popup::new(Color::White).padding(0).header(),
            body: vec![],
            confirm_style: Style::new().bg(Color::Green).fg(Color::Black),
            cancel_style: Style::new().bg(Color::Gray).fg(Color::Black),
            confirm_label: "Confirmar",
            cancel_label: "Cancelar",
        }
    }

    pub fn title(mut self, title: &'a str) -> Self {
        self.popup = self.popup.title(title);
        self
    }

    pub fn body(mut self, body: Vec<Line<'a>>) -> Self {
        self.body = body;
        self
    }

    pub fn border_color(mut self, color: Color) -> Self {
        self.popup = self.popup.border_color(color);
        self
    }

    /// Background color for the entire popup area (including border).
    /// Delegates to `Popup::bg_color`.
    pub fn bg_color(mut self, color: Color) -> Self {
        self.popup = self.popup.bg_color(color);
        self
    }

    pub fn confirm_style(mut self, style: Style) -> Self {
        self.confirm_style = style;
        self
    }

    pub fn cancel_style(mut self, style: Style) -> Self {
        self.cancel_style = style;
        self
    }

    pub fn options(mut self, confirm_label: &'a str, cancel_label: &'a str) -> Self {
        self.confirm_label = confirm_label;
        self.cancel_label = cancel_label;
        self
    }

    pub fn border_type(mut self, border_type: BorderType) -> Self {
        self.popup = self.popup.border_type(border_type);
        self
    }

    pub fn padding(mut self, padding: u16) -> Self {
        self.popup = self.popup.padding(padding);
        self
    }

    pub fn position(mut self, x: u16, y: u16) -> Self {
        self.popup = self.popup.position(x, y);
        self
    }

    pub fn width(mut self, width: PopupSize) -> Self {
        self.popup = self.popup.width(width);
        self
    }

    pub fn height(mut self, height: PopupSize) -> Self {
        self.popup = self.popup.height(height);
        self
    }

    pub fn origin(mut self, x: u16, y: u16) -> Self {
        self.popup = self.popup.origin(x, y);
        self
    }

    pub fn header(mut self) -> Self {
        self.popup = self.popup.header();
        self
    }

    pub fn resolve_rect(&self, area: Rect) -> Rect {
        self.popup.resolve_rect(area)
    }

    pub fn no_background(mut self) -> Self {
        self.popup = self.popup.no_background();
        self
    }

    pub fn background_dots(mut self, color: Color, symbol: &str, density: u16) -> Self {
        self.popup = self.popup.background_dots(color, symbol, density);
        self
    }

    pub fn background_pattern(mut self, pattern: DotPattern) -> Self {
        self.popup = self.popup.background_pattern(pattern);
        self
    }

    pub fn background_spacing(mut self, density_x: u16, density_y: u16) -> Self {
        self.popup = self.popup.background_spacing(density_x, density_y);
        self
    }
}

impl Default for ConfirmationPopup<'_> {
    fn default() -> Self {
        Self::new()
    }
}

impl Widget for ConfirmationPopup<'_> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let Self { popup, body, confirm_style, cancel_style, confirm_label, cancel_label } = self;
        let border_color = popup.get_border_color();

        let inner = popup.render_inner(area, buf);

        let chunks = Layout::vertical([
            Constraint::Min(0),
            Constraint::Length(2),
        ]).split(inner);

        let body_para = Paragraph::new(body)
            .block(Block::new().padding(Padding::new(1, 1, 0, 0)))
            .wrap(Wrap { trim: false });
        body_para.render(chunks[0], buf);

        let border_style = Style::new().bold();
        let options_block = Block::new()
            .borders(Borders::TOP)
            .border_style(border_style.fg(border_color))
            .padding(Padding::new(1, 1, 0, 0));

        let options_inner = options_block.inner(chunks[1]);
        let option_chunks = Layout::horizontal([
            Constraint::Percentage(48),
            Constraint::Length(1),
            Constraint::Percentage(48),
        ]).split(options_inner);

        let confirm_para = Paragraph::new(Span::from(confirm_label).dim())
            .alignment(Alignment::Center)
            .style(confirm_style)
            .block(Block::new().padding(Padding::new(1, 1, 0, 0)));
        confirm_para.render(option_chunks[0], buf);

        let cancel_para = Paragraph::new(Span::from(cancel_label).dim())
            .alignment(Alignment::Center)
            .style(cancel_style)
            .block(Block::new().padding(Padding::new(1, 1, 0, 0)));
        cancel_para.render(option_chunks[2], buf);

        options_block.render(chunks[1], buf);
    }
}

#[cfg(test)]
mod tests;