File: ui.lua

package info (click to toggle)
openmw 0.49.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 33,992 kB
  • sloc: cpp: 372,479; xml: 2,149; sh: 1,403; python: 797; makefile: 26
file content (255 lines) | stat: -rw-r--r-- 8,737 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
local ui = require('openmw.ui')
local util = require('openmw.util')
local self = require('openmw.self')
local core = require('openmw.core')
local ambient = require('openmw.ambient')

local MODE = ui._getAllUiModes()
local WINDOW = ui._getAllWindowIds()

local replacedWindows = {}
local hiddenWindows = {}
local modeStack = {}

local modePause = {}
for _, mode in pairs(MODE) do
    modePause[mode] = true
end

local function registerWindow(window, showFn, hideFn)
    if not WINDOW[window] then
        error('At the moment it is only possible to override existing windows. Window "'..
              tostring(window)..'" not found.')
    end
    ui._setWindowDisabled(window, true)
    if replacedWindows[window] then
        replacedWindows[window].hideFn()
    end
    replacedWindows[window] = {showFn = showFn, hideFn = hideFn, visible = false}
    hiddenWindows[window] = nil
end

local function updateHidden(mode, options)
    local toHide = {}
    if options and options.windows then
        for _, w in pairs(ui._getAllowedWindows(mode)) do
            toHide[w] = true
        end
        for _, w in pairs(options.windows) do
            toHide[w] = nil
        end
    end
    for w, _ in pairs(hiddenWindows) do
        if toHide[w] then
            toHide[w] = nil
        else
            hiddenWindows[w] = nil
            if not replacedWindows[w] then
                ui._setWindowDisabled(w, false)
            end
        end
    end
    for w, _ in pairs(toHide) do
        hiddenWindows[w] = true
        if not replacedWindows[w] then
            ui._setWindowDisabled(w, true)
        end
    end
end

local function setMode(mode, options)
    local function impl()
        updateHidden(mode, options)
        ui._setUiModeStack({mode}, options and options.target)
    end
    if mode then
        if not pcall(impl) then
            error('Invalid mode: ' .. tostring(mode))
        end
    else
        ui._setUiModeStack({})
    end
end

