diff --git a/README.md b/README.md index d63a6b9..282efe2 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ Rapidly prototype a simple 2D game, pixel-based animations, software renderers, ## Examples -- [Pixel Invaders](./examples/invaders) - [Conway's Game of Life](./examples/conway) +- [Minimal example with `winit`](./examples/minimal-winit) +- [Pixel Invaders](./examples/invaders) ## Comparison with `minifb` diff --git a/examples/minimal-winit/README.md b/examples/minimal-winit/README.md new file mode 100644 index 0000000..87a0579 --- /dev/null +++ b/examples/minimal-winit/README.md @@ -0,0 +1,17 @@ +# Hello Pixels + +![Hello Pixels](../../img/minimal-winit.png) + +Minimal example with `winit`. + +## Running + +```bash +cargo run --release --example minimal-winit +``` + +## About + +This example demonstrates the absolute minimum for creating a `winit` window and pixel buffer. It animates a purple box moving on a blue background, just for _something_ interesting to display. + +It uses `winit_input_helper` to provide a slightly better code presentation, but it doesn't greatly reduce the number of lines in the example. diff --git a/examples/minimal-winit/main.rs b/examples/minimal-winit/main.rs new file mode 100644 index 0000000..657734a --- /dev/null +++ b/examples/minimal-winit/main.rs @@ -0,0 +1,130 @@ +#![deny(clippy::all)] +#![forbid(unsafe_code)] + +use pixels::{wgpu::Surface, Error, Pixels, SurfaceTexture}; +use winit::dpi::LogicalSize; +use winit::event::{Event, VirtualKeyCode, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::window::WindowBuilder; +use winit_input_helper::WinitInputHelper; + +const WIDTH: u32 = 320; +const HEIGHT: u32 = 240; +const BOX_SIZE: i16 = 64; + +/// Representation of the application state. In this example, a box will bounce around the screen. +struct World { + box_x: i16, + box_y: i16, + velocity_x: i16, + velocity_y: i16, +} + +fn main() -> Result<(), Error> { + let event_loop = EventLoop::new(); + let mut input = WinitInputHelper::new(); + let window = { + let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64); + WindowBuilder::new() + .with_title("Hello Pixels") + .with_inner_size(size) + .with_min_inner_size(size) + .build(&event_loop) + .unwrap() + }; + let mut hidpi_factor = window.hidpi_factor(); + + let mut pixels = { + let surface = Surface::create(&window); + let surface_texture = SurfaceTexture::new(WIDTH, HEIGHT, surface); + Pixels::new(WIDTH, HEIGHT, surface_texture)? + }; + let mut world = World::new(); + + event_loop.run(move |event, _, control_flow| { + // Draw the current frame + if let Event::WindowEvent { + event: WindowEvent::RedrawRequested, + .. + } = event + { + world.draw(pixels.get_frame()); + pixels.render(); + } + + // Handle input events + if input.update(event) { + // Close events + if input.key_pressed(VirtualKeyCode::Escape) || input.quit() { + *control_flow = ControlFlow::Exit; + return; + } + + // Adjust high DPI factor + if let Some(factor) = input.hidpi_changed() { + hidpi_factor = factor; + } + + // Resize the window + if let Some(size) = input.window_resized() { + let size = size.to_physical(hidpi_factor); + let width = size.width.round() as u32; + let height = size.height.round() as u32; + + pixels.resize(width, height); + } + + // Update internal state and request a redraw + world.update(); + window.request_redraw(); + } + }); +} + +impl World { + /// Create a new `World` instance that can draw a moving box. + fn new() -> Self { + Self { + box_x: 24, + box_y: 16, + velocity_x: 1, + velocity_y: 1, + } + } + + /// Update the `World` internal state; bounce the box around the screen. + fn update(&mut self) { + if self.box_x < 0 || self.box_x + BOX_SIZE >= WIDTH as i16 { + self.velocity_x *= -1; + } + if self.box_y < 0 || self.box_y + BOX_SIZE >= HEIGHT as i16 { + self.velocity_y *= -1; + } + + self.box_x += self.velocity_x; + self.box_y += self.velocity_y; + } + + /// Draw the `World` state to the frame buffer. + /// + /// Assumes the default texture format: [`wgpu::TextureFormat::Rgba8UnormSrgb`] + fn draw(&self, frame: &mut [u8]) { + for (i, pixel) in frame.chunks_exact_mut(4).enumerate() { + let x = (i % WIDTH as usize) as i16; + let y = (i / WIDTH as usize) as i16; + + let inside_the_box = x >= self.box_x + && x < self.box_x + BOX_SIZE + && y >= self.box_y + && y < self.box_y + BOX_SIZE; + + let rgba = if inside_the_box { + [0x5e, 0x48, 0xe8, 0xff] + } else { + [0x48, 0xb2, 0xe8, 0xff] + }; + + pixel.copy_from_slice(&rgba); + } + } +} diff --git a/img/minimal-winit.png b/img/minimal-winit.png new file mode 100644 index 0000000..e132477 Binary files /dev/null and b/img/minimal-winit.png differ