Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Recoil:Widgets and Gadgets: Difference between revisions

From Fightorder
Created page with " RmlUi is a UI framework that is defined using a HTML/CSS style workflow (using Lua instead of JS) intended to simplify UI development especially for those already familiar with web development. It is designed for interactive applications, and so is reactive by default. You can learn more about it on the [RmlUI website] and [differences in the Recoil version here](#differences-between-upstream-rmlui-and-rmlui-in-recoil). ## How does RmlUI Work? To get started, it's imp..."
 
No edit summary
 
Line 1: Line 1:
## Introduction
Before we get to Widgets and Gadgets we will start with an overview of some key areas of Recoil and the entry points into game code.


RmlUi is a UI framework that is defined using a HTML/CSS style workflow (using Lua instead of JS) intended to simplify UI development especially for those already familiar with web development. It is designed for interactive applications, and so is reactive by default. You can learn more about it on the [RmlUI website] and [differences in the Recoil version here](#differences-between-upstream-rmlui-and-rmlui-in-recoil).
### Engine Concepts Refresh
- Synced mode is the environment that affects game simulation e.g ordering units around. Synced commands are distributed and run by all players in the game to ensure the simulation remains synced.  
- Unsynced mode is the players own environment, functionality here could for example enhance controls, display helpful information.


## How does RmlUI Work?
When in Unsynced mode LuaIntro, LuaUi, LuaRules and LuaGaia provide read access to synced but only LuaRules and LuaGaia have full access to simulation information (all players units etc.). In LuaIntro and LuaUi read access to synced is scoped to just what that player can see i.e. observing LoS & radar ranges.


To get started, it's important to learn a few key concepts.
### Key Areas
- Context: This is a bundle of documents and data models.
- LuaRules - Generally home to lower level customisations that affect unit behavior or overall game operation
- Document: This is a document tree/DOM (document object model) holding the actual UI.
- LuaGaia - The controller of world objects not owned by a player (e.g wrecks, map features)
- RML: This is the markup language used to define the document, it is very similar to XHTML (HTML but must be well-formed XML).
- LuaIntro - The handler for showing the game load screens.
- RCSS: This is the styling language used to style the document, it is very similar to CSS2 but does differ in some places.
- LuaMenu - The handler for showing the main menu, both present on game launch (if no script is specified) and can be switched back to at any time during the game.
- Data Model: This holds information (a Lua table) that is used in the UI code in data bindings.
- LuaUI - The handler for the in-game UI.


On the Lua side, you are creating 1 or more contexts and adding data models and documents to them.
### Entrypoint into Lua code
Each of these handlers will provide a Lua environment with some predefined globals and start by executing a main.lua file within their respective folders in the game. (LuaGaia and LuaRules have two entry points main.lua for Synced and draw.lua for Unsynced) [more information on the environments and what is available is here](https://springrts.com/wiki/Lua:Environments)


On the Rml side, you are creating documents to be loaded by the Lua which will likely include data bindings to show information from the data model, and references to lua functions for event handlers.
To allow you to control and extend the game/engine behavior predefined functions in your lua code will be called (if they are present) by the engine at certain hook points or on events in the engine. These are commonly referred to as call-ins (but could also be known as event handlers, callbacks). The list of available call-ins is extensive and can be retrieved in Lua using Script.GetCallInList()


As each widget/component you create will likely comprise of several files (.lua, .rml & .rcss) you may find having a folder for each widget/component a useful way to organise your files.
## Widgets and Gadgets
Widgets and Gadgets are concepts that have been adopted by several games using Recoil as a modular way to extend the game and are not an engine level concept. Practically at a general functionality level both widgets and gadgets are the same and often abstracted to just the term addon. The different terms are mostly used to logically separate the types of addons.


## Getting Started
Gadgets typically being lower level game logic, unit behaviour functionality that defines your game and should be present for all players, they are typically only found in LuaRules and LuaGaia and are often included using VFS.ZIP_ONLY so as not to be easily overridden by end users (your packaged version takes priority).  
RmlUi is available in LuaUI (the game UI) with future availability in LuaIntro & LuaMenu planned, so it is already there for you to use.
However, to get going with it there is some setup code that should be considered for loading fonts, cursors and configuring ui scaling, an example script is provided below.
If you are working on a widget for an existing game, it is likely that the game already has some form of this setup code in, so you may be able to skip this section.


### Setup script
Widgets usually involve improving UX or showing helpful UI interfaces, and are often considered things that users can turn on/off in the game to suit their needs, be it through settings or a widget manager UI. They are usually specific to LuaIntro, LuaMenu and LuaUi.


Here is the "[handler](https://github.com/beyond-all-reason/Beyond-All-Reason/blob/master/luaui/rml_setup.lua)", written by lov and ChrisFloofyKitsune, 2 of the people responsible for the RmlUi implementation.
To manage the invocation of Widgets & Gadgets a "**handler**" is setup in the Lua entrypoint. For environments with synced and unsyned entry points these typically use the same handler which calls the same widgets/gadgets but use a function the he handler like IsSyncedCode to check if they are being run in synced mode or unsynced mode and running the appropriate section of code.


```lua
### Basic Addon Outline
--  luaui/rml_setup.lua
--  author:  lov + ChrisFloofyKitsune
--
--  Copyright (C) 2024.
--  Licensed under the terms of the GNU GPL, v2 or later.
 
if (RmlGuard or not RmlUi) then
return
end
-- don't allow this initialization code to be run multiple times
RmlGuard = true
 
--[[
Recoil uses a custom set of Lua bindings (check out rts/Rml/SolLua/bind folder in the C++ engine code)
Aside from the Lua API, the rest of the RmlUi documentation is still relevant
https://mikke89.github.io/RmlUiDoc/index.html
]]
 
--[[ create a common Context to be used for widgets
pros:
* Documents in the same Context can make use of the same DataModels, allowing for less duplicate data
* Documents can be arranged in front/behind of each other dynamically
cons:
* Documents in the same Context can make use of the same data models, leading to side effects
* DataModels must have unique names within the same Context
 
If you have lots of DataModel use you may want to create your own Context
otherwise you should be able to just use the shared Context
 
Contexts created with the Lua API are automatically disposed of when the LuaUi environment is unloaded
]]
 
local oldCreateContext = RmlUi.CreateContext
 
local function NewCreateContext(name)
local context = oldCreateContext(name)
 
-- set up dp_ratio considering the user's UI scale preference and the screen resolution
local viewSizeX, viewSizeY = Spring.GetViewGeometry()
 
local userScale = Spring.GetConfigFloat("ui_scale", 1)
 
local baseWidth = 1920
local baseHeight = 1080
local resFactor = math.min(viewSizeX / baseWidth, viewSizeY / baseHeight)
 
context.dp_ratio = resFactor * userScale
 
context.dp_ratio = math.floor(context.dp_ratio * 100) / 100
return context
end
 
RmlUi.CreateContext = NewCreateContext
 
-- Load fonts
local font_files = {
}
for _, file in ipairs(font_files) do
Spring.Echo("loading font", file)
RmlUi.LoadFontFace(file, true)
end
 
 
-- Mouse Cursor Aliases
--[[
These let standard CSS cursor names be used when doing styling.
If a cursor set via RCSS does not have an alias, it is unchanged.
CSS cursor list: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
RmlUi documentation: https://mikke89.github.io/RmlUiDoc/pages/rcss/user_interface.html#cursor
]]
 
-- when "cursor: normal" is set via RCSS, "cursornormal" will be sent to the engine... and so on for the rest
RmlUi.SetMouseCursorAlias("default", 'cursornormal')
RmlUi.SetMouseCursorAlias("pointer", 'Move') -- command cursors use the command name. TODO: replace with actual pointer cursor?
RmlUi.SetMouseCursorAlias("move", 'uimove')
RmlUi.SetMouseCursorAlias("nesw-resize", 'uiresized2')
RmlUi.SetMouseCursorAlias("nwse-resize", 'uiresized1')
RmlUi.SetMouseCursorAlias("ns-resize", 'uiresizev')
RmlUi.SetMouseCursorAlias("ew-resize", 'uiresizeh')
 
RmlUi.CreateContext("shared")
```
 
What this does is create a unified context 'shared' for all your documents and data models, which is currently the recommended way to architect documents. If you have any custom font files, list them in `font_files`, otherwise leave it empty.


The setup script above can then be included from your `luaui/main.lua` (main.lua is the entry point for the LuaUI environment).
Both widgets and gadgets usually follow a basic blueprint like this:


```lua
```lua
VFS.Include(LUAUI_DIRNAME .. "rml_setup.lua",  nil, VFS.ZIP) -- Runs the script
-- There is an injected global called addon/widget/gadget.
```
local addon = addon
> [!NOTE] The rml_setup.lua script included in base content only imports a font and cursor and doesn't create a context or set scaling
 
 
### Writing Your First Document


You will be creating files with .rml and .rcss extensions, as these closely resemble HTML and CSS it is worth configuring your editor to treat these file extensions as HTML and CSS respectively, see the [IDE Setup](#ide-setup) section for more information.
function addon:GetInfo()
 
Now, create an RML file somewhere under `luaui/widgets/`, like `luaui/widgets/getting_started.rml`. This is the UI document.
 
Writing it is much like HTML by design. There are some differences, the most immediate being the root tag is called rml instead of html, most other RML/HTML differences relate to attributes for databindings and events, but for the time being, we don't need to worry about them.
 
By default RmlUi has *NO* styles, this includes setting default element behaviour like block/inline and styles web developers would expect like input elements default appearances, as a starting point you can use [RmlUi documentation](https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html) though these do not include styles for form elements.
 
Here's a basic widget written by Mupersega.
 
```html
<rml>
<head>
    <title>Rml Starter</title>
 
    <style>
        #rml-starter-widget {
            pointer-events: auto;
            width: 400dp;
            right: 0;
            top: 50%;
            transform: translateY(-90%);
            position: absolute;
            margin-right: 10dp;
        }
        #main-content {
            padding: 10dp;
            border-radius: 8dp;
            z-index: 1;
        }
        #expanding-content {
            transform: translateY(0%);
            transition: top 0.1s linear-in-out;
            z-index: 0;
            height: 100%;
            width: 100%;
            position: absolute;
            top: 0dp;
            left: 0dp;
            border-radius: 8dp;
            display: flex;
            flex-direction: column;
            justify-content: flex-end;
            align-items: center;
            padding-bottom: 20dp;
        }
        /* This is just a checkbox sitting above the entirety of the expanding content */
        /* It is bound directly with data-checked attr to the expanded value */
        #expanding-content>input {
            height: 100%;
            width: 100%;
            z-index: 1;
            position: absolute;
            top: 0dp;
            left: 0dp;
        }
        #expanding-content.expanded {
            top: 90%;
        }
        #expanding-content:hover {
            background-color: rgba(255, 0, 0, 125);
        }
    </style>
