mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 11:35:22 +09:00
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
This commit is contained in:
203
exp_gui/docs/motivation.md
Normal file
203
exp_gui/docs/motivation.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Design motivation
|
||||
|
||||
This document outlines why I created this framework, and the reasoning behind some of the opinionated decisions that shaped its design.
|
||||
|
||||
The motivation came from my experience with existing libraries, which often enforced a strict separation between element definitions, event handling, and GUI-related data.
|
||||
In many cases, these libraries focused solely on element creation, leaving developers to manually manage event filtering and data scoping themselves.
|
||||
|
||||
I found that approach cumbersome and unintuitive.
|
||||
I believed there was a better way, one that embraced a different kind of encapsulation, making the conceptual model easier to understand and work with.
|
||||
And so I created a framework with four distinct and independent parts that all come together with a sense of locality not seen in our libraries.
|
||||
|
||||
Additionally, the guide places greater emphasis on naming conventions and calling patterns, rather than just listing what each function does.
|
||||
These conventions are key to how the framework is expected to be used and are intended to make development feel more cohesive and intuitive.
|
||||
|
||||
At the heart of the framework are four core concepts that bring everything together:
|
||||
|
||||
## ExpElement
|
||||
|
||||
ExpElement serves as the prototype for all element definitions.
|
||||
It's intentionally designed as a wrapper around LuaGuiElement.add and associated event handlers.
|
||||
It takes in definition tables and functions, and returns a function that can be used to create a LuaGuiElement.
|
||||
|
||||
This focused purpose makes it easier to reason about.
|
||||
It also reduces boilerplate, allowing you to concentrate on functionality rather than repetitive setup.
|
||||
|
||||
You can optionally add methods to the definition, such as `add_row` or `refresh`.
|
||||
While these could technically be local functions, including them directly in the definition makes it immediately clear which data they interact with or modify.
|
||||
This enhances both readability and maintainability.
|
||||
|
||||
For example, the following two snippets are conceptually equivalent:
|
||||
|
||||
```lua
|
||||
Elements.my_label = Gui.define("my_label")
|
||||
:draw{
|
||||
type = "label",
|
||||
caption = "Hello, World!",
|
||||
}
|
||||
:style{
|
||||
font_color = { r = 1, g = 0, b = 0 },
|
||||
width = Gui.from_argument(1),
|
||||
}
|
||||
:element_data{
|
||||
foo = "bar"
|
||||
}
|
||||
:on_click(function(def, player, element, event)
|
||||
element.caption = "Clicked!"
|
||||
end)
|
||||
|
||||
function Elements.my_label.reset(my_label)
|
||||
my_label.caption = "Hello, World!"
|
||||
end
|
||||
```
|
||||
|
||||
```lua
|
||||
local my_label_data = GuiData.create("my_label")
|
||||
function Elements.my_label(parent, width)
|
||||
-- :draw
|
||||
local element = parent.add{
|
||||
type = "label",
|
||||
caption = "Hello, World!",
|
||||
}
|
||||
|
||||
-- :style
|
||||
local style = element.style
|
||||
style.font_color = { r = 1, g = 0, b = 0 }
|
||||
style.width = width
|
||||
|
||||
-- :element_data
|
||||
my_label_data[element] = {
|
||||
foo = "bar"
|
||||
}
|
||||
|
||||
-- event handlers
|
||||
local tags = element.tags or {}
|
||||
local event_tags = tags.event_tags or {}
|
||||
event_tags[#event_tags + 1] = "my_label"
|
||||
element.tags = tags
|
||||
|
||||
return element
|
||||
end
|
||||
|
||||
local function my_label_reset(my_label)
|
||||
my_label.caption = "Hello, World!"
|
||||
end
|
||||
|
||||
local function on_gui_click(event)
|
||||
local element = event.element
|
||||
if is_my_label(element) then -- pseudo function to check event_tags
|
||||
element.caption = "Clicked!"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
In the example, I use table-style definitions, which are the most common approach for simple elements and are encouraged wherever possible.
|
||||
Internally, these tables are converted into draw functions, which can also be passed directly if needed.
|
||||
|
||||
You could, of course, write everything into a single "create" function, or even place all logic inside a `:draw` method, but maintaining a separation between these responsibilities serves as a form of clear signposting.
|
||||
This improves readability and makes the structure of your code easier to follow at a glance.
|
||||
|
||||
```lua
|
||||
Elements.my_label = Gui.define("my_label")
|
||||
:draw(function(def, parent, width)
|
||||
return parent.add{
|
||||
type = "label",
|
||||
caption = "Hello, World!",
|
||||
}
|
||||
end)
|
||||
:style(function(def, element, parent, width)
|
||||
return {
|
||||
font_color = { r = 1, g = 0, b = 0 },
|
||||
width = width,
|
||||
}
|
||||
end)
|
||||
:element_data(function(def, element, parent, width)
|
||||
return {
|
||||
foo = "bar"
|
||||
}
|
||||
end)
|
||||
:on_click(function()
|
||||
print("Clicked!")
|
||||
end)
|
||||
```
|
||||
|
||||
## GuiData
|
||||
|
||||
Building on the goal of keeping GUI data close to where it’s used and displayed, I introduced `GuiData`, which is integrated as `ExpElement.data`.
|
||||
Like the other components, its purpose is focused and singular, and it can even be used standalone if it's the only part of the framework you find useful.
|
||||
|
||||
In simple terms, GuiData creates a table in `storage` with a custom `__index` metamethod that enables automatic scoping of data.
|
||||
It also cleans up data when the associated key is destroyed, helping to reduce unnecessary memory usage.
|
||||
|
||||
One common use case, explained earlier in this guide, is storing references to other elements.
|
||||
This approach removes the tight coupling between event handlers and the GUI structure by giving handlers direct access to what they need.
|
||||
|
||||
Additionally, it encourages you to make assumptions explicit by requiring references as arguments.
|
||||
While this pattern can take some getting used to, it makes dependencies much easier to identify and reason about.
|
||||
|
||||
While this example exposes some of the internal mechanics, it should help you understand the convenience and clarity that scoped data access provides.
|
||||
|
||||
```lua
|
||||
local data = GuiData.create("my_data")
|
||||
|
||||
-- data[element] = "foo"
|
||||
storage.gui_data.scopes["my_data"].element_data[element.player_index][element.index] = "foo"
|
||||
|
||||
-- data[player] = "bar"
|
||||
storage.gui_data.scopes["my_data"].player_data[player.index] = "bar"
|
||||
|
||||
-- data[force] = "baz"
|
||||
storage.gui_data.scopes["my_data"].force_data[force.index] = "baz"
|
||||
```
|
||||
|
||||
## GuiIter
|
||||
|
||||
With scoped data easily accessible, it became straightforward to track elements belonging to a specific player, especially for updates or state changes.
|
||||
However, this pattern became so common (and often cluttered `GuiData`) that I created a dedicated iterator: `GuiIter`.
|
||||
|
||||
As with the other modules, GuiIter can be used independently if you like what it offers, or through its integration with ExpElement.
|
||||
|
||||
Whenever an element is created, or at any point, really, it can be registered with the iterator for future access.
|
||||
Retrieval is then handled by applying a filter across all tracked elements, returning them one by one.
|
||||
Don’t worry, the underlying data structure is designed for efficient lookup and automatic cleanup.
|
||||
|
||||
This can be incredibly powerful.
|
||||
It gives you direct access to GUI elements without having to manually navigate from `player.gui`, and the filtering makes it simple to, for example, target only elements belonging to online players in a specific force.
|
||||
|
||||
Below is an example of how GuiIter can be used as a standalone utility:
|
||||
|
||||
```lua
|
||||
local function teammate_counter(player)
|
||||
local frame = player.gui.left.add{ type = "frame" }
|
||||
local label = frame.add{ type = "label", caption = tostring(#player.force.players) }
|
||||
GuiIter.add_element("teammate_counter", label)
|
||||
end
|
||||
|
||||
local function on_player_changed_force(event)
|
||||
local old_force = event.old_force
|
||||
local old_force_count = tostring(#old_force.players)
|
||||
for player, label in GuiIter.get_online_elements("teammate_counter", old_force) do
|
||||
label.caption = caption
|
||||
end
|
||||
|
||||
local new_force = game.get_player(event.player_index).force
|
||||
local new_force_count = tostring(#new_force.players)
|
||||
for player, label in GuiIter.get_online_elements("teammate_counter", new_force) do
|
||||
label.caption = caption
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Toolbar
|
||||
|
||||
While ExpElement ties individual components together into self-contained units, the Toolbar acts as a singleton that manages them all.
|
||||
From an implementation standpoint, it’s split into two parts: one that handles drawing elements when a player joins, and an optional settings menu named "Toolbox".
|
||||
|
||||
The element-drawing functionality is the final piece of the puzzle for eliminating boilerplate and letting you focus on functionality.
|
||||
You simply register an element at a given location, and it gets drawn automatically on player join, it really is that straightforward.
|
||||
|
||||
The optional settings menu provides a standardised way to manage button behaviour, while also giving players control over which buttons are visible.
|
||||
This was born out of necessity: as the number of GUI modules grew, having all of them visible by default became overwhelming.
|
||||
The settings menu solves that by letting players hide modules they don’t need.
|
||||
|
||||

|
||||
457
exp_gui/docs/reference.md
Normal file
457
exp_gui/docs/reference.md
Normal file
@@ -0,0 +1,457 @@
|
||||
# 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 haven’t already, we recommend starting with the [framework guide](../readme.md).
|
||||
It lays the groundwork and gives you the context you’ll need to understand how these methods work together.
|
||||
|
||||
A [full reference](./reference_full.md) 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.
|
||||
|
||||
```lua
|
||||
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 it’s created for all players when they join.
|
||||
Once registered, the framework takes ownership of the element’s 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`](#guitoggle__state) or apply custom logic.
|
||||
|
||||
If you're using the Toolbar or GuiIter, you likely won’t need to manually retrieve your element at all.
|
||||
For example, the Toolbar provides convenience methods like [`Toolbar.get_left_element_visible_state`](#guitoolbarset_left_element_visible_state) and [`Toolbar.set_button_toggled_state`](#guitoolbarset_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.
|
||||
|
||||
```lua
|
||||
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.
|
||||
|
||||
```lua
|
||||
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, you’ll 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`.
|
||||
|
||||
```lua
|
||||
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`.
|
||||
|
||||
```lua
|
||||
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, it’s 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.
|
||||
|
||||
```lua
|
||||
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, there’s 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.
|
||||
|
||||
```lua
|
||||
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`.
|
||||
|
||||
```lua
|
||||
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.
|
||||
|
||||
```lua
|
||||
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.
|
||||
|
||||
```lua
|
||||
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`
|
||||
|
||||
```lua
|
||||
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.
|
||||
|
||||
```lua
|
||||
Gui.toolbar.set_button_toggled_state(Elements.my_frame, true)
|
||||
```
|
||||
485
exp_gui/docs/reference_full.md
Normal file
485
exp_gui/docs/reference_full.md
Normal file
@@ -0,0 +1,485 @@
|
||||
# ExpGui Full API Reference
|
||||
|
||||
If you haven’t already, please read the [framework guide](../readme.md) and [condensed reference](./reference.md) first, this full reference won’t be very useful without that context.
|
||||
|
||||
Additionally, if you find yourself needing to rely heavily on this document, I strongly recommend reading the actual implementation of each function.
|
||||
It will give you a far better understanding than the limited comments provided here.
|
||||
|
||||
[Pre-defined elements](../module/elements.lua), [styles](../module/styles.lua), and elements defined by the toolbar are not included in this reference.
|
||||
|
||||
## Control ([`control.lua`](../module/control.lua))
|
||||
|
||||
It is expected this file is required as `modules/exp_gui` and is assigned to the variable `Gui`.
|
||||
|
||||
### `Gui.define`
|
||||
|
||||
This is a reference to [`ExpElement.new`](#expelementnew)
|
||||
|
||||
It creates new element definations.
|
||||
The name provided must be unqiue to your mod.
|
||||
|
||||
### `Gui.from_argument`
|
||||
|
||||
This is a reference to [`ExpElement.from_argument`](#expelementfrom_argument)
|
||||
|
||||
It is used within defiantion tables (draw, style, and data) to use a value from an argument.
|
||||
The argument can be named or positional, and a default value can be provided.
|
||||
|
||||
### `Gui.from_name`
|
||||
|
||||
This is a reference to [`ExpElement.from_name`](#expelementfrom_name)
|
||||
|
||||
It is used within defiantion tables (draw, style, and data) to use a value from the defination name. The most common use case is `name = Gui.from_name` within a draw table.
|
||||
|
||||
### `Gui.no_return`
|
||||
|
||||
This is a reference to [`ExpElement.no_return`](#expelementno_return)
|
||||
|
||||
It is used exclusively within [`ExpElement:draw`](#expelementdraw) to signal the intental lack of a return value.
|
||||
|
||||
### `Gui.top_elements`
|
||||
|
||||
This is a table of all registered elements for the top flow.
|
||||
|
||||
The keys are ExpElement and the values are the default visibltiy callback / boolean.
|
||||
|
||||
### `Gui.left_elements`
|
||||
|
||||
This is a table of all registered elements for the left flow.
|
||||
|
||||
The keys are ExpElement and the values are the default visibltiy callback / boolean.
|
||||
|
||||
### `Gui.relative_elements`
|
||||
|
||||
This is a table of all registered elements for relative locations.
|
||||
|
||||
The keys are ExpElement and the values are the default visibltiy callback / boolean.
|
||||
|
||||
### `Gui.get_top_flow`
|
||||
|
||||
This is a reference to `mod_gui.get_button_flow`
|
||||
|
||||
It gets the flow where top elements are added to.
|
||||
|
||||
### `Gui.get_left_flow`
|
||||
|
||||
This is a reference to `mod_gui.get_frame_flow`
|
||||
|
||||
It gets the flow where left elements are added to.
|
||||
|
||||
### `Gui._debug`
|
||||
|
||||
After this function is called, all players GUIs will be redrawn every join.
|
||||
This is helpful for structure and style debugging.
|
||||
|
||||
### `Gui.get_player`
|
||||
|
||||
A version of `game.get_player` that accepts LuaGuiElement and events containing `event.element` as a property.
|
||||
|
||||
### `Gui.toggle_enabled_state`
|
||||
|
||||
Toggles the enabled state of the passed LuaGuiElement.
|
||||
It will return the new enabled state.
|
||||
|
||||
The second argument makes this `set_enabled_state`
|
||||
|
||||
### `Gui.toggle_visible_state`
|
||||
|
||||
Toggles the visible state of the passed LuaGuiElement.
|
||||
It will return the new visible state.
|
||||
|
||||
The second argument makes this `set_visible_state`
|
||||
|
||||
### `destroy_if_valid`
|
||||
|
||||
Destories the passed LuaGuiElement.
|
||||
Does nothing if the element is nil or an invalid reference.
|
||||
|
||||
### `Gui.add_top_element`
|
||||
|
||||
Registers an ExpElement to be drawn on the top flow.
|
||||
The second argument is the default visible state, which can be a function.
|
||||
|
||||
### `Gui.add_left_element`
|
||||
|
||||
Registers an ExpElement to be drawn on the left flow.
|
||||
The second argument is the default visible state, which can be a function.
|
||||
|
||||
### `Gui.add_relative_element`
|
||||
|
||||
Registers an ExpElement to be drawn relative to a core GUI.
|
||||
The second argument is the default visible state, which can be a function.
|
||||
|
||||
The core gui is defined with `:draw{ anchor: GuiAnchor }`
|
||||
|
||||
### `Gui.get_top_element`
|
||||
|
||||
Returns the LuaGuiElement for the passed ExpElement.
|
||||
Errors if the element is not registered to the top flow.
|
||||
|
||||
### `Gui.get_left_element`
|
||||
|
||||
Returns the LuaGuiElement for the passed ExpElement.
|
||||
Errors if the element is not registered to the left flow.
|
||||
|
||||
### `Gui.get_relative_element`
|
||||
|
||||
Returns the LuaGuiElement for the passed ExpElement.
|
||||
Errors if the element is not registered to the relative flow.
|
||||
|
||||
### `Gui._ensure_consistency`
|
||||
|
||||
If for any reason registered elements need to be redrawn, this method will handle it.
|
||||
One example is updates within a custom permission system.
|
||||
|
||||
## Gui Data ([`data.lua`](../module/data.lua))
|
||||
|
||||
It is expected this file is required as `modules/exp_gui/data` and is assigned to the variable `GuiData`.
|
||||
|
||||
Alternativly, instances of GuiData exist on all ExpElements as `ExpElement.data`.
|
||||
|
||||
### `GuiData:__index`
|
||||
|
||||
No data is directly stored within an instance of GuiData, instead `__index` will be called and fetch the data from `GuiData._raw`.
|
||||
|
||||
Currently accepted indexes are: LuaGuiElement, LuaPlayer, LuaForce, and "global_data".
|
||||
|
||||
### `GuiData:__newindex`
|
||||
|
||||
No data is directly stored within an instance of GuiData, instead `__newindex` will be called and store the data in `GuiData._raw`.
|
||||
|
||||
Currently accepted indexes are: LuaGuiElement, LuaPlayer, LuaForce.
|
||||
Setting "global_data" is not supported, although settings keys of "global_data" is permitted.
|
||||
|
||||
### `GuiData.create`
|
||||
|
||||
Creates a new instance of GuiData with a given scope.
|
||||
Only a single instance can exist for any scope, use [`GuiData.get`](#guidataget) to retrive existing instances.
|
||||
|
||||
### `GuiData.get`
|
||||
|
||||
Retrives and existing instance of GuiData for a given scope.
|
||||
Only use this if you have a circular dependency, otherwise you should be passing by reference.
|
||||
|
||||
## Gui Iter ([`iter.lua`](../module/iter.lua))
|
||||
|
||||
It is expected this file is required as `modules/exp_gui/iter` and is assigned to the variable `GuiIter`.
|
||||
|
||||
Alternativly, references to GuiIter exist on all ExpElements as `ExpElement:track_element`, `ExpElement:untrack_element`, `ExpElement:tracked_elements`, `ExpElement:online_elements`, and `ExpElement:track_all_elements`
|
||||
|
||||
### `GuiIter.player_elements`
|
||||
|
||||
Iterates all elements for a single player in a given scope.
|
||||
The returned tupple is `LuaPlayer, LuaGuiElement`.
|
||||
|
||||
### `GuiIter.filtered_elements`
|
||||
|
||||
Iterates all elements for the provided players in a given scope.
|
||||
The returned tupple is `LuaPlayer, LuaGuiElement`.
|
||||
|
||||
This method is named "filtered" because it is expected that the player list provided has been filtered on some condition and only elements for players in this list are returned.
|
||||
The optional third argument can be provided to filter to online only if you have not done so yourself.
|
||||
|
||||
### `GuiIter.all_element`
|
||||
|
||||
Iterates all elements for all players in a given scope.
|
||||
The returned tupple is `LuaPlayer, LuaGuiElement`.
|
||||
|
||||
### `GuiIter.get_tracked_elements`
|
||||
|
||||
Iterates all elements for all players in a given scope who pass the provided filter.
|
||||
The returned tupple is `LuaPlayer, LuaGuiElement`.
|
||||
|
||||
The accepted filters are: nil, LuaPlayer, LuaPlayer[], and LuaForce.
|
||||
|
||||
Functions are NOT supported, instead pass an array of players you have pre-filtered.
|
||||
|
||||
### `GuiIter.get_online_elements`
|
||||
|
||||
Iterates all elements for online players in a given scope who pass the provided filter.
|
||||
The returned tupple is `LuaPlayer, LuaGuiElement`.
|
||||
|
||||
The accepted filters are: nil, LuaPlayer, LuaPlayer[], and LuaForce.
|
||||
|
||||
Functions are NOT supported, instead pass an array of players you have pre-filtered.
|
||||
|
||||
### `GuiIter.add_element`
|
||||
|
||||
Adds an element to be tracked within a scope.
|
||||
Elements can be tracked in multiple scopes.
|
||||
|
||||
### `GuiIter.remove_element`
|
||||
|
||||
Remove an element from a scope.
|
||||
Does nothing if the element was not tracked.
|
||||
|
||||
Elements are automatically removed when destoryed.
|
||||
|
||||
## ExpElement ([`prototype.lua`](../module/prototype.lua))
|
||||
|
||||
It is expected this file is required as `modules/exp_gui/prototype` and is assigned to the variable `ExpElement`.
|
||||
|
||||
Alternativly, instances of ExpElement can be created with [`Gui.define`](#guidefine).
|
||||
|
||||
### `ExpElement.no_return`
|
||||
|
||||
It is used exclusively within `ExpElement:draw` to signal the intental lack of a return value.
|
||||
|
||||
Also accessible through [`Gui.no_return`](#guino_return)
|
||||
|
||||
### `ExpElement.from_name`
|
||||
|
||||
It is used within defiantion tables (draw, style, and data) to use a value from the defination name.
|
||||
|
||||
Also accessible through [`Gui.from_name`](#guifrom_name)
|
||||
|
||||
### `ExpElement.from_argument`
|
||||
|
||||
It is used within defiantion tables (draw, style, and data) to use a value from an argument.
|
||||
|
||||
The argument can be named or positional, and a default value can be provided.
|
||||
|
||||
Also accessible through [`Gui.from_argument`](#guifrom_argument)
|
||||
|
||||
### `ExpElement.new`
|
||||
|
||||
It creates new element definations.
|
||||
The name provided must be unqiue to your mod.
|
||||
|
||||
Also accessible through [`Gui.define`](#guidefine)
|
||||
|
||||
### `ExpElement.get`
|
||||
|
||||
Gets the existing ExpElement with the given name.
|
||||
Only use this if you have a circular dependency, otherwise you should be passing by reference.
|
||||
|
||||
### `ExpElement:create`
|
||||
|
||||
Creates a LuaGuiElement following the element defination.
|
||||
|
||||
Also accessible through `__call` allowing direct calls of this table to create an element.
|
||||
|
||||
Order of operations is: [draw](#expelementdraw), [style](#expelementstyle), [element_data](#expelementelement_data), [player_data](#expelementplayer_data), [force_data](#expelementforce_data), [global_data](#expelementglobal_data), [track_element](#expelementtrack_element), [link_element](#expelementlink_element).
|
||||
|
||||
### `ExpElement:track_all_elements`
|
||||
|
||||
When called, `ExpElement:track_element` will be called for all elements at the end of [`ExpElement:create`](#expelementcreate)
|
||||
|
||||
### `ExpElement:empty`
|
||||
|
||||
Defines [`ExpElement:draw`](#expelementdraw) as an empty flow.
|
||||
This is intended to be used when you are first setting up the structure of your gui.
|
||||
When used warnings will be logged, do not rely on this when you want an empty flow.
|
||||
|
||||
### `ExpElement:draw`
|
||||
|
||||
Defines the draw function for you element defination.
|
||||
Successive calls will overwrite previous calls.
|
||||
|
||||
Accepts either a table to be passed to `LuaGuiElement.add` or a function that returns a LuaGuiElement.
|
||||
|
||||
### `ExpElement:style`
|
||||
|
||||
Defines the style function for you element defination.
|
||||
Successive calls will overwrite previous calls.
|
||||
|
||||
Accepts either a table with key values equlaient to LuaStyle, or a function that can return this table, or [`ExpElement:from_argument`](#expelementfrom_argument).
|
||||
|
||||
### `ExpElement:element_data`
|
||||
|
||||
Defines the element data init function for you element defination.
|
||||
Successive calls will overwrite previous calls.
|
||||
|
||||
Accepts any non-function value to deep copy, or a function that can return this value, or [`ExpElement:from_argument`](#expelementfrom_argument).
|
||||
|
||||
When a non-function value is used or returned, it will not overwrite existing data.
|
||||
If you want this behaviour then modify the data directly in your function rather than returning a value.
|
||||
|
||||
### `ExpElement:player_data`
|
||||
|
||||
Defines the player data init function for you element defination.
|
||||
Successive calls will overwrite previous calls.
|
||||
|
||||
Accepts any non-function value to deep copy, or a function that can return this value, or [`ExpElement:from_argument`](#expelementfrom_argument).
|
||||
|
||||
When a non-function value is used or returned, it will not overwrite existing data.
|
||||
If you want this behaviour then modify the data directly in your function rather than returning a value.
|
||||
|
||||
### `ExpElement:force_data`
|
||||
|
||||
Defines the force data init function for you element defination.
|
||||
Successive calls will overwrite previous calls.
|
||||
|
||||
Accepts any non-function value to deep copy, or a function that can return this value, or [`ExpElement:from_argument`](#expelementfrom_argument).
|
||||
|
||||
When a non-function value is used or returned, it will not overwrite existing data.
|
||||
If you want this behaviour then modify the data directly in your function rather than returning a value.
|
||||
|
||||
### `ExpElement:global_data`
|
||||
|
||||
Defines the global data init function for you element defination.
|
||||
Successive calls will overwrite previous calls.
|
||||
|
||||
Accepts only a table value to deep copy, or a function that can return a table, or [`ExpElement:from_argument`](#expelementfrom_argument).
|
||||
|
||||
When a table is used or returned, it will not overwrite existing data.
|
||||
If you want this behaviour then modify the data directly in your function rather than returning a table.
|
||||
|
||||
### `ExpElement:tracked_elements`
|
||||
|
||||
A proxy call to [`GuiIter.get_tracked_elements`](#guiiterget_tracked_elements) with the scope pre-populated.
|
||||
|
||||
### `ExpElement:online_elements`
|
||||
|
||||
A proxy call to [`GuiIter.get_online_elements`](#guiiterget_online_elements) with the scope pre-populated.
|
||||
|
||||
### `ExpElement:track_element`
|
||||
|
||||
A proxy call to [`GuiIter.add_element`](#guiiteradd_element) with the scope pre-populated.
|
||||
|
||||
### `ExpElement:untrack_element`
|
||||
|
||||
A proxy call to [`GuiIter.remove_element`](#guiiterremove_element) with the scope pre-populated.
|
||||
|
||||
If returned from a draw function then [`ExpElement:track_all_elements`](#expelementtrack_all_elements) is ignored.
|
||||
|
||||
### `ExpElement:link_element`
|
||||
|
||||
Links an element to this define in order to trigger event handlers.
|
||||
|
||||
Should only be used to link additional elements because elements returned from draw are linked automatically.
|
||||
|
||||
### `ExpElement:unlink_element`
|
||||
|
||||
Unlinks an element from this define in order to prevent event handlers triggering.
|
||||
|
||||
If returned from a draw function then automatic linking will be prevented.
|
||||
|
||||
### `ExpElement:raise_event`
|
||||
|
||||
Raise an event on this define.
|
||||
|
||||
This can be useful for defering events to other definiations or for raising custom events.
|
||||
|
||||
### `ExpElement:on_event`
|
||||
|
||||
Allows connecting to arbitary events.
|
||||
Multiple handlers are supported.
|
||||
|
||||
### `ExpElement:on_checked_state_changed`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_checked_state_changed`.
|
||||
|
||||
### `ExpElement:on_click`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_click`.
|
||||
|
||||
### `ExpElement:on_closed`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_closed`.
|
||||
|
||||
### `ExpElement:on_confirmed`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_confirmed`.
|
||||
|
||||
### `ExpElement:on_elem_changed`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_elem_changed`.
|
||||
|
||||
### `ExpElement:on_hover`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_hover`.
|
||||
|
||||
### `ExpElement:on_leave`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_leave`.
|
||||
|
||||
### `ExpElement:on_location_changed`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_location_changed`.
|
||||
|
||||
### `ExpElement:on_opened`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_opened`.
|
||||
|
||||
### `ExpElement:on_selected_tab_changed`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_selected_tab_changed`.
|
||||
|
||||
### `ExpElement:on_selection_state_changed`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_selection_state_changed`.
|
||||
|
||||
### `ExpElement:on_switch_state_changed`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_switch_state_changed`.
|
||||
|
||||
### `ExpElement:on_text_changed`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_text_changed`.
|
||||
|
||||
### `ExpElement:on_value_changed`
|
||||
|
||||
Connects a handler to `defines.events.on_gui_value_changed`.
|
||||
|
||||
## Toolbar ([`toolbar.lua`](../module/toolbar.lua))
|
||||
|
||||
It is expected this file is required as `modules/exp_gui/toolbar` and is assigned to the variable `Toolbar`.
|
||||
|
||||
Alternativly, it can be accessed through `Gui.toolbar`.
|
||||
|
||||
A point of clarifcation, the "toolbar" in this framework refers to the top flow which may also be refered to as the "favoruites bar", while the "toolbox" is the custom gui for configruing the toolbar.
|
||||
|
||||
### `Toolbar.set_visible_state`
|
||||
|
||||
Sets the visible state of the toolbar for a player.
|
||||
|
||||
If a state is not given then this becomes `toggle_visible_state` and returns the new visible state.
|
||||
|
||||
The name difference compared to [`Gui.toggle_visible_state`](#guitoggle_visible_state) despite same beaviour is due to the expected use case for each function.
|
||||
|
||||
### `Toolbar.get_visible_state`
|
||||
|
||||
Gets the visible state of the toolbar for a player.
|
||||
|
||||
### `Toolbar.set_button_toggled_state`
|
||||
|
||||
Sets the toggled state for a toolbar button. It is expected that the element define is given not the LuaGuiElement because all instances of a toolbar button should be in sync, and the toolbar does not expose the LuaGuiElement except though [`Gui.get_top_element`](#guiget_top_element).
|
||||
|
||||
If a state is not given then this becomes `toggle_button_toggled_state` and returns the new visible state.
|
||||
|
||||
### `Toolbar.get_button_toggled_state`
|
||||
|
||||
Gets the toggled state for a toolbar button. It is expected that the element define is given not the LuaGuiElement because all instances of a toolbar button should be in sync, and the toolbar does not expose the LuaGuiElement except though [`Gui.get_top_element`](#guiget_top_element).
|
||||
|
||||
### `Toolbar.set_left_element_visible_state`
|
||||
|
||||
Sets the visible state for a left element. It is expected that the element define is given not the LuaGuiElement because only a single left element can exist, and the toolbar does not expose the LuaGuiElement except though [`Gui.get_left_element`](#guiget_left_element).
|
||||
|
||||
If a state is not given then this becomes `toggle_left_element_visible_state` and returns the new visible state.
|
||||
|
||||
### `Toolbar.get_left_element_visible_state`
|
||||
|
||||
Gets the visible state for a left element. It is expected that the element define is given not the LuaGuiElement because only a single left element can exist, and the toolbar does not expose the LuaGuiElement except though [`Gui.get_left_element`](#guiget_left_element).
|
||||
|
||||
### `Toolbar.has_visible_buttons`
|
||||
|
||||
Returns true if the player has any visible toolbar buttons.
|
||||
|
||||
### `Toolbar.has_visible_left_elements`
|
||||
|
||||
Returns true if the player has any visible left elements.
|
||||
|
||||
### `Toolbar.create_button`
|
||||
|
||||
Creates a new element define representing a toolbar button.
|
||||
|
||||
The new button is automaticaly registered to the top flow, has the option to auto toggle, and the option to have a left element linked to it. As this creates a new element define the name provided must be unqiue to your mod.
|
||||
|
||||
### `Toolbar.set_state`
|
||||
|
||||
Sets the whole state of the toolbar for a player, the value given should be a value previously returned from [`Toolbar.get_state`](#toolbarget_state).
|
||||
|
||||
### `Toolbar.get_state`
|
||||
|
||||
Gets the whol state of the toolbar for a player which can later be restored with [`Toolbar.set_state`](#toolbarset_state).
|
||||
BIN
exp_gui/docs/toolbox.png
Normal file
BIN
exp_gui/docs/toolbox.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 241 KiB |
@@ -3,37 +3,48 @@ local Storage = require("modules/exp_util/storage")
|
||||
|
||||
local ExpElement = require("modules/exp_gui/prototype")
|
||||
|
||||
--- @alias ExpGui.VisibleCallback fun(player: LuaPlayer, element: LuaGuiElement): boolean
|
||||
--- @alias Gui.VisibleCallback fun(player: LuaPlayer, element: LuaGuiElement): boolean
|
||||
|
||||
--- @class ExpGui.player_elements
|
||||
--- @class Gui.player_elements
|
||||
--- @field top table<string, LuaGuiElement?>
|
||||
--- @field left table<string, LuaGuiElement?>
|
||||
--- @field relative table<string, LuaGuiElement?>
|
||||
|
||||
--- @type table<uint, ExpGui.player_elements>
|
||||
--- @type table<uint, Gui.player_elements> | table<"_debug", boolean>
|
||||
local player_elements = {}
|
||||
Storage.register(player_elements, function(tbl)
|
||||
player_elements = tbl
|
||||
end)
|
||||
|
||||
--- @class ExpGui
|
||||
local ExpGui = {
|
||||
element = ExpElement.create,
|
||||
property_from_arg = ExpElement.property_from_arg,
|
||||
property_from_name = ExpElement.property_from_name,
|
||||
top_elements = {}, --- @type table<ExpElement, ExpGui.VisibleCallback | boolean>
|
||||
left_elements = {}, --- @type table<ExpElement, ExpGui.VisibleCallback | boolean>
|
||||
relative_elements = {}, --- @type table<ExpElement, ExpGui.VisibleCallback | boolean>
|
||||
--- @class Gui
|
||||
local Gui = {
|
||||
define = ExpElement.new,
|
||||
from_argument = ExpElement.from_argument,
|
||||
from_name = ExpElement.from_name,
|
||||
no_return = ExpElement.no_return,
|
||||
top_elements = {}, --- @type table<ExpElement, Gui.VisibleCallback | boolean>
|
||||
left_elements = {}, --- @type table<ExpElement, Gui.VisibleCallback | boolean>
|
||||
relative_elements = {}, --- @type table<ExpElement, Gui.VisibleCallback | boolean>
|
||||
}
|
||||
|
||||
local mod_gui = require("mod-gui")
|
||||
ExpGui.get_top_flow = mod_gui.get_button_flow
|
||||
ExpGui.get_left_flow = mod_gui.get_frame_flow
|
||||
Gui.get_top_flow = mod_gui.get_button_flow
|
||||
Gui.get_left_flow = mod_gui.get_frame_flow
|
||||
|
||||
--- Set the gui to redraw all elements on join, helps with style debugging
|
||||
--- @param state boolean
|
||||
function Gui._debug(state)
|
||||
if state == nil then
|
||||
player_elements._debug = true
|
||||
else
|
||||
player_elements._debug = state
|
||||
end
|
||||
end
|
||||
|
||||
--- Get a player from an element or gui event
|
||||
--- @param input LuaGuiElement | { player_index: uint } | { element: LuaGuiElement }
|
||||
--- @return LuaPlayer
|
||||
function ExpGui.get_player(input)
|
||||
function Gui.get_player(input)
|
||||
if type(input) == "table" and not input.player_index then
|
||||
return assert(game.get_player(input.element.player_index))
|
||||
end
|
||||
@@ -44,7 +55,7 @@ end
|
||||
--- @param element LuaGuiElement
|
||||
--- @param state boolean?
|
||||
--- @return boolean
|
||||
function ExpGui.toggle_enabled_state(element, state)
|
||||
function Gui.toggle_enabled_state(element, state)
|
||||
if not element or not element.valid then return false end
|
||||
if state == nil then
|
||||
state = not element.enabled
|
||||
@@ -57,7 +68,7 @@ end
|
||||
--- @param element LuaGuiElement
|
||||
--- @param state boolean?
|
||||
--- @return boolean
|
||||
function ExpGui.toggle_visible_state(element, state)
|
||||
function Gui.toggle_visible_state(element, state)
|
||||
if not element or not element.valid then return false end
|
||||
if state == nil then
|
||||
state = not element.visible
|
||||
@@ -68,41 +79,41 @@ end
|
||||
|
||||
--- Destroy an element if it exists and is valid
|
||||
--- @param element LuaGuiElement?
|
||||
function ExpGui.destroy_if_valid(element)
|
||||
function Gui.destroy_if_valid(element)
|
||||
if not element or not element.valid then return end
|
||||
element.destroy()
|
||||
end
|
||||
|
||||
--- Register a element define to be drawn to the top flow on join
|
||||
--- @param define ExpElement
|
||||
--- @param visible ExpGui.VisibleCallback | boolean | nil
|
||||
function ExpGui.add_top_element(define, visible)
|
||||
assert(ExpGui.top_elements[define.name] == nil, "Element is already added to the top flow")
|
||||
ExpGui.top_elements[define] = visible or false
|
||||
--- @param visible Gui.VisibleCallback | boolean | nil
|
||||
function Gui.add_top_element(define, visible)
|
||||
assert(Gui.top_elements[define.name] == nil, "Element is already added to the top flow")
|
||||
Gui.top_elements[define] = visible or false
|
||||
end
|
||||
|
||||
--- Register a element define to be drawn to the left flow on join
|
||||
--- @param define ExpElement
|
||||
--- @param visible ExpGui.VisibleCallback | boolean | nil
|
||||
function ExpGui.add_left_element(define, visible)
|
||||
assert(ExpGui.left_elements[define.name] == nil, "Element is already added to the left flow")
|
||||
ExpGui.left_elements[define] = visible or false
|
||||
--- @param visible Gui.VisibleCallback | boolean | nil
|
||||
function Gui.add_left_element(define, visible)
|
||||
assert(Gui.left_elements[define.name] == nil, "Element is already added to the left flow")
|
||||
Gui.left_elements[define] = visible or false
|
||||
|
||||
end
|
||||
|
||||
--- Register a element define to be drawn to the relative flow on join
|
||||
--- @param define ExpElement
|
||||
--- @param visible ExpGui.VisibleCallback | boolean | nil
|
||||
function ExpGui.add_relative_element(define, visible)
|
||||
assert(ExpGui.relative_elements[define.name] == nil, "Element is already added to the relative flow")
|
||||
ExpGui.relative_elements[define] = visible or false
|
||||
--- @param visible Gui.VisibleCallback | boolean | nil
|
||||
function Gui.add_relative_element(define, visible)
|
||||
assert(Gui.relative_elements[define.name] == nil, "Element is already added to the relative flow")
|
||||
Gui.relative_elements[define] = visible or false
|
||||
end
|
||||
|
||||
--- Register a element define to be drawn to the top flow on join
|
||||
--- @param define ExpElement
|
||||
--- @param player LuaPlayer
|
||||
--- @return LuaGuiElement
|
||||
function ExpGui.get_top_element(define, player)
|
||||
function Gui.get_top_element(define, player)
|
||||
return assert(player_elements[player.index].top[define.name], "Element is not on the top flow")
|
||||
end
|
||||
|
||||
@@ -110,7 +121,7 @@ end
|
||||
--- @param define ExpElement
|
||||
--- @param player LuaPlayer
|
||||
--- @return LuaGuiElement
|
||||
function ExpGui.get_left_element(define, player)
|
||||
function Gui.get_left_element(define, player)
|
||||
return assert(player_elements[player.index].left[define.name], "Element is not on the left flow")
|
||||
end
|
||||
|
||||
@@ -118,13 +129,13 @@ end
|
||||
--- @param define ExpElement
|
||||
--- @param player LuaPlayer
|
||||
--- @return LuaGuiElement
|
||||
function ExpGui.get_relative_element(define, player)
|
||||
function Gui.get_relative_element(define, player)
|
||||
return assert(player_elements[player.index].relative[define.name], "Element is not on the relative flow")
|
||||
end
|
||||
|
||||
--- Ensure all the correct elements are visible and exist
|
||||
--- @param player LuaPlayer
|
||||
--- @param element_defines table<ExpElement, ExpGui.VisibleCallback | boolean>
|
||||
--- @param element_defines table<ExpElement, Gui.VisibleCallback | boolean>
|
||||
--- @param elements table<string, LuaGuiElement?>
|
||||
--- @param parent LuaGuiElement
|
||||
local function ensure_elements(player, element_defines, elements, parent)
|
||||
@@ -135,7 +146,7 @@ local function ensure_elements(player, element_defines, elements, parent)
|
||||
if not element or not element.valid then
|
||||
element = assert(define(parent), "Element define did not return an element: " .. define.name)
|
||||
elements[define.name] = element
|
||||
|
||||
|
||||
if type(visible) == "function" then
|
||||
visible = visible(player, element)
|
||||
end
|
||||
@@ -153,39 +164,45 @@ end
|
||||
|
||||
--- Ensure all elements have been created
|
||||
--- @param event EventData.on_player_created | EventData.on_player_joined_game
|
||||
function ExpGui._ensure_consistency(event)
|
||||
function Gui._ensure_consistency(event)
|
||||
local player = assert(game.get_player(event.player_index))
|
||||
local elements = player_elements[event.player_index]
|
||||
if not elements then
|
||||
elements = {
|
||||
top = {},
|
||||
left = {},
|
||||
relative = {},
|
||||
}
|
||||
player_elements[event.player_index] = elements
|
||||
local elements = player_elements[event.player_index] or {
|
||||
top = {},
|
||||
left = {},
|
||||
relative = {},
|
||||
}
|
||||
player_elements[event.player_index] = elements
|
||||
|
||||
if player_elements._debug and event.name == defines.events.on_player_joined_game then
|
||||
log("Gui debug active, clearing gui for: " .. player.name)
|
||||
player.gui.relative.clear()
|
||||
player.gui.screen.clear()
|
||||
player.gui.center.clear()
|
||||
player.gui.left.clear()
|
||||
player.gui.top.clear()
|
||||
end
|
||||
|
||||
ensure_elements(player, ExpGui.top_elements, elements.top, ExpGui.get_top_flow(player))
|
||||
ensure_elements(player, ExpGui.left_elements, elements.left, ExpGui.get_left_flow(player))
|
||||
ensure_elements(player, ExpGui.relative_elements, elements.relative, player.gui.relative)
|
||||
ensure_elements(player, Gui.top_elements, elements.top, Gui.get_top_flow(player))
|
||||
ensure_elements(player, Gui.left_elements, elements.left, Gui.get_left_flow(player))
|
||||
ensure_elements(player, Gui.relative_elements, elements.relative, player.gui.relative)
|
||||
|
||||
-- This check isn't needed, but allows the toolbar file to be deleted without modifying any lib code
|
||||
if ExpGui.toolbar then
|
||||
if Gui.toolbar then
|
||||
--- @diagnostic disable-next-line invisible
|
||||
ExpGui.toolbar._create_elements(player)
|
||||
Gui.toolbar._create_elements(player)
|
||||
--- @diagnostic disable-next-line invisible
|
||||
ExpGui.toolbar._ensure_consistency(player)
|
||||
Gui.toolbar._ensure_consistency(player)
|
||||
end
|
||||
end
|
||||
|
||||
--- Rerun the visible check for relative elements
|
||||
--- @param event EventData.on_gui_opened
|
||||
local function on_gui_opened(event)
|
||||
local player = ExpGui.get_player(event)
|
||||
local player = Gui.get_player(event)
|
||||
local original_element = event.element
|
||||
|
||||
for define, visible in pairs(ExpGui.relative_elements) do
|
||||
local element = ExpGui.get_relative_element(define, player)
|
||||
for define, visible in pairs(Gui.relative_elements) do
|
||||
local element = Gui.get_relative_element(define, player)
|
||||
|
||||
if type(visible) == "function" then
|
||||
visible = visible(player, element)
|
||||
@@ -204,10 +221,10 @@ end
|
||||
|
||||
local e = defines.events
|
||||
local events = {
|
||||
[e.on_player_created] = ExpGui._ensure_consistency,
|
||||
[e.on_player_joined_game] = ExpGui._ensure_consistency,
|
||||
[e.on_player_created] = Gui._ensure_consistency,
|
||||
[e.on_player_joined_game] = Gui._ensure_consistency,
|
||||
[e.on_gui_opened] = on_gui_opened,
|
||||
}
|
||||
|
||||
ExpGui.events = events
|
||||
return ExpGui
|
||||
Gui.events = events
|
||||
return Gui
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--[[-- ExpGui - GuiData
|
||||
--[[-- Gui - GuiData
|
||||
Provides a method of storing data for elements, players, and forces under a given scope.
|
||||
This is not limited to GUI element definitions but this is the most common use case.
|
||||
]]
|
||||
@@ -6,10 +6,10 @@ This is not limited to GUI element definitions but this is the most common use c
|
||||
local ExpUtil = require("modules/exp_util")
|
||||
local Storage = require("modules/exp_util/storage")
|
||||
|
||||
--- @type table<string, ExpGui.GuiData_Raw>
|
||||
--- @type table<string, GuiData_Raw>
|
||||
local scope_data = {}
|
||||
|
||||
--- @type table<string, ExpGui.GuiData_Internal>
|
||||
--- @type table<string, GuiData_Internal>
|
||||
local registered_scopes = {}
|
||||
|
||||
--- @type table<uint, uint> Reg -> Player Index
|
||||
@@ -38,12 +38,12 @@ Storage.register({
|
||||
end
|
||||
end)
|
||||
|
||||
--- @class ExpGui_GuiData
|
||||
--- @class _GuiData
|
||||
local GuiData = {
|
||||
_scopes = registered_scopes,
|
||||
}
|
||||
|
||||
--- @class ExpGui.GuiData_Raw
|
||||
--- @class GuiData_Raw
|
||||
--- @field element_data table<uint, table<uint, any>?>?
|
||||
--- @field player_data table<uint, any>?
|
||||
--- @field force_data table<uint, any>?
|
||||
@@ -51,13 +51,13 @@ local GuiData = {
|
||||
-- This class has no prototype methods
|
||||
-- Keep this in sync with DataKeys to block arbitrary strings
|
||||
|
||||
--- @class ExpGui.GuiData_Internal
|
||||
--- @class GuiData_Internal
|
||||
--- @field _scope string
|
||||
--- @field _raw ExpGui.GuiData_Raw
|
||||
--- @field _raw GuiData_Raw
|
||||
-- This class has no prototype methods
|
||||
-- Do add keys to _raw without also referencing scope_data
|
||||
|
||||
--- @class ExpGui.GuiData: ExpGui.GuiData_Internal
|
||||
--- @class GuiData: GuiData_Internal
|
||||
--- @field element_data table<uint, table<uint, any>>
|
||||
--- @field player_data table<uint, any>
|
||||
--- @field force_data table<uint, any>
|
||||
@@ -70,7 +70,7 @@ GuiData._metatable = {
|
||||
}
|
||||
|
||||
--- Return the index for a given key
|
||||
--- @param self ExpGui.GuiData_Internal
|
||||
--- @param self GuiData_Internal
|
||||
--- @param key DataKeys | DataKey
|
||||
--- @return any
|
||||
function GuiData._metatable.__index(self, key)
|
||||
@@ -105,7 +105,7 @@ end
|
||||
|
||||
--- Set the value index of a given key
|
||||
-- Internal type is not used here to allow for creation of storage
|
||||
--- @param self ExpGui.GuiData
|
||||
--- @param self GuiData
|
||||
--- @param key DataKey
|
||||
--- @param value unknown
|
||||
function GuiData._metatable.__newindex(self, key, value)
|
||||
@@ -134,7 +134,7 @@ end
|
||||
|
||||
--- Create the data object for a given scope
|
||||
--- @param scope string
|
||||
--- @return ExpGui.GuiData
|
||||
--- @return GuiData
|
||||
function GuiData.create(scope)
|
||||
ExpUtil.assert_not_runtime()
|
||||
assert(GuiData._scopes[scope] == nil, "Scope already exists with name: " .. scope)
|
||||
@@ -145,15 +145,15 @@ function GuiData.create(scope)
|
||||
}
|
||||
|
||||
GuiData._scopes[scope] = instance
|
||||
--- @cast instance ExpGui.GuiData
|
||||
--- @cast instance GuiData
|
||||
return setmetatable(instance, GuiData._metatable)
|
||||
end
|
||||
|
||||
--- Get the link to an existing data scope
|
||||
--- @param scope string
|
||||
--- @return ExpGui.GuiData
|
||||
--- @return GuiData
|
||||
function GuiData.get(scope)
|
||||
return GuiData._scopes[scope] --[[ @as ExpGui.GuiData ]]
|
||||
return GuiData._scopes[scope] --[[ @as GuiData ]]
|
||||
end
|
||||
|
||||
--- Used to clean up data from destroyed elements
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
--- @class ExpGui
|
||||
local ExpGui = require("modules/exp_gui")
|
||||
--- @class Gui
|
||||
local Gui = require("modules/exp_gui")
|
||||
|
||||
--- @class ExpGui.elements
|
||||
local elements = {}
|
||||
ExpGui.elements = elements
|
||||
--- @class Gui.elements
|
||||
local Elements = {}
|
||||
Gui.elements = Elements
|
||||
|
||||
--- @class Gui.elements.aligned_flow.opts
|
||||
--- @field horizontal_align ("left" | "center" | "right")?
|
||||
--- @field vertical_align ("top" | "center" | "bottom")?
|
||||
|
||||
--- A flow which aligns its content as specified
|
||||
elements.aligned_flow = ExpGui.element("aligned_flow")
|
||||
--- @class Gui.elements.aligned_flow: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement, opts: Gui.elements.aligned_flow.opts?): LuaGuiElement
|
||||
Elements.aligned_flow = Gui.define("aligned_flow")
|
||||
:draw{
|
||||
type = "flow",
|
||||
name = ExpGui.property_from_arg("name"),
|
||||
name = Gui.from_argument("name"),
|
||||
}
|
||||
:style(function(def, element, parent, opts)
|
||||
opts = opts or {}
|
||||
opts = opts or {} --- @cast opts Gui.elements.aligned_flow.opts
|
||||
local vertical_align = opts.vertical_align or "center"
|
||||
local horizontal_align = opts.horizontal_align or "right"
|
||||
return {
|
||||
@@ -22,15 +28,18 @@ elements.aligned_flow = ExpGui.element("aligned_flow")
|
||||
vertically_stretchable = vertical_align ~= "center",
|
||||
horizontally_stretchable = horizontal_align ~= "center",
|
||||
}
|
||||
end)
|
||||
end) --[[ @as any ]]
|
||||
|
||||
--- A solid horizontal white bar element
|
||||
elements.bar = ExpGui.element("bar")
|
||||
--- @class Gui.elements.bar: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement, width: number?): LuaGuiElement
|
||||
Elements.bar = Gui.define("bar")
|
||||
:draw{
|
||||
type = "progressbar",
|
||||
value = 1,
|
||||
}
|
||||
:style(function(def, element, parent, width)
|
||||
--- @cast width number?
|
||||
local style = element.style
|
||||
style.color = { r = 255, g = 255, b = 255 }
|
||||
style.height = 4
|
||||
@@ -39,50 +48,58 @@ elements.bar = ExpGui.element("bar")
|
||||
else
|
||||
style.horizontally_stretchable = true
|
||||
end
|
||||
end)
|
||||
end) --[[ @as any ]]
|
||||
|
||||
--- A label which is centered
|
||||
elements.centered_label = ExpGui.element("centered_label")
|
||||
--- @class Gui.elements.centered_label: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement, width: number, caption: LocalisedString, tooltip: LocalisedString?): LuaGuiElement
|
||||
Elements.centered_label = Gui.define("centered_label")
|
||||
:draw{
|
||||
type = "label",
|
||||
caption = ExpGui.property_from_arg(2),
|
||||
tooltip = ExpGui.property_from_arg(3),
|
||||
caption = Gui.from_argument(2),
|
||||
tooltip = Gui.from_argument(3),
|
||||
}
|
||||
:style{
|
||||
horizontal_align = "center",
|
||||
single_line = false,
|
||||
width = ExpGui.property_from_arg(1),
|
||||
}
|
||||
width = Gui.from_argument(1),
|
||||
} --[[ @as any ]]
|
||||
|
||||
--- A label which has two white bars on either side of it
|
||||
elements.title_label = ExpGui.element("title_label")
|
||||
--- @class Gui.elements.title_label: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement, width: number, caption: LocalisedString, tooltip: LocalisedString?): LuaGuiElement
|
||||
Elements.title_label = Gui.define("title_label")
|
||||
:draw(function(def, parent, width, caption, tooltip)
|
||||
local flow =
|
||||
parent.add{
|
||||
type = "flow"
|
||||
}
|
||||
|
||||
--- @cast width number
|
||||
--- @cast caption LocalisedString
|
||||
--- @cast tooltip LocalisedString?
|
||||
local flow = parent.add{
|
||||
type = "flow"
|
||||
}
|
||||
|
||||
flow.style.vertical_align = "center"
|
||||
elements.bar(flow, width)
|
||||
|
||||
local label =
|
||||
flow.add{
|
||||
type = "label",
|
||||
style = "frame_title",
|
||||
caption = caption,
|
||||
tooltip = tooltip,
|
||||
}
|
||||
Elements.bar(flow, width)
|
||||
|
||||
elements.bar(flow)
|
||||
local label = flow.add{
|
||||
type = "label",
|
||||
style = "frame_title",
|
||||
caption = caption,
|
||||
tooltip = tooltip,
|
||||
}
|
||||
|
||||
Elements.bar(flow)
|
||||
|
||||
return label
|
||||
end)
|
||||
end) --[[ @as any ]]
|
||||
|
||||
--- A fixed size vertical scroll pane
|
||||
elements.scroll_pane = ExpGui.element("scroll_pane")
|
||||
--- @class Gui.elements.scroll_pane: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement, maximal_height: number, name: string?): LuaGuiElement
|
||||
Elements.scroll_pane = Gui.define("scroll_pane")
|
||||
:draw{
|
||||
type = "scroll-pane",
|
||||
name = ExpGui.property_from_arg(2),
|
||||
name = Gui.from_argument(2),
|
||||
direction = "vertical",
|
||||
horizontal_scroll_policy = "never",
|
||||
vertical_scroll_policy = "auto",
|
||||
@@ -90,14 +107,19 @@ elements.scroll_pane = ExpGui.element("scroll_pane")
|
||||
}
|
||||
:style{
|
||||
padding = { 1, 3 },
|
||||
maximal_height = ExpGui.property_from_arg(1),
|
||||
maximal_height = Gui.from_argument(1),
|
||||
horizontally_stretchable = true,
|
||||
}
|
||||
} --[[ @as any ]]
|
||||
|
||||
--- A fixed size vertical scroll pane containing a table
|
||||
elements.scroll_table = ExpGui.element("scroll_table")
|
||||
:draw(function(def, parent, height, column_count, scroll_name)
|
||||
local scroll_pane = elements.scroll_pane(parent, height, scroll_name)
|
||||
--- @class Gui.elements.scroll_table: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement, maximal_height: number, column_count: number, scroll_name: string?): LuaGuiElement
|
||||
Elements.scroll_table = Gui.define("scroll_table")
|
||||
:draw(function(def, parent, maximal_height, column_count, scroll_name)
|
||||
--- @cast maximal_height number
|
||||
--- @cast column_count number
|
||||
--- @cast scroll_name string?
|
||||
local scroll_pane = Elements.scroll_pane(parent, maximal_height, scroll_name)
|
||||
|
||||
return scroll_pane.add{
|
||||
type = "table",
|
||||
@@ -106,25 +128,25 @@ elements.scroll_table = ExpGui.element("scroll_table")
|
||||
}
|
||||
end)
|
||||
:style{
|
||||
padding = 0,
|
||||
cell_padding = 0,
|
||||
padding = { 3, 2 },
|
||||
cell_padding = 1,
|
||||
vertical_align = "center",
|
||||
horizontally_stretchable = true,
|
||||
}
|
||||
} --[[ @as any ]]
|
||||
|
||||
--- A container frame
|
||||
elements.container = ExpGui.element("container")
|
||||
:draw(function(def, parent, width, container_name)
|
||||
local container =
|
||||
parent.add{
|
||||
type = "frame",
|
||||
name = container_name,
|
||||
}
|
||||
--- @class Gui.elements.container: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement, minimal_width: number?, name: string?): LuaGuiElement
|
||||
Elements.container = Gui.define("container")
|
||||
:draw(function(def, parent, minimal_width, name)
|
||||
--- @cast minimal_width number?
|
||||
--- @cast name string?
|
||||
local container = parent.add{
|
||||
type = "frame",
|
||||
name = name,
|
||||
}
|
||||
|
||||
local style = container.style
|
||||
style.horizontally_stretchable = false
|
||||
style.minimal_width = width
|
||||
style.padding = 2
|
||||
container.style.padding = 2
|
||||
|
||||
return container.add{
|
||||
type = "frame",
|
||||
@@ -135,59 +157,176 @@ elements.container = ExpGui.element("container")
|
||||
end)
|
||||
:style{
|
||||
vertically_stretchable = false,
|
||||
}
|
||||
horizontally_stretchable = false,
|
||||
minimal_width = Gui.from_argument(1),
|
||||
} --[[ @as any ]]
|
||||
|
||||
--- Get the root element of a container
|
||||
--- @param container LuaGuiElement
|
||||
--- @return LuaGuiElement
|
||||
function Elements.container.get_root_element(container)
|
||||
return container.parent
|
||||
end
|
||||
|
||||
--- A frame within a container
|
||||
elements.subframe_base = ExpGui.element("container_subframe")
|
||||
--- @class Gui.elements.subframe_base: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement, style: string, name: string?): LuaGuiElement
|
||||
Elements.subframe_base = Gui.define("container_subframe")
|
||||
:draw{
|
||||
type = "frame",
|
||||
name = ExpGui.property_from_arg(2),
|
||||
style = ExpGui.property_from_arg(1),
|
||||
name = Gui.from_argument(2),
|
||||
style = Gui.from_argument(1),
|
||||
}
|
||||
:style{
|
||||
height = 0,
|
||||
minimal_height = 36,
|
||||
padding = { 3, 4 },
|
||||
padding = { 3, 6, 0, 6 },
|
||||
use_header_filler = false,
|
||||
horizontally_stretchable = true,
|
||||
}
|
||||
} --[[ @as any ]]
|
||||
|
||||
--- @class Gui.elements.header.opts
|
||||
--- @field name string?
|
||||
--- @field caption LocalisedString?
|
||||
--- @field tooltip LocalisedString?
|
||||
|
||||
--- A header frame within a container
|
||||
elements.header = ExpGui.element("container_header")
|
||||
--- @class Gui.elements.header: ExpElement
|
||||
--- @field label LuaGuiElement
|
||||
--- @overload fun(parent: LuaGuiElement, opts: Gui.elements.header.opts?): LuaGuiElement
|
||||
Elements.header = Gui.define("container_header")
|
||||
:draw(function(def, parent, opts)
|
||||
opts = opts or {}
|
||||
local subframe = elements.subframe_base(parent, "subheader_frame", opts.name)
|
||||
opts = opts or {} --- @cast opts Gui.elements.header.opts
|
||||
local subframe = Elements.subframe_base(parent, "subheader_frame", opts.name)
|
||||
|
||||
if opts.caption then
|
||||
subframe.add{
|
||||
type = "label",
|
||||
name = opts.label_name,
|
||||
name = "label",
|
||||
caption = opts.caption,
|
||||
tooltip = opts.tooltip,
|
||||
style = "frame_title",
|
||||
}
|
||||
end
|
||||
|
||||
return opts.no_flow and subframe or elements.aligned_flow(subframe, { name = "flow" })
|
||||
end)
|
||||
subframe.add{ type = "empty-widget" }.style.horizontally_stretchable = true
|
||||
|
||||
return subframe
|
||||
end) --[[ @as any ]]
|
||||
|
||||
--- @class Gui.elements.footer.opts
|
||||
--- @field name string?
|
||||
--- @field caption LocalisedString?
|
||||
--- @field tooltip LocalisedString?
|
||||
|
||||
--- A footer frame within a container
|
||||
elements.footer = ExpGui.element("container_footer")
|
||||
--- @class Gui.elements.footer: ExpElement
|
||||
--- @field label LuaGuiElement
|
||||
--- @overload fun(parent: LuaGuiElement, opts: Gui.elements.footer.opts?): LuaGuiElement
|
||||
Elements.footer = Gui.define("container_footer")
|
||||
:draw(function(def, parent, opts)
|
||||
opts = opts or {}
|
||||
local subframe = elements.subframe_base(parent, "subfooter_frame", opts.name)
|
||||
opts = opts or {} --- @cast opts Gui.elements.footer.opts
|
||||
local subframe = Elements.subframe_base(parent, "subfooter_frame", opts.name)
|
||||
|
||||
if opts.caption then
|
||||
subframe.add{
|
||||
type = "label",
|
||||
name = opts.label_name,
|
||||
name = "label",
|
||||
caption = opts.caption,
|
||||
tooltip = opts.tooltip,
|
||||
style = "frame_title",
|
||||
}
|
||||
end
|
||||
|
||||
return opts.no_flow and subframe or elements.aligned_flow(subframe, { name = "flow" })
|
||||
end)
|
||||
subframe.add{ type = "empty-widget" }.style.horizontally_stretchable = true
|
||||
|
||||
return elements
|
||||
return subframe
|
||||
end) --[[ @as any ]]
|
||||
|
||||
--- A button used to destroy its target when clicked, intended for screen frames
|
||||
--- @class Gui.elements.screen_frame_close: ExpElement
|
||||
--- @field data table<LuaGuiElement, LuaGuiElement>
|
||||
--- @overload fun(parent: LuaGuiElement, target: LuaGuiElement): LuaGuiElement
|
||||
Elements.screen_frame_close = Gui.define("screen_frame_close")
|
||||
:draw{
|
||||
type = "sprite-button",
|
||||
style = "frame_action_button",
|
||||
sprite = "utility/close",
|
||||
}
|
||||
:element_data(
|
||||
Gui.from_argument(1)
|
||||
)
|
||||
:on_click(function(def, player, element, event)
|
||||
--- @cast def Gui.elements.screen_frame_close
|
||||
Gui.destroy_if_valid(def.data[element])
|
||||
end) --[[ @as any ]]
|
||||
|
||||
--- A draggable frame with close button and button flow
|
||||
--- @class Gui.elements.screen_frame: ExpElement
|
||||
--- @field data table<LuaGuiElement, LuaGuiElement?>
|
||||
--- @overload fun(parent: LuaGuiElement, caption: LocalisedString?, button_flow: boolean?): LuaGuiElement
|
||||
Elements.screen_frame = Gui.define("screen_frame")
|
||||
:draw(function(def, parent, caption, button_flow)
|
||||
local container = parent.add{
|
||||
type = "frame",
|
||||
direction = "vertical",
|
||||
}
|
||||
container.style.padding = 2
|
||||
|
||||
local header = container.add{
|
||||
type = "flow",
|
||||
}
|
||||
|
||||
if caption then
|
||||
local label = header.add{
|
||||
type = "label",
|
||||
caption = caption,
|
||||
style = "frame_title"
|
||||
}
|
||||
label.style.top_margin = -3
|
||||
label.style.bottom_padding = 3
|
||||
end
|
||||
|
||||
local filler = header.add{
|
||||
type = "empty-widget",
|
||||
style = "draggable_space_header",
|
||||
}
|
||||
|
||||
filler.drag_target = container
|
||||
local filler_style = filler.style
|
||||
filler_style.horizontally_stretchable = true
|
||||
filler_style.vertically_stretchable = true
|
||||
filler_style.left_margin = caption and 4 or 0
|
||||
filler_style.natural_height = 24
|
||||
filler_style.height = 24
|
||||
|
||||
if button_flow then
|
||||
local _button_flow = header.add{ type = "flow" }
|
||||
def.data[container] = _button_flow
|
||||
_button_flow.style.padding = 0
|
||||
end
|
||||
|
||||
Elements.screen_frame_close(header, container)
|
||||
|
||||
return container.add{
|
||||
type = "frame",
|
||||
direction = "vertical",
|
||||
style = "inside_shallow_frame_packed",
|
||||
}
|
||||
end) --[[ @as any ]]
|
||||
|
||||
--- Get the button flow for a screen frame
|
||||
--- @param screen_frame LuaGuiElement
|
||||
--- @return LuaGuiElement
|
||||
function Elements.screen_frame.get_button_flow(screen_frame)
|
||||
return assert(Elements.screen_frame.data[screen_frame.parent], "Screen frame has no button flow")
|
||||
end
|
||||
|
||||
--- Get the root element of a screen frame
|
||||
--- @param screen_frame LuaGuiElement
|
||||
--- @return LuaGuiElement
|
||||
function Elements.screen_frame.get_root_element(screen_frame)
|
||||
return screen_frame.parent
|
||||
end
|
||||
|
||||
return Elements
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
--[[-- ExpGui - GuiData
|
||||
--[[-- Gui - GuiData
|
||||
Provides a method of storing elements created for a player and provide a global iterator for them.
|
||||
]]
|
||||
|
||||
local ExpUtil = require("modules/exp_util")
|
||||
local Storage = require("modules/exp_util/storage")
|
||||
|
||||
--- @alias ExpGui_GuiIter.FilterType LuaPlayer | LuaForce | LuaPlayer[] | nil
|
||||
--- @alias ExpGui_GuiIter.ReturnType fun(): LuaPlayer?, LuaGuiElement?
|
||||
--- @alias GuiIter.FilterType LuaPlayer | LuaForce | LuaPlayer[] | nil
|
||||
--- @alias GuiIter.ReturnType fun(): LuaPlayer?, LuaGuiElement?
|
||||
|
||||
--- @type table<string, table<uint, table<uint, LuaGuiElement>>>
|
||||
local registered_scopes = {}
|
||||
@@ -23,7 +23,7 @@ Storage.register({
|
||||
registration_numbers = tbl.registration_numbers
|
||||
end)
|
||||
|
||||
--- @class ExpGui_GuiIter
|
||||
--- @class GuiIter
|
||||
local GuiIter = {
|
||||
_scopes = registered_scopes,
|
||||
}
|
||||
@@ -76,7 +76,7 @@ end
|
||||
--- Iterate over all valid elements for a player
|
||||
--- @param scope string
|
||||
--- @param player LuaPlayer
|
||||
--- @return ExpGui_GuiIter.ReturnType
|
||||
--- @return GuiIter.ReturnType
|
||||
function GuiIter.player_elements(scope, player)
|
||||
if not player.valid then return no_loop end
|
||||
|
||||
@@ -98,7 +98,7 @@ end
|
||||
--- @param scope string
|
||||
--- @param players LuaPlayer[]
|
||||
--- @param online boolean?
|
||||
--- @return ExpGui_GuiIter.ReturnType
|
||||
--- @return GuiIter.ReturnType
|
||||
function GuiIter.filtered_elements(scope, players, online)
|
||||
local scope_elements = registered_scopes[scope]
|
||||
if not scope_elements then return no_loop end
|
||||
@@ -125,7 +125,7 @@ end
|
||||
|
||||
--- Iterate over all valid elements
|
||||
--- @param scope string
|
||||
--- @return ExpGui_GuiIter.ReturnType
|
||||
--- @return GuiIter.ReturnType
|
||||
function GuiIter.all_elements(scope)
|
||||
local scope_elements = registered_scopes[scope]
|
||||
if not scope_elements then return no_loop end
|
||||
@@ -161,8 +161,8 @@ end
|
||||
|
||||
--- Iterate over all valid gui elements for all players
|
||||
--- @param scope string
|
||||
--- @param filter ExpGui_GuiIter.FilterType
|
||||
--- @return ExpGui_GuiIter.ReturnType
|
||||
--- @param filter GuiIter.FilterType
|
||||
--- @return GuiIter.ReturnType
|
||||
function GuiIter.get_tracked_elements(scope, filter)
|
||||
local class_name = ExpUtil.get_class_name(filter)
|
||||
if class_name == "nil" then
|
||||
@@ -184,8 +184,8 @@ end
|
||||
|
||||
--- Iterate over all valid gui elements for all online players
|
||||
--- @param scope string
|
||||
--- @param filter ExpGui_GuiIter.FilterType
|
||||
--- @return ExpGui_GuiIter.ReturnType
|
||||
--- @param filter GuiIter.FilterType
|
||||
--- @return GuiIter.ReturnType
|
||||
function GuiIter.get_online_elements(scope, filter)
|
||||
local class_name = ExpUtil.get_class_name(filter)
|
||||
if class_name == "nil" then
|
||||
|
||||
@@ -4,7 +4,7 @@ local ExpUtil = require("modules/exp_util")
|
||||
local GuiData = require("modules/exp_gui/data")
|
||||
local GuiIter = require("modules/exp_gui/iter")
|
||||
|
||||
--- @class ExpGui_ExpElement
|
||||
--- @class _ExpElement
|
||||
local ExpElement = {
|
||||
_elements = {}, --- @type table<string, ExpElement>
|
||||
}
|
||||
@@ -13,14 +13,14 @@ ExpElement.events = {}
|
||||
|
||||
--- @alias ExpElement.DrawCallback fun(def: ExpElement, parent: LuaGuiElement, ...): LuaGuiElement?, function?
|
||||
--- @alias ExpElement.PostDrawCallback fun(def: ExpElement, element: LuaGuiElement?, parent: LuaGuiElement, ...): table?
|
||||
--- @alias ExpElement.PostDrawCallbackAdder fun(self: ExpElement, definition: table | ExpElement.PostDrawCallback): ExpElement
|
||||
--- @alias ExpElement.PostDrawCallbackAdder fun(self: ExpElement, definition: table | number | string | boolean | ExpElement.PostDrawCallback): ExpElement
|
||||
--- @alias ExpElement.EventHandler<E> fun(def: ExpElement, player: LuaPlayer, element: LuaGuiElement, event: E)
|
||||
--- @alias ExpElement.OnEventAdder<E> fun(self: ExpElement, handler: fun(def: ExpElement, player: LuaPlayer, element: LuaGuiElement, event: E)): ExpElement
|
||||
|
||||
--- @class ExpElement._debug
|
||||
--- @field defined_at string
|
||||
--- @field draw_definition table?
|
||||
--- @field draw_from_args table?
|
||||
--- @field draw_signals table?
|
||||
--- @field style_definition table?
|
||||
--- @field style_from_args table?
|
||||
--- @field element_data_definition table?
|
||||
@@ -34,7 +34,7 @@ ExpElement.events = {}
|
||||
|
||||
--- @class ExpElement
|
||||
--- @field name string
|
||||
--- @field data ExpGui.GuiData
|
||||
--- @field data GuiData
|
||||
--- @field _debug ExpElement._debug
|
||||
--- @field _draw ExpElement.DrawCallback?
|
||||
--- @field _style ExpElement.PostDrawCallback?
|
||||
@@ -55,40 +55,95 @@ ExpElement._metatable = {
|
||||
__class = "ExpElement",
|
||||
}
|
||||
|
||||
--- Used to signal the intentional lack of a return value from draw
|
||||
--- @return nil, function
|
||||
function ExpElement.no_return()
|
||||
return nil, ExpElement.no_return
|
||||
end
|
||||
|
||||
--- Used to signal that the property should be the same as the define name
|
||||
--- @return function
|
||||
function ExpElement.property_from_name()
|
||||
return ExpElement.property_from_name
|
||||
function ExpElement.from_name()
|
||||
return ExpElement.from_name
|
||||
end
|
||||
|
||||
--- Used to signal that a property should be taken from the arguments, a string means key of last arg
|
||||
--- @param arg number|string|nil
|
||||
--- @return [function, number|string|nil]
|
||||
function ExpElement.property_from_arg(arg)
|
||||
return { ExpElement.property_from_arg, arg }
|
||||
--- @generic A: number|string, D: any
|
||||
--- @param arg A
|
||||
--- @param default D?
|
||||
--- @return [function, A, D]
|
||||
function ExpElement.from_argument(arg, default)
|
||||
return { ExpElement.from_argument, assert(arg), default }
|
||||
end
|
||||
|
||||
--- @alias ExpElement._signals table<string|number, [string, any]> | [function, string|number, any|nil]
|
||||
|
||||
--- Extract the from args properties from a definition
|
||||
--- @param definition table
|
||||
--- @return table<string|number, string>
|
||||
--- @return ExpElement._signals
|
||||
function ExpElement._prototype:_extract_signals(definition)
|
||||
local from_args = {}
|
||||
for k, v in pairs(definition) do
|
||||
if v == ExpElement.property_from_arg then
|
||||
from_args[#from_args + 1] = k
|
||||
elseif v == ExpElement.property_from_name then
|
||||
definition[k] = self.name
|
||||
elseif type(v) == "table" and rawget(v, 1) == ExpElement.property_from_arg then
|
||||
from_args[v[2] or (#from_args + 1)] = k
|
||||
-- Check if the definition is from_argument
|
||||
if definition[1] == ExpElement.from_argument then
|
||||
return definition
|
||||
end
|
||||
|
||||
-- Otherwise check if any of the values are from_argument or from_name
|
||||
local signals = {}
|
||||
for prop, value in pairs(definition) do
|
||||
if value == ExpElement.from_argument then
|
||||
error("ExpElement.from_argument must be called with an argument index / key")
|
||||
elseif value == ExpElement.from_name then
|
||||
definition[prop] = self.name
|
||||
elseif type(value) == "table" and rawget(value, 1) == ExpElement.from_argument then
|
||||
local key = value[2] or (#signals + 1)
|
||||
signals[key] = { prop, value[3] }
|
||||
end
|
||||
end
|
||||
return from_args
|
||||
|
||||
return signals
|
||||
end
|
||||
|
||||
--- Apply the previously extracted signals to a definition using the create args
|
||||
--- @param definition table
|
||||
--- @param signals ExpElement._signals
|
||||
--- @param args table
|
||||
--- @return any
|
||||
function ExpElement._prototype:_apply_signals(definition, signals, args)
|
||||
local last = args[#args] or args -- 'or args' used instead of empty table
|
||||
-- Check if the root is from_argument
|
||||
if signals[1] == ExpElement.from_argument then
|
||||
local key, rtn = signals[2], nil
|
||||
if type(key) == "string" then
|
||||
rtn = last[key]
|
||||
else
|
||||
rtn = args[key]
|
||||
end
|
||||
if rtn == nil then
|
||||
return signals[3]
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
-- Otherwise set the properties of the definition
|
||||
for i, pair in pairs(signals) do
|
||||
local key = pair[1]
|
||||
if type(i) == "string" then
|
||||
definition[key] = last[i]
|
||||
else
|
||||
definition[key] = args[i]
|
||||
end
|
||||
if definition[key] == nil then
|
||||
definition[key] = pair[2]
|
||||
end
|
||||
end
|
||||
|
||||
return definition
|
||||
end
|
||||
|
||||
--- Register a new instance of a prototype
|
||||
--- @param name string
|
||||
--- @return ExpElement
|
||||
function ExpElement.create(name)
|
||||
function ExpElement.new(name)
|
||||
ExpUtil.assert_not_runtime()
|
||||
local module_name = ExpUtil.get_module_name(2)
|
||||
local element_name = module_name .. "/" .. name
|
||||
@@ -103,7 +158,7 @@ function ExpElement.create(name)
|
||||
},
|
||||
}
|
||||
|
||||
ExpElement._elements[element_name] = instance
|
||||
ExpElement._elements[element_name] = instance --[[ @as ExpElement ]]
|
||||
return setmetatable(instance, ExpElement._metatable)
|
||||
end
|
||||
|
||||
@@ -122,11 +177,14 @@ function ExpElement._prototype:create(parent, ...)
|
||||
assert(self._draw, "Element does not have a draw definition")
|
||||
local element, status = self:_draw(parent, ...)
|
||||
local player = assert(game.get_player(parent.player_index))
|
||||
if status ~= ExpElement.no_return then
|
||||
assert(element and element.object_name == "LuaGuiElement", "Draw did not return a LuaGuiElement")
|
||||
end
|
||||
|
||||
if self._style then
|
||||
assert(element, "Cannot set style when no element was returned by draw definition")
|
||||
local style = self:_style(element, parent, ...)
|
||||
if style then
|
||||
assert(element, "Cannot set style when no element was returned by draw definition")
|
||||
local element_style = element.style
|
||||
for k, v in pairs(style) do
|
||||
element_style[k] = v
|
||||
@@ -135,23 +193,23 @@ function ExpElement._prototype:create(parent, ...)
|
||||
end
|
||||
|
||||
if self._element_data then
|
||||
assert(element, "Cannot set element data when no element was returned by draw definition")
|
||||
local data = self:_element_data(element, parent, ...)
|
||||
if data then
|
||||
assert(element, "Cannot set element data when no element was returned by draw definition")
|
||||
if data ~= nil and self.data[element] == nil then
|
||||
self.data[element] = data
|
||||
end
|
||||
end
|
||||
|
||||
if self._player_data then
|
||||
local data = self:_player_data(element, parent, ...)
|
||||
if data then
|
||||
if data ~= nil and self.data[player] == nil then
|
||||
self.data[player] = data
|
||||
end
|
||||
end
|
||||
|
||||
if self._force_data then
|
||||
local data = self:_force_data(element, parent, ...)
|
||||
if data then
|
||||
if data ~= nil and self.data[player.force] == nil then
|
||||
self.data[player.force] = data
|
||||
end
|
||||
end
|
||||
@@ -161,7 +219,9 @@ function ExpElement._prototype:create(parent, ...)
|
||||
if data then
|
||||
local global_data = self.data.global_data
|
||||
for k, v in pairs(data) do
|
||||
global_data[k] = v
|
||||
if global_data[k] == nil then
|
||||
global_data[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -189,8 +249,8 @@ end
|
||||
|
||||
--- Add a temp draw definition, useful for when working top down
|
||||
--- @return ExpElement
|
||||
function ExpElement._prototype:dev()
|
||||
log("Dev draw used for " .. self.name)
|
||||
function ExpElement._prototype:empty()
|
||||
log("empty draw used for " .. self.name)
|
||||
return self:draw{
|
||||
type = "flow"
|
||||
}
|
||||
@@ -203,71 +263,67 @@ end
|
||||
--- @return ExpElement
|
||||
function ExpElement._prototype:draw(definition)
|
||||
ExpUtil.assert_not_runtime()
|
||||
if type(definition) == "function" then
|
||||
if type(definition) == "function" and definition ~= ExpElement.from_argument then
|
||||
self._draw = definition
|
||||
return self
|
||||
end
|
||||
|
||||
assert(type(definition) == "table", "Definition is not a table or function")
|
||||
local from_args = self:_extract_signals(definition)
|
||||
local signals = self:_extract_signals(definition)
|
||||
self._debug.draw_definition = definition
|
||||
|
||||
if not next(from_args) then
|
||||
if not next(signals) then
|
||||
-- If no signals then skip var arg
|
||||
self._draw = function(_, parent)
|
||||
return parent.add(definition)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
self._debug.draw_from_args = from_args
|
||||
self._debug.draw_signals = signals
|
||||
self._draw = function(_, parent, ...)
|
||||
local args = { ... }
|
||||
local last = args[#args] or args
|
||||
-- 'or args' used instead of empty table
|
||||
for i, k in pairs(from_args) do
|
||||
if type(i) == "string" then
|
||||
definition[k] = last[i]
|
||||
else
|
||||
definition[k] = args[i]
|
||||
end
|
||||
end
|
||||
return parent.add(definition)
|
||||
return parent.add(self:_apply_signals(definition, signals, { ... }))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a definition adder for anything other than draaw
|
||||
--- Create a definition adder for anything other than draw
|
||||
--- @param prop_name string
|
||||
--- @param debug_def string
|
||||
--- @param debug_args string
|
||||
--- @param debug_signals string
|
||||
--- @return ExpElement.PostDrawCallbackAdder
|
||||
local function definition_factory(prop_name, debug_def, debug_args)
|
||||
local function definition_factory(prop_name, debug_def, debug_signals)
|
||||
return function(self, definition)
|
||||
ExpUtil.assert_not_runtime()
|
||||
if type(definition) == "function" then
|
||||
if type(definition) == "function" and definition ~= ExpElement.from_argument then
|
||||
self[prop_name] = definition
|
||||
return self
|
||||
end
|
||||
|
||||
assert(type(definition) == "table", "Definition is not a table or function")
|
||||
local from_args = self:_extract_signals(definition)
|
||||
self._debug[debug_def] = definition
|
||||
|
||||
if #from_args == 0 then
|
||||
if type(definition) ~= "table" and definition ~= ExpElement.from_argument then
|
||||
-- Primitive value so we can just return it
|
||||
self[prop_name] = function(_, _, _)
|
||||
return definition
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
self._debug[debug_args] = from_args
|
||||
self[prop_name] = function(_, _, _, ...)
|
||||
local args = { ... }
|
||||
for i, k in pairs(from_args) do
|
||||
definition[k] = args[i]
|
||||
assert(type(definition) == "table", "Definition is not a table or function")
|
||||
local signals = self:_extract_signals(definition)
|
||||
self._debug[debug_def] = definition
|
||||
|
||||
if not next(signals) then
|
||||
-- If no signals then skip var arg
|
||||
self[prop_name] = function(_, _, _)
|
||||
return table.deep_copy(definition)
|
||||
end
|
||||
return definition
|
||||
return self
|
||||
end
|
||||
|
||||
self._debug[debug_signals] = signals
|
||||
self[prop_name] = function(_, _, _, ...)
|
||||
return self:_apply_signals(table.deep_copy(definition), signals, { ... })
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -276,34 +332,34 @@ end
|
||||
|
||||
--- Set the style definition
|
||||
--- @type ExpElement.PostDrawCallbackAdder
|
||||
ExpElement._prototype.style = definition_factory("_style", "style_definition", "style_from_args")
|
||||
ExpElement._prototype.style = definition_factory("_style", "style_definition", "style_signals")
|
||||
|
||||
--- Set the default element data
|
||||
--- @type ExpElement.PostDrawCallbackAdder
|
||||
ExpElement._prototype.element_data = definition_factory("_element_data", "element_data_definition", "element_data_from_args")
|
||||
ExpElement._prototype.element_data = definition_factory("_element_data", "element_data_definition", "element_data_signals")
|
||||
|
||||
--- Set the default player data
|
||||
--- @type ExpElement.PostDrawCallbackAdder
|
||||
ExpElement._prototype.player_data = definition_factory("_player_data", "player_data_definition", "player_data_from_args")
|
||||
ExpElement._prototype.player_data = definition_factory("_player_data", "player_data_definition", "player_data_signals")
|
||||
|
||||
--- Set the default force data
|
||||
--- @type ExpElement.PostDrawCallbackAdder
|
||||
ExpElement._prototype.force_data = definition_factory("_force_data", "force_data_definition", "force_data_from_args")
|
||||
ExpElement._prototype.force_data = definition_factory("_force_data", "force_data_definition", "force_data_signals")
|
||||
|
||||
--- Set the default global data
|
||||
--- @type ExpElement.PostDrawCallbackAdder
|
||||
ExpElement._prototype.global_data = definition_factory("_global_data", "global_data_definition", "global_data_from_args")
|
||||
ExpElement._prototype.global_data = definition_factory("_global_data", "global_data_definition", "global_data_signals")
|
||||
|
||||
--- Iterate the tracked elements of all players
|
||||
--- @param filter ExpGui_GuiIter.FilterType
|
||||
--- @return ExpGui_GuiIter.ReturnType
|
||||
--- @param filter GuiIter.FilterType
|
||||
--- @return GuiIter.ReturnType
|
||||
function ExpElement._prototype:tracked_elements(filter)
|
||||
return GuiIter.get_tracked_elements(self.name, filter)
|
||||
end
|
||||
|
||||
--- Iterate the tracked elements of all online players
|
||||
--- @param filter ExpGui_GuiIter.FilterType
|
||||
--- @return ExpGui_GuiIter.ReturnType
|
||||
--- @param filter GuiIter.FilterType
|
||||
--- @return GuiIter.ReturnType
|
||||
function ExpElement._prototype:online_elements(filter)
|
||||
return GuiIter.get_online_elements(self.name, filter)
|
||||
end
|
||||
@@ -337,10 +393,10 @@ function ExpElement._prototype:link_element(element)
|
||||
element_tags = {}
|
||||
end
|
||||
|
||||
local event_tags = element_tags["ExpGui"]
|
||||
local event_tags = element_tags["Gui"]
|
||||
if not event_tags then
|
||||
event_tags = {}
|
||||
element_tags["ExpGui"] = event_tags
|
||||
element_tags["Gui"] = event_tags
|
||||
end
|
||||
--- @cast event_tags string[]
|
||||
|
||||
@@ -363,10 +419,10 @@ function ExpElement._prototype:unlink_element(element)
|
||||
return element, ExpElement._prototype.unlink_element
|
||||
end
|
||||
|
||||
local event_tags = element_tags["ExpGui"]
|
||||
local event_tags = element_tags["Gui"]
|
||||
if not event_tags then
|
||||
event_tags = {}
|
||||
element_tags["ExpGui"] = event_tags
|
||||
element_tags["Gui"] = event_tags
|
||||
end
|
||||
--- @cast event_tags string[]
|
||||
|
||||
@@ -381,7 +437,7 @@ local function event_handler(event)
|
||||
local element = event.element
|
||||
if not element or not element.valid then return end
|
||||
|
||||
local event_tags = element.tags and element.tags["ExpGui"]
|
||||
local event_tags = element.tags and element.tags["Gui"]
|
||||
if not event_tags then return end
|
||||
--- @cast event_tags string[]
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
--- @class ExpGui
|
||||
local ExpGui = require("modules/exp_gui")
|
||||
--- @class Gui
|
||||
local Gui = require("modules/exp_gui")
|
||||
|
||||
--- @class ExpGui.styles
|
||||
--- @class Gui.styles
|
||||
local styles = {}
|
||||
ExpGui.styles = styles
|
||||
Gui.styles = styles
|
||||
|
||||
function styles.sprite(style)
|
||||
style = style or {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
--- @class ExpGui
|
||||
local ExpGui = require("modules/exp_gui")
|
||||
--- @class Gui
|
||||
local Gui = require("modules/exp_gui")
|
||||
local ExpElement = require("modules/exp_gui/prototype")
|
||||
local mod_gui = require("mod-gui")
|
||||
|
||||
@@ -9,9 +9,9 @@ local toolbar_button_active_style = "menu_button_continue"
|
||||
local toolbar_button_size = 36
|
||||
local toolbar_button_small = 20
|
||||
|
||||
--- @class ExpGui.toolbar
|
||||
--- @class Gui.toolbar
|
||||
local Toolbar = {}
|
||||
ExpGui.toolbar = Toolbar
|
||||
Gui.toolbar = Toolbar
|
||||
|
||||
local elements = {}
|
||||
Toolbar.elements = elements
|
||||
@@ -20,13 +20,27 @@ local toolbar_buttons = {} --- @type ExpElement[]
|
||||
local left_elements_with_button = {} --- @type table<ExpElement, ExpElement>
|
||||
local buttons_with_left_element = {} --- @type table<string, ExpElement>
|
||||
|
||||
--- Called when toolbar button toggle state is changed.
|
||||
Toolbar.on_gui_button_toggled = script.generate_event_name()
|
||||
--- @class (exact) EventData.on_gui_button_toggled: EventData
|
||||
--- @field element LuaGuiElement
|
||||
--- @field state boolean
|
||||
|
||||
--- @class _ExpElement._prototype
|
||||
--- @field on_button_toggled ExpElement.OnEventAdder<EventData.on_gui_button_toggled>
|
||||
|
||||
--- @diagnostic disable-next-line: invisible, inject-field
|
||||
function ExpElement._prototype.on_button_toggled(self, handler)
|
||||
return self:on_event(Toolbar.on_gui_button_toggled, handler)
|
||||
end
|
||||
|
||||
--- Set the visible state of the toolbar
|
||||
--- @param player LuaPlayer
|
||||
--- @param state boolean? toggles if nil
|
||||
--- @return boolean
|
||||
function Toolbar.set_visible_state(player, state)
|
||||
-- Update the top flow
|
||||
local top_flow = assert(ExpGui.get_top_flow(player).parent)
|
||||
local top_flow = assert(Gui.get_top_flow(player).parent)
|
||||
if state == nil then state = not top_flow.visible end
|
||||
top_flow.visible = state
|
||||
|
||||
@@ -47,33 +61,46 @@ end
|
||||
--- @param player LuaPlayer
|
||||
--- @return boolean
|
||||
function Toolbar.get_visible_state(player)
|
||||
local top_flow = assert(ExpGui.get_top_flow(player).parent)
|
||||
local top_flow = assert(Gui.get_top_flow(player).parent)
|
||||
return top_flow.visible
|
||||
end
|
||||
|
||||
--- Set the toggle state of a toolbar button, does not check for a linked left element
|
||||
--- Set the toggle state of a toolbar button
|
||||
--- @param define ExpElement
|
||||
--- @param player LuaPlayer
|
||||
--- @param state boolean? toggles if nil
|
||||
--- @param _from_left boolean?
|
||||
--- @return boolean
|
||||
function Toolbar.set_button_toggled_state(define, player, state)
|
||||
local top_element = assert(ExpGui.get_top_element(define, player), "Element is not on the top flow")
|
||||
function Toolbar.set_button_toggled_state(define, player, state, _from_left)
|
||||
local top_element = assert(Gui.get_top_element(define, player), "Element is not on the top flow")
|
||||
if state == nil then state = top_element.style.name == toolbar_button_default_style end
|
||||
|
||||
for _, element in define:tracked_elements(player) do
|
||||
local original_width, original_height = element.style.minimal_width, element.style.maximal_height
|
||||
element.style = state and toolbar_button_active_style or toolbar_button_default_style
|
||||
local left_element = buttons_with_left_element[define.name]
|
||||
if left_element and not _from_left then
|
||||
return Toolbar.set_left_element_visible_state(left_element, player, state)
|
||||
end
|
||||
|
||||
for _, button in define:tracked_elements(player) do
|
||||
local original_width, original_height = button.style.minimal_width, button.style.maximal_height
|
||||
button.style = state and toolbar_button_active_style or toolbar_button_default_style
|
||||
|
||||
-- Make the extra required adjustments
|
||||
local style = element.style
|
||||
local style = button.style
|
||||
style.minimal_width = original_width
|
||||
style.maximal_height = original_height
|
||||
if element.type == "sprite-button" then
|
||||
if button.type == "sprite-button" then
|
||||
style.padding = -2
|
||||
else
|
||||
style.font = "default-semibold"
|
||||
style.padding = 0
|
||||
end
|
||||
|
||||
script.raise_event(Toolbar.on_gui_button_toggled, {
|
||||
name = Toolbar.on_gui_button_toggled,
|
||||
tick = game.tick,
|
||||
element = button,
|
||||
state = state,
|
||||
})
|
||||
end
|
||||
|
||||
return state
|
||||
@@ -84,7 +111,7 @@ end
|
||||
--- @param player LuaPlayer
|
||||
--- @return boolean
|
||||
function Toolbar.get_button_toggled_state(define, player)
|
||||
local element = assert(ExpGui.get_top_element(define, player), "Element is not on the top flow")
|
||||
local element = assert(Gui.get_top_element(define, player), "Element is not on the top flow")
|
||||
return element.style.name == toolbar_button_active_style
|
||||
end
|
||||
|
||||
@@ -95,14 +122,14 @@ end
|
||||
--- @param _skip_consistency boolean?
|
||||
--- @return boolean
|
||||
function Toolbar.set_left_element_visible_state(define, player, state, _skip_consistency)
|
||||
local element = assert(ExpGui.get_left_element(define, player), "Element is not on the left flow")
|
||||
local element = assert(Gui.get_left_element(define, player), "Element is not on the left flow")
|
||||
if state == nil then state = not element.visible end
|
||||
element.visible = state
|
||||
|
||||
-- Check if there is a linked toolbar button and update it
|
||||
local button = left_elements_with_button[define]
|
||||
if button then
|
||||
Toolbar.set_button_toggled_state(button, player, state)
|
||||
Toolbar.set_button_toggled_state(button, player, state, true)
|
||||
end
|
||||
|
||||
-- This check is O(n^2) when setting all left elements to hidden, so internals can it
|
||||
@@ -122,7 +149,7 @@ end
|
||||
--- @param player LuaPlayer
|
||||
--- @return boolean
|
||||
function Toolbar.get_left_element_visible_state(define, player)
|
||||
local element = assert(ExpGui.get_left_element(define, player), "Element is not on the left flow")
|
||||
local element = assert(Gui.get_left_element(define, player), "Element is not on the left flow")
|
||||
return element.visible
|
||||
end
|
||||
|
||||
@@ -130,11 +157,11 @@ end
|
||||
--- @param player any
|
||||
--- @return boolean
|
||||
function Toolbar.has_visible_buttons(player)
|
||||
local top_flow = ExpGui.get_top_flow(player)
|
||||
local settings_button = ExpGui.get_top_element(elements.close_toolbar, player)
|
||||
local top_flow = Gui.get_top_flow(player)
|
||||
local settings_button = Gui.get_top_element(elements.close_toolbar, player)
|
||||
|
||||
for _, element in pairs(top_flow.children) do
|
||||
if element.visible and element ~= settings_button then
|
||||
for _, top_element in pairs(top_flow.children) do
|
||||
if top_element.visible and top_element ~= settings_button then
|
||||
return true
|
||||
end
|
||||
end
|
||||
@@ -146,11 +173,11 @@ end
|
||||
--- @param player any
|
||||
--- @return boolean
|
||||
function Toolbar.has_visible_left_elements(player)
|
||||
local left_flow = ExpGui.get_left_flow(player)
|
||||
local core_button_flow = ExpGui.get_left_element(elements.core_button_flow, player)
|
||||
local left_flow = Gui.get_left_flow(player)
|
||||
local core_button_flow = Gui.get_left_element(elements.core_button_flow, player)
|
||||
|
||||
for _, element in pairs(left_flow.children) do
|
||||
if element.visible and element ~= core_button_flow then
|
||||
for _, left_element in pairs(left_flow.children) do
|
||||
if left_element.visible and left_element ~= core_button_flow then
|
||||
return true
|
||||
end
|
||||
end
|
||||
@@ -158,14 +185,14 @@ function Toolbar.has_visible_left_elements(player)
|
||||
return false
|
||||
end
|
||||
|
||||
--- @class ExpGui.toolbar.create_button__param: LuaGuiElement.add_param.sprite_button, LuaGuiElement.add_param.button
|
||||
--- @class Gui.toolbar.create_button__param: LuaGuiElement.add_param.sprite_button, LuaGuiElement.add_param.button
|
||||
--- @field name string
|
||||
--- @field type nil
|
||||
--- @field left_element ExpElement| nil
|
||||
--- @field visible ExpGui.VisibleCallback | boolean | nil
|
||||
--- @field visible Gui.VisibleCallback | boolean | nil
|
||||
|
||||
--- Create a toolbar button
|
||||
--- @param options ExpGui.toolbar.create_button__param
|
||||
--- @param options Gui.toolbar.create_button__param
|
||||
--- @return ExpElement
|
||||
function Toolbar.create_button(options)
|
||||
-- Extract the custom options from the element.add options
|
||||
@@ -187,7 +214,7 @@ function Toolbar.create_button(options)
|
||||
end
|
||||
|
||||
-- Create the new element define
|
||||
local toolbar_button = ExpGui.element(name)
|
||||
local toolbar_button = Gui.define(name)
|
||||
:track_all_elements()
|
||||
:draw(options)
|
||||
:style{
|
||||
@@ -215,12 +242,12 @@ function Toolbar.create_button(options)
|
||||
|
||||
-- Add the define to the top flow and return
|
||||
toolbar_buttons[#toolbar_buttons + 1] = toolbar_button
|
||||
ExpGui.add_top_element(toolbar_button, visible)
|
||||
Gui.add_top_element(toolbar_button, visible)
|
||||
return toolbar_button
|
||||
end
|
||||
|
||||
--- Toggles the toolbar settings, RMB will instead hide the toolbar
|
||||
elements.close_toolbar = ExpGui.element("close_toolbar")
|
||||
elements.close_toolbar = Gui.define("close_toolbar")
|
||||
:draw{
|
||||
type = "sprite-button",
|
||||
sprite = "utility/preset",
|
||||
@@ -241,7 +268,7 @@ elements.close_toolbar = ExpGui.element("close_toolbar")
|
||||
end)
|
||||
|
||||
--- Shows the toolbar, if no buttons are visible then it shows the settings instead
|
||||
elements.open_toolbar = ExpGui.element("open_toolbar")
|
||||
elements.open_toolbar = Gui.define("open_toolbar")
|
||||
:track_all_elements()
|
||||
:draw{
|
||||
type = "sprite-button",
|
||||
@@ -263,7 +290,7 @@ elements.open_toolbar = ExpGui.element("open_toolbar")
|
||||
end)
|
||||
|
||||
--- Hides all left elements when clicked
|
||||
elements.clear_left_flow = ExpGui.element("clear_left_flow")
|
||||
elements.clear_left_flow = Gui.define("clear_left_flow")
|
||||
:track_all_elements()
|
||||
:draw{
|
||||
type = "sprite-button",
|
||||
@@ -278,7 +305,7 @@ elements.clear_left_flow = ExpGui.element("clear_left_flow")
|
||||
}
|
||||
:on_click(function(def, player, element)
|
||||
element.visible = false
|
||||
for define in pairs(ExpGui.left_elements) do
|
||||
for define in pairs(Gui.left_elements) do
|
||||
if define ~= elements.core_button_flow then
|
||||
Toolbar.set_left_element_visible_state(define, player, false, true)
|
||||
end
|
||||
@@ -286,7 +313,7 @@ elements.clear_left_flow = ExpGui.element("clear_left_flow")
|
||||
end)
|
||||
|
||||
--- Contains the two buttons on the left flow
|
||||
elements.core_button_flow = ExpGui.element("core_button_flow")
|
||||
elements.core_button_flow = Gui.define("core_button_flow")
|
||||
:draw(function(def, parent)
|
||||
local flow = parent.add{
|
||||
type = "flow",
|
||||
@@ -327,15 +354,15 @@ local function move_toolbar_button(player, item, offset)
|
||||
list.swap_children(old_index, new_index)
|
||||
|
||||
-- Swap the position in the top flow, offset by 1 because of settings button
|
||||
local top_flow = ExpGui.get_top_flow(player)
|
||||
local top_flow = Gui.get_top_flow(player)
|
||||
top_flow.swap_children(old_index + 1, new_index + 1)
|
||||
|
||||
-- Check if the element has a left element to move
|
||||
local left_element = buttons_with_left_element[item.name]
|
||||
local other_left_element = buttons_with_left_element[other_item.name]
|
||||
if left_element and other_left_element then
|
||||
local element = ExpGui.get_left_element(left_element, player)
|
||||
local other_element = ExpGui.get_left_element(other_left_element, player)
|
||||
local element = Gui.get_left_element(left_element, player)
|
||||
local other_element = Gui.get_left_element(other_left_element, player)
|
||||
local left_index = element.get_index_in_parent()
|
||||
local other_index = other_element.get_index_in_parent()
|
||||
element.parent.swap_children(left_index, other_index)
|
||||
@@ -360,15 +387,15 @@ local function move_toolbar_button(player, item, offset)
|
||||
end
|
||||
end
|
||||
|
||||
--- @alias ExpGui.ToolbarOrder { name: string, favourite: boolean }[]
|
||||
--- @alias Gui.ToolbarOrder { name: string, favourite: boolean }[]
|
||||
|
||||
--- Reorder the toolbar buttons
|
||||
--- @param player LuaPlayer
|
||||
--- @param order ExpGui.ToolbarOrder
|
||||
--- @param order Gui.ToolbarOrder
|
||||
function Toolbar.set_order(player, order)
|
||||
local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
|
||||
local left_flow = ExpGui.get_left_flow(player)
|
||||
local top_flow = ExpGui.get_top_flow(player)
|
||||
local left_flow = Gui.get_left_flow(player)
|
||||
local top_flow = Gui.get_top_flow(player)
|
||||
|
||||
-- Reorder the buttons
|
||||
local left_index = 1
|
||||
@@ -380,7 +407,7 @@ function Toolbar.set_order(player, order)
|
||||
|
||||
-- Switch the toolbar button order
|
||||
local element_define = ExpElement.get(item_state.name)
|
||||
local toolbar_button = ExpGui.get_top_element(element_define, player)
|
||||
local toolbar_button = Gui.get_top_element(element_define, player)
|
||||
top_flow.swap_children(index + 1, toolbar_button.get_index_in_parent())
|
||||
|
||||
-- Update the children buttons
|
||||
@@ -392,21 +419,21 @@ function Toolbar.set_order(player, order)
|
||||
-- Switch the left element order
|
||||
local left_define = buttons_with_left_element[item_state.name]
|
||||
if left_define then
|
||||
local left_element = ExpGui.get_left_element(left_define, player)
|
||||
local left_element = Gui.get_left_element(left_define, player)
|
||||
left_flow.swap_children(left_index, left_element.get_index_in_parent())
|
||||
left_index = left_index + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @class (exact) ExpGui.ToolbarState
|
||||
--- @field order ExpGui.ToolbarOrder
|
||||
--- @class (exact) Gui.ToolbarState
|
||||
--- @field order Gui.ToolbarOrder
|
||||
--- @field open string[]
|
||||
--- @field visible boolean
|
||||
|
||||
--- Reorder the toolbar buttons and set the open state of the left flows
|
||||
--- @param player LuaPlayer
|
||||
--- @param state ExpGui.ToolbarState
|
||||
--- @param state Gui.ToolbarState
|
||||
function Toolbar.set_state(player, state)
|
||||
Toolbar.set_order(player, state.order)
|
||||
Toolbar.set_visible_state(player, state.visible)
|
||||
@@ -420,7 +447,7 @@ function Toolbar.set_state(player, state)
|
||||
end
|
||||
|
||||
-- Make all other elements hidden
|
||||
for left_element in pairs(ExpGui.left_elements) do
|
||||
for left_element in pairs(Gui.left_elements) do
|
||||
if not done[left_element] then
|
||||
Toolbar.set_left_element_visible_state(left_element, player, false, true)
|
||||
end
|
||||
@@ -435,7 +462,7 @@ end
|
||||
|
||||
--- Get the full toolbar state for a player
|
||||
--- @param player LuaPlayer
|
||||
--- @return ExpGui.ToolbarState
|
||||
--- @return Gui.ToolbarState
|
||||
function Toolbar.get_state(player)
|
||||
-- Get the order of toolbar buttons
|
||||
local order = {}
|
||||
@@ -446,7 +473,7 @@ function Toolbar.get_state(player)
|
||||
|
||||
-- Get the names of all open left elements
|
||||
local open, open_index = {}, 1
|
||||
for left_element in pairs(ExpGui.left_elements) do
|
||||
for left_element in pairs(Gui.left_elements) do
|
||||
if Toolbar.get_left_element_visible_state(left_element, player) then
|
||||
open[open_index] = left_element.name
|
||||
open_index = open_index + 1
|
||||
@@ -462,10 +489,10 @@ function Toolbar._create_elements(player)
|
||||
-- Add any missing items to the gui
|
||||
local toolbar_list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
|
||||
local previous_last_index = #toolbar_list.children_names
|
||||
for define in pairs(ExpGui.top_elements) do
|
||||
for define in pairs(Gui.top_elements) do
|
||||
if define ~= elements.close_toolbar and toolbar_list[define.name] == nil then
|
||||
local element = elements.toolbar_list_item(toolbar_list, define)
|
||||
element.visible = ExpGui.get_top_element(define, player).visible
|
||||
element.visible = Gui.get_top_element(define, player).visible
|
||||
end
|
||||
end
|
||||
|
||||
@@ -489,8 +516,8 @@ function Toolbar._ensure_consistency(player)
|
||||
local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
|
||||
for _, button in ipairs(toolbar_buttons) do
|
||||
-- Update the visible state based on if the player is allowed the button
|
||||
local element = ExpGui.get_top_element(button, player)
|
||||
local allowed = ExpGui.top_elements[button]
|
||||
local element = Gui.get_top_element(button, player)
|
||||
local allowed = Gui.top_elements[button]
|
||||
if type(allowed) == "function" then
|
||||
allowed = allowed(player, element)
|
||||
end
|
||||
@@ -500,9 +527,10 @@ function Toolbar._ensure_consistency(player)
|
||||
-- Update the toggle state and hide the linked left element if the button is not allowed
|
||||
local left_define = buttons_with_left_element[button.name]
|
||||
if left_define then
|
||||
local left_element = ExpGui.get_left_element(left_define, player)
|
||||
Toolbar.set_button_toggled_state(button, player, left_element.visible)
|
||||
if not allowed then
|
||||
if allowed then
|
||||
local left_element = Gui.get_left_element(left_define, player)
|
||||
Toolbar.set_button_toggled_state(button, player, left_element.visible, true)
|
||||
else
|
||||
Toolbar.set_left_element_visible_state(left_define, player, false)
|
||||
end
|
||||
end
|
||||
@@ -515,7 +543,7 @@ function Toolbar._ensure_consistency(player)
|
||||
end
|
||||
|
||||
-- Update open_toolbar
|
||||
local top_flow = assert(ExpGui.get_top_flow(player).parent)
|
||||
local top_flow = assert(Gui.get_top_flow(player).parent)
|
||||
for _, open_toolbar in elements.open_toolbar:tracked_elements(player) do
|
||||
open_toolbar.visible = not top_flow.visible
|
||||
end
|
||||
@@ -528,15 +556,15 @@ function Toolbar._ensure_consistency(player)
|
||||
end
|
||||
|
||||
do
|
||||
local default_order --- @type ExpGui.ToolbarOrder
|
||||
local default_order --- @type Gui.ToolbarOrder
|
||||
--- Gets the default order for the toolbar
|
||||
--- @return ExpGui.ToolbarOrder
|
||||
--- @return Gui.ToolbarOrder
|
||||
function Toolbar.get_default_order()
|
||||
if default_order then return default_order end
|
||||
|
||||
local index = 1
|
||||
default_order = {}
|
||||
for define in pairs(ExpGui.top_elements) do
|
||||
for define in pairs(Gui.top_elements) do
|
||||
if define ~= elements.close_toolbar then
|
||||
default_order[index] = { name = define.name, favourite = true }
|
||||
index = index + 1
|
||||
@@ -548,7 +576,7 @@ do
|
||||
end
|
||||
|
||||
--- Toggle the visibility of the toolbar, does not care if buttons are visible
|
||||
elements.toggle_toolbar = ExpGui.element("toggle_toolbar")
|
||||
elements.toggle_toolbar = Gui.define("toggle_toolbar")
|
||||
:track_all_elements()
|
||||
:draw{
|
||||
type = "sprite-button",
|
||||
@@ -557,7 +585,7 @@ elements.toggle_toolbar = ExpGui.element("toggle_toolbar")
|
||||
style = "tool_button",
|
||||
auto_toggle = true,
|
||||
}
|
||||
:style(ExpGui.styles.sprite{
|
||||
:style(Gui.styles.sprite{
|
||||
size = 22,
|
||||
})
|
||||
:on_click(function(def, player, element)
|
||||
@@ -565,14 +593,14 @@ elements.toggle_toolbar = ExpGui.element("toggle_toolbar")
|
||||
end)
|
||||
|
||||
--- Reset the toolbar to its default state
|
||||
elements.reset_toolbar = ExpGui.element("reset_toolbar")
|
||||
elements.reset_toolbar = Gui.define("reset_toolbar")
|
||||
:draw{
|
||||
type = "sprite-button",
|
||||
sprite = "utility/reset",
|
||||
style = "shortcut_bar_button_red",
|
||||
tooltip = { "exp-gui_toolbar-settings.reset" },
|
||||
}
|
||||
:style(ExpGui.styles.sprite{
|
||||
:style(Gui.styles.sprite{
|
||||
size = 22,
|
||||
padding = -1,
|
||||
})
|
||||
@@ -581,13 +609,13 @@ elements.reset_toolbar = ExpGui.element("reset_toolbar")
|
||||
end)
|
||||
|
||||
--- Move an item up/left on the toolbar
|
||||
elements.move_item_up = ExpGui.element("move_item_up")
|
||||
elements.move_item_up = Gui.define("move_item_up")
|
||||
:draw{
|
||||
type = "sprite-button",
|
||||
sprite = "utility/speed_up",
|
||||
tooltip = { "exp-gui_toolbar-settings.move-up" },
|
||||
}
|
||||
:style(ExpGui.styles.sprite{
|
||||
:style(Gui.styles.sprite{
|
||||
size = toolbar_button_small,
|
||||
})
|
||||
:on_click(function(def, player, element)
|
||||
@@ -596,13 +624,13 @@ elements.move_item_up = ExpGui.element("move_item_up")
|
||||
end)
|
||||
|
||||
--- Move an item down/right on the toolbar
|
||||
elements.move_item_down = ExpGui.element("move_item_down")
|
||||
elements.move_item_down = Gui.define("move_item_down")
|
||||
:draw{
|
||||
type = "sprite-button",
|
||||
sprite = "utility/speed_down",
|
||||
tooltip = { "exp-gui_toolbar-settings.move-down" },
|
||||
}
|
||||
:style(ExpGui.styles.sprite{
|
||||
:style(Gui.styles.sprite{
|
||||
size = toolbar_button_small,
|
||||
})
|
||||
:on_click(function(def, player, element)
|
||||
@@ -611,11 +639,11 @@ elements.move_item_down = ExpGui.element("move_item_down")
|
||||
end)
|
||||
|
||||
--- Set an item as a favourite, making it appear on the toolbar
|
||||
elements.set_favourite = ExpGui.element("set_favourite")
|
||||
elements.set_favourite = Gui.define("set_favourite")
|
||||
:draw(function(def, parent, item_define)
|
||||
--- @cast item_define ExpElement
|
||||
local player = ExpGui.get_player(parent)
|
||||
local top_element = ExpGui.get_top_element(item_define, player)
|
||||
local player = Gui.get_player(parent)
|
||||
local top_element = Gui.get_top_element(item_define, player)
|
||||
|
||||
return parent.add{
|
||||
type = "checkbox",
|
||||
@@ -631,7 +659,7 @@ elements.set_favourite = ExpGui.element("set_favourite")
|
||||
}
|
||||
:on_checked_state_changed(function(def, player, element)
|
||||
local define = ExpElement.get(element.tags.element_name --[[ @as string ]])
|
||||
local top_element = ExpGui.get_top_element(define, player)
|
||||
local top_element = Gui.get_top_element(define, player)
|
||||
local had_visible = Toolbar.has_visible_buttons(player)
|
||||
top_element.visible = element.state
|
||||
|
||||
@@ -649,7 +677,7 @@ elements.set_favourite = ExpGui.element("set_favourite")
|
||||
end
|
||||
end)
|
||||
|
||||
elements.toolbar_list_item = ExpGui.element("toolbar_list_item")
|
||||
elements.toolbar_list_item = Gui.define("toolbar_list_item")
|
||||
:draw(function(def, parent, item_define)
|
||||
--- @cast item_define ExpElement
|
||||
local data = {}
|
||||
@@ -665,8 +693,8 @@ elements.toolbar_list_item = ExpGui.element("toolbar_list_item")
|
||||
|
||||
-- Add the button and the icon edit button
|
||||
local element = item_define(flow)
|
||||
local player = ExpGui.get_player(parent)
|
||||
local top_element = ExpGui.get_top_element(item_define, player)
|
||||
local player = Gui.get_player(parent)
|
||||
local top_element = Gui.get_top_element(item_define, player)
|
||||
copy_style(top_element, element)
|
||||
|
||||
-- Add the favourite checkbox and label
|
||||
@@ -683,7 +711,7 @@ elements.toolbar_list_item = ExpGui.element("toolbar_list_item")
|
||||
end)
|
||||
|
||||
--- Main list for all toolbar items
|
||||
elements.toolbar_list = ExpGui.element("toolbar_list")
|
||||
elements.toolbar_list = Gui.define("toolbar_list")
|
||||
:draw(function(def, parent)
|
||||
local scroll = parent.add{
|
||||
type = "scroll-pane",
|
||||
@@ -708,16 +736,16 @@ elements.toolbar_list = ExpGui.element("toolbar_list")
|
||||
}
|
||||
|
||||
-- The main container for the toolbar settings
|
||||
elements.toolbar_settings = ExpGui.element("toolbar_settings")
|
||||
elements.toolbar_settings = Gui.define("toolbar_settings")
|
||||
:draw(function(def, parent)
|
||||
-- Draw the container
|
||||
local frame = ExpGui.elements.container(parent, 268)
|
||||
local frame = Gui.elements.container(parent, 268)
|
||||
frame.style.maximal_width = 268
|
||||
frame.style.minimal_width = 268
|
||||
|
||||
-- Draw the header
|
||||
local player = ExpGui.get_player(parent)
|
||||
local header = ExpGui.elements.header(frame, {
|
||||
local player = Gui.get_player(parent)
|
||||
local header = Gui.elements.header(frame, {
|
||||
caption = { "exp-gui_toolbar-settings.main-caption" },
|
||||
tooltip = { "exp-gui_toolbar-settings.main-tooltip" },
|
||||
})
|
||||
@@ -732,8 +760,8 @@ elements.toolbar_settings = ExpGui.element("toolbar_settings")
|
||||
return frame.parent
|
||||
end)
|
||||
|
||||
ExpGui.add_left_element(elements.core_button_flow, true)
|
||||
ExpGui.add_left_element(elements.toolbar_settings, false)
|
||||
ExpGui.add_top_element(elements.close_toolbar, true)
|
||||
Gui.add_left_element(elements.core_button_flow, true)
|
||||
Gui.add_left_element(elements.toolbar_settings, false)
|
||||
Gui.add_top_element(elements.close_toolbar, true)
|
||||
|
||||
return Toolbar
|
||||
|
||||
573
exp_gui/readme.md
Normal file
573
exp_gui/readme.md
Normal file
@@ -0,0 +1,573 @@
|
||||
# ExpGui Framework Guide
|
||||
|
||||
This guide presents best practices for creating GUIs in Factorio and demonstrates how the ExpGui framework can help streamline development.
|
||||
If you’re new to GUI creation in Factorio, we recommend starting with [Therenas' helpful tutorial](https://github.com/ClaudeMetz/UntitledGuiGuide/wiki) to build a solid foundation.
|
||||
We also recommend [Raiguard's style guide](https://man.sr.ht/~raiguard/factorio-gui-style-guide/) if you are looking to mimic the design principles employed by the game's own guis.
|
||||
|
||||
Additional details for the methods available in this library can be found in the [api reference](./docs/reference.md).
|
||||
|
||||
All examples in this guide assume you are using popular VSCode extensions for Factorio development, such as FMTK (`justarandomgeek.factoriomod-debug`) and LuaLs (`sumneko.lua`).
|
||||
|
||||
## Motivation
|
||||
|
||||
This section explains the motivation behind the framework and the thinking that shaped its design.
|
||||
A full breakdown of my thoughts can be found in the [motivation document](./docs/motivation.md).
|
||||
|
||||
It began with a frustration I had using existing libraries, which often required separating elements, event logic, and data into different parts of the codebase.
|
||||
While that may work for some, I found it unintuitive and overly sparse; jumping between element definitions and event handlers became tiring.
|
||||
I wanted something that felt more cohesive, where all the code for a GUI component could live in one place, and where data and behaviour were naturally scoped to that component.
|
||||
|
||||
The result is a framework built around four focused modules: [ExpElement](./docs/motivation.md#expelement), [GuiData](./docs/motivation.md#guidata), [GuiIter](./docs/motivation.md#guiiter), and [Toolbar](./docs/motivation.md#toolbar).
|
||||
Each part solves a specific problem but is designed to work seamlessly with the others.
|
||||
Additionally, naming conventions and calling patterns play a key role in this framework; they’re not just style preferences, but fundamental to how everything fits together conceptually.
|
||||
|
||||
The goal throughout has been to reduce boilerplate, increase clarity, and make GUI development feel more natural.
|
||||
If you’ve ever felt like writing GUI code was harder than it needed to be, this framework might be what you’re looking for.
|
||||
|
||||
## Glossary
|
||||
|
||||
- "element": A `LuaGuiElement` instance representing a GUI component in Factorio.
|
||||
- "element definition": An instance of `ExpElement` containing the details and logic for creating a GUI element.
|
||||
- "element method": A custom method added to an element definition, the first argument should be a LuaGuiElement belonging to the definition.
|
||||
- "gui event handler": A function called in response to a GUI-specific event (e.g., a button click).
|
||||
- "event handler": A function called in response to any event occurring in the game.
|
||||
- "gui data": Persistent data stored and accessed by GUI event handlers, typically linked to specific elements.
|
||||
- "pre-defined element": An element provided by the framework that includes built-in styles and layout logic for common GUI patterns.
|
||||
- "draw function": A function defined within an element definition that constructs and returns the corresponding `LuaGuiElement`.
|
||||
- "draw table": A plain Lua table used as a shorthand for a draw function, typically employed when no dynamic logic or nested elements are required.
|
||||
- "toolbar": A shared GUI area for displaying module buttons, often positioned at the top-left of the screen.
|
||||
- "cache": Temporarily stored computed data used to avoid repeated expensive calculations; cached variables are often prefixed with an underscore to indicate they should not be accessed directly.
|
||||
|
||||
### Naming conventions
|
||||
|
||||
- `Elements`: A common local variable name used to store returned element definitions from a GUI module, capitalised to avoid shadowing more generic variable names like `elements`.
|
||||
- `Elements.container`: A common name for the variable holding the root element definition, especially when registering it to be drawn on the left flow.
|
||||
- `scroll_table`: A built-in composite element in the framework designed to display tabular data with scrolling support.
|
||||
- `calculate`: A method that generates the data required to draw or refresh GUI elements.
|
||||
- `refresh`: A method that updates the GUI elements to reflect the current data without fully reconstructing the interface.
|
||||
- `link`: A method used to associate elements with data or other elements after initial creation.
|
||||
- `element_data`: A lua table containing all data required to draw or refresh an specific element.
|
||||
- `row_elements`: A lua table containing all elements in a row of a gui table.
|
||||
- `row_data`: A lua table containing all data required to draw or refresh a row of a table.
|
||||
- `add_row`: A method added to table element definitions that appends a single row based on provided data.
|
||||
- `tooltip-`: Prefix used for locale string keys to indicate their intended display location, improving clarity and organisation. Others include `caption-` and `error-`
|
||||
|
||||
## The boiler plate
|
||||
|
||||
### Tip 1: Use `event_handler.add_lib`
|
||||
|
||||
When working with multiple GUI modules, it's a good practice to use `event_handler.add_lib` to register them.
|
||||
This approach helps avoid conflicts between event handlers and makes it easier to scale your mod's interface across different components.
|
||||
`event_handler` is a core lualib provided by factorio, it's full path is `__core__.lualib.event_handler` but should be required as `event_handler`
|
||||
|
||||
The ExpGui framework relies on this same mechanism to register your GUI event handlers, so following this pattern ensures compatibility and consistency.
|
||||
|
||||
By organising your code into libraries and registering them via add_lib, you also improve modularity; making it easier to reason about each part of your GUI separately and debug issues when they arise.
|
||||
|
||||
```lua
|
||||
local add_lib = require("event_handler").add_lib
|
||||
add_lib(require("modules/your_module/gui_foo"))
|
||||
```
|
||||
|
||||
### Tip 2: Return your element definitions
|
||||
|
||||
Each GUI module should return its element definitions as part of its public interface.
|
||||
Doing so offers two major advantages:
|
||||
|
||||
- It makes debugging easier; you can inspect the associated GUI data stored for each element definition, which helps track down issues with state or layout.
|
||||
- It allows your definitions to be reused across other modules, encouraging modularity and reducing duplication.
|
||||
|
||||
Following both Tip 1 and Tip 2 gives you a clean boilerplate structure for starting any GUI module.
|
||||
In most cases, the returned definitions are assigned to a local variable named `Elements` (capitalised).
|
||||
This helps avoid naming conflicts with local variables like `elements`, which are commonly used within the same scope.
|
||||
|
||||
```lua
|
||||
local ExpUtil = require("modules/exp_util")
|
||||
local Gui = require("modules/exp_gui")
|
||||
|
||||
--- @class ExampleGui.elements
|
||||
local Elements = {}
|
||||
|
||||
local e = defines.events
|
||||
return {
|
||||
elements = Elements,
|
||||
events = {
|
||||
|
||||
},
|
||||
on_nth_tick = {
|
||||
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Creating a root element
|
||||
|
||||
### Tip 3: Register a single root element definition to be drawn when a player is created
|
||||
|
||||
Most GUI modules are built around a single root element; an element that contains all other child elements and acts as the entry point to your interface.
|
||||
|
||||
In typical usage, you’ll want this root element to be created automatically when a new player joins the game because it allows for a persistent GUI state.
|
||||
Manually creating it in your own `on_player_created` handler can lead to redundant code and inconsistency.
|
||||
|
||||
Instead, the framework provides a way to register your root element definition, and it will handle drawing it for every newly created player.
|
||||
This ensures the element is always present and your GUI state is initialised properly without extra boilerplate.
|
||||
|
||||
To create a new element definition you should call `Gui.define` with the name of your element, this name must be unique within your mod.
|
||||
In the below example `Elements.container` is defined, this will be expanded in the next tip as all defines should have a draw method rather than using empty.
|
||||
|
||||
```lua
|
||||
--- The root element of the example gui
|
||||
Elements.container = Gui.define("container")
|
||||
:empty()
|
||||
|
||||
--- Add the element to the left flow with it hidden by default
|
||||
Gui.add_left_element(Elements.container, false)
|
||||
```
|
||||
|
||||
### Tip 4: Use pre-defined elements as a starting point
|
||||
|
||||
The framework includes several [pre defined elements](./module/elements.lua) that help maintain a consistent appearance and layout across GUI modules.
|
||||
These elements simplify the process of creating common interface structures while encouraging visual consistency throughout your mod.
|
||||
|
||||
When using a pre defined element, or when defining your own element that contains other elements, you should include a `draw` method in the element definition.
|
||||
This method is responsible for building the GUI structure at runtime.
|
||||
The return value is used by the framework to attach event handlers and track elements, after which it is given to the caller.
|
||||
|
||||
For left side frames, such as in the example module, the `container` element is a good place to start.
|
||||
It provides a standard layout that works well for persistent side panels.
|
||||
|
||||
```lua
|
||||
Elements.container = Gui.define("container")
|
||||
:draw(function(def, parent)
|
||||
-- def is self reference to the element definition
|
||||
-- parent is where you should add your new element
|
||||
|
||||
-- to create an element you call its definition passing a parent element
|
||||
local container = Gui.elements.container(parent)
|
||||
|
||||
-- header is another common pre-defined element, footer exists too
|
||||
-- note, adding custom draw arguments will be covered later
|
||||
local header = Gui.elements.header(container, {
|
||||
caption = { "example-gui.caption-main" }
|
||||
})
|
||||
|
||||
-- for elements registered to be drawn on join, the root element should be returned
|
||||
-- note that container.parent ~= parent because container is a composite element, so use get root
|
||||
return Gui.elements.container.get_root_element(container)
|
||||
end)
|
||||
```
|
||||
|
||||
### Tip 5: Use the toolbar for buttons
|
||||
|
||||
The framework includes a shared toolbar that allows GUI modules to register buttons in a consistent and user friendly way. These buttons follow a standard style and layout, helping your interface stay visually unified across modules.
|
||||
The toolbar also supports player customisation via the toolbox.
|
||||
Players can choose which buttons are visible and rearrange their order according to personal preference.
|
||||
|
||||

|
||||
|
||||
A toolbar button does not require a `left_element`, but if one is provided, the framework will automatically register an `on_click` handler.
|
||||
This handler toggles the visibility of the named element.
|
||||
You can optionally define a `visible` function as part of the button definition.
|
||||
This function is called when the button is first drawn and determines whether a specific player is allowed to see the button.
|
||||
|
||||
```lua
|
||||
Gui.toolbar.create_button{
|
||||
name = "toggle_example_gui",
|
||||
sprite = "item/iron-plate",
|
||||
tooltip = { "example-gui.tooltip-main" },
|
||||
left_element = Elements.container,
|
||||
visible = function(player, element)
|
||||
-- this button will only be visible to admins
|
||||
return player.admin
|
||||
end,
|
||||
}
|
||||
```
|
||||
|
||||
## Receiving user input
|
||||
|
||||
### Tip 6: For simpler elements use draw tables
|
||||
|
||||
If an element does not contain any child elements and all of its properties are static, you can define it using a draw table instead of a full function.
|
||||
|
||||
A draw table is simply a Lua table that describes the structure and properties of a single GUI element.
|
||||
The framework automatically converts this into a draw function internally, making it a convenient shorthand for simple elements.
|
||||
A direct comparison of the two can be found in the [motivation section](./docs/motivation.md#expelement).
|
||||
|
||||
This approach helps reduce boilerplate and improves readability when creating basic buttons, labels, flows, or other standalone GUI elements.
|
||||
|
||||
### Tip 7: Table definitions also work for applying styles
|
||||
|
||||
Styles can be applied to an element using the `:style(function(def, parent, element) end)` method.
|
||||
However, for simpler elements like buttons and labels, you can also define the style directly as a table.
|
||||
|
||||
This shorthand approach allows you to include static style properties (such as font, padding, or alignment) in the same table format used to define the element itself.
|
||||
It helps keep simple element definitions concise and easy to read.
|
||||
|
||||
### Tip 8: Use gui event handler methods
|
||||
|
||||
Instead of writing separate event handlers and manually routing events, you can define GUI event handler methods directly on your element definitions.
|
||||
The framework will automatically register these methods and filter incoming events, calling the correct handler based on the element involved.
|
||||
|
||||
This approach simplifies your code by keeping the event logic close to the element it concerns.
|
||||
It also reduces boilerplate and improves maintainability by leveraging the framework’s built-in event dispatch system.
|
||||
|
||||
All gui event handlers are supported following the naming convention of `on_gui_click` -> `on_click`
|
||||
|
||||
```lua
|
||||
Elements.example_button = Gui.define("example_button")
|
||||
:draw{
|
||||
caption = "Hi",
|
||||
tooltip = { "example-gui.tooltip-example-button" },
|
||||
-- string styles are applied during draw
|
||||
style = "shortcut_bar_button",
|
||||
}
|
||||
:style{
|
||||
size = 24,
|
||||
}
|
||||
:on_click(function(def, player, element, event)
|
||||
player.print("Hello, World!")
|
||||
end)
|
||||
|
||||
-- within Elements.container:draw
|
||||
Elements.example_button(header)
|
||||
```
|
||||
|
||||
## Displaying data
|
||||
|
||||
### Tip 9: Scroll tables are your friend
|
||||
|
||||
Displaying data in a scrollable table is a common GUI pattern, and this framework includes a pre defined composite element specifically for this purpose.
|
||||
|
||||
In the upcoming examples, you will see type annotations used with the element definitions.
|
||||
These annotations are necessary due to limitations in LuaLS, including the explicit type casts (using as) used to help the language server correctly interpret overloaded functions.
|
||||
|
||||
```lua
|
||||
--- @class ExpGui_Example.elements.display_table: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
||||
Elements.display_table = Gui.define("display_table")
|
||||
:draw(function(def, parent)
|
||||
-- 2nd arg is max vertical size, 3rd arg is column count
|
||||
return Gui.elements.scroll_table(parent, 200, 3)
|
||||
end) --[[ @as any ]]
|
||||
```
|
||||
|
||||
### Tip 10: Separate data calculation and drawing
|
||||
|
||||
To avoid repeating code, it’s best to calculate the data you want to display in a separate function from the one that creates the row elements.
|
||||
|
||||
This separation makes your code cleaner and more modular.
|
||||
It also allows you to reuse the calculated data in other methods such as `refresh`, where the GUI needs to update without rebuilding everything from scratch.
|
||||
|
||||
The return type of this function will typically be a collection of locale strings and other values that will be displayed in your GUI.
|
||||
For tables, this should be named `row_data` but other elements should be `display_data` or include more specific details like `team_data`.
|
||||
The value should then be passed to create or refresh an element or table row, tip 14 has an example of this.
|
||||
For intensive calculations called frequently, you can incorporate passing a previous table allocation, see second example.
|
||||
|
||||
```lua
|
||||
--- @class Elements.display_table.row_data
|
||||
--- @field name string
|
||||
--- @field sprite string
|
||||
--- @field caption LocalisedString
|
||||
--- @field count number
|
||||
|
||||
--- @param inventory LuaInventory
|
||||
--- @param item_name string
|
||||
--- @return Elements.display_table.row_data
|
||||
function Elements.display_table.calculate_row_data(inventory, item_name)
|
||||
return {
|
||||
name = item_name,
|
||||
sprite = "item/" .. item_name,
|
||||
name = { "item-name." .. item_name },
|
||||
count = inventory.get_item_count(item_name)
|
||||
}
|
||||
end
|
||||
|
||||
--- @param inventory LuaInventory
|
||||
--- @param item_name string
|
||||
--- @param row_data Elements.display_table.row_data
|
||||
--- @return Elements.display_table.row_data
|
||||
function Elements.display_table.calculate_row_data(inventory, item_name, row_data)
|
||||
row_data = row_data or { name = {} }
|
||||
row_data.name = item_name
|
||||
row_data.sprite = "item/" .. item_name
|
||||
row_data.name[1] = "item-name." .. item_name
|
||||
row_data.count = inventory.get_item_count(item_name)
|
||||
return row_data
|
||||
end
|
||||
```
|
||||
|
||||
### Tip 11: Use a function to add rows rather than an element define
|
||||
|
||||
Element definitions are intended for creating single elements or composite elements with a clear structure.
|
||||
When they are used to create multiple rows in tables, managing data ownership and state can become confusing.
|
||||
|
||||
To keep your code clean and your data flow clear, it’s recommended to extend your table element definition with an `add_row` method.
|
||||
This method handles adding new rows one at a time, keeping row creation logic separate from element definition and making it easier to manage dynamic content.
|
||||
Tip 14 shows an example of `add_row` being used within the container draw function.
|
||||
|
||||
### Tip 12: Store row elements in gui data
|
||||
|
||||
When adding elements to a row, you will often need to reference those elements later for updates or interaction.
|
||||
|
||||
To manage this, use gui data to store references to these elements.
|
||||
The framework provides a convenient initialiser method, `:element_data{}`, which creates an empty table at `def.data[element]`.
|
||||
This table can be used to store per-row GUI element references or other related data.
|
||||
|
||||
For good encapsulation, it is best practice to access gui data only within the methods of the element definition it belongs to.
|
||||
This keeps your data management organised and reduces the risk of unintended side effects.
|
||||
|
||||
```lua
|
||||
--- @class Elements.display_table.row_elements
|
||||
--- @field sprite LuaGuiElement
|
||||
--- @field label_name LuaGuiElement
|
||||
--- @field label_count LuaGuiElement
|
||||
|
||||
--- @param display_table LuaGuiElement
|
||||
--- @param row_data Elements.display_table.row_data
|
||||
function Elements.display_table.add_row(display_table, row_data)
|
||||
local rows = Elements.display_table.data[display_table]
|
||||
assert(rows[row_data.name] == nil, "Row already exists")
|
||||
|
||||
local visible = row_data.count > 0
|
||||
rows[row_data.name] = {
|
||||
sprite = display_table.add{
|
||||
type = "sprite",
|
||||
sprite = row_data.sprite,
|
||||
visible = visible
|
||||
},
|
||||
label_name = display_table.add{
|
||||
type = "label",
|
||||
caption = row_data.name,
|
||||
visible = visible
|
||||
},
|
||||
label_count = display_table.add{
|
||||
type = "label",
|
||||
caption = tostring(row_data.count),
|
||||
visible = visible
|
||||
},
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
## Refreshing displayed data
|
||||
|
||||
### Tip 13: Use 'refresh' functions to optimise updates
|
||||
|
||||
Instead of clearing and rebuilding the entire table every time it changes, it’s more efficient to update the existing GUI elements directly.
|
||||
|
||||
To keep your code clean and modular, place this update logic inside a `refresh` function.
|
||||
This function adjusts the current elements to match the new data state without unnecessary reconstruction.
|
||||
You may also encounter variants like `refresh_all`, `refresh_online`, `refresh_force` to indicate different scopes or contexts for the update.
|
||||
|
||||
```lua
|
||||
--- @param display_table LuaGuiElement
|
||||
--- @param row_data Elements.display_table.row_data
|
||||
function Elements.display_table.refresh_row(display_table, row_data)
|
||||
local row = assert(Elements.display_table.data[display_table][row_data.name])
|
||||
row.label_count.caption = tostring(row_data.count)
|
||||
|
||||
local visible = row_data.count > 0
|
||||
for _, element in pairs(row) do
|
||||
element.visible = visible
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Tip 14: Pass references rather than names
|
||||
|
||||
Instead of using element names to identify GUI elements, it’s better to pass direct references to those elements whenever possible.
|
||||
Using references reduces the impact of GUI restructuring and improves performance by avoiding lookups.
|
||||
However, be cautious not to use references to elements that might be destroyed, as this can lead to invalid references and crashes.
|
||||
|
||||
To maintain encapsulation and avoid tight coupling, passing references often means you’ll need to design your methods to accept custom arguments explicitly.
|
||||
For example, updating a button’s event handler to receive element references directly rather than traversing the GUI tree.
|
||||
|
||||
```lua
|
||||
--- @class ExpGui_Example.elements.example_button: ExpElement
|
||||
--- @field data table<LuaGuiElement, LuaGuiElement>
|
||||
--- @overload fun(parent: LuaGuiElement, display_table: LuaGuiElement): LuaGuiElement
|
||||
Elements.example_button = Gui.define("example_button")
|
||||
:draw{
|
||||
caption = "Refresh",
|
||||
tooltip = { "example-gui.tooltip-example-button" },
|
||||
style = "shortcut_bar_button",
|
||||
}
|
||||
:style{
|
||||
size = 24,
|
||||
}
|
||||
:element_data(
|
||||
-- Set the element data to the first argument given
|
||||
Gui.from_argument(1)
|
||||
)
|
||||
:on_click(function(def, player, element, event)
|
||||
--- @cast def ExpGui_Example.elements.example_button
|
||||
local display_table = def.data[element]
|
||||
for _, item_name in pairs{ "iron-place", "copper-plate", "coal", "stone" } do
|
||||
local row_data = Elements.display_table.calculate_row_data(inventory, item_name)
|
||||
Elements.display_table.refresh(display_table, row_data)
|
||||
end
|
||||
end) --[[ @as any ]]
|
||||
|
||||
-- within Elements.container:draw
|
||||
local inventory = Gui.get_player(container).get_main_inventory()
|
||||
local display_table = Elements.display_table(container)
|
||||
for _, item_name in pairs{ "iron-place", "copper-plate", "coal", "stone" } do
|
||||
local row_data = Elements.display_table.calculate_row_data(inventory, item_name)
|
||||
Elements.display_table.add_row(display_table, row_data)
|
||||
end
|
||||
|
||||
Elements.example_button(header, display_table)
|
||||
```
|
||||
|
||||
### Tip 15: Use the custom gui iterator to optimise refreshes
|
||||
|
||||
When your data requires frequent updates, whether triggered by events or on every nth game tick, it’s efficient to use the framework’s custom GUI iterator.
|
||||
This iterator filters and returns only the specific elements that need refreshing, reducing unnecessary work.
|
||||
|
||||
To enable this, you must tell your element definition which GUI elements to track.
|
||||
In most cases, calling `:track_all_elements()` is sufficient to track all relevant elements automatically.
|
||||
|
||||
For updates that happen every nth tick, it’s better to use `:online_elements()` instead of `:tracked_elements()`.
|
||||
The `online_elements()` iterator returns only elements associated with players currently online, which helps avoid updating GUI elements for disconnected players unnecessarily.
|
||||
|
||||
```lua
|
||||
--- @param event EventData.on_player_main_inventory_changed
|
||||
local function on_player_main_inventory_changed(event)
|
||||
local player = assert(game.get_player(event.player_index))
|
||||
for _player, display_table in Elements.display_table:tracked_elements(player) do
|
||||
for _, item_name in pairs{ "iron-place", "copper-plate", "coal", "stone" } do
|
||||
local row_data = Elements.display_table.calculate_row_data(inventory, item_name)
|
||||
Elements.display_table.refresh(display_table, row_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
### Tip 16: Don't set sizes and instead use horizontally stretchable
|
||||
|
||||
Rather than explicitly setting fixed sizes on GUI elements, it is better to leave sizes undetermined and enable the horizontally stretchable property on the appropriate elements within your GUI.
|
||||
You don’t need to set this property on every element, only on those that are at the deepest level of your GUI hierarchy where flexible spacing is required.
|
||||
|
||||
A common and effective use case is employing stretchable empty widgets to create flexible space between elements.
|
||||
This approach leads to cleaner, more adaptive layouts that adjust gracefully to different languages.
|
||||
|
||||
### Tip 17: Cache data where possible
|
||||
|
||||
If the data you display is common to all players on a force or surface, it’s best to cache this data rather than recalculating it for each player individually.
|
||||
|
||||
You can store cached data as a local variable within a `refresh_all` function to limit its scope and lifetime. Alternatively, if you’re confident in your data management, you may cache it in a higher scope to reuse across multiple refresh cycles.
|
||||
Be cautious when caching in higher scopes, as improper management can lead to desyncs issues between players.
|
||||
|
||||
```lua
|
||||
function Elements.unnamed_element.refresh_all()
|
||||
local force_data = {}
|
||||
for player, unnamed_element in Elements.unnamed_element:online_elements() do
|
||||
local force = player.force --[[ @as LuaForce ]]
|
||||
local element_data = force_data[force.name] or Elements.unnamed_element.calculate_data(force)
|
||||
force_data[force.name] = element_data
|
||||
Elements.unnamed_element.refresh(unnamed_element, element_data)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
```lua
|
||||
local _force_data = {}
|
||||
function Elements.here_be_desyncs.get_data(force)
|
||||
local data = _force_data[force.name] or Elements.here_be_desyncs.calculate_data(force)
|
||||
_force_data[force.name] = data
|
||||
return data
|
||||
end
|
||||
```
|
||||
|
||||
### Tip 18: Use named arguments when many are optional
|
||||
|
||||
For elements like `header` that have many optional arguments, it is better to provide those arguments as named values in a table rather than relying on positional order.
|
||||
This can be done by passing a string key to `Gui.from_argument("key_name", default_value)`, which treats the final argument as a table of named parameters.
|
||||
|
||||
Positional arguments still support default values, but using named arguments improves readability and reduces errors when many options are involved.
|
||||
|
||||
```lua
|
||||
--- @class ExpGui_Example.elements.label: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement, opts: { caption: string?, width: number? }): LuaGuiElement
|
||||
Elements.label = Gui.define("label")
|
||||
:draw{
|
||||
caption = Gui.from_argument("caption"),
|
||||
}
|
||||
:style{
|
||||
width = Gui.from_argument("width", 25),
|
||||
} --[[ @as any ]]
|
||||
```
|
||||
|
||||
### Tip 19: Use force and player data within gui data
|
||||
|
||||
GUI data is not limited to just individual elements—you can also store and share data at the force or player level.
|
||||
This allows multiple elements to access common data relevant to a specific player or force, improving consistency and reducing duplication.
|
||||
Using force and player scoped gui data helps manage state effectively across complex interfaces.
|
||||
|
||||
All GUI data initialisers also accept functions, similar to the `:style` method, enabling you to define dynamic starting states that can change based on the current context.
|
||||
|
||||
```lua
|
||||
:force_data{
|
||||
clicked_time = 0,
|
||||
clicked_by = "No one."
|
||||
}
|
||||
:on_click(function(def, player, element, event)
|
||||
local force = player.force --[[ @as LuaForce ]]
|
||||
local force_data = def.data[force]
|
||||
force_data.clicked_time = event.tick
|
||||
force_data.clicked_by = player.name
|
||||
end)
|
||||
```
|
||||
|
||||
### Tip 20: Have clear data ownership
|
||||
|
||||
Store GUI data in the highest-level element definition where it is needed, then pass references to child elements.
|
||||
This allows children to access and modify the shared data as necessary while keeping ownership clear and centralized.
|
||||
|
||||
```lua
|
||||
-- on the settings button
|
||||
:element_data(
|
||||
Gui.from_argument(1)
|
||||
)
|
||||
|
||||
-- on the parent
|
||||
:draw(function(def, parent)
|
||||
local player = Gui.get_player(parent)
|
||||
local player_data = def.data[player] or {}
|
||||
def.data[player] = player_data
|
||||
|
||||
local flow = parent.add{ type = "flow" }
|
||||
for _, setting in pairs(player_data) do
|
||||
Elements.settings_button(flow, setting)
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
Sometimes, due to the order in which elements are drawn, passing references at creation time isn’t possible.
|
||||
In these cases, a `link` method should be used after creation to connect child elements together.
|
||||
It’s also common to pass a table of elements that can be populated incrementally, helping to manage collections of related GUI components cleanly.
|
||||
|
||||
```lua
|
||||
-- on toggle_enabled
|
||||
:on_click(function(def, player, element, event)
|
||||
--- @cast def ExpGui_Example.elements.toggle_enabled
|
||||
local other_element = def.data[element]
|
||||
if other_element then
|
||||
other_element.enabled = not other_element.enabled
|
||||
end
|
||||
end)
|
||||
|
||||
function Elements.toggle_enabled.link_element(toggle_enabled, other_element)
|
||||
Elements.toggle_enabled.data[toggle_enabled] = other_element
|
||||
end
|
||||
|
||||
-- on the parent
|
||||
:draw(function(def, parent)
|
||||
local flow = parent.add{ type = "flow" }
|
||||
local toggle_enabled = Elements.toggle_enabled(flow)
|
||||
local other_button = Elements.other_button(flow)
|
||||
Elements.toggle_enabled.link_element(toggle_enabled, other_button)
|
||||
end)
|
||||
```
|
||||
Reference in New Issue
Block a user