From 709426d8efc557fe5cf0745d81a1513d6190723f Mon Sep 17 00:00:00 2001
From: Filip Tibell <filip.tibell@gmail.com>
Date: Wed, 15 Mar 2023 16:14:17 +0100
Subject: [PATCH] Implement ColorSequence roblox datatype

---
 .../lib-roblox/src/datatypes/conversion.rs    |  13 +--
 .../src/datatypes/types/color_sequence.rs     | 104 ++++++++++++++++++
 .../types/color_sequence_keypoint.rs          |  60 ++++++++++
 .../lib-roblox/src/datatypes/types/mod.rs     |   4 +
 packages/lib-roblox/src/lib.rs                |  18 +--
 5 files changed, 184 insertions(+), 15 deletions(-)
 create mode 100644 packages/lib-roblox/src/datatypes/types/color_sequence.rs
 create mode 100644 packages/lib-roblox/src/datatypes/types/color_sequence_keypoint.rs

diff --git a/packages/lib-roblox/src/datatypes/conversion.rs b/packages/lib-roblox/src/datatypes/conversion.rs
index 54a6660..d49d44d 100644
--- a/packages/lib-roblox/src/datatypes/conversion.rs
+++ b/packages/lib-roblox/src/datatypes/conversion.rs
@@ -120,9 +120,6 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> {
             // Not yet implemented datatypes
             // Rbx::Axes(_) => todo!(),
             // Rbx::CFrame(_) => todo!(),
-            // Rbx::Color3(_) => todo!(),
-            // Rbx::Color3uint8(_) => todo!(),
-            // Rbx::ColorSequence(_) => todo!(),
             // Rbx::Enum(_) => todo!(),
             // Rbx::Faces(_) => todo!(),
             // Rbx::NumberRange(_) => todo!(),
@@ -136,8 +133,9 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> {
 
             Rbx::BrickColor(value) => lua.create_userdata(BrickColor::from(value))?,
 
-            Rbx::Color3(value)      => lua.create_userdata(Color3::from(value))?,
-            Rbx::Color3uint8(value) => lua.create_userdata(Color3::from(value))?,
+            Rbx::Color3(value)        => lua.create_userdata(Color3::from(value))?,
+            Rbx::Color3uint8(value)   => lua.create_userdata(Color3::from(value))?,
+            Rbx::ColorSequence(value) => lua.create_userdata(ColorSequence::from(value))?,
 
             Rbx::UDim(value)  => lua.create_userdata(UDim::from(value))?,
             Rbx::UDim2(value) => lua.create_userdata(UDim2::from(value))?,
@@ -171,8 +169,9 @@ impl<'lua> LuaToRbxVariant<'lua> for LuaAnyUserData<'lua> {
         let f = match variant_type {
             RbxVariantType::BrickColor => convert::<BrickColor, rbx::BrickColor>,
 
-            RbxVariantType::Color3      => convert::<Color3, rbx::Color3>,
-            RbxVariantType::Color3uint8 => convert::<Color3, rbx::Color3uint8>,
+            RbxVariantType::Color3        => convert::<Color3,        rbx::Color3>,
+            RbxVariantType::Color3uint8   => convert::<Color3,        rbx::Color3uint8>,
+            RbxVariantType::ColorSequence => convert::<ColorSequence, rbx::ColorSequence>,
 
             RbxVariantType::UDim  => convert::<UDim,  rbx::UDim>,
             RbxVariantType::UDim2 => convert::<UDim2, rbx::UDim2>,
diff --git a/packages/lib-roblox/src/datatypes/types/color_sequence.rs b/packages/lib-roblox/src/datatypes/types/color_sequence.rs
new file mode 100644
index 0000000..742dc30
--- /dev/null
+++ b/packages/lib-roblox/src/datatypes/types/color_sequence.rs
@@ -0,0 +1,104 @@
+use core::fmt;
+
+use mlua::prelude::*;
+use rbx_dom_weak::types::{
+    ColorSequence as RbxColorSequence, ColorSequenceKeypoint as RbxColorSequenceKeypoint,
+};
+
+use super::{Color3, ColorSequenceKeypoint};
+
+/**
+    An implementation of the [ColorSequence](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequence) Roblox datatype.
+
+    This implements all documented properties, methods & constructors of the ColorSequence class as of March 2023.
+*/
+#[derive(Debug, Clone, PartialEq)]
+pub struct ColorSequence {
+    pub(crate) keypoints: Vec<ColorSequenceKeypoint>,
+}
+
+impl ColorSequence {
+    pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
+        type ArgsColor = Color3;
+        type ArgsColors = (Color3, Color3);
+        type ArgsKeypoints = Vec<ColorSequenceKeypoint>;
+        datatype_table.set(
+            "new",
+            lua.create_function(|lua, args: LuaMultiValue| {
+                if let Ok(color) = ArgsColor::from_lua_multi(args.clone(), lua) {
+                    Ok(ColorSequence {
+                        keypoints: vec![
+                            ColorSequenceKeypoint { time: 0.0, color },
+                            ColorSequenceKeypoint { time: 1.0, color },
+                        ],
+                    })
+                } else if let Ok((c0, c1)) = ArgsColors::from_lua_multi(args.clone(), lua) {
+                    Ok(ColorSequence {
+                        keypoints: vec![
+                            ColorSequenceKeypoint {
+                                time: 0.0,
+                                color: c0,
+                            },
+                            ColorSequenceKeypoint {
+                                time: 1.0,
+                                color: c1,
+                            },
+                        ],
+                    })
+                } else if let Ok(keypoints) = ArgsKeypoints::from_lua_multi(args, lua) {
+                    Ok(ColorSequence { keypoints })
+                } else {
+                    // FUTURE: Better error message here using given arg types
+                    Err(LuaError::RuntimeError(
+                        "Invalid arguments to constructor".to_string(),
+                    ))
+                }
+            })?,
+        )
+    }
+}
+
+impl LuaUserData for ColorSequence {
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_field_method_get("Keypoints", |_, this| Ok(this.keypoints.clone()));
+    }
+}
+
+impl fmt::Display for ColorSequence {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        for (index, keypoint) in self.keypoints.iter().enumerate() {
+            if index < self.keypoints.len() - 1 {
+                write!(f, "{}, ", keypoint)?;
+            } else {
+                write!(f, "{}", keypoint)?;
+            }
+        }
+        Ok(())
+    }
+}
+
+impl From<RbxColorSequence> for ColorSequence {
+    fn from(v: RbxColorSequence) -> Self {
+        Self {
+            keypoints: v
+                .keypoints
+                .iter()
+                .cloned()
+                .map(ColorSequenceKeypoint::from)
+                .collect(),
+        }
+    }
+}
+
+impl From<ColorSequence> for RbxColorSequence {
+    fn from(v: ColorSequence) -> Self {
+        Self {
+            keypoints: v
+                .keypoints
+                .iter()
+                .cloned()
+                .map(RbxColorSequenceKeypoint::from)
+                .collect(),
+        }
+    }
+}
diff --git a/packages/lib-roblox/src/datatypes/types/color_sequence_keypoint.rs b/packages/lib-roblox/src/datatypes/types/color_sequence_keypoint.rs
new file mode 100644
index 0000000..8b7fe8e
--- /dev/null
+++ b/packages/lib-roblox/src/datatypes/types/color_sequence_keypoint.rs
@@ -0,0 +1,60 @@
+use core::fmt;
+
+use mlua::prelude::*;
+use rbx_dom_weak::types::ColorSequenceKeypoint as RbxColorSequenceKeypoint;
+
+use super::Color3;
+
+/**
+    An implementation of the [ColorSequenceKeypoint](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequenceKeypoint) Roblox datatype.
+
+    This implements all documented properties, methods & constructors of the ColorSequenceKeypoint class as of March 2023.
+*/
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct ColorSequenceKeypoint {
+    pub(crate) time: f32,
+    pub(crate) color: Color3,
+}
+
+impl ColorSequenceKeypoint {
+    pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
+        datatype_table.set(
+            "new",
+            lua.create_function(|_, (time, color): (f32, Color3)| {
+                Ok(ColorSequenceKeypoint { time, color })
+            })?,
+        )?;
+        Ok(())
+    }
+}
+
+impl LuaUserData for ColorSequenceKeypoint {
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_field_method_get("Time", |_, this| Ok(this.time));
+        fields.add_field_method_get("Value", |_, this| Ok(this.color));
+    }
+}
+
+impl fmt::Display for ColorSequenceKeypoint {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{} > {}", self.time, self.color)
+    }
+}
+
+impl From<RbxColorSequenceKeypoint> for ColorSequenceKeypoint {
+    fn from(v: RbxColorSequenceKeypoint) -> Self {
+        Self {
+            time: v.time,
+            color: v.color.into(),
+        }
+    }
+}
+
+impl From<ColorSequenceKeypoint> for RbxColorSequenceKeypoint {
+    fn from(v: ColorSequenceKeypoint) -> Self {
+        Self {
+            time: v.time,
+            color: v.color.into(),
+        }
+    }
+}
diff --git a/packages/lib-roblox/src/datatypes/types/mod.rs b/packages/lib-roblox/src/datatypes/types/mod.rs
index 71fca58..54631e3 100644
--- a/packages/lib-roblox/src/datatypes/types/mod.rs
+++ b/packages/lib-roblox/src/datatypes/types/mod.rs
@@ -1,5 +1,7 @@
 mod brick_color;
 mod color3;
