summaryrefslogtreecommitdiff
path: root/engine/src/window.rs
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/window.rs')
-rw-r--r--engine/src/window.rs312
1 files changed, 312 insertions, 0 deletions
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<Dimens<u32>, 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<unsafe extern "C" fn(), Error>
+ {
+ let proc_name_c: Cow<CStr> = 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<u32>) + '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<f64>) + '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<u32>, title: &str) -> Result<Window, Error>
+ {
+ 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<u32>,
+ 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<u32>) -> Self
+ {
+ self.window_size = window_size;
+
+ self
+ }
+
+ #[must_use]
+ pub fn window_title(mut self, window_title: impl Into<String>) -> 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<glfw::Error> for Error
+{
+ fn from(err: glfw::Error) -> Self
+ {
+ Self(err)
+ }
+}
+
+fn initialize(window: Single<Window>, 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>)
+{
+ window
+ .swap_buffers()
+ .expect("Failed to swap window buffers");
+
+ window.poll_events().expect("Failed to poll window events");
+}