Full GUI pattern¶
This guide shows how to build a GUI with RitnLibGui and RitnLibGuiElement, starting from the simplest case up to the full pattern used in RitnCharacters.
Minimal example¶
For a simple GUI (one frame + one button), everything can fit in control.lua.
The class¶
-- my-mod/control.lua
MyGui = ritnlib.classFactory.newclass(RitnLibGui, function(self, event)
-- mod_name + root frame suffix ("panel-frame-main" in game)
RitnLibGui.init(self, event, "my-mod", "frame-main")
self.object_name = "MyGui"
self.gui_name = "panel"
-- Allowed actions: "normalised_type-element_name"
self.gui_action = {
["panel"] = {
["button-close"] = true,
}
}
-- self.gui[1] = the container, not the root frame
self.gui = { self.player.gui.center }
self.content = {}
end)
function MyGui:create()
-- Check if the GUI already exists
if self.gui[1]["panel-frame-main"] then return self end
local frame = self.gui[1].add(
RitnLibGuiElement("panel", "frame", "main"):vertical():get()
)
frame.add(
RitnLibGuiElement("panel", "button", "close"):caption("Close"):get()
)
return self
end
function MyGui:action_close()
local frame = self.gui[1]["panel-frame-main"]
if frame then frame.destroy() end
return self
end
The remote interface¶
remote.add_interface("my-mod", {
gui_action_panel = function(action, event)
if action == "button-close" then
MyGui(event):action_close()
end
end,
})
The handlers¶
script.on_event(defines.events.on_player_created, function(event)
MyGui(event):create()
end)
script.on_event(defines.events.on_gui_click, function(event)
MyGui(event):on_gui_click()
-- parses "panel-button-close" → action = "button-close"
-- → remote.call("my-mod", "gui_action_panel", "button-close", event)
end)
Full pattern — 3-file structure¶
For a more complex GUI (list, nested flows, styles), the recommended approach is to split into 3 files as in RitnCharacters.
my-mod/
├─ gui/my-panel.lua ← element specs + paths
├─ classes/MyGui.lua ← class with :create() and actions
└─ modules/storage.lua ← remote.add_interface
gui/my-panel.lua — specs and paths¶
local function getElement(gui_name)
return {
frame = {
main = RitnLibGuiElement(gui_name, "frame", "main"):vertical():get(),
submain = RitnLibGuiElement(gui_name, "frame", "submain"):vertical()
:style("inside_shallow_frame"):get(),
},
flow = {
footer = RitnLibGuiElement(gui_name, "flow", "footer"):horizontal():get(),
},
list = RitnLibGuiElement(gui_name, "list-box", "items"):get(),
button = {
confirm = RitnLibGuiElement(gui_name, "button", "confirm"):caption("Confirm"):get(),
close = RitnLibGuiElement(gui_name, "button", "close"):caption("Close"):get(),
},
}
end
local function getContent()
return {
frame = {
main = { "frame-main" },
submain = { "frame-main", "frame-submain" },
},
flow = {
footer = { "frame-main", "frame-submain", "flow-footer" },
},
list = { "frame-main", "frame-submain", "listbox-items" },
button = {
confirm = { "frame-main", "frame-submain", "flow-footer", "button-confirm" },
close = { "frame-main", "frame-submain", "flow-footer", "button-close" },
},
}
end
return { getElement = getElement, getContent = getContent }
getContent describes the traversal path from self.gui[1] to each element. self:getElement("list") walks it, prepending gui_name .. "-" to each step.
classes/MyGui.lua¶
local fGui = require(ritnlib.defines.mymod.gui.panel)
MyGui = ritnlib.classFactory.newclass(RitnLibGui, function(self, event)
RitnLibGui.init(self, event, ritnlib.defines.mymod.name, "frame-main")
self.object_name = "MyGui"
self.gui_name = "panel"
self.gui_action = {
["panel"] = {
["button-confirm"] = true,
["button-close"] = true,
}
}
self.gui = { self.player.gui.center }
self.content = fGui.getContent()
end)
function MyGui:create()
if self.gui[1]["panel-frame-main"] then return self end
local e = fGui.getElement(self.gui_name)
local c = {}
c.frame_main = self.gui[1].add(e.frame.main)
c.frame_submain = c.frame_main.add(e.frame.submain)
c.list = c.frame_submain.add(e.list)
c.flow_footer = c.frame_submain.add(e.flow.footer)
c.btn_confirm = c.flow_footer.add(e.button.confirm)
c.btn_close = c.flow_footer.add(e.button.close)
-- Post-creation styling
RitnLibStyle(c.frame_main):padding(4)
RitnLibStyle(c.list):horizontalStretch():maxHeight(400)
RitnLibStyle(c.flow_footer):align("right")
for _, item in pairs({ "Option A", "Option B", "Option C" }) do
c.list.add_item(item)
end
return self
end
function MyGui:action_close()
local frame = self.gui[1]["panel-frame-main"]
if frame then frame.destroy() end
return self
end
function MyGui:action_confirm()
local list = self:getElement("list")
if not list or list.selected_index == 0 then return self end
self.player.player.print("Selected: " .. list.get_item(list.selected_index))
self:action_close()
return self
end
modules/storage.lua¶
remote.add_interface("my-mod", {
["gui_action_panel"] = function(action, event)
if action == "button-confirm" then
MyGui(event):action_confirm()
elseif action == "button-close" then
MyGui(event):action_close()
end
end,
})
return {}
Quick reference¶
RitnLibGuiElement — naming¶
RitnLibGuiElement(gui_name, type, name) generates the in-game name gui_name-normalised_type-name:
| Type passed | Normalised | Example with gui_name="panel" |
|---|---|---|
"frame" |
"frame" |
"panel-frame-main" |
"flow" |
"flow" |
"panel-flow-footer" |
"button" / "sprite-button" |
"button" |
"panel-button-close" |
"list-box" |
"listbox" |
"panel-listbox-items" |
"drop-down" |
"dropdown" |
"panel-dropdown-x" |
"text-box" |
"textbox" |
"panel-textbox-x" |
Common methods¶
| Method | Purpose |
|---|---|
:horizontal() / :vertical() |
Direction (frame, flow, line) |
:caption(text) |
Displayed text |
:style(name) |
Factorio GUI style |
:tooltip(text) |
Tooltip |
:visible(bool) |
Initial visibility |
:enabled(bool) |
Enabled/disabled |
:get() |
Returns the raw payload (to pass to parent.add(...)) |
:add(parent) |
Adds directly to parent, returns the LuaGuiElement |