</head>
<body>
    <div id="rml-starter-widget" class="relative" data-model="starter_model">
        <div id="main-content">
            <h1 class="text-primary">Welcome to an Rml Starter Widget</h1>
            <p>This is a simple example of an RMLUI widget.</p>
            <div>
                <button onclick="widget:Reload()">reload widget</button>
            </div>
        </div>
        <div id="expanding-content" data-class-expanded="expanded">
            <input type="checkbox" value="expanded" data-checked="expanded"/>
            <p>{{message}}</p>
            <div>
                <span data-for="test, i: testArray">name:{{test.name}} index:{{i}}</span>
            </div>
        </div>
    </div>
</body>
</rml>
```
 
Let's take a look at different areas that are important to look at.
 
`<div id="rml-starter-widget" class="relative" data-model="starter_model">`
1. Here, we bind to the data model using `data-model`. This is what we will need to name the data model in our Lua script later. Everything inside the model will be in scope and beneath the div.
2. Typically, it is recommended to bind your data model inside of a div beneath `body` rather than `body` itself.
 
```html
<div id="expanding-content" data-class-expanded="expanded">
    <input type="checkbox" value="expanded" data-checked="expanded"/>
```
any attribute starting with `data-` is a "data event". We will go through a couple below, but you can find out more here [on the RmlUi docs site](https://mikke89.github.io/RmlUiDoc/pages/data_bindings/views_and_controllers.html).
1. Double curly braces are used to show values from the data model within the document text. e.g. `{{message}}` shows the value of `message` in the data model.
2. `data-class-expanded="expanded"` applies the `expanded` class to the div if the value `expanded` in the data model is `true`.
3. `<input type="checkbox" value="expanded" data-checked="expanded"/>` This checkbox will set the data model value in `data-checked` to true.
4. When the data model is changed, the document is rerendered automatically. So, the expanding div will have the `expanded` class applied to it or removed whenever the check box is toggled.
 
There are data bindings to allow you to loop through arrays (data-for) have conditional sections of the document (data-if) and many others.
 
### The Lua
 
> [!NOTE] For information on setting up your editor to provide intellisense behaviour see the [Lua Language Server guide]({{% ref "lua-language-server" %}}).
 
To load your document into the shared context we created earlier and to define and add the data model you will need to have a lua script something like the one below.
 
```lua
-- luaui/widgets/getting_started.lua
if not RmlUi then
    return
