use crate::*;
use buffer::HasDimensions;
use winit::dpi::{Size, PhysicalSize};
use winit::event_loop::EventLoopWindowTarget;
use winit::window::WindowId;
pub struct Window {
pub controller: Box<dyn WindowController>,
window: winit::window::Window,
pixel_scale: u32,
buffer: Buffer,
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 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,
current_render_hint: RenderHint::Redraw,
previous_cursor_position: None,
}
}
pub fn id(&self) -> WindowId {
self.window.id()
}
pub fn update_title(&mut self) {
self.window.set_title(&self.controller.title());
}
pub fn update_cursor_icon(&mut self) {
let icon = self.controller.cursor_icon().unwrap_or(CursorIcon::Default);
self.window.set_cursor_icon(icon);
}
/// 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);
if pixel_scale != self.pixel_scale {
let increment = PhysicalSize { width: pixel_scale, height: pixel_scale };
self.window.set_resize_increments(Some(increment));
self.resize_buffer_and_surface(self.surface_dimensions);
}
}
}
pub fn update_cursor_visible(&mut self) {
self.window.set_cursor_visible(self.controller.is_cursor_visible());
}
/// 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.current_render_hint = RenderHint::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.current_render_hint &= hint;
self.window.request_redraw();
} else if self.current_render_hint.is_redraw() {
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();
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();
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 current_render_hint back to the lowest variant for the next frame.
self.current_render_hint = RenderHint::Update;
}
}
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));
}