use std::borrow::Cow; use std::ffi::{CStr, CString}; use bitflags::bitflags; use ecs::actions::Actions; use ecs::extension::Collector as ExtensionCollector; use ecs::sole::Single; use ecs::Sole; use glfw::window::{Hint as WindowCreationHint, HintValue as WindowCreationHintValue}; use glfw::WindowSize; use util_macros::VariantArr; use crate::data_types::dimens::Dimens; use crate::event::{Conclude as ConcludeEvent, Start as StartEvent}; use crate::vector::Vec2; #[derive(Debug, Sole)] /// Has to be dropped last since it holds the OpenGL context. #[sole(drop_last)] pub struct Window { inner: glfw::Window, } impl Window { /// Returns a new Window builder. #[must_use] pub fn builder() -> Builder { Builder::default() } /// Sets the value of a input mode. /// /// # Errors /// Returns `Err` if the input mode is unsupported on the current system. pub fn set_input_mode( &self, input_mode: InputMode, enabled: bool, ) -> Result<(), Error> { Ok(self .inner .set_input_mode(input_mode.to_glfw_input_mode(), enabled)?) } /// Sets the cursor mode. /// /// # Errors /// If a platform error occurs. pub fn set_cursor_mode(&self, cursor_mode: CursorMode) -> Result<(), Error> { Ok(self .inner .set_cursor_mode(cursor_mode.to_glfw_cursor_mode())?) } /// Returns whether or not the window should close. Will return true when the user has /// attempted to close the window. #[must_use] pub fn should_close(&self) -> bool { self.inner.should_close() } /// Processes all pending events. /// /// # Errors /// If a platform error occurs. pub fn poll_events(&self) -> Result<(), Error> { Ok(self.inner.poll_events()?) } /// Swaps the front and back buffers of the window. /// /// # Errors /// Will return `Err` if a platform error occurs or if no OpenGL window context /// is present. pub fn swap_buffers(&self) -> Result<(), Error> { Ok(self.inner.swap_buffers()?) } /// Returns the size of the window. /// /// # Errors /// Will return `Err` if a platform error occurs. pub fn size(&self) -> Result, Error> { let size = self.inner.size()?; Ok(Dimens { width: size.width, height: size.height, }) } /// Returns the address of the specified OpenGL function, if it is supported by the /// current OpenGL context. /// /// # Errors /// Will return `Err` if a platform error occurs or if no current context has /// been set. /// /// # Panics /// Will panic if the `proc_name` argument contains a nul byte. pub fn get_proc_address( &self, proc_name: &str, ) -> Result { let proc_name_c: Cow = CStr::from_bytes_with_nul(proc_name.as_bytes()) .map(Cow::Borrowed) .or_else(|_| CString::new(proc_name).map(Cow::Owned)) .expect("OpenGL function name contains a nul byte"); Ok(self.inner.get_proc_address(&proc_name_c)?) } /// Makes the OpenGL context of the window current for the calling thread. /// /// # Errors /// Will return `Err` if a platform error occurs or if no OpenGL context is /// present. pub fn make_context_current(&self) -> Result<(), Error> { Ok(self.inner.make_context_current()?) } /// Sets the window's framebuffer size callback. pub fn set_framebuffer_size_callback(&self, callback: impl Fn(Dimens) + 'static) { self.inner.set_framebuffer_size_callback(move |size| { callback(Dimens { width: size.width, height: size.height, }); }); } /// Sets the window's key size callback. pub fn set_key_callback( &self, callback: impl Fn(Key, i32, KeyState, KeyModifiers) + 'static, ) { self.inner .set_key_callback(move |key, scancode, key_state, key_modifiers| { let Some(key_state) = KeyState::from_glfw_key_state(key_state) else { return; }; callback( Key::from_glfw_key(key), scancode, key_state, KeyModifiers::from_bits_truncate(key_modifiers.bits()), ) }); } /// Sets the window's cursor position callback. pub fn set_cursor_pos_callback(&self, callback: impl Fn(Vec2) + 'static) { self.inner .set_cursor_pos_callback(move |pos| callback(Vec2 { x: pos.x, y: pos.y })); } /// Sets the window's close callback. pub fn set_close_callback(&self, callback: impl Fn() + 'static) { self.inner.set_close_callback(callback); } /// Sets the window's focus callback. The callback is called when the window loses or /// gains input focus. pub fn set_focus_callback(&self, callback: impl Fn(bool) + 'static) { self.inner.set_focus_callback(callback); } } /// [`Window`] builder. #[derive(Debug, Clone, Default)] pub struct Builder { inner: glfw::WindowBuilder, } impl Builder { /// Sets whether the OpenGL context should be created in debug mode, which may /// provide additional error and diagnostic reporting functionality. pub fn opengl_debug_context(mut self, enabled: bool) -> Self { self.inner = self.inner.hint( WindowCreationHint::OpenGLDebugContext, WindowCreationHintValue::Bool(enabled), ); self } /// Set the desired number of samples to use for multisampling. Zero disables /// multisampling. pub fn multisampling_sample_count(mut self, sample_count: u16) -> Self { self.inner = self.inner.hint( WindowCreationHint::Samples, WindowCreationHintValue::Number(sample_count as i32), ); self } /// Creates a new window. /// /// # Errors /// Will return `Err` if the title contains a internal nul byte or if a platform error /// occurs. pub fn create(&self, size: Dimens, title: &str) -> Result { let builder = self.inner.clone().hint( WindowCreationHint::OpenGLDebugContext, WindowCreationHintValue::Bool(true), ); let window = builder.create( &WindowSize { width: size.width, height: size.height, }, title, )?; Ok(Window { inner: window }) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, VariantArr)] #[variant_arr(name = KEYS)] pub enum Key { Space, Apostrophe, Comma, Minus, Period, Slash, Digit0, Digit1, Digit2, Digit3, Digit4, Digit5, Digit6, Digit7, Digit8, Digit9, Semicolon, Equal, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, LeftBracket, Backslash, RightBracket, GraveAccent, World1, World2, Escape, Enter, Tab, Backspace, Insert, Delete, Right, Left, Down, Up, PageUp, PageDown, Home, End, CapsLock, ScrollLock, NumLock, PrintScreen, Pause, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, Kp0, Kp1, Kp2, Kp3, Kp4, Kp5, Kp6, Kp7, Kp8, Kp9, KpDecimal, KpDivide, KpMultiply, KpSubtract, KpAdd, KpEnter, KpEqual, LeftShift, LeftControl, LeftAlt, LeftSuper, RightShift, RightControl, RightAlt, RightSuper, Menu, } impl Key { fn from_glfw_key(glfw_key: glfw::window::Key) -> Self { match glfw_key { glfw::window::Key::Space => Self::Space, glfw::window::Key::Apostrophe => Self::Apostrophe, glfw::window::Key::Comma => Self::Comma, glfw::window::Key::Minus => Self::Minus, glfw::window::Key::Period => Self::Period, glfw::window::Key::Slash => Self::Slash, glfw::window::Key::Digit0 => Self::Digit0, glfw::window::Key::Digit1 => Self::Digit1, glfw::window::Key::Digit2 => Self::Digit2, glfw::window::Key::Digit3 => Self::Digit3, glfw::window::Key::Digit4 => Self::Digit4, glfw::window::Key::Digit5 => Self::Digit5, glfw::window::Key::Digit6 => Self::Digit6, glfw::window::Key::Digit7 => Self::Digit7, glfw::window::Key::Digit8 => Self::Digit8, glfw::window::Key::Digit9 => Self::Digit9, glfw::window::Key::Semicolon => Self::Semicolon, glfw::window::Key::Equal => Self::Equal, glfw::window::Key::A => Self::A, glfw::window::Key::B => Self::B, glfw::window::Key::C => Self::C, glfw::window::Key::D => Self::D, glfw::window::Key::E => Self::E, glfw::window::Key::F => Self::F, glfw::window::Key::G => Self::G, glfw::window::Key::H => Self::H, glfw::window::Key::I => Self::I, glfw::window::Key::J => Self::J, glfw::window::Key::K => Self::K, glfw::window::Key::L => Self::L, glfw::window::Key::M => Self::M, glfw::window::Key::N => Self::N, glfw::window::Key::O => Self::O, glfw::window::Key::P => Self::P, glfw::window::Key::Q => Self::Q, glfw::window::Key::R => Self::R, glfw::window::Key::S => Self::S, glfw::window::Key::T => Self::T, glfw::window::Key::U => Self::U, glfw::window::Key::V => Self::V, glfw::window::Key::W => Self::W, glfw::window::Key::X => Self::X, glfw::window::Key::Y => Self::Y, glfw::window::Key::Z => Self::Z, glfw::window::Key::LeftBracket => Self::LeftBracket, glfw::window::Key::Backslash => Self::Backslash, glfw::window::Key::RightBracket => Self::RightBracket, glfw::window::Key::GraveAccent => Self::GraveAccent, glfw::window::Key::World1 => Self::World1, glfw::window::Key::World2 => Self::World2, glfw::window::Key::Escape => Self::Escape, glfw::window::Key::Enter => Self::Enter, glfw::window::Key::Tab => Self::Tab, glfw::window::Key::Backspace => Self::Backspace, glfw::window::Key::Insert => Self::Insert, glfw::window::Key::Delete => Self::Delete, glfw::window::Key::Right => Self::Right, glfw::window::Key::Left => Self::Left, glfw::window::Key::Down => Self::Down, glfw::window::Key::Up => Self::Up, glfw::window::Key::PageUp => Self::PageUp, glfw::window::Key::PageDown => Self::PageDown, glfw::window::Key::Home => Self::Home, glfw::window::Key::End => Self::End, glfw::window::Key::CapsLock => Self::CapsLock, glfw::window::Key::ScrollLock => Self::ScrollLock, glfw::window::Key::NumLock => Self::NumLock, glfw::window::Key::PrintScreen => Self::PrintScreen, glfw::window::Key::Pause => Self::Pause, glfw::window::Key::F1 => Self::F1, glfw::window::Key::F2 => Self::F2, glfw::window::Key::F3 => Self::F3, glfw::window::Key::F4 => Self::F4, glfw::window::Key::F5 => Self::F5, glfw::window::Key::F6 => Self::F6, glfw::window::Key::F7 => Self::F7, glfw::window::Key::F8 => Self::F8, glfw::window::Key::F9 => Self::F9, glfw::window::Key::F10 => Self::F10, glfw::window::Key::F11 => Self::F11, glfw::window::Key::F12 => Self::F12, glfw::window::Key::F13 => Self::F13, glfw::window::Key::F14 => Self::F14, glfw::window::Key::F15 => Self::F15, glfw::window::Key::F16 => Self::F16, glfw::window::Key::F17 => Self::F17, glfw::window::Key::F18 => Self::F18, glfw::window::Key::F19 => Self::F19, glfw::window::Key::F20 => Self::F20, glfw::window::Key::F21 => Self::F21, glfw::window::Key::F22 => Self::F22, glfw::window::Key::F23 => Self::F23, glfw::window::Key::F24 => Self::F24, glfw::window::Key::F25 => Self::F25, glfw::window::Key::Kp0 => Self::Kp0, glfw::window::Key::Kp1 => Self::Kp1, glfw::window::Key::Kp2 => Self::Kp2, glfw::window::Key::Kp3 => Self::Kp3, glfw::window::Key::Kp4 => Self::Kp4, glfw::window::Key::Kp5 => Self::Kp5, glfw::window::Key::Kp6 => Self::Kp6, glfw::window::Key::Kp7 => Self::Kp7, glfw::window::Key::Kp8 => Self::Kp8, glfw::window::Key::Kp9 => Self::Kp9, glfw::window::Key::KpDecimal => Self::KpDecimal, glfw::window::Key::KpDivide => Self::KpDivide, glfw::window::Key::KpMultiply => Self::KpMultiply, glfw::window::Key::KpSubtract => Self::KpSubtract, glfw::window::Key::KpAdd => Self::KpAdd, glfw::window::Key::KpEnter => Self::KpEnter, glfw::window::Key::KpEqual => Self::KpEqual, glfw::window::Key::LeftShift => Self::LeftShift, glfw::window::Key::LeftControl => Self::LeftControl, glfw::window::Key::LeftAlt => Self::LeftAlt, glfw::window::Key::LeftSuper => Self::LeftSuper, glfw::window::Key::RightShift => Self::RightShift, glfw::window::Key::RightControl => Self::RightControl, glfw::window::Key::RightAlt => Self::RightAlt, glfw::window::Key::RightSuper => Self::RightSuper, glfw::window::Key::Menu => Self::Menu, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum KeyState { Pressed, Released, } impl KeyState { fn from_glfw_key_state(glfw_key_state: glfw::window::KeyState) -> Option { match glfw_key_state { glfw::window::KeyState::Pressed => Some(Self::Pressed), glfw::window::KeyState::Released => Some(Self::Released), glfw::window::KeyState::Repeat => None, } } } bitflags! { #[derive(Debug, Clone, Copy)] pub struct KeyModifiers: i32 { const SHIFT = glfw::window::KeyModifiers::SHIFT.bits(); const CONTROL = glfw::window::KeyModifiers::CONTROL.bits(); const ALT = glfw::window::KeyModifiers::ALT.bits(); const SUPER = glfw::window::KeyModifiers::SUPER.bits(); const CAPS_LOCK = glfw::window::KeyModifiers::CAPS_LOCK.bits(); const NUM_LOCK = glfw::window::KeyModifiers::NUM_LOCK.bits(); } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum CursorMode { /// Hides and grabs the cursor, providing virtual and unlimited cursor movement. Disabled, /// Makes the cursor invisible when it is over the content area of the window but /// does not restrict the cursor from leaving. Hidden, /// Makes the cursor visible and behaving normally. Normal, } impl CursorMode { fn to_glfw_cursor_mode(self) -> glfw::window::CursorMode { match self { Self::Disabled => glfw::window::CursorMode::Disabled, Self::Hidden => glfw::window::CursorMode::Hidden, Self::Normal => glfw::window::CursorMode::Normal, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum InputMode { /// 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, } impl InputMode { fn to_glfw_input_mode(self) -> glfw::window::InputMode { match self { Self::RawMouseMotion => glfw::window::InputMode::RawMouseMotion, } } } #[derive(Debug)] pub struct Extension { window_builder: Builder, window_size: Dimens, window_title: String, } impl Extension { #[must_use] pub fn new(window_builder: Builder) -> Self { Self { window_builder, ..Default::default() } } #[must_use] pub fn window_size(mut self, window_size: Dimens) -> Self { self.window_size = window_size; self } #[must_use] pub fn window_title(mut self, window_title: impl Into) -> Self { self.window_title = window_title.into(); self } } impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ExtensionCollector<'_>) { collector.add_system(StartEvent, initialize); collector.add_system(ConcludeEvent, update); let window = self .window_builder .create(self.window_size, &self.window_title) .unwrap(); window.set_cursor_mode(CursorMode::Normal).unwrap(); collector.add_sole(window).ok(); } } impl Default for Extension { fn default() -> Self { Self { window_builder: Builder::default(), window_size: Dimens { width: 1920, height: 1080 }, window_title: String::new(), } } } #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct Error(glfw::Error); impl From for Error { fn from(err: glfw::Error) -> Self { Self(err) } } fn initialize(window: Single, actions: Actions) { let actions_weak_ref = actions.to_weak_ref(); window.set_close_callback(move || { let actions_weak_ref = actions_weak_ref.clone(); let actions_ref = actions_weak_ref.access().expect("No world"); actions_ref.to_actions().stop(); }); } fn update(window: Single) { window .swap_buffers() .expect("Failed to swap window buffers"); window.poll_events().expect("Failed to poll window events"); }