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, surface_dimensions: Dimensions, #[allow(dead_code)] context: softbuffer::Context, render_request: RenderRequest, previous_cursor_position: Option, previous_cursor: Option, previous_title: String, } 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)); } }; let window = builder.build(event_loop).unwrap(); let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); let surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); Self { controller, window, pixel_scale, 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(), } } pub fn id(&self) -> WindowId { self.window.id() } 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 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; } } /// 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); } } 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); } } /// 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 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 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(); } } 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. 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(); // Return early if this render wasn't requested by us. Unrequested // renders have been observed under Windows 10. if let RenderRequest::Render(hint) = self.render_request { self.controller.on_render(&mut self.buffer, hint); } else { return; } 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]); } 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 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; } } } // Fill the excess space on the bottom edge with black. let excess = surface.len().saturating_sub(si); surface[si..si+excess].fill(0); } surface.present().unwrap(); // Reset render_request back to the lowest variant for the next frame. self.render_request = RenderRequest::None; } } 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)); }