crow 0.7.2

A pixel perfect 2D rendering engine
Documentation
//! An example using a draw target modifiers: `Offset` and `Scaled`.
//!
//! This is also one of the more demanding targets,
//! it might be necessary to run this in `--release` mode.
#[macro_use]
extern crate log;

use std::{
    collections::VecDeque,
    thread,
    time::{Duration, Instant},
};

use crow::{
    glutin::{
        dpi::LogicalSize,
        event::{Event, WindowEvent},
        event_loop::{ControlFlow, EventLoop},
        window::WindowBuilder,
    },
    target::{Offset, Scaled},
    Context, DrawConfig, DrawTarget, Texture,
};

use rand::Rng;

const SCALE: u32 = 2;
const WINDOW_SIZE: (u32, u32) = (360, 240);

pub struct Rectangle {
    position: (i32, i32),
    size: (u32, u32),
    color: (f32, f32, f32),
}

fn main() -> Result<(), crow::Error> {
    pretty_env_logger::formatted_timed_builder()
        .filter_level(log::LevelFilter::max())
        .init();

    let event_loop = EventLoop::new();
    let mut ctx = Context::new(
        WindowBuilder::new()
            .with_inner_size(LogicalSize::new(
                WINDOW_SIZE.0 * SCALE,
                WINDOW_SIZE.1 * SCALE,
            ))
            .with_resizable(false),
        &event_loop,
    )?;

    let rectangle_vertical = Texture::load(&mut ctx, "textures/rectangle_vertical.png")?;
    let rectangle_horizontal = Texture::load(&mut ctx, "textures/rectangle_horizontal.png")?;

    let mut rng = rand::thread_rng();

    let mut rectangles = VecDeque::new();
    rectangles.push_back(Rectangle {
        position: (-10, -10),
        size: (WINDOW_SIZE.0 * 3 / 5 + 10, WINDOW_SIZE.1 / 3 + 10),
        color: rng.gen(),
    });
    rectangles.push_back(Rectangle {
        position: (WINDOW_SIZE.0 as i32 * 4 / 5, WINDOW_SIZE.1 as i32 / 2),
        size: (150, 100),
        color: rng.gen(),
    });

    let mut position = 0;
    let mut frames_to_next = 0;

    let mut fps = FrameRateLimiter::new(60);
    event_loop.run(
        move |event: Event<()>, _window_target: _, control_flow: &mut ControlFlow| match event {
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                ..
            } => *control_flow = ControlFlow::Exit,
            Event::MainEventsCleared => ctx.window().request_redraw(),
            Event::RedrawRequested(_) => {
                let mut surface = Scaled::new(ctx.surface(), (SCALE, SCALE));

                ctx.clear_color(&mut surface, (0.3, 0.3, 0.8, 1.0));

                if frames_to_next == 0 {
                    frames_to_next = rng.gen_range(50, 170);
                    rectangles.push_back(Rectangle {
                        position: (
                            position + WINDOW_SIZE.0 as i32,
                            rng.gen_range(-40, WINDOW_SIZE.1 as i32),
                        ),
                        size: (rng.gen_range(40, 200), rng.gen_range(40, 200)),
                        color: rng.gen(),
                    })
                } else {
                    frames_to_next -= 1;
                }

                position += 1;

                if let Some(first) = rectangles.front() {
                    if (first.position.0 + first.size.0 as i32) < position as i32 {
                        rectangles.pop_front();
                    }
                }

                draw_rectangles(
                    &rectangles,
                    &rectangle_vertical,
                    &rectangle_horizontal,
                    &mut Offset::new(&mut surface, (position, 0)),
                    &mut ctx,
                );
                ctx.present(surface.into_inner()).unwrap();
            }
            Event::RedrawEventsCleared => fps.frame(),
            _ => (),
        },
    )
}

