Files
factorio-scenario-ExpCluster/exp_gui/docs/reference.md
Cooldude2606 7ab721b4b6 Refactor some of the Guis from the legacy plugin (#399)
* Fix bugs in core and add default args to Gui defs

* Refactor production Gui

* Refactor landfill blueprint button

* Fix more bugs in core

* Consistent naming of new guis

* Refactor module inserter gui

* Refactor surveillance gui

* Add shorthand for data from arguments

* Make element names consistent

* Add types

* Change how table rows work

* Refactor player stats gui

* Refactor quick actions gui

* Refactor research milestones gui

* Refactor player bonus gui

* Refactor science production gui

* Refactor autofill gui

* Cleanup use of aligned flow

* Rename "Gui.element" to "Gui.define"

* Rename Gui types

* Rename property_from_arg

* Add guide for making guis

* Add full reference document

* Add condensed reference

* Apply style guide to refactored guis

* Bug fixes
2025-08-29 14:30:30 +01:00

19 KiB
Raw Blame History

ExpGui API Reference

This is a streamlined version of the API reference, highlighting the methods you're most likely to use day-to-day. It includes extra detail and practical examples to help you get up and running quickly.

If you havent already, we recommend starting with the framework guide. It lays the groundwork and gives you the context youll need to understand how these methods work together.

A full reference is also available. But if you plan to rely on it, we strongly suggest reviewing and familiarizing yourself with the underlying implementation of the functions.

Utility Functions

These helper methods are designed to simplify common tasks when working with GUI elements, like toggling states or safely destroying elements.

Gui.get_player

Retrieves the player associated with a given context. This can be:

  • A LuaGuiElement
  • An event containing event.player_index
  • An event containing event.element

The method includes a not-nil assertion to handle rare cases where a player might have become invalid.

Gui.toggle_*_state

Refers to Gui.toggle_enabled_state and Gui.toggle_visible_state.

These functions toggle the specified state (either enabled or visible) for a LuaGuiElement, and return the new state after toggling. If you provide a second argument (true or false), the function becomes set_*_state and sets the state directly instead of toggling.

Each function checks if the element is non-nil and still valid before performing any action. If those checks fail, nothing happens.

Gui.destroy_if_valid

Destroys a given LuaGuiElement, but only if it exists and is valid.

If either condition is not met, the function exits quietly without performing any action.

Element Definations

This section covers how to define, register, and manage GUI elements using the framework. These definitions form the foundation of how your GUI behaves, persists, and responds to player interaction.

Gui.define

All GUI elements begin with a call to Gui.define, where you provide a unique name for your element. This name only needs to be unique within your own mod or module.

When you're first setting up a GUI, you may not know its full structure right away. In these cases, it can be useful to use :empty() child elements until you are ready to define them.

Elements.my_button = Gui.define("my_button")
    :empty()

Gui.add_*_element

Refers to Gui.add_top_element, Gui.add_left_element, and Gui.add_relative_element.

These functions register a root element to a GUI flow, ensuring its created for all players when they join. Once registered, the framework takes ownership of the elements lifetime, guaranteeing that it always exists.

This makes them ideal entry points for GUIs that maintain persistent state.

You can retrieve the created instance using Gui.get_*_element, passing in the definition as the first argument. From there, you can perform operations like Gui.toggle_visible_state or apply custom logic.

If you're using the Toolbar or GuiIter, you likely wont need to manually retrieve your element at all. For example, the Toolbar provides convenience methods like Toolbar.get_left_element_visible_state and Toolbar.set_button_toggled_state, both of which accept your element definition directly.

ExpElement:draw

When you're ready to define an element, the first required step is to call :draw().

This method accepts either:

  • A table that defines the GUI structure
  • A function that returns a LuaGuiElement

Using a table is encouraged and it supports dynamic values via Gui.from_argument, allowing you to pass data through arguments easily. While this doesn't cover every use case, it handles the majority of common needs.

Elements.my_label = Gui.define("my_label")
    :draw{
        caption = Gui.from_argument(1),
        style = "heading_2_label",
    }

For elements with many optional values, it's recommended to use an "options" table. Simply provide named keys instead of using array indexes. The options table is always assumed to be the final argument, so required values can still be passed by index. You can also define default values.

Elements.my_camera = Gui.define("my_camera")
    :draw{
        surface_index = Gui.from_argument(1),
        position = Gui.from_argument("position", { 0, 0 }),
        zoom = Gui.from_argument("zoom"),
    }

When an element is a composite of other elements, i.e. it has children, then you need to use the function defination method. This is because child elements may need arguments to be supplied to them which is more easily done in a function body rather than inventing a new table syntax.

Your draw function should always return the most "meaningful" LuaGuiElement, this can mean: the element that raises events, the element that children should be added to, or the root element for those registered to a gui flow (top / left / relative). If multiple of these conditions apply to different children then you will need to look into manually calling :link_element for event handling or clearly document where children should be added by callers. If none of these apply, then consider if you should be using an element defination at all, if you must then you can return Gui.no_return.

If your element is a composite (i.e. it contains child elements), then using a function is required. This is because you may need to pass arguments to children which is more cleanly done in a function body rather than a complex table structure.

Your draw function should return the most “meaningful” LuaGuiElement. This might be:

  • The element that raises GUI events
  • The element where children should be added
  • The root element registered to a GUI flow (top, left, or relative)

If these responsibilities apply to different children, youll need to either manually link elements to events using :link_element or document clearly where children should be added by callers. If none of these conditions apply, consider whether a standalone element definition is appropriate. If you still need one, you can return Gui.no_return.

Elements.my_frame = Gui.define("my_Frame")
    :draw(function(def, parent)
        local player = Gui.get_player(parent)
        local frame = parent.add{ type = "frame" }
        Elements.my_button(frame)
        Element.my_label(frame, "Hello, " .. player.name)
        Element.my_camera(frame, player.surface.index, {
            zoom = 0.5,
        })
        return frame
    end)

Elements.no_return = Gui.define("no_Return")
    :draw(function(def, parent)
        return Gui.no_return()
    end)

ExpElement:style

This method defines the style of your element, and is very similar to :draw. Styling is only applied to the element returned from :draw.

If you use a function to define styles, the signature is fun(def, element, parent).

If you return a table from this function, it should mimic the structure of a LuaStyle object. However, you are not required to return anything. You can also apply styles directly to the element, which is useful for read-only properties like LuaStyle.column_alignments.

Elements.my_label_big = Gui.define("my_label_big")
    :draw{
        caption = Gui.from_argument(1),
        style = "heading_2_label",
    }
    :style{
        width = 400,
    }

Elements.right_aligned_numbers = Gui.define("right_aligned_numbers")
    :draw{
        caption = Gui.from_argument(1),
    }
    :style(function(def, element, parent, caption)
        return {
            width = 400,
            horizontal_align = tonumber(caption) and "right" or "left"
        }
    end)

ExpElement:*_data

Refers to ExpElement:element_data, ExpElement:player_data, ExpElement:force_data, and ExpElement:global_data; standlone use of GuiData is not covered.

These methods initialize GUI-related data within your element definition. You can access the data later through ExpElement.data, or more commonly as def.data in event handlers.

If you pass a non-function value, it will be deep-copied to create the initial data (if it does not already exist).

If you pass a function, it will be called to either mutate existing data or return a new value to be used as the inital data.

For complex data structures, especially those involving references to child elements, its often better to assign data directly inside your :draw method. Keep in mind that initializers do not run until after :draw completes, so you cannot rely on data being in a consistent state during draw.

Elements.my_primaitve_data = Gui.define("my_primaitve_data")
    :empty()
    :element_data(0)

Elements.my_table_data = Gui.define("my_table_data")
    :empty()
    :element_data{
        count = 0,
    }

Elements.my_function_data = Gui.define("my_function_data")
    :empty()
    :element_data(function(def, element, parent)
        return {
            seed = math.random()
        }
    end)

Elements.shared_counter = Gui.define("shared_counter")
    :draw(function(def, parent)
        local player = Gui.get_player(parent)
        local count = def.data[player.force] or 0
        return parent.add{
            type = "button",
            caption = tostring(count),
        }
    end)
    :force_data(0)
    :on_click(function(def, player, element, event)
        local old_count = def.data[player.force]
        local new_count = old_count + 1
        def.data[player.force] = new_count
        element.caption = tostring(new_count)
    end)

Elements.composite = Gui.define("composite")
    :draw(function(def, parent)
        local frame = parent.add{ type = "frame" }      
        def.data[frame] = {
            shared_counter = Elements.shared_counter(frame),
            label = frame.add{ type = "label" },
        }
        return frame
    end)

ExpElement:on_*

Refers to all gui events and ExpElement:on_event for other arbitary events. Gui events are converted from on_gui_ to on_ for examplse on_gui_clicked to on_clicked.

These methods allow you to attach event handlers to your element definition.

For general on_event usage, theres no extra filtering, the handler will be called when the event occurs.

For GUI-specific events, the handler is only called for linked elements. Handlers are automatically linked to any element returned from :draw. You can manually link other elements using :link_element.

If you want to prevent an element from being linked automatically, you can call and return :unlink_element. However, needing to do this might suggest a misalignment in how your functions responsibilities are structured. In the example below, it would be best practice to introduce Elements.title_label which has an on_click handler.

Elements.my_clickable_button = Gui.define("my_clickable_button")
    :draw{
        type = "button",
        caption = "Click Me",
    }
    :on_click(function(def, player, element, event)
        player.print("Clicked!")
    end)

Elements.my_clickable_title = Gui.define("my_clickable_title")
    :draw(function(def, parent)
        local frame = parent.add{ type = "frame" }
        local title = frame.add{ type = "label", caption = "Click Me" }
        def:link_element(title)
        return def:unlink_element(frame)
    end)
    :on_click(function(def, player, element, event)
        player.print("The title was clicked!")
    end)

ExpElement:track_all_elements

The most common use of the GUI iterator is to track all created elements. Therefore track_all_elements was added which will track every element that is returned from your draw function. You can also manually track additional elements with :track_element, and you can exclude elements from tracking using :untrack_element.

Elements.my_tracked_label = Gui.define("my_tracked_label")
    :track_all_elements()
    :draw{
        type = "label",
        caption = "Im tracked by GuiIter",
    }

Element.my_tracked_children = Gui.define("my_tracked_children")
    :draw(function(def, parent)
        local frame = parent.add{ type = "frame" }
        def:track_element(frame.add{ type = "label", caption = "Im tracked 1" })
        def:track_element(frame.add{ type = "label", caption = "Im tracked 2" })
        def:track_element(frame.add{ type = "label", caption = "Im tracked 3" })
        return frame
    end)

Elements.my_sometimes_tracked_label = Gui.define("my_sometimes_tracked_label")
    :track_all_elements()
    :draw(function(def, parent)
        local player = Gui.get_player(parent)
        if player.admin then
            return parent.add{ type = "label", caption = "Im tracked" }
        end
        return def:untrack_element(parent.add{ type = "label", caption = "Im tracked" })
    end)

Gui Iterator

Refers to ExpElement:tracked_elements and ExpElement:online_elements; standalone use of GuiIter is not covered.

Once an element has been tracked using :track_all_elements or :track_element, it can be iterated over using these custom GUI iterators. Each method accepts an optional filter parameter, which can be: LuaPlayer, LuaForce, or an array of LuaPlayer. As their names suggest, :tracked_elements returns all tracked elements while :online_elements limits the results to online players only.

If you're caching data per force, it is more efficient to use a single unfiltered iteration rather than multiple filtered ones.

The naming convention to be followed is:

  • refresh when the first argument is an instance of the element.
  • refresh_all when there using :tracked_elements without a filter.
  • refresh_online when there using :online_elements without a filter.
  • refresh_* when there using :tracked_elements with a filter, e.g. refresh_force.
  • refresh_*_online when there using :online_elements with a filter, e.g. refresh_force_online.
  • update can be used when the new state is dependend on the old state, e.g. incrementing a counter.
  • reset can be used when the element has a default state it can be returned to.
  • save can be used when the current state is stored in some way.
  • *_row when the action applies to a row within a table rather than an element.
  • The name does not indicate if a cache is used, this is because a cache should be used where possible.
function Elements.my_tracked_label.calculate_force_data(force)
    return {
        caption = "I was refreshed: " .. force.name,
    }
end

function Elements.my_tracked_label.refresh_all()
    for player, element in Elements.my_tracked_label:tracked_elements() do
        element.caption = "I was refreshed"
    end
end

function Elements.my_tracked_label.refresh_online()
    for player, element in Elements.my_tracked_label:online_elements() do
        element.caption = "I was refreshed: online"
    end
end

function Elements.my_tracked_label.refresh_force(force)
    local force_data = Elements.my_tracked_label.calculate_force_data(force)
    for player, element in Elements.my_tracked_label:tracked_elements(force) do
        element.caption = force_data.caption
    end
end

-- a different implimention of refresh all with a force cache
function Elements.my_tracked_label.refresh_all()
    local _force_data = {}
    for _, force in pairs(game.forces) do
        _force_data[force.name] = Elements.my_tracked_label.calculate_force_data(force)
    end

    for player, element in Elements.my_tracked_label:tracked_elements() do
        local force_data = _force_data[player.force.name]
        element.caption = force_data.caption
    end
end

-- a different implimention of refresh online with a force cache
function Elements.my_tracked_label.refresh_online()
    local _force_data = {}
    for _, force in pairs(game.forces) do
        if next(force.connected_players) then
            _force_data[force.name] = Elements.my_tracked_label.calculate_force_data(force)
        end
    end

    for player, element in Elements.my_tracked_label:online_elements() do
        local force_data = _force_data[player.force.name]
        element.caption = force_data.caption
    end
end

Toolbar

The toolbar API provides convenience methods for adding toggleable buttons and syncing them with GUI elements in the left flow. This is especially useful for building persistent interfaces.

Gui.toolbar.create_button

Creates a new button on the toolbar, with the option to link it to an element defined in the left flow.

This method also creates a new element definition for the button, so the provided name must be unique within your mod or module. You can attach event handlers (such as on_click or on_button_toggled) to the button as needed.

If a left-side element definition is provided, the button is automatically set to toggle the visibility of that element when clicked.

The button type is set automatically based on the presence of a sprite option. If sprite is defined, it creates a sprite button; otherwise, a standard button is used.

Gui.toolbar.create_button{
    name = "click_me",
    caption = "Click Me!",
}
:on_click(function(def, player, element, event)
    player.print("Clicked!")
end)

Elements.toggle_me =
    Gui.toolbar.create_button{
        name = "toggle_me",
        caption = "Toggle Me!",
        auto_toggle = true,
    }
    :on_button_toggled(function(def, player, element, event)
        player.print("I am now: " .. event.state)
    end)

Gui.add_left_element(Elements.my_frame)
Gui.toolbar.create_button{
    name = "toggle_my_frame",
    caption = "Toggle Me!",
    left_element = Elements.my_frame,
}

Gui.toolbar.set_button_toggled_state

Sets the toggled state of a toolbar button, keeping it in sync with a linked left-side element if one is defined. The element definition must have been previously returned by create_button. If a state argument is not given then this becomes toggle_button_toggled_state. This method does not raise on_click; instead, it raises on_button_toggled

Gui.toolbar.set_button_toggled_state(Elements.toggle_me, true)

Gui.toolbar.set_left_element_visible_state

Sets the visibility state of a left-flow GUI element, and updates the state of a linked toolbar button if one is defined. The element definition must have been previously passed to Gui.add_left_element. If a state argument is not given then this becomes toggle_left_element_visible_state. When a toolbar button is linked, this method also raises on_button_toggled for that button to reflect the change.

Gui.toolbar.set_button_toggled_state(Elements.my_frame, true)