cursive-image 0.0.6

Image view for the Cursive TUI library
Documentation
use super::{super::kitty::*, image::*, source::*};

use {
    crossterm::{ExecutableCommand, cursor::*},
    cursive::*,
    std::{io, sync::atomic::*},
};

static NEXT_ID: AtomicUsize = AtomicUsize::new(1);

impl Drop for Image {
    fn drop(&mut self) {
        if let Some(id) = self.get_id() {
            _ = release_image(id);
        }
    }
}

//
// KittyImage
//

/// Kitty image.
pub trait KittyImage {
    /// Register with terminal (if not already registered).
    ///
    /// Returns the ID.
    fn register(&self) -> io::Result<usize>;

    /// Release from terminal (if registered).
    fn release(&self) -> io::Result<()>;

    /// Add a placement. Will also remove the existing placement (if there is one).
    ///
    /// Position and size are in cell coordinates. If size is set will stretch the image to fit.
    ///
    /// If window is set will display only that part of the image. The window dimensions are in
    /// pixels.
    fn show<PositionT, SizeT>(&self, position: PositionT, size: Option<SizeT>, window: Option<Rect>) -> io::Result<()>
    where
        PositionT: Into<Vec2>,
        SizeT: Into<Vec2>;

    /// Remove placement (if there is one).
    fn hide(&self) -> io::Result<()>;
}

impl KittyImage for Image {
    fn register(&self) -> io::Result<usize> {
        Ok(match self.get_id() {
            Some(id) => id,

            None => {
                let id = new_id();
                self.set_id(Some(id));

                let mut command = Command::default().with('f', self.format).with('i', id);

                if self.format.is_raw() {
                    command.add('s', self.size.x);
                    command.add('v', self.size.y);
                }

                match &self.source {
                    ImageSource::Owned(data, compressed) => {
                        if *compressed {
                            command.add('o', 'z');
                        }
                        command.execute_with_payload(data.as_slice())?;
                    }

                    ImageSource::LocalFile(path, compressed) => {
                        if *compressed {
                            command.add('o', 'z');
                        }
                        command.add('t', 'f');
                        command.execute_with_path_payload(path)?;
                    }

                    ImageSource::Stream(stream) => {
                        let (reader, compressed) = stream.open()?;
                        if compressed {
                            command.add('o', 'z');
                        }
                        command.execute_with_payload_from(reader)?;
                    }
                };

                id
            }
        })
    }

    fn release(&self) -> io::Result<()> {
        self.set_placement(None);
        if let Some(id) = self.get_id() {
            release_image(id)?;
            self.set_id(None);
        }
        Ok(())
    }

    fn show<PositionT, SizeT>(&self, position: PositionT, size: Option<SizeT>, window: Option<Rect>) -> io::Result<()>
    where
        PositionT: Into<Vec2>,
        SizeT: Into<Vec2>,
    {
        let id = self.register()?;
        let old_placement = self.get_placement();

        let placement = new_id();
        self.set_placement(Some(placement));

        _ = move_cursor(position.into());

        // Place
        let mut command = Command::default().with('a', 'p').with('i', id).with('p', placement).with('q', 1);

        if let Some(size) = size {
            let size = size.into();
            command.add('c', size.x);
            command.add('r', size.y);
        }

        if let Some(window) = window {
            command.add('x', window.left());
            command.add('y', window.top());
            command.add('w', window.width());
            command.add('h', window.height());
        }

        command.execute()?;

        // Remove old placement
        // (We do this *after* creating the new placement in order to avoid flickering, especially when scrolling)
        if let Some(old_placement) = old_placement {
            delete_placement(id, old_placement)?;
        }

        Ok(())
    }

    fn hide(&self) -> io::Result<()> {
        if let Some(placement) = self.get_placement()
            && let Some(id) = self.get_id()
        {
            delete_placement(id, placement)?;
            self.set_placement(None);
        }
        Ok(())
    }
}

fn new_id() -> usize {
    NEXT_ID.fetch_add(1, Ordering::SeqCst)
}

fn move_cursor(position: Vec2) -> io::Result<()> {
    io::stdout().execute(MoveTo(position.x as u16, position.y as u16))?;
    Ok(())
}

fn release_image(id: usize) -> io::Result<()> {
    Command::default().with('a', 'd').with('d', 'I').with('i', id).execute()
}

fn delete_placement(id: usize, placement: usize) -> io::Result<()> {
    Command::default().with('a', 'd').with('d', 'i').with('i', id).with('p', placement).execute()
}