diff options
author | Ben Bridle <bridle.benjamin@gmail.com> | 2023-11-05 14:17:49 +1300 |
---|---|---|
committer | Ben Bridle <bridle.benjamin@gmail.com> | 2023-11-05 14:17:49 +1300 |
commit | cd3769a48efcc3fdd2dc1304b1babfe6d26f788d (patch) | |
tree | 37c62c9ce10bd415326e4b1abac42e3a73d8d78f | |
parent | 8e08d723ff7a853f2b10dc0f1408911d5801cea8 (diff) | |
download | phosphor-cd3769a48efcc3fdd2dc1304b1babfe6d26f788d.zip |
Implement window scaling
A window now can declare a scale factor to be used when rendering logical
pixels to a physical window. Each logical pixel will be drawn as an NxN
block of physical pixels, where N is the scale factor.
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/window.rs | 154 | ||||
-rw-r--r-- | src/window_controller.rs | 21 | ||||
-rw-r--r-- | src/window_manager.rs | 12 |
4 files changed, 130 insertions, 58 deletions
@@ -14,6 +14,7 @@ pub use winit::{ event::VirtualKeyCode as KeyCode, window::CursorIcon, }; +pub use std::num::NonZeroU32; pub type Point = geometry::Point<i32>; pub type Dimensions = geometry::Dimensions<u32>; diff --git a/src/window.rs b/src/window.rs index ffe35e8..697c0bb 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,6 +1,6 @@ use crate::*; -use std::num::NonZeroU32; +use buffer::HasDimensions; use winit::dpi::{Size, PhysicalSize}; use winit::event_loop::EventLoopWindowTarget; use winit::window::WindowId; @@ -8,35 +8,50 @@ use winit::window::WindowId; pub struct Window { pub controller: Box<dyn WindowController>, window: winit::window::Window, + pixel_scale: u32, buffer: Buffer, - dimensions: Dimensions, - #[allow(dead_code)] context: softbuffer::Context, surface: softbuffer::Surface, + surface_dimensions: Dimensions, + #[allow(dead_code)] context: softbuffer::Context, current_render_hint: RenderHint, previous_cursor_position: Option<Point>, } impl Window { pub fn new(event_loop: &EventLoopWindowTarget<()>, controller: Box<dyn WindowController>) -> Self { - let window = winit::window::WindowBuilder::new() - .with_title(controller.title()) - .with_resizable(controller.is_resizable()) - .with_inner_size(dim_to_size(controller.initial_size())) - .build(event_loop) - .unwrap(); + let mut builder = winit::window::WindowBuilder::new().with_title(controller.title()); + let pixel_scale = controller.pixel_scale().get(); + 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), - dimensions: Dimensions::ZERO, - context, surface, - previous_cursor_position: None, + surface_dimensions: Dimensions::ZERO, + context, current_render_hint: RenderHint::Redraw, + previous_cursor_position: None, } } @@ -53,40 +68,52 @@ impl Window { self.window.set_cursor_icon(icon); } - pub fn update_minimum_size(&mut self) { - let size = self.controller.minimum_size().map(|d| dim_to_size(d)); - self.window.set_min_inner_size(size); - } - - pub fn update_maximum_size(&mut self) { - let size = self.controller.maximum_size().map(|d| dim_to_size(d)); - self.window.set_max_inner_size(size); + /// 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 pixel_scale = self.controller.pixel_scale().get(); + if let Some(dimensions) = self.controller.exact_size() { + // Without this early return, the constant re-setting of the window + // size prevents the window from being able to be positioned with the + // bottom edge below the screen bounds. + 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); + } else { + 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); + } } pub fn update_cursor_visible(&mut self) { self.window.set_cursor_visible(self.controller.is_cursor_visible()); } - pub fn update_resizable(&mut self) { - let is_resizable = self.controller.is_resizable(); - self.window.set_resizable(is_resizable); - // Hack to force window to be impossible to resize on DWM, where the - // 'is_resizable' window attribute isn't respected. - if !is_resizable { - self.window.set_min_inner_size(Some(self.window.inner_size())); - self.window.set_max_inner_size(Some(self.window.inner_size())); + /// 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 self.surface_dimensions == physical_dimensions { + return; } - } - - /// Resize the frame buffer to be the new size of the window. - pub fn resize(&mut self, dimensions: Dimensions) { - if self.dimensions == dimensions { return } - self.dimensions = dimensions; - self.buffer.resize(dimensions); - if let Some((width, height)) = dim_to_nonzero_size(dimensions) { + if let Some((width, height)) = dim_to_nonzero_size(physical_dimensions) { self.surface.resize(width, height).unwrap(); + self.surface_dimensions = physical_dimensions; }; - self.controller.on_resize(dimensions); + let pixel_scale = self.controller.pixel_scale().get(); + let logical_dimensions = physical_dimensions / pixel_scale; + self.buffer.resize(logical_dimensions); + self.controller.on_resize(logical_dimensions); + + self.pixel_scale = pixel_scale; self.current_render_hint = RenderHint::Redraw; } @@ -107,16 +134,57 @@ impl Window { } pub fn render(&mut self) { - let size = self.window.inner_size(); - let dim = Dimensions::new(size.width, size.height); - self.resize(dim); + // 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 pixel_scale = self.pixel_scale; + let physical_dim = self.surface_dimensions; + let logical_dim = self.buffer.dimensions(); self.controller.on_render(&mut self.buffer, self.current_render_hint); let buffer = self.buffer.as_u32_slice(); let mut surface = self.surface.buffer_mut().unwrap(); - let buffer_len = buffer.len(); - let surface_len = surface.len(); - surface[..buffer_len].copy_from_slice(&buffer[..surface_len]); + + if self.pixel_scale == 1 { + let overlap = std::cmp::min(buffer.len(), surface.len()); + surface[..overlap].copy_from_slice(&buffer[..overlap]); + } else { + let logical_content_width = logical_dim.width as usize; + let physical_content_width = (logical_dim.width * pixel_scale) as usize; + let row_excess = (physical_dim.width as usize).saturating_sub(physical_content_width); + let mut bi: usize = 0; + let mut si: usize = 0; + for _y in 0..logical_dim.height { + for _py in 0..pixel_scale { + for _x in 0..logical_dim.width { + for _px in 0..pixel_scale { + surface[si] = buffer[bi]; + si += 1; + } + bi += 1; + } + // Fill the excess space on the right edge with black. + for _ in 0..row_excess { + surface[si] = 0; + si += 1; + } + bi -= logical_content_width; + } + bi += logical_content_width; + } + // Fill the excess space on the bottom edge with black. + let excess = surface.len().saturating_sub(si); + for _ in 0..excess { + surface[si] = 0; + si += 1; + } + } surface.present().unwrap(); + // Reset current_render_hint back to the lowest variant for the next frame. self.current_render_hint = RenderHint::Update; } diff --git a/src/window_controller.rs b/src/window_controller.rs index 34f9fd4..78af0e1 100644 --- a/src/window_controller.rs +++ b/src/window_controller.rs @@ -1,17 +1,22 @@ use crate::*; +use std::num::NonZeroU32; +use std::path::Path; + +const NON_ZERO_ONE: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1) }; pub trait WindowController { fn title(&self) -> String { String::from("Phosphor") } - fn cursor_icon(&self) -> Option<CursorIcon> { None } - fn initial_size(&self) -> Dimensions { Dimensions::new(800,600) } + fn initial_size(&self) -> Option<Dimensions> { None } fn minimum_size(&self) -> Option<Dimensions> { None } fn maximum_size(&self) -> Option<Dimensions> { None } - fn pixel_scale(&self) -> u32 { 1 } - fn render_request(&self) -> RenderRequest { RenderRequest::None } + fn exact_size(&self) -> Option<Dimensions> { None } + fn pixel_scale(&self) -> NonZeroU32 { NON_ZERO_ONE } + + fn cursor_icon(&mut self) -> Option<CursorIcon> { None } + fn render_request(&mut self) -> RenderRequest { RenderRequest::None } fn is_visible(&self) -> bool { true } fn is_cursor_visible(&self) -> bool { true } - fn is_resizable(&self) -> bool { true } fn on_init(&mut self) {} fn on_resize(&mut self, _size: Dimensions) {} @@ -37,8 +42,8 @@ pub trait WindowController { fn on_keyboard_input(&mut self, _input: KeyboardInput) {} fn on_keyboard_modifier_change(&mut self, _modifiers: ModifiersState) {} - fn on_character_input(&mut self, _character: char) {} - fn on_file_hover(&mut self, _path: std::path::PathBuf) {} + fn on_character_input(&mut self, _input: char) {} + fn on_file_hover(&mut self, _path: &Path) {} fn on_file_hover_cancel(&mut self) {} - fn on_file_drop(&mut self, _path: std::path::PathBuf) {} + fn on_file_drop(&mut self, _path: &Path) {} } diff --git a/src/window_manager.rs b/src/window_manager.rs index 065d517..2652682 100644 --- a/src/window_manager.rs +++ b/src/window_manager.rs @@ -31,7 +31,7 @@ impl WindowManager { // Called when the event loop is first initialized. Event::NewEvents(StartCause::Init) => (), - Event::NewEvents(_) => { + Event::NewEvents(..) => { control_flow.set_wait_timeout(self.min_frame_duration); } @@ -46,7 +46,7 @@ impl WindowManager { use WindowEvent::*; match event { - Resized(dim) => window.resize(Dimensions::new(dim.width, dim.height)), + Resized(dim) => window.resize_buffer_and_surface(Dimensions::new(dim.width, dim.height)), Moved(position) => window.controller.on_move(Point::new(position.x, position.y)), Focused(is_focused) => window.controller.on_focus_change(is_focused), @@ -76,9 +76,9 @@ impl WindowManager { KeyboardInput { input, .. } => if let Ok(input) = input.try_into() { window.controller.on_keyboard_input(input)}, ModifiersChanged(state) => window.controller.on_keyboard_modifier_change(state), ReceivedCharacter(character) => window.controller.on_character_input(character), - HoveredFile(path) => window.controller.on_file_hover(path), + HoveredFile(path) => window.controller.on_file_hover(&path), HoveredFileCancelled => window.controller.on_file_hover_cancel(), - DroppedFile(path) => window.controller.on_file_drop(path), + DroppedFile(path) => window.controller.on_file_drop(&path), CloseRequested => { window.controller.on_close_request(); @@ -105,9 +105,7 @@ impl WindowManager { for window in self.windows.values_mut() { window.controller.on_process(); window.update_title(); - window.update_minimum_size(); - window.update_maximum_size(); - window.update_resizable(); + window.update_window_size(); window.update_cursor_icon(); window.update_cursor_visible(); window.handle_render_request(); |