rura 1.6.0

Interactive TUI pipeline editor built for rapid iteration
use crate::completable_input::CompletableInput;
use crate::theme::Theme;
use ratatui::buffer::Buffer;
use ratatui::layout::Constraint::Length;
use ratatui::layout::{Constraint, Direction, Layout, Margin, Rect};
use ratatui::prelude::{Line, Stylize, Widget};
use ratatui::style::Styled;
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
use std::cell::Cell;

pub struct SaveToFileWidget {
    pub title: String,
    pub theme: Theme,
    pub file_path_input: CompletableInput,
    pub error_message: Option<String>,
    cursor: Cell<(u16, u16)>,
}

impl SaveToFileWidget {
    pub fn new(title: String, shell: String, theme: Theme) -> Self {
        Self {
            title,
            theme,
            file_path_input: CompletableInput::file_only("", &shell),
            error_message: None,
            cursor: Cell::new((0, 0)),
        }
    }

    pub fn cursor(&self) -> (u16, u16) {
        self.cursor.get()
    }
}

impl Widget for &SaveToFileWidget {
    fn render(self, area: Rect, buf: &mut Buffer)
    where
        Self: Sized,
    {
        let height = if self.error_message.is_some() { 7 } else { 6 };

        let centered_area = area.centered(Constraint::Percentage(60), Constraint::Length(height));

        let centered_inner_area = centered_area.inner(Margin::new(1, 1));

        let [path_area, error_area, buttons_area] = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Length(3),
                Length(if self.error_message.is_some() { 1 } else { 0 }),
                Length(1),
            ])
            .areas(centered_inner_area);

        Clear.render(centered_area, buf);
        Block::default()
            .borders(Borders::ALL)
            .title(self.title.clone())
            .set_style(self.theme.popup)
            .render(centered_area, buf);

        let path_input_area = centered_inner_area.inner(Margin::new(1, 1));
        let shift = self
            .file_path_input
            .cursor()
            .saturating_sub(path_input_area.width.into()) as u16;
        Paragraph::new(self.file_path_input.value())
            .block(Block::default().borders(Borders::ALL))
            .scroll((0, shift))
            .render(path_area, buf);

        if let Some(error_message) = &self.error_message {
            Line::from(error_message.clone())
                .red()
                .on_white()
                .render(error_area, buf);
        }

        Line::from(vec![
            "Enter ".bold(),
            "Save | ".into(),
            "Esc ".bold(),
            "Cancel".into(),
        ])
        .right_aligned()
        .render(buttons_area, buf);

        self.cursor.set((
            (path_input_area.x + self.file_path_input.cursor() as u16)
                .min(path_input_area.width + path_area.x),
            path_input_area.y,
        ));
    }
}