From 096255534a00cf93c50eda583dea9207d6655268 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 7 Mar 2022 21:28:05 +0100 Subject: [PATCH 01/15] Initial Lua Settings API prototype --- files/builtin_scripts/builtin.omwscripts | 2 + .../scripts/omw/settings/common.lua | 150 ++++++++++++++++++ .../scripts/omw/settings/global.lua | 30 ++++ .../scripts/omw/settings/interface.lua | 51 ++++++ .../scripts/omw/settings/player.lua | 26 +++ .../scripts/omw/settings/register.lua | 76 +++++++++ .../scripts/omw/settings/render.lua | 61 +++++++ 7 files changed, 396 insertions(+) create mode 100644 files/builtin_scripts/scripts/omw/settings/common.lua create mode 100644 files/builtin_scripts/scripts/omw/settings/global.lua create mode 100644 files/builtin_scripts/scripts/omw/settings/interface.lua create mode 100644 files/builtin_scripts/scripts/omw/settings/player.lua create mode 100644 files/builtin_scripts/scripts/omw/settings/register.lua create mode 100644 files/builtin_scripts/scripts/omw/settings/render.lua diff --git a/files/builtin_scripts/builtin.omwscripts b/files/builtin_scripts/builtin.omwscripts index af6320e0b2..989575ee1a 100644 --- a/files/builtin_scripts/builtin.omwscripts +++ b/files/builtin_scripts/builtin.omwscripts @@ -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 diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua new file mode 100644 index 0000000000..f6645b3a11 --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -0,0 +1,150 @@ +local prequire = function(path) + local status, result = pcall(function() + return require(path) + end) + return status and result or nil +end + +local core = require('openmw.core') +local types = require('openmw.types') +local storage = require('openmw.storage') +local self = prequire('openmw.self') +local world = prequire('openmw.world') + +local isPlayerScript = self and true or false +local isGlobalScript = world and true or false + +local EVENTS = { + SettingChanged = 'omwSettingsChanged', + SettingSet = 'omwSettingsGlobalSet', + GroupRegistered = 'omwSettingsGroupRegistered', +} + +local SCOPE = { + Global = 'Global', + Player = 'Player', + SaveGlobal = 'SaveGlobal', + SavePlayer = 'SavePlayer', +} + +local groups = storage.globalSection('OMW_Settings_Groups') +local saveGlobalSection = storage.globalSection('OMW_Settings_SaveGlobal') + +if isGlobalScript then + groups:removeOnExit() + saveGlobalSection:removeOnExit() +end + +local savePlayerSection = nil +if isPlayerScript then + savePlayerSection = storage.playerSection('OMW_Setting_SavePlayer') + savePlayerSection:removeOnExit() +end + +local scopes = { + [SCOPE.Global] = storage.globalSection('OMW_Setting_Global'), + [SCOPE.Player] = isPlayerScript and storage.playerSection('OMW_Setting_Player'), + [SCOPE.SaveGlobal] = saveGlobalSection, + [SCOPE.SavePlayer] = savePlayerSection, +} + +local function isGlobalScope(scope) + return scope == SCOPE.Global or scope == SCOPE.SaveGlobal +end + +local function getSetting(groupName, settingName) + local group = groups:get(groupName) + if not group then + error('Unknown group') + end + local setting = group[settingName] + if not setting then + error('Unknown setting') + end + return setting +end + +local function getSettingValue(groupName, settingName) + local setting = getSetting(groupName, settingName) + local scopeSection = scopes[setting.scope] + if not scopeSection then + error(('Setting %s is not available in this context'):format(setting.name)) + end + if not scopeSection:get(groupName) then + scopeSection:set(groupName, {}) + end + return scopeSection:get(groupName)[setting.name] or setting.default +end + +local function notifySettingChange(scope, event) + if isGlobalScope(scope) then + core.sendGlobalEvent(EVENTS.SettingChanged, event) + for _, a in ipairs(world.activeActors) do + if a.type == types.Player then + a:sendEvent(EVENTS.SettingChanged, event) + end + end + else + self:sendEvent(EVENTS.SettingChanged, event) + end +end + +local function setSettingValue(groupName, settingName, value) + local setting = getSetting(groupName, settingName) + local event = { + groupName = groupName, + settingName = setting.name, + value = value, + } + if isPlayerScript and isGlobalScope(setting.scope) then + core.sendGlobalEvent(EVENTS.SettingSet, event) + return + end + + local scopeSection = scopes[setting.scope] + if not scopeSection:get(groupName) then + scopeSection:set(groupName, {}) + end + local copy = scopeSection:getCopy(groupName) + copy[setting.name] = value + scopeSection:set(groupName, copy) + + notifySettingChange(setting.scope, event) +end + +local groupMeta = { + __index = { + get = function(self, settingName) + return getSettingValue(self.name, settingName) + end, + set = function(self, settingName, value) + setSettingValue(self.name, settingName, value) + end, + onChange = function(self, callback) + table.insert(self.__callbacks, callback) + end, + __changed = function(self, settingName, value) + for _, callback in ipairs(self.__callbacks) do + callback(settingName, value) + end + end, + }, +} +local cachedGroups = {} +local function getGroup(groupName) + if not cachedGroups[groupName] then + cachedGroups[groupName] = setmetatable({ + name = groupName, + __callbacks = {}, + }, groupMeta) + end + return cachedGroups[groupName] +end + +return { + EVENTS = EVENTS, + SCOPE = SCOPE, + scopes = scopes, + groups = groups, + getGroup = getGroup, +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/global.lua b/files/builtin_scripts/scripts/omw/settings/global.lua new file mode 100644 index 0000000000..148d21847a --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/global.lua @@ -0,0 +1,30 @@ +local common = require('scripts.omw.settings.common') +local register = require('scripts.omw.settings.register') + +local saveScope = common.scopes[common.SCOPE.SaveGlobal] +return { + interfaceName = 'Settings', + interface = { + SCOPE = common.SCOPE, + getGroup = common.getGroup, + registerGroup = register.registerGroup, + }, + engineHandlers = { + onLoad = function(saved) + common.groups:reset() + saveScope:reset(saved) + end, + onSave = function() + return saveScope:asTable() + end, + onPlayerAdded = register.onPlayerAdded, + }, + eventHandlers = { + [common.EVENTS.SettingChanged] = function(e) + common.getGroup(e.groupName):__changed(e.settingName, e.value) + end, + [common.EVENTS.SettingSet] = function(e) + common.getGroup(e.groupName):set(e.settingName, e.value) + end, + } +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/interface.lua b/files/builtin_scripts/scripts/omw/settings/interface.lua new file mode 100644 index 0000000000..4fb60d2c4d --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/interface.lua @@ -0,0 +1,51 @@ +return function(player) + local core = require('openmw.core') + local types = require('openmw.types') + local world = not player and require('openmw.world') + + + local sections = require('scripts.omw.settings.sections') + local render = player and require('scripts.omw.settings.render') or nil + + local settingChangeEvent = 'omwSettingChanged' + local globalSetEvent = 'omwSettingGlobalSet' + local registerEvent = 'omwSettingGroupRegistered' + + local groups, scopes, SCOPE, isGlobal = sections.groups, sections.scopes, sections.SCOPE, sections.isGlobal + + + + local saveScope = scopes[player and SCOPE.SavePlayer or SCOPE.SaveGlobal] + return { + interfaceName = 'Settings', + interface = { + getGroup = getGroup, + SCOPE = SCOPE, + registerGroup = not player and require('scripts.omw.settings.register') or nil, + registerType = player and render.registerType or nil, + }, + engineHandlers = { + onLoad = function(saved) + if not player then groups:reset() end + saveScope:reset(saved) + end, + onSave = function() + return saveScope:asTable() + end, + }, + eventHandlers = { + [settingChangeEvent] = function(e) + getGroup(e.groupName):__changed(e.settingName, e.value) + end, + [globalSetEvent] = not player and function(e) + local setting = getSetting(e.groupName, e.settingName) + if isGlobal(setting.scope) then + setSettingValue(e.groupName, e.settingName, e.value) + else + error(('Unexpected Setting event for a non-global setting %s'):format(e.settingName)) + end + end or nil, + [registerEvent] = player and render.onGroupRegistered or nil, + } + } +end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua new file mode 100644 index 0000000000..42e333a4be --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -0,0 +1,26 @@ +local common = require('scripts.omw.settings.common') +local render = require('scripts.omw.settings.render') + +local saveScope = common.scopes[common.SCOPE.SavePlayer] +return { + interfaceName = 'Settings', + interface = { + SCOPE = common.SCOPE, + getGroup = common.getGroup, + registerRenderer = render.registerRenderer, + }, + engineHandlers = { + onLoad = function(saved) + saveScope:reset(saved) + end, + onSave = function() + return saveScope:asTable() + end, + }, + eventHandlers = { + [common.EVENTS.SettingChanged] = function(e) + common.getGroup(e.groupName):__changed(e.settingName, e.value) + end, + [common.EVENTS.GroupRegistered] = render.onGroupRegistered, + } +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua new file mode 100644 index 0000000000..93dcfc18bb --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/register.lua @@ -0,0 +1,76 @@ +local world = require('openmw.world') +local types = require('openmw.types') + +local common = require('scripts.omw.settings.common') + +local groups, SCOPE = common.groups, common.SCOPE + +local function validScope(scope) + local valid = false + for _, v in pairs(SCOPE) do + if v == scope then + valid = true + break + end + end + return valid +end + +local function validateSettingOptions(options) + if type(options.name) ~= 'string' then + error('Setting must have a name') + end + if options.default == nil then + error('Setting must have a default value') + end + if type(options.description) ~= 'string' then + error('Setting must have a description') + end + if not validScope(options.scope) then + error(('Invalid setting scope %s'):format(options.scope)) + end + if type(options.renderer) ~= 'string' then + error('Setting must have a renderer') + end +end + +local function addSetting(group, options) + validateSettingOptions(options) + if group[options.name] then + error(('Duplicate setting name %s'):format(options.name)) + end + group[options.name] = { + name = options.name, + scope = options.scope or SCOPE.Global, + default = options.default, + description = options.description, + renderer = options.renderer, + } +end + +local function registerGroup(groupName, list) + if groups:get(groupName) then + print(('Overwriting group %s'):format(groupName)) + end + local settings = {} + for _, opt in ipairs(list) do + addSetting(settings, opt) + end + groups:set(groupName, settings) + for _, a in ipairs(world.activeActors) do + if a.type == types.Player and a:isValid() then + a:sendEvent(common.EVENTS.GroupRegistered, groupName) + end + end +end + +local function onPlayerAdded(player) + for groupName in pairs(groups:asTable()) do + player:sendEvent(common.EVENTS.GroupRegistered, groupName) + end +end + +return { + registerGroup = registerGroup, + onPlayerAdded = onPlayerAdded, +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua new file mode 100644 index 0000000000..e77badcff7 --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -0,0 +1,61 @@ +local ui = require('openmw.ui') +local util = require('openmw.util') + +local common = require('scripts.omw.settings.common') + +local renderers = {} +local function registerRenderer(name, renderFunction) + renderers[name] = renderFunction +end + +local groupOptions = {} + +local function renderSetting(groupName, setting, value, index) + local renderFunction = renderers[setting.renderer] + if not renderFunction then + error(('Setting %s of %s has unknown renderer %s'):format(setting.name, groupName, setting.renderer)) + end + local layout = renderFunction(setting, value or setting.default, function(value) + local group = common.getGroup(groupName) + group:set(setting.name, value) + local element = groupOptions[groupName].element + element.layout.content[setting.name] = renderSetting(groupName, setting, value, index) + element:update() + end) + layout.name = setting.name + -- temporary hacky position and size + layout.props = layout.props or {} + layout.props.position = util.vector2(0, 100 * (index - 1)) + layout.props.size = util.vector2(400, 100) + return layout +end + +local function onGroupRegistered(groupName) + local group = common.groups:get(groupName) + local layout = { + content = ui.content{}, + } + local searchHints = { groupName } + local count = 0 + for _, setting in pairs(group) do + count = count + 1 + layout.content:add(renderSetting(groupName, setting, setting.default, count)) + table.insert(searchHints, setting.name) + end + layout.props = { + size = util.vector2(400, 100 * count) + } + local options = { + name = groupName, + element = ui.create(layout), + searchHints = table.concat(searchHints, ' '), + } + groupOptions[groupName] = options + print(('registering group %s'):format(groupName)) + ui.registerSettingsPage(options) +end + +return { + onGroupRegistered = onGroupRegistered, + registerRenderer = registerRenderer, +} \ No newline at end of file From aea2c019de175c1ff64e5799d85c4faec3dc5af7 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 29 Mar 2022 20:17:48 +0200 Subject: [PATCH 02/15] Fix content names for layouts inserted with :add() --- components/lua_ui/content.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/lua_ui/content.cpp b/components/lua_ui/content.cpp index 6f9cf61f2f..e7cf474bc9 100644 --- a/components/lua_ui/content.cpp +++ b/components/lua_ui/content.cpp @@ -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 name = mOrdered[i]["name"]; if (name.has_value()) From 1f5e3b78d4c0dcbeb4a9a6e0c5c8f36629120ba6 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 7 Apr 2022 17:43:03 +0200 Subject: [PATCH 03/15] Use Flex, don't force re-renders on layout table changes --- components/lua_ui/element.cpp | 3 +-- components/lua_ui/flex.cpp | 1 + .../scripts/omw/settings/render.lua | 16 +++++++--------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 46eb543cca..9f70cfc1da 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -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); } diff --git a/components/lua_ui/flex.cpp b/components/lua_ui/flex.cpp index e90fba2a1a..dfe943a02f 100644 --- a/components/lua_ui/flex.cpp +++ b/components/lua_ui/flex.cpp @@ -71,6 +71,7 @@ namespace LuaUi w->forcePosition(childPosition); primary(size) += static_cast(growFactor * getGrow(w)); w->forceSize(size); + w->updateCoord(); primary(childPosition) += primary(size); w->updateCoord(); } diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index e77badcff7..0f9249a390 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -19,32 +19,30 @@ local function renderSetting(groupName, setting, value, index) local group = common.getGroup(groupName) group:set(setting.name, value) local element = groupOptions[groupName].element - element.layout.content[setting.name] = renderSetting(groupName, setting, value, index) + local settingLayout = renderSetting(groupName, setting, value, index) + settingLayout.name = setting.name + element.layout.content[setting.name] = settingLayout element:update() end) layout.name = setting.name - -- temporary hacky position and size - layout.props = layout.props or {} - layout.props.position = util.vector2(0, 100 * (index - 1)) - layout.props.size = util.vector2(400, 100) return layout end local function onGroupRegistered(groupName) local group = common.groups:get(groupName) local layout = { + type = ui.TYPE.Flex, content = ui.content{}, } local searchHints = { groupName } local count = 0 for _, setting in pairs(group) do count = count + 1 - layout.content:add(renderSetting(groupName, setting, setting.default, count)) + local settingLayout = renderSetting(groupName, setting, setting.default, count) + settingLayout.name = setting.name + layout.content:add(settingLayout) table.insert(searchHints, setting.name) end - layout.props = { - size = util.vector2(400, 100 * count) - } local options = { name = groupName, element = ui.create(layout), From a0c0c39a8cde66a402726f35129eedb79976de50 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 9 Apr 2022 19:58:32 +0200 Subject: [PATCH 04/15] Separate setting keys and names, support localization --- .../scripts/omw/settings/common.lua | 58 ++++++------- .../scripts/omw/settings/player.lua | 1 + .../scripts/omw/settings/register.lua | 19 ++--- .../scripts/omw/settings/render.lua | 85 +++++++++++++------ 4 files changed, 97 insertions(+), 66 deletions(-) diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index f6645b3a11..ec7afcae22 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -52,28 +52,28 @@ local function isGlobalScope(scope) return scope == SCOPE.Global or scope == SCOPE.SaveGlobal end -local function getSetting(groupName, settingName) - local group = groups:get(groupName) +local function getSetting(groupKey, settingKey) + local group = groups:get(groupKey) if not group then error('Unknown group') end - local setting = group[settingName] + local setting = group[settingKey] if not setting then error('Unknown setting') end return setting end -local function getSettingValue(groupName, settingName) - local setting = getSetting(groupName, settingName) +local function getSettingValue(groupKey, settingKey) + local setting = getSetting(groupKey, settingKey) local scopeSection = scopes[setting.scope] if not scopeSection then - error(('Setting %s is not available in this context'):format(setting.name)) + error(('Setting %s is not available in this context'):format(setting.key)) end - if not scopeSection:get(groupName) then - scopeSection:set(groupName, {}) + if not scopeSection:get(groupKey) then + scopeSection:set(groupKey, {}) end - return scopeSection:get(groupName)[setting.name] or setting.default + return scopeSection:get(groupKey)[setting.key] or setting.default end local function notifySettingChange(scope, event) @@ -89,11 +89,11 @@ local function notifySettingChange(scope, event) end end -local function setSettingValue(groupName, settingName, value) - local setting = getSetting(groupName, settingName) +local function setSettingValue(groupKey, settingKey, value) + local setting = getSetting(groupKey, settingKey) local event = { - groupName = groupName, - settingName = setting.name, + groupName = groupKey, + settingName = setting.key, value = value, } if isPlayerScript and isGlobalScope(setting.scope) then @@ -102,43 +102,43 @@ local function setSettingValue(groupName, settingName, value) end local scopeSection = scopes[setting.scope] - if not scopeSection:get(groupName) then - scopeSection:set(groupName, {}) + if not scopeSection:get(groupKey) then + scopeSection:set(groupKey, {}) end - local copy = scopeSection:getCopy(groupName) - copy[setting.name] = value - scopeSection:set(groupName, copy) + local copy = scopeSection:getCopy(groupKey) + copy[setting.key] = value + scopeSection:set(groupKey, copy) notifySettingChange(setting.scope, event) end local groupMeta = { __index = { - get = function(self, settingName) - return getSettingValue(self.name, settingName) + get = function(self, settingKey) + return getSettingValue(self.key, settingKey) end, - set = function(self, settingName, value) - setSettingValue(self.name, settingName, value) + set = function(self, settingKey, value) + setSettingValue(self.key, settingKey, value) end, onChange = function(self, callback) table.insert(self.__callbacks, callback) end, - __changed = function(self, settingName, value) + __changed = function(self, settingKey, value) for _, callback in ipairs(self.__callbacks) do - callback(settingName, value) + callback(settingKey, value) end end, }, } local cachedGroups = {} -local function getGroup(groupName) - if not cachedGroups[groupName] then - cachedGroups[groupName] = setmetatable({ - name = groupName, +local function getGroup(groupKey) + if not cachedGroups[groupKey] then + cachedGroups[groupKey] = setmetatable({ + key = groupKey, __callbacks = {}, }, groupMeta) end - return cachedGroups[groupName] + return cachedGroups[groupKey] end return { diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 42e333a4be..fb9a434e05 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -8,6 +8,7 @@ return { SCOPE = common.SCOPE, getGroup = common.getGroup, registerRenderer = render.registerRenderer, + localizeGroup = render.localizeGroup, }, engineHandlers = { onLoad = function(saved) diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua index 93dcfc18bb..bc8812acf6 100644 --- a/files/builtin_scripts/scripts/omw/settings/register.lua +++ b/files/builtin_scripts/scripts/omw/settings/register.lua @@ -17,14 +17,8 @@ local function validScope(scope) end local function validateSettingOptions(options) - if type(options.name) ~= 'string' then - error('Setting must have a name') - end - if options.default == nil then - error('Setting must have a default value') - end - if type(options.description) ~= 'string' then - error('Setting must have a description') + if type(options.key) ~= 'string' then + error('Setting must have a key') end if not validScope(options.scope) then error(('Invalid setting scope %s'):format(options.scope)) @@ -36,14 +30,13 @@ end local function addSetting(group, options) validateSettingOptions(options) - if group[options.name] then - error(('Duplicate setting name %s'):format(options.name)) + if group[options.key] then + error(('Duplicate setting key %s'):format(options.key)) end - group[options.name] = { - name = options.name, + group[options.key] = { + key = options.key, scope = options.scope or SCOPE.Global, default = options.default, - description = options.description, renderer = options.renderer, } end diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 0f9249a390..17217aab6f 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -9,51 +9,88 @@ local function registerRenderer(name, renderFunction) end local groupOptions = {} +local localization = {} -local function renderSetting(groupName, setting, value, index) +local function renderSetting(groupKey, setting, value, index) local renderFunction = renderers[setting.renderer] if not renderFunction then - error(('Setting %s of %s has unknown renderer %s'):format(setting.name, groupName, setting.renderer)) + error(('Setting %s of %s has unknown renderer %s'):format(setting.key, groupKey, setting.renderer)) end - local layout = renderFunction(setting, value or setting.default, function(value) - local group = common.getGroup(groupName) - group:set(setting.name, value) - local element = groupOptions[groupName].element - local settingLayout = renderSetting(groupName, setting, value, index) - settingLayout.name = setting.name - element.layout.content[setting.name] = settingLayout + local loc = localization[groupKey] and localization[groupKey].settings[setting.key] or { + name = setting.key, + description = '', + } + local layout = renderFunction(loc, value or setting.default, function(value) + local group = common.getGroup(groupKey) + group:set(setting.key, value) + local element = groupOptions[groupKey].element + local settingLayout = renderSetting(groupKey, setting, value, index) + settingLayout.name = setting.key + element.layout.content.settings.content[setting.key] = settingLayout element:update() end) - layout.name = setting.name + layout.name = setting.key return layout end -local function onGroupRegistered(groupName) - local group = common.groups:get(groupName) - local layout = { +local function updateLocalization(groupKey) + local loc = localization[groupKey] + local options = groupOptions[groupKey] + if not options or not loc then return end + local searchHints = { loc.name, loc.description } + options.name = loc.name + options.searchHints = table.concat(searchHints, ' ') + local layout = options.element.layout + layout.content.header.props.text = loc.description +end + +local function onGroupRegistered(groupKey) + local group = common.groups:get(groupKey) + local settingsLayout = { + name = 'settings', type = ui.TYPE.Flex, content = ui.content{}, } - local searchHints = { groupName } local count = 0 for _, setting in pairs(group) do count = count + 1 - local settingLayout = renderSetting(groupName, setting, setting.default, count) - settingLayout.name = setting.name - layout.content:add(settingLayout) - table.insert(searchHints, setting.name) + local settingLayout = renderSetting(groupKey, setting, setting.default, count) + settingLayout.key = setting.key + settingsLayout.content:add(settingLayout) end - local options = { - name = groupName, - element = ui.create(layout), - searchHints = table.concat(searchHints, ' '), + local layout = { + type = ui.TYPE.Flex, + content = ui.content { + { + name = 'header', + type = ui.TYPE.Text, + props = { + text = '', + textSize = 30, + textColor = util.color.rgb(1, 1, 1), + }, + }, + settingsLayout, + }, } - groupOptions[groupName] = options - print(('registering group %s'):format(groupName)) + local options = { + name = groupKey, + element = ui.create(layout), + searchHints = '', + } + groupOptions[groupKey] = options + updateLocalization(groupKey) + print(('registering group %s'):format(groupKey)) ui.registerSettingsPage(options) end +local function localizeGroup(groupKey, loc) + localization[groupKey] = loc + updateLocalization(groupKey) +end + return { onGroupRegistered = onGroupRegistered, registerRenderer = registerRenderer, + localizeGroup = localizeGroup, } \ No newline at end of file From 711f982e1941915f1b99454a03b32f088670f486 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 13 Apr 2022 20:08:33 +0200 Subject: [PATCH 05/15] Simplify renderers, standard setting reset --- .../scripts/omw/settings/player.lua | 18 +++++ .../scripts/omw/settings/register.lua | 1 + .../scripts/omw/settings/render.lua | 80 ++++++++++++------- 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index fb9a434e05..1f55380ccc 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -1,5 +1,23 @@ local common = require('scripts.omw.settings.common') local render = require('scripts.omw.settings.render') +local ui = require('openmw.ui') +local async = require('openmw.async') +local util = require('openmw.util') + +render.registerRenderer('text', function(value, set, arg) + return { + type = ui.TYPE.TextEdit, + props = { + size = util.vector2(arg and arg.size or 300, 100), + text = value, + textColor = util.color.rgb(1, 1, 1), + textSize = 30, + }, + events = { + textChanged = async:callback(function(s) set(s) end), + }, + } +end) local saveScope = common.scopes[common.SCOPE.SavePlayer] return { diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua index bc8812acf6..29f4263fa8 100644 --- a/files/builtin_scripts/scripts/omw/settings/register.lua +++ b/files/builtin_scripts/scripts/omw/settings/register.lua @@ -38,6 +38,7 @@ local function addSetting(group, options) scope = options.scope or SCOPE.Global, default = options.default, renderer = options.renderer, + argument = options.argument, } end diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 17217aab6f..9fc42bb72e 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -1,5 +1,6 @@ local ui = require('openmw.ui') local util = require('openmw.util') +local async = require('openmw.async') local common = require('scripts.omw.settings.common') @@ -11,26 +12,55 @@ end local groupOptions = {} local localization = {} -local function renderSetting(groupKey, setting, value, index) +local function renderSetting(groupKey, setting, value) local renderFunction = renderers[setting.renderer] if not renderFunction then error(('Setting %s of %s has unknown renderer %s'):format(setting.key, groupKey, setting.renderer)) end - local loc = localization[groupKey] and localization[groupKey].settings[setting.key] or { - name = setting.key, - description = '', - } - local layout = renderFunction(loc, value or setting.default, function(value) + local settingName = localization[groupKey] + and localization[groupKey].settings[setting.key].name + or setting.key + local set = function(value) local group = common.getGroup(groupKey) group:set(setting.key, value) - local element = groupOptions[groupKey].element - local settingLayout = renderSetting(groupKey, setting, value, index) - settingLayout.name = setting.key - element.layout.content.settings.content[setting.key] = settingLayout - element:update() - end) - layout.name = setting.key - return layout + renderSetting(groupKey, setting, value) + end + local element = groupOptions[groupKey].element + local settingsLayout = element.layout.content.settings + settingsLayout.content[setting.key] = { + name = setting.key, + type = ui.TYPE.Flex, + props = { + horizontal = true, + align = ui.ALIGNMENT.Start, + arrange = ui.ALIGNMENT.Center, + }, + content = ui.content { + { + type = ui.TYPE.Text, + props = { + text = settingName .. ':', + textColor = util.color.rgb(1, 1, 1), + textSize = 30, + }, + }, + renderFunction(value or setting.default, set, setting.argument), + { + type = ui.TYPE.Text, + props = { + text = 'Reset', + textColor = util.color.rgb(1, 1, 1), + textSize = 30, + }, + events = { + mouseClick = async:callback(function() + set(setting.default) + end), + }, + }, + }, + } + element:update() end local function updateLocalization(groupKey) @@ -46,18 +76,6 @@ end local function onGroupRegistered(groupKey) local group = common.groups:get(groupKey) - local settingsLayout = { - name = 'settings', - type = ui.TYPE.Flex, - content = ui.content{}, - } - local count = 0 - for _, setting in pairs(group) do - count = count + 1 - local settingLayout = renderSetting(groupKey, setting, setting.default, count) - settingLayout.key = setting.key - settingsLayout.content:add(settingLayout) - end local layout = { type = ui.TYPE.Flex, content = ui.content { @@ -70,7 +88,11 @@ local function onGroupRegistered(groupKey) textColor = util.color.rgb(1, 1, 1), }, }, - settingsLayout, + { + name = 'settings', + type = ui.TYPE.Flex, + content = ui.content{}, + }, }, } local options = { @@ -79,6 +101,10 @@ local function onGroupRegistered(groupKey) searchHints = '', } groupOptions[groupKey] = options + for _, setting in pairs(group) do + layout.content.settings.content:add({ name = setting.key }) + renderSetting(groupKey, setting, setting.default) + end updateLocalization(groupKey) print(('registering group %s'):format(groupKey)) ui.registerSettingsPage(options) From 76b16f57dab8d8a0a8331c9dc667c7e9f26e47fc Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 16 Apr 2022 10:41:20 +0200 Subject: [PATCH 06/15] Change settings localization, add group name and description --- .../scripts/omw/settings/common.lua | 2 +- .../scripts/omw/settings/player.lua | 6 +- .../scripts/omw/settings/register.lua | 58 +++++-- .../scripts/omw/settings/render.lua | 163 +++++++++++------- 4 files changed, 152 insertions(+), 77 deletions(-) diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index ec7afcae22..6f6cebb2cf 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -57,7 +57,7 @@ local function getSetting(groupKey, settingKey) if not group then error('Unknown group') end - local setting = group[settingKey] + local setting = group.settings[settingKey] if not setting then error('Unknown setting') end diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 1f55380ccc..b4cf75b127 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -8,10 +8,11 @@ render.registerRenderer('text', function(value, set, arg) return { type = ui.TYPE.TextEdit, props = { - size = util.vector2(arg and arg.size or 300, 100), + size = util.vector2(arg and arg.size or 300, 30), text = value, textColor = util.color.rgb(1, 1, 1), - textSize = 30, + textSize = 15, + textAlignV = ui.ALIGNMENT.Center, }, events = { textChanged = async:callback(function(s) set(s) end), @@ -26,7 +27,6 @@ return { SCOPE = common.SCOPE, getGroup = common.getGroup, registerRenderer = render.registerRenderer, - localizeGroup = render.localizeGroup, }, engineHandlers = { onLoad = function(saved) diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua index 29f4263fa8..ca41843e00 100644 --- a/files/builtin_scripts/scripts/omw/settings/register.lua +++ b/files/builtin_scripts/scripts/omw/settings/register.lua @@ -26,34 +26,70 @@ local function validateSettingOptions(options) 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 addSetting(group, options) +local function addSetting(settings, options) validateSettingOptions(options) - if group[options.key] then + if settings[options.key] then error(('Duplicate setting key %s'):format(options.key)) end - group[options.key] = { + settings[options.key] = { key = options.key, scope = options.scope or SCOPE.Global, default = options.default, renderer = options.renderer, argument = options.argument, + + name = options.name, + description = options.description, } end -local function registerGroup(groupName, list) - if groups:get(groupName) then - print(('Overwriting group %s'):format(groupName)) +local function validateGroupOptions(options) + if type(options.key) ~= 'string' then + error('Group must have a key') end - local settings = {} - for _, opt in ipairs(list) do - addSetting(settings, opt) + if type(options.localization) ~= 'string' then + error('Group must have a localization context') end - groups:set(groupName, settings) + 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 +end + +local function registerGroup(options) + validateGroupOptions(options) + if groups:get(options.key) then + print(('Overwriting group %s'):format(options.key)) + end + local group = { + key = options.key, + localization = options.localization, + + name = options.name, + description = options.description, + + settings = {}, + } + for _, opt in ipairs(options.settings) do + addSetting(group.settings, opt) + end + groups:set(options.key, group) for _, a in ipairs(world.activeActors) do if a.type == types.Player and a:isValid() then - a:sendEvent(common.EVENTS.GroupRegistered, groupName) + a:sendEvent(common.EVENTS.GroupRegistered, options.key) end end end diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 9fc42bb72e..57368a6a15 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -1,6 +1,7 @@ local ui = require('openmw.ui') local util = require('openmw.util') local async = require('openmw.async') +local core = require('openmw.core') local common = require('scripts.omw.settings.common') @@ -10,84 +11,122 @@ local function registerRenderer(name, renderFunction) end local groupOptions = {} -local localization = {} + + +local padding = function(size) + return { + props = { + size = util.vector2(size, size), + } + } +end + +local header = { + props = { + textColor = util.color.rgb(1, 1, 1), + textSize = 30, + }, +} + +local normal = { + props = { + textColor = util.color.rgb(1, 1, 1), + textSize = 25, + }, +} local function renderSetting(groupKey, setting, value) local renderFunction = renderers[setting.renderer] if not renderFunction then error(('Setting %s of %s has unknown renderer %s'):format(setting.key, groupKey, setting.renderer)) end - local settingName = localization[groupKey] - and localization[groupKey].settings[setting.key].name - or setting.key + local group = common.getGroup(groupKey) + value = value or group:get(setting.key) local set = function(value) - local group = common.getGroup(groupKey) group:set(setting.key, value) renderSetting(groupKey, setting, value) end local element = groupOptions[groupKey].element + local localization = groupOptions[groupKey].localization local settingsLayout = element.layout.content.settings settingsLayout.content[setting.key] = { name = setting.key, type = ui.TYPE.Flex, - props = { - horizontal = true, - align = ui.ALIGNMENT.Start, - arrange = ui.ALIGNMENT.Center, - }, content = ui.content { { - type = ui.TYPE.Text, + type = ui.TYPE.Flex, props = { - text = settingName .. ':', - textColor = util.color.rgb(1, 1, 1), - textSize = 30, - }, - }, - renderFunction(value or setting.default, set, setting.argument), - { - type = ui.TYPE.Text, - props = { - text = 'Reset', - textColor = util.color.rgb(1, 1, 1), - textSize = 30, - }, - events = { - mouseClick = async:callback(function() - set(setting.default) - end), + horizontal = true, + align = ui.ALIGNMENT.Start, + arrange = ui.ALIGNMENT.End, + }, + content = ui.content { + { + type = ui.TYPE.Text, + template = normal, + props = { + text = localization(setting.name), + }, + }, + padding(10), + renderFunction(value, set, setting.argument), + padding(10), + { + type = ui.TYPE.Text, + template = normal, + props = { + text = 'Reset', + }, + events = { + mouseClick = async:callback(function() + set(setting.default) + end), + }, + }, }, }, + padding(20), }, } element:update() end -local function updateLocalization(groupKey) - local loc = localization[groupKey] - local options = groupOptions[groupKey] - if not options or not loc then return end - local searchHints = { loc.name, loc.description } - options.name = loc.name - options.searchHints = table.concat(searchHints, ' ') - local layout = options.element.layout - layout.content.header.props.text = loc.description -end - -local function onGroupRegistered(groupKey) +local function renderGroup(groupKey) local group = common.groups:get(groupKey) - local layout = { + local element = groupOptions[groupKey].element + local localization = groupOptions[groupKey].localization + element.layout = { type = ui.TYPE.Flex, content = ui.content { + padding(10), { - name = 'header', - type = ui.TYPE.Text, + type = ui.TYPE.Flex, props = { - text = '', - textSize = 30, - textColor = util.color.rgb(1, 1, 1), + horizontal = true, + align = ui.ALIGNMENT.Start, + arrange = ui.ALIGNMENT.Center, + }, + content = ui.content { + { + name = 'name', + type = ui.TYPE.Text, + template = header, + props = { + text = localization(group.name), + }, + }, + padding(10), + { + name = 'description', + type = ui.TYPE.Text, + template = normal, + props = { + text = localization(group.description), + }, + }, }, }, + padding(50), { name = 'settings', type = ui.TYPE.Flex, @@ -95,28 +134,28 @@ local function onGroupRegistered(groupKey) }, }, } - local options = { - name = groupKey, - element = ui.create(layout), - searchHints = '', - } - groupOptions[groupKey] = options - for _, setting in pairs(group) do - layout.content.settings.content:add({ name = setting.key }) - renderSetting(groupKey, setting, setting.default) + local settingsContent = element.layout.content.settings.content + for _, setting in pairs(group.settings) do + settingsContent:add({ name = setting.key }) + renderSetting(groupKey, setting) end - updateLocalization(groupKey) - print(('registering group %s'):format(groupKey)) - ui.registerSettingsPage(options) + element:update() end -local function localizeGroup(groupKey, loc) - localization[groupKey] = loc - updateLocalization(groupKey) +local function onGroupRegistered(groupKey) + local group = common.groups:get(groupKey) + local options = { + name = groupKey, + element = ui.create{}, + searchHints = '', + localization = core.l10n(group.localization), + } + groupOptions[groupKey] = options + renderGroup(groupKey) + ui.registerSettingsPage(options) end return { onGroupRegistered = onGroupRegistered, registerRenderer = registerRenderer, - localizeGroup = localizeGroup, } \ No newline at end of file From 38e0f5c0af4587faa918a19a85d8a60cba235366 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 16 Apr 2022 11:29:37 +0200 Subject: [PATCH 07/15] Fix a docs typo --- docs/source/reference/lua-scripting/widgets/flex.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/lua-scripting/widgets/flex.rst b/docs/source/reference/lua-scripting/widgets/flex.rst index 359d0d4394..2648e88996 100644 --- a/docs/source/reference/lua-scripting/widgets/flex.rst +++ b/docs/source/reference/lua-scripting/widgets/flex.rst @@ -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 From a35bc1dee0663a05ab5763beaa16ae282f59f0bc Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 18 Apr 2022 08:42:02 +0200 Subject: [PATCH 08/15] openmw_aux.settings, rework to support local scripts --- files/builtin_scripts/openmw_aux/settings.lua | 4 + .../scripts/omw/settings/common.lua | 257 ++++++++++-------- .../scripts/omw/settings/global.lua | 34 ++- .../scripts/omw/settings/interface.lua | 51 ---- .../scripts/omw/settings/player.lua | 17 +- .../scripts/omw/settings/register.lua | 14 +- .../scripts/omw/settings/render.lua | 22 +- 7 files changed, 204 insertions(+), 195 deletions(-) create mode 100644 files/builtin_scripts/openmw_aux/settings.lua delete mode 100644 files/builtin_scripts/scripts/omw/settings/interface.lua diff --git a/files/builtin_scripts/openmw_aux/settings.lua b/files/builtin_scripts/openmw_aux/settings.lua new file mode 100644 index 0000000000..8ce9929eaa --- /dev/null +++ b/files/builtin_scripts/openmw_aux/settings.lua @@ -0,0 +1,4 @@ +local common = require('scripts.omw.settings.common') +return { + group = common.group, +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index 6f6cebb2cf..bc01bc20b5 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -1,23 +1,20 @@ -local prequire = function(path) - local status, result = pcall(function() - return require(path) - end) - return status and result or nil -end - +local storage = require('openmw.storage') local core = require('openmw.core') local types = require('openmw.types') -local storage = require('openmw.storage') -local self = prequire('openmw.self') -local world = prequire('openmw.world') - -local isPlayerScript = self and true or false -local isGlobalScript = world and true or false +local selfObject +do + local success, result = pcall(function() return require('openmw.self') end) + selfObject = success and result or nil +end +local playerObject = selfObject and selfObject.type == types.Player and selfObject or nil +local eventPrefix = 'omwSettings' local EVENTS = { - SettingChanged = 'omwSettingsChanged', - SettingSet = 'omwSettingsGlobalSet', - GroupRegistered = 'omwSettingsGroupRegistered', + SettingChanged = eventPrefix .. 'Changed', + SetValue = eventPrefix .. 'GlobalSetValue', + GroupRegistered = eventPrefix .. 'GroupRegistered', + RegisterGroup = eventPrefix .. 'RegisterGroup', + Subscribe = eventPrefix .. 'Subscribe', } local SCOPE = { @@ -27,124 +24,160 @@ local SCOPE = { SavePlayer = 'SavePlayer', } -local groups = storage.globalSection('OMW_Settings_Groups') -local saveGlobalSection = storage.globalSection('OMW_Settings_SaveGlobal') - -if isGlobalScript then - groups:removeOnExit() - saveGlobalSection:removeOnExit() +local function isPlayerScope(scope) + return scope == SCOPE.Player or scope == SCOPE.SavePlayer end -local savePlayerSection = nil -if isPlayerScript then - savePlayerSection = storage.playerSection('OMW_Setting_SavePlayer') - savePlayerSection:removeOnExit() +local function isSaveScope(scope) + return scope == SCOPE.SaveGlobal or scope == SCOPE.SavePlayer end -local scopes = { - [SCOPE.Global] = storage.globalSection('OMW_Setting_Global'), - [SCOPE.Player] = isPlayerScript and storage.playerSection('OMW_Setting_Player'), - [SCOPE.SaveGlobal] = saveGlobalSection, - [SCOPE.SavePlayer] = savePlayerSection, -} +local prefix = 'omw_settings_' +local settingsPattern = prefix .. 'settings_%s%s' -local function isGlobalScope(scope) - return scope == SCOPE.Global or scope == SCOPE.SaveGlobal +local groupsSection = storage.globalSection(prefix .. 'groups') +if groupsSection.removeOnExit then + groupsSection:removeOnExit() end -local function getSetting(groupKey, settingKey) - local group = groups:get(groupKey) - if not group then - error('Unknown group') - end - local setting = group.settings[settingKey] - if not setting then - error('Unknown setting') - end - return setting -end - -local function getSettingValue(groupKey, settingKey) - local setting = getSetting(groupKey, settingKey) - local scopeSection = scopes[setting.scope] - if not scopeSection then - error(('Setting %s is not available in this context'):format(setting.key)) - end - if not scopeSection:get(groupKey) then - scopeSection:set(groupKey, {}) - end - return scopeSection:get(groupKey)[setting.key] or setting.default -end - -local function notifySettingChange(scope, event) - if isGlobalScope(scope) then - core.sendGlobalEvent(EVENTS.SettingChanged, event) - for _, a in ipairs(world.activeActors) do - if a.type == types.Player then - a:sendEvent(EVENTS.SettingChanged, event) - end - end +local function values(groupKey, scope) + local player = isPlayerScope(scope) + local save = isSaveScope(scope) + local sectionKey = settingsPattern:format(groupKey, save and '_save' or '') + local section + if player then + section = storage.playerSection and storage.playerSection(sectionKey) or nil else - self:sendEvent(EVENTS.SettingChanged, event) + section = storage.globalSection(sectionKey) + end + if save and section and section.removeOnExit then + section:removeOnExit() + end + return section +end + +local function saveScope(scope) + local saved = {} + for _, group in pairs(groupsSection:asTable()) do + saved[group.key] = values(group.key, scope):asTable() + end + return saved +end + +local function loadScope(scope, saved) + if not saved then return end + for _, group in pairs(saved) do + values(group.key, scope):reset(saved[group.key]) end end -local function setSettingValue(groupKey, settingKey, value) - local setting = getSetting(groupKey, settingKey) +local function groupSubscribeEvent(groupKey) + return ('%sSubscribe%s'):format(eventPrefix, groupKey) +end + +local subscriptions = {} +local function handleSubscription(event) + if not subscriptions[event.groupKey] then + subscriptions[event.groupKey] = {} + end + table.insert(subscriptions[event.groupKey], event.object or false) +end + +local function subscribe(self) + local groupKey = rawget(self, 'groupKey') local event = { - groupName = groupKey, - settingName = setting.key, - value = value, + groupKey = groupKey, + object = selfObject, } - if isPlayerScript and isGlobalScope(setting.scope) then - core.sendGlobalEvent(EVENTS.SettingSet, event) - return + core.sendGlobalEvent(EVENTS.Subscribe, event) + if playerObject then + playerObject:sendEvent(EVENTS.Subscribe, event) end - - local scopeSection = scopes[setting.scope] - if not scopeSection:get(groupKey) then - scopeSection:set(groupKey, {}) - end - local copy = scopeSection:getCopy(groupKey) - copy[setting.key] = value - scopeSection:set(groupKey, copy) - - notifySettingChange(setting.scope, event) + return groupSubscribeEvent(groupKey) end local groupMeta = { - __index = { - get = function(self, settingKey) - return getSettingValue(self.key, settingKey) - end, - set = function(self, settingKey, value) - setSettingValue(self.key, settingKey, value) - end, - onChange = function(self, callback) - table.insert(self.__callbacks, callback) - end, - __changed = function(self, settingKey, value) - for _, callback in ipairs(self.__callbacks) do - callback(settingKey, value) + __newindex = function(self, settingKey, value) + local group = groupsSection:get(rawget(self, 'groupKey')) + local setting = group.settings[settingKey] + if not setting then + error(('Setting %s does not exist'):format(settingKey)) + end + local section = values(group.key, setting.scope) + local event = { + groupKey = group.key, + settingKey = settingKey, + value = value, + } + if section.set then + section:set(settingKey, value) + if playerObject then + playerObject:sendEvent(EVENTS.SettingChanged, event) + else + core.sendGlobalEvent(EVENTS.SettingChanged, event) end - end, - }, + if subscriptions[group.key] then + local eventKey = groupSubscribeEvent(group.key) + for _, object in ipairs(subscriptions[group.key]) do + if object then + object:sendEvent(eventKey, event) + else + core.sendGlobalEvent(eventKey, event) + end + end + end + else + if isPlayerScope(setting.scope) then + error(("Can't change player scope setting %s from global scope"):format(settingKey)) + else + core.sendGlobalEvent(EVENTS.SetValue, event) + end + end + end, + __index = function(self, key) + if key == "subscribe" then return subscribe end + local settingKey = key + local group = groupsSection:get(rawget(self, 'groupKey')) + local setting = group.settings[settingKey] + if not setting then + error(('Unknown setting %s'):format(settingKey)) + end + local section = rawget(self, 'sections')[setting.scope] + if not section then + error(("Can't access setting %s from scope %s"):format(settingKey, setting.scope)) + end + return section:get(setting.key) or setting.default + end, } -local cachedGroups = {} -local function getGroup(groupKey) - if not cachedGroups[groupKey] then - cachedGroups[groupKey] = setmetatable({ - key = groupKey, - __callbacks = {}, - }, groupMeta) + +local function group(groupKey) + if not groupsSection:get(groupKey) then + print(("Settings group %s wasn't registered yet"):format(groupKey)) end - return cachedGroups[groupKey] + local s = {} + for _, scope in pairs(SCOPE) do + local section = values(groupKey, scope) + if section then + s[scope] = section + end + end + return setmetatable({ + groupKey = groupKey, + sections = s, + }, groupMeta) end return { - EVENTS = EVENTS, SCOPE = SCOPE, - scopes = scopes, - groups = groups, - getGroup = getGroup, + EVENTS = EVENTS, + isPlayerScope = isPlayerScope, + isSaveScope = isSaveScope, + values = values, + groups = function() + return groupsSection + end, + saveScope = saveScope, + loadScope = loadScope, + group = group, + handleSubscription = handleSubscription, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/global.lua b/files/builtin_scripts/scripts/omw/settings/global.lua index 148d21847a..ec14a07f73 100644 --- a/files/builtin_scripts/scripts/omw/settings/global.lua +++ b/files/builtin_scripts/scripts/omw/settings/global.lua @@ -1,30 +1,44 @@ local common = require('scripts.omw.settings.common') local register = require('scripts.omw.settings.register') +local world = require('openmw.world') +local types = require('openmw.types') -local saveScope = common.scopes[common.SCOPE.SaveGlobal] return { interfaceName = 'Settings', interface = { SCOPE = common.SCOPE, - getGroup = common.getGroup, + group = common.group, registerGroup = register.registerGroup, }, engineHandlers = { onLoad = function(saved) - common.groups:reset() - saveScope:reset(saved) + common.groups():reset() + common.loadScope(common.SCOPE.SaveGlobal, saved) end, onSave = function() - return saveScope:asTable() + common.saveScope(common.SCOPE.SaveGlobal) end, onPlayerAdded = register.onPlayerAdded, }, eventHandlers = { - [common.EVENTS.SettingChanged] = function(e) - common.getGroup(e.groupName):__changed(e.settingName, e.value) + [common.EVENTS.SetValue] = function(event) + local group = common.group(event.groupKey) + group[event.settingKey] = event.value end, - [common.EVENTS.SettingSet] = function(e) - common.getGroup(e.groupName):set(e.settingName, e.value) + [common.EVENTS.RegisterGroup] = function(options) + if common.groups():get(options.key) then return end + register.registerGroup(options) end, - } + [common.EVENTS.SettingChanged] = function(event) + local setting = common.groups():get(event.groupKey).settings[event.settingKey] + if common.isPlayerScope(setting.scope) then + for _, a in ipairs(world.activeActors) do + if a.type == types.Player and a:isValid() then + a:sendEvent(common.EVENTS.SettingChanged, event) + end + end + end + end, + [common.EVENTS.Subscribe] = common.handleSubscription, + }, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/interface.lua b/files/builtin_scripts/scripts/omw/settings/interface.lua deleted file mode 100644 index 4fb60d2c4d..0000000000 --- a/files/builtin_scripts/scripts/omw/settings/interface.lua +++ /dev/null @@ -1,51 +0,0 @@ -return function(player) - local core = require('openmw.core') - local types = require('openmw.types') - local world = not player and require('openmw.world') - - - local sections = require('scripts.omw.settings.sections') - local render = player and require('scripts.omw.settings.render') or nil - - local settingChangeEvent = 'omwSettingChanged' - local globalSetEvent = 'omwSettingGlobalSet' - local registerEvent = 'omwSettingGroupRegistered' - - local groups, scopes, SCOPE, isGlobal = sections.groups, sections.scopes, sections.SCOPE, sections.isGlobal - - - - local saveScope = scopes[player and SCOPE.SavePlayer or SCOPE.SaveGlobal] - return { - interfaceName = 'Settings', - interface = { - getGroup = getGroup, - SCOPE = SCOPE, - registerGroup = not player and require('scripts.omw.settings.register') or nil, - registerType = player and render.registerType or nil, - }, - engineHandlers = { - onLoad = function(saved) - if not player then groups:reset() end - saveScope:reset(saved) - end, - onSave = function() - return saveScope:asTable() - end, - }, - eventHandlers = { - [settingChangeEvent] = function(e) - getGroup(e.groupName):__changed(e.settingName, e.value) - end, - [globalSetEvent] = not player and function(e) - local setting = getSetting(e.groupName, e.settingName) - if isGlobal(setting.scope) then - setSettingValue(e.groupName, e.settingName, e.value) - else - error(('Unexpected Setting event for a non-global setting %s'):format(e.settingName)) - end - end or nil, - [registerEvent] = player and render.onGroupRegistered or nil, - } - } -end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index b4cf75b127..39d989d5cc 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -1,8 +1,10 @@ local common = require('scripts.omw.settings.common') local render = require('scripts.omw.settings.render') + local ui = require('openmw.ui') local async = require('openmw.async') local util = require('openmw.util') +local core = require('openmw.core') render.registerRenderer('text', function(value, set, arg) return { @@ -20,26 +22,27 @@ render.registerRenderer('text', function(value, set, arg) } end) -local saveScope = common.scopes[common.SCOPE.SavePlayer] return { interfaceName = 'Settings', interface = { SCOPE = common.SCOPE, - getGroup = common.getGroup, + group = common.group, registerRenderer = render.registerRenderer, + registerGroup = function(options) + core.sendGlobalEvent(common.EVENTS.RegisterGroup, options) + end, }, engineHandlers = { onLoad = function(saved) - saveScope:reset(saved) + common.loadScope(common.SCOPE.SavePlayer, saved) end, onSave = function() - return saveScope:asTable() + common.saveScope(common.SCOPE.SavePlayer) end, }, eventHandlers = { - [common.EVENTS.SettingChanged] = function(e) - common.getGroup(e.groupName):__changed(e.settingName, e.value) - end, [common.EVENTS.GroupRegistered] = render.onGroupRegistered, + [common.EVENTS.SettingChanged] = render.onSettingChanged, + [common.EVENTS.Subscribe] = common.handleSubscription, } } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua index ca41843e00..02a85cdeca 100644 --- a/files/builtin_scripts/scripts/omw/settings/register.lua +++ b/files/builtin_scripts/scripts/omw/settings/register.lua @@ -3,11 +3,9 @@ local types = require('openmw.types') local common = require('scripts.omw.settings.common') -local groups, SCOPE = common.groups, common.SCOPE - local function validScope(scope) local valid = false - for _, v in pairs(SCOPE) do + for _, v in pairs(common.SCOPE) do if v == scope then valid = true break @@ -41,7 +39,7 @@ local function addSetting(settings, options) end settings[options.key] = { key = options.key, - scope = options.scope or SCOPE.Global, + scope = options.scope, default = options.default, renderer = options.renderer, argument = options.argument, @@ -70,14 +68,14 @@ local function validateGroupOptions(options) end local function registerGroup(options) + local groups = common.groups() validateGroupOptions(options) if groups:get(options.key) then - print(('Overwriting group %s'):format(options.key)) - end + error(('Duplicate group %s'):format(options.key)) + end local group = { key = options.key, localization = options.localization, - name = options.name, description = options.description, @@ -95,7 +93,7 @@ local function registerGroup(options) end local function onPlayerAdded(player) - for groupName in pairs(groups:asTable()) do + for groupName in pairs(common.groups():asTable()) do player:sendEvent(common.EVENTS.GroupRegistered, groupName) end end diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 57368a6a15..bbbb8bfc6d 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -40,10 +40,10 @@ local function renderSetting(groupKey, setting, value) if not renderFunction then error(('Setting %s of %s has unknown renderer %s'):format(setting.key, groupKey, setting.renderer)) end - local group = common.getGroup(groupKey) - value = value or group:get(setting.key) + local group = common.group(groupKey) + value = value or group[setting.key] local set = function(value) - group:set(setting.key, value) + group[setting.key] = value renderSetting(groupKey, setting, value) end local element = groupOptions[groupKey].element @@ -92,7 +92,7 @@ local function renderSetting(groupKey, setting, value) end local function renderGroup(groupKey) - local group = common.groups:get(groupKey) + local group = common.groups():get(groupKey) local element = groupOptions[groupKey].element local localization = groupOptions[groupKey].localization element.layout = { @@ -143,19 +143,27 @@ local function renderGroup(groupKey) end local function onGroupRegistered(groupKey) - local group = common.groups:get(groupKey) + local group = common.groups():get(groupKey) + local loc = core.l10n(group.localization) local options = { - name = groupKey, + name = loc(group.name), element = ui.create{}, searchHints = '', - localization = core.l10n(group.localization), + localization = loc, } groupOptions[groupKey] = options renderGroup(groupKey) ui.registerSettingsPage(options) end +local function onSettingChanged(event) + local group = common.groups():get(event.groupKey) + local setting = group.settings[event.settingKey] + renderSetting(event.groupKey, setting, event.value) +end + return { onGroupRegistered = onGroupRegistered, + onSettingChanged = onSettingChanged, registerRenderer = registerRenderer, } \ No newline at end of file From b899320e9fa9b330877ac6b865a497ca8f75df2d Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 27 Apr 2022 21:28:16 +0200 Subject: [PATCH 09/15] Use storage subscribe, unify groups into pages --- .../scripts/omw/settings/common.lua | 263 +++++++----------- .../scripts/omw/settings/global.lua | 39 +-- .../scripts/omw/settings/player.lua | 29 +- .../scripts/omw/settings/register.lua | 104 ------- .../scripts/omw/settings/render.lua | 215 ++++++++++---- 5 files changed, 280 insertions(+), 370 deletions(-) delete mode 100644 files/builtin_scripts/scripts/omw/settings/register.lua diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index bc01bc20b5..3ea462daf9 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -1,183 +1,126 @@ local storage = require('openmw.storage') -local core = require('openmw.core') -local types = require('openmw.types') -local selfObject -do - local success, result = pcall(function() return require('openmw.self') end) - selfObject = success and result or nil -end -local playerObject = selfObject and selfObject.type == types.Player and selfObject or nil -local eventPrefix = 'omwSettings' -local EVENTS = { - SettingChanged = eventPrefix .. 'Changed', - SetValue = eventPrefix .. 'GlobalSetValue', - GroupRegistered = eventPrefix .. 'GroupRegistered', - RegisterGroup = eventPrefix .. 'RegisterGroup', - Subscribe = eventPrefix .. 'Subscribe', -} +local contextSection = storage.playerSection or storage.globalSection +local groupSectionKey = 'OmwSettingGroups' +local groupSection = contextSection(groupSectionKey) +groupSection:removeOnExit() -local SCOPE = { - Global = 'Global', - Player = 'Player', - SaveGlobal = 'SaveGlobal', - SavePlayer = 'SavePlayer', -} - -local function isPlayerScope(scope) - return scope == SCOPE.Player or scope == SCOPE.SavePlayer -end - -local function isSaveScope(scope) - return scope == SCOPE.SaveGlobal or scope == SCOPE.SavePlayer -end - -local prefix = 'omw_settings_' -local settingsPattern = prefix .. 'settings_%s%s' - -local groupsSection = storage.globalSection(prefix .. 'groups') -if groupsSection.removeOnExit then - groupsSection:removeOnExit() -end - -local function values(groupKey, scope) - local player = isPlayerScope(scope) - local save = isSaveScope(scope) - local sectionKey = settingsPattern:format(groupKey, save and '_save' or '') - local section - if player then - section = storage.playerSection and storage.playerSection(sectionKey) or nil - else - section = storage.globalSection(sectionKey) +local function validateSettingOptions(options) + if type(options.key) ~= 'string' then + error('Setting must have a key') end - if save and section and section.removeOnExit then - section:removeOnExit() + if type(options.saveOnly) ~= 'boolean' then + error('Setting must be save only or not') end - return section -end - -local function saveScope(scope) - local saved = {} - for _, group in pairs(groupsSection:asTable()) do - saved[group.key] = values(group.key, scope):asTable() + if type(options.renderer) ~= 'string' then + error('Setting must have a renderer') end - return saved -end - -local function loadScope(scope, saved) - if not saved then return end - for _, group in pairs(saved) do - values(group.key, scope):reset(saved[group.key]) + 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 groupSubscribeEvent(groupKey) - return ('%sSubscribe%s'):format(eventPrefix, groupKey) -end - -local subscriptions = {} -local function handleSubscription(event) - if not subscriptions[event.groupKey] then - subscriptions[event.groupKey] = {} +local function validateGroupOptions(options) + 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.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 - table.insert(subscriptions[event.groupKey], event.object or false) end -local function subscribe(self) - local groupKey = rawget(self, 'groupKey') - local event = { - groupKey = groupKey, - object = selfObject, +local function registerSetting(options) + return { + key = options.key, + saveOnly = options.saveOnly, + default = options.default, + renderer = options.renderer, + argument = options.argument, + + name = options.name, + description = options.description, } - core.sendGlobalEvent(EVENTS.Subscribe, event) - if playerObject then - playerObject:sendEvent(EVENTS.Subscribe, event) - end - return groupSubscribeEvent(groupKey) end -local groupMeta = { - __newindex = function(self, settingKey, value) - local group = groupsSection:get(rawget(self, 'groupKey')) - local setting = group.settings[settingKey] - if not setting then - error(('Setting %s does not exist'):format(settingKey)) - end - local section = values(group.key, setting.scope) - local event = { - groupKey = group.key, - settingKey = settingKey, - value = value, - } - if section.set then - section:set(settingKey, value) - if playerObject then - playerObject:sendEvent(EVENTS.SettingChanged, event) - else - core.sendGlobalEvent(EVENTS.SettingChanged, event) - end - if subscriptions[group.key] then - local eventKey = groupSubscribeEvent(group.key) - for _, object in ipairs(subscriptions[group.key]) do - if object then - object:sendEvent(eventKey, event) - else - core.sendGlobalEvent(eventKey, event) - end - end - end - else - if isPlayerScope(setting.scope) then - error(("Can't change player scope setting %s from global scope"):format(settingKey)) - else - core.sendGlobalEvent(EVENTS.SetValue, event) - end - end - end, - __index = function(self, key) - if key == "subscribe" then return subscribe end - local settingKey = key - local group = groupsSection:get(rawget(self, 'groupKey')) - local setting = group.settings[settingKey] - if not setting then - error(('Unknown setting %s'):format(settingKey)) - end - local section = rawget(self, 'sections')[setting.scope] - if not section then - error(("Can't access setting %s from scope %s"):format(settingKey, setting.scope)) - end - return section:get(setting.key) or setting.default - 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, + l10n = options.l10n, + name = options.name, + description = options.description, -local function group(groupKey) - if not groupsSection:get(groupKey) then - print(("Settings group %s wasn't registered yet"):format(groupKey)) - end - local s = {} - for _, scope in pairs(SCOPE) do - local section = values(groupKey, scope) - if section then - s[scope] = section + 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 - return setmetatable({ - groupKey = groupKey, - sections = s, - }, groupMeta) + groupSection:set(group.key, group) end return { - SCOPE = SCOPE, - EVENTS = EVENTS, - isPlayerScope = isPlayerScope, - isSaveScope = isSaveScope, - values = values, - groups = function() - return groupsSection + getSection = function(global, key) + return (global and storage.globalSection or storage.playerSection)(key) end, - saveScope = saveScope, - loadScope = loadScope, - group = group, - handleSubscription = handleSubscription, + 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 group.settings[key].saveOnly then + saved[groupKey][key] = value + end + end + end + groupSection:reset() + return saved + end, + registerGroup = registerGroup, + validateGroupOptions = validateGroupOptions, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/global.lua b/files/builtin_scripts/scripts/omw/settings/global.lua index ec14a07f73..5818668b0e 100644 --- a/files/builtin_scripts/scripts/omw/settings/global.lua +++ b/files/builtin_scripts/scripts/omw/settings/global.lua @@ -1,44 +1,19 @@ +local storage = require('openmw.storage') + local common = require('scripts.omw.settings.common') -local register = require('scripts.omw.settings.register') -local world = require('openmw.world') -local types = require('openmw.types') return { interfaceName = 'Settings', interface = { - SCOPE = common.SCOPE, - group = common.group, - registerGroup = register.registerGroup, + registerGroup = common.registerGroup, }, engineHandlers = { - onLoad = function(saved) - common.groups():reset() - common.loadScope(common.SCOPE.SaveGlobal, saved) - end, - onSave = function() - common.saveScope(common.SCOPE.SaveGlobal) - end, - onPlayerAdded = register.onPlayerAdded, + onLoad = common.onLoad, + onSave = common.onSave, }, eventHandlers = { - [common.EVENTS.SetValue] = function(event) - local group = common.group(event.groupKey) - group[event.settingKey] = event.value + [common.setGlobalEvent] = function(e) + storage.globalSection(e.groupKey):set(e.settingKey, e.value) end, - [common.EVENTS.RegisterGroup] = function(options) - if common.groups():get(options.key) then return end - register.registerGroup(options) - end, - [common.EVENTS.SettingChanged] = function(event) - local setting = common.groups():get(event.groupKey).settings[event.settingKey] - if common.isPlayerScope(setting.scope) then - for _, a in ipairs(world.activeActors) do - if a.type == types.Player and a:isValid() then - a:sendEvent(common.EVENTS.SettingChanged, event) - end - end - end - end, - [common.EVENTS.Subscribe] = common.handleSubscription, }, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 39d989d5cc..14d5b12033 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -1,16 +1,15 @@ -local common = require('scripts.omw.settings.common') -local render = require('scripts.omw.settings.render') - local ui = require('openmw.ui') local async = require('openmw.async') local util = require('openmw.util') -local core = require('openmw.core') + +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 300, 30), + size = util.vector2(arg and arg.size or 150, 30), text = value, textColor = util.color.rgb(1, 1, 1), textSize = 15, @@ -25,24 +24,12 @@ end) return { interfaceName = 'Settings', interface = { - SCOPE = common.SCOPE, - group = common.group, + registerPage = render.registerPage, registerRenderer = render.registerRenderer, - registerGroup = function(options) - core.sendGlobalEvent(common.EVENTS.RegisterGroup, options) - end, + registerGroup = common.registerGroup, }, engineHandlers = { - onLoad = function(saved) - common.loadScope(common.SCOPE.SavePlayer, saved) - end, - onSave = function() - common.saveScope(common.SCOPE.SavePlayer) - end, + onLoad = common.onLoad, + onSave = common.onSave, }, - eventHandlers = { - [common.EVENTS.GroupRegistered] = render.onGroupRegistered, - [common.EVENTS.SettingChanged] = render.onSettingChanged, - [common.EVENTS.Subscribe] = common.handleSubscription, - } } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua deleted file mode 100644 index 02a85cdeca..0000000000 --- a/files/builtin_scripts/scripts/omw/settings/register.lua +++ /dev/null @@ -1,104 +0,0 @@ -local world = require('openmw.world') -local types = require('openmw.types') - -local common = require('scripts.omw.settings.common') - -local function validScope(scope) - local valid = false - for _, v in pairs(common.SCOPE) do - if v == scope then - valid = true - break - end - end - return valid -end - -local function validateSettingOptions(options) - if type(options.key) ~= 'string' then - error('Setting must have a key') - end - if not validScope(options.scope) then - error(('Invalid setting scope %s'):format(options.scope)) - 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 addSetting(settings, options) - validateSettingOptions(options) - if settings[options.key] then - error(('Duplicate setting key %s'):format(options.key)) - end - settings[options.key] = { - key = options.key, - scope = options.scope, - default = options.default, - renderer = options.renderer, - argument = options.argument, - - name = options.name, - description = options.description, - } -end - -local function validateGroupOptions(options) - if type(options.key) ~= 'string' then - error('Group must have a key') - end - if type(options.localization) ~= '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 -end - -local function registerGroup(options) - local groups = common.groups() - validateGroupOptions(options) - if groups:get(options.key) then - error(('Duplicate group %s'):format(options.key)) - end - local group = { - key = options.key, - localization = options.localization, - name = options.name, - description = options.description, - - settings = {}, - } - for _, opt in ipairs(options.settings) do - addSetting(group.settings, opt) - end - groups:set(options.key, group) - for _, a in ipairs(world.activeActors) do - if a.type == types.Player and a:isValid() then - a:sendEvent(common.EVENTS.GroupRegistered, options.key) - end - end -end - -local function onPlayerAdded(player) - for groupName in pairs(common.groups():asTable()) do - player:sendEvent(common.EVENTS.GroupRegistered, groupName) - end -end - -return { - registerGroup = registerGroup, - onPlayerAdded = onPlayerAdded, -} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index bbbb8bfc6d..0d06214600 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -2,6 +2,7 @@ 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') @@ -10,8 +11,8 @@ local function registerRenderer(name, renderFunction) renderers[name] = renderFunction end -local groupOptions = {} - +local groups = {} +local pageOptions = {} local padding = function(size) return { @@ -20,36 +21,46 @@ local padding = function(size) } } end +local smallPadding = padding(10) +local bigPadding = padding(25) -local header = { +local pageHeader = { props = { textColor = util.color.rgb(1, 1, 1), textSize = 30, }, } - -local normal = { +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(groupKey, setting, value) +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, groupKey, setting.renderer)) + error(('Setting %s of %s has unknown renderer %s'):format(setting.key, group.key, setting.renderer)) end - local group = common.group(groupKey) - value = value or group[setting.key] local set = function(value) - group[setting.key] = value - renderSetting(groupKey, setting, 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 element = groupOptions[groupKey].element - local localization = groupOptions[groupKey].localization - local settingsLayout = element.layout.content.settings - settingsLayout.content[setting.key] = { + local l10n = core.l10n(group.l10n) + return { name = setting.key, type = ui.TYPE.Flex, content = ui.content { @@ -65,12 +76,12 @@ local function renderSetting(groupKey, setting, value) type = ui.TYPE.Text, template = normal, props = { - text = localization(setting.name), + text = l10n(setting.name), }, }, - padding(10), + smallPadding, renderFunction(value, set, setting.argument), - padding(10), + smallPadding, { type = ui.TYPE.Text, template = normal, @@ -85,20 +96,20 @@ local function renderSetting(groupKey, setting, value) }, }, }, - padding(20), }, } - element:update() end -local function renderGroup(groupKey) - local group = common.groups():get(groupKey) - local element = groupOptions[groupKey].element - local localization = groupOptions[groupKey].localization - element.layout = { +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 { - padding(10), { type = ui.TYPE.Flex, props = { @@ -110,60 +121,158 @@ local function renderGroup(groupKey) { name = 'name', type = ui.TYPE.Text, - template = header, + template = groupHeader, props = { - text = localization(group.name), + text = l10n(group.name), }, }, - padding(10), + smallPadding, { name = 'description', type = ui.TYPE.Text, template = normal, props = { - text = localization(group.description), + text = l10n(group.description), }, }, }, }, - padding(50), + smallPadding, { name = 'settings', type = ui.TYPE.Flex, content = ui.content{}, }, + bigPadding, }, } - local settingsContent = element.layout.content.settings.content + local settingsContent = layout.content.settings.content + local valueSection = common.getSection(global, group.key) for _, setting in pairs(group.settings) do - settingsContent:add({ name = setting.key }) - renderSetting(groupKey, setting) + settingsContent:add(renderSetting(group, setting, valueSection:get(setting.key), global)) end + return layout +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.Center, + }, + 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 + for _, pageGroup in ipairs(groups[page.key]) 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 = '', + } +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 {} + table.insert(groups[group.page], { + key = group.key, + global = global, + }) + common.getSection(global, group.key):subscribe(onSettingChanged(global)) + + if not pageOptions[group.page] then return end + local element = pageOptions[group.page].element + local groupsLayout = element.layout.content.groups + -- TODO: make group order deterministic + groupsLayout.content:add(renderGroup(group, global)) element:update() end - -local function onGroupRegistered(groupKey) - local group = common.groups():get(groupKey) - local loc = core.l10n(group.localization) - local options = { - name = loc(group.name), - element = ui.create{}, - searchHints = '', - localization = loc, - } - groupOptions[groupKey] = options - renderGroup(groupKey) - ui.registerSettingsPage(options) +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 onSettingChanged(event) - local group = common.groups():get(event.groupKey) - local setting = group.settings[event.settingKey] - renderSetting(event.groupKey, setting, event.value) +local function registerPage(options) + 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, + } + groups[page.key] = groups[page.key] or {} + pageOptions[page.key] = renderPage(page) + ui.registerSettingsPage(pageOptions[page.key]) end return { - onGroupRegistered = onGroupRegistered, - onSettingChanged = onSettingChanged, + registerPage = registerPage, registerRenderer = registerRenderer, } \ No newline at end of file From d9b4871f0c7425026a0826ac7cde645ca8148056 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 27 Apr 2022 22:05:39 +0200 Subject: [PATCH 10/15] Apply Flex arrange to each child separately --- files/builtin_scripts/scripts/omw/settings/player.lua | 2 +- files/builtin_scripts/scripts/omw/settings/render.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 14d5b12033..434f7b69c5 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -13,7 +13,7 @@ render.registerRenderer('text', function(value, set, arg) text = value, textColor = util.color.rgb(1, 1, 1), textSize = 15, - textAlignV = ui.ALIGNMENT.Center, + textAlignV = ui.ALIGNMENT.End, }, events = { textChanged = async:callback(function(s) set(s) end), diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 0d06214600..403eae44a6 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -115,7 +115,7 @@ local function renderGroup(group, global) props = { horizontal = true, align = ui.ALIGNMENT.Start, - arrange = ui.ALIGNMENT.Center, + arrange = ui.ALIGNMENT.End, }, content = ui.content { { @@ -166,7 +166,7 @@ local function renderPage(page) props = { horizontal = true, align = ui.ALIGNMENT.Start, - arrange = ui.ALIGNMENT.Center, + arrange = ui.ALIGNMENT.End, }, content = ui.content { { From 9b27973479ce9914c2cf54412fd591e0d5f00495 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Apr 2022 08:08:42 +0000 Subject: [PATCH 11/15] Deleted files/builtin_scripts/openmw_aux/settings.lua --- files/builtin_scripts/openmw_aux/settings.lua | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 files/builtin_scripts/openmw_aux/settings.lua diff --git a/files/builtin_scripts/openmw_aux/settings.lua b/files/builtin_scripts/openmw_aux/settings.lua deleted file mode 100644 index 8ce9929eaa..0000000000 --- a/files/builtin_scripts/openmw_aux/settings.lua +++ /dev/null @@ -1,4 +0,0 @@ -local common = require('scripts.omw.settings.common') -return { - group = common.group, -} \ No newline at end of file From 26154c85a163db5cf76057f4ae6517c5071913c8 Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 29 Apr 2022 18:56:37 +0200 Subject: [PATCH 12/15] Check if options arguments are a table --- files/builtin_scripts/scripts/omw/settings/common.lua | 6 ++++++ files/builtin_scripts/scripts/omw/settings/render.lua | 3 +++ 2 files changed, 9 insertions(+) diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index 3ea462daf9..96bfd747a2 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -6,6 +6,9 @@ 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 @@ -24,6 +27,9 @@ local function validateSettingOptions(options) 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 diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 403eae44a6..ba537b2099 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -249,6 +249,9 @@ storage.playerSection(common.groupSectionKey):subscribe(async:callback(function( 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 From 5e90b1db0d4131ee6b52eb99c6a92d4267f1e525 Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 29 Apr 2022 19:23:20 +0200 Subject: [PATCH 13/15] Define order of groups in a page --- .../scripts/omw/settings/common.lua | 4 +++ .../scripts/omw/settings/render.lua | 28 +++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index 96bfd747a2..51e22b4465 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -40,6 +40,9 @@ local function validateGroupOptions(options) 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 @@ -78,6 +81,7 @@ local function registerGroup(options) local group = { key = options.key, page = options.page, + order = options.order or 0, l10n = options.l10n, name = options.name, description = options.description, diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index ba537b2099..95b4bf2ff1 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -154,6 +154,12 @@ local function renderGroup(group, global) 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 renderPage(page) local l10n = core.l10n(page.l10n) local layout = { @@ -197,7 +203,11 @@ local function renderPage(page) }, } local groupsContent = layout.content.groups.content - for _, pageGroup in ipairs(groups[page.key]) do + 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 @@ -224,17 +234,25 @@ end local function onGroupRegistered(global, key) local group = common.getSection(global, common.groupSectionKey):get(key) groups[group.page] = groups[group.page] or {} - table.insert(groups[group.page], { + local pageGroup = { key = group.key, global = global, - }) + order = group.order, + } + table.insert(groups[group.page], pageGroup) common.getSection(global, group.key):subscribe(onSettingChanged(global)) + local index = 1 + for _, g in ipairs(groups[group.page]) do + if pageGroupComparator(pageGroup, g) then + index = index + 1 + end + end + if not pageOptions[group.page] then return end local element = pageOptions[group.page].element local groupsLayout = element.layout.content.groups - -- TODO: make group order deterministic - groupsLayout.content:add(renderGroup(group, global)) + groupsLayout.content:insert(index, renderGroup(group, global)) element:update() end local globalGroups = storage.globalSection(common.groupSectionKey) From 1b62dda9f6648c15a166b50d17c74f01c5abc73c Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 29 Apr 2022 19:35:01 +0200 Subject: [PATCH 14/15] Generate search hints --- .../scripts/omw/settings/render.lua | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 95b4bf2ff1..c072d20e94 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -11,6 +11,7 @@ local function registerRenderer(name, renderFunction) renderers[name] = renderFunction end +local pages = {} local groups = {} local pageOptions = {} @@ -160,6 +161,25 @@ local function pageGroupComparator(a, b) ) 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 = { @@ -214,7 +234,7 @@ local function renderPage(page) return { name = l10n(page.name), element = ui.create(layout), - searchHints = '', + searchHints = generateSearchHints(page), } end @@ -242,18 +262,12 @@ local function onGroupRegistered(global, key) table.insert(groups[group.page], pageGroup) common.getSection(global, group.key):subscribe(onSettingChanged(global)) - local index = 1 - for _, g in ipairs(groups[group.page]) do - if pageGroupComparator(pageGroup, g) then - index = index + 1 - end + 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 - - if not pageOptions[group.page] then return end - local element = pageOptions[group.page].element - local groupsLayout = element.layout.content.groups - groupsLayout.content:insert(index, renderGroup(group, global)) - element:update() end local globalGroups = storage.globalSection(common.groupSectionKey) for groupKey in pairs(globalGroups:asTable()) do @@ -288,6 +302,7 @@ local function registerPage(options) 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]) From cd3535cd63f074941e4c1772eb531f8bc3f1b99b Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 1 May 2022 16:57:33 +0200 Subject: [PATCH 15/15] Document Settings interface, add scripts to CMakeLists --- docs/source/generate_luadoc.sh | 1 + docs/source/reference/lua-scripting/api.rst | 24 ++-- .../lua-scripting/interface_settings.rst | 6 + .../reference/lua-scripting/overview.rst | 23 ++-- files/builtin_scripts/CMakeLists.txt | 4 + .../scripts/omw/settings/common.lua | 9 +- .../scripts/omw/settings/player.lua | 125 ++++++++++++++++++ 7 files changed, 171 insertions(+), 21 deletions(-) create mode 100644 docs/source/reference/lua-scripting/interface_settings.rst diff --git a/docs/source/generate_luadoc.sh b/docs/source/generate_luadoc.sh index 5376aa0ab9..d6bbbe3634 100755 --- a/docs/source/generate_luadoc.sh +++ b/docs/source/generate_luadoc.sh @@ -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 diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 110771fbcf..a6009edf1a 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -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 ` | by local scripts | | Control basic AI of NPCs and creatures. | -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`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 ` + - by local scripts + - Control basic AI of NPCs and creatures. + * - :ref:`Camera ` + - by player scripts + - | Allows to alter behavior of the built-in camera script + | without overriding the script completely. + * - :ref:`Settings ` + - by player and global scripts + - Save, display and track changes of setting values. diff --git a/docs/source/reference/lua-scripting/interface_settings.rst b/docs/source/reference/lua-scripting/interface_settings.rst new file mode 100644 index 0000000000..cd1994ccfa --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_settings.rst @@ -0,0 +1,6 @@ +Interface Settings +================== + +.. raw:: html + :file: generated_html/scripts_omw_settings_player.html + diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 91c0a071e0..e21469a53f 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -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 ` | by local scripts | | Control basic AI of NPCs and creatures. | -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`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 ` + - by local scripts + - Control basic AI of NPCs and creatures. + * - :ref:`Camera ` + - by player scripts + - | Allows to alter behavior of the built-in camera script + | without overriding the script completely. + * - :ref:`Settings ` + - by player and global scripts + - Save, display and track changes of setting values. Event system ============ diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index a3ba1f5149..318e389585 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -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 diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index 51e22b4465..1e87dfd62b 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -12,8 +12,8 @@ local function validateSettingOptions(options) if type(options.key) ~= 'string' then error('Setting must have a key') end - if type(options.saveOnly) ~= 'boolean' then - error('Setting must be save only or not') + 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') @@ -63,7 +63,7 @@ end local function registerSetting(options) return { key = options.key, - saveOnly = options.saveOnly, + permanentStorage = options.permanentStorage, default = options.default, renderer = options.renderer, argument = options.argument, @@ -123,7 +123,7 @@ return { local section = contextSection(groupKey) saved[groupKey] = {} for key, value in pairs(section:asTable()) do - if group.settings[key].saveOnly then + if not group.settings[key].permanentStorage then saved[groupKey][key] = value end end @@ -132,5 +132,4 @@ return { return saved end, registerGroup = registerGroup, - validateGroupOptions = validateGroupOptions, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 434f7b69c5..612ba3ec7b 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -21,11 +21,136 @@ render.registerRenderer('text', function(value, set, arg) } 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 = {