From cd94ebf1d3d11c5b3768b6af06609557ec822bc5 Mon Sep 17 00:00:00 2001 From: Spike Mellino Date: Wed, 12 Feb 2025 11:42:09 -0700 Subject: [PATCH 1/9] Rewrite to include user event handling also documented it! things are a little different so there might have to be a bit of a warning before i update softbuffer-quickstart --- src/lib.rs | 128 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 88 insertions(+), 40 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 855f8e6..a33125c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,49 +11,42 @@ use winit::window::{Window, WindowId}; /// Contains a few potential properties to set for a SoftbufferWindow when it is created. 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 { fn default() -> WindowProperties { WindowProperties { - size: PhysicalSize::new(800, 600), - title: "Softbuffer window".into(), - } - } -} - -impl WindowProperties { - pub fn new(width: u32, height: u32, title: &str) -> WindowProperties { - WindowProperties { - size: PhysicalSize::new(width, height), - title: title.into(), + width: 800, + height: 600, + title: "Softbuffer Window", } } } /// Wrapper for Softbuffer and a Winit window -pub struct SoftbufferWindow -where - T: FnMut(Rc, &mut [u32]), -{ +pub struct SoftbufferWindow { window: Option>, - loop_fn: T, + loop_fn: Option>, surface: Option, Rc>>, properties: WindowProperties, } -impl ApplicationHandler for SoftbufferWindow -where - T: FnMut(Rc, &mut [u32]), -{ +impl ApplicationHandler for SoftbufferWindow { 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), + .with_inner_size(PhysicalSize::new( + self.properties.width, + self.properties.height, + )), ); Rc::new(window.unwrap()) }; @@ -63,12 +56,12 @@ where } fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + // Automatic event handling 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() @@ -79,38 +72,93 @@ where ) .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(); - } _ => (), } + + 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 { + let buffer = self.surface.as_mut().unwrap().buffer_mut().unwrap(); + buffer.present().unwrap(); + + self.window.as_ref().unwrap().request_redraw(); + } } } -impl SoftbufferWindow -where - T: FnMut(Rc, &mut [u32]), -{ +impl SoftbufferWindow { /// 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 { + /// Example usage: + /// ```rust + /// let window = SoftbufferWindow::new(WindowProperties::default()); + /// ``` + pub fn new(properties: WindowProperties) -> Self { SoftbufferWindow { window: None, - loop_fn, + loop_fn: None, surface: None, properties, } } /// Runs a SoftbufferWindow event loop. - pub fn run(&mut self) -> Result<(), EventLoopError> { + /// To handle events, you need winit's `WindowEvent` enum. + /// Example usage: + /// ```rust + /// 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)); let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); event_loop.run_app(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) + } + + /// Copies a u32 slice to the buffer + pub fn buffer_cpy(&mut self, src: &[u32]) { + self.surface + .as_mut() + .unwrap() + .buffer_mut() + .unwrap() + .as_mut() + .copy_from_slice(src); + } + + /// Sets the color value of a pixel in the buffer + pub fn buffer_set(&mut self, idx: usize, src: u32) { + self.surface.as_mut().unwrap().buffer_mut().unwrap()[idx] = src; + } + + /// Gets the color value of a pixel in the buffer + pub fn buffer_get(&mut self, idx: usize) -> u32 { + self.surface.as_mut().unwrap().buffer_mut().unwrap()[idx] + } + + /// Returns the size of the buffer + pub fn buffer_len(&mut self) -> usize { + self.surface.as_mut().unwrap().buffer_mut().unwrap().len() + } } From 356a6e9ec8006b802f3e687955cf08e15ca9a2c0 Mon Sep 17 00:00:00 2001 From: Spike Mellino Date: Wed, 12 Feb 2025 11:42:52 -0700 Subject: [PATCH 2/9] Update version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"] From 15179eb4379dce1386994f341533d15a1727a06d Mon Sep 17 00:00:00 2001 From: Spike Mellino Date: Wed, 12 Feb 2025 11:48:11 -0700 Subject: [PATCH 3/9] Update README --- README.md | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 2d96aff..42e7b51 100644 --- a/README.md +++ b/README.md @@ -5,29 +5,27 @@ 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 = (x * y) % 255; + let mut window = SoftbufferWindow::new(WindowProperties::default()); + window.run(move |window, event| { + match event { + WindowEvent::RedrawRequested => { + let (width, height) = window.inner_size(); + for index in 0..(width * height) { + let y = index / width; + let x = index % width; + let red = x % 255; + let green = y % 255; + let blue = (x * y) % 255; - buffer[index as usize] = blue | (green << 8) | (red << 16); + window.buffer_set(index, blue | (green << 8) | (red << 16)); + } } - }, - // 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 :("); } ``` From c3c99992365587c01ac391dba64cd57c19c7005b Mon Sep 17 00:00:00 2001 From: Spike Mellino Date: Wed, 12 Feb 2025 13:24:28 -0700 Subject: [PATCH 4/9] Optimizations --- src/lib.rs | 46 ++++++++++++++-------------------------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a33125c..0caf1fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,7 @@ impl ApplicationHandler for SoftbufferWindow { let window = { let window = event_loop.create_window( Window::default_attributes() - .with_title(self.properties.title.clone()) + .with_title(self.properties.title) .with_inner_size(PhysicalSize::new( self.properties.width, self.properties.height, @@ -61,14 +61,13 @@ impl ApplicationHandler for SoftbufferWindow { WindowEvent::CloseRequested => { event_loop.exit(); } - WindowEvent::Resized(new_size) => { - let (width, height) = (new_size.width, new_size.height); + WindowEvent::Resized(size) => { self.surface .as_mut() .unwrap() .resize( - NonZeroU32::new(width).unwrap(), - NonZeroU32::new(height).unwrap(), + NonZeroU32::new(size.width).unwrap(), + NonZeroU32::new(size.height).unwrap(), ) .unwrap(); } @@ -86,9 +85,13 @@ impl ApplicationHandler for SoftbufferWindow { // Displays buffer automatically if event is RedrawRequested if is_redraw_requested { - let buffer = self.surface.as_mut().unwrap().buffer_mut().unwrap(); - buffer.present().unwrap(); - + self.surface + .as_mut() + .unwrap() + .buffer_mut() + .unwrap() + .present() + .unwrap(); self.window.as_ref().unwrap().request_redraw(); } } @@ -136,29 +139,8 @@ impl SoftbufferWindow { (size.width as usize, size.height as usize) } - /// Copies a u32 slice to the buffer - pub fn buffer_cpy(&mut self, src: &[u32]) { - self.surface - .as_mut() - .unwrap() - .buffer_mut() - .unwrap() - .as_mut() - .copy_from_slice(src); - } - - /// Sets the color value of a pixel in the buffer - pub fn buffer_set(&mut self, idx: usize, src: u32) { - self.surface.as_mut().unwrap().buffer_mut().unwrap()[idx] = src; - } - - /// Gets the color value of a pixel in the buffer - pub fn buffer_get(&mut self, idx: usize) -> u32 { - self.surface.as_mut().unwrap().buffer_mut().unwrap()[idx] - } - - /// Returns the size of the buffer - pub fn buffer_len(&mut self) -> usize { - self.surface.as_mut().unwrap().buffer_mut().unwrap().len() + /// 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() } } From f46ea8c2eac489d7299dfe67ba285a62cfb92d1c Mon Sep 17 00:00:00 2001 From: Spike Mellino Date: Wed, 12 Feb 2025 13:24:33 -0700 Subject: [PATCH 5/9] Update README --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 42e7b51..98f7973 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ use winit::event::WindowEvent; fn main() { let mut window = SoftbufferWindow::new(WindowProperties::default()); - window.run(move |window, event| { - match event { + 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; @@ -20,12 +21,12 @@ fn main() { let green = y % 255; let blue = (x * y) % 255; - window.buffer_set(index, blue | (green << 8) | (red << 16)); + buffer[index] = (blue | (green << 8) | (red << 16)).try_into().unwrap(); } } - _ => () - } - }).expect("window can't run :("); + _ => (), + }) + .expect("window can't run :("); } ``` From d96120b167734a4b4dd38b45cf59a124112c7fc5 Mon Sep 17 00:00:00 2001 From: Spike Date: Thu, 13 Feb 2025 06:24:01 +0000 Subject: [PATCH 6/9] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 98f7973..22baf3f 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,4 @@ PRs are welcome! As with any of my other projects it might take a while for me t 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) From a8c0dc1d0b5daadc144877c784af26a7fed182dc Mon Sep 17 00:00:00 2001 From: Spike Date: Thu, 13 Feb 2025 06:47:31 +0000 Subject: [PATCH 7/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22baf3f..092a1e9 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ fn main() { let x = index % width; let red = x % 255; let green = y % 255; - let blue = (x * y) % 255; + let blue = (255 - (red + green).min(255)) % 255; buffer[index] = (blue | (green << 8) | (red << 16)).try_into().unwrap(); } From 09be6be7e6221ca80132fe511b3e3b6460e5734e Mon Sep 17 00:00:00 2001 From: Spike Mellino Date: Fri, 25 Apr 2025 22:09:35 -0600 Subject: [PATCH 8/9] Rewrite to support custom ApplicationHandler structs --- src/lib.rs | 180 +++++++++++++++++++---------------------------- src/sb_window.rs | 99 ++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 108 deletions(-) create mode 100644 src/sb_window.rs diff --git a/src/lib.rs b/src/lib.rs index 0caf1fd..508a8a6 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,9 +11,12 @@ 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}; + +pub type RawWindow = Option>; +pub type RawSurface = Option, Rc>>; -/// Contains a few potential properties to set for a SoftbufferWindow when it is created. +/// Simple struct that holds some properties (size, title) for windows pub struct WindowProperties { /// Initial width pub width: u32, @@ -20,6 +27,7 @@ pub struct WindowProperties { } impl Default for WindowProperties { + /// Creates an 800x600 window named "Softbuffer Window" fn default() -> WindowProperties { WindowProperties { width: 800, @@ -29,118 +37,74 @@ impl Default for WindowProperties { } } -/// Wrapper for Softbuffer and a Winit window -pub struct SoftbufferWindow { - window: Option>, - loop_fn: Option>, - surface: Option, Rc>>, - properties: WindowProperties, +/// Shorthand to run a struct that implements `ApplicationHandler` +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) } -impl ApplicationHandler for SoftbufferWindow { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let window = { - let window = event_loop.create_window( - Window::default_attributes() - .with_title(self.properties.title) - .with_inner_size(PhysicalSize::new( - self.properties.width, - self.properties.height, - )), - ); - 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) { - // Automatic event handling - match event { - WindowEvent::CloseRequested => { - event_loop.exit(); - } - WindowEvent::Resized(size) => { - self.surface - .as_mut() - .unwrap() - .resize( - NonZeroU32::new(size.width).unwrap(), - NonZeroU32::new(size.height).unwrap(), - ) - .unwrap(); - } - _ => (), - } - - 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 { - self.surface - .as_mut() - .unwrap() - .buffer_mut() - .unwrap() - .present() - .unwrap(); - self.window.as_ref().unwrap().request_redraw(); - } - } +/// Initialises and returns a new RawWindow and RawSurface given an `ActiveEventLoop` and `WindowProperties`. +/// For instance, implementation within `ApplicationHandler::resumed` may look like: +/// ```rust +/// //... +/// fn resumed(&mut self, event_loop: &ActiveEventLoop) { +/// (self.window, self.surface) = init(event_loop, &self.properties); +/// } +/// //... +/// ``` +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 SoftbufferWindow { - /// Creates a new SoftbufferWindow. - /// Example usage: - /// ```rust - /// 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 - /// 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)); - let event_loop = EventLoop::new().unwrap(); - event_loop.set_control_flow(ControlFlow::Poll); - event_loop.run_app(self) +/// 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(); } +} - /// 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) +/// Shorthand to listen for and handle WindowEvent::Resized by resizing a buffer +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(); } +} - /// 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() - } +/// Redraws a RawSurface. Call this on `Window::RedrawRequested`. +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` +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..a659eb2 --- /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() + } +} From cbd1023a8e164a502b096f987e4ae210c2ef857d Mon Sep 17 00:00:00 2001 From: Spike Mellino Date: Sat, 31 May 2025 02:08:58 -0600 Subject: [PATCH 9/9] Document softbuffer_quickstart 0.3.0 --- README.md | 4 ++ doc/ADVANCED.md | 134 +++++++++++++++++++++++++++++++++++++++++++++++ doc/BASIC.md | 76 +++++++++++++++++++++++++++ src/lib.rs | 37 ++++++++++--- src/sb_window.rs | 2 +- 5 files changed, 244 insertions(+), 9 deletions(-) create mode 100644 doc/ADVANCED.md create mode 100644 doc/BASIC.md diff --git a/README.md b/README.md index 092a1e9..fba8bc0 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ fn main() { } ``` +## 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. @@ -37,3 +40,4 @@ I'm looking for any ways to boost performance as much as possible while making t ## Ideas: - 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 508a8a6..46e8025 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ impl Default for WindowProperties { } } -/// Shorthand to run a struct that implements `ApplicationHandler` +/// 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); @@ -47,11 +47,29 @@ pub fn run>(window: &mut A) -> Result<(), EventLoopErr /// Initialises and returns a new RawWindow and RawSurface given an `ActiveEventLoop` and `WindowProperties`. /// For instance, implementation within `ApplicationHandler::resumed` may look like: /// ```rust -/// //... -/// fn resumed(&mut self, event_loop: &ActiveEventLoop) { -/// (self.window, self.surface) = init(event_loop, &self.properties); +/// 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 = { @@ -78,7 +96,7 @@ pub fn close(event_loop: &ActiveEventLoop, event: &WindowEvent) { } } -/// Shorthand to listen for and handle WindowEvent::Resized by resizing a buffer +/// 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 @@ -92,7 +110,8 @@ pub fn resize(event: &WindowEvent, surface: &mut RawSurface) { } } -/// Redraws a RawSurface. Call this on `Window::RedrawRequested`. +/// 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() @@ -104,7 +123,9 @@ pub fn redraw(window: &mut RawWindow, surface: &mut RawSurface) { window.as_ref().unwrap().request_redraw(); } -/// Gets a mutable reference to a buffer from a `RawSurface` +/// 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 index a659eb2..7cbf0a2 100644 --- a/src/sb_window.rs +++ b/src/sb_window.rs @@ -9,7 +9,7 @@ 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 +/// Wrapper for Softbuffer and a Winit window. pub struct SoftbufferWindow { window: RawWindow, surface: RawSurface,