tuigui 0.23.0

An easy-to-use, highly extensible, and speedy TUI library.
Documentation
pub use crossterm;

use std::io::{ self, Write };
use crossterm::{ QueueableCommand, terminal, cursor, event };

use crate::preludes::backend_creation::*;

/// Helper function for polling terminal events
///
/// This function accepts a closure for handling
/// key presses and also passes in the mutable
/// reference to the event state
pub fn crossterm_poll_events(
    timeout: std::time::Duration,
    event_state: &mut crate::event::EventState,
    mut key_function: impl FnMut(
        crossterm::event::KeyEvent,
        &mut crate::event::EventState,
    ) -> Result<(), io::Error>,
) -> Result<(), io::Error> {
    if crossterm::event::poll(timeout)? {
        match crossterm::event::read()? {
            crossterm::event::Event::Mouse(mouse_event) => {
                match mouse_event.kind {
                    crossterm::event::MouseEventKind::Up(button) => {
                        match button {
                            crossterm::event::MouseButton::Left => event_state.mouse.buttons.left = false,
                            crossterm::event::MouseButton::Middle => event_state.mouse.buttons.middle = false,
                            crossterm::event::MouseButton::Right => event_state.mouse.buttons.right = false,
                        };
                    },
                    crossterm::event::MouseEventKind::Down(button) => {
                        match button {
                            crossterm::event::MouseButton::Left => event_state.mouse.buttons.left = true,
                            crossterm::event::MouseButton::Middle => event_state.mouse.buttons.middle = true,
                            crossterm::event::MouseButton::Right => event_state.mouse.buttons.right = true,
                        };
                    },
                    crossterm::event::MouseEventKind::ScrollUp => event_state.mouse.scroll.go_up(1),
                    crossterm::event::MouseEventKind::ScrollDown => event_state.mouse.scroll.go_down(1),
                    crossterm::event::MouseEventKind::ScrollLeft => event_state.mouse.scroll.go_left(1),
                    crossterm::event::MouseEventKind::ScrollRight => event_state.mouse.scroll.go_right(1),
                    _ => (),
                };

                event_state.mouse.position = Position::new(mouse_event.column as i16, mouse_event.row as i16);
            },
            crossterm::event::Event::Key(key_event) => key_function(key_event, event_state)?,
            crossterm::event::Event::FocusGained => event_state.terminal.focused = true,
            crossterm::event::Event::FocusLost => event_state.terminal.focused = false,
            crossterm::event::Event::Paste(paste) => event_state.terminal.paste = Some(std::rc::Rc::new(paste)),
            _ => (),
        };
    }

    Ok(())
}

/// Crossterm Backend
pub struct CrosstermBackend {
    pub stdout: io::Stdout,
}

impl CrosstermBackend {
    pub fn new() -> Self {
        Self {
            stdout: io::stdout(),
        }
    }
}

impl Backend<String> for CrosstermBackend {
    fn flush(&mut self) -> Result<(), io::Error> {
        self.stdout.flush()
    }

    fn terminal_size(&self) -> Result<Size, io::Error> {
        let raw = terminal::size()?;

        Ok(Size::new(raw.0, raw.1))
    }

    fn set_cursor_pos(&mut self, position: Position) -> Result<(), io::Error> {
        self.stdout.queue(cursor::MoveTo(position.col as u16, position.row as u16))?;

        Ok(())
    }

    fn alt_screen(&mut self, enable: bool) -> Result<(), io::Error> {
        match enable {
            true => self.stdout.queue(terminal::EnterAlternateScreen),
            false => self.stdout.queue(terminal::LeaveAlternateScreen),
        }?;

        Ok(())
    }

    fn raw_mode(&mut self, enable: bool) -> Result<(), io::Error> {
        match enable {
            true => terminal::enable_raw_mode()?,
            false => terminal::disable_raw_mode()?,
        };

        Ok(())
    }

    fn capture_mouse(&mut self, enable: bool) -> Result<(), io::Error> {
        match enable {
            true => self.stdout.queue(event::EnableMouseCapture),
            false => self.stdout.queue(event::DisableMouseCapture),
        }?;

        Ok(())
    }

    fn clear(&mut self, clear_type: ClearType) -> Result<(), io::Error> {
        macro_rules! queue_clear {
            ($clear_type: ident) => {
                self.stdout.queue(terminal::Clear(terminal::ClearType::$clear_type))
            };
        }

        match clear_type {
            ClearType::All => queue_clear!(All),
            ClearType::Purge => queue_clear!(Purge),
            ClearType::CurrentLine => queue_clear!(CurrentLine),
            ClearType::FromCursorDown => queue_clear!(FromCursorDown),
            ClearType::FromCursorUp => queue_clear!(FromCursorUp),
            ClearType::UntilNewLine => queue_clear!(UntilNewLine),
        }?;

        Ok(())
    }

    fn show_cursor(&mut self, enable: bool) -> Result<(), io::Error> {
        match enable {
            true => self.stdout.queue(cursor::Show)?,
            false => self.stdout.queue(cursor::Hide)?,
        };

        Ok(())
    }

    fn begin_sync_update(&mut self) -> Result<(), io::Error> {
        self.stdout.queue(terminal::BeginSynchronizedUpdate)?;

        Ok(())
    }

    fn end_sync_update(&mut self) -> Result<(), io::Error> {
        self.stdout.queue(terminal::EndSynchronizedUpdate)?;

        Ok(())
    }

    fn print(&mut self, content: String) -> Result<(), io::Error> {
        write!(self.stdout, "{}", content)?;

        Ok(())
    }

    fn cursor_position(&self) -> Result<Position, io::Error> {
        let raw = cursor::position()?;

        Ok(Position::new(raw.0 as i16, raw.1 as i16))
    }
}