end
 
local widget = widget ---@type Widget
 
function widget:GetInfo()
     return {
     return {
         name = "Rml Starter",
         name = "My addon", -- What it's called (Shows up in addon management menus, if there are any)
         desc = "This widget is a starter example for RmlUi widgets.",
         description = "This is an addon", -- Description of your addon.
         author = "Mupersega",
         author = "Me", -- Who wrote it
         date = "2025",
         date = "Present day, Present Time! Hahahahaha!", -- Nobody really cares about this.
         license = "GNU GPL, v2 or later",
         license = "GPL 3.0", -- License for other people who want to use your addon. Most code in Recoil games are GPL 3.0 or later.
         layer = -1000000,
         layer = 1, -- Affects execution order. Lower is earlier execution, and can go below 0. Useful if addons depend on eachother.
        enabled = true
     }
     }
end
end


local document
-- Actual functional code
local dm_handle
local init_model = {
    expanded = false,
    message = "Hello, find my text in the data model!",
    testArray = {
        { name = "Item 1", value = 1 },
        { name = "Item 2", value = 2 },
        { name = "Item 3", value = 3 },
    },
}


local main_model_name = "starter_model"
if addonHandler:IsSyncedCode() then
    -- Your synced code goes here.
    function addon:Initialize()
        Spring.Echo("We have liftoff!")
    end


