changxi 0.3.0

TUI EPUB Reader
use crate::app::{App, ViewMode};
use crate::ui::{Component, centered_rect, get_border_type};
use ratatui::style::{Color, Style, Stylize};
use ratatui::{
    Frame,
    layout::{Constraint, Direction, Layout, Rect},
    text::Text,
    widgets::{Block, Borders, Clear, List, ListItem, Paragraph},
};
use ratatui_image::picker::Picker;

pub struct BookmarkBrowser;

impl Component for BookmarkBrowser {
    fn render(&self, f: &mut Frame, area: Rect, app: &mut App, _picker: &mut Picker) {
        let area = centered_rect(80, 80, area);

        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Min(0), Constraint::Length(3)])
            .split(area);

        let main_area = chunks[0];
        let help_area = chunks[1];

        let inner_chunks = Layout::default()
            .direction(Direction::Horizontal)
            .constraints([Constraint::Percentage(40), Constraint::Percentage(60)])
            .split(main_area);

        let list_area = inner_chunks[0];
        let preview_area = inner_chunks[1];

        // Render bookmark list
        self.render_bookmark_list(f, list_area, app);

        // Render bookmark preview
        self.render_bookmark_preview(f, preview_area, app);

        // Render help
        self.render_help(f, help_area, app);

        if app.mode == ViewMode::BookmarkRenaming {
            self.render_renaming_popup(f, area, app);
        }
    }
}

impl BookmarkBrowser {
    fn render_renaming_popup(&self, f: &mut Frame, area: Rect, app: &mut App) {
        let area = centered_rect(60, 20, area);
        f.render_widget(Clear, area);

        let border_type = get_border_type(&app.config);
        let block = Block::default()
            .borders(Borders::ALL)
            .border_type(border_type)
            .title(" Rename Bookmark ");

        let input = Paragraph::new(app.bookmark_rename_input.as_str())
            .block(block)
            .slow_blink()
            .style(Style::default().fg(Color::Yellow));

        f.render_widget(input, area);

        // Render cursor
        f.set_cursor_position((
            area.x + app.bookmark_rename_cursor_position as u16 + 1,
            area.y + 1,
        ));
    }

    fn render_bookmark_list(&self, f: &mut Frame, area: Rect, app: &mut App) {
        let mut list_items = Vec::new();

        for (i, bookmark) in app.bookmarks.iter().enumerate() {
            let content = format!("{}. {}", i + 1, bookmark.name);
            let mut text = Text::from(content);

            if app.bookmark_list_state.selected() == Some(i) {
                text = text.style(Style::default().fg(Color::Yellow));
            }

            list_items.push(ListItem::new(text));
        }

        if list_items.is_empty() {
            list_items.push(ListItem::new(Text::from("No bookmarks found.")));
        }

        let border_type = get_border_type(&app.config);

        let list = List::new(list_items)
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .border_type(border_type)
                    .title(" Bookmarks "),
            )
            .highlight_style(Style::default().fg(Color::Yellow))
            .highlight_symbol("> ");

        f.render_stateful_widget(list, area, &mut app.bookmark_list_state);
    }

    fn render_bookmark_preview(&self, f: &mut Frame, area: Rect, app: &mut App) {
        let preview_content = if let Some(selected) = app.bookmark_list_state.selected() {
            if let Some(bookmark) = app.bookmarks.get(selected) {
                let mut text = format!("Chapter: {}\n", bookmark.chapter_title);
                text.push_str(&format!("Position: {}\n\n", bookmark.scroll_position));
                text.push_str(&bookmark.text_preview);
                Text::from(text)
            } else {
                Text::from("No bookmark selected.")
            }
        } else {
            Text::from("No bookmark selected.")
        };

        let border_type = get_border_type(&app.config);

        let preview_paragraph = Paragraph::new(preview_content)
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .border_type(border_type)
                    .title(" Preview "),
            )
            .style(Style::default().fg(Color::White))
            .wrap(ratatui::widgets::Wrap { trim: true });

        f.render_widget(preview_paragraph, area);
    }

    fn render_help(&self, f: &mut Frame, area: Rect, app: &mut App) {
        let border_type = get_border_type(&app.config);
        let help_text = Text::from("Enter: Jump | d: Delete | r: Rename | Esc/b: Back");
        let help_paragraph = Paragraph::new(help_text)
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .border_type(border_type),
            )
            .style(Style::default().fg(Color::Gray));
        f.render_widget(help_paragraph, area);
    }
}