bawa 0.1.0

A TUI game save organizer
Documentation
use super::{popup::window_from_dimensions, Scroller};
use crate::{app::App, config::THEME};
use ratatui::{
    layout::{Alignment, Constraint, Layout, Offset, Rect},
    prelude::Buffer,
    style::{Style, Stylize},
    text::{Line, Span},
    widgets::{
        Block, BorderType, Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation,
        ScrollbarState, StatefulWidget, Widget, Wrap,
    },
    Frame,
};

pub fn draw_confirmation_window(f: &mut Frame, prompt: &mut Prompt) {
    let window = window_from_dimensions(20, 70, f.area());
    f.render_widget(prompt, window);
}

#[derive(Clone, Copy, Debug)]
pub enum Context {
    Deletion,
    Replacing,
    GameDeletion,
    ProfileDeletion,
}

pub struct Prompt {
    title: String,
    body: Vec<String>,
    pub context: Context,
    pub scroller: Scroller,
}

impl Prompt {
    pub fn new(app: &App, context: Context) -> Self {
        let title = match context {
            Context::Deletion => {
                let (count, postfix) = if app.tree_state.marked.is_empty() {
                    (1, "")
                } else {
                    (app.tree_state.marked.len(), "s")
                };
                format!("Permanently delete {count} selected file{postfix}")
            }
            Context::Replacing => "Overwrite the selected file".to_owned(),
            Context::GameDeletion => "Permanently delete the selected game".to_owned(),
            Context::ProfileDeletion => "Permanently delete the selected profile".to_owned(),
        };

        let body = match context {
            Context::Deletion if !app.tree_state.marked.is_empty() => {
                let profile = app.games.get_profile().unwrap();
                let marked_entries = app.tree_state.marked.iter();
                marked_entries
                    .map(|id| profile.rel_path_to(&profile.entries[*id].path))
                    .collect()
            }
            Context::Deletion | Context::Replacing => {
                let profile = app.games.get_profile().unwrap();
                vec![profile.rel_path_to(&app.selected_entry().unwrap().path)]
            }
            Context::GameDeletion => {
                vec![app.games.inner.get_selected().unwrap().name().into_owned()]
            }
            Context::ProfileDeletion => {
                vec![(app.games.get_profiles())
                    .get_selected()
                    .unwrap()
                    .name()
                    .into_owned()]
            }
        };

        Self {
            title,
            body,
            context,
            scroller: Scroller::default(),
        }
    }
}

impl Widget for &mut Prompt {
    fn render(self, area: Rect, buf: &mut Buffer)
    where
        Self: Sized,
    {
        Clear.render(area, buf);

        Block::bordered()
            .title(Line::styled(self.title.clone(), THEME.confirmation_border))
            .border_type(BorderType::Rounded)
            .border_style(THEME.confirmation_border)
            .title_alignment(Alignment::Center)
            .render(area, buf);

        let [body_area, yes_no] = Layout::vertical([Constraint::Fill(1), Constraint::Length(1)])
            .margin(1)
            .areas(area);

        let block = Block::default()
            .borders(Borders::BOTTOM)
            .border_style(THEME.confirmation_border);
        let inner_body_area = block.inner(body_area);

        let body = self
            .body
            .iter()
            .map(String::as_str)
            .map(Line::from)
            .collect::<Vec<_>>();

        let offset = self.scroller.offset(inner_body_area, &body);

        let mut scrollbar_state =
            ScrollbarState::new(self.scroller.length()).position(offset.into());
        Scrollbar::new(ScrollbarOrientation::VerticalRight).render(
            inner_body_area.offset(Offset { x: 1, y: 0 }),
            buf,
            &mut scrollbar_state,
        );

        let mut text = Paragraph::new(body).scroll((offset, 0)).block(block);

        if body_area.width > 0 {
            text = text.wrap(Wrap { trim: false });
        }

        let [yes_area, no_area] =
            Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).areas(yes_no);

        let yes = Paragraph::new(Line::from(vec![
            Span::styled("Y", Style::new().green()),
            Span::raw("es"),
        ]))
        .centered();
        let no = Paragraph::new(Line::from(vec![
            Span::styled("N", Style::new().red()),
            Span::raw("o"),
        ]))
        .centered();

        text.render(body_area, buf);
        yes.render(yes_area, buf);
        no.render(no_area, buf);
    }
}