Skip to content

Rewrite Lua UI interface - #6397

Merged
rr- merged 7 commits into
LostArtefacts:developfrom
rr-:lua-ui-angrace
Aug 26, 2026
Merged

Rewrite Lua UI interface#6397
rr- merged 7 commits into
LostArtefacts:developfrom
rr-:lua-ui-angrace

Conversation

@rr-

@rr- rr- commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Checklist

  • I have read the coding conventions
  • I have added a changelog entry about what my pull request accomplishes, or it is an internal change

Description

Adds a new Lua module, trx.signal, and rewrites trx.ui around it. Signals track values and report when they change. Widgets are built once and update only when their signals change, instead of being redrawn every frame. Crucially, widgets stick around once added, and stay in place until removed with trx.ui.regions.remove, which is different from what we have in the C API. We still emit primitive draws on every frame, but these are only direct draws (put text on this X,Y; put a gradient rectangle there) – the Lua widgets orchestrate those with as few changes as possible across, and are orthogonal to the C widgets.

The screen is now split into nine named regions across three bands, replacing the old six. Regions reserve space for script widgets alongside the engine UI, preventing them from overlapping each other.

-- A signal reports when its value changes. The engine owns some; a script
-- polls its own once a tick, and reads a setting as one.
local hp = trx.lara.signals.hp
local music = trx.signal.config("audio.enable_music")
local in_play = trx.signal.polled(function()
  return trx.game.is_playable and not trx.cutscenes.is_playing
end)

-- Signals derive from each other, and combine with `&`, `|` and `~`.
local fill = trx.signal.combine(hp, trx.lara.signals.max_hp, function(hp, max_hp)
  return hp / max_hp
end)
local shown = in_play & ~music

-- A widget is built once and follows the signals it was given.
local panel = trx.ui.widgets.Stack({
  children = {
    trx.ui.widgets.Label({ text = hp:map(tostring) }),
    trx.ui.widgets.Bar({ type = trx.ui.BarType.LARA_HP, value = fill }),
  },
  spacing = 2,
  align = trx.ui.HAlign.CENTER,
  shown = shown,
})

trx.ui.regions.place(trx.ui.Region.TOP_LEFT, panel)

-- A script that lays its own out asks a region for room, then draws into it.
local slot
trx.events.on_ui_draw(function(region)
  if region == trx.ui.Region.BOTTOM_RIGHT then
    local w, h = trx.ui.primitive.measure_text("00:00:00")
    slot = trx.ui.primitive.reserve(region, w, h)
  end
end)

trx.events.on_ui_paint(function()
  local x, y = trx.ui.primitive.slot_box(slot)
  if x ~= nil then
    trx.ui.primitive.text("00:00:00", x, y)
  end
end)

-- `trx.events.on_tick` and `trx.signal.tick` happen once per game tick,
-- whether a level is being played or a menu is open.
trx.events.on_tick(function() end)

A level script's widgets and signals last until the level ends, just like its event handlers. A global script's last for the whole session unless stopped early.

No migration notice, since the previous UI Lua integration was never released.

The Race for the Iris clock is the first widget using this system and now persists instead of being redrawn every frame. Nothing else uses the new system yet.

Resolves #5351 / TRX185

Testing

  • The clock appears once Von Croy sets off and counts up
  • The clock holds still while a cutscene plays
  • The clock stays on screen across a save and a load
  • The clock leaves the screen for good when the finish is reached
  • Bars that are configured to appear in the top-center region do not overlap the clock

@rr-
rr- requested review from a team, aredfan and lahm86 August 25, 2026 08:25
@rr- rr- self-assigned this Aug 25, 2026
@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

@aredfan

aredfan commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Issues:

  • The clock stops counting when saving and reloading.
  • The clock persists onscreen when issuing commands such as /play 2 or /title.
  • When Lara is sliding down the slope at the start of the 2nd level, save and reload, and the clock doesn't appear after the intro cutscene.

@aredfan

aredfan commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

New issue:

  • At the start of level 2, there is a collision that prevents Lara from reaching the bottom of the slope.

@rr-

rr- commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

@aredfan I think this is unrelated and also should be happening on develop - can we confirm?

@aredfan

aredfan commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

@aredfan I think this is unrelated and also should be happening on develop - can we confirm?

I just had a quick look at the latest dev snapshot, it seems the bug doesn't manifest there.

rr- added 2 commits August 26, 2026 10:41
Gives the screen nine named regions across three bands, with widgets in
the same region stacked together. Replaces the old six-region layout and
removes conflicting dialog and inventory space tracking.
Adds `trxc.events.is_level_script`, which tells a level script from a
global one. Modules of the public surface read it to end what a level
script made when the level ends, as its event handlers already do.
rr- added 5 commits August 26, 2026 10:56
Adds `trx.signal` for tracking value changes. Signals can be combined
with `&`, `|`, and `~`, and only report when their result changes.
Settings are exposed through `trx.signal.config`, with other values
available through `trx.signal.polled`. Script state now updates on game
ticks instead of rendered frames, including in menus.
Lets regions reserve space for script-drawn widgets and reports their
layout before drawing. Script drawing is moved out of the overlay and
uses all nine regions, so script widgets stack alongside engine widgets
and remain independent of the overlay.
Adds persistent script widgets driven by signals, avoiding unnecessary
work when nothing changes, and replacing the old per-frame drawing API.

- `trx.ui.primitive` provides layout, quads, and text
- `trx.ui.widgets` builds widgets from them
- `trx.ui.regions` places them on screen
Moves the Race for the Iris clock to a persistent widget that is shown
while the level is being played. Replaces the old per-frame drawing
calls.
Every level script run now ends with the unload event, whether the level
was played or only scanned. Before this, the stats scan that runs every
level's script stayed silent, and what such a run left behind stayed for
the rest of the session.
@rr-

rr- commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Sorry – fixed all :happybat:

@rr-
rr- merged commit 37a20a6 into LostArtefacts:develop Aug 26, 2026
5 checks passed
@rr-
rr- deleted the lua-ui-angrace branch August 26, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Allow for customisation of HUD through LUA

3 participants