use std::cell::RefCell; use std::ffi::{c_int, CString}; use std::hint::unreachable_unchecked; use std::io::{stdout, Write}; use std::panic::catch_unwind; use std::ptr::null_mut; use bitflags::bitflags; use crate::init::{initialize, Glfw}; use crate::util::enum_from_repr; 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. #[cfg(feature = "opengl")] 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. #[cfg(feature = "opengl")] pub fn get_proc_address( &self, proc_name: &std::ffi::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), ); } } pub fn set_key_callback( &self, callback: impl Fn(Key, i32, KeyState, KeyModifiers) + 'static, ) { KEY_CALLBACK.with_borrow_mut(|key_callback| { *key_callback = 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::glfwSetKeyCallback(self.handle, Some(key_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(); } } }) } /// Returns the last reported state of a mouse button. /// /// # Errors /// Will return `Err` if a GLFW error occurs. pub fn get_mouse_button( &self, mouse_button: MouseButton, ) -> 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::glfwGetMouseButton(self.handle, mouse_button.into_raw()) }; get_glfw_error()?; Ok(match state { crate::ffi::GLFW_PRESS => MouseButtonState::Pressed, crate::ffi::GLFW_RELEASE => MouseButtonState::Released, _ => { // SAFETY: glfwGetMouseButton can only return GLFW_PRESS or GLFW_RELEASE unsafe { unreachable_unchecked(); } } }) } /// Returns the position of the cursor, in screen coordinates, relative to the /// upper-left corner of the content area of the window. /// /// # Errors /// Will return `Err` if a GLFW error occurs. pub fn get_cursor_position(&self) -> Result { let mut x_pos = 0.0; let mut y_pos = 0.0; // SAFETY: The initialize function (called when the window is created) makes sure // the current thread is the main thread unsafe { crate::ffi::glfwGetCursorPos(self.handle, &mut x_pos, &mut y_pos); } get_glfw_error()?; Ok(CursorPosition { x: x_pos, y: y_pos }) } /// Sets a input mode option. /// /// # Errors /// Will return `Err` if a GLFW error occurs. pub fn set_input_mode( &self, input_mode: InputMode, enabled: bool, ) -> Result<(), Error> { // SAFETY: The initialize function (called when the window is created) makes sure // the current thread is the main thread unsafe { crate::ffi::glfwSetInputMode( self.handle, input_mode as i32, i32::from(enabled), ); } get_glfw_error()?; Ok(()) } /// Sets the cursor mode. /// /// # Errors /// Will return `Err` if a GLFW error occurs. pub fn set_cursor_mode(&self, cursor_mode: CursorMode) -> Result<(), Error> { // SAFETY: The initialize function (called when the window is created) makes sure // the current thread is the main thread unsafe { crate::ffi::glfwSetInputMode( self.handle, crate::ffi::GLFW_CURSOR, cursor_mode as i32, ); } get_glfw_error()?; Ok(()) } /// Returns whether or not raw mouse motion is supported. /// /// # Errors /// Will return `Err` if a GLFW error occurs. pub fn is_raw_mouse_motion_supported(&self) -> Result { // SAFETY: The initialize function (called when the window is created) makes sure // the current thread is the main thread let supported = unsafe { crate::ffi::glfwRawMouseMotionSupported() }; get_glfw_error()?; Ok(supported == crate::ffi::GLFW_TRUE) } } /// [`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. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Size { pub width: u32, pub height: u32, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(i32)] pub enum InputMode { /// When sticky keys mode is enabled, the pollable state of a key will remain /// [`KeyState::Pressed`] until the state of that key is polled with /// [`Window::get_key`]. Once it has been polled, if a key release event had been /// processed in the meantime, the state will reset to [`KeyState::Released`], /// otherwise it will remain [`KeyState::Pressed`]. StickyKeys = crate::ffi::GLFW_STICKY_KEYS, /// When sticky mouse buttons mode is enabled, the pollable state of a mouse button /// will remain [`MouseButtonState::Pressed`] until the state of that button is /// polled with [`Window::get_mouse_button`]. Once it has been polled, if a mouse /// button release event had been processed in the meantime, the state will reset /// to [`MouseButtonState::Released`], otherwise it will remain /// [`MouseButton::Pressed`]. StickyMouseButtons = crate::ffi::GLFW_STICKY_MOUSE_BUTTONS, LockKeyMods = crate::ffi::GLFW_LOCK_KEY_MODS, /// When the cursor is disabled, raw (unscaled and unaccelerated) mouse motion can be /// enabled if available. /// /// Raw mouse motion is closer to the actual motion of the mouse across a surface. It /// is not affected by the scaling and acceleration applied to the motion of the /// desktop cursor. That processing is suitable for a cursor while raw motion is /// better for controlling for example a 3D camera. Because of this, raw mouse motion /// is only provided when the cursor is disabled. RawMouseMotion = crate::ffi::GLFW_RAW_MOUSE_MOTION, } #[derive(Debug, Clone, Copy)] #[repr(i32)] pub enum CursorMode { /// Hides and grabs the cursor, providing virtual and unlimited cursor movement. This /// is useful for implementing for example 3D camera controls. Disabled = crate::ffi::GLFW_CURSOR_DISABLED, /// Makes the cursor invisible when it is over the content area of the window but /// does not restrict the cursor from leaving. Hidden = crate::ffi::GLFW_CURSOR_HIDDEN, /// Makes the cursor visible and behaving normally. Normal = crate::ffi::GLFW_CURSOR_NORMAL, } enum_from_repr!( #[repr(i32)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 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, } ); enum_from_repr!( #[repr(i32)] #[derive(Debug, Clone, Copy)] pub enum KeyState { Pressed = crate::ffi::GLFW_PRESS, Released = crate::ffi::GLFW_RELEASE, Repeat = crate::ffi::GLFW_REPEAT, } ); bitflags! { #[derive(Debug, Clone, Copy)] pub struct KeyModifiers: i32 { const SHIFT = crate::ffi::GLFW_MOD_SHIFT; const CONTROL = crate::ffi::GLFW_MOD_CONTROL; const ALT = crate::ffi::GLFW_MOD_ALT; const SUPER = crate::ffi::GLFW_MOD_SUPER; const CAPS_LOCK = crate::ffi::GLFW_MOD_CAPS_LOCK; const NUM_LOCK = crate::ffi::GLFW_MOD_NUM_LOCK; } } #[derive(Debug, Clone, Copy)] pub enum MouseButton { One, Two, Three, Four, Five, Six, Seven, Eight, } impl MouseButton { pub const LEFT: Self = Self::One; pub const MIDDLE: Self = Self::Three; pub const RIGHT: Self = Self::Two; fn into_raw(self) -> i32 { match self { Self::One => crate::ffi::GLFW_MOUSE_BUTTON_1, Self::Two => crate::ffi::GLFW_MOUSE_BUTTON_2, Self::Three => crate::ffi::GLFW_MOUSE_BUTTON_3, Self::Four => crate::ffi::GLFW_MOUSE_BUTTON_4, Self::Five => crate::ffi::GLFW_MOUSE_BUTTON_5, Self::Six => crate::ffi::GLFW_MOUSE_BUTTON_6, Self::Seven => crate::ffi::GLFW_MOUSE_BUTTON_7, Self::Eight => crate::ffi::GLFW_MOUSE_BUTTON_8, } } } #[derive(Debug, Clone, Copy)] pub enum MouseButtonState { Pressed, Released, } #[derive(Debug, Clone)] pub struct CursorPosition { pub x: f64, pub y: f64, } type FramebufferSizeCb = Box; type KeyCallback = Box; thread_local! { static FRAMEBUFFER_SIZE_CB: RefCell> = RefCell::new(None); static KEY_CALLBACK: 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"); } } extern "C" fn key_callback( _window: *mut crate::ffi::GLFWwindow, key_raw: c_int, scancode: c_int, action_raw: c_int, mods: c_int, ) { let Some(key) = Key::from_repr(key_raw) else { write!(stdout(), "Unknown key {key_raw}").ok(); return; }; let Some(key_state) = KeyState::from_repr(action_raw) else { write!(stdout(), "Unknown key state {action_raw}").ok(); return; }; let Some(key_modifiers) = KeyModifiers::from_bits(mods) else { write!( stdout(), "Key modifiers {action_raw} contain one or more unknown bit(s)" ) .ok(); return; }; // Unwinds are catched because unwinding from Rust code into foreign code is UB. let res = catch_unwind(|| { KEY_CALLBACK .try_with(|key_callback| { if let Some(cb) = key_callback.borrow().as_deref() { cb(key, scancode, key_state, key_modifiers); } }) .ok(); }); if res.is_err() { write!(stdout(), "ERROR: Panic in key callback").ok(); } }