1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-16 18:59:57 +00:00

Merge branch 'update_setting_pages' into 'master'

Optimize setting group rendering

See merge request OpenMW/openmw!3929
This commit is contained in:
psi29a 2024-03-08 08:39:49 +00:00
commit f09f5c644c
4 changed files with 101 additions and 53 deletions

View file

@ -134,7 +134,10 @@ namespace MWLua
}; };
api["updateAll"] = [luaManager = context.mLuaManager, menu]() { api["updateAll"] = [luaManager = context.mLuaManager, menu]() {
LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->mUpdate = true; }); LuaUi::Element::forEach(menu, [](LuaUi::Element* e) {
if (e->mState == LuaUi::Element::Created)
e->mState = LuaUi::Element::Update;
});
luaManager->addAction([menu]() { LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->update(); }); }, luaManager->addAction([menu]() { LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->update(); }); },
"Update all menu UI elements"); "Update all menu UI elements");
}; };
@ -305,15 +308,15 @@ namespace MWLua
element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; }, element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; },
[](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; });
element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) { element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) {
if (element->mDestroy || element->mUpdate) if (element->mState != LuaUi::Element::Created)
return; return;
element->mUpdate = true; element->mState = LuaUi::Element::Update;
luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI"); luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI");
}; };
element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) { element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) {
if (element->mDestroy) if (element->mState == LuaUi::Element::Destroyed)
return; return;
element->mDestroy = true; element->mState = LuaUi::Element::Destroy;
luaManager->addAction( luaManager->addAction(
[element] { wrapAction(element, [&] { LuaUi::Element::erase(element.get()); }); }, "Destroy UI"); [element] { wrapAction(element, [&] { LuaUi::Element::erase(element.get()); }); }, "Destroy UI");
}; };

View file

