summaryrefslogtreecommitdiff
path: root/src/window.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/window.rs')
-rw-r--r--src/window.rs397
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));
-}
+