I’ve used these for multiple of my libraries, they are pretty lightweight and work perfect for my needs
Q: Why use this instead of something like Fusion?
A: Fusion and vide’s codebase lock you into roblox hiearchy, if you want to use something like darklua it breaks down (Also my creator’s reactive-ness is pretty simple to understand)
creator.luau (2.0 KB) (Requires binder module)
binder.luau (1.8 KB)
Note that the code isn’t anything to marvel at, I haven’t made any changes to make it better to look at now that im releasing it, it’s the same thing i use, i just decided to randomly release them today. (Plus it was heavily modified by AI after some issues with reactivity)
Creator
The creation is completely backwards compatible with vide and probably fusion, the only difference is reactivity.
How does it work?
The system is based on three things:
- ValueObject
- Dynamic State (DynamicKeys)
- Contextual State (ContextKeys)
ValueObjects
Value objects are the backbone of this system, they let the creator module know when values you assign it change
local number = creator.Value(5)
number:Connect(function(newValue)
print("Value changed:", newValue)
end)
number.Value += 1 -- "Value changed: 6"
Example of a theme system using ValueObjects:
--// Imports
local creator = require("../modules/creator")
--// References
local value = creator.Value
--// Functions
local function color4(color: Color3 | string, alpha: number)
local parsedColor = (typeof(color) == "Color3" and color) or (typeof(color) == "string" and Color3.fromHex(color))
return {
value(parsedColor),
value(1 - (alpha / 100)),
}
end
--// Initialize
return {
Accents = {
["blue"] = color4("0059FF", 100),
},
Text = {
["text-inverted"] = color4("1E1E21", 100),
["text"] = color4("EAEAFF", 100),
["text-secondary"] = color4("D2D2E5", 100),
["text-muted"] = color4("A7A7B6", 100),
},
}
Dynamic State
Dynamic state will automatically update properties that you assign to a ValueState when the value changes. An example of this is a theme system.
Example:
create("UIStroke")({
Name = "UIStroke",
ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
Thickness = 1,
__dynamicKeys = {
Color = self.Theme.Controls.ViewBorder, -- ValueState
},
})
Contextual State
Contextual state is for systems that have multiple states for one instance.
You assign each property a function, which will get called when that property’s assigned DynamicState is changed (Yes you will need to have dynamickeys stated first) and the value your function returns will be assigned as the properties value.
Example (Complex):
create("TextBox")({
Name = "Field",
AutomaticSize = Enum.AutomaticSize.XY,
Size = UDim2.fromOffset(138, 0),
BackgroundColor3 = Color3.fromRGB(255, 255, 255),
BackgroundTransparency = 1,
BorderColor3 = Color3.fromRGB(0, 0, 0),
BorderSizePixel = 0,
CursorPosition = -1,
TextXAlignment = Enum.TextXAlignment.Left,
FontFace = Font.new("rbxassetid://12187365364"),
PlaceholderText = "",
Text = "",
TextSize = 15,
__dynamicKeys = {
PlaceholderColor3 = self.Theme.Text.Tertiary[1],
TextTransparency = self.Theme.Text.Tertiary[2],
},
__contextKeys = {
_general = function()
local field = structures["Field"]
if not field then
return
end
if field.Text == "" then
field.PlaceholderColor3 = self.Theme.Text.Tertiary[1].Value
field.TextTransparency = self.Theme.Text.Tertiary[2].Value
else
field.TextColor3 = self.Theme.Text.Primary[1].Value
field.TextTransparency = self.Theme.Text.Primary[2].Value
end
end,
TextTransparency = function()
if not structures["Field"] then
return 1
end
return structures["Field"].Text == "" and self.Theme.Text.Tertiary[2].Value
or self.Theme.Text.Primary[2].Value
end,
}
Misc
You can get the actual instance of the custom object by indexing __instance, This is essential for things like tween, setting an instances parent to the object (Yes, i know this is really inconvenient)
Binder
This module lets you bind custom properties to an instance, and returns the wrapped object. This is really powerful especially for things like UI libraries, item management, etc.
I’ve found this module very useful in many scenarios, it reduces boilerplate and simplifies the process entirely.
binder.Wrap(properties, bindings, instance)
properties: Contains your initial unmanaged INPUT propertiesbindings: Contains bindings/functions for each property to execute when they are changedinstance: OPTIONAL, an instance for the object to wrap. Basically if you try to get/set a property not in the properties table then the binder will try to find it in the instance instead.
How it works
The binder.Wrap function makes a proxy table around your input properties using metatables.
binder.Apply(properties, object
This function can be used to apply an initial set of properties to a wrapped object, all it does is iterates through the properties table and assigns each value to the corresponding key on the object. Can be useful for setting the initial state of your components since it triggers the associated bindings
Example:
local object
local bindings = {
Maximized = function(value: boolean)
print("Maximized")
end,
Minimized = function(value: boolean)
print("Minimized")
end,
}
object = binder.Wrap(properties, bindings, structures.Window)