Skip to content
This repository was archived by the owner on Jul 8, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
43 changes: 22 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
134 changes: 134 additions & 0 deletions doc/ADVANCED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
Advanced Introduction to `softbuffer-quickstart`
================================================
<small>a.k.a, "Well, what *more* can I do?"</small>

> [!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);
}
_ => (),
}
}
}
```
76 changes: 76 additions & 0 deletions doc/BASIC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
Basic Introduction to `softbuffer-quickstart`
=============================================
<small>a.k.a, "I just wanna make a window, dab nagit!"</small>

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.
Loading