icmd 0.1.0

A command-line software framework
Documentation
//! Module for canvas drawing operations.

use std::vec;

use crate::{
    core::renderer::{draw_onto, FrameBuffer},
    node::Node,
    style::{color, GeneralBorderPattern, GeneralStyle, NoStyle, TString},
};

/// The Canvas struct represents a drawing surface.
#[derive(Debug)]
pub struct Canvas {
    pub buffer: FrameBuffer,
}

impl Canvas {
    /// Creates a new Canvas using node measurements and applies a default black background.
    pub fn new(node: &dyn Node) -> Self {
        Canvas {
            buffer: FrameBuffer::from_node_measure(vec![], node),
        }
        .fill_style(color::AsBackground(color::Black))
    }

    /// Creates a new Canvas template pre-filled with spaces.
    pub fn new_template(node: &dyn Node) -> Self {
        Canvas {
            buffer: FrameBuffer::from_node_measure(
                vec![TString::from_str(&n_char(' ', 600), NoStyle); 600],
                node,
            ),
        }
    }

    /// Creates a new Canvas with a specified style.
    pub fn new_with_style(node: &dyn Node, color: impl Into<GeneralStyle>) -> Self {
        Self::new(node).fill_style(color)
    }

    /// Creates a new Canvas and fills it with a character using the provided style.
    pub fn new_and_fill(node: &dyn Node, c: char, style: impl Into<GeneralStyle>) -> Self {
        Self::new(node).fill(c, style)
    }

    /// Returns a clone of the current FrameBuffer.
    pub fn finish(&self) -> FrameBuffer {
        self.buffer.clone()
    }

    /// Fills the Canvas with a repeated character using the given style.
    pub fn fill(mut self, c: char, style: impl Into<GeneralStyle>) -> Self {
        let width = self.buffer.right - self.buffer.left;
        let height = self.buffer.bottom - self.buffer.top;
        self.buffer.data =
            vec![TString::from_str(&n_char(c, width as usize), style); height as usize];
        self
    }

    /// Fills the Canvas with spaces using the provided style.
    pub fn fill_style(self, color: impl Into<GeneralStyle>) -> Self {
        self.fill(' ', color)
    }

    /// Draws another Canvas buffer onto the current Canvas.
    pub fn draw_buffer(mut self, buffer: Canvas) -> Self {
        draw_onto(&mut self.buffer, vec![buffer.buffer]);
        self
    }

    /// Draws multiple Canvas buffers onto the current Canvas.
    pub fn draw_buffers(mut self, buffers: Vec<Canvas>) -> Self {
        let mut buffers_ = vec![];
        for buffer in buffers {
            buffers_.push(buffer.buffer);
        }
        draw_onto(&mut self.buffer, buffers_);
        self
    }

    /// Draws a rectangle at the specified position and size.
    pub fn draw_rect(
        mut self,
        pos: (i32, i32),
        size: (i32, i32),
        c: char,
        style: impl Into<GeneralStyle>,
    ) -> Self {
        let (x, y) = pos;
        let (w, h) = size;
        if w == 0 || h == 0 {
            return self;
        }
        let style: GeneralStyle = style.into();
        let data = vec![
            TString::from_str(
                vec![c; w as usize].into_iter().collect::<String>().as_str(),
                style
            );
            h as usize
        ];
        let result = FrameBuffer::new(
            data,
            self.buffer.left + x,
            self.buffer.top + y,
            self.buffer.left + x + w,
            self.buffer.top + y + h,
        );
        draw_onto(&mut self.buffer, vec![result]);
        self
    }

    /// Draws text on the Canvas at the given (x,y) coordinates.
    pub fn draw_text(mut self, text: &str, style: impl Into<GeneralStyle>, x: i32, y: i32) -> Self {
        let text = text.to_string();
        let style: GeneralStyle = style.into();
        let ttext: Vec<_> = text
            .split('\n')
            .map(|line| TString::from_str(line, style.clone()))
            .collect();
        let result = FrameBuffer::new(
            ttext,
            self.buffer.left + x,
            self.buffer.top + y,
            self.buffer.left + x + text.len() as i32,
            self.buffer.top + y + 1,
        );
        draw_onto(&mut self.buffer, vec![result]);
        self
    }

    /// Draws a border around the Canvas using the specified border pattern and style.
    pub fn draw_border(
        self,
        width: i32,
        height: i32,
        border_pattern: impl Into<GeneralBorderPattern>,
        border_style: impl Into<GeneralStyle>,
    ) -> Self {
        let style = border_style.into();
        let pattern = border_pattern.into();
        self.draw_rect((0, 0), (1, 1), pattern.top_right, style.clone())
            .draw_rect((width - 1, 0), (1, 1), pattern.top_left, style.clone())
            .draw_rect((0, height - 1), (1, 1), pattern.bottom_right, style.clone())
            .draw_rect(
                (width - 1, height - 1),
                (1, 1),
                pattern.bottom_left,
                style.clone(),
            )
            .draw_rect((1, 0), (width - 2, 1), pattern.top, style.clone())
            .draw_rect(
                (1, height - 1),
                (width - 2, 1),
                pattern.bottom,
                style.clone(),
            )
            .draw_rect((0, 1), (1, height - 2), pattern.left, style.clone())
            .draw_rect(
                (width - 1, 1),
                (1, height - 2),
                pattern.right,
                style.clone(),
            )
    }
}

/// Returns a String with the character `c` repeated `n` times.
fn n_char(c: char, n: usize) -> String {
    let mut result = String::new();
    for _ in 0..n {
        result.push(c);
    }
    result
}