tmaze 1.15.0

Simple multiplatform maze solving game for terminal written entirely in Rust
Documentation
use crate::core::*;
use crate::renderer::Frame;

use crossterm::style::ContentStyle;

pub use substring::Substring;

pub fn draw_box(frame: &mut Frame, pos: Dims, size: Dims, style: ContentStyle) {
    draw_str(
        frame,
        pos.0,
        pos.1,
        &format!("{}", "".repeat(size.0 as usize - 2)),
        style,
    );

    for y in pos.1 + 1..pos.1 + size.1 - 1 {
        draw_char(frame, pos.0, y, '', style);
        draw_char(frame, pos.0 + size.0 - 1, y, '', style);
    }

    draw_str(
        frame,
        pos.0,
        pos.1 + size.1 - 1,
        &format!("{}", "".repeat(size.0 as usize - 2)),
        style,
    );
}

pub fn draw_line(frame: &mut Frame, pos: Dims, vertical: bool, len: usize, style: ContentStyle) {
    if vertical {
        for y in 0..len {
            draw_char(frame, pos.0, pos.1 + y as i32, '', style);
        }
    } else {
        draw_str(frame, pos.0, pos.1, &"".repeat(len), style);
    }
}

pub fn draw_str(frame: &mut Frame, mut x: i32, y: i32, mut text: &str, style: ContentStyle) {
    if y < 0 {
        return;
    }

    if x < 0 && text.len() as i32 > -x + 1 {
        text = text.substring(-x as usize, text.len() - 1);
        x = 0;
    }

    if x > u16::MAX as i32 || y > u16::MAX as i32 {
        return;
    }

    frame.draw(Dims(x, y), (text, style));
}

pub fn draw_char(frame: &mut Frame, x: i32, y: i32, text: char, style: ContentStyle) {
    if y < 0 || x < 0 || x > u16::MAX as i32 || y > u16::MAX as i32 {
        return;
    }

    frame.draw(Dims(x, y), (text, style));
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rect {
    pub start: Dims,
    pub end: Dims,
}

impl Rect {
    pub fn new(start: Dims, end: Dims) -> Self {
        Self { start, end }
    }

    pub fn new_sized(start: Dims, size: Dims) -> Self {
        Self::new(start, Dims(start.0 + size.0, start.1 + size.1))
    }

    pub fn size(&self) -> Dims {
        Dims(self.end.0 - self.start.0, self.end.1 - self.start.1) + Dims(1, 1)
    }

    pub fn contains(&self, pos: Dims) -> bool {
        pos.0 >= self.start.0 && pos.0 <= self.end.0 && pos.1 >= self.start.1 && pos.1 <= self.end.1
    }

    pub fn trim_absolute<'a>(&'a self, text: &'a impl AsRef<str>, mut pos: Dims) -> (&str, Dims) {
        let mut text = text.as_ref();
        let size = self.size();

        if pos.1 < self.start.1 || pos.1 > self.end.1 {
            return ("", pos);
        }

        if pos.0 < self.start.0 {
            let offset = self.start.0 - pos.0;
            text = text.substring(offset as usize, text.chars().count());
            pos = Dims(self.start.0, pos.1);
        }

        if text.chars().count() as i32 + pos.0 > self.end.0 {
            let x = size.0 - (pos.0 - self.start.0);
            let x = x.max(0) as usize;
            text = text.substring(0, x);
        }

        (text, pos)
    }

    #[allow(dead_code)]
    pub fn trim_relative<'a>(&'a self, text: &'a impl AsRef<str>, pos: Dims) -> (&str, Dims) {
        let (text, pos) = self.trim_absolute(text, pos + self.start);
        (text, pos - self.start)
    }

    pub fn with_margin(&self, margin: Dims) -> Self {
        Self {
            start: self.start + margin,
            end: self.end - margin,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{Dims, Rect};

    #[test]
    fn frame_trim_absolute() {
        let frame = Rect::new_sized(Dims(0, 0), Dims(3, 1));
        let (text, ..) = frame.trim_absolute(&"123456", Dims(0, 0));
        assert_eq!(text, "123");

        let (text, ..) = frame.trim_absolute(&"123456", Dims(1, 0));
        assert_eq!(text, "12");

        let (text, ..) = frame.trim_absolute(&"123456", Dims(-1, 0));
        assert_eq!(text, "234");

        let (text, ..) = frame.trim_absolute(&"123456", Dims(-4, 0));
        assert_eq!(text, "56");

        let (text, ..) = frame.trim_absolute(&"123456", Dims(-3, 0));
        assert_eq!(text, "456");
    }
}