use crate::*; 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, redraw_full: bool, } 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 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); } window.set_window_icon(builder.icon); Self { 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, redraw_full: true, } } 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::SetIcon(icon) => self.winit.set_window_icon(icon), 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 set_size(&mut self, dimensions: Dimensions) { let size = to_physical_size(dimensions, self.scale); let _ = self.winit.request_inner_size(size); } pub fn set_size_bounds(&mut self, bounds: SizeBounds) { if self.size_bounds != bounds { self.size_bounds = bounds; self.update_size_bounds(); } } 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 }); } pub fn scale(&self) -> u32 { self.scale } 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 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); } } /// 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 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; } // 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 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 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; 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 fractional bottom edge with black. surface[si..].fill(0); } surface.present().unwrap(); } }