diff --git a/packages/lib-roblox/src/datatypes/conversion.rs b/packages/lib-roblox/src/datatypes/conversion.rs index 7a250b1..c9f5350 100644 --- a/packages/lib-roblox/src/datatypes/conversion.rs +++ b/packages/lib-roblox/src/datatypes/conversion.rs @@ -127,7 +127,6 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> { // Rbx::OptionalCFrame(_) => todo!(), // Rbx::PhysicalProperties(_) => todo!(), // Rbx::Ray(_) => todo!(), - // Rbx::Rect(_) => todo!(), // Rbx::Region3(_) => todo!(), // Rbx::Region3int16(_) => todo!(), @@ -139,6 +138,7 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> { Rbx::Color3uint8(value) => lua.create_userdata(Color3::from(value))?, Rbx::ColorSequence(value) => lua.create_userdata(ColorSequence::from(value))?, + Rbx::Rect(value) => lua.create_userdata(Rect::from(value))?, Rbx::UDim(value) => lua.create_userdata(UDim::from(value))?, Rbx::UDim2(value) => lua.create_userdata(UDim2::from(value))?, @@ -179,6 +179,7 @@ impl<'lua> LuaToRbxVariant<'lua> for LuaAnyUserData<'lua> { RbxVariantType::Enum => convert::, + RbxVariantType::Rect => convert::, RbxVariantType::UDim => convert::, RbxVariantType::UDim2 => convert::, diff --git a/packages/lib-roblox/src/datatypes/types/mod.rs b/packages/lib-roblox/src/datatypes/types/mod.rs index de7bfab..5b13585 100644 --- a/packages/lib-roblox/src/datatypes/types/mod.rs +++ b/packages/lib-roblox/src/datatypes/types/mod.rs @@ -7,6 +7,7 @@ mod r#enum; mod r#enum_item; mod r#enums; mod faces; +mod rect; mod udim; mod udim2; mod vector2; @@ -23,6 +24,7 @@ pub use faces::Faces; pub use r#enum::Enum; pub use r#enum_item::EnumItem; pub use r#enums::Enums; +pub use rect::Rect; pub use udim::UDim; pub use udim2::UDim2; pub use vector2::Vector2; @@ -79,6 +81,7 @@ mod tests { color_sequence_keypoint: "datatypes/ColorSequenceKeypoint", r#enum: "datatypes/Enum", faces: "datatypes/Faces", + rect: "datatypes/Rect", udim: "datatypes/UDim", udim2: "datatypes/UDim2", vector2: "datatypes/Vector2", diff --git a/packages/lib-roblox/src/datatypes/types/rect.rs b/packages/lib-roblox/src/datatypes/types/rect.rs new file mode 100644 index 0000000..c60e794 --- /dev/null +++ b/packages/lib-roblox/src/datatypes/types/rect.rs @@ -0,0 +1,118 @@ +use core::fmt; +use std::ops; + +use glam::Vec2; +use mlua::prelude::*; +use rbx_dom_weak::types::Rect as RbxRect; + +use super::{super::*, Vector2}; + +/** + An implementation of the [Rect](https://create.roblox.com/docs/reference/engine/datatypes/Rect) + Roblox datatype, backed by [`glam::Vec2`]. + + This implements all documented properties, methods & constructors of the Rect class as of March 2023. +*/ +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Rect { + pub(crate) min: Vec2, + pub(crate) max: Vec2, +} + +impl Rect { + fn new(lhs: Vec2, rhs: Vec2) -> Self { + Self { + min: lhs.min(rhs), + max: lhs.max(rhs), + } + } +} + +impl Rect { + pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { + type ArgsVector2s = (Option, Option); + type ArgsNums = (Option, Option, Option, Option); + datatype_table.set( + "new", + lua.create_function(|lua, args: LuaMultiValue| { + if let Ok((min, max)) = ArgsVector2s::from_lua_multi(args.clone(), lua) { + Ok(Rect::new( + min.unwrap_or_default().0, + max.unwrap_or_default().0, + )) + } else if let Ok((x0, y0, x1, y1)) = ArgsNums::from_lua_multi(args, lua) { + let min = Vec2::new(x0.unwrap_or_default(), y0.unwrap_or_default()); + let max = Vec2::new(x1.unwrap_or_default(), y1.unwrap_or_default()); + Ok(Rect::new(min, max)) + } else { + // FUTURE: Better error message here using given arg types + Err(LuaError::RuntimeError( + "Invalid arguments to constructor".to_string(), + )) + } + })?, + ) + } +} + +impl LuaUserData for Rect { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("Min", |_, this| Ok(Vector2(this.min))); + fields.add_field_method_get("Max", |_, this| Ok(Vector2(this.max))); + fields.add_field_method_get("Width", |_, this| Ok(this.max.x - this.min.x)); + fields.add_field_method_get("Height", |_, this| Ok(this.max.y - this.min.y)); + } + + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq); + methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string); + methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm); + methods.add_meta_method(LuaMetaMethod::Add, userdata_impl_add); + methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub); + } +} + +impl fmt::Display for Rect { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}, {}", self.min, self.max) + } +} + +impl ops::Neg for Rect { + type Output = Self; + fn neg(self) -> Self::Output { + Rect::new(-self.min, -self.max) + } +} + +impl ops::Add for Rect { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + Rect::new(self.min + rhs.min, self.max + rhs.max) + } +} + +impl ops::Sub for Rect { + type Output = Self; + fn sub(self, rhs: Self) -> Self::Output { + Rect::new(self.min - rhs.min, self.max - rhs.max) + } +} + +impl From for Rect { + fn from(v: RbxRect) -> Self { + Rect { + min: Vec2::new(v.min.x, v.min.y), + max: Vec2::new(v.max.x, v.max.y), + } + } +} + +impl From for RbxRect { + fn from(v: Rect) -> Self { + RbxRect { + min: Vector2(v.min).into(), + max: Vector2(v.max).into(), + } + } +} diff --git a/packages/lib-roblox/src/datatypes/types/vector2.rs b/packages/lib-roblox/src/datatypes/types/vector2.rs index 39f588d..ddf3a50 100644 --- a/packages/lib-roblox/src/datatypes/types/vector2.rs +++ b/packages/lib-roblox/src/datatypes/types/vector2.rs @@ -14,7 +14,7 @@ use super::super::*; This implements all documented properties, methods & constructors of the Vector2 class as of March 2023. */ -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Vector2(pub Vec2); impl Vector2 { diff --git a/packages/lib-roblox/src/lib.rs b/packages/lib-roblox/src/lib.rs index 128aa90..c6a7c86 100644 --- a/packages/lib-roblox/src/lib.rs +++ b/packages/lib-roblox/src/lib.rs @@ -25,6 +25,7 @@ fn make_all_datatypes(lua: &Lua) -> LuaResult> { ("ColorSequence", make_dt(lua, ColorSequence::make_table)?), ("ColorSequenceKeypoint", make_dt(lua, ColorSequenceKeypoint::make_table)?), ("Faces", make_dt(lua, Faces::make_table)?), + ("Rect", make_dt(lua, Rect::make_table)?), ("UDim", make_dt(lua, UDim::make_table)?), ("UDim2", make_dt(lua, UDim2::make_table)?), ("Vector2", make_dt(lua, Vector2::make_table)?), diff --git a/tests/roblox/datatypes/Rect.luau b/tests/roblox/datatypes/Rect.luau new file mode 100644 index 0000000..9dd2b0a --- /dev/null +++ b/tests/roblox/datatypes/Rect.luau @@ -0,0 +1,42 @@ +-- HACK: Make luau happy, with the mlua rust +-- crate all globals are also present in _G +local Vector2 = _G.Vector2 +local Rect = _G.Rect + +-- Constructors & properties + +Rect.new() +Rect.new(0) +Rect.new(0, 0) +Rect.new(0, 0, 0) +Rect.new(0, 0, 0, 0) +Rect.new(0 / 0, 0, 0 / 0, 0) + +Rect.new(Vector2.zero) +Rect.new(Vector2.zero, Vector2.zero) + +assert(not pcall(function() + return Rect.new(false) +end)) +assert(not pcall(function() + return Rect.new("", "") +end)) +assert(not pcall(function() + return Rect.new(newproxy(true)) +end)) + +assert(Rect.new(1, 0, 2, 4).Min == Vector2.new(1, 0)) +assert(Rect.new(1, 0, 2, 4).Max == Vector2.new(2, 4)) +assert(Rect.new(0, 0, 1, 2).Width == 1) +assert(Rect.new(0, 0, 1, 2).Height == 2) + +assert(Rect.new(Vector2.new(1, 0), Vector2.new(2, 4)).Min == Vector2.new(1, 0)) +assert(Rect.new(Vector2.new(1, 0), Vector2.new(2, 4)).Max == Vector2.new(2, 4)) +assert(Rect.new(Vector2.new(1, 0), Vector2.new(2, 4)).Width == 1) +assert(Rect.new(Vector2.new(1, 0), Vector2.new(2, 4)).Height == 4) + +-- Ops + +assert(Rect.new(2, 4, 6, 8) + Rect.new(1, 1, 1, 1) == Rect.new(3, 5, 7, 9)) +assert(Rect.new(2, 4, 6, 8) - -Rect.new(1, 1, 1, 1) == Rect.new(3, 5, 7, 9)) +assert(Rect.new(2, 4, 6, 8) - Rect.new(1, 1, 1, 1) == Rect.new(1, 3, 5, 7))