+mod color_sequence;
+mod color_sequence_keypoint;
 mod udim;
 mod udim2;
 mod vector2;
@@ -9,6 +11,8 @@ mod vector3int16;
 
 pub use brick_color::BrickColor;
 pub use color3::Color3;
+pub use color_sequence::ColorSequence;
+pub use color_sequence_keypoint::ColorSequenceKeypoint;
 pub use udim::UDim;
 pub use udim2::UDim2;
 pub use vector2::Vector2;
diff --git a/packages/lib-roblox/src/lib.rs b/packages/lib-roblox/src/lib.rs
index 0323605..1ca677f 100644
--- a/packages/lib-roblox/src/lib.rs
+++ b/packages/lib-roblox/src/lib.rs
@@ -21,14 +21,16 @@ where
 fn make_all_datatypes(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaTable)>> {
 	use datatypes::types::*;
     Ok(vec![
-        ("BrickColor",   make_dt(lua, BrickColor::make_table)?),
-        ("Color3",       make_dt(lua, Color3::make_table)?),
-        ("UDim",         make_dt(lua, UDim::make_table)?),
-        ("UDim2",        make_dt(lua, UDim2::make_table)?),
-        ("Vector2",      make_dt(lua, Vector2::make_table)?),
-        ("Vector2int16", make_dt(lua, Vector2int16::make_table)?),
-        ("Vector3",      make_dt(lua, Vector3::make_table)?),
-        ("Vector3int16", make_dt(lua, Vector3int16::make_table)?),
+        ("BrickColor",            make_dt(lua, BrickColor::make_table)?),
+        ("Color3",                make_dt(lua, Color3::make_table)?),
+        ("ColorSequence",         make_dt(lua, ColorSequence::make_table)?),
+        ("ColorSequenceKeypoint", make_dt(lua, ColorSequenceKeypoint::make_table)?),
+        ("UDim",                  make_dt(lua, UDim::make_table)?),
+        ("UDim2",                 make_dt(lua, UDim2::make_table)?),
+        ("Vector2",               make_dt(lua, Vector2::make_table)?),
+        ("Vector2int16",          make_dt(lua, Vector2int16::make_table)?),
+        ("Vector3",               make_dt(lua, Vector3::make_table)?),
+        ("Vector3int16",          make_dt(lua, Vector3int16::make_table)?),
     ])
 }