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<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,
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<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::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<CursorIcon>) {
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();
}
}