Use storage subscribe, unify groups into pages

pull/3227/head
uramer 3 years ago
parent a35bc1dee0
commit b899320e9f

@ -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 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 contextSection = storage.playerSection or storage.globalSection
local groupSectionKey = 'OmwSettingGroups'
local groupSection = contextSection(groupSectionKey)
groupSection:removeOnExit()
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
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
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])
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
end
local function groupSubscribeEvent(groupKey)
return ('%sSubscribe%s'):format(eventPrefix, groupKey)
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,
}
end
local subscriptions = {}
local function handleSubscription(event)
if not subscriptions[event.groupKey] then
subscriptions[event.groupKey] = {}
local function registerGroup(options)
validateGroupOptions(options)
if groupSection:get(options.key) then
error(('Group with key %s was already registered'):format(options.key))
end
table.insert(subscriptions[event.groupKey], event.object or false)
end
local group = {
key = options.key,
page = options.page,
l10n = options.l10n,
name = options.name,
description = options.description,
local function subscribe(self)
local groupKey = rawget(self, 'groupKey')
local event = {
groupKey = groupKey,
object = selfObject,
settings = {},
}
core.sendGlobalEvent(EVENTS.Subscribe, event)
if playerObject then
playerObject:sendEvent(EVENTS.Subscribe, event)
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 groupSubscribeEvent(groupKey)
groupSection:set(group.key, group)
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
return {
getSection = function(global, key)
return (global and storage.globalSection or storage.playerSection)(key)
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))
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
return section:get(setting.key) or setting.default
end,
}
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
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
end
return setmetatable({
groupKey = groupKey,
sections = s,
}, groupMeta)
end
return {
SCOPE = SCOPE,
EVENTS = EVENTS,
isPlayerScope = isPlayerScope,
isSaveScope = isSaveScope,
values = values,
groups = function()
return groupsSection
groupSection:reset()
return saved
end,
saveScope = saveScope,
loadScope = loadScope,
group = group,
handleSubscription = handleSubscription,
registerGroup = registerGroup,
validateGroupOptions = validateGroupOptions,
}

@ -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
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
[common.setGlobalEvent] = function(e)
storage.globalSection(e.groupKey):set(e.settingKey, e.value)
end,
[common.EVENTS.Subscribe] = common.handleSubscription,
},
}

@ -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,
}
}

@ -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,
}

@ -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
element:update()
return layout
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{},
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 = '',
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)
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 globalGroups = storage.globalSection(common.groupSectionKey)
for groupKey in pairs(globalGroups:asTable()) do
onGroupRegistered(true, groupKey)
end
globalGroups:subscribe(async:callback(function(_, key)
if key then onGroupRegistered(true, key) end
end))
storage.playerSection(common.groupSectionKey):subscribe(async:callback(function(_, key)
if key then onGroupRegistered(false, key) end
end))
local function registerPage(options)
if type(options.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,
}
Loading…
Cancel
Save