diff --git a/Cargo.toml b/Cargo.toml
index df1bf29..9c83b41 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,7 @@
name = "softbuffer_quickstart"
description = "minifb-like interface for softbuffer"
repository = "https://github.com/pastthepixels/softbuffer-quickstart"
-version = "0.2.0"
+version = "0.3.0"
license = "GPL-3.0"
keywords = ["graphics"]
categories = ["graphics", "multimedia", "rendering"]
diff --git a/README.md b/README.md
index d692bf6..fba8bc0 100644
--- a/README.md
+++ b/README.md
@@ -5,38 +5,39 @@ A wrapper that makes using Softbuffer as easy as using minifb.
Running the Softbuffer example in softbuffer-quickstart:
```rust
use softbuffer_quickstart::{SoftbufferWindow, WindowProperties};
+use winit::event::WindowEvent;
fn main() {
- let mut window = SoftbufferWindow::new(
- // This is the "loop closure" -- called on every update loop
- |window, buffer| {
- let (width, height) = {
- let size = window.inner_size();
- (size.width, size.height)
- };
- for index in 0..(width * height) {
- let y = index / width;
- let x = index % width;
- let red = x % 255;
- let green = y % 255;
- let blue = (255 - (red + green).min(255)) % 255;
+ let mut window = SoftbufferWindow::new(WindowProperties::default());
+ window
+ .run(move |window, event| match event {
+ WindowEvent::RedrawRequested => {
+ let (width, height) = window.inner_size();
+ let mut buffer = window.buffer_mut();
+ for index in 0..(width * height) {
+ let y = index / width;
+ let x = index % width;
+ let red = x % 255;
+ let green = y % 255;
+ let blue = (255 - (red + green).min(255)) % 255;
- buffer[index as usize] = blue | (green << 8) | (red << 16);
+ buffer[index] = (blue | (green << 8) | (red << 16)).try_into().unwrap();
+ }
}
- },
- // This is how we can set properties for the window when it's initially created.
- WindowProperties::default()
- );
- window.run().expect("window can't run :(");
+ _ => (),
+ })
+ .expect("window can't run :(");
}
```
+## I wanna know more!
+`cargo add softbuffer_quickstart` and read the documentation at `doc/`!
+
## Contributing
PRs are welcome! As with any of my other projects it might take a while for me to respond to issues/pull requests. I recommend not squashing your commits before you submit a PR as doing so makes it a bit harder to review your code.
I'm looking for any ways to boost performance as much as possible while making the library simpler and more intuitive.
## Ideas:
-- Handling Winit events (like resizing)
-- Improving performance with the buffer (for loops in general are slow! there has to be a faster way to iterate over everything in the buffer)
- Adding icons to WindowProperties (probably good for new contributors)
+- Supporting compiling to wasm (hell)
\ No newline at end of file
diff --git a/doc/ADVANCED.md b/doc/ADVANCED.md
new file mode 100644
index 0000000..cdc8f99
--- /dev/null
+++ b/doc/ADVANCED.md
@@ -0,0 +1,134 @@
+Advanced Introduction to `softbuffer-quickstart`
+================================================
+a.k.a, "Well, what *more* can I do?"
+
+> [!NOTE]
+> This assumes that you've read the basic introduction first. If you haven't taken a look at it--and you have time--I encourage you to do so!`
+
+`softbuffer_quickstart`, at its core, actually provides utility functions for people who want to implement their own Winit windows. Let's work backwards. Say you want to implement your own Winit window. Then you'd certainly implement [`ApplicationHandler`](https://rust-windowing.github.io/winit/winit/application/trait.ApplicationHandler.html):
+
+```rust
+use winit::application::ApplicationHandler;
+use winit::event::WindowEvent;
+use winit::event_loop::ActiveEventLoop;
+use winit::window::WindowId;
+
+
+use softbuffer_quickstart::{init, WindowProperties};
+
+struct MyWindow {}
+
+impl ApplicationHandler for MyWindow {
+ fn resumed(&mut self, event_loop: &ActiveEventLoop) {
+ todo!()
+ }
+
+ fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
+ todo!()
+ }
+}
+```
+
+But wait! How does `softbuffer_quickstart` fit into this? Well, let's take a look at those TODOs.
+
+## 1. Initializing `softbuffer_quickstart`
+
+Initializing `softbuffer_quickstart` is simple. We'll call `init` with the given event loop and a set of initial window properties (remember `WindowProperties`?), which will give us a `RawWindow` and `RawSurface` to store ourselves:
+
+```rust
+use winit::application::ApplicationHandler;
+use winit::event::WindowEvent;
+use winit::event_loop::ActiveEventLoop;
+use winit::window::WindowId;
+
+
+use softbuffer_quickstart::{init, RawSurface, RawWindow, WindowProperties};
+
+struct MyWindow {
+ window: RawWindow,
+ surface: RawSurface
+}
+
+impl ApplicationHandler for MyWindow {
+ fn resumed(&mut self, event_loop: &ActiveEventLoop) {
+ (self.window, self.surface) = init(event_loop, &WindowProperties::default());
+ }
+
+ fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
+ todo!()
+ }
+}
+```
+
+## 2. Drawing to a window (and other handy functions)
+
+Under `window_event` we're given free rein to handle anything. There are two handy functions that can make handling some events simpler:
+- `softbuffer_quickstart::close(&event_loop, &event)` checks to see if a "close" event has been signalled, and closes the window if it has.
+- `softbuffer_quickstart::resize(&event, &mut surface)` check to see if a "resize" event has been signalled, and resizes the underlying buffer.
+
+We can of course omit these and implement those checks ourselves, if we'd like. (However, no one probably wants to unwrap and clone and get references a bunch of times.)
+
+Everything else is stupid simple. We update the buffer how we want, and then call `redraw` once we're done.
+- `softbuffer_quickstart::buffer_mut(surface: &mut RawSurface)` returns a mutable reference to a `RawSurface`. (Hey, just like in the basic tutorial!)
+- `softbuffer_quickstart::redraw(window: &mut RawWindow, surface: &mut RawSurface)` presents the `RawSurface` to the screen, using the `RawWindow`.
+
+The final piece of the puzzle is *running* the thing. All we have to do is call `softbuffer_quickstart::run()` with a mutable reference to our window.
+
+So if we recreated the code in the basic introduction, it'd look like this:
+
+```rust
+use winit::application::ApplicationHandler;
+use winit::event::WindowEvent;
+use winit::event_loop::ActiveEventLoop;
+use winit::window::WindowId;
+
+
+use softbuffer_quickstart::{init, RawSurface, RawWindow, WindowProperties};
+
+fn main() {
+ let mut window = MyWindow {
+ window: None,
+ surface: None,
+ size: (800, 600)
+ };
+ softbuffer_quickstart::run(&mut window).expect("Window can't run");
+}
+
+struct MyWindow {
+ window: RawWindow,
+ surface: RawSurface,
+ size: (usize, usize)
+}
+
+impl ApplicationHandler for MyWindow {
+ fn resumed(&mut self, event_loop: &ActiveEventLoop) {
+ (self.window, self.surface) = init(event_loop, &WindowProperties::default());
+ }
+
+ fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
+ softbuffer_quickstart::close(event_loop, &event);
+ softbuffer_quickstart::resize(&event, &mut self.surface);
+
+ match event {
+ WindowEvent::Resized(size) => {
+ self.size = (size.width as usize, size.height as usize);
+ }
+ WindowEvent::RedrawRequested => {
+ let (width, height) = self.size;
+ let mut buffer = softbuffer_quickstart::buffer_mut(&mut self.surface);
+ for index in 0..(width * height) {
+ let y = index / width;
+ let x = index % width;
+ let red = x % 255;
+ let green = y % 255;
+ let blue = (255 - (red + green).min(255)) % 255;
+
+ buffer[index] = (blue | (green << 8) | (red << 16)).try_into().unwrap();
+ }
+ softbuffer_quickstart::redraw(&mut self.window, &mut self.surface);
+ }
+ _ => (),
+ }
+ }
+}
+```
diff --git a/doc/BASIC.md b/doc/BASIC.md
new file mode 100644
index 0000000..98a02a8
--- /dev/null
+++ b/doc/BASIC.md
@@ -0,0 +1,76 @@
+Basic Introduction to `softbuffer-quickstart`
+=============================================
+a.k.a, "I just wanna make a window, dab nagit!"
+
+If you want to make a simple window, `softbuffer-quickstart` has you covered. (I mean, that's its entire thing!) Keep in mind that you'll lose a bit of control over window/device events, so if having more control is important to you, check out the advanced guide.
+
+## Quick, gimmie the source code!
+```rust
+use softbuffer_quickstart::{SoftbufferWindow, WindowProperties};
+use winit::event::WindowEvent;
+
+fn main() {
+ let mut window = SoftbufferWindow::new(WindowProperties::default());
+ window
+ .run(move |window, event| match event {
+ WindowEvent::RedrawRequested => {
+ let (width, height) = window.inner_size();
+ let mut buffer = window.buffer_mut();
+ for index in 0..(width * height) {
+ let y = index / width;
+ let x = index % width;
+ let red = x % 255;
+ let green = y % 255;
+ let blue = (255 - (red + green).min(255)) % 255;
+
+ buffer[index] = (blue | (green << 8) | (red << 16)).try_into().unwrap();
+ }
+ }
+ _ => (),
+ })
+ .expect("window can't run :(");
+}
+```
+
+## What does this do?
+`softbuffer_quickstart` has a handy dandy `SoftbufferWindow` class that pretty much handles the pain of using Winit. To create a new window, just use `SoftbufferWindow::new()` with a `WindowProperties` struct.
+
+`WindowProperties` is a quick way to define things like the width, height, and title of a window. This will help `softbuffer_quickstart` to create a Winit window and resize the buffer. You can create a `WindowProperties` like this:
+
+```rust
+use softbuffer_quickstart::{SoftbufferWindow, WindowProperties};
+
+fn main() {
+ let properties = WindowProperties {
+ title: "My super cool window",
+ width: 800, // Measurements in pixels
+ height: 600
+ };
+}
+```
+
+or this:
+
+```rust
+use softbuffer_quickstart::{SoftbufferWindow, WindowProperties};
+
+fn main() {
+ let properties = WindowProperties {
+ title: "My super cool window",
+ ..
+ WindowProperties::default() // We can just tell Rust to
+ // make the rest of the properties
+ // default ones.
+ };
+}
+```
+
+Next. What does `window.run` do?
+
+When you run a window, you have to move ownership of every variable you used outside the loop function *into* the loop function with the `move` keyword. Then, that function is called every time Winit polls your operating system for new events. Handling `WindowEvents` is in the [Winit documentation](https://docs.rs/winit/latest/winit/event/enum.WindowEvent.html), but all we really need is to see when we're supposed to redraw a window. Winit caps this to the monitor's refresh rate.
+
+Inside the loop function, we get the window's width and height from Winit with `window.inner_size()`, get a mutable reference to the buffer with `window.buffer_mut()`, and then set each pixel to some weird thing. All that matters about the weird thing we do in the for loop is that the indices in the buffer are referenced and set to a `u32` value. What are some ways to define 32-bit color values? Look no further than hexadecimal notation, which you've probably seen when styling things with CSS. For instance, pure red can be defined with `0xff0000`. Each channel (R, G, B) is represented with two hexadecimal values that go up to 255.
+
+After the loop function has been run, `softbuffer_quickstart` will automatically show the buffer and clear it for the next loop.
+
+Finally, the `expect()` at the bottom is because Winit sometimes can't make a window. In this case--which is rare--we'll just print out some small message and Rust will handle a stack trace for us.
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 855f8e6..46e8025 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,8 @@
+mod sb_window;
+
use std::num::NonZeroU32;
+pub use sb_window::SoftbufferWindow;
+
use std::rc::Rc;
use softbuffer::Surface;
@@ -7,110 +11,121 @@ use winit::dpi::PhysicalSize;
use winit::error::EventLoopError;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
-use winit::window::{Window, WindowId};
+use winit::window::{Window};
-/// Contains a few potential properties to set for a SoftbufferWindow when it is created.
+pub type RawWindow = Option>;
+pub type RawSurface = Option, Rc>>;
+
+/// Simple struct that holds some properties (size, title) for windows
pub struct WindowProperties {
- pub size: PhysicalSize,
- pub title: Box,
+ /// Initial width
+ pub width: u32,
+ /// Initial height
+ pub height: u32,
+ /// Title
+ pub title: &'static str,
}
impl Default for WindowProperties {
+ /// Creates an 800x600 window named "Softbuffer Window"
fn default() -> WindowProperties {
WindowProperties {
- size: PhysicalSize::new(800, 600),
- title: "Softbuffer window".into(),
+ width: 800,
+ height: 600,
+ title: "Softbuffer Window",
}
}
}
-impl WindowProperties {
- pub fn new(width: u32, height: u32, title: &str) -> WindowProperties {
- WindowProperties {
- size: PhysicalSize::new(width, height),
- title: title.into(),
- }
- }
+/// Shorthand to run a struct that implements winit's [`ApplicationHandler`](https://rust-windowing.github.io/winit/winit/application/trait.ApplicationHandler.html)
+pub fn run>(window: &mut A) -> Result<(), EventLoopError> {
+ let event_loop = EventLoop::new()?;
+ event_loop.set_control_flow(ControlFlow::Poll);
+ event_loop.run_app(window)
}
-/// Wrapper for Softbuffer and a Winit window
-pub struct SoftbufferWindow
-where
- T: FnMut(Rc, &mut [u32]),
-{
- window: Option>,
- loop_fn: T,
- surface: Option, Rc>>,
- properties: WindowProperties,
+/// Initialises and returns a new RawWindow and RawSurface given an `ActiveEventLoop` and `WindowProperties`.
+/// For instance, implementation within `ApplicationHandler::resumed` may look like:
+/// ```rust
+/// use winit::application::ApplicationHandler;
+/// use winit::event::WindowEvent;
+/// use winit::event_loop::ActiveEventLoop;
+/// use winit::window::WindowId;
+///
+///
+/// use softbuffer_quickstart::{init, RawSurface, RawWindow, WindowProperties};
+///
+/// struct MyWindow {
+/// window: RawWindow,
+/// surface: RawSurface
+/// }
+///
+/// impl ApplicationHandler for MyWindow {
+/// /// `ApplicationHandler::resumed()` implementation here
+/// fn resumed(&mut self, event_loop: &ActiveEventLoop) {
+/// (self.window, self.surface) = init(event_loop, &WindowProperties::default());
+/// }
+///
+/// fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
+/// todo!()
+/// }
+/// }
+/// ```
+pub fn init(event_loop: &ActiveEventLoop, properties: &WindowProperties) -> (RawWindow, RawSurface) {
+ let window = {
+ let window = event_loop.create_window(
+ Window::default_attributes()
+ .with_title(properties.title)
+ .with_inner_size(PhysicalSize::new(
+ properties.width,
+ properties.height,
+ )),
+ );
+ Rc::new(window.unwrap())
+ };
+ let context = softbuffer::Context::new(window.clone()).unwrap();
+ let window : RawWindow = Some(window.clone());
+ let surface : RawSurface = Some(Surface::new(&context, window.clone().unwrap()).unwrap());
+ (window, surface)
}
-impl ApplicationHandler for SoftbufferWindow
-where
- T: FnMut(Rc, &mut [u32]),
-{
- fn resumed(&mut self, event_loop: &ActiveEventLoop) {
- let window = {
- let window = event_loop.create_window(
- Window::default_attributes()
- .with_title(self.properties.title.clone())
- .with_inner_size(self.properties.size),
- );
- Rc::new(window.unwrap())
- };
- let context = softbuffer::Context::new(window.clone()).unwrap();
- self.window = Some(window.clone());
- self.surface = Some(Surface::new(&context, window.clone()).unwrap());
- }
-
- fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
- match event {
- WindowEvent::CloseRequested => {
- event_loop.exit();
- }
- WindowEvent::Resized(new_size) => {
- self.properties.size = new_size;
- let (width, height) = (new_size.width, new_size.height);
- self.surface
- .as_mut()
- .unwrap()
- .resize(
- NonZeroU32::new(width).unwrap(),
- NonZeroU32::new(height).unwrap(),
- )
- .unwrap();
- }
- WindowEvent::RedrawRequested => {
- let mut buffer = self.surface.as_mut().unwrap().buffer_mut().unwrap();
- (self.loop_fn)(self.window.clone().unwrap(), buffer.as_mut());
- buffer.present().unwrap();
- self.window.as_ref().unwrap().request_redraw();
- }
- _ => (),
- }
+/// Shorthand to listen for and handle WindowEvent::CloseRequested by closing windows
+pub fn close(event_loop: &ActiveEventLoop, event: &WindowEvent) {
+ if let WindowEvent::CloseRequested = event {
+ event_loop.exit();
}
}
-impl SoftbufferWindow
-where
- T: FnMut(Rc, &mut [u32]),
-{
- /// Creates a new SoftbufferWindow.
- /// `loop_fn` will be called every time the window needs to redraw,
- /// and `properties` contains a WindowProperties instance that will be
- /// read when the window is created.
- pub fn new(loop_fn: T, properties: WindowProperties) -> SoftbufferWindow {
- SoftbufferWindow {
- window: None,
- loop_fn,
- surface: None,
- properties,
- }
+/// Shorthand to listen for and handle WindowEvent::Resized by resizing a buffer (`RawSurface`)
+pub fn resize(event: &WindowEvent, surface: &mut RawSurface) {
+ if let WindowEvent::Resized(size) = event {
+ surface
+ .as_mut()
+ .unwrap()
+ .resize(
+ NonZeroU32::new(size.width).unwrap(),
+ NonZeroU32::new(size.height).unwrap(),
+ )
+ .unwrap();
}
+}
- /// Runs a SoftbufferWindow event loop.
- pub fn run(&mut self) -> Result<(), EventLoopError> {
- let event_loop = EventLoop::new().unwrap();
- event_loop.set_control_flow(ControlFlow::Poll);
- event_loop.run_app(self)
- }
+/// Redraws a `RawSurface`. Call this on `WindowEvent::RedrawRequested` inside `ApplicationHandler::window_event`,
+/// right after you've drawn everything to the `RawSurface`.
+pub fn redraw(window: &mut RawWindow, surface: &mut RawSurface) {
+ surface
+ .as_mut()
+ .unwrap()
+ .buffer_mut()
+ .unwrap()
+ .present()
+ .unwrap();
+ window.as_ref().unwrap().request_redraw();
}
+
+/// Gets a mutable reference to a buffer from a `RawSurface`. Colors are `u32`s.
+/// Accessing an array might look like `softbuffer_quickstart::buffer_mut(&mut self.surface)[y * width + x] = 0xffffff`.
+/// Keep in mind you have to keep track of the buffer width yourself--the RawBuffer type can't do that.
+pub fn buffer_mut(surface: &mut RawSurface) -> softbuffer::Buffer<'_, Rc, Rc> {
+ surface.as_mut().unwrap().buffer_mut().unwrap()
+}
\ No newline at end of file
diff --git a/src/sb_window.rs b/src/sb_window.rs
new file mode 100644
index 0000000..7cbf0a2
--- /dev/null
+++ b/src/sb_window.rs
@@ -0,0 +1,99 @@
+use std::num::NonZeroU32;
+use std::rc::Rc;
+use softbuffer::Surface;
+use winit::application::ApplicationHandler;
+use winit::dpi::PhysicalSize;
+use winit::error::EventLoopError;
+use winit::event::WindowEvent;
+use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
+use winit::window::{Window, WindowId};
+use crate::{close, init, redraw, resize, run, RawSurface, RawWindow, WindowProperties};
+
+/// Wrapper for Softbuffer and a Winit window.
+pub struct SoftbufferWindow {
+ window: RawWindow,
+ surface: RawSurface,
+ loop_fn: Option>,
+ properties: WindowProperties,
+}
+
+impl ApplicationHandler for SoftbufferWindow {
+ fn resumed(&mut self, event_loop: &ActiveEventLoop) {
+ (self.window, self.surface) = init(event_loop, &self.properties);
+ }
+
+ fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
+ // Automatic event handling
+ close(event_loop, &event);
+ resize(&event, &mut self.surface);
+
+
+ let is_redraw_requested = event == WindowEvent::RedrawRequested;
+ // User event handling
+ if self.loop_fn.is_some() {
+ let mut loop_fn = self.loop_fn.take().unwrap();
+ loop_fn(self, event);
+ self.loop_fn = Some(loop_fn);
+ }
+
+ // Displays buffer automatically if event is RedrawRequested
+ if is_redraw_requested {
+ redraw(&mut self.window, &mut self.surface);
+ }
+ }
+}
+
+impl SoftbufferWindow {
+ /// Creates a new SoftbufferWindow.
+ /// Example usage:
+ /// ```rust
+ /// use softbuffer_quickstart::{SoftbufferWindow, WindowProperties};
+ /// let window = SoftbufferWindow::new(WindowProperties::default());
+ /// ```
+ pub fn new(properties: WindowProperties) -> Self {
+ SoftbufferWindow {
+ window: None,
+ loop_fn: None,
+ surface: None,
+ properties,
+ }
+ }
+
+ /// Runs a SoftbufferWindow event loop.
+ /// To handle events, you need winit's `WindowEvent` enum.
+ /// Example usage:
+ /// ```rust
+ /// use winit::event::WindowEvent;
+ /// use softbuffer_quickstart::{SoftbufferWindow, WindowProperties};
+ /// let mut window = SoftbufferWindow::new(WindowProperties::default());
+ /// window.run(move |window, event| {
+ /// match event {
+ /// WindowEvent::RedrawRequested => (),
+ /// _ => ()
+ /// }
+ /// })?;
+ /// ```
+ pub fn run(
+ &mut self,
+ event_fn: impl FnMut(&mut SoftbufferWindow, WindowEvent) + 'static,
+ ) -> Result<(), EventLoopError> {
+ self.loop_fn = Some(Box::new(event_fn));
+ run(self)
+ }
+
+ /// Returns the size of a window as a tuple
+ pub fn inner_size(&mut self) -> (usize, usize) {
+ let size = self.window.clone().unwrap().inner_size();
+ (size.width as usize, size.height as usize)
+ }
+
+ /// Gets a mutable reference to the buffer
+ pub fn buffer_mut(&mut self) -> softbuffer::Buffer<'_, Rc, Rc> {
+ self.surface.as_mut().unwrap().buffer_mut().unwrap()
+ }
+
+ /// Gets a mutable reference to the window
+ pub fn window_mut(&mut self) -> Rc {
+ self.window.clone().unwrap()
+ }
+}