summaryrefslogblamecommitdiff
path: root/src/window.rs
blob: 70c9c02e2ac36064d2c429f4a366b03a3e377d7f (plain) (tree)
1
2
             
 



























                                                                              
                                   
                      
 
















                                                                                       
          
 
















                                                                                  
 
                                             
              







                                                          
                    
                                                 
                              

         










                                                                                        
                                                                           





                                                                                            
     

                                                            
     
 


                                                           
         
     
 











                                                                                     
 
                                
     





                                                                         
         
     













                                                              

         


















                                                                                              

         

                                                                               

                                              
 

                                                                
 
                                                   
                                                             
 

                                                                                   
                







                                                                                    
                                  














                                                                
                 
             
                                                      
         
 
                                   
     
 
 
 
 
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();
    }
}