diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp index eff4578d00..bff978b633 100644 --- a/components/lua/storage.cpp +++ b/components/lua/storage.cpp @@ -38,7 +38,7 @@ namespace LuaUtil void LuaStorage::Section::runCallbacks(sol::optional changedKey) { - mStorage->mRunningCallbacks = true; + mStorage->mRunningCallbacks.insert(this); mCallbacks.erase(std::remove_if(mCallbacks.begin(), mCallbacks.end(), [&](const Callback& callback) { bool valid = callback.isValid(); @@ -46,13 +46,20 @@ namespace LuaUtil callback.tryCall(mSectionName, changedKey); return !valid; }), mCallbacks.end()); - mStorage->mRunningCallbacks = false; + mStorage->mRunningCallbacks.erase(this); + } + + void LuaStorage::Section::throwIfCallbackRecursionIsTooDeep() + { + if (mStorage->mRunningCallbacks.count(this) > 0) + throw std::runtime_error("Storage handler shouldn't change the storage section it handles (leads to an infinite recursion)"); + if (mStorage->mRunningCallbacks.size() > 10) + throw std::runtime_error("Too many subscribe callbacks triggering in a chain, likely an infinite recursion"); } void LuaStorage::Section::set(std::string_view key, const sol::object& value) { - if (mStorage->mRunningCallbacks) - throw std::runtime_error("Not allowed to change storage in storage handlers because it can lead to an infinite recursion"); + throwIfCallbackRecursionIsTooDeep(); if (value != sol::nil) mValues[std::string(key)] = Value(value); else @@ -68,8 +75,7 @@ namespace LuaUtil void LuaStorage::Section::setAll(const sol::optional& values) { - if (mStorage->mRunningCallbacks) - throw std::runtime_error("Not allowed to change storage in storage handlers because it can lead to an infinite recursion"); + throwIfCallbackRecursionIsTooDeep(); mValues.clear(); if (values) { diff --git a/components/lua/storage.hpp b/components/lua/storage.hpp index 11ea91f039..8ae944c5ab 100644 --- a/components/lua/storage.hpp +++ b/components/lua/storage.hpp @@ -62,6 +62,7 @@ namespace LuaUtil void setAll(const sol::optional& values); sol::table asTable(); void runCallbacks(sol::optional changedKey); + void throwIfCallbackRecursionIsTooDeep(); LuaStorage* mStorage; std::string mSectionName; @@ -81,7 +82,7 @@ namespace LuaUtil lua_State* mLua; std::map> mData; const Listener* mListener = nullptr; - bool mRunningCallbacks = false; + std::set mRunningCallbacks; }; } diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index 103351d044..e7425cccbb 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -27,7 +27,7 @@ set(LUA_BUILTIN_FILES scripts/omw/mwui/constants.lua scripts/omw/mwui/borders.lua - scripts/omw/mwui/box.lua + scripts/omw/mwui/filters.lua scripts/omw/mwui/text.lua scripts/omw/mwui/textEdit.lua scripts/omw/mwui/space.lua diff --git a/files/builtin_scripts/scripts/omw/mwui/borders.lua b/files/builtin_scripts/scripts/omw/mwui/borders.lua index bd8d99a7b7..5d056254fc 100644 --- a/files/builtin_scripts/scripts/omw/mwui/borders.lua +++ b/files/builtin_scripts/scripts/omw/mwui/borders.lua @@ -6,7 +6,7 @@ local auxUi = require('openmw_aux.ui') local constants = require('scripts.omw.mwui.constants') local v2 = util.vector2 -local whiteTexture = ui.texture{ path = 'white' } +local whiteTexture = constants.whiteTexture local menuTransparency = ui._getMenuTransparency() local sideParts = { diff --git a/files/builtin_scripts/scripts/omw/mwui/box.lua b/files/builtin_scripts/scripts/omw/mwui/box.lua deleted file mode 100644 index e7737165ae..0000000000 --- a/files/builtin_scripts/scripts/omw/mwui/box.lua +++ /dev/null @@ -1,45 +0,0 @@ -local ui = require('openmw.ui') -local util = require('openmw.util') - -local whiteTexture = ui.texture{ path = 'white' } - -local menuTransparency = ui._getMenuTransparency() - -return function(templates) - templates.backgroundTransparent = { - props = { - resource = whiteTexture, - color = util.color.rgb(0, 0, 0), - alpha = menuTransparency, - }, - } - templates.backgroundSolid = { - props = { - resource = whiteTexture, - color = util.color.rgb(0, 0, 0), - }, - } - templates.box = { - props = { - inheritAlpha = false, - }, - content = ui.content { - { - type = ui.TYPE.Image, - template = templates.backgroundTransparent, - props = { - relativeSize = util.vector2(1, 1), - }, - }, - { - template = templates.borders, - props = { - relativeSize = util.vector2(1, 1), - }, - external = { - slot = true, - }, - }, - }, - } -end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/constants.lua b/files/builtin_scripts/scripts/omw/mwui/constants.lua index 3791025717..8a02268f77 100644 --- a/files/builtin_scripts/scripts/omw/mwui/constants.lua +++ b/files/builtin_scripts/scripts/omw/mwui/constants.lua @@ -1,3 +1,4 @@ +local ui = require('openmw.ui') local util = require('openmw.util') return { @@ -8,4 +9,5 @@ return { border = 2, thickBorder = 4, padding = 2, + whiteTexture = ui.texture { path = 'white' }, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/filters.lua b/files/builtin_scripts/scripts/omw/mwui/filters.lua new file mode 100644 index 0000000000..684ee5e680 --- /dev/null +++ b/files/builtin_scripts/scripts/omw/mwui/filters.lua @@ -0,0 +1,31 @@ +local ui = require('openmw.ui') +local util = require('openmw.util') + +local constants = require('scripts.omw.mwui.constants') + +return function(templates) + templates.disabled = { + type = ui.TYPE.Container, + props = { + alpha = 0.6, + }, + content = ui.content { + { + props = { + relativeSize = util.vector2(1, 1), + }, + external = { + slot = true, + }, + }, + { + type = ui.TYPE.Image, + props = { + resource = constants.whiteTexture, + color = util.color.rgb(0, 0, 0), + relativeSize = util.vector2(1, 1), + }, + }, + }, + } +end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/init.lua b/files/builtin_scripts/scripts/omw/mwui/init.lua index d95a07040b..a9d798ef2a 100644 --- a/files/builtin_scripts/scripts/omw/mwui/init.lua +++ b/files/builtin_scripts/scripts/omw/mwui/init.lua @@ -93,6 +93,11 @@ require('scripts.omw.mwui.text')(templates) -- @field [parent=#Templates] openmw.ui#Layout textEditBox require('scripts.omw.mwui.textEdit')(templates) +--- +-- Shades its children and makes them uninteractible +-- @field [parent=#Templates] openmw.ui#Layout disabled +require('scripts.omw.mwui.filters')(templates) + --- -- Interface version -- @field [parent=#MWUI] #number version diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index 0d5e6d234a..b917d14a93 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -1,7 +1,10 @@ local storage = require('openmw.storage') -local contextSection = storage.playerSection or storage.globalSection local groupSectionKey = 'OmwSettingGroups' +local conventionPrefix = 'Settings' +local argumentSectionPostfix = 'Arguments' + +local contextSection = storage.playerSection or storage.globalSection local groupSection = contextSection(groupSectionKey) groupSection:removeOnExit() @@ -30,7 +33,6 @@ 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 @@ -88,6 +90,8 @@ local function registerGroup(options) settings = {}, } local valueSection = contextSection(options.key) + local argumentSection = contextSection(options.key .. argumentSectionPostfix) + argumentSection:removeOnExit() for i, opt in ipairs(options.settings) do local setting = registerSetting(opt) setting.order = i @@ -95,9 +99,10 @@ local function registerGroup(options) error(('Duplicate setting key %s'):format(options.key)) end group.settings[setting.key] = setting - if not valueSection:get(setting.key) then + if valueSection:get(setting.key) == nil then valueSection:set(setting.key, setting.default) end + argumentSection:set(setting.key, setting.argument) end groupSection:set(group.key, group) end @@ -106,6 +111,13 @@ return { getSection = function(global, key) return (global and storage.globalSection or storage.playerSection)(key) end, + getArgumentSection = function(global, key) + return (global and storage.globalSection or storage.playerSection)(key .. argumentSectionPostfix) + end, + updateRendererArgument = function(groupKey, settingKey, argument) + local argumentSection = contextSection(groupKey .. argumentSectionPostfix) + argumentSection:set(settingKey, argument) + end, setGlobalEvent = 'OMWSettingsGlobalSet', groupSectionKey = groupSectionKey, onLoad = function(saved) diff --git a/files/builtin_scripts/scripts/omw/settings/global.lua b/files/builtin_scripts/scripts/omw/settings/global.lua index 5818668b0e..d84794f61d 100644 --- a/files/builtin_scripts/scripts/omw/settings/global.lua +++ b/files/builtin_scripts/scripts/omw/settings/global.lua @@ -6,6 +6,7 @@ return { interfaceName = 'Settings', interface = { registerGroup = common.registerGroup, + updateRendererArgument = common.updateRendererArgument, }, engineHandlers = { onLoad = common.onLoad, diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 2efe5f0820..e072d7a4c1 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -137,6 +137,13 @@ return { -- } -- } registerGroup = common.registerGroup, + --- + -- @function [parent=#Settings] updateRendererArgument Change the renderer argument of a setting + -- available both in player and global scripts + -- @param #string groupKey A settings group key + -- @param #string settingKey A setting key + -- @param argument A renderer argument + updateRendererArgument = common.updateRendererArgument, }, engineHandlers = { onLoad = common.onLoad, diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 8274f04670..18aa41be40 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -110,6 +110,7 @@ local function renderSetting(group, setting, value, global) }, } end + local argument = common.getArgumentSection(global, group.key):get(setting.key) return { name = setting.key, type = ui.TYPE.Flex, @@ -123,7 +124,7 @@ local function renderSetting(group, setting, value, global) content = ui.content { titleLayout, growingIntreval, - renderFunction(value, set, setting.argument), + renderFunction(value, set, argument), }, } end @@ -350,6 +351,20 @@ local function onGroupRegistered(global, key) } table.insert(groups[group.page], pageGroup) common.getSection(global, group.key):subscribe(onSettingChanged(global)) + common.getArgumentSection(global, group.key):subscribe(async:callback(function(_, settingKey) + local groupKey = group.key + local group = common.getSection(global, common.groupSectionKey):get(groupKey) + if not group or not pageOptions[group.page] then return end + + local value = common.getSection(global, group.key):get(settingKey) + + local element = pageOptions[group.page].element + local groupsLayout = element.layout.content.groups + local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] + local settingsContent = groupLayout.content.settings.content + settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) + element:update() + end)) if not pages[group.page] then return end local options = renderPage(pages[group.page]) diff --git a/files/builtin_scripts/scripts/omw/settings/renderers.lua b/files/builtin_scripts/scripts/omw/settings/renderers.lua index 9b862c3b4b..561849f862 100644 --- a/files/builtin_scripts/scripts/omw/settings/renderers.lua +++ b/files/builtin_scripts/scripts/omw/settings/renderers.lua @@ -2,38 +2,80 @@ local ui = require('openmw.ui') local async = require('openmw.async') local I = require('openmw.interfaces') -return function(registerRenderer) - registerRenderer('textLine', function(value, set) +local function applyDefaults(argument, defaults) + if not argument then return defaults end + if pairs(defaults) and pairs(argument) then + local result = {} + for k, v in pairs(defaults) do + result[k] = v + end + for k, v in pairs(argument) do + result[k] = v + end + return result + end + return argument +end + +local function disable(disabled, layout) + if disabled then return { - template = I.MWUI.templates.textEditLine, - props = { - text = tostring(value), - }, - events = { - textChanged = async:callback(function(s) set(s) end), + template = I.MWUI.templates.disabled, + content = ui.content { + layout, }, } - end) + else + return layout + end +end - registerRenderer('yesNo', function(value, set) - return { - template = I.MWUI.templates.box, - content = ui.content { - { - template = I.MWUI.templates.padding, - content = ui.content { - { - template = I.MWUI.templates.textNormal, - props = { - text = value and 'Yes' or 'No', - }, - events = { - mouseClick = async:callback(function() set(not value) end), +return function(registerRenderer) + do + local defaultArgument = { + disabled = false, + } + registerRenderer('textLine', function(value, set, argument) + argument = applyDefaults(argument, defaultArgument) + return disable(argument.disabled, { + template = I.MWUI.templates.textEditLine, + props = { + text = tostring(value), + }, + events = { + textChanged = async:callback(function(s) set(s) end), + }, + }) + end) + end + + do + local defaultArgument = { + disabled = false, + trueLabel = 'Yes', + falseLabel = 'No', + } + registerRenderer('checkbox', function(value, set, argument) + argument = applyDefaults(argument, defaultArgument) + return disable(argument.disabled, { + template = I.MWUI.templates.box, + content = ui.content { + { + template = I.MWUI.templates.padding, + content = ui.content { + { + template = I.MWUI.templates.textNormal, + props = { + text = value and argument.trueLabel or argument.falseLabel + }, + events = { + mouseClick = async:callback(function() set(not value) end), + }, }, }, }, }, - }, - } - end) + }) + end) + end end \ No newline at end of file