cursive-image 0.0.6

Image view for the Cursive TUI library
Documentation
use super::{image::*, sizing::*, state::*, vec2f::*};

use {
    crossterm::terminal::*,
    cursive::{align::*, *},
    std::{cell::*, sync::*},
};

//
// ImageView
//

/// Image view.
///
/// Works on terminals that support the
/// [Kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol.html)
///
/// The view supports shrink, fit, and scale sizing modes (shrink is the default), image alignment,
/// and scrolling.
pub struct ImageView {
    pub(crate) image: Option<Image>,
    pub(crate) sizing: Sizing,
    pub(crate) align: Align,

    pub(crate) size: Option<Vec2>,
    pub(crate) content_offset: Mutex<RefCell<Option<Vec2>>>,
    pub(crate) state: Mutex<RefCell<State>>,

    pub(crate) cell_size: XY<f64>,
    pub(crate) cell_aspect_ratio: f64,
}

impl ImageView {
    /// Image.
    pub fn image(&self) -> Option<&Image> {
        self.image.as_ref()
    }

    /// Set image.
    pub fn set_image(&mut self, image: Image) {
        // Note: the existing image will be dropped and thus deleted
        self.image = Some(image);
        self.reset_layout();
    }

    /// Take image.
    ///
    /// You muse manually [release](Image::release) or [hide](Image::hide) the image.
    pub fn take_image(&mut self) -> Option<Image> {
        self.reset_layout();
        self.image.take()
    }

    /// Set image.
    ///
    /// Chainable.
    pub fn with_image(self, image: Image) -> Self {
        self.with(|self_| self_.set_image(image))
    }

    /// Sizing.
    pub fn sizing(&self) -> Sizing {
        self.sizing
    }

    /// Set sizing.
    pub fn set_sizing(&mut self, sizing: Sizing) {
        if self.sizing != sizing {
            self.sizing = sizing;
            self.reset_layout();
        }
    }

    /// Set sizing.
    ///
    /// Chainable.
    pub fn with_sizing(self, sizing: Sizing) -> Self {
        self.with(|self_| self_.set_sizing(sizing))
    }

    /// Align.
    pub fn align(&self) -> Align {
        self.align
    }

    /// Set align.
    pub fn set_align(&mut self, align: Align) {
        if self.align != align {
            self.align = align;
            self.reset_layout();
        }
    }

    /// Set align.
    ///
    /// Chainable.
    pub fn with_align(self, align: Align) -> Self {
        self.with(|self_| self_.set_align(align))
    }

    /// Hide the image.
    pub fn hide(&self) {
        if let Some(image) = &self.image {
            _ = image.hide();
        }
    }

    // Unset size and content_offset.
    fn reset_layout(&mut self) {
        self.size = None;
        self.set_content_offset(None);
    }

    pub(crate) fn get_state(&self) -> State {
        *self.state.lock().unwrap().borrow()
    }

    pub(crate) fn set_state(&self, state: State) {
        *self.state.lock().unwrap().borrow_mut() = state;
    }

    pub(crate) fn get_content_offset(&self) -> Option<Vec2> {
        *self.content_offset.lock().unwrap().borrow()
    }

    pub(crate) fn set_content_offset(&self, content_offset: Option<Vec2>) {
        *self.content_offset.lock().unwrap().borrow_mut() = content_offset;
    }
}

impl Default for ImageView {
    fn default() -> Self {
        let cell_size = cell_size();
        Self {
            image: None,
            sizing: Default::default(),
            align: Align::center(),
            size: None,
            content_offset: Default::default(),
            state: Default::default(),
            cell_size,
            cell_aspect_ratio: cell_size.aspect_ratio(),
        }
    }
}

// Utils

// Cell size in pixels.
fn cell_size() -> Vec2f {
    match window_size() {
        Ok(size) if size.width != 0 && size.height != 0 && size.columns != 0 && size.rows != 0 => {
            (size.width as f64 / size.columns as f64, size.height as f64 / size.rows as f64)
        }

        _ => (8., 8.),
    }
    .into()
}