joshuto 0.9.6

Terminal file manager inspired by ranger
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::widgets::Widget;

use crate::context::WorkerContext;
use crate::io::{FileOperationProgress, IoWorkerObserver};
use crate::util::format;

pub struct TuiWorker<'a> {
    pub context: &'a WorkerContext,
}

impl<'a> TuiWorker<'a> {
    pub fn new(context: &'a WorkerContext) -> Self {
        Self { context }
    }
}

impl<'a> Widget for TuiWorker<'a> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        if area.height < 7 {
            return;
        }
        match self.context.worker_ref() {
            Some(io_obs) => {
                if let Some(progress) = io_obs.progress.as_ref() {
                    let current_area = Rect {
                        y: area.y + 1,
                        ..area
                    };
                    TuiCurrentWorker::new(io_obs, progress).render(current_area, buf);
                }

                // draw queued up work
                let style = Style::default()
                    .fg(Color::Yellow)
                    .add_modifier(Modifier::BOLD);
                buf.set_stringn(0, 6, "Queue:", area.width as usize, style);

                let queue_area = Rect {
                    y: area.y + 7,
                    ..area
                };
                TuiWorkerQueue::new(self.context).render(queue_area, buf);
            }
            _ => {
                let style = Style::default();
                buf.set_stringn(0, 2, "No operations running", area.width as usize, style);
                let style = Style::default()
                    .fg(Color::Yellow)
                    .add_modifier(Modifier::BOLD);
                buf.set_stringn(0, 4, "Queue:", area.width as usize, style);
            }
        }
    }
}

pub struct TuiCurrentWorker<'a> {
    pub observer: &'a IoWorkerObserver,
    pub progress: &'a FileOperationProgress,
}

impl<'a> TuiCurrentWorker<'a> {
    pub fn new(observer: &'a IoWorkerObserver, progress: &'a FileOperationProgress) -> Self {
        Self { observer, progress }
    }
}

impl<'a> Widget for TuiCurrentWorker<'a> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let top = area.top();
        let left = area.left();
        let right = area.right();

        let op_str = self.progress.kind().actioning_str();

        let processed_size = format::file_size_to_string(self.progress.bytes_processed());
        let total_size = format::file_size_to_string(self.progress.total_bytes());

        let msg = format!(
            "{} ({}/{}) ({}/{}) {:?}",
            op_str,
            self.progress.files_processed() + 1,
            self.progress.total_files(),
            processed_size,
            total_size,
            self.observer.dest_path(),
        );
        buf.set_stringn(left, top, msg, right as usize, Style::default());

        if let Some(file_name) = self
            .progress
            .current_file()
            .file_name()
            .map(|s| s.to_string_lossy())
        {
            buf.set_stringn(
                left,
                top + 1,
                format!("{}", file_name),
                right as usize,
                Style::default(),
            );
        }

        // draw a progress bar
        let progress_bar_width = (self.progress.files_processed() as f32
            / self.progress.total_files() as f32
            * area.width as f32) as usize;
        let progress_bar_space = " ".repeat(progress_bar_width);
        let progress_bar_style = Style::default().bg(Color::Blue);
        buf.set_stringn(
            left,
            top + 2,
            progress_bar_space,
            right as usize,
            progress_bar_style,
        );
    }
}

pub struct TuiWorkerQueue<'a> {
    pub context: &'a WorkerContext,
}

impl<'a> TuiWorkerQueue<'a> {
    pub fn new(context: &'a WorkerContext) -> Self {
        Self { context }
    }
}

impl<'a> Widget for TuiWorkerQueue<'a> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let top = area.top();
        let left = area.left();
        let right = area.right();
        let width = right - left;

        let style = Style::default();

        for (i, worker) in self.context.iter().enumerate() {
            let msg = format!(
                "{:02} {} {} items {:?}",
                i + 1,
                worker.kind(),
                worker.paths.len(),
                worker.dest
            );
            buf.set_stringn(left, (top as usize + i) as u16, msg, width as usize, style);
        }
    }
}