pixel_grid 0.5.0

Library for drawing with a grid of rectangles
Documentation
#[macro_use]
extern crate gfx;
extern crate coord_2d;
extern crate gfx_device_gl;
extern crate gfx_window_glutin;
extern crate glutin;
extern crate grid_2d;

pub use coord_2d::{Coord, Size};
use gfx::traits::FactoryExt;
use gfx::Device;
use gfx::Factory;
use grid_2d::coord_system::{CoordSystem, XThenY, XThenYIter};
use std::iter;
use std::slice;

type ColourFormat = gfx::format::Srgba8;
type DepthFormat = gfx::format::DepthStencil;

const QUAD_INDICES: [u16; 6] = [0, 1, 2, 2, 3, 0];
const QUAD_COORDS: [[f32; 2]; 4] = [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]];

type Colour = [f32; 4];

mod graphics {
    use super::*;

    gfx_vertex_struct!(QuadCorners {
        corner_zero_to_one: [f32; 2] = "a_CornerZeroToOne",
    });

    gfx_vertex_struct!(Cell {
        coord: [f32; 2] = "a_Coord",
        colour: Colour = "a_Colour",
    });

    gfx_constant_struct!(Properties {
        window_size_in_pixels: [f32; 2] = "u_WindowSizeInPixels",
        cell_size_in_pixels: [f32; 2] = "u_CellSizeInPixels",
    });

    gfx_pipeline!(pipe {
        quad_corners: gfx::VertexBuffer<QuadCorners> = (),
        cells: gfx::InstanceBuffer<Cell> = (),
        properties: gfx::ConstantBuffer<Properties> = "Properties",
        out_colour: gfx::BlendTarget<ColourFormat> =
            ("Target", gfx::state::ColorMask::all(), gfx::preset::blend::ALPHA),
    });
}

use graphics::*;

pub struct Window {
    encoder: gfx::Encoder<gfx_device_gl::Resources, gfx_device_gl::CommandBuffer>,
    device: gfx_device_gl::Device,
    factory: gfx_device_gl::Factory,
    window: glutin::GlWindow,
    events_loop: glutin::EventsLoop,
    bundle:
        gfx::pso::bundle::Bundle<gfx_device_gl::Resources, pipe::Data<gfx_device_gl::Resources>>,
    cell_upload: gfx::handle::Buffer<gfx_device_gl::Resources, Cell>,
    num_cells: usize,
    coord_system: XThenY,
    closed: bool,
}

pub struct PixelGrid<'a> {
    coord_system: XThenY,
    writer: gfx::mapping::Writer<'a, gfx_device_gl::Resources, Cell>,
}

pub struct Pixel<'a> {
    cell: &'a mut Cell,
}

impl<'a> Pixel<'a> {
    pub fn set_colour_array_rgba_f32(&mut self, colour: Colour) {
        self.cell.colour = colour;
    }
    pub fn set_colour_array_rgba_u8(&mut self, [r, g, b, a]: [u8; 4]) {
        self.set_colour_array_rgba_f32([
            r as f32 / 255.,
            g as f32 / 255.,
            b as f32 / 255.,
            a as f32 / 255.,
        ]);
    }
    pub fn set_colour_array_rgb_f32(&mut self, [r, g, b]: [f32; 3]) {
        self.set_colour_array_rgba_f32([r, g, b, 1.]);
    }
    pub fn set_colour_array_rgb_u8(&mut self, [r, g, b]: [u8; 3]) {
        self.set_colour_array_rgba_u8([r, g, b, 255]);
    }
}

pub struct WindowSpec {
    pub title: String,
    pub grid_size: Size,
    pub cell_size: Size,
}

impl WindowSpec {
    pub fn new(title: String, grid_size: Size, cell_size: Size) -> Self {
        Self {
            title,
            grid_size,
            cell_size,
        }
    }
}

