Skip to main content

Creating Custom Widgets

Overview

Iris has a widget constructor method to create widgets with. Once a widget has been constructed, you can than use it like any other widget. Every widget follows a set of guidelines it must follow when constructed.

To construct a new widget, you can call Iris.WidgetConstructor() with the widget name and widget class. To then use the widget you can call Iris.Internal._Insert() with the widget name and then optional argument and state tables.

Documentation

Widget Construction

For Instance, this is the call to Iris.WidgetConstructor for the Iris.Text widget:

Iris.WidgetConstructor("Text", {
hasState = false,
hasChildren = false,
Args = {
["Text"] = 1
},
Events {
["hovered"] = {
...
}
}
Generate = function(thisWidget)
local Text = Instance.new("TextLabel")

...

return Text
end,
Update = function(thisWidget)
...
end,
Discard = function(thisWidget)
thisWidget.Instance:Destroy()
end
})

The first argument, type: string, specifies a name for the widget.

The second argument is the widget class. The methods which a widget class has depends on the value of hasState and hasChildren. Every widget class should specify if it hasState and hasChildren. The example widget, a text label, has no state, and it does not contain other widgets, so both are false. Every widget must have the following functions:

All WidgetsWidgets with StateWidgets with Children
GenerateGenerateStateChildAdded
UpdateUpdateStateChildDiscarded (optional)
Discard
Args
Events

Generate

Generate is called when a widget is first instantiated. It should create all the instances and properly adjust them to fit the config properties. Generate is also called when style properties change.

Generate should return the instance which acts as the root of the widget. (what should be parented to the parents designated Instance)

Update

Update is called only after instantiation and when widget arguments have changed. For instance, in Iris.Text

Update = function(thisWidget)
local Text = thisWidget.Instance
if thisWidget.arguments.Text == nil then
error("Iris.Text Text Argument is required", 5)
end
Text.Text = thisWidget.arguments.Text
end

Discard

Discard is called when the widget stops being displayed. In most cases the function body should resemble this:

Discard = function(thisWidget)
thisWidget.Instance:Destroy()
end

Events

Events is a table, not a method. It contains all of the possible events which a widget can have. Lets look at the hovered event as an example.

["hovered"] = {
["Init"] = function(thisWidget)
local hoveredGuiObject = thisWidget.Instance
thisWidget.isHoveredEvent = false

hoveredGuiObject.MouseEnter:Connect(function()
thisWidget.isHoveredEvent = true
end)
hoveredGuiObject.MouseLeave:Connect(function()
thisWidget.isHoveredEvent = false
end)
end,
["Get"] = function(thisWidget)
return thisWidget.isHoveredEvent
end
}

Every event has 2 methods, Init and Get. Init is called when a widget first polls the value of an event. Because of this, you can instantiate events and variables for an event to only widgets which need it. Get is the actual function which is called by the call to an event (like Button.hovered()), it should return the event value.

Args

Args is a table, not a method. It enumerates all of the possible arguments which may be passed as arguments into the widget. The order of the tables indicies indicate which position the Argument will be interpreted as. For instance, in Iris.Text:

Args = {
["Text"] = 1
}

when a Text widget is generated, the first index of the Arguments table will be interpreted as the 'Text' parameter

Iris.Text({[1] = "Hello"})
-- same result
Iris.Text({"Hello"})

the Update function can retrieve arguments from thisWidget.arguments, such as thisWidget.arguments.Text

GenerateState

GenerateState is called when the widget is first Instantiated, It should generate any state objects which weren't passed as a state by the user. For Instance, in Iris.Checkbox:

GenerateState = function(thisWidget)
if thisWidget.state.isChecked == nil then
thisWidget.state.isChecked = Iris._widgetState(thisWidget, "checked", false)
end
end

UpdateState

UpdateState is called whenever ANY state objects are updated, using its :set() method. For instance, in Iris.Checkbox:

UpdateState = function(thisWidget)
local Checkbox = thisWidget.Instance.CheckboxBox
if thisWidget.state.isChecked.value then
Checkbox.Text = ICONS.CHECK_MARK
thisWidget.events.checked = true
else
Checkbox.Text = ""
thisWidget.events.unchecked = true
end
end
caution

calling :set() to any of a widget's own state objects inside of UpdateState may cause an infinite loop of state updates. UpdateState should avoid calling :set().

ChildAdded

ChildAdded is called when a widget is first Initiated and is a child of the widget. ChildAdded should return the Instance which the Child will be parented to.

ChildDiscarded

ChildDiscarded is called when a widget is Discarded and is a child of the widget. ChildDiscarded is optional.

Widget Usage

To use this widget once it has been constructed, you can use:

Iris.Internal._Insert("Text", {"Sampele text"}, nil) -- "Text" argument and no state

This is the same as calling any other widget but requires the widget name as passed to Iris.WidgetConstructor() as the first argument.


When does a widget need to have state?

State should only be used by widgets when there are properties which are able to be set by BOTH the widget, and by the user's code.

For Instance, Iris.Window has a state, size. This field can be changed by the user's code, to adjust or initiate the size, and the widget also changes the size when it is resized.

If the window was never able to change the size property, such as if there were no resize feature, then instead it should be an argument.

This table demonstrates the relation between User / Widget permissions, and where the field should belong inside the widget class.

Sample Display Output