mirror of
https://github.com/OpenMW/openmw.git
synced 2025-04-02 13:06:39 +00:00
Merge branch 'settings_interface' into 'master'
Settings interface See merge request OpenMW/openmw!1731
This commit is contained in:
commit
39a48d3275
14 changed files with 677 additions and 22 deletions
|
@ -44,11 +44,10 @@ namespace LuaUi
|
||||||
|
|
||||||
void Content::insert(size_t index, const sol::table& table)
|
void Content::insert(size_t index, const sol::table& table)
|
||||||
{
|
{
|
||||||
size_t size = mOrdered.size();
|
if (mOrdered.size() < index)
|
||||||
if (size < index)
|
|
||||||
throw std::logic_error("Can't have gaps in UI Content.");
|
throw std::logic_error("Can't have gaps in UI Content.");
|
||||||
mOrdered.insert(mOrdered.begin() + index, table);
|
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"];
|
sol::optional<std::string> name = mOrdered[i]["name"];
|
||||||
if (name.has_value())
|
if (name.has_value())
|
||||||
|
|
|
@ -55,8 +55,7 @@ namespace LuaUi
|
||||||
{
|
{
|
||||||
WidgetExtension* ext = children[i];
|
WidgetExtension* ext = children[i];
|
||||||
sol::table newLayout = content.at(i);
|
sol::table newLayout = content.at(i);
|
||||||
if (ext->widget()->getTypeName() == widgetType(newLayout)
|
if (ext->widget()->getTypeName() == widgetType(newLayout))
|
||||||
&& ext->getLayout() == newLayout)
|
|
||||||
{
|
{
|
||||||
updateWidget(ext, newLayout);
|
updateWidget(ext, newLayout);
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ namespace LuaUi
|
||||||
w->forcePosition(childPosition);
|
w->forcePosition(childPosition);
|
||||||
primary(size) += static_cast<int>(growFactor * getGrow(w));
|
primary(size) += static_cast<int>(growFactor * getGrow(w));
|
||||||
w->forceSize(size);
|
w->forceSize(size);
|
||||||
|
w->updateCoord();
|
||||||
primary(childPosition) += primary(size);
|
primary(childPosition) += primary(size);
|
||||||
w->updateCoord();
|
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/ai.lua
|
||||||
$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/camera.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/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_ai
|
||||||
interface_camera
|
interface_camera
|
||||||
interface_mwui
|
interface_mwui
|
||||||
|
interface_settings
|
||||||
iterables
|
iterables
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,12 +95,19 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid
|
||||||
|
|
||||||
**Interfaces of built-in scripts**
|
**Interfaces of built-in scripts**
|
||||||
|
|
||||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
.. list-table::
|
||||||
| Interface | Can be used | Description |
|
:widths: 20 20 60
|
||||||
+=========================================================+====================+===============================================================+
|
|
||||||
|: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. |
|
|
||||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
|
||||||
|
|
||||||
|
* - 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**
|
**Interfaces of built-in scripts**
|
||||||
|
|
||||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
.. list-table::
|
||||||
| Interface | Can be used | Description |
|
:widths: 20 20 60
|
||||||
+=========================================================+====================+===============================================================+
|
|
||||||
|: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. |
|
|
||||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
|
||||||
|
|
||||||
|
* - 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
|
Event system
|
||||||
============
|
============
|
||||||
|
|
|
@ -24,7 +24,7 @@ Properties
|
||||||
- ui.ALIGNMENT (Start)
|
- ui.ALIGNMENT (Start)
|
||||||
- Where to align the children in the main axis.
|
- Where to align the children in the main axis.
|
||||||
* - arrange
|
* - arrange
|
||||||
- ui.ALIGNMETN (Start)
|
- ui.ALIGNMENT (Start)
|
||||||
- How to arrange the children in the cross axis.
|
- How to arrange the children in the cross axis.
|
||||||
|
|
||||||
External
|
External
|
||||||
|
|
|
@ -17,6 +17,10 @@ set(LUA_BUILTIN_FILES
|
||||||
scripts/omw/console/player.lua
|
scripts/omw/console/player.lua
|
||||||
scripts/omw/console/global.lua
|
scripts/omw/console/global.lua
|
||||||
scripts/omw/console/local.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
|
l10n/Calendar/en.yaml
|
||||||
|
|
||||||
|
|
|
@ -4,3 +4,5 @@ PLAYER: scripts/omw/console/player.lua
|
||||||
GLOBAL: scripts/omw/console/global.lua
|
GLOBAL: scripts/omw/console/global.lua
|
||||||
CUSTOM: scripts/omw/console/local.lua
|
CUSTOM: scripts/omw/console/local.lua
|
||||||
PLAYER: scripts/omw/mwui/init.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…
Reference in a new issue