From 101b455e51f9b702da5517cabe2c3b1086fcb2e7 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 14 Apr 2024 12:34:52 +0200 Subject: feat(engine): use ECS architecture --- engine/src/window.rs | 312 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 engine/src/window.rs (limited to 'engine/src/window.rs') diff --git a/engine/src/window.rs b/engine/src/window.rs new file mode 100644 index 0000000..fde2cca --- /dev/null +++ b/engine/src/window.rs @@ -0,0 +1,312 @@ +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::{PostPresent as PostPresentEvent, Start as StartEvent}; +use crate::vector::Vec2; + +mod reexports +{ + pub use glfw::window::{ + CursorMode, + Hint as CreationHint, + 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); + } +} + +/// [`Window`] builder. +#[derive(Debug, Clone, Default)] +pub struct Builder +{ + inner: glfw::WindowBuilder, +} + +impl Builder +{ + #[must_use] + pub fn creation_hint(mut self, creation_hint: CreationHint, value: i32) -> Self + { + self.inner = self.inner.hint( + match creation_hint { + CreationHint::OpenGLDebugContext => { + glfw::window::Hint::OpenGLDebugContext + } + }, + 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 window = self.inner.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(PostPresentEvent, 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"); +} -- cgit v1.2.3-18-g5258