use std::borrow::Cow; use std::ffi::{CStr, CString}; use ecs::actions::Actions; use ecs::extension::Collector as ExtensionCollector; use ecs::sole::Single; use ecs::Sole; use glfw::WindowSize; use crate::data_types::dimens::Dimens; use crate::event::{Conclude as ConcludeEvent, Start as StartEvent}; use crate::vector::Vec2; mod reexports { pub use glfw::window::{ CursorMode, Hint as CreationHint, HintValue as CreationHintValue, InputMode, Key, KeyModifiers, KeyState, }; } pub use reexports::*; #[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, 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)?) } /// 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(callback); } /// 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 { #[must_use] pub fn creation_hint(mut self, hint: CreationHint, value: CreationHintValue) -> Self { self.inner = self.inner.hint(hint, value); 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( CreationHint::OpenGLDebugContext, CreationHintValue::Bool(cfg!(feature = "debug")), ); let window = builder.create( &WindowSize { width: size.width, height: size.height, }, title, )?; Ok(Window { inner: window }) } } #[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"); }