summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <bridle.benjamin@gmail.com>2023-11-05 14:17:49 +1300
committerBen Bridle <bridle.benjamin@gmail.com>2023-11-05 14:17:49 +1300
commitcd3769a48efcc3fdd2dc1304b1babfe6d26f788d (patch)
tree37c62c9ce10bd415326e4b1abac42e3a73d8d78f
parent8e08d723ff7a853f2b10dc0f1408911d5801cea8 (diff)
downloadphosphor-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.rs1
-rw-r--r--src/window.rs154
-rw-r--r--src/window_controller.rs21
-rw-r--r--src/window_manager.rs12
4 files changed, 130 insertions, 58 deletions
diff --git a/src/lib.rs b/src/lib.rs
index b610486..a73f3df 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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();