direct-gui 0.1.20

Simple direct rendering of GUI controls on a buffer
Documentation
use blit::{Color, BlitBuffer, BlitExt};
use image;
use std::fmt;
use std::path::Path;
use std::error::Error;

use font::*;

/// An error type for when a image has the wrong extension.
#[derive(Debug, Clone)]
pub struct InvalidImageFormat;

impl fmt::Display for InvalidImageFormat {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "file format doesn't match '.png' or '.blit'")
    }
}

impl Error for InvalidImageFormat {
    fn description(&self) -> &str {
        "file format doesn't match '.png' or '.blit'"
    }

    fn cause(&self) -> Option<&Error> {
        None
    }
}

/// A newtype for handling sprites objects externally by reference.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct SpriteRef(usize);

/// A newtype for handling font objects externally by reference.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct FontRef(usize);

/// A internal handler of static resources such as sprites and fonts.
#[derive(Debug)]
pub struct Resources {
    sprites: Vec<BlitBuffer>,
    fonts: Vec<Font>
}

impl Resources {
    pub fn new() -> Self {
        let mut fonts = Vec::new();

        // Load the default font
        let default_font_buffer = BlitBuffer::from_memory(include_bytes!("../resources/ArtosSans.png.blit")).unwrap();

        let default_font_settings = FontSettings {
            start: '!',
            char_size: (9, 9),
            leading_offset: 2,
            mask_color: Color::from_u32(0xFF00FF)
        };
        fonts.push(Font::new(default_font_buffer, default_font_settings));

        Resources {
            fonts,
            sprites: Vec::new()
        }
    }

    /// Return the default font loaded from the `assets/` folder and parsed by `build.rs`.
    pub fn default_font(&self) -> FontRef {
        FontRef(0)
    }

    /// Load image from a path. Accepts both PNG & BlitBuffer images which should have the `.png`
    /// and `.blit` extension respectively.
    ///
    /// The mask color is the color that will be used as alpha in the sprite, a common color to use
    /// for this is `0xFF00FF`.
    ///
    /// Returns a reference to the image.
    pub fn load_sprite_from_file<P>(&mut self, path: P, mask_color: Color) -> Result<SpriteRef, Box<Error>> where P: AsRef<Path> {
        let index = self.sprites.len();

        let buffer = Resources::load_blitbuffer(path.as_ref(), mask_color)?;
        self.sprites.push(buffer);

        Ok(SpriteRef(index))
    }

    /// Load image from serialized memory.
    pub fn load_sprite_from_memory(&mut self, buffer: &[u8]) -> Result<SpriteRef, Box<Error>> {
        let index = self.sprites.len();

        let blitbuffer = BlitBuffer::from_memory(buffer)?;
        self.sprites.push(blitbuffer);

        Ok(SpriteRef(index))
    }

    /// Retrieves the sprite if it exists.
    pub fn get_sprite(&self, sprite_ref: SpriteRef) -> Option<&BlitBuffer> {
        if sprite_ref.0 < self.sprites.len() {
            Some(&self.sprites[sprite_ref.0])
        } else {
            None
        }
    }

    /// Load font image from a path. Accepts both PNG & BlitBuffer images which should have the `.png`
    /// and `.blit` extension respectively.
    ///
    /// Returns a reference to the font.
    pub fn load_font_sprite_from_file<P>(&mut self, path: P, settings: FontSettings) -> Result<FontRef, Box<Error>> where P: AsRef<Path> {
        let index = self.fonts.len();

        let buffer = Resources::load_blitbuffer(path.as_ref(), settings.mask_color)?;
        self.fonts.push(Font::new(buffer, settings));

        Ok(FontRef(index))
    }

    /// Load image from serialized memory.
    pub fn load_font_sprite_from_memory(&mut self, buffer: &[u8], settings: FontSettings) -> Result<FontRef, Box<Error>> {
        let index = self.sprites.len();

        let blitbuffer = BlitBuffer::from_memory(buffer)?;
        self.fonts.push(Font::new(blitbuffer, settings));

        Ok(FontRef(index))
    }

    /// Retrieves the font if it exists.
    pub fn get_font(&self, font_ref: FontRef) -> Option<&Font> {
        if font_ref.0 < self.fonts.len() {
            Some(&self.fonts[font_ref.0])
        } else {
            None
        }
    }

    pub fn load_blitbuffer(path: &Path, mask_color: Color) -> Result<BlitBuffer, Box<Error>> {
        let ext = path.extension().and_then(|s| s.to_str()).map_or("".to_string(), |s| s.to_ascii_lowercase());

        let buffer = match &ext[..] {
            "blit" => {
                BlitBuffer::open(path)?
            },
            "png" => {
                // Open the image from the path and convert it to a blit buffer
                let img = image::open(path)?;
                let rgb = img.as_rgb8().expect("Image is not of a valid type, consider removing the alpha channel");

                rgb.to_blit_buffer(mask_color)
            },
            _ => return Err(Box::new(InvalidImageFormat))
        };

        Ok(buffer)
    }
}