impl Window {
    pub fn new(
        WindowSpec {
            title,
            grid_size,
            cell_size,
        }: WindowSpec,
    ) -> Self {
        let events_loop = glutin::EventsLoop::new();
        let size_in_pixels =
            Size::new(grid_size.x() * cell_size.x(), grid_size.y() * cell_size.y());
        let glutin_size =
            glutin::dpi::LogicalSize::new(size_in_pixels.x() as f64, size_in_pixels.y() as f64);
        let builder = glutin::WindowBuilder::new()
            .with_title(title)
            .with_resizable(false)
            .with_dimensions(glutin_size)
            .with_max_dimensions(glutin_size)
            .with_min_dimensions(glutin_size);
        let context = glutin::ContextBuilder::new().with_vsync(true);
        let (window, device, mut factory, rtv, _dsv) =
            gfx_window_glutin::init::<ColourFormat, DepthFormat>(builder, context, &events_loop)
                .expect("Failed to create window");
        let (width, height): (u32, u32) = window.get_outer_size().unwrap().into();
        let mut encoder: gfx::Encoder<gfx_device_gl::Resources, gfx_device_gl::CommandBuffer> =
            factory.create_command_buffer().into();
        let pso = factory
            .create_pipeline_simple(
                r#"
                #version 150 core
                uniform Properties {
                    vec2 u_WindowSizeInPixels;
                    vec2 u_CellSizeInPixels;
                };
                in vec2 a_CornerZeroToOne;
                in vec2 a_Coord;
                in vec4 a_Colour;
                flat out vec4 v_Colour;
                void main() {
                    v_Colour = a_Colour;
                    vec2 top_left_corner_in_pixels = (a_Coord + a_CornerZeroToOne) * u_CellSizeInPixels;
                    float x = ((top_left_corner_in_pixels.x / u_WindowSizeInPixels.x) * 2) - 1;
                    float y = 1 - ((top_left_corner_in_pixels.y / u_WindowSizeInPixels.y) * 2);
                    gl_Position = vec4(x, y, 0, 1);
                }
                "#.as_bytes(),
                r#"
                #version 150 core
                out vec4 Target;
                flat in vec4 v_Colour;
                void main() {
                    Target = v_Colour;
                }
                "#.as_bytes(),
                pipe::new(),
            )
            .expect("Failed to create pipeline");
        let quad_corners_data = QUAD_COORDS
            .iter()
            .map(|v| QuadCorners {
                corner_zero_to_one: *v,
            })
            .collect::<Vec<_>>();
        let (quad_corners_buffer, mut slice) =
            factory.create_vertex_buffer_with_slice(&quad_corners_data, &QUAD_INDICES[..]);
        let num_cells = grid_size.count();
        slice.instances = Some((num_cells as u32, 0));
        let cell_buffer: gfx::handle::Buffer<gfx_device_gl::Resources, Cell> = factory
            .create_buffer(
                num_cells,
                gfx::buffer::Role::Vertex,
                gfx::memory::Usage::Data,
                gfx::memory::Bind::TRANSFER_DST,
            )
            .expect("Failed to create instance buffer");
        let cell_upload: gfx::handle::Buffer<gfx_device_gl::Resources, Cell> = factory
            .create_upload_buffer(num_cells)
            .expect("Failed to create instance upload buffer");
        for (coord, cell) in XThenYIter::from(grid_size).zip(
            factory
                .write_mapping(&cell_upload)
                .expect("Failed to map instance upload buffer")
                .iter_mut(),
        ) {
            cell.coord = [coord.x as f32, coord.y as f32];
            cell.colour = [0., 0., 0., 0.];
        }
        let properties_buffer: gfx::handle::Buffer<gfx_device_gl::Resources, Properties> =
            factory.create_constant_buffer(1);
        let data = pipe::Data {
            quad_corners: quad_corners_buffer,
            cells: cell_buffer,
            properties: properties_buffer,
            out_colour: rtv,
        };
        encoder.update_constant_buffer(
            &data.properties,
            &Properties {
                window_size_in_pixels: [width as f32, height as f32],
                cell_size_in_pixels: [cell_size.x() as f32, cell_size.y() as f32],
            },
        );
        let bundle = gfx::pso::bundle::Bundle::new(slice, pso, data);
        Self {
            encoder,
            device,
            factory,
            window,
            events_loop,
            bundle,
            cell_upload,
            num_cells,
            coord_system: XThenY::from(grid_size),
            closed: false,
        }
    }
    pub fn draw(&mut self) {
        let mut closed = false;
        self.events_loop.poll_events(|event| match event {
            glutin::Event::WindowEvent { event, .. } => match event {
                glutin::WindowEvent::CloseRequested => {
                    closed = true;
                }
                _ => (),
            },
            _ => (),
        });
        self.closed = self.closed || closed;
        if self.closed {
            return;
        }
        self.encoder
            .clear(&self.bundle.data.out_colour, [0., 0., 0., 0.]);
        self.encoder
            .copy_buffer(
                &self.cell_upload,
                &self.bundle.data.cells,
                0,
                0,
                self.num_cells,
            )
            .expect("Failed to copy cells");
        self.bundle.encode(&mut self.encoder);
        self.encoder.flush(&mut self.device);
        self.window.swap_buffers().unwrap();
        self.device.cleanup();
    }
    pub fn is_closed(&self) -> bool {
        self.closed
    }
    pub fn pixel_grid(&mut self) -> PixelGrid {
        let writer = self
            .factory
            .write_mapping(&self.cell_upload)
            .expect("Failed to map instance upload buffer");
        PixelGrid {
            writer,
            coord_system: self.coord_system.clone(),
        }
    }
    pub fn with_pixel_grid<F: FnMut(PixelGrid)>(&mut self, mut f: F) {
        f(self.pixel_grid())
    }
}

pub struct IterMut<'a> {
    iter: slice::IterMut<'a, Cell>,
}

impl<'a> Iterator for IterMut<'a> {
    type Item = Pixel<'a>;
    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|cell| Pixel { cell })
    }
}

pub type EnumerateMut<'a> = iter::Zip<XThenYIter, IterMut<'a>>;

pub type CoordIter = XThenYIter;

impl<'a> PixelGrid<'a> {
    pub fn size(&self) -> Size {
        self.coord_system.size()
    }
    pub fn width(&self) -> u32 {
        self.size().width()
    }
    pub fn height(&self) -> u32 {
        self.size().height()
    }
    pub fn len(&self) -> usize {
        self.size().count()
    }
    pub fn iter_mut(&mut self) -> IterMut {
        IterMut {
            iter: self.writer.iter_mut(),
        }
    }
    pub fn coord_iter(&self) -> CoordIter {
        self.coord_system.coord_iter()
    }
    pub fn enumerate_mut(&mut self) -> EnumerateMut {
        self.coord_iter().zip(self.iter_mut())
    }
    pub fn get_mut(&mut self, coord: Coord) -> Option<Pixel> {
        self.coord_system
            .index_of_coord(coord)
            .map(move |index| Pixel {
                cell: &mut self.writer[index],
            })
    }
    pub fn get_checked_mut(&mut self, coord: Coord) -> Pixel {
        let index = self.coord_system.index_of_coord_checked(coord);
        Pixel {
            cell: &mut self.writer[index],
        }
    }
    pub fn index_of_coord(&self, coord: Coord) -> Option<usize> {
        self.coord_system.index_of_coord(coord)
    }
    pub fn get_index_mut(&mut self, index: usize) -> Pixel {
        Pixel {
            cell: &mut self.writer[index],
        }
    }
}