mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-25 20:56:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			423 lines
		
	
	
		
			No EOL
		
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			423 lines
		
	
	
		
			No EOL
		
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| 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 common = require('scripts.omw.settings.common')
 | |
| 
 | |
| local renderers = {}
 | |
| local function registerRenderer(name, renderFunction)
 | |
|     renderers[name] = renderFunction
 | |
| end
 | |
| 
 | |
| local interfaceL10n = core.l10n('Interface')
 | |
| 
 | |
| local pages = {}
 | |
| local groups = {}
 | |
| local pageOptions = {}
 | |
| 
 | |
| 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 i = 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)
 | |
|     return {
 | |
|         name = setting.key,
 | |
|         type = ui.TYPE.Flex,
 | |
|         props = {
 | |
|             horizontal = true,
 | |
|             arrange = ui.ALIGNMENT.Center,
 | |
|         },
 | |
|         external = {
 | |
|             stretch = 1,
 | |
|         },
 | |
|         content = ui.content {
 | |
|             titleLayout,
 | |
|             growingIntreval,
 | |
|             renderFunction(value, set, argument),
 | |
|         },
 | |
|     }
 | |
| 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:get(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 = {}
 | |
|     local l10n = core.l10n(page.l10n)
 | |
|     table.insert(hints, l10n(page.name))
 | |
|     if page.description then
 | |
|         table.insert(hints, l10n(page.description))
 | |
|     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)
 | |
|     local l10n = core.l10n(page.l10n)
 | |
|     local sortedGroups = {}
 | |
|     for i, v in ipairs(groups[page.key]) do sortedGroups[i] = v end
 | |
|     table.sort(sortedGroups, pageGroupComparator)
 | |
|     local groupLayouts = {}
 | |
|     for _, pageGroup in ipairs(sortedGroups) do
 | |
|         local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key)
 | |
|         table.insert(groupLayouts, renderGroup(group, pageGroup.global))
 | |
|     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,
 | |
|         },
 | |
|     }
 | |
|     return {
 | |
|         name = l10n(page.name),
 | |
|         element = ui.create(layout),
 | |
|         searchHints = generateSearchHints(page),
 | |
|     }
 | |
| end
 | |
| 
 | |
| local function onSettingChanged(global)
 | |
|     return async:callback(function(groupKey, settingKey)
 | |
|         local group = common.getSection(global, common.groupSectionKey):get(groupKey)
 | |
|         if not group or not pageOptions[group.page] then return end
 | |
| 
 | |
|         local value = common.getSection(global, group.key):get(settingKey)
 | |
| 
 | |
|         local element = pageOptions[group.page].element
 | |
|         local groupsLayout = element.layout.content.groups
 | |
|         local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)]
 | |
|         local settingsContent = groupLayout.content.settings.content
 | |
|         settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global)
 | |
|         element:update()
 | |
|     end)
 | |
| end
 | |
| local function onGroupRegistered(global, key)
 | |
|     local group = common.getSection(global, common.groupSectionKey):get(key)
 | |
|     groups[group.page] = groups[group.page] or {}
 | |
|     local pageGroup =  {
 | |
|         key = group.key,
 | |
|         global = global,
 | |
|         order = group.order,
 | |
|     }
 | |
|     table.insert(groups[group.page], pageGroup)
 | |
|     common.getSection(global, group.key):subscribe(onSettingChanged(global))
 | |
|     common.getArgumentSection(global, group.key):subscribe(async:callback(function(_, settingKey)
 | |
|         local groupKey = group.key
 | |
|         local group = common.getSection(global, common.groupSectionKey):get(groupKey)
 | |
|         if not group or not pageOptions[group.page] then return end
 | |
| 
 | |
|         local value = common.getSection(global, group.key):get(settingKey)
 | |
| 
 | |
|         local element = pageOptions[group.page].element
 | |
|         local groupsLayout = element.layout.content.groups
 | |
|         local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)]
 | |
|         local settingsContent = groupLayout.content.settings.content
 | |
|         settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global)
 | |
|         element:update()
 | |
|     end))
 | |
| 
 | |
|     if not pages[group.page] then return end
 | |
|     local options = renderPage(pages[group.page])
 | |
|     if pageOptions[group.page] then
 | |
|         pageOptions[group.page].element:destroy()
 | |
|     else
 | |
|         pageOptions[group.page] = {}
 | |
|     end
 | |
|     for k, v in pairs(options) do
 | |
|         pageOptions[group.page][k] = v
 | |
|     end
 | |
| end
 | |
| local globalGroups = storage.globalSection(common.groupSectionKey)
 | |
| for groupKey in pairs(globalGroups:asTable()) do
 | |
|     onGroupRegistered(true, groupKey)
 | |
| end
 | |
| globalGroups:subscribe(async:callback(function(_, key)
 | |
|     if key then onGroupRegistered(true, key) end
 | |
| end))
 | |
| storage.playerSection(common.groupSectionKey):subscribe(async:callback(function(_, key)
 | |
|     if key then onGroupRegistered(false, key) 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] = renderPage(page)
 | |
|     ui.registerSettingsPage(pageOptions[page.key])
 | |
| end
 | |
| 
 | |
| return {
 | |
|     registerPage = registerPage,
 | |
|     registerRenderer = registerRenderer,
 | |
| } |