diff --git a/README.md b/README.md index a7df661..a4890ec 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Rapidly prototype a simple 2D game, pixel-based animations, software renderers, - [Dear ImGui example with `winit`](./examples/imgui-winit) - [Minimal example with SDL2](./examples/minimal-sdl2) - [Minimal example with `winit`](./examples/minimal-winit) +- [Minimal example with `fltk`](./examples/minimal-fltk) - [Pixel Invaders](./examples/invaders) - [`raqote` example](./examples/raqote-winit) diff --git a/examples/minimal-fltk/Cargo.toml b/examples/minimal-fltk/Cargo.toml new file mode 100644 index 0000000..1a1a8bd --- /dev/null +++ b/examples/minimal-fltk/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "minimal-fltk" +version = "0.1.0" +authors = ["Jay Oster "] +edition = "2018" +publish = false + +[features] +optimize = ["log/release_max_level_warn"] +default = ["optimize"] + +[dependencies] +fltk = { version = "0.14", features = ["no-pango"] } +env_logger = "0.7.1" +log = "0.4.8" +pixels = { path = "../.." } diff --git a/examples/minimal-fltk/README.md b/examples/minimal-fltk/README.md new file mode 100644 index 0000000..b332073 --- /dev/null +++ b/examples/minimal-fltk/README.md @@ -0,0 +1,15 @@ +# Hello Pixels + +![Hello Pixels](../../img/minimal-fltk.png) + +Minimal example with fltk. + +## Running + +```bash +cargo run --release --package minimal-fltk +``` + +## About + +This example demonstrates the absolute minimum for creating an fltk window and pixel buffer. It animates a purple circle moving on a darker purple background, just for _something_ interesting to display. diff --git a/examples/minimal-fltk/src/main.rs b/examples/minimal-fltk/src/main.rs new file mode 100644 index 0000000..87a4ffa --- /dev/null +++ b/examples/minimal-fltk/src/main.rs @@ -0,0 +1,107 @@ +#![deny(clippy::all)] +#![forbid(unsafe_code)] +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +use fltk::{app, prelude::*, window::Window}; +use log::error; +use pixels::{Error, Pixels, SurfaceTexture}; +use std::{thread, time::Duration}; + +const WIDTH: u32 = 600; +const HEIGHT: u32 = 400; +const CIRCLE_RADIUS: i16 = 64; +const SLEEP: u64 = 16; + +/// Representation of the application state. In this example, a circle will bounce around the screen. +struct World { + circle_x: i16, + circle_y: i16, + velocity_x: i16, + velocity_y: i16, +} + +fn main() -> Result<(), Error> { + env_logger::init(); + let app = app::App::default(); + let mut win = Window::default() + .with_size(WIDTH as i32, HEIGHT as i32) + .with_label("Hello Pixels"); + win.end(); + win.make_resizable(true); + win.show(); + + let mut pixels = { + let surface_texture = SurfaceTexture::new(WIDTH, HEIGHT, &win); + Pixels::new(WIDTH, HEIGHT, surface_texture)? + }; + + let mut world = World::new(); + + while app.wait() { + // Update internal state + world.update(); + // Draw the current frame + world.draw(pixels.get_frame()); + if pixels + .render() + .map_err(|e| error!("pixels.render() failed: {}", e)) + .is_err() + { + app.quit(); + } + win.redraw(); + // Calls to redraw in the event loop require an explicit sleep + thread::sleep(Duration::from_millis(SLEEP)); + } + + Ok(()) +} + +impl World { + /// Create a new `World` instance that can draw a moving circle. + fn new() -> Self { + Self { + circle_x: 300, + circle_y: 200, + velocity_x: 5, + velocity_y: 5, + } + } + + /// Update the `World` internal state; bounce the circle around the screen. + fn update(&mut self) { + if self.circle_x - CIRCLE_RADIUS <= 0 || self.circle_x + CIRCLE_RADIUS > WIDTH as i16 { + self.velocity_x *= -1; + } + if self.circle_y - CIRCLE_RADIUS <= 0 || self.circle_y + CIRCLE_RADIUS > HEIGHT as i16 { + self.velocity_y *= -1; + } + + self.circle_x += self.velocity_x; + self.circle_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 d = { + let xd = x as i32 - self.circle_x as i32; + let yd = y as i32 - self.circle_y as i32; + ((xd.pow(2) + yd.pow(2)) as f64).sqrt().powi(2) + }; + let inside_the_circle = d < (CIRCLE_RADIUS as f64).powi(2); + + let rgba = if inside_the_circle { + [0xac, 0x00, 0xe6, 0xff] + } else { + [0x26, 0x00, 0x33, 0xff] + }; + + pixel.copy_from_slice(&rgba); + } + } +} diff --git a/img/minimal-fltk.png b/img/minimal-fltk.png new file mode 100644 index 0000000..0cfdff5 Binary files /dev/null and b/img/minimal-fltk.png differ