effy 0.11.0

A small and friendly terminal FFmpeg helper that simplifies common tasks
use crossterm::event::{KeyCode, KeyEvent};
use ratatui::{
    layout::{Constraint, Flex, Layout, Rect},
    prelude::Frame,
    style::Stylize as _,
    symbols,
    text::{Line, Span},
    widgets::{Block, List, ListState, Paragraph, Widget as _},
};

use super::{KeyboardHandler, ModalResult, UiModal};
use crate::ui::{Theme, is_portrait, widget::BgClear};

pub(crate) struct LutModal {
    names: Vec<String>,
    state: ListState,
}

impl LutModal {
    pub(crate) fn new(names: Vec<String>, selected_index: usize) -> Self {
        Self {
            names,
            state: ListState::default().with_selected(Some(selected_index)),
        }
    }
}

impl UiModal for LutModal {
    fn render(&mut self, frame: &mut Frame, theme: &Theme) {
        let area = frame.area();
        let portrait = is_portrait(area);
        let [modal_area] = Layout::vertical([Constraint::Length(14)])
            .horizontal_margin(if portrait { 1 } else { area.width / 4 })
            .flex(Flex::Center)
            .areas(area);
        let [list_area, hints_area] =
            Layout::vertical([Constraint::Fill(1), Constraint::Length(1)])
                .horizontal_margin(2)
                .vertical_margin(1)
                .areas(modal_area);

        frame.render_widget(BgClear::new(theme.background_color()), modal_area);
        Block::bordered()
            .title(" LUT ".fg(theme.modal_title_color()))
            .border_set(symbols::border::THICK)
            .border_style(theme.border_modal_style())
            .render(modal_area, frame.buffer_mut());

        let list = List::new(self.names.iter().map(String::as_str))
            .style(theme.text_color())
            .highlight_style(theme.list_highlight_style())
            .highlight_symbol("> ");
        frame.render_stateful_widget(list, list_area, &mut self.state);
        self.render_status(hints_area, frame, theme);
    }
}

impl KeyboardHandler for LutModal {
    fn handle_key(&mut self, key: KeyEvent) -> ModalResult {
        let selected = self.state.selected().unwrap_or(0);
        let count = self.names.len();
        match key.code {
            KeyCode::Esc => return ModalResult::Close,
            KeyCode::Enter => return ModalResult::Lut(selected),
            KeyCode::Up | KeyCode::Left | KeyCode::Char('k') => {
                self.state.select(Some((selected + count - 1) % count));
            }
            KeyCode::Down | KeyCode::Right | KeyCode::Char('j') => {
                self.state.select(Some((selected + 1) % count));
            }
            KeyCode::Home => self.state.select(Some(0)),
            KeyCode::End => self.state.select(Some(count - 1)),
            _ => {}
        }
        ModalResult::None
    }
}

impl LutModal {
    fn render_status(&self, area: Rect, frame: &mut Frame, theme: &Theme) {
        let key_style = theme.key_style();
        let text_style = theme.text_color();
        let line = Line::from(vec![
            Span::styled("↑/↓", key_style),
            Span::styled(": select  ", text_style),
            Span::styled("Enter", key_style),
            Span::styled(": confirm  ", text_style),
            Span::styled("Esc", key_style),
            Span::styled(": close", text_style),
        ]);
        frame.render_widget(Paragraph::new(line), area);
    }
}