function widget:Initialize()
    function addon:SomeSyncedCallin()
    widget.rmlContext = RmlUi.GetContext("shared") -- Get the context from the setup lua
        Spring.Echo("Synced callin!")
    end


     -- Open the model, using init_model as the template. All values inside are copied.
     function addon:Shutdown()
    -- Returns a handle, which we will touch on later.
         Spring.Echo("They came from... behind...")
    dm_handle = widget.rmlContext:OpenDataModel(main_model_name, init_model)
    if not dm_handle then
         Spring.Echo("RmlUi: Failed to open data model ", main_model_name)
        return
     end
     end
     -- Load the document we wrote earlier.
else
     document = widget.rmlContext:LoadDocument("luaui/widgets/getting_started.rml", widget)
     -- Your unsynced code egoes here.
    if not document then
     function addon:Initialize()
         Spring.Echo("Failed to load document")
         Spring.Echo("My life for the horde!")
        return
     end
     end


     -- uncomment the line below to enable debugger
     function addon:SomeUnsyncedCallin()
    -- RmlUi.SetDebugContext('shared')
        Spring.Echo("Unsynced callin!")
 
     end
    document:ReloadStyleSheet()
     document:Show()
end


function widget:Shutdown()
    function addon:Shutdown()
    widget.rmlContext:RemoveDataModel(main_model_name)
        Spring.Echo("Arrrgh!!!")
    if document then
        document:Close()
     end
     end
end
end


-- This function is only for dev experience, ideally it would be a hot reload, and not required at all in a completed widget.
function widget:Reload(event)
    Spring.Echo("Reloading")
    Spring.Echo(event)
    widget:Shutdown()
    widget:Initialize()
end
```
```


### The Data Model Handle
## Handlers
 
Handlers are the code that runs in the entry point to load in addons/widgets/gadgets and distribute callins to them, and example implementation of a handler is included in the [Recoil base content](https://github.com/beyond-all-reason/RecoilEngine/blob/master/cont/base/springcontent/LuaHandler/handler.lua) for LuaIntro, LuaRules and LuaUi, and will likely do well for a simple game, although many games eventually choose to make their own handlers. This means for LuaIntro and LuaUi you can start creating widgets without worrying about handlers in the luaintro/widgets or luaui/widgets folders, and for creating gadgets for LuaRules in luarules/gadgets folder.
In the script, we are given a data model handle. This is a proxy for the Lua table used as the data model; as the Recoil RmlUi integration uses Sol2 as a wrapper data cannot be accessed directly.
 
In most cases, you can simply do `dm_handle.expanded = true`, but this only works for table entries with string keys. What if you have an array, like `testArray` above? To loop through on the Lua side, you will need to get the underlying table:


Below is a very simplified pseudocode example of a handler is below to illustrate adding a gadget and forwarding callins to gadgets.
```lua
```lua
local model_handle = dm_handle:__GetTable()
--- @class GadgetHandler
```
--- @field gadgets Gadget[]
GadgetHandler = {
    gadgets = {},
    shared_table = {}
}


As of writing, this function is not documented in the Lua API, due to some problems with language server generics that haven't been sorted out yet. It is there, however, and will be added back in in the future. it returns the table with the shape of your inital model.
--- @param file string filepath to gadget
You can then iterate through it, and change things as you please:
function register_gadget(file)
 
    if not VFS.FileExists(filename) then
```lua
        return {}
for i, v in pairs(model_handle.testArray) do
    end
     Spring.Echo(i, v.name)
   
     v.value = v.value + 1
    local gadget_env = {}
    gadget_env.shared_table = GadgetHandler.shared_table
    local success, rvalue = pcall(VFS.Include, filename, gadget_env)
     if success then
        return gadget_env.gadget
    end
   
     return {}
end
end
-- If you modified anything in the table, as we did here, we need to set the model handle "dirty", so it refreshes.
dm_handle:__SetDirty("testArray") -- We modified something nested in the array, so we mark the top level table entry as dirty
```
### Debugging
RmlUi comes with a debugger that lets you see and interact with the DOM. It's very handy! To use it, use this:
```lua
RmlUi.SetDebugContext(DATA_MODEL_NAME)
```
And it will appear in-game. A useful idiom I like is to put this in `rml_setup.lua`:
```lua
local DEBUG_RMLUI = true
--...


if DEBUG_RMLUI then
--- @param file string filepath to gadget
     RmlUi.SetDebugContext('shared')
function GadgetHandler:add_gadget(file)
    local new_gadget = register_gadget(file)
     table.insert(GadgetHandler.gadgets, new_gadget)
end
end
```


If you use the shared context, you can see everything that happens in it! Neat!
--- @param delta number
 
function Update(delta)
## Things to Know and Best Practices
    for _, gadget in GadgetHandler.gadgets do
 
        if gadget:Update then
### Things to know
            gadget:Update(delta)
Some of the rough edges you are likely to run into have already been discussed, like the data model thing, but here are some more:
         end
 
---
 
Unlike a web browser a default set of styles is not included, as a starting point you can look at the [RmlUi documentation](https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html) though this doesn't provide styles for form elements.
 
When using Context:OpenDataModel in Lua you must assign the return value to a variable, not doing so will cause the engine to crash when the model is reference in RML.
 
Input elements of type submit & button behave differently to HTML and more like Button elements in that their text is not set by the value attribute. (This is likely to be corrected in a future version)
 
The alpha/transparency value of an RGBA colour is different to CSS (0-1) and instead uses 0-255. The css opacity does still use 0-1.
 
List styling is unavailable (list-style-type etc.), you can still use UL/OL/LI elements but there is no special meaning to them, whilst you could use background images to replicate bullets there isn't a practical way to achieve numbered list items with RML/RCSS.
 
Only solid borders are supported, so the border-style property is unavailable and the shorthand border property doesn't include a style part (```border: 1dp solid black;``` won't work, instead use ```border: 1dp black;```).
 
background-color behaves as expected, all other background styles are different and use decorators instead see the [RmlUi documentation for more information on decorators.](https://mikke89.github.io/RmlUiDoc/pages/rcss/decorators.html)
 
There are two kinds of events: data events, like `data-mouseover`, and normal events, like `onmouseover`. These have different data in their scopes.
- Data events have the data model in their scope.
- Normal events don't have the data model, but they *do* have whatever is passed into `widget` on `widget.rmlContext:LoadDocument`. `widget` doesn't have to be a widget, just any table with data in it.
 
For example, take this:
 
```lua
local model = {
    add = function(a, b)
         Spring.Echo(a + b)
     end
     end
}
end
local document_table = {
    print = function(msg)
        Spring.Echo(msg)
    end,
}
 
dm_handle = widget.rmlContext:OpenDataModel("test", model)
document = widget.rmlContext:LoadDocument("document.rml", document_table)
```


```html
Spring.UpdateCallin('Update')
<h1>Normal Events</h1>
<input type="button" onclick="add(1, 2)">Won't work!</input>
<input type="button" onclick="print('test')">Will work!</input>


<h1>Data Events</h1>
GadgetHandler:add_gadget("luarules/gadgets/testwidget.lua")
<input type="button" data-click="add(1, 2)">Will work!</input>
<input type="button" data-click="print('test')">Won't work!</input>
```
```


### Best Practices
This isn't how it's implemented exactly, but it illustrates the concept. The gadgets are loaded from a file and added to the handler's list of gadgets. When a callin is triggered, such as `Update` here, it calls `Update()` on all gadgets that have it overridden (be aware that the available callins vary by endpoint (Intro/Menu/Ui/Rules/Gaia) you can use Script.GetCallInList() to check what is available).
 
- To create a scalable interface the use of the dp unit over px is recommended as the scale can be set per context with SetDensityIndependentPixelRatio.
- For styles unique to a document, put them in a `style` tag. For shared styles, put them in an `rcss` file.
- Rely on Recoil's RmlUi Lua bindings doc for what you can and can't do. The Recoil implementation has some extra stuff the RmlUi docs don't.
- The Beyond All Reason devs prefer to use one shared context for all rmlui widgets.
 
 
### Differences between upstream RmlUI and RmlUI in Recoil
 
- The SVG element allows either a filepath or raw SVG data in the src attribute, allowing for inline svg to be used (this may change to svg being supported between the opening and closing tag when implemented upstream)
- An additional element ```<texture>``` is available which allows for textures loaded in Recoil to be used, this behaves the same as an ```<img>``` element except the src attribute takes a [texture reference]({{% ref "articles/texture-reference-strings" %}})
 
 
### IDE Setup


To get the best experience with RmlUi, you should set up your editor to use HTML syntax highlighting for .rml file extensions and CSS syntax highlighting for .rcss file extensions.
## Widget/Gadget Communication
For information on how widgets and gadgets can communicate between themselves and to other environments see [Wupget communication and best practices]({{% ref "../wupget" %}})


In VS Code, this can be done by opening a file with the extension you want to setup, then clicking on the language mode  in the bottom right corner of the window (probably shows as Plain Text). From there, you can select "Configure File Association for '.rml'" from the top menu that appears and choose "HTML" from the list. Do the same for .rcss files, but select "CSS".
## Creating User Interfaces


[RmlUI website]: https://mikke89.github.io/RmlUiDoc/
Whether you decide to use a widget based approach or not you will inevitably need to create user interfaces in the game, there are two main options for doing this:-
- OpenGL drawing, this can be done with direct OpenGL commands using the `gl` global provided in Lua environment or using a framework like [FlowUi](https://github.com/beyond-all-reason/Beyond-All-Reason/blob/master/luaui/Widgets/flowui_gl4.lua) from Beyond All Reason or Chili [Chili](https://springrts.com/wiki/Chili) from the game lobby Chobby.
- Recoil has built in support for the [RmlUi framework](https://mikke89.github.io/RmlUiDoc/) which allows you to create user interfaces using HTML/CSS style markup, this was added in 2024. There is a recoil specific article on it here [article on it]({{% ref "getting-started-with-rmlui" %}}).

Latest revision as of 19:09, 23 February 2026

    1. Introduction

Before we get to Widgets and Gadgets we will start with an overview of some key areas of Recoil and the entry points into game code.

      1. Engine Concepts Refresh

- Synced mode is the environment that affects game simulation e.g ordering units around. Synced commands are distributed and run by all players in the game to ensure the simulation remains synced. - Unsynced mode is the players own environment, functionality here could for example enhance controls, display helpful information.

When in Unsynced mode LuaIntro, LuaUi, LuaRules and LuaGaia provide read access to synced but only LuaRules and LuaGaia have full access to simulation information (all players units etc.). In LuaIntro and LuaUi read access to synced is scoped to just what that player can see i.e. observing LoS & radar ranges.

      1. Key Areas

- LuaRules - Generally home to lower level customisations that affect unit behavior or overall game operation - LuaGaia - The controller of world objects not owned by a player (e.g wrecks, map features) - LuaIntro - The handler for showing the game load screens. - LuaMenu - The handler for showing the main menu, both present on game launch (if no script is specified) and can be switched back to at any time during the game. - LuaUI - The handler for the in-game UI.

      1. Entrypoint into Lua code

Each of these handlers will provide a Lua environment with some predefined globals and start by executing a main.lua file within their respective folders in the game. (LuaGaia and LuaRules have two entry points main.lua for Synced and draw.lua for Unsynced) [more information on the environments and what is available is here](https://springrts.com/wiki/Lua:Environments)

To allow you to control and extend the game/engine behavior predefined functions in your lua code will be called (if they are present) by the engine at certain hook points or on events in the engine. These are commonly referred to as call-ins (but could also be known as event handlers, callbacks). The list of available call-ins is extensive and can be retrieved in Lua using Script.GetCallInList()

    1. Widgets and Gadgets

Widgets and Gadgets are concepts that have been adopted by several games using Recoil as a modular way to extend the game and are not an engine level concept. Practically at a general functionality level both widgets and gadgets are the same and often abstracted to just the term addon. The different terms are mostly used to logically separate the types of addons.

Gadgets typically being lower level game logic, unit behaviour functionality that defines your game and should be present for all players, they are typically only found in LuaRules and LuaGaia and are often included using VFS.ZIP_ONLY so as not to be easily overridden by end users (your packaged version takes priority).

Widgets usually involve improving UX or showing helpful UI interfaces, and are often considered things that users can turn on/off in the game to suit their needs, be it through settings or a widget manager UI. They are usually specific to LuaIntro, LuaMenu and LuaUi.

To manage the invocation of Widgets & Gadgets a "**handler**" is setup in the Lua entrypoint. For environments with synced and unsyned entry points these typically use the same handler which calls the same widgets/gadgets but use a function the he handler like IsSyncedCode to check if they are being run in synced mode or unsynced mode and running the appropriate section of code.

      1. Basic Addon Outline

Both widgets and gadgets usually follow a basic blueprint like this:

```lua -- There is an injected global called addon/widget/gadget. local addon = addon

function addon:GetInfo()

   return {
       name = "My addon", -- What it's called (Shows up in addon management menus, if there are any)
       description = "This is an addon", -- Description of your addon.
       author = "Me", -- Who wrote it
       date = "Present day, Present Time! Hahahahaha!", -- Nobody really cares about this.
       license = "GPL 3.0", -- License for other people who want to use your addon. Most code in Recoil games are GPL 3.0 or later.
       layer = 1, -- Affects execution order. Lower is earlier execution, and can go below 0. Useful if addons depend on eachother.
   }

end

-- Actual functional code

if addonHandler:IsSyncedCode() then

   -- Your synced code goes here.
   function addon:Initialize()
       Spring.Echo("We have liftoff!")
   end
   function addon:SomeSyncedCallin()
       Spring.Echo("Synced callin!")
   end
   function addon:Shutdown()
       Spring.Echo("They came from... behind...")
   end

else

   -- Your unsynced code egoes here.
   function addon:Initialize()
       Spring.Echo("My life for the horde!")
   end
   function addon:SomeUnsyncedCallin()
       Spring.Echo("Unsynced callin!")
   end
   function addon:Shutdown()
       Spring.Echo("Arrrgh!!!")
   end

end

```

    1. Handlers

Handlers are the code that runs in the entry point to load in addons/widgets/gadgets and distribute callins to them, and example implementation of a handler is included in the [Recoil base content](https://github.com/beyond-all-reason/RecoilEngine/blob/master/cont/base/springcontent/LuaHandler/handler.lua) for LuaIntro, LuaRules and LuaUi, and will likely do well for a simple game, although many games eventually choose to make their own handlers. This means for LuaIntro and LuaUi you can start creating widgets without worrying about handlers in the luaintro/widgets or luaui/widgets folders, and for creating gadgets for LuaRules in luarules/gadgets folder.

Below is a very simplified pseudocode example of a handler is below to illustrate adding a gadget and forwarding callins to gadgets. ```lua --- @class GadgetHandler --- @field gadgets Gadget[] GadgetHandler = {

   gadgets = {},
   shared_table = {}

}

--- @param file string filepath to gadget function register_gadget(file)

   if not VFS.FileExists(filename) then
       return {}
   end
   
   local gadget_env = {}
   gadget_env.shared_table = GadgetHandler.shared_table
   local success, rvalue = pcall(VFS.Include, filename, gadget_env)
   if success then
       return gadget_env.gadget
   end
   
   return {}

end

--- @param file string filepath to gadget function GadgetHandler:add_gadget(file)

   local new_gadget = register_gadget(file)
   table.insert(GadgetHandler.gadgets, new_gadget)

end

--- @param delta number function Update(delta)

   for _, gadget in GadgetHandler.gadgets do
       if gadget:Update then
           gadget:Update(delta)
       end
   end

end

Spring.UpdateCallin('Update')

GadgetHandler:add_gadget("luarules/gadgets/testwidget.lua") ```

This isn't how it's implemented exactly, but it illustrates the concept. The gadgets are loaded from a file and added to the handler's list of gadgets. When a callin is triggered, such as `Update` here, it calls `Update()` on all gadgets that have it overridden (be aware that the available callins vary by endpoint (Intro/Menu/Ui/Rules/Gaia) you can use Script.GetCallInList() to check what is available).

    1. Widget/Gadget Communication

For information on how widgets and gadgets can communicate between themselves and to other environments see [Wupget communication and best practices](Template:% ref "../wupget" %)

    1. Creating User Interfaces

Whether you decide to use a widget based approach or not you will inevitably need to create user interfaces in the game, there are two main options for doing this:- - OpenGL drawing, this can be done with direct OpenGL commands using the `gl` global provided in Lua environment or using a framework like [FlowUi](https://github.com/beyond-all-reason/Beyond-All-Reason/blob/master/luaui/Widgets/flowui_gl4.lua) from Beyond All Reason or Chili [Chili](https://springrts.com/wiki/Chili) from the game lobby Chobby. - Recoil has built in support for the [RmlUi framework](https://mikke89.github.io/RmlUiDoc/) which allows you to create user interfaces using HTML/CSS style markup, this was added in 2024. There is a recoil specific article on it here [article on it](Template:% ref "getting-started-with-rmlui" %).