mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-26 09:35:28 +00:00
Merge branch 'settings_interface' into 'master'
Settings interface See merge request OpenMW/openmw!1731
This commit is contained in:
commit
39a48d3275
@ -44,11 +44,10 @@ namespace LuaUi
|
||||
|
||||
void Content::insert(size_t index, const sol::table& table)
|
||||
{
|
||||
size_t size = mOrdered.size();
|
||||
if (size < index)
|
||||
if (mOrdered.size() < index)
|
||||
throw std::logic_error("Can't have gaps in UI Content.");
|
||||
mOrdered.insert(mOrdered.begin() + index, table);
|
||||
for (size_t i = index; i < size; ++i)
|
||||
for (size_t i = index; i < mOrdered.size(); ++i)
|
||||
{
|
||||
sol::optional<std::string> name = mOrdered[i]["name"];
|
||||
if (name.has_value())
|
||||
|
@ -55,8 +55,7 @@ namespace LuaUi
|
||||
{
|
||||
WidgetExtension* ext = children[i];
|
||||
sol::table newLayout = content.at(i);
|
||||
if (ext->widget()->getTypeName() == widgetType(newLayout)
|
||||
&& ext->getLayout() == newLayout)
|
||||
if (ext->widget()->getTypeName() == widgetType(newLayout))
|
||||
{
|
||||
updateWidget(ext, newLayout);
|
||||
}
|
||||
|
@ -71,6 +71,7 @@ namespace LuaUi
|
||||
w->forcePosition(childPosition);
|
||||
primary(size) += static_cast<int>(growFactor * getGrow(w));
|
||||
w->forceSize(size);
|
||||
w->updateCoord();
|
||||
primary(childPosition) += primary(size);
|
||||
w->updateCoord();
|
||||
}
|
||||
|
@ -68,3 +68,4 @@ $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw_aux/*lua
|
||||
$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/ai.lua
|
||||
$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/camera.lua
|
||||
$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/mwui/init.lua
|
||||
$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/settings/player.lua
|
||||
|
@ -27,6 +27,7 @@ Lua API reference
|
||||
interface_ai
|
||||
interface_camera
|
||||
interface_mwui
|
||||
interface_settings
|
||||
iterables
|
||||
|
||||
|
||||
@ -94,12 +95,19 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid
|
||||
|
||||
**Interfaces of built-in scripts**
|
||||
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
| Interface | Can be used | Description |
|
||||
+=========================================================+====================+===============================================================+
|
||||
|:ref:`AI <Interface AI>` | by local scripts | | Control basic AI of NPCs and creatures. |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`Camera <Interface Camera>` | by player scripts | | Allows to alter behavior of the built-in camera script |
|
||||
| | | | without overriding the script completely. |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
.. list-table::
|
||||
:widths: 20 20 60
|
||||
|
||||
* - Interface
|
||||
- Can be used
|
||||
- Description
|
||||
* - :ref:`AI <Interface AI>`
|
||||
- by local scripts
|
||||
- Control basic AI of NPCs and creatures.
|
||||
* - :ref:`Camera <Interface Camera>`
|
||||
- by player scripts
|
||||
- | Allows to alter behavior of the built-in camera script
|
||||
| without overriding the script completely.
|
||||
* - :ref:`Settings <Interface Settings>`
|
||||
- by player and global scripts
|
||||
- Save, display and track changes of setting values.
|
||||
|
@ -0,0 +1,6 @@
|
||||
Interface Settings
|
||||
==================
|
||||
|
||||
.. raw:: html
|
||||
:file: generated_html/scripts_omw_settings_player.html
|
||||
|
@ -460,15 +460,22 @@ The order in which the scripts are started is important. So if one mod should ov
|
||||
|
||||
**Interfaces of built-in scripts**
|
||||
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
| Interface | Can be used | Description |
|
||||
+=========================================================+====================+===============================================================+
|
||||
|:ref:`AI <Interface AI>` | by local scripts | | Control basic AI of NPCs and creatures. |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`Camera <Interface Camera>` | by player scripts | | Allows to alter behavior of the built-in camera script |
|
||||
| | | | without overriding the script completely. |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
.. list-table::
|
||||
:widths: 20 20 60
|
||||
|
||||
* - Interface
|
||||
- Can be used
|
||||
- Description
|
||||
* - :ref:`AI <Interface AI>`
|
||||
- by local scripts
|
||||
- Control basic AI of NPCs and creatures.
|
||||
* - :ref:`Camera <Interface Camera>`
|
||||
- by player scripts
|
||||
- | Allows to alter behavior of the built-in camera script
|
||||
| without overriding the script completely.
|
||||
* - :ref:`Settings <Interface Settings>`
|
||||
- by player and global scripts
|
||||
- Save, display and track changes of setting values.
|
||||
|
||||
Event system
|
||||
============
|
||||
|
@ -24,7 +24,7 @@ Properties
|
||||
- ui.ALIGNMENT (Start)
|
||||
- Where to align the children in the main axis.
|
||||
* - arrange
|
||||
- ui.ALIGNMETN (Start)
|
||||
- ui.ALIGNMENT (Start)
|
||||
- How to arrange the children in the cross axis.
|
||||
|
||||
External
|
||||
|
@ -17,6 +17,10 @@ set(LUA_BUILTIN_FILES
|
||||
scripts/omw/console/player.lua
|
||||
scripts/omw/console/global.lua
|
||||
scripts/omw/console/local.lua
|
||||
scripts/omw/settings/player.lua
|
||||
scripts/omw/settings/global.lua
|
||||
scripts/omw/settings/common.lua
|
||||
scripts/omw/settings/render.lua
|
||||
|
||||
l10n/Calendar/en.yaml
|
||||
|
||||
|
@ -4,3 +4,5 @@ PLAYER: scripts/omw/console/player.lua
|
||||
GLOBAL: scripts/omw/console/global.lua
|
||||
CUSTOM: scripts/omw/console/local.lua
|
||||
PLAYER: scripts/omw/mwui/init.lua
|
||||
GLOBAL: scripts/omw/settings/global.lua
|
||||
PLAYER: scripts/omw/settings/player.lua
|
||||
|
135
files/builtin_scripts/scripts/omw/settings/common.lua
Normal file
135
files/builtin_scripts/scripts/omw/settings/common.lua
Normal file
@ -0,0 +1,135 @@
|
||||
local storage = require('openmw.storage')
|
||||
|
||||
local contextSection = storage.playerSection or storage.globalSection
|
||||
local groupSectionKey = 'OmwSettingGroups'
|
||||
local groupSection = contextSection(groupSectionKey)
|
||||
groupSection:removeOnExit()
|
||||
|
||||
local function validateSettingOptions(options)
|
||||
if type(options) ~= 'table' then
|
||||
error('Setting options must be a table')
|
||||
end
|
||||
if type(options.key) ~= 'string' then
|
||||
error('Setting must have a key')
|
||||
end
|
||||
if type(options.permanentStorage) ~= 'boolean' then
|
||||
error('Setting must have a permanentStorage flag')
|
||||
end
|
||||
if type(options.renderer) ~= 'string' then
|
||||
error('Setting must have a renderer')
|
||||
end
|
||||
if type(options.name) ~= 'string' then
|
||||
error('Setting must have a name localization key')
|
||||
end
|
||||
if type(options.description) ~= 'string' then
|
||||
error('Setting must have a descripiton localization key')
|
||||
end
|
||||
end
|
||||
|
||||
local function validateGroupOptions(options)
|
||||
if type(options) ~= 'table' then
|
||||
error('Group options must be a table')
|
||||
end
|
||||
if type(options.key) ~= 'string' then
|
||||
error('Group must have a key')
|
||||
end
|
||||
local conventionPrefix = "Settings"
|
||||
if options.key:sub(1, conventionPrefix:len()) ~= conventionPrefix then
|
||||
print(("Group key %s doesn't start with %s"):format(options.key, conventionPrefix))
|
||||
end
|
||||
if type(options.page) ~= 'string' then
|
||||
error('Group must belong to a page')
|
||||
end
|
||||
if type(options.order) ~= 'number' and type(options.order) ~= 'nil' then
|
||||
error('Group order must be a number')
|
||||
end
|
||||
if type(options.l10n) ~= 'string' then
|
||||
error('Group must have a localization context')
|
||||
end
|
||||
if type(options.name) ~= 'string' then
|
||||
error('Group must have a name localization key')
|
||||
end
|
||||
if type(options.description) ~= 'string' then
|
||||
error('Group must have a description localization key')
|
||||
end
|
||||
if type(options.settings) ~= 'table' then
|
||||
error('Group must have a table of settings')
|
||||
end
|
||||
for _, opt in ipairs(options.settings) do
|
||||
validateSettingOptions(opt)
|
||||
end
|
||||
end
|
||||
|
||||
local function registerSetting(options)
|
||||
return {
|
||||
key = options.key,
|
||||
permanentStorage = options.permanentStorage,
|
||||
default = options.default,
|
||||
renderer = options.renderer,
|
||||
argument = options.argument,
|
||||
|
||||
name = options.name,
|
||||
description = options.description,
|
||||
}
|
||||
end
|
||||
|
||||
local function registerGroup(options)
|
||||
validateGroupOptions(options)
|
||||
if groupSection:get(options.key) then
|
||||
error(('Group with key %s was already registered'):format(options.key))
|
||||
end
|
||||
local group = {
|
||||
key = options.key,
|
||||
page = options.page,
|
||||
order = options.order or 0,
|
||||
l10n = options.l10n,
|
||||
name = options.name,
|
||||
description = options.description,
|
||||
|
||||
settings = {},
|
||||
}
|
||||
local valueSection = contextSection(options.key)
|
||||
for _, opt in ipairs(options.settings) do
|
||||
local setting = registerSetting(opt)
|
||||
if group.settings[setting.key] then
|
||||
error(('Duplicate setting key %s'):format(options.key))
|
||||
end
|
||||
group.settings[setting.key] = setting
|
||||
if not valueSection:get(setting.key) then
|
||||
valueSection:set(setting.key, setting.default)
|
||||
end
|
||||
end
|
||||
groupSection:set(group.key, group)
|
||||
end
|
||||
|
||||
return {
|
||||
getSection = function(global, key)
|
||||
return (global and storage.globalSection or storage.playerSection)(key)
|
||||
end,
|
||||
setGlobalEvent = 'OMWSettingsGlobalSet',
|
||||
groupSectionKey = groupSectionKey,
|
||||
onLoad = function(saved)
|
||||
if not saved then return end
|
||||
for groupKey, settings in pairs(saved) do
|
||||
local section = contextSection(groupKey)
|
||||
for key, value in pairs(settings) do
|
||||
section:set(key, value)
|
||||
end
|
||||
end
|
||||
end,
|
||||
onSave = function()
|
||||
local saved = {}
|
||||
for groupKey, group in pairs(groupSection:asTable()) do
|
||||
local section = contextSection(groupKey)
|
||||
saved[groupKey] = {}
|
||||
for key, value in pairs(section:asTable()) do
|
||||
if not group.settings[key].permanentStorage then
|
||||
saved[groupKey][key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
groupSection:reset()
|
||||
return saved
|
||||
end,
|
||||
registerGroup = registerGroup,
|
||||
}
|
19
files/builtin_scripts/scripts/omw/settings/global.lua
Normal file
19
files/builtin_scripts/scripts/omw/settings/global.lua
Normal file
@ -0,0 +1,19 @@
|
||||
local storage = require('openmw.storage')
|
||||
|
||||
local common = require('scripts.omw.settings.common')
|
||||
|
||||
return {
|
||||
interfaceName = 'Settings',
|
||||
interface = {
|
||||
registerGroup = common.registerGroup,
|
||||
},
|
||||
engineHandlers = {
|
||||
onLoad = common.onLoad,
|
||||
onSave = common.onSave,
|
||||
},
|
||||
eventHandlers = {
|
||||
[common.setGlobalEvent] = function(e)
|
||||
storage.globalSection(e.groupKey):set(e.settingKey, e.value)
|
||||
end,
|
||||
},
|
||||
}
|
160
files/builtin_scripts/scripts/omw/settings/player.lua
Normal file
160
files/builtin_scripts/scripts/omw/settings/player.lua
Normal file
@ -0,0 +1,160 @@
|
||||
local ui = require('openmw.ui')
|
||||
local async = require('openmw.async')
|
||||
local util = require('openmw.util')
|
||||
|
||||
local common = require('scripts.omw.settings.common')
|
||||
local render = require('scripts.omw.settings.render')
|
||||
|
||||
render.registerRenderer('text', function(value, set, arg)
|
||||
return {
|
||||
type = ui.TYPE.TextEdit,
|
||||
props = {
|
||||
size = util.vector2(arg and arg.size or 150, 30),
|
||||
text = value,
|
||||
textColor = util.color.rgb(1, 1, 1),
|
||||
textSize = 15,
|
||||
textAlignV = ui.ALIGNMENT.End,
|
||||
},
|
||||
events = {
|
||||
textChanged = async:callback(function(s) set(s) end),
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
---
|
||||
-- @type PageOptions
|
||||
-- @field #string key A unique key
|
||||
-- @field #string l10n A localization context (an argument of core.l10n)
|
||||
-- @field #string name A key from the localization context
|
||||
-- @field #string description A key from the localization context
|
||||
|
||||
---
|
||||
-- @type GroupOptions
|
||||
-- @field #string key A unique key, starts with "Settings" by convention
|
||||
-- @field #string l10n A localization context (an argument of core.l10n)
|
||||
-- @field #string name A key from the localization context
|
||||
-- @field #string description A key from the localization context
|
||||
-- @field #string page Key of a page which will contain this group
|
||||
-- @field #number order Groups within the same page are sorted by this number, or their key for equal values.
|
||||
-- Defaults to 0.
|
||||
-- @field #list<#SettingOptions> settings A [iterables#List](iterables.html#List) of #SettingOptions
|
||||
|
||||
---
|
||||
-- @type SettingOptions
|
||||
-- @field #string key A unique key
|
||||
-- @field #string name A key from the localization context
|
||||
-- @field #string description A key from the localization context
|
||||
-- @field default A default value
|
||||
-- @field #string renderer A renderer key
|
||||
-- @field argument An argument for the renderer
|
||||
-- @field #boolean permanentStorage Whether the setting should is stored in permanent storage, or in the save file
|
||||
|
||||
return {
|
||||
interfaceName = 'Settings',
|
||||
---
|
||||
-- @module Settings
|
||||
-- @usage
|
||||
-- -- In a player script
|
||||
-- local storage = require('openmw.storage')
|
||||
-- local I = require('openmw.interfaces')
|
||||
-- I.Settings.registerGroup({
|
||||
-- key = 'SettingsPlayerMyMod',
|
||||
-- page = 'MyPage',
|
||||
-- l10n = 'mymod',
|
||||
-- name = 'modName',
|
||||
-- description = 'modDescription',
|
||||
-- settings = {
|
||||
-- {
|
||||
-- key = 'Greeting',
|
||||
-- renderer = 'text',
|
||||
-- name = 'greetingName',
|
||||
-- description = 'greetingDescription',
|
||||
-- default = 'Hello, world!',
|
||||
-- argument = {
|
||||
-- size = 200,
|
||||
-- },
|
||||
-- },
|
||||
-- },
|
||||
-- })
|
||||
-- local playerSettings = storage.playerSection('SettingsPlayerMyMod')
|
||||
-- -- access a setting page registered by a global script
|
||||
-- local globalSettings = storage.globalSection('SettingsGlobalMyMod')
|
||||
interface = {
|
||||
---
|
||||
-- @field [parent=#Settings] #string version
|
||||
version = 0,
|
||||
---
|
||||
-- @function [parent=#Settings] registerPage Register a page to be displayed in the settings menu,
|
||||
-- only available in player scripts
|
||||
-- @param #PageOptions options
|
||||
-- @usage
|
||||
-- I.Settings.registerPage({
|
||||
-- key = 'MyModName',
|
||||
-- l10n = 'MyModName',
|
||||
-- name = 'MyModName',
|
||||
-- description = 'MyModDescription',
|
||||
-- })---
|
||||
registerPage = render.registerPage,
|
||||
---
|
||||
-- @function [parent=#Settings] registerRenderer Register a renderer,
|
||||
-- only avaialable in player scripts
|
||||
-- @param #string key
|
||||
-- @param #function renderer A renderer function, receives setting's value,
|
||||
-- a function to change it and an argument from the setting options
|
||||
-- @usage
|
||||
-- I.Settings.registerRenderer('text', function(value, set, arg)
|
||||
-- return {
|
||||
-- type = ui.TYPE.TextEdit,
|
||||
-- props = {
|
||||
-- size = util.vector2(arg and arg.size or 150, 30),
|
||||
-- text = value,
|
||||
-- textColor = util.color.rgb(1, 1, 1),
|
||||
-- textSize = 15,
|
||||
-- textAlignV = ui.ALIGNMENT.End,
|
||||
-- },
|
||||
-- events = {
|
||||
-- textChanged = async:callback(function(s) set(s) end),
|
||||
-- },
|
||||
-- }
|
||||
-- end)
|
||||
registerRenderer = render.registerRenderer,
|
||||
---
|
||||
-- @function [parent=#Settings] registerGroup Register a group to be attached to a page,
|
||||
-- available both in player and global scripts
|
||||
-- @param #GroupOptions options
|
||||
-- @usage
|
||||
-- I.Settings.registerGroup {
|
||||
-- key = 'SettingsTest',
|
||||
-- page = 'test',
|
||||
-- l10n = 'test',
|
||||
-- name = 'Player',
|
||||
-- description = 'Player settings group',
|
||||
-- settings = {
|
||||
-- {
|
||||
-- key = 'Greeting',
|
||||
-- saveOnly = true,
|
||||
-- default = 'Hi',
|
||||
-- renderer = 'text',
|
||||
-- argument = {
|
||||
-- size = 200,
|
||||
-- },
|
||||
-- name = 'Text Input',
|
||||
-- description = 'Short text input',
|
||||
-- },
|
||||
-- {
|
||||
-- key = 'Key',
|
||||
-- saveOnly = false,
|
||||
-- default = input.KEY.LeftAlt,
|
||||
-- renderer = 'keybind',
|
||||
-- name = 'Key',
|
||||
-- description = 'Bind Key',
|
||||
-- },
|
||||
-- }
|
||||
-- }
|
||||
registerGroup = common.registerGroup,
|
||||
},
|
||||
engineHandlers = {
|
||||
onLoad = common.onLoad,
|
||||
onSave = common.onSave,
|
||||
},
|
||||
}
|
314
files/builtin_scripts/scripts/omw/settings/render.lua
Normal file
314
files/builtin_scripts/scripts/omw/settings/render.lua
Normal file
@ -0,0 +1,314 @@
|
||||
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 common = require('scripts.omw.settings.common')
|
||||
|
||||
local renderers = {}
|
||||
local function registerRenderer(name, renderFunction)
|
||||
renderers[name] = renderFunction
|
||||
end
|
||||
|
||||
local pages = {}
|
||||
local groups = {}
|
||||
local pageOptions = {}
|
||||
|
||||
local padding = function(size)
|
||||
return {
|
||||
props = {
|
||||
size = util.vector2(size, size),
|
||||
}
|
||||
}
|
||||
end
|
||||
local smallPadding = padding(10)
|
||||
local bigPadding = padding(25)
|
||||
|
||||
local pageHeader = {
|
||||
props = {
|
||||
textColor = util.color.rgb(1, 1, 1),
|
||||
textSize = 30,
|
||||
},
|
||||
}
|
||||
local groupHeader = {
|
||||
props = {
|
||||
textColor = util.color.rgb(1, 1, 1),
|
||||
textSize = 25,
|
||||
},
|
||||
}
|
||||
local normal = {
|
||||
props = {
|
||||
textColor = util.color.rgb(1, 1, 1),
|
||||
textSize = 20,
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
if global then
|
||||
core.sendGlobalEvent(common.setGlobalEvent, {
|
||||
groupKey = group.key,
|
||||
settingKey = setting.key,
|
||||
value = value,
|
||||
})
|
||||
else
|
||||
storage.playerSection(group.key):set(setting.key, value)
|
||||
end
|
||||
end
|
||||
local l10n = core.l10n(group.l10n)
|
||||
return {
|
||||
name = setting.key,
|
||||
type = ui.TYPE.Flex,
|
||||
content = ui.content {
|
||||
{
|
||||
type = ui.TYPE.Flex,
|
||||
props = {
|
||||
horizontal = true,
|
||||
align = ui.ALIGNMENT.Start,
|
||||
arrange = ui.ALIGNMENT.End,
|
||||
},
|
||||
content = ui.content {
|
||||
{
|
||||
type = ui.TYPE.Text,
|
||||
template = normal,
|
||||
props = {
|
||||
text = l10n(setting.name),
|
||||
},
|
||||
},
|
||||
smallPadding,
|
||||
renderFunction(value, set, setting.argument),
|
||||
smallPadding,
|
||||
{
|
||||
type = ui.TYPE.Text,
|
||||
template = normal,
|
||||
props = {
|
||||
text = 'Reset',
|
||||
},
|
||||
events = {
|
||||
mouseClick = async:callback(function()
|
||||
set(setting.default)
|
||||
end),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
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 layout = {
|
||||
name = groupLayoutName(group.key, global),
|
||||
type = ui.TYPE.Flex,
|
||||
content = ui.content {
|
||||
{
|
||||
type = ui.TYPE.Flex,
|
||||
props = {
|
||||
horizontal = true,
|
||||
align = ui.ALIGNMENT.Start,
|
||||
arrange = ui.ALIGNMENT.End,
|
||||
},
|
||||
content = ui.content {
|
||||
{
|
||||
name = 'name',
|
||||
type = ui.TYPE.Text,
|
||||
template = groupHeader,
|
||||
props = {
|
||||
text = l10n(group.name),
|
||||
},
|
||||
},
|
||||
smallPadding,
|
||||
{
|
||||
name = 'description',
|
||||
type = ui.TYPE.Text,
|
||||
template = normal,
|
||||
props = {
|
||||
text = l10n(group.description),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
smallPadding,
|
||||
{
|
||||
name = 'settings',
|
||||
type = ui.TYPE.Flex,
|
||||
content = ui.content{},
|
||||
},
|
||||
bigPadding,
|
||||
},
|
||||
}
|
||||
local settingsContent = layout.content.settings.content
|
||||
local valueSection = common.getSection(global, group.key)
|
||||
for _, setting in pairs(group.settings) do
|
||||
settingsContent:add(renderSetting(group, setting, valueSection:get(setting.key), global))
|
||||
end
|
||||
return layout
|
||||
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))
|
||||
table.insert(hints, l10n(page.description))
|
||||
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))
|
||||
table.insert(hints, l10n(group.description))
|
||||
for _, setting in pairs(group.settings) do
|
||||
table.insert(hints, l10n(setting.name))
|
||||
table.insert(hints, l10n(setting.description))
|
||||
end
|
||||
end
|
||||
return table.concat(hints, ' ')
|
||||
end
|
||||
|
||||
local function renderPage(page)
|
||||
local l10n = core.l10n(page.l10n)
|
||||
local layout = {
|
||||
name = page.key,
|
||||
type = ui.TYPE.Flex,
|
||||
content = ui.content {
|
||||
smallPadding,
|
||||
{
|
||||
type = ui.TYPE.Flex,
|
||||
props = {
|
||||
horizontal = true,
|
||||
align = ui.ALIGNMENT.Start,
|
||||
arrange = ui.ALIGNMENT.End,
|
||||
},
|
||||
content = ui.content {
|
||||
{
|
||||
name = 'name',
|
||||
type = ui.TYPE.Text,
|
||||
template = pageHeader,
|
||||
props = {
|
||||
text = l10n(page.name),
|
||||
},
|
||||
},
|
||||
smallPadding,
|
||||
{
|
||||
name = 'description',
|
||||
type = ui.TYPE.Text,
|
||||
template = normal,
|
||||
props = {
|
||||
text = l10n(page.description),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
bigPadding,
|
||||
{
|
||||
name = 'groups',
|
||||
type = ui.TYPE.Flex,
|
||||
content = ui.content {},
|
||||
},
|
||||
},
|
||||
}
|
||||
local groupsContent = layout.content.groups.content
|
||||
local pageGroups = groups[page.key]
|
||||
local sortedGroups = {}
|
||||
for i, v in ipairs(pageGroups) do sortedGroups[i] = v end
|
||||
table.sort(sortedGroups, pageGroupComparator)
|
||||
for _, pageGroup in ipairs(sortedGroups) do
|
||||
local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key)
|
||||
groupsContent:add(renderGroup(group, pageGroup.global))
|
||||
end
|
||||
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 pageOptions[group.page] then return end
|
||||
|
||||
local element = pageOptions[group.page].element
|
||||
local groupLayout = element.layout.content.groups.content[groupLayoutName(group.key, global)]
|
||||
local settingsLayout = groupLayout.content.settings
|
||||
local value = common.getSection(global, group.key):get(settingKey)
|
||||
settingsLayout.content[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))
|
||||
|
||||
if not pages[group.page] then return end
|
||||
local options = renderPage(pages[group.page])
|
||||
pageOptions[group.page].element:destroy()
|
||||
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 type(options.description) ~= 'string' then
|
||||
error('Page must have a description')
|
||||
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,
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user