use std::cell::RefCell; use std::ffi::{c_int, CStr, CString}; use std::hint::unreachable_unchecked; use std::panic::catch_unwind; use std::ptr::null_mut; use crate::init::{initialize, Glfw}; use crate::{get_glfw_error, Error}; #[derive(Debug)] pub struct Window { _init: Glfw, handle: *mut crate::ffi::GLFWwindow, } impl Window { /// 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), ); } } /// Returns the last reported state of a keyboard key. /// /// # Errors /// Will return `Err` if a GLFW error occurs. pub fn get_key(&self, key: Key) -> Result { // SAFETY: The initialize function (called when the window is created) makes sure // the current thread is the main thread let state = unsafe { crate::ffi::glfwGetKey(self.handle, key as i32) }; get_glfw_error()?; Ok(match state { crate::ffi::GLFW_PRESS => KeyState::Pressed, crate::ffi::GLFW_RELEASE => KeyState::Released, _ => { // SAFETY: glfwGetKey can only return GLFW_PRESS or GLFW_RELEASE unsafe { unreachable_unchecked(); } } }) } } /// [`Window`] builder. #[derive(Debug, Clone, Default)] pub struct Builder { hints: Vec<(Hint, i32)>, } impl Builder { #[must_use] pub fn new() -> Self { Self { hints: Vec::new() } } /// Adds a window creation hint to set. #[must_use] pub fn hint(mut self, hint: Hint, value: i32) -> Self { self.hints.push((hint, value)); self } /// Sets the window hints to set. pub fn hints(mut self, hints: impl IntoIterator) { self.hints = hints.into_iter().collect(); } /// Creates a new window. /// /// # Errors /// Will return `Err` if /// - The title contains an internal nul byte /// - A GLFW error occurs pub fn create(&self, size: &Size, title: &str) -> Result { let c_title = CString::new(title).map_err(|_| Error::InternalNulByteInWindowTitle)?; let init = initialize()?; for (hint, value) in &self.hints { // SAFETY: The initialize function makes sure the current thread is the main // thread // // Error is not checked for after since the two possible errors // (GLFW_NOT_INITIALIZED and GLFW_INVALID_ENUM) cannot occur. unsafe { crate::ffi::glfwWindowHint(hint.to_glfw(), *value); } } // 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(Window { _init: init, handle, }) } } /// Window creation hint #[derive(Debug, Clone, Copy)] pub enum Hint { OpenGLDebugContext, } impl Hint { fn to_glfw(self) -> c_int { match self { Self::OpenGLDebugContext => crate::ffi::GLFW_OPENGL_DEBUG_CONTEXT, } } } /// Window size. pub struct Size { pub width: u32, pub height: u32, } #[derive(Debug, Clone, Copy)] #[repr(i32)] pub enum Key { // Unknown = crate::ffi::GLFW_KEY_UNKNOWN, Space = crate::ffi::GLFW_KEY_SPACE, Apostrophe = crate::ffi::GLFW_KEY_APOSTROPHE, Comma = crate::ffi::GLFW_KEY_COMMA, Minus = crate::ffi::GLFW_KEY_MINUS, Period = crate::ffi::GLFW_KEY_PERIOD, Slash = crate::ffi::GLFW_KEY_SLASH, Digit0 = crate::ffi::GLFW_KEY_0, Digit1 = crate::ffi::GLFW_KEY_1, Digit2 = crate::ffi::GLFW_KEY_2, Digit3 = crate::ffi::GLFW_KEY_3, Digit4 = crate::ffi::GLFW_KEY_4, Digit5 = crate::ffi::GLFW_KEY_5, Digit6 = crate::ffi::GLFW_KEY_6, Digit7 = crate::ffi::GLFW_KEY_7, Digit8 = crate::ffi::GLFW_KEY_8, Digit9 = crate::ffi::GLFW_KEY_9, Semicolon = crate::ffi::GLFW_KEY_SEMICOLON, Equal = crate::ffi::GLFW_KEY_EQUAL, A = crate::ffi::GLFW_KEY_A, B = crate::ffi::GLFW_KEY_B, C = crate::ffi::GLFW_KEY_C, D = crate::ffi::GLFW_KEY_D, E = crate::ffi::GLFW_KEY_E, F = crate::ffi::GLFW_KEY_F, G = crate::ffi::GLFW_KEY_G, H = crate::ffi::GLFW_KEY_H, I = crate::ffi::GLFW_KEY_I, J = crate::ffi::GLFW_KEY_J, K = crate::ffi::GLFW_KEY_K, L = crate::ffi::GLFW_KEY_L, M = crate::ffi::GLFW_KEY_M, N = crate::ffi::GLFW_KEY_N, O = crate::ffi::GLFW_KEY_O, P = crate::ffi::GLFW_KEY_P, Q = crate::ffi::GLFW_KEY_Q, R = crate::ffi::GLFW_KEY_R, S = crate::ffi::GLFW_KEY_S, T = crate::ffi::GLFW_KEY_T, U = crate::ffi::GLFW_KEY_U, V = crate::ffi::GLFW_KEY_V, W = crate::ffi::GLFW_KEY_W, X = crate::ffi::GLFW_KEY_X, Y = crate::ffi::GLFW_KEY_Y, Z = crate::ffi::GLFW_KEY_Z, LeftBracket = crate::ffi::GLFW_KEY_LEFT_BRACKET, Backslash = crate::ffi::GLFW_KEY_BACKSLASH, RightBracket = crate::ffi::GLFW_KEY_RIGHT_BRACKET, GraveAccent = crate::ffi::GLFW_KEY_GRAVE_ACCENT, World1 = crate::ffi::GLFW_KEY_WORLD_1, World2 = crate::ffi::GLFW_KEY_WORLD_2, Escape = crate::ffi::GLFW_KEY_ESCAPE, Enter = crate::ffi::GLFW_KEY_ENTER, Tab = crate::ffi::GLFW_KEY_TAB, Backspace = crate::ffi::GLFW_KEY_BACKSPACE, Insert = crate::ffi::GLFW_KEY_INSERT, Delete = crate::ffi::GLFW_KEY_DELETE, Right = crate::ffi::GLFW_KEY_RIGHT, Left = crate::ffi::GLFW_KEY_LEFT, Down = crate::ffi::GLFW_KEY_DOWN, Up = crate::ffi::GLFW_KEY_UP, PageUp = crate::ffi::GLFW_KEY_PAGE_UP, PageDown = crate::ffi::GLFW_KEY_PAGE_DOWN, Home = crate::ffi::GLFW_KEY_HOME, End = crate::ffi::GLFW_KEY_END, CapsLock = crate::ffi::GLFW_KEY_CAPS_LOCK, ScrollLock = crate::ffi::GLFW_KEY_SCROLL_LOCK, NumLock = crate::ffi::GLFW_KEY_NUM_LOCK, PrintScreen = crate::ffi::GLFW_KEY_PRINT_SCREEN, Pause = crate::ffi::GLFW_KEY_PAUSE, F1 = crate::ffi::GLFW_KEY_F1, F2 = crate::ffi::GLFW_KEY_F2, F3 = crate::ffi::GLFW_KEY_F3, F4 = crate::ffi::GLFW_KEY_F4, F5 = crate::ffi::GLFW_KEY_F5, F6 = crate::ffi::GLFW_KEY_F6, F7 = crate::ffi::GLFW_KEY_F7, F8 = crate::ffi::GLFW_KEY_F8, F9 = crate::ffi::GLFW_KEY_F9, F10 = crate::ffi::GLFW_KEY_F10, F11 = crate::ffi::GLFW_KEY_F11, F12 = crate::ffi::GLFW_KEY_F12, F13 = crate::ffi::GLFW_KEY_F13, F14 = crate::ffi::GLFW_KEY_F14, F15 = crate::ffi::GLFW_KEY_F15, F16 = crate::ffi::GLFW_KEY_F16, F17 = crate::ffi::GLFW_KEY_F17, F18 = crate::ffi::GLFW_KEY_F18, F19 = crate::ffi::GLFW_KEY_F19, F20 = crate::ffi::GLFW_KEY_F20, F21 = crate::ffi::GLFW_KEY_F21, F22 = crate::ffi::GLFW_KEY_F22, F23 = crate::ffi::GLFW_KEY_F23, F24 = crate::ffi::GLFW_KEY_F24, F25 = crate::ffi::GLFW_KEY_F25, Kp0 = crate::ffi::GLFW_KEY_KP_0, Kp1 = crate::ffi::GLFW_KEY_KP_1, Kp2 = crate::ffi::GLFW_KEY_KP_2, Kp3 = crate::ffi::GLFW_KEY_KP_3, Kp4 = crate::ffi::GLFW_KEY_KP_4, Kp5 = crate::ffi::GLFW_KEY_KP_5, Kp6 = crate::ffi::GLFW_KEY_KP_6, Kp7 = crate::ffi::GLFW_KEY_KP_7, Kp8 = crate::ffi::GLFW_KEY_KP_8, Kp9 = crate::ffi::GLFW_KEY_KP_9, KpDecimal = crate::ffi::GLFW_KEY_KP_DECIMAL, KpDivide = crate::ffi::GLFW_KEY_KP_DIVIDE, KpMultiply = crate::ffi::GLFW_KEY_KP_MULTIPLY, KpSubtract = crate::ffi::GLFW_KEY_KP_SUBTRACT, KpAdd = crate::ffi::GLFW_KEY_KP_ADD, KpEnter = crate::ffi::GLFW_KEY_KP_ENTER, KpEqual = crate::ffi::GLFW_KEY_KP_EQUAL, LeftShift = crate::ffi::GLFW_KEY_LEFT_SHIFT, LeftControl = crate::ffi::GLFW_KEY_LEFT_CONTROL, LeftAlt = crate::ffi::GLFW_KEY_LEFT_ALT, LeftSuper = crate::ffi::GLFW_KEY_LEFT_SUPER, RightShift = crate::ffi::GLFW_KEY_RIGHT_SHIFT, RightControl = crate::ffi::GLFW_KEY_RIGHT_CONTROL, RightAlt = crate::ffi::GLFW_KEY_RIGHT_ALT, RightSuper = crate::ffi::GLFW_KEY_RIGHT_SUPER, Menu = crate::ffi::GLFW_KEY_MENU, } #[derive(Debug, Clone, Copy)] pub enum KeyState { Pressed, Released, } 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"); } }