local function addMode(mode, options)
    local function impl()
        updateHidden(mode, options)
        ui._setUiModeStack(modeStack, options and options.target)
    end
    modeStack[#modeStack + 1] = mode
    if not pcall(impl) then
        modeStack[#modeStack] = nil
        error('Invalid mode: ' .. tostring(mode))
    end
end

local function removeMode(mode)
    local sizeBefore = #modeStack
    local j = 1
    for i = 1, sizeBefore do
        if modeStack[i] ~= mode then
            modeStack[j] = modeStack[i]
            j = j + 1
        end
    end
    for i = j, sizeBefore do modeStack[i] = nil end
    if sizeBefore > #modeStack then
        ui._setUiModeStack(modeStack)
    end
end

local oldMode = nil
local function onUiModeChanged(changedByLua, arg)
    local newStack = ui._getUiModeStack()
    for i = 1, math.max(#modeStack, #newStack) do
        modeStack[i] = newStack[i]
    end
    for w, state in pairs(replacedWindows) do
        if state.visible then
            state.hideFn()
            state.visible = false
        end
    end
    local mode = newStack[#newStack]
    if mode then
        if not changedByLua then
            updateHidden(mode)
        end
        for _, w in pairs(ui._getAllowedWindows(mode)) do
            local state = replacedWindows[w]
            if state and not hiddenWindows[w] then
                state.showFn(arg)
                state.visible = true
            end
        end
    end
    local shouldPause = false
    for _, m in pairs(modeStack) do
        shouldPause = shouldPause or modePause[m]
    end
    if shouldPause then
        core.sendGlobalEvent('Pause', 'ui')
    else
        core.sendGlobalEvent('Unpause', 'ui')
    end
    self:sendEvent('UiModeChanged', {oldMode = oldMode, newMode = mode, arg = arg})
    oldMode = mode
end

local function onUiModeChangedEvent(data)
    if data.oldMode == data.newMode then
        return
    end
    -- Sounds are processed in the event handler rather than in engine handler
    -- in order to allow them to be overridden in mods.
    if data.newMode == MODE.Journal or data.newMode == MODE.Book then
        ambient.playSound('book open', {scale = false})
    elseif data.oldMode == MODE.Journal or data.oldMode == MODE.Book then
        if not ambient.isSoundPlaying('item book up') then
            ambient.playSound('book close', {scale = false})
        end
    elseif data.newMode == MODE.Scroll or data.oldMode == MODE.Scroll then
        if not ambient.isSoundPlaying('item book up') then
            ambient.playSound('scroll', {scale = false})
        end
    end
end

return {
    interfaceName = 'UI',
    ---
    -- @module UI
    -- @usage require('openmw.interfaces').UI
    interface = {
        --- Interface version
        -- @field [parent=#UI] #number version
        version = 1,

        --- All available UI modes.
        -- Use `view(I.UI.MODE)` in `luap` console mode to see the list.
        -- @field [parent=#UI] #table MODE
        MODE = util.makeStrictReadOnly(MODE),

        --- All windows.
        -- Use `view(I.UI.WINDOW)` in `luap` console mode to see the list.
        -- @field [parent=#UI] #table WINDOW
        WINDOW = util.makeStrictReadOnly(WINDOW),

        --- Register new implementation for the window with given name; overrides previous implementation.
        -- Adding new windows is not supported yet. At the moment it is only possible to override built-in windows.
        -- @function [parent=#UI] registerWindow
        -- @param #string windowName
        -- @param #function showFn Callback that will be called when the window should become visible
        -- @param #function hideFn Callback that will be called when the window should be hidden
        registerWindow = registerWindow,

        --- Returns windows that can be shown in given mode.
        -- @function [parent=#UI] getWindowsForMode
        -- @param #string mode
        -- @return #table
        getWindowsForMode = ui._getAllowedWindows,

        --- Stack of currently active modes
        -- @field [parent=#UI] modes
        modes = util.makeReadOnly(modeStack),

        --- Get current mode (nil if all windows are closed), equivalent to `I.UI.modes[#I.UI.modes]`
        -- @function [parent=#UI] getMode
        -- @return #string
        getMode = function() return modeStack[#modeStack] end,

        --- Drop all active modes and set mode.
        -- @function [parent=#UI] setMode
        -- @param #string mode (optional) New mode
        -- @param #table options (optional) Table with keys 'windows' and/or 'target' (see example).
        -- @usage I.UI.setMode() -- drop all modes
        -- @usage I.UI.setMode('Interface') -- drop all modes and open interface
        -- @usage -- Drop all modes, open interface, but show only the map window.
        -- I.UI.setMode('Interface', {windows = {'Map'}})
        setMode = setMode,

        --- Add mode to stack without dropping other active modes.
        -- @function [parent=#UI] addMode
        -- @param #string mode New mode
        -- @param #table options (optional) Table with keys 'windows' and/or 'target' (see example).
        -- @usage I.UI.addMode('Journal') -- open journal without dropping active modes.
        -- @usage -- Open barter with an NPC
        -- I.UI.addMode('Barter', {target = actor})
        addMode = addMode,

        --- Remove the specified mode from active modes.
        -- @function [parent=#UI] removeMode
        -- @param #string mode Mode to drop
        removeMode = removeMode,

        --- Set whether the mode should pause the game.
        -- @function [parent=#UI] setPauseOnMode
        -- @param #string mode Mode to configure
        -- @param #boolean shouldPause
        setPauseOnMode = function(mode, shouldPause) modePause[mode] = shouldPause end,

        --- Set whether the UI should be visible.
        -- @function [parent=#UI] setHudVisibility
        -- @param #boolean showHud
        setHudVisibility = function(showHud) ui._setHudVisibility(showHud) end,

        ---
        -- Returns if the player HUD is visible or not
        -- @function [parent=#UI] isHudVisible
        -- @return #boolean
        isHudVisible = function() return ui._isHudVisible() end,

        -- TODO
        -- registerHudElement = function(name, showFn, hideFn) end,
        -- showHudElement = function(name, bool) end,
        -- hudElements,  -- map from element name to its visibility
    },
    engineHandlers = {
        _onUiModeChanged = onUiModeChanged,
    },
    eventHandlers = {
        UiModeChanged = onUiModeChangedEvent,
        AddUiMode = function(options) addMode(options.mode, options) end,
        SetUiMode = function(options) setMode(options.mode, options) end,
    },
}