fn mat((r, g, b): (f32, f32, f32)) -> [[f32; 4]; 4] {
    [
        [r, 0.0, 0.0, 0.0],
        [0.0, g, 0.0, 0.0],
        [0.0, 0.0, b, 0.0],
        [0.0, 0.0, 0.0, 1.0],
    ]
}

pub fn draw_rectangles(
    rectangles: &VecDeque<Rectangle>,
    vertical: &Texture,
    horizontal: &Texture,
    surface: &mut impl DrawTarget,
    ctx: &mut Context,
) {
    for rectangle in rectangles.iter() {
        let right_pos = rectangle.position.0 + rectangle.size.0 as i32 - vertical.width() as i32;
        let mut height = rectangle.size.1;

        while let Some(h) = height.checked_sub(vertical.height()) {
            height = h;

            ctx.draw(
                surface,
                vertical,
                (rectangle.position.0, height as i32 + rectangle.position.1),
                &DrawConfig {
                    color_modulation: mat(rectangle.color),
                    ..Default::default()
                },
            );

            ctx.draw(
                surface,
                vertical,
                (right_pos, height as i32 + rectangle.position.1),
                &DrawConfig {
                    color_modulation: mat(rectangle.color),
                    flip_horizontally: true,
                    ..Default::default()
                },
            );
        }

        let vertical_section =
            vertical.get_section((0, vertical.height() - height), (vertical.width(), height));
        ctx.draw(
            surface,
            &vertical_section,
            (rectangle.position.0, rectangle.position.1),
            &DrawConfig {
                color_modulation: mat(rectangle.color),
                ..Default::default()
            },
        );

        ctx.draw(
            surface,
            &vertical_section,
            (right_pos, rectangle.position.1),
            &DrawConfig {
                color_modulation: mat(rectangle.color),
                flip_horizontally: true,
                ..Default::default()
            },
        );

        let horizontal_height =
            rectangle.position.1 + rectangle.size.1 as i32 - horizontal.height() as i32;
        let mut horizontal_pos = rectangle.size.0;
        while let Some(p) = horizontal_pos.checked_sub(horizontal.width()) {
            horizontal_pos = p;
            ctx.draw(
                surface,
                horizontal,
                (
                    rectangle.position.0 + horizontal_pos as i32,
                    horizontal_height,
                ),
                &DrawConfig {
                    color_modulation: mat(rectangle.color),
                    ..Default::default()
                },
            );
            ctx.draw(
                surface,
                horizontal,
                (
                    rectangle.position.0 + horizontal_pos as i32,
                    rectangle.position.1,
                ),
                &DrawConfig {
                    color_modulation: mat(rectangle.color),
                    flip_vertically: true,
                    ..Default::default()
                },
            );
        }

        let horizontal_section = horizontal.get_section(
            (horizontal.width() - horizontal_pos, 0),
            (horizontal_pos, horizontal.height()),
        );
        ctx.draw(
            surface,
            &horizontal_section,
            (rectangle.position.0, horizontal_height),
            &DrawConfig {
                color_modulation: mat(rectangle.color),
                ..Default::default()
            },
        );
        ctx.draw(
            surface,
            &horizontal_section,
            (rectangle.position.0, rectangle.position.1),
            &DrawConfig {
                color_modulation: mat(rectangle.color),
                flip_vertically: true,
                ..Default::default()
            },
        );
    }
}

pub struct FrameRateLimiter {
    start: Instant,
    frame_count: u32,
    fps: u32,
}

impl FrameRateLimiter {
    pub fn new(fps: u32) -> Self {
        Self {
            start: Instant::now(),
            frame_count: 0,
            fps,
        }
    }

    pub fn frame(&mut self) {
        self.frame_count += 1;
        let finish = Duration::from_micros(1_000_000 / u64::from(self.fps)) * self.frame_count;
        if self.start.elapsed() < finish {
            while self.start.elapsed() < finish {
                thread::yield_now();
            }
        } else {
            warn!("Lag at frame {}", self.frame_count)
        }
    }
}