cursive-extras 0.13.4

Extra views for the Cursive TUI library as well some helper functions and macros
Documentation
use crossbeam_channel::{bounded, Receiver};
use cursive_core::{
    Printer, Vec2,
    view::View,
    theme::{
        ColorStyle,
        Color
    }
};
use image::{
    ImageReader,
    error::ImageError,
    imageops::FilterType
};
use itertools::Itertools;
use rust_utils::{chainable, encapsulated};
use std::{
    path::PathBuf,
    thread::{self, JoinHandle}
};

type Image = Vec<Vec<(Color, Color)>>;
type ImageResult = Result<Image, ImageError>;
type ImageRenderThread = JoinHandle<ImageResult>;


//TODO: static image view
// a view that uses a procedural macro that will generate an image
// to display at compile time
/// View that can render a low res image as text
#[encapsulated]
pub struct ImageView {
    data: Option<Image>,
    renderer: Option<ImageRenderThread>,
    ready_rcv: Option<Receiver<()>>,
    size: Vec2,

    #[getter(doc = "Gets the path of the image")]
    path: PathBuf,

    #[setter(doc = "Minimize the view when empty?")]
    #[chainable]
    minimize: bool
}

impl ImageView {
    /// Create a new empty `ImageView`
    pub fn new<S: Into<Vec2>>(size: S) -> ImageView {
        ImageView {
            data: None,
            renderer: None,
            ready_rcv: None,
            path: PathBuf::new(),
            minimize: false,
            size: size.into()
        }
    }

    /// Set the image to render
    #[chainable]
    pub fn set_image<P: Into<PathBuf>>(&mut self, path: P) {
        self.path = path.into();
        self.data = None;
        self.renderer = None;
        self.ready_rcv = None;
    }

    /// Set the rendering size of the image
    ///
    /// Size is measured in terminal cells
    pub fn set_size<S: Into<Vec2>>(&mut self, size: S) {
        self.size = size.into();
        self.data = None;
        self.renderer = None;
        self.ready_rcv = None;
    }

    fn render_image_thread(&self) -> (Receiver<()>, ImageRenderThread) {
        let width = self.size.x as u32;
        let img_height = self.size.y * 2;
        let img_reader = ImageReader::open(&self.path);
        let (snd, rcv) = bounded::<()>(1);
        
        (
            rcv,
            thread::spawn(move || {
                let img = img_reader?.with_guessed_format()?.decode()?;
                let mut img_colors: Vec<Vec<(Color, Color)>> = Vec::new();
                let img_buffer = img
                    .adjust_contrast(50.)
                    .resize_exact(width, img_height as u32, FilterType::Triangle)
                    .to_rgb8();

                for (cur_row, next_row) in img_buffer.rows().tuples() {
                    let mut colors: Vec<(Color, Color)> = Vec::new();
                    for (top_pxl, bottom_pxl) in cur_row.zip(next_row) {
                        colors.push((
                            Color::Rgb(bottom_pxl.0[0], bottom_pxl.0[1], bottom_pxl.0[2]),
                            Color::Rgb(top_pxl.0[0], top_pxl.0[1], top_pxl.0[2])
                        ));
                    }

                    img_colors.push(colors);
                }

                snd.send(()).unwrap_or_default();
                Ok(img_colors)
            })
        )
    }
}

impl View for ImageView {
    fn draw(&self, printer: &Printer) {
        if let Some(ref image_data) = self.data {
            for (y, row) in image_data.iter().enumerate() {
                for (x, style) in row.iter().enumerate() {
                    printer.with_color(ColorStyle::from(*style), |printer| printer.print((x, y), ""))
                }
            }
        }
    }

    fn required_size(&mut self, _: Vec2) -> Vec2 {
        if self.minimize && self.data.is_none() && self.path.parent().is_none() {
            Vec2::new(0, 0)
        }
        else {
            self.size
        }
    }

    fn layout(&mut self, _: Vec2) {
        if self.data.is_none() && self.renderer.is_none() {
            let (rcv, renderer) = self.render_image_thread();
            self.ready_rcv = Some(rcv);
            self.renderer = Some(renderer);
        }
        else if self.data.is_none() {
            if let Some(ready_rcv) = self.ready_rcv.as_ref() {
                if ready_rcv.is_full() {
                    if let Some(renderer) = self.renderer.take() {
                        if let Ok(data) = renderer.join().unwrap() {
                            self.data = Some(data);
                        }

                        self.ready_rcv = None;
                    }
                }
            }
        }
    }
}