gui module

GUI structuring tools and event handling.

See also

Usage

local gui = require("__flib__.gui")

Functions

build(parent, structures) Build a GUI structure.
dispatch_handlers(event_data) Dispatch GUI handlers for the given event.
update_filters(name, player_index, filters, mode) Add or remove GUI filters to or from a handler or group of handlers.
remove_player_filters(player_index) Remove all GUI filters for the given player.

Concepts

GuiFilter An index or string used for filtering GUI handlers on specific elements.
GuiStructure A series of nested tables used to build a GUI.
GuiOutputTable First output of gui.build, containing saved elements.
GuiOutputFiltersTable Second output of gui.build, containing a handler_name –> filters mapping.

Setup functions

init() Initial setup.
build_lookup_tables() Generate template and handler lookup tables.
check_filter_validity() Purge all filters tied to non-existent handlers from global.
register_handlers() Register handlers for all GUI events to pass through the module.
add_templates(input) Add content to the gui.templates table.
add_handlers(input) Add content to the gui.handlers table.

Fields

handlers GUI handlers, built using gui.add_handlers.
handler_lookup One-dimensional handler lookup table, generated using gui.build_lookup_tables.
handler_groups Mapping of group names to all handlers belonging to them, generated using gui.build_lookup_tables.
templates GUI templates, built using gui.add_templates.
template_lookup One-dimensional template lookup table, generating using gui.build_lookup_tables.

Functions

# build(parent, structures)

Build a GUI structure.

Parameters: Returns: Usage:
gui.build(player.gui.screen, {
  {type="frame", direction="vertical", handlers="window", save_as="window", children={
    -- titlebar
    {type="flow", save_as="titlebar_flow", children={
      {type="label", style="frame_title", caption="Menu", elem_mods={ignored_by_interaction=true}},
      {type="empty-widget", style="flib_titlebar_drag_handle", elem_mods={ignored_by_interaction=true}},
      {type="condition", condition=(not is_dialog_frame), children={
        {template="frame_action_button",
          sprite="utility/close_white",
          handlers="titlebar.close_button",
          save_as="titlebar.close_button"
        }
      }}
    }},
    {type="frame", style="inside_shallow_frame_with_padding", children={
      {type="table", style="slot_table", column_count=10, save_as="content.slot_table"}
    }},
    {type="condition", condition=is_dialog_frame, children={
      {type="flow", style="dialog_buttons_horizontal_flow", children={
        {type="button", style="back_button", caption={"gui.back"}, handlers="footer.back_button"},
        {type="empty-widget",
          style="flib_dialog_footer_drag_handle",
          style_mods={right_margin=0},
          save_as="footer.drag_handle"
        }
      }}
    }}
  }}
})
# dispatch_handlers(event_data)

Dispatch GUI handlers for the given event.

Only needed if not using gui.register_handlers or if overriding a handler registered using that function.

Parameters: Returns:
  • (boolean) If a handler was dispatched.
Usage:
event.on_gui_opened(function(e)
  -- dispatch any matching handlers
  if not gui.dispatch_handlers(e) then
    -- run custom logic if no handlers were dispatched
    inventory.on_gui_opened(e)
  end
end)
# update_filters(name, player_index, filters, mode)

Add or remove GUI filters to or from a handler or group of handlers.

When destroying a GUI element with a filter, be certain to call this function destroying that filter, to avoid memory leaks.

Parameters:
  • name : (string) The handler name, or group name.
  • player_index : (number)
  • filters : (GuiFilter[] or nil) An array of filters, or nil to clear all filters when in remove mode.
  • mode : (string) One of “add” or “remove”.
Usage:
-- add a filter to a group of events
gui.update_filters("main.content.inventory_button", player_index, {"demo_inventory_button"}, "add")
-- remove a filter from a group of events
gui.update_filters("main.content.inventory_button", player_index, {"demo_inventory_button"}, "remove")
-- remove all filters tied to a group of events
gui.update_filters("main.content.inventory_button", player_index, nil, "remove")
-- add a filter to a specific event
gui.update_filters("main.titlebar.drag_handle.on_gui_click", player_index, {elems.drag_handle.index}, "add")
# remove_player_filters(player_index)

Remove all GUI filters for the given player.

Parameters:

Concepts

# GuiFilter

An index or string used for filtering GUI handlers on specific elements.

Defined as one of the following:

  • A string corresponding to an element’s name. Partial names may be matched by separating the common section from the unique section with two underscores __.
  • A uint corresponding to an element’s index.

# GuiStructure

A series of nested tables used to build a GUI.

This is an extension of LuaGuiElement, providing new features and options.

This inherits all required properties from its base LuaGuiElement, i.e. if the type field is sprite-button, the GuiStructure must contain all the fields that a sprite-button LuaGuiElement requires.

There are two new types that are exposed, each of which restrict what parameters can be added:

  • condition: Accepts the condition and children parameters only.
  • tab-and-content: Accepts the tab and content parameters only.

