mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-06 00:55:50 +00:00
541 lines
16 KiB
Lua
541 lines
16 KiB
Lua
local menu = require('openmw.menu')
|
|
local ui = require('openmw.ui')
|
|
local util = require('openmw.util')
|
|
local async = require('openmw.async')
|
|
local core = require('openmw.core')
|
|
local storage = require('openmw.storage')
|
|
local I = require('openmw.interfaces')
|
|
|
|
local auxUi = require('openmw_aux.ui')
|
|
|
|
local common = require('scripts.omw.settings.common')
|
|
common.getSection(false, common.groupSectionKey):setLifeTime(storage.LIFE_TIME.GameSession)
|
|
-- need to :reset() on reloadlua as well as game session end
|
|
common.getSection(false, common.groupSectionKey):reset()
|
|
|
|
local renderers = {}
|
|
local function registerRenderer(name, renderFunction)
|
|
renderers[name] = renderFunction
|
|
end
|
|
require('scripts.omw.settings.renderers')(registerRenderer)
|
|
|
|
local interfaceL10n = core.l10n('Interface')
|
|
|
|
local pages = {}
|
|
local groups = {}
|
|
local pageOptions = {}
|
|
local groupElements = {}
|
|
|
|
local interval = { template = I.MWUI.templates.interval }
|
|
local growingIntreval = {
|
|
template = I.MWUI.templates.interval,
|
|
external = {
|
|
grow = 1,
|
|
},
|
|
}
|
|
local spacer = {
|
|
props = {
|
|
size = util.vector2(0, 10),
|
|
},
|
|
}
|
|
local bigSpacer = {
|
|
props = {
|
|
size = util.vector2(0, 50),
|
|
},
|
|
}
|
|
local stretchingLine = {
|
|
template = I.MWUI.templates.horizontalLine,
|
|
external = {
|
|
stretch = 1,
|
|
},
|
|
}
|
|
local spacedLines = function(count)
|
|
local content = {}
|
|
table.insert(content, spacer)
|
|
table.insert(content, stretchingLine)
|
|
for _ = 2, count do
|
|
table.insert(content, interval)
|
|
table.insert(content, stretchingLine)
|
|
end
|
|
table.insert(content, spacer)
|
|
return {
|
|
type = ui.TYPE.Flex,
|
|
external = {
|
|
stretch = 1,
|
|
},
|
|
content = ui.content(content),
|
|
}
|
|
end
|
|
|
|
local function interlaceSeparator(layouts, separator)
|
|
local result = {}
|
|
result[1] = layouts[1]
|
|
for i = 2, #layouts do
|
|
table.insert(result, separator)
|
|
table.insert(result, layouts[i])
|
|
end
|
|
return result
|
|
end
|
|
|
|
local function setSettingValue(global, groupKey, settingKey, value)
|
|
if global then
|
|
core.sendGlobalEvent(common.setGlobalEvent, {
|
|
groupKey = groupKey,
|
|
settingKey = settingKey,
|
|
value = value,
|
|
})
|
|
else
|
|
storage.playerSection(groupKey):set(settingKey, value)
|
|
end
|
|
end
|
|
|
|
local function renderSetting(group, setting, value, global)
|
|
local renderFunction = renderers[setting.renderer]
|
|
if not renderFunction then
|
|
error(('Setting %s of %s has unknown renderer %s'):format(setting.key, group.key, setting.renderer))
|
|
end
|
|
local set = function(value)
|
|
setSettingValue(global, group.key, setting.key, value)
|
|
end
|
|
local l10n = core.l10n(group.l10n)
|
|
local titleLayout = {
|
|
type = ui.TYPE.Flex,
|
|
content = ui.content {
|
|
{
|
|
template = I.MWUI.templates.textHeader,
|
|
props = {
|
|
text = l10n(setting.name),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
if setting.description then
|
|
titleLayout.content:add(interval)
|
|
titleLayout.content:add {
|
|
template = I.MWUI.templates.textParagraph,
|
|
props = {
|
|
text = l10n(setting.description),
|
|
size = util.vector2(300, 0),
|
|
},
|
|
}
|
|
end
|
|
local argument = common.getArgumentSection(global, group.key):get(setting.key)
|
|
local ok, rendererResult = pcall(renderFunction, value, set, argument)
|
|
if not ok then
|
|
print(string.format('Setting %s renderer "%s" error: %s', setting.key, setting.renderer, rendererResult))
|
|
end
|
|
|
|
return {
|
|
name = setting.key,
|
|
type = ui.TYPE.Flex,
|
|
props = {
|
|
horizontal = true,
|
|
arrange = ui.ALIGNMENT.Center,
|
|
},
|
|
external = {
|
|
stretch = 1,
|
|
},
|
|
content = ui.content {
|
|
titleLayout,
|
|
growingIntreval,
|
|
ok and rendererResult or {}, -- TODO: display error?
|
|
},
|
|
}
|
|
end
|
|
|
|
local groupLayoutName = function(key, global)
|
|
return ('%s%s'):format(global and 'global_' or 'player_', key)
|
|
end
|
|
|
|
local function renderGroup(group, global)
|
|
local l10n = core.l10n(group.l10n)
|
|
|
|
local valueSection = common.getSection(global, group.key)
|
|
local settingLayouts = {}
|
|
local sortedSettings = {}
|
|
for _, setting in pairs(group.settings) do
|
|
sortedSettings[setting.order] = setting
|
|
end
|
|
for _, setting in ipairs(sortedSettings) do
|
|
table.insert(settingLayouts, renderSetting(group, setting, valueSection:getCopy(setting.key), global))
|
|
end
|
|
local settingsContent = ui.content(interlaceSeparator(settingLayouts, spacedLines(1)))
|
|
|
|
local resetButtonLayout = {
|
|
template = I.MWUI.templates.box,
|
|
events = {
|
|
mouseClick = async:callback(function()
|
|
for _, setting in pairs(group.settings) do
|
|
setSettingValue(global, group.key, setting.key, setting.default)
|
|
end
|
|
end),
|
|
},
|
|
content = ui.content {
|
|
{
|
|
template = I.MWUI.templates.padding,
|
|
content = ui.content {
|
|
{
|
|
template = I.MWUI.templates.textNormal,
|
|
props = {
|
|
text = interfaceL10n('Reset')
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
local titleLayout = {
|
|
type = ui.TYPE.Flex,
|
|
external = {
|
|
stretch = 1,
|
|
},
|
|
content = ui.content {
|
|
{
|
|
template = I.MWUI.templates.textHeader,
|
|
props = {
|
|
text = l10n(group.name),
|
|
textSize = 20,
|
|
},
|
|
}
|
|
},
|
|
}
|
|
if group.description then
|
|
titleLayout.content:add(interval)
|
|
titleLayout.content:add {
|
|
template = I.MWUI.templates.textParagraph,
|
|
props = {
|
|
text = l10n(group.description),
|
|
size = util.vector2(300, 0),
|
|
},
|
|
}
|
|
end
|
|
|
|
return {
|
|
name = groupLayoutName(group.key, global),
|
|
type = ui.TYPE.Flex,
|
|
external = {
|
|
stretch = 1,
|
|
},
|
|
content = ui.content {
|
|
{
|
|
type = ui.TYPE.Flex,
|
|
props = {
|
|
horizontal = true,
|
|
arrange = ui.ALIGNMENT.Center,
|
|
},
|
|
external = {
|
|
stretch = 1,
|
|
},
|
|
content = ui.content {
|
|
titleLayout,
|
|
growingIntreval,
|
|
resetButtonLayout,
|
|
},
|
|
},
|
|
spacedLines(2),
|
|
{
|
|
name = 'settings',
|
|
type = ui.TYPE.Flex,
|
|
content = settingsContent,
|
|
external = {
|
|
stretch = 1,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
end
|
|
|
|
local function pageGroupComparator(a, b)
|
|
return a.order < b.order or (
|
|
a.order == b.order and a.key < b.key
|
|
)
|
|
end
|
|
|
|
local function generateSearchHints(page)
|
|
local hints = {}
|
|
do
|
|
local l10n = core.l10n(page.l10n)
|
|
table.insert(hints, l10n(page.name))
|
|
if page.description then
|
|
table.insert(hints, l10n(page.description))
|
|
end
|
|
end
|
|
local pageGroups = groups[page.key]
|
|
for _, pageGroup in pairs(pageGroups) do
|
|
local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key)
|
|
local l10n = core.l10n(group.l10n)
|
|
table.insert(hints, l10n(group.name))
|
|
if group.description then
|
|
table.insert(hints, l10n(group.description))
|
|
end
|
|
for _, setting in pairs(group.settings) do
|
|
table.insert(hints, l10n(setting.name))
|
|
if setting.description then
|
|
table.insert(hints, l10n(setting.description))
|
|
end
|
|
end
|
|
end
|
|
return table.concat(hints, ' ')
|
|
end
|
|
|
|
local function renderPage(page, options)
|
|
local l10n = core.l10n(page.l10n)
|
|
local sortedGroups = {}
|
|
for _, group in pairs(groups[page.key]) do
|
|
table.insert(sortedGroups, group)
|
|
end
|
|
table.sort(sortedGroups, pageGroupComparator)
|
|
local groupLayouts = {}
|
|
for _, pageGroup in ipairs(sortedGroups) do
|
|
local group = common.getSection(pageGroup.global, common.groupSectionKey):getCopy(pageGroup.key)
|
|
if not group then
|
|
error(string.format('%s group "%s" was not found', pageGroup.global and 'Global' or 'Player', pageGroup.key))
|
|
end
|
|
local groupElement = groupElements[page.key][group.key]
|
|
if not groupElement or not groupElement.layout then
|
|
groupElement = ui.create(renderGroup(group, pageGroup.global))
|
|
end
|
|
if groupElement.layout == nil then
|
|
error(string.format('Destroyed group element for %s %s', page.key, group.key))
|
|
end
|
|
groupElements[page.key][group.key] = groupElement
|
|
table.insert(groupLayouts, groupElement)
|
|
end
|
|
local groupsLayout = {
|
|
name = 'groups',
|
|
type = ui.TYPE.Flex,
|
|
external = {
|
|
stretch = 1,
|
|
},
|
|
content = ui.content(interlaceSeparator(groupLayouts, bigSpacer)),
|
|
}
|
|
local titleLayout = {
|
|
type = ui.TYPE.Flex,
|
|
external = {
|
|
stretch = 1,
|
|
},
|
|
content = ui.content {
|
|
{
|
|
template = I.MWUI.templates.textHeader,
|
|
props = {
|
|
text = l10n(page.name),
|
|
textSize = 22,
|
|
},
|
|
},
|
|
spacedLines(3),
|
|
},
|
|
}
|
|
if page.description then
|
|
titleLayout.content:add {
|
|
template = I.MWUI.templates.textParagraph,
|
|
props = {
|
|
text = l10n(page.description),
|
|
size = util.vector2(300, 0),
|
|
},
|
|
}
|
|
end
|
|
local layout = {
|
|
name = page.key,
|
|
type = ui.TYPE.Flex,
|
|
props = {
|
|
position = util.vector2(10, 10),
|
|
},
|
|
content = ui.content {
|
|
titleLayout,
|
|
bigSpacer,
|
|
groupsLayout,
|
|
bigSpacer,
|
|
},
|
|
}
|
|
options.name = l10n(page.name)
|
|
options.searchHints = generateSearchHints(page)
|
|
if options.element then
|
|
options.element.layout = layout
|
|
options.element:update()
|
|
else
|
|
options.element = ui.create(layout)
|
|
end
|
|
end
|
|
|
|
local function onSettingChanged(global)
|
|
return async:callback(function(groupKey, settingKey)
|
|
local group = common.getSection(global, common.groupSectionKey):getCopy(groupKey)
|
|
if not group or not pageOptions[group.page] then return end
|
|
|
|
local groupElement = groupElements[group.page][group.key]
|
|
|
|
if not settingKey then
|
|
if groupElement then
|
|
groupElement.layout = renderGroup(group)
|
|
groupElement:update()
|
|
else
|
|
renderPage(pages[group.page], pageOptions[group.page])
|
|
end
|
|
return
|
|
end
|
|
|
|
local value = common.getSection(global, group.key):getCopy(settingKey)
|
|
local settingsContent = groupElement.layout.content.settings.content
|
|
auxUi.deepDestroy(settingsContent[settingKey]) -- support setting renderers which return UI elements
|
|
settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global)
|
|
groupElement:update()
|
|
end)
|
|
end
|
|
|
|
local function onGroupRegistered(global, key)
|
|
local group = common.getSection(global, common.groupSectionKey):get(key)
|
|
if not group then return end
|
|
|
|
groups[group.page] = groups[group.page] or {}
|
|
groupElements[group.page] = groupElements[group.page] or {}
|
|
|
|
local pageGroup = {
|
|
key = group.key,
|
|
global = global,
|
|
order = group.order,
|
|
}
|
|
|
|
if not groups[group.page][pageGroup.key] then
|
|
common.getSection(global, group.key):subscribe(onSettingChanged(global))
|
|
common.getArgumentSection(global, group.key):subscribe(async:callback(function(_, settingKey)
|
|
if settingKey == nil then return end
|
|
|
|
local group = common.getSection(global, common.groupSectionKey):get(group.key)
|
|
if not group or not pageOptions[group.page] then return end
|
|
|
|
local value = common.getSection(global, group.key):getCopy(settingKey)
|
|
|
|
local element = groupElements[group.page][group.key]
|
|
local settingsContent = element.layout.content.settings.content
|
|
settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global)
|
|
element:update()
|
|
end))
|
|
end
|
|
|
|
groups[group.page][pageGroup.key] = pageGroup
|
|
|
|
if not pages[group.page] then return end
|
|
pageOptions[group.page] = pageOptions[group.page] or {}
|
|
renderPage(pages[group.page], pageOptions[group.page])
|
|
end
|
|
|
|
local function updateGroups(global)
|
|
local groupSection = common.getSection(global, common.groupSectionKey)
|
|
for groupKey in pairs(groupSection:asTable()) do
|
|
onGroupRegistered(global, groupKey)
|
|
end
|
|
groupSection:subscribe(async:callback(function(_, key)
|
|
if key then
|
|
onGroupRegistered(global, key)
|
|
else
|
|
for groupKey in pairs(groupSection:asTable()) do
|
|
onGroupRegistered(global, groupKey)
|
|
end
|
|
end
|
|
end))
|
|
end
|
|
local updatePlayerGroups = function() updateGroups(false) end
|
|
local updateGlobalGroups = function() updateGroups(true) end
|
|
|
|
local menuGroups = {}
|
|
local menuPages = {}
|
|
|
|
local function resetPlayerGroups()
|
|
local playerGroupsSection = storage.playerSection(common.groupSectionKey)
|
|
for pageKey, page in pairs(groups) do
|
|
for groupKey in pairs(page) do
|
|
if not menuGroups[groupKey] then
|
|
if groupElements[pageKey][groupKey] then
|
|
groupElements[pageKey][groupKey]:destroy()
|
|
groupElements[pageKey][groupKey] = nil
|
|
end
|
|
page[groupKey] = nil
|
|
playerGroupsSection:set(groupKey, nil)
|
|
end
|
|
end
|
|
local options = pageOptions[pageKey]
|
|
if options then
|
|
if not menuPages[pageKey] then
|
|
if options.element then
|
|
auxUi.deepDestroy(options.element)
|
|
options.element = nil
|
|
end
|
|
ui.removeSettingsPage(options)
|
|
pageOptions[pageKey] = nil
|
|
else
|
|
renderPage(pages[pageKey], options)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function registerPage(options)
|
|
if type(options) ~= 'table' then
|
|
error('Page options must be a table')
|
|
end
|
|
if type(options.key) ~= 'string' then
|
|
error('Page must have a key')
|
|
end
|
|
if type(options.l10n) ~= 'string' then
|
|
error('Page must have a localization context')
|
|
end
|
|
if type(options.name) ~= 'string' then
|
|
error('Page must have a name')
|
|
end
|
|
if options.description ~= nil and type(options.description) ~= 'string' then
|
|
error('Page description key must be a string')
|
|
end
|
|
local page = {
|
|
key = options.key,
|
|
l10n = options.l10n,
|
|
name = options.name,
|
|
description = options.description,
|
|
}
|
|
pages[page.key] = page
|
|
groups[page.key] = groups[page.key] or {}
|
|
pageOptions[page.key] = pageOptions[page.key] or {}
|
|
renderPage(page, pageOptions[page.key])
|
|
ui.registerSettingsPage(pageOptions[page.key])
|
|
end
|
|
|
|
updatePlayerGroups()
|
|
if menu.getState() == menu.STATE.Running then -- handle reloadlua correctly
|
|
updateGlobalGroups()
|
|
end
|
|
|
|
return {
|
|
interfaceName = 'Settings',
|
|
interface = {
|
|
version = 1,
|
|
registerPage = function(options)
|
|
registerPage(options)
|
|
menuPages[options.key] = true
|
|
end,
|
|
registerRenderer = registerRenderer,
|
|
registerGroup = function(options)
|
|
if not options.permanentStorage then
|
|
error('Menu scripts are only allowed to register setting groups with permanentStorage = true')
|
|
end
|
|
common.registerGroup(options)
|
|
menuGroups[options.key] = true
|
|
end,
|
|
updateRendererArgument = common.updateRendererArgument,
|
|
},
|
|
engineHandlers = {
|
|
onStateChanged = function()
|
|
if menu.getState() == menu.STATE.Running then
|
|
updatePlayerGroups()
|
|
updateGlobalGroups()
|
|
elseif menu.getState() == menu.STATE.NoGame then
|
|
resetPlayerGroups()
|
|
end
|
|
end,
|
|
},
|
|
eventHandlers = {
|
|
[common.registerPageEvent] = function(options)
|
|
registerPage(options)
|
|
end,
|
|
}
|
|
}
|