diff options
Diffstat (limited to 'src/window.rs')
-rw-r--r-- | src/window.rs | 397 |
1 files changed, 198 insertions, 199 deletions
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<dyn WindowController>, - 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<u32> { + PhysicalSize { + width: max(1, dimensions.width * scale), + height: max(1, dimensions.height * scale), + } +} + + +pub(crate) struct PhosphorWindow { + pub winit: Rc<Window>, + pub program: Box<dyn WindowProgram>, + pub requests: EventQueue<Request>, + pub pointer: Option<Position>, + pub marked_for_destruction: bool, + pub size_bounds: SizeBounds, // logical dimensions + + scale: u32, + buffer: buffer::Buffer, + surface: softbuffer::Surface<Rc<Window>, Rc<Window>>, surface_dimensions: Dimensions, - #[allow(dead_code)] context: softbuffer::Context, - render_request: RenderRequest, - previous_cursor_position: Option<Point>, - previous_cursor: Option<CursorIcon>, - previous_title: String, + redraw_full: bool, } -impl Window { - pub fn new(event_loop: &EventLoopWindowTarget<()>, controller: Box<dyn WindowController>) -> 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<WindowBuilder>) { + 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<CursorIcon>) { + 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)); -} + |