From 990f2b310bfecf2e04a8a462f6939833080c62bf Mon Sep 17 00:00:00 2001 From: Ben Bridle Date: Sat, 19 Oct 2024 18:20:09 +1300 Subject: Complete rewrite of Phosphor The previous version of the library passed events to an implementation of a WindowController trait by calling the trait method associated with each event, and received requests by calling different trait methods and reading the returned values. This had the downside of requiring that any data received from one event had to be stored in the type so that it could be passed back to Phosphor when a request method was called. The new library structure uses a single handle_event method on a trait, which is passed data representing any single event when it is called. Data is returned via a passed mutable reference to an EventQueue, meaning that any number of responses for any event can be immediately returned to Phosphor without having to wait in storage. --- src/window.rs | 397 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 198 insertions(+), 199 deletions(-) (limited to 'src/window.rs') diff --git a/src/window.rs b/src/window.rs index 261af36..377d460 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,251 +1,250 @@ use crate::*; -use buffer::HasDimensions; -use winit::dpi::{Size, PhysicalSize}; -use winit::event_loop::EventLoopWindowTarget; -use winit::window::{Fullscreen, WindowId}; - -pub struct Window { - pub controller: Box, - window: winit::window::Window, - pixel_scale: u32, - buffer: Buffer, - surface: softbuffer::Surface, +use buffer::{Dimensions, HasDimensions}; +use event_queue::EventQueue; + +use winit::dpi::PhysicalSize; +use winit::event_loop::ActiveEventLoop; +use winit::window::{CursorIcon, Fullscreen, Window}; + +use std::rc::Rc; +use std::cmp::{min, max}; + +fn to_physical_size(dimensions: Dimensions, scale: u32) -> PhysicalSize { + PhysicalSize { + width: max(1, dimensions.width * scale), + height: max(1, dimensions.height * scale), + } +} + + +pub(crate) struct PhosphorWindow { + pub winit: Rc, + pub program: Box, + pub requests: EventQueue, + pub pointer: Option, + pub marked_for_destruction: bool, + pub size_bounds: SizeBounds, // logical dimensions + + scale: u32, + buffer: buffer::Buffer, + surface: softbuffer::Surface, Rc>, surface_dimensions: Dimensions, - #[allow(dead_code)] context: softbuffer::Context, - render_request: RenderRequest, - previous_cursor_position: Option, - previous_cursor: Option, - previous_title: String, + redraw_full: bool, } -impl Window { - pub fn new(event_loop: &EventLoopWindowTarget<()>, controller: Box) -> Self { - let pixel_scale = controller.pixel_scale().get(); - let increment = PhysicalSize { width: pixel_scale, height: pixel_scale }; - let mut builder = winit::window::WindowBuilder::new() - .with_title(controller.title()) - .with_resize_increments(increment); - if let Some(exact_dimensions) = controller.exact_size() { - let exact_size = dim_to_size(exact_dimensions * pixel_scale); - builder = builder - .with_resizable(false) - .with_inner_size(exact_size) - .with_min_inner_size(exact_size) - .with_max_inner_size(exact_size); - } else if let Some(initial_dimensions) = controller.initial_size() { - builder = builder.with_inner_size(dim_to_size(initial_dimensions * pixel_scale)); - if let Some(min_dimensions) = controller.minimum_size() { - builder = builder.with_min_inner_size(dim_to_size(min_dimensions * pixel_scale)); - } - if let Some(max_dimensions) = controller.maximum_size() { - builder = builder.with_max_inner_size(dim_to_size(max_dimensions * pixel_scale)); - } + +impl PhosphorWindow { + pub fn from_builder(builder: WindowBuilder, event_loop: &ActiveEventLoop) -> Self { + let mut attr = Window::default_attributes(); + let scale = builder.scale; + let size_bounds = builder.size_bounds.unwrap_or(SizeBounds::default()); + + if let Some(size_bounds) = builder.size_bounds { + let (min_size, max_size) = size_bounds.as_min_max_size(scale); + attr = attr.with_min_inner_size(min_size); + attr = attr.with_max_inner_size(max_size); + } + if let Some(dimensions) = builder.dimensions { + attr = attr.with_inner_size(to_physical_size(dimensions, scale)); + } + match builder.fullscreen { + true => attr = attr.with_fullscreen(Some(Fullscreen::Borderless(None))), + false => attr = attr.with_fullscreen(None), }; - let window = builder.build(event_loop).unwrap(); - let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); - let surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); + let increment = PhysicalSize { width: scale, height: scale }; + attr = attr.with_resize_increments(increment); + + attr = attr.with_title(match builder.title { + Some(title) => title, + None => String::from("Untitled"), + }); + + let window = Rc::new(event_loop.create_window(attr).unwrap()); + let context = softbuffer::Context::new(window.clone()).unwrap(); + let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + + if let Some(cursor) = builder.cursor { + window.set_cursor(cursor); + window.set_cursor_visible(true); + } else { + window.set_cursor_visible(false); + } Self { - controller, - window, - pixel_scale, - buffer: Buffer::new(Dimensions::ZERO), + winit: window, + program: builder.program, + requests: EventQueue::new(), + pointer: None, + marked_for_destruction: false, + size_bounds, + + scale, + buffer: buffer::Buffer::new(Dimensions::ZERO), surface, surface_dimensions: Dimensions::ZERO, - context, - render_request: RenderRequest::REDRAW, - previous_cursor_position: None, - previous_cursor: Some(CursorIcon::Default), - previous_title: String::new(), + redraw_full: true, } } - pub fn id(&self) -> WindowId { - self.window.id() + pub fn handle_requests(&mut self, builders: &mut Vec) { + for request in self.requests.drain() { + match request { + Request::CreateWindow(builder) => builders.push(builder), + Request::SetTitle(title) => self.winit.set_title(&title), + Request::SetSize(dimensions) => self.set_size(dimensions), + Request::SetSizeBounds(bounds) => self.set_size_bounds(bounds), + Request::SetResizable(resizable) => self.winit.set_resizable(resizable), + Request::SetFullscreen(fullscreen) => self.set_fullscreen(fullscreen), + Request::SetVisible(visible) => self.winit.set_visible(visible), + Request::SetPixelScale(scale) => self.set_scale(scale), + Request::SetCursor(cursor) => self.set_cursor(cursor), + Request::Redraw => self.winit.request_redraw(), + Request::CloseWindow => { + self.marked_for_destruction = true; + self.program.handle_event(Event::Close, &mut self.requests.as_writer()); + }, + } + } } - pub fn update_title(&mut self) { - let title = self.controller.title(); - if title != self.previous_title { - self.window.set_title(&title); - self.previous_title = title; - } + pub fn set_size(&mut self, dimensions: Dimensions) { + let size = to_physical_size(dimensions, self.scale); + let _ = self.winit.request_inner_size(size); } - pub fn update_cursor(&mut self) { - let cursor = self.controller.cursor(); - if cursor != self.previous_cursor { - self.window.set_cursor_visible(cursor.is_some()); - if let Some(icon) = cursor { - self.window.set_cursor_icon(icon); - } - self.previous_cursor = cursor; + pub fn set_size_bounds(&mut self, bounds: SizeBounds) { + if self.size_bounds != bounds { + self.size_bounds = bounds; + self.update_size_bounds(); } } - /// This method only requests that the window size be changed; it does not - /// alter any buffer sizes or struct fields. When the window size eventually - /// changes, the state updates are handled by `resize_buffer_and_surface`. - pub fn update_window_size(&mut self) { - let fullscreen = self.controller.fullscreen(); - if fullscreen != self.window.fullscreen().is_some() { - if fullscreen { - if let Some(monitor) = self.window.current_monitor() { - if let Some(mode) = monitor.video_modes().nth(0) { - self.window.set_fullscreen(Some(Fullscreen::Exclusive(mode))); - } - }; - } else { - self.window.set_fullscreen(None); - } - } + pub fn update_size_bounds(&mut self) { + // Set the minimum and maximum dimensions of the window. + let (min_size, max_size) = self.size_bounds.as_min_max_size(self.scale); + self.winit.set_min_inner_size(Some(min_size)); + self.winit.set_max_inner_size(Some(max_size)); + + // Resize the window to fit the new size bounds. + let current_size = self.winit.inner_size(); + let width = min(max(current_size.width, min_size.width), max_size.width); + let height = min(max(current_size.height, min_size.height), max_size.height); + let _ = self.winit.request_inner_size(PhysicalSize { width, height }); + self.update_buffer_size(Dimensions { width, height }); + } - let pixel_scale = self.controller.pixel_scale().get(); - if let Some(dimensions) = self.controller.exact_size() { - // Prevent the window properties from being modified every frame - // and interrupting the actions of the window manager. - if pixel_scale == self.pixel_scale - && dimensions * pixel_scale == self.surface_dimensions { - return; - } - self.window.set_resizable(false); - let size = dim_to_size(dimensions * pixel_scale); - self.window.set_min_inner_size(Some(size)); - self.window.set_max_inner_size(Some(size)); - self.window.set_inner_size(size); - self.render_request = RenderRequest::REDRAW; - self.resize_buffer_and_surface(dimensions * pixel_scale); - } else { - // Prevent the window properties from being modified every frame - // and interrupting the actions of the window manager. - if pixel_scale == self.pixel_scale { - return; - } - self.window.set_resizable(true); - let size = self.controller.minimum_size().map(|d| dim_to_size(d * pixel_scale)); - self.window.set_min_inner_size(size); - let size = self.controller.maximum_size().map(|d| dim_to_size(d * pixel_scale)); - self.window.set_max_inner_size(size); - let increment = PhysicalSize { width: pixel_scale, height: pixel_scale }; - self.window.set_resize_increments(Some(increment)); - self.render_request = RenderRequest::REDRAW; - self.resize_buffer_and_surface(self.surface_dimensions); - } + pub fn scale(&self) -> u32 { + self.scale } - /// Resize the frame buffer and screen surface to the new size of the window. - pub fn resize_buffer_and_surface(&mut self, physical_dimensions: Dimensions) { - if let Some((width, height)) = dim_to_nonzero_size(physical_dimensions) { - self.surface.resize(width, height).unwrap(); - self.surface_dimensions = physical_dimensions; - }; - self.pixel_scale = self.controller.pixel_scale().get(); - let logical_dimensions = physical_dimensions / self.pixel_scale; - if logical_dimensions != self.buffer.dimensions() { - self.buffer.resize(logical_dimensions); - self.controller.on_resize(logical_dimensions); - self.render_request = RenderRequest::REDRAW; + pub fn set_scale(&mut self, scale: u32) { + if self.scale != scale { + self.scale = max(1, scale); + let increment = PhysicalSize { width: scale, height: scale }; + self.winit.set_resize_increments(Some(increment)); + self.update_size_bounds(); + self.requests.write(Request::Redraw); } } - pub fn move_cursor(&mut self, position: Point) { - // Convert cursor position from physical position to logical position. - let pixel_scale = self.controller.pixel_scale().get() as i32; - let logical_position = Point::new(position.x / pixel_scale, position.y / pixel_scale); - // The cursor position is rounded to i32 from f64, so we need to ignore - // duplicate consecutive cursor positions. - if self.previous_cursor_position != Some(logical_position) { - self.previous_cursor_position = Some(logical_position); - self.controller.on_cursor_move(logical_position); + pub fn set_fullscreen(&mut self, fullscreen: bool) { + self.winit.set_fullscreen( + match fullscreen { + true => Some(Fullscreen::Borderless(None)), + false => None, + } + ); + } + + pub fn set_cursor(&mut self, cursor: Option) { + if let Some(cursor) = cursor { + self.winit.set_cursor(cursor); + self.winit.set_cursor_visible(true); + } else { + self.winit.set_cursor_visible(false); } } - pub fn handle_render_request(&mut self) { - if let RenderRequest::Render(hint) = self.controller.render_request() { - self.render_request &= RenderRequest::Render(hint); - self.window.request_redraw(); - } else if let RenderRequest::Render(_) = self.render_request { - self.window.request_redraw(); + /// Called with new physical dimensions when window is resized. + pub fn update_buffer_size(&mut self, dimensions: Dimensions) { + if dimensions.is_zero() { + return; + } + // Set surface to physical dimensions. + unsafe { + self.surface.resize( + std::num::NonZeroU32::new_unchecked(dimensions.width), + std::num::NonZeroU32::new_unchecked(dimensions.height), + ).unwrap(); + } + self.surface_dimensions = dimensions; + + // Set buffer to logical dimensions. + let logical = dimensions / self.scale; + if logical != self.buffer.dimensions() { + self.buffer.resize(logical); + self.program.handle_event(Event::Resize(logical), &mut self.requests.as_writer()); + self.redraw_full = true; } } - pub fn render(&mut self) { - // If the surface dimensions are zero, the first resize event hasn't yet - // come through, and the Surface object will panic when `.present()` is - // called on it later in this method. We will instead skip rendering for - // this frame, knowing that the pending resize will trigger a re-render. + pub fn redraw(&mut self) { + // The surface dimensions will be zero until the first resize event + // arrives, which would cause a panic when calling `surface.present()`. if self.surface_dimensions.is_zero() { return; } - let scale = self.pixel_scale as usize; - let physical_dim = self.surface_dimensions; - let logical_dim = self.buffer.dimensions(); - if let RenderRequest::Render(hint) = self.render_request { - self.controller.on_render(&mut self.buffer, hint); - } else { - // TODO: Look into the cause of reduntant unrequested render - // requests under Windows. - self.controller.on_render(&mut self.buffer, RenderHint::Redraw); - } + // Render the program to the buffer. + self.program.render(&mut self.buffer, self.redraw_full); + self.redraw_full = false; + // Render the scaled buffer to the surface. let buffer = self.buffer.as_u32_slice(); let mut surface = self.surface.buffer_mut().unwrap(); - if scale == 1 { - let overlap = std::cmp::min(buffer.len(), surface.len()); - surface[..overlap].copy_from_slice(&buffer[..overlap]); + if self.scale == 1 && self.buffer.dimensions() == self.surface_dimensions { + let length = min(buffer.len(), surface.len()); + surface[..length].copy_from_slice(&buffer[..length]); } else { - let logical_width = logical_dim.width as usize; - let logical_height = logical_dim.height as usize; - let physical_width = physical_dim.width as usize; - let physical_content_width = logical_dim.width as usize * scale; - let row_excess = physical_width.saturating_sub(physical_content_width); + let scale = self.scale as usize; + let surface_width = self.surface_dimensions.width as usize; + let surface_height = self.surface_dimensions.height as usize; + let buffer_width = self.buffer.dimensions().width as usize; + let buffer_height = self.buffer.dimensions().height as usize; + let blank_width = surface_width.saturating_sub(buffer_width * scale); + let min_width = min(buffer_width, surface_width / scale); + let min_height = min(buffer_height, surface_height / scale); + let mut bi: usize = 0; let mut si: usize = 0; - let mut row_start: usize = 0; - let mut row_end: usize = 0; - for _y in 0..logical_height { - for py in 0..scale { - if py == 0 { - row_start = si; - for _x in 0..logical_width { - surface[si..si+scale].fill(buffer[bi]); - si += scale; - bi += 1; - } - // Fill the excess space on the right edge with black. - surface[si..si+row_excess].fill(0); - si += row_excess; - row_end = si; - } else { - surface.copy_within(row_start..row_end, si); - si += physical_width; - } + for _ in 0..min_height { + // Horizontally scale a buffer row. + let row_start = si; + for _ in 0..min_width { + surface[si..si+scale].fill(buffer[bi]); + si += scale; + bi += 1; + } + // Fill fractional right edge with black. + surface[si..si+blank_width].fill(0); + si += blank_width; + let row_end = si; + // Vertically scale the buffer row. + for _ in 0..scale.saturating_sub(1) { + surface.copy_within(row_start..row_end, si); + si += surface_width; } } - // Fill the excess space on the bottom edge with black. - let excess = surface.len().saturating_sub(si); - surface[si..si+excess].fill(0); + // Fill fractional bottom edge with black. + surface[si..].fill(0); } - surface.present().unwrap(); - // Reset render_request back to the lowest variant for the next frame. - self.render_request = RenderRequest::None; + surface.present().unwrap(); } } -fn dim_to_size(dimensions: Dimensions) -> Size { - Size::Physical( PhysicalSize { - width: std::cmp::max(1, dimensions.width), - height: std::cmp::max(1, dimensions.height), - }) -} -fn dim_to_nonzero_size(dimensions: Dimensions) -> Option<(NonZeroU32, NonZeroU32)> { - let width = NonZeroU32::new(dimensions.width)?; - let height = NonZeroU32::new(dimensions.height)?; - return Some((width, height)); -} + -- cgit v1.2.3-70-g09d2