joshuto 0.9.6

Terminal file manager inspired by ranger
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Style};
use ratatui::text::Span;
use ratatui::widgets::{Block, Borders, Paragraph, Widget, Wrap};

use crate::context::AppContext;
use crate::ui::widgets::{TuiDirListDetailed, TuiFooter, TuiTopBar};

pub struct TuiHSplitView<'a> {
    pub context: &'a AppContext,
    pub show_bottom_status: bool,
}

impl<'a> TuiHSplitView<'a> {
    pub fn new(context: &'a AppContext) -> Self {
        Self {
            context,
            show_bottom_status: true,
        }
    }
}

impl<'a> Widget for TuiHSplitView<'a> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let tab_context = self.context.tab_context_ref();

        let config = self.context.config_ref();
        let display_options = config.display_options_ref();
        let constraints = &[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)];

        let layout_rect = if display_options.show_borders() {
            let area = Rect {
                y: area.top() + 1,
                height: area.height - 2,
                ..area
            };

            let layout = calculate_layout_with_borders(area, constraints);

            let block = Block::default().borders(Borders::ALL);
            let inner = block.inner(area);
            block.render(area, buf);

            let layout_rect = Layout::default()
                .direction(Direction::Horizontal)
                .constraints(constraints.as_ref())
                .split(inner);

            let block = Block::default().borders(Borders::RIGHT);
            block.render(layout_rect[0], buf);

            let block = Block::default().borders(Borders::LEFT);
            block.render(layout_rect[1], buf);

            layout
        } else {
            let area = Rect {
                y: area.top() + 1,
                height: area.height - 2,
                ..area
            };
            calculate_layout(area, constraints)
        };

        let tab_id = tab_context.curr_tab_id();
        let tab_index = tab_context.index;
        if let Some(curr_tab) = tab_context.tab_ref(&tab_id) {
            let curr_list = curr_tab.curr_list_ref();

            let layout_rect = if tab_index % 2 == 0 {
                layout_rect[0]
            } else {
                layout_rect[1]
            };

            // render current view
            if let Some(list) = curr_list.as_ref() {
                TuiDirListDetailed::new(list, display_options, curr_tab.option_ref(), true)
                    .render(layout_rect, buf);
                let rect = Rect {
                    x: 0,
                    y: area.height - 1,
                    width: area.width,
                    height: 1,
                };

                if self.show_bottom_status {
                    /* draw the bottom status bar */
                    if let Some(msg) = self.context.worker_context_ref().get_msg() {
                        let message_style = Style::default().fg(Color::Yellow);
                        let text = Span::styled(msg, message_style);
                        Paragraph::new(text)
                            .wrap(Wrap { trim: true })
                            .render(rect, buf);
                    } else if let Some(msg) = self.context.message_queue_ref().current_message() {
                        let text = Span::styled(msg.content.as_str(), msg.style);
                        Paragraph::new(text)
                            .wrap(Wrap { trim: true })
                            .render(rect, buf);
                    } else {
                        TuiFooter::new(list, curr_tab.option_ref()).render(rect, buf);
                    }
                }
            }

            let topbar_width = area.width;
            let rect = Rect {
                x: 0,
                y: 0,
                width: topbar_width,
                height: 1,
            };
            TuiTopBar::new(self.context).render(rect, buf);
        }

        let other_tab_index = if tab_index % 2 == 0 {
            tab_index + 1
        } else {
            tab_index - 1
        };

        if other_tab_index < tab_context.tab_order.len() {
            let other_tab_id = tab_context.tab_order[other_tab_index];
            if let Some(curr_tab) = tab_context.tab_ref(&other_tab_id) {
                let curr_list = curr_tab.curr_list_ref();

                let layout_rect = if other_tab_index % 2 == 0 {
                    layout_rect[0]
                } else {
                    layout_rect[1]
                };

                if let Some(list) = curr_list.as_ref() {
                    TuiDirListDetailed::new(list, display_options, curr_tab.option_ref(), false)
                        .render(layout_rect, buf);
                }
            }
        }
    }
}

fn calculate_layout(area: Rect, constraints: &[Constraint; 2]) -> Vec<Rect> {
    let mut layout_rect = Layout::default()
        .direction(Direction::Horizontal)
        .constraints(constraints.as_ref())
        .split(area)
        .to_vec();

    layout_rect[0] = Rect {
        width: layout_rect[0].width - 1,
        ..layout_rect[0]
    };
    layout_rect[1] = Rect {
        width: layout_rect[1].width - 1,
        ..layout_rect[1]
    };
    layout_rect
}

fn calculate_layout_with_borders(area: Rect, constraints: &[Constraint; 2]) -> Vec<Rect> {
    let block = Block::default().borders(Borders::ALL);
    let inner = block.inner(area);

    let layout_rect = Layout::default()
        .direction(Direction::Horizontal)
        .constraints(constraints.as_ref())
        .split(inner);

    let block = Block::default().borders(Borders::RIGHT);
    let inner1 = block.inner(layout_rect[0]);

    let block = Block::default().borders(Borders::LEFT);
    let inner2 = block.inner(layout_rect[1]);

    vec![inner1, inner2]
}