From f5b3b11ad449a60322bca096de010a35ba2b30c8 Mon Sep 17 00:00:00 2001
From: HampusM <hampus@hampusmat.com>
Date: Wed, 22 Jan 2025 18:00:03 +0100
Subject: feat(engine): add texture & texture properties builders

---
 engine/src/texture.rs | 152 +++++++++++++++++++++++++++++++++++---------------
 1 file changed, 107 insertions(+), 45 deletions(-)

diff --git a/engine/src/texture.rs b/engine/src/texture.rs
index 16c1941..4a4fe86 100644
--- a/engine/src/texture.rs
+++ b/engine/src/texture.rs
@@ -8,6 +8,7 @@ use image::{DynamicImage, ImageError, Rgb, RgbImage};
 use crate::color::Color;
 use crate::data_types::dimens::Dimens;
 use crate::opengl::texture::PixelDataFormat;
+use crate::util::builder;
 
 static NEXT_ID: AtomicU32 = AtomicU32::new(0);
 
@@ -30,62 +31,26 @@ pub struct Texture
 
 impl Texture
 {
+    pub fn builder() -> Builder
+    {
+        Builder::default()
+    }
+
     /// Opens a texture image.
     ///
     /// # Errors
     /// Will return `Err` if:
     /// - Opening the image fails
     /// - The image data is not 8-bit/color RGB
-    #[allow(clippy::new_without_default)]
     pub fn open(path: &Path) -> Result<Self, Error>
     {
-        let image = ImageReader::open(path)
-            .map_err(Error::OpenImageFailed)?
-            .decode()
-            .map_err(Error::DecodeImageFailed)?;
-
-        let pixel_data_format = match &image {
-            DynamicImage::ImageRgb8(_) => PixelDataFormat::Rgb8,
-            DynamicImage::ImageRgba8(_) => PixelDataFormat::Rgba8,
-            _ => {
-                return Err(Error::UnsupportedImageDataKind);
-            }
-        };
-
-        let dimensions = Dimens {
-            width: image.width(),
-            height: image.height(),
-        };
-
-        let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
-
-        Ok(Self {
-            id: Id::new(id),
-            image,
-            pixel_data_format,
-            dimensions,
-            properties: Properties::default(),
-        })
+        Self::builder().open(path)
     }
 
     #[must_use]
     pub fn new_from_color(dimensions: &Dimens<u32>, color: &Color<u8>) -> Self
     {
-        let image = RgbImage::from_pixel(
-            dimensions.width,
-            dimensions.height,
-            Rgb([color.red, color.green, color.blue]),
-        );
-
-        let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
-
-        Self {
-            id: Id::new(id),
-            image: image.into(),
-            pixel_data_format: PixelDataFormat::Rgb8,
-            dimensions: *dimensions,
-            properties: Properties::default(),
-        }
+        Self::builder().build_with_single_color(dimensions, color)
     }
 
     #[must_use]
@@ -132,6 +97,84 @@ impl Drop for Texture
     }
 }
 
+/// Texture builder.
+#[derive(Debug, Default, Clone)]
+pub struct Builder
+{
+    properties: Properties,
+}
+
+impl Builder
+{
+    pub fn properties(mut self, properties: Properties) -> Self
+    {
+        self.properties = properties;
+        self
+    }
+
+    /// Opens a image as a texture.
+    ///
+    /// # Errors
+    /// Will return `Err` if:
+    /// - Opening the image fails
+    /// - Decoding the image fails
+    /// - The image data is in a unsupported format
+    pub fn open(&self, path: &(impl AsRef<Path> + ?Sized)) -> Result<Texture, Error>
+    {
+        let image = ImageReader::open(path)
+            .map_err(Error::OpenImageFailed)?
+            .decode()
+            .map_err(Error::DecodeImageFailed)?;
+
+        let pixel_data_format = match &image {
+            DynamicImage::ImageRgb8(_) => PixelDataFormat::Rgb8,
+            DynamicImage::ImageRgba8(_) => PixelDataFormat::Rgba8,
+            _ => {
+                return Err(Error::UnsupportedImageDataFormat);
+            }
+        };
+
+        let dimensions = Dimens {
+            width: image.width(),
+            height: image.height(),
+        };
+
+        let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
+
+        Ok(Texture {
+            id: Id::new(id),
+            image,
+            pixel_data_format,
+            dimensions,
+            properties: self.properties.clone(),
+        })
+    }
+
+    #[must_use]
+    pub fn build_with_single_color(
+        &self,
+        dimensions: &Dimens<u32>,
+        color: &Color<u8>,
+    ) -> Texture
+    {
+        let image = RgbImage::from_pixel(
+            dimensions.width,
+            dimensions.height,
+            Rgb([color.red, color.green, color.blue]),
+        );
+
+        let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
+
+        Texture {
+            id: Id::new(id),
+            image: image.into(),
+            pixel_data_format: PixelDataFormat::Rgb8,
+            dimensions: *dimensions,
+            properties: self.properties.clone(),
+        }
+    }
+}
+
 /// Texture error.
 #[derive(Debug, thiserror::Error)]
 pub enum Error
@@ -142,11 +185,13 @@ pub enum Error
     #[error("Failed to decode texture image")]
     DecodeImageFailed(#[source] ImageError),
 
-    #[error("Unsupported image data kind")]
-    UnsupportedImageDataKind,
+    #[error("Unsupported image data format")]
+    UnsupportedImageDataFormat,
 }
 
+builder! {
 /// Texture properties
+#[builder(name = PropertiesBuilder, derives=(Debug, Clone))]
 #[derive(Debug, Clone)]
 #[non_exhaustive]
 pub struct Properties
@@ -155,6 +200,15 @@ pub struct Properties
     pub magnifying_filter: Filtering,
     pub minifying_filter: Filtering,
 }
+}
+
+impl Properties
+{
+    pub fn builder() -> PropertiesBuilder
+    {
+        PropertiesBuilder::default()
+    }
+}
 
 impl Default for Properties
 {
@@ -168,6 +222,14 @@ impl Default for Properties
     }
 }
 
+impl Default for PropertiesBuilder
+{
+    fn default() -> Self
+    {
+        Properties::default().into()
+    }
+}
+
 /// Texture ID.
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct Id
-- 
cgit v1.2.3-18-g5258