@ -89,12 +89,16 @@ namespace LuaUi
root->updateCoord(); root->updateCoord();
} }
WidgetExtension* pluckElementRoot(const sol::object& child) WidgetExtension* pluckElementRoot(const sol::object& child, uint64_t depth)
{ {
std::shared_ptr<Element> element = child.as<std::shared_ptr<Element>>(); std::shared_ptr<Element> element = child.as<std::shared_ptr<Element>>();
WidgetExtension* root = element->mRoot; if (element->mState == Element::Destroyed || element->mState == Element::Destroy)
if (!root)
throw std::logic_error("Using a destroyed element as a layout child"); throw std::logic_error("Using a destroyed element as a layout child");
// child Element was created in the same frame and its action hasn't been processed yet
if (element->mState == Element::New)
element->create(depth + 1);
WidgetExtension* root = element->mRoot;
assert(root);
WidgetExtension* parent = root->getParent(); WidgetExtension* parent = root->getParent();
if (parent) if (parent)
{ {
@ -107,7 +111,7 @@ namespace LuaUi
return root; return root;
} }
WidgetExtension* createWidget(const sol::table& layout, uint64_t depth); WidgetExtension* createWidget(const sol::table& layout, bool isRoot, uint64_t depth);
void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth); void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth);
std::vector<WidgetExtension*> updateContent( std::vector<WidgetExtension*> updateContent(
@ -130,7 +134,7 @@ namespace LuaUi
sol::object child = content.at(i); sol::object child = content.at(i);
if (child.is<Element>()) if (child.is<Element>())
{ {
WidgetExtension* root = pluckElementRoot(child); WidgetExtension* root = pluckElementRoot(child, depth);
if (ext != root) if (ext != root)
destroyChild(ext); destroyChild(ext);
result[i] = root; result[i] = root;
@ -145,7 +149,7 @@ namespace LuaUi
else else
{ {
destroyChild(ext); destroyChild(ext);
ext = createWidget(newLayout, depth); ext = createWidget(newLayout, false, depth);
} }
result[i] = ext; result[i] = ext;
} }
@ -156,9 +160,9 @@ namespace LuaUi
{ {
sol::object child = content.at(i); sol::object child = content.at(i);
if (child.is<Element>()) if (child.is<Element>())
result[i] = pluckElementRoot(child); result[i] = pluckElementRoot(child, depth);
else else
result[i] = createWidget(child.as<sol::table>(), depth); result[i] = createWidget(child.as<sol::table>(), false, depth);
} }
return result; return result;
} }
@ -191,7 +195,7 @@ namespace LuaUi
}); });
} }
WidgetExtension* createWidget(const sol::table& layout, uint64_t depth) WidgetExtension* createWidget(const sol::table& layout, bool isRoot, uint64_t depth)
{ {
static auto widgetTypeMap = widgetTypeToName(); static auto widgetTypeMap = widgetTypeToName();
std::string type = widgetType(layout); std::string type = widgetType(layout);
@ -205,7 +209,7 @@ namespace LuaUi
WidgetExtension* ext = dynamic_cast<WidgetExtension*>(widget); WidgetExtension* ext = dynamic_cast<WidgetExtension*>(widget);
if (!ext) if (!ext)
throw std::runtime_error("Invalid widget!"); throw std::runtime_error("Invalid widget!");
ext->initialize(layout.lua_state(), widget, depth == 0); ext->initialize(layout.lua_state(), widget, isRoot);
updateWidget(ext, layout, depth); updateWidget(ext, layout, depth);
return ext; return ext;
@ -247,8 +251,7 @@ namespace LuaUi
: mRoot(nullptr) : mRoot(nullptr)
, mLayout(std::move(layout)) , mLayout(std::move(layout))
, mLayer() , mLayer()
, mUpdate(false) , mState(Element::New)
, mDestroy(false)
{ {
} }
@ -267,12 +270,12 @@ namespace LuaUi
sGameElements.erase(element); sGameElements.erase(element);
} }
void Element::create() void Element::create(uint64_t depth)
{ {
assert(!mRoot); assert(!mRoot);
if (!mRoot) if (mState == New)
{ {
mRoot = createWidget(layout(), 0); mRoot = createWidget(layout(), true, depth);
mLayer = setLayer(mRoot, layout()); mLayer = setLayer(mRoot, layout());
updateRootCoord(mRoot); updateRootCoord(mRoot);
} }
@ -280,15 +283,16 @@ namespace LuaUi
void Element::update() void Element::update()
{ {
if (mRoot && mUpdate) if (mState == Update)
{ {
assert(mRoot);
if (mRoot->widget()->getTypeName() != widgetType(layout())) if (mRoot->widget()->getTypeName() != widgetType(layout()))
{ {
destroyRoot(mRoot); destroyRoot(mRoot);
WidgetExtension* parent = mRoot->getParent(); WidgetExtension* parent = mRoot->getParent();
auto children = parent->children(); auto children = parent->children();
auto it = std::find(children.begin(), children.end(), mRoot); auto it = std::find(children.begin(), children.end(), mRoot);
mRoot = createWidget(layout(), 0); mRoot = createWidget(layout(), true, 0);
assert(it != children.end()); assert(it != children.end());
*it = mRoot; *it = mRoot;
parent->setChildren(children); parent->setChildren(children);
@ -301,16 +305,18 @@ namespace LuaUi
mLayer = setLayer(mRoot, layout()); mLayer = setLayer(mRoot, layout());
updateRootCoord(mRoot); updateRootCoord(mRoot);
} }
mUpdate = false; mState = Created;
} }
void Element::destroy() void Element::destroy()
{ {
if (mRoot) if (mState != Destroyed)
{ {
destroyRoot(mRoot); destroyRoot(mRoot);
mRoot = nullptr; mRoot = nullptr;
if (mState != New)
mLayout = sol::make_object(mLayout.lua_state(), sol::nil); mLayout = sol::make_object(mLayout.lua_state(), sol::nil);
mState = Destroyed;
} }
} }
} }

View file

@ -21,10 +21,18 @@ namespace LuaUi
WidgetExtension* mRoot; WidgetExtension* mRoot;
sol::object mLayout; sol::object mLayout;
std::string mLayer; std::string mLayer;
bool mUpdate;
bool mDestroy;
void create(); enum State
{
New,
Created,
Update,
Destroy,
Destroyed,
};
State mState;
void create(uint64_t dept = 0);
void update(); void update();

View file

@ -6,8 +6,11 @@ local core = require('openmw.core')
local storage = require('openmw.storage') local storage = require('openmw.storage')
local I = require('openmw.interfaces') local I = require('openmw.interfaces')
local auxUi = require('openmw_aux.ui')
local common = require('scripts.omw.settings.common') local common = require('scripts.omw.settings.common')
-- :reset on startup instead of :removeOnExit common.getSection(false, common.groupSectionKey):setLifeTime(storage.LIFE_TIME.GameSession)
-- need to :reset() on reloadlua as well as game session end
common.getSection(false, common.groupSectionKey):reset() common.getSection(false, common.groupSectionKey):reset()
local renderers = {} local renderers = {}
@ -21,6 +24,7 @@ local interfaceL10n = core.l10n('Interface')
local pages = {} local pages = {}
local groups = {} local groups = {}
local pageOptions = {} local pageOptions = {}
local groupElements = {}
local interval = { template = I.MWUI.templates.interval } local interval = { template = I.MWUI.templates.interval }
local growingIntreval = { local growingIntreval = {
@ -116,6 +120,11 @@ local function renderSetting(group, setting, value, global)
} }
end end
local argument = common.getArgumentSection(global, group.key):get(setting.key) local argument = common.getArgumentSection(global, group.key):get(setting.key)
local ok, rendererResult = pcall(renderFunction, value, set, argument)
if not ok then
print(string.format('Setting %s renderer "%s" error: %s', setting.key, setting.renderer, rendererResult))
end
return { return {
name = setting.key, name = setting.key,
type = ui.TYPE.Flex, type = ui.TYPE.Flex,
@ -129,7 +138,7 @@ local function renderSetting(group, setting, value, global)
content = ui.content { content = ui.content {
titleLayout, titleLayout,
growingIntreval, growingIntreval,
renderFunction(value, set, argument), ok and rendererResult or {}, -- TODO: display error?
}, },
} }
end end
@ -245,11 +254,13 @@ end
local function generateSearchHints(page) local function generateSearchHints(page)
local hints = {} local hints = {}
do
local l10n = core.l10n(page.l10n) local l10n = core.l10n(page.l10n)
table.insert(hints, l10n(page.name)) table.insert(hints, l10n(page.name))
if page.description then if page.description then
table.insert(hints, l10n(page.description)) table.insert(hints, l10n(page.description))
end end
end
local pageGroups = groups[page.key] local pageGroups = groups[page.key]
for _, pageGroup in pairs(pageGroups) do for _, pageGroup in pairs(pageGroups) do
local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key)
@ -281,7 +292,15 @@ local function renderPage(page, options)
if not group then if not group then
error(string.format('%s group "%s" was not found', pageGroup.global and 'Global' or 'Player', pageGroup.key)) error(string.format('%s group "%s" was not found', pageGroup.global and 'Global' or 'Player', pageGroup.key))
end end
table.insert(groupLayouts, renderGroup(group, pageGroup.global)) local groupElement = groupElements[page.key][group.key]
if not groupElement or not groupElement.layout then
groupElement = ui.create(renderGroup(group, pageGroup.global))
end
if groupElement.layout == nil then
error(string.format('Destroyed group element for %s %s', page.key, group.key))
end
groupElements[page.key][group.key] = groupElement
table.insert(groupLayouts, groupElement)
end end
local groupsLayout = { local groupsLayout = {
name = 'groups', name = 'groups',
@ -329,10 +348,14 @@ local function renderPage(page, options)
bigSpacer, bigSpacer,
}, },
} }
if options.element then options.element:destroy() end
options.name = l10n(page.name) options.name = l10n(page.name)
options.element = ui.create(layout)
options.searchHints = generateSearchHints(page) options.searchHints = generateSearchHints(page)
if options.element then
options.element.layout = layout
options.element:update()
else
options.element = ui.create(layout)
end
end end
local function onSettingChanged(global) local function onSettingChanged(global)
@ -340,18 +363,23 @@ local function onSettingChanged(global)
local group = common.getSection(global, common.groupSectionKey):get(groupKey) local group = common.getSection(global, common.groupSectionKey):get(groupKey)
if not group or not pageOptions[group.page] then return end if not group or not pageOptions[group.page] then return end
local groupElement = groupElements[group.page][group.key]
if not settingKey then if not settingKey then
if groupElement then
groupElement.layout = renderGroup(group)
groupElement:update()
else
renderPage(pages[group.page], pageOptions[group.page]) renderPage(pages[group.page], pageOptions[group.page])
end
return return
end end
local value = common.getSection(global, group.key):get(settingKey) local value = common.getSection(global, group.key):get(settingKey)
local element = pageOptions[group.page].element local settingsContent = groupElement.layout.content.settings.content
local groupsLayout = element.layout.content.groups auxUi.deepDestroy(settingsContent[settingKey]) -- support setting renderers which return UI elements
local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value)
local settingsContent = groupLayout.content.settings.content groupElement:update()
settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global)
element:update()
end) end)
end end
@ -360,6 +388,8 @@ local function onGroupRegistered(global, key)
if not group then return end if not group then return end
groups[group.page] = groups[group.page] or {} groups[group.page] = groups[group.page] or {}
groupElements[group.page] = groupElements[group.page] or {}
local pageGroup = { local pageGroup = {
key = group.key, key = group.key,
global = global, global = global,
@ -376,11 +406,9 @@ local function onGroupRegistered(global, key)
local value = common.getSection(global, group.key):get(settingKey) local value = common.getSection(global, group.key):get(settingKey)
local element = pageOptions[group.page].element local element = groupElements[group.page][group.key]
local groupsLayout = element.layout.content.groups local settingsContent = element.layout.content.settings.content
local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value)
local settingsContent = groupLayout.content.settings.content
settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global)
element:update() element:update()
end)) end))
end end
@ -418,6 +446,11 @@ local function resetPlayerGroups()
for pageKey, page in pairs(groups) do for pageKey, page in pairs(groups) do
for groupKey in pairs(page) do for groupKey in pairs(page) do
if not menuGroups[groupKey] then if not menuGroups[groupKey] then
if groupElements[pageKey][groupKey] then
groupElements[pageKey][groupKey]:destroy()
print(string.format('destroyed group element %s %s', pageKey, groupKey))
groupElements[pageKey][groupKey] = nil
end
page[groupKey] = nil page[groupKey] = nil
playerGroupsSection:set(groupKey, nil) playerGroupsSection:set(groupKey, nil)
end end
@ -426,7 +459,8 @@ local function resetPlayerGroups()
if options then if options then
if not menuPages[pageKey] then if not menuPages[pageKey] then
if options.element then if options.element then
options.element:destroy() auxUi.deepDestroy(options.element)
options.element = nil
end end
ui.removeSettingsPage(options) ui.removeSettingsPage(options)
pageOptions[pageKey] = nil pageOptions[pageKey] = nil
@ -461,9 +495,6 @@ local function registerPage(options)
} }
pages[page.key] = page pages[page.key] = page
groups[page.key] = groups[page.key] or {} groups[page.key] = groups[page.key] or {}
if pageOptions[page.key] then
pageOptions[page.key].element:destroy()
end
pageOptions[page.key] = pageOptions[page.key] or {} pageOptions[page.key] = pageOptions[page.key] or {}
renderPage(page, pageOptions[page.key]) renderPage(page, pageOptions[page.key])
ui.registerSettingsPage(pageOptions[page.key]) ui.registerSettingsPage(pageOptions[page.key])