There are a number of new fields that can be applied to a GuiStructure depending on the type:

template

A string corresponding to a GuiStructure in the gui.template_lookup table. The contents of this “template” will be used as a base, and any other paramters in this GuiStructure will overwrite / add to this base.

condition

For condition types only. If true, the children of this GuiStructure will be added to the parent of this GuiStructure. If false, they will not.

tab

For tab-and-content types only. A GuiStructure defining a tab to be placed in a tabbed-pane.

content

For tab-and-content types only. A GuiStructure defining the content that will be shown when the corresponding tab is active.

style_mods

A key –> value dictionary defining modifications to make to the element’s style. Available properties are listed in LuaStyle.

elem_mods

A key –> value dictionary defining modifications to make to the element. Available properties are listed in LuaGuiElement.

handlers

A string corresponding to an item in the gui.handler_groups table. The element will be registered to the handlers in that group, and when the events are raised by the game relating to this element, the corresponding handlers will be dispatched.

save_as

A string defining a table path to save this element to, in the first return value of gui.build. This is a dot-deliminated list of nested table names, followed by a key to save the element as. The module will construct the output table dynamically based on the contents of save_as throughout the structure.

children

An array of GuiStructure that will be added as children of this element.

Usage:
{type="frame", direction="vertical", handlers="window", save_as="window", children={
  -- titlebar
  {type="flow", save_as="titlebar_flow", children={
    {type="label", style="frame_title", caption="Menu", elem_mods={ignored_by_interaction=true}},
    {type="empty-widget", style="flib_titlebar_drag_handle", elem_mods={ignored_by_interaction=true}},
    {type="condition", condition=(not is_dialog_frame), children={
      {template="frame_action_button",
        sprite="utility/close_white",
        handlers="titlebar.close_button",
        save_as="titlebar.close_button"
      }
    }}
  }},
  {type="frame", style="inside_shallow_frame_with_padding", children={
    {type="table", style="slot_table", column_count=10, save_as="content.slot_table"}
  }},
  {type="condition", condition=is_dialog_frame, children={
    {type="flow", style="dialog_buttons_horizontal_flow", children={
      {type="button", style="back_button", caption={"gui.back"}, handlers="footer.back_button"},
      {type="empty-widget",
        style="flib_dialog_footer_drag_handle",
        style_mods={right_margin=0},
        save_as="footer.drag_handle"
      }
    }}
  }}
}}
# GuiOutputTable

First output of gui.build, containing saved elements.

The layout of this table is defined in the GuiStructure using the save_as parameter. Consists of nested tables and key –> LuaGuiElement pairs.

# GuiOutputFiltersTable

Second output of gui.build, containing a handler_name –> filters mapping.

This is useful for knowing which filters are assigned to which handlers for this specific structure, so you can remove them later using gui.update_filters.

Usage:
{
  ["window.on_gui_closed"] = {23},
  ["content_pane.ingredients_list.ingredient_button.on_gui_click"] = {"demo_ingredient_button", 30},
  ["titlebar.close_button.on_gui_click"] = {21}
}

Setup functions

# init()

Initial setup.

Must be called during on_init before any structures are built.

If adding the module to an existing mod, this must be called in on_configuration_changed for that version as well.

This function can also be used to wipe all GUI filters for all players.

# build_lookup_tables()

Generate template and handler lookup tables.

Must be called during on_init after gui.init, and during on_init and on_load after all templates and handlers have been added, but before any structures are built.

# check_filter_validity()

Purge all filters tied to non-existent handlers from global.

Must be called during on_configuration_changed. This function is necessary in order to prevent crashes when a handler was removed between mod versions, but still has filters assigned to it stored in global.

# register_handlers()

Register handlers for all GUI events to pass through the module.

This will loop through all GUI-related defines.events events and register them to gui.dispatch_handlers.

This function should be called in the root scope.

Usage:
-- register handlers for all GUI events
gui.register_handlers()

-- overwrite a handler to add custom logic
event.on_gui_opened(function(e)
  -- pass through the GUI module
  if not gui.dispatch_handlers(e) then
    -- run our custom logic if no handlers were dispatched
    inventory.on_gui_opened(e)
  end
end)
# add_templates(input)

Add content to the gui.templates table.

The table you provide will be merged with the current contents of the table.

This table must be complete before gui.build_lookup_tables is run, and must not be changed afterwards.

NOTE: In order to be detected properly, a template must include either a type key or another template key. If both of these are omitted, the template will not be added to the lookup table and will be unusable.

Parameters: See also:
# add_handlers(input)

Add content to the gui.handlers table.

The table you provide will be merged with the current contents of the table.

This table must be complete before gui.build_lookup_tables is run, and must not be changed afterwards.

Parameters: See also:

Fields

# handlers

GUI handlers, built using gui.add_handlers.

Usage:
-- add handlers
gui.add_handlers{
  -- you can organize the handlers however you like
  base = {
    titlebar = {
      -- this is a handler group for a specific GUI element
      close_button = {
        -- name of `defines.events` event -> handler function
        on_gui_click = function(e)
          log(serpent.block(e))
        end
      }
    }
  }
}

-- use the handlers
gui.build(player.gui.screen, {
  {type="sprite-button",
    style="frame_action_button",
    sprite="utility/close_white",
    handlers="base.titlebar.close_button"
  }
})
# handler_lookup

One-dimensional handler lookup table, generated using gui.build_lookup_tables.

This is what gui.dispatch_handlers uses to retrieve and dispatch handlers tied to certain elements. Each value is a table containing the event ID, handler, groups that handler belongs to, and a mapping of the GUI filters currently assigned to the handler.

Usage:
-- given:
gui.add_handlers{
  base = {
    titlebar = {
      close_button = {
        on_gui_click = function(e)
          log(serpent.block(e))
        end
      }
    }
  }
}
gui.build(player.gui.screen, {
  {type="sprite-button",
    style="frame_action_button",
    sprite="utility/close_white",
    handlers="base.titlebar.close_button"
  }
})

-- contents of gui.handler_lookup will be:
{
  ["base.titlebar.close_button.on_gui_click"] = {
    id = defines.events.on_gui_click,
    handler = function(e)
      __DebugAdapter.print(e)
    end,
    groups = {
      "base",
      "base.titlebar",
      "base.titlebar.close_button"
    },
    filters = {
      [1] = { -- the player's index
        __size = 1,
        [5] = 5 -- the element's index, as both the key and the value
      }
    }
  }
}
# handler_groups

Mapping of group names to all handlers belonging to them, generated using gui.build_lookup_tables.

This is what gui.build uses to find handler groups when using the handlers parameter in a GuiStructure.

Usage:
-- given:
gui.add_handlers{
  base = {
    titlebar = {
      close_button = {
        on_gui_click = function(e)
          log(serpent.block(e))
        end,
        my_custom_event = {id=constants.my_custom_event, handler=function(e)
          log(serpent.block(e))
        }
      }
    },
    content = {
      item_button = {
        on_gui_click = function(e)
          log(serpent.block(e))
        end
      }
    }
  }
}

-- contents of gui.handler_groups will be:
{
  ["base"] = {
    "base.titlebar.close_button.on_gui_click",
    "base.titlebar.close_button.my_custom_event",
    "base.content.item_button.on_gui_click"
  },
  ["base.titlebar"] = {
    "base.titlebar.close_button.on_gui_click",
    "base.titlebar.close_button.my_custom_event"
  },
  ["base.titlebar.close_button"] = {
    "base.titlebar.close_button.on_gui_click",
    "base.titlebar.close_button.my_custom_event"
  },
  ["base.content"] = {
    "base.content.item_button.on_gui_click"
  },
  ["base.content.item_button"] = {
    "base.content.item_button.on_gui_click"
  }
}
# templates

GUI templates, built using gui.add_templates.

Usage:
-- add content to the table
gui.add_templates{
  -- templates are usually defined as GuiStructures
  frame_action_button = {type="sprite-button", style="frame_action_button", mouse_button_filter={"left"}},
  -- you can divide and organize templates however you wish
  pushers = {
    horizontal = {type="empty-widget", style_mods={horizontally_stretchable=true}},
    vertical = {type="empty-widget", style_mods={vertically_stretchable=true}}
  },
  -- templates can also be functions
  list_box_with_label = function(name)
    return
      {type="flow", direction="vertical", children={
        {type="label", style="bold_label", caption={"my-listbox-labels."..name}, save_as="listboxes."..name..".label"},
        {type="list-box", save_as="listboxes."..name..".list_box"}
      }}
  end
}

-- use the templates
gui.build(player.gui.screen, {
  -- use GuiStructure templates using the "template" parameter
  {template="pushers.horizontal"},
  {template="frame_action_button", sprite="utility/close_white"},
  -- use function templates by calling them directly
  -- you do not necessarily need to keep function templates in the `templates` table, but the module supports doing so
  gui.templates.list_box_with_label("ingredients"),
  gui.templates.list_box_with_label("products")
})
# template_lookup

One-dimensional template lookup table, generating using gui.build_lookup_tables.

This is what gui.build uses to find templates when using the templates parameter in a GuiStructure.

Usage:
-- given:
gui.add_templates{
  frame_action_button = {type="sprite-button", style="frame_action_button", mouse_button_filter={"left"}},
  pushers = {
    horizontal = {type="empty-widget", style_mods={horizontally_stretchable=true}},
    vertical = {type="empty-widget", style_mods={vertically_stretchable=true}}
  }
}

-- contents of template_lookup will be:
{
  ["frame_action_button"] = {type="sprite-button", style="frame_action_button", mouse_button_filter={"left"}},
  ["pushers.horizontal"] = {type="empty-widget", style_mods={horizontally_stretchable=true}},
  ["pushers.vertical"] = {type="empty-widget", style_mods={vertically_stretchable=true}}
}