use std::cell::RefCell; use std::ffi::{c_int, CStr, CString}; use std::panic::catch_unwind; use std::ptr::null_mut; use crate::init::{initialize, Glfw}; use crate::{get_glfw_error, Error}; pub struct Window { _init: Glfw, handle: *mut crate::ffi::GLFWwindow, } impl Window { /// Creates a new window. /// /// # Errors /// Will return `Err` if /// - The title contains an internal nul byte /// - A GLFW error occurs pub fn create(size: &Size, title: &str) -> Result { let init = initialize()?; let c_title = CString::new(title).map_err(|_| Error::InternalNulByteInWindowTitle)?; // SAFETY: The initialize function makes sure the current thread is the main // thread let handle = unsafe { #[allow(clippy::cast_possible_wrap)] crate::ffi::glfwCreateWindow( size.width as i32, size.height as i32, c_title.as_ptr(), null_mut(), null_mut(), ) }; get_glfw_error()?; Ok(Self { _init: init, handle, }) } /// Makes the context of the window current for the calling thread. /// /// # Errors /// Will return `Err` if a GLFW platform error occurs or if no OpenGL context is /// present. pub fn make_context_current(&self) -> Result<(), Error> { unsafe { crate::ffi::glfwMakeContextCurrent(self.handle) }; get_glfw_error()?; Ok(()) } /// Returns the address of the specified OpenGL function, if it is supported by the /// current context. /// /// # Errors /// Will return `Err` if a GLFW platform error occurs or if no current context has /// been set. pub fn get_proc_address( &self, proc_name: &CStr, ) -> Result { let proc_addr = unsafe { crate::ffi::glfwGetProcAddress(proc_name.as_ptr()) }; get_glfw_error()?; // SAFETY: Is only None when a error has occured and that case is handled above Ok(unsafe { proc_addr.unwrap_unchecked() }) } /// Processes all pending events. /// /// # Errors /// Will return `Err` if a GLFW platform error occurs. pub fn poll_events(&self) -> Result<(), Error> { // SAFETY: The initialize function (called when the window is created) makes sure // the current thread is the main thread unsafe { crate::ffi::glfwPollEvents() }; get_glfw_error()?; Ok(()) } /// Swaps the front and back buffers of the window. /// /// # Errors /// Will return `Err` if a GLFW platform error occurs or if no OpenGL window context /// is present. pub fn swap_buffers(&self) -> Result<(), Error> { unsafe { crate::ffi::glfwSwapBuffers(self.handle); }; get_glfw_error()?; Ok(()) } /// Returns whether or not the window should close. #[must_use] pub fn should_close(&self) -> bool { let should_close = unsafe { crate::ffi::glfwWindowShouldClose(self.handle) }; should_close == crate::ffi::GLFW_TRUE } /// Retrieves the size of the window. /// /// # Errors /// Will return `Err` if a GLFW platform error occurs. pub fn size(&self) -> Result { let mut width = 0; let mut height = 0; // SAFETY: The initialize function (called when the window is created) makes sure // the current thread is the main thread unsafe { crate::ffi::glfwGetWindowSize(self.handle, &mut width, &mut height) }; get_glfw_error()?; #[allow(clippy::cast_sign_loss)] Ok(Size { width: width as u32, height: height as u32, }) } pub fn set_framebuffer_size_callback(&self, callback: impl Fn(Size) + 'static) { FRAMEBUFFER_SIZE_CB.with_borrow_mut(|framebuffer_size_cb| { *framebuffer_size_cb = Some(Box::new(callback)); }); // SAFETY: The initialize function (called when the window is created) makes sure // the current thread is the main thread unsafe { crate::ffi::glfwSetFramebufferSizeCallback( self.handle, Some(framebuffer_size_callback), ); } } } /// Window size. pub struct Size { pub width: u32, pub height: u32, } type FramebufferSizeCb = Box; thread_local! { static FRAMEBUFFER_SIZE_CB: RefCell> = RefCell::new(None); } extern "C" fn framebuffer_size_callback( _window: *mut crate::ffi::GLFWwindow, c_width: c_int, c_height: c_int, ) { // Width and height can't possibly have their sign bit set let width = u32::from_le_bytes(c_width.to_le_bytes()); let height = u32::from_le_bytes(c_height.to_le_bytes()); // Unwinds are catched because unwinding from Rust code into foreign code is UB. let res = catch_unwind(|| { FRAMEBUFFER_SIZE_CB .try_with(|framebuffer_size_cb| { if let Some(cb) = framebuffer_size_cb.borrow().as_deref() { cb(Size { width, height }); } }) .ok(); }); if res.is_err() { println!("ERROR: Panic in framebuffer size callback"); } }