focusLoss event is sometimes a delayed action

Mads Buvik Sandvei
2026-04-02T20:44:01Z

<!--
PLEASE READ THIS BEFORE POSTING:
If you're reporting an issue specific to an OpenMW fork:
- TES3MP (Multiplayer): Report at https://github.com/TES3MP/openmw-tes3mp/issues
- OpenMW-VR: Report at https://gitlab.com/madsbuvi/openmw/-/issues

Mod list issues or mod installer issues do not belong here.
If reporting an issue with a specific open Merge Request, comment on the MR itself.
-->

## Issue Description

Normally, events used in lua ui layouts are not run as delayed actions, and are consequentially able to create delayed actions.

However in the edge case where `focusLoss` is run as a result of an element:update() call moving or hiding the focused widget, then the event is run as a delayed action, making it unable to create delayed actions.

A simple example script:

mousing over e2 should destroy e1, but instead gives a delayed action error.

If you comment out or remove the focusGain event, then e1 is destroyed as expected on focusLoss.

```lua
local ui = require('openmw.ui')
local I = require('openmw.interfaces')
local async = require('openmw.async')

local e1 = nil
e1 = ui.create{
template = I.MWUI.templates.boxSolid,
layer = 'Modal',
content = ui.content{{
type = ui.TYPE.Text,
props = {
text = 'I should be destroyed when you mouse over the widget below',
textSize = 32,
textColor = util.color.rgb(0.5, 0.5, 0.5),
},
}},
props = {
position = util.vector2(0, 50),
}
}

local e2 = nil
e2 = ui.create{
template = I.MWUI.templates.boxSolid,
layer = 'Modal',
content = ui.content{{
type = ui.TYPE.Text,
props = {
text = 'I should hide when you mouse over me',
textSize = 32,
textColor = util.color.rgb(0.5, 0.5, 0.5),
},
}},
props = {
position = util.vector2(0, 100),
},
events = {
focusGain = async:callback(function()
e2.layout.props.visible = false
e2:update()
end),
focusLoss = async:callback(function()
e1:destroy()
end)
}
}
```