1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-19 15:09:43 +00:00

Merge branch 'lua_ui_child_element' into 'master'

Lua Element as layout children

See merge request OpenMW/openmw!3582
This commit is contained in:
Zackhasacat 2023-11-28 21:54:55 +00:00
commit ae3f9f8dcf
12 changed files with 191 additions and 98 deletions

View file

@ -95,6 +95,13 @@ namespace MWLua
MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager();
auto element = context.mLua->sol().new_usertype<LuaUi::Element>("Element");
element[sol::meta_function::to_string] = [](const LuaUi::Element& element) {
std::stringstream res;
res << "UiElement";
if (element.mLayer != "")
res << "[" << element.mLayer << "]";
return res.str();
};
element["layout"] = sol::property([](LuaUi::Element& element) { return element.mLayout; },
[](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; });
element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) {

View file

@ -18,11 +18,16 @@ namespace LuaUi
{
mContainer = MyGUI::Gui::getInstancePtr()->createWidget<LuaContainer>(
"", MyGUI::IntCoord(), MyGUI::Align::Default, "", "");
mContainer->initialize(luaState, mContainer);
mContainer->onCoordChange([this](WidgetExtension* ext, MyGUI::IntCoord coord) { setSize(coord.size()); });
mContainer->initialize(luaState, mContainer, false);
mContainer->widget()->eventChangeCoord += MyGUI::newDelegate(this, &LuaAdapter::containerChangedCoord);
mContainer->widget()->attachToWidget(this);
}
void LuaAdapter::containerChangedCoord(MyGUI::Widget*)
{
setSize(mContainer->getSize());
}
void LuaAdapter::attach(const std::shared_ptr<Element>& element)
{
detachElement();
@ -44,14 +49,20 @@ namespace LuaUi
void LuaAdapter::attachElement()
{
if (mElement.get())
mElement->attachToWidget(mContainer);
if (!mElement.get())
return;
if (!mElement->mRoot)
throw std::logic_error("Attempting to use a destroyed UI Element");
mContainer->setChildren({ mElement->mRoot });
mElement->mRoot->updateCoord();
mContainer->updateCoord();
}
void LuaAdapter::detachElement()
{
if (mElement.get())
mElement->detachFromWidget();
mContainer->setChildren({});
if (mElement && mElement->mRoot)
mElement->mRoot->widget()->detachFromWidget();
mElement = nullptr;
}
}

View file

@ -25,6 +25,8 @@ namespace LuaUi
void attachElement();
void detachElement();
void containerChangedCoord(MyGUI::Widget*);
};
}

View file

@ -9,6 +9,7 @@ namespace LuaUi
{
MYGUI_RTTI_DERIVED(LuaContainer)
public:
MyGUI::IntSize calculateSize() override;
void updateCoord() override;

View file

@ -1,4 +1,5 @@
#include "content.hpp"
#include "element.hpp"
namespace LuaUi
{
@ -14,6 +15,8 @@ namespace LuaUi
bool isValidContent(const sol::object& object)
{
if (object.is<Element>())
return true;
if (object.get_type() != sol::type::table)
return false;
sol::table table = object;

View file

@ -22,7 +22,7 @@ namespace LuaUi
: mTable(std::move(table))
{
if (!isValidContent(mTable))
throw std::domain_error("Expected a Content table");
throw std::domain_error("Invalid UI Content");
}
size_t size() const { return mTable.size(); }
@ -43,17 +43,17 @@ namespace LuaUi
}
void insert(size_t index, const sol::table& table) { callMethod("insert", toLua(index), table); }
sol::table at(size_t index) const
sol::object at(size_t index) const
{
if (index < size())
return mTable.get<sol::table>(toLua(index));
return mTable.get<sol::object>(toLua(index));
else
throw std::range_error("Invalid Content index");
}
sol::table at(std::string_view name) const
sol::object at(std::string_view name) const
{
if (indexOf(name).has_value())
return mTable.get<sol::table>(name);
return mTable.get<sol::object>(name);
else
throw std::range_error("Invalid Content key");
}

View file

@ -1,12 +1,17 @@
local M = {}
M.__Content = true
function validateContentChild(v)
if not (type(v) == 'table' or v.__type and v.__type.name == 'LuaUi::Element') then
error('Content can only contain tables and Elements')
end
end
M.new = function(source)
local result = {}
result.__nameIndex = {}
for i, v in ipairs(source) do
if type(v) ~= 'table' then
error('Content can only contain tables')
end
validateContentChild(v)
result[i] = v
if type(v.name) == 'string' then
result.__nameIndex[v.name] = i
@ -38,9 +43,7 @@ end
local methods = {
insert = function(self, index, value)
validateIndex(self, index)
if type(value) ~= 'table' then
error('Content can only contain tables')
end
validateContentChild(value)
for i = #self, index, -1 do
rawset(self, i + 1, rawget(self, i))
local name = rawget(self, i + 1)
@ -56,7 +59,7 @@ local methods = {
indexOf = function(self, value)
if type(value) == 'string' then
return self.__nameIndex[value]
elseif type(value) == 'table' then
else
for i = 1, #self do
if rawget(self, i) == value then
return i
@ -113,10 +116,9 @@ M.__newindex = function(self, key, value)
local index = getIndexFromKey(self, key)
if value == nil then
remove(self, index)
elseif type(value) == 'table' then
assign(self, index, value)
else
error('Content can only contain tables')
validateContentChild(value)
assign(self, index, value)
end
end
M.__tostring = function(self)

View file

@ -43,12 +43,70 @@ namespace LuaUi
return type;
}
void destroyWidget(LuaUi::WidgetExtension* ext)
void destroyWidget(WidgetExtension* ext)
{
ext->deinitialize();
MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget());
}
void destroyChild(WidgetExtension* ext)
{
if (!ext->isRoot())
destroyWidget(ext);
else
ext->widget()->detachFromWidget();
}
void detachElements(WidgetExtension* ext)
{
for (auto* child : ext->children())
{
if (child->isRoot())
child->widget()->detachFromWidget();
else
detachElements(child);
}
for (auto* child : ext->templateChildren())
{
if (child->isRoot())
child->widget()->detachFromWidget();
else
detachElements(child);
}
}
void destroyRoot(WidgetExtension* ext)
{
detachElements(ext);
destroyWidget(ext);
}
void updateRootCoord(WidgetExtension* ext)
{
WidgetExtension* root = ext;
while (root->getParent())
root = root->getParent();
root->updateCoord();
}
WidgetExtension* pluckElementRoot(const sol::object& child)
{
std::shared_ptr<Element> element = child.as<std::shared_ptr<Element>>();
WidgetExtension* root = element->mRoot;
if (!root)
throw std::logic_error("Using a destroyed element as a layout child");
WidgetExtension* parent = root->getParent();
if (parent)
{
auto children = parent->children();
std::remove(children.begin(), children.end(), root);
parent->setChildren(children);
root->widget()->detachFromWidget();
}
root->updateCoord();
return root;
}
WidgetExtension* createWidget(const sol::table& layout, uint64_t depth);
void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth);
@ -60,7 +118,7 @@ namespace LuaUi
if (contentObj == sol::nil)
{
for (WidgetExtension* w : children)
destroyWidget(w);
destroyChild(w);
return result;
}
ContentView content(LuaUtil::cast<sol::table>(contentObj));
@ -69,22 +127,39 @@ namespace LuaUi
for (size_t i = 0; i < minSize; i++)
{
WidgetExtension* ext = children[i];
sol::table newLayout = content.at(i);
if (ext->widget()->getTypeName() == widgetType(newLayout))
sol::object child = content.at(i);
if (child.is<Element>())
{
updateWidget(ext, newLayout, depth);
WidgetExtension* root = pluckElementRoot(child);
if (ext != root)
destroyChild(ext);
result[i] = root;
}
else
{
destroyWidget(ext);
ext = createWidget(newLayout, depth);
sol::table newLayout = child.as<sol::table>();
if (ext->widget()->getTypeName() == widgetType(newLayout))
{
updateWidget(ext, newLayout, depth);
}
else
{
destroyChild(ext);
ext = createWidget(newLayout, depth);
}
result[i] = ext;
}
result[i] = ext;
}
for (size_t i = minSize; i < children.size(); i++)
destroyWidget(children[i]);
destroyChild(children[i]);
for (size_t i = minSize; i < content.size(); i++)
result[i] = createWidget(content.at(i), depth);
{
sol::object child = content.at(i);
if (child.is<Element>())
result[i] = pluckElementRoot(child);
else
result[i] = createWidget(child.as<sol::table>(), depth);
}
return result;
}
@ -97,7 +172,7 @@ namespace LuaUi
ext->setTemplateChildren(updateContent(ext->templateChildren(), content, depth));
}
void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::object& eventsObj)
void setEventCallbacks(WidgetExtension* ext, const sol::object& eventsObj)
{
ext->clearCallbacks();
if (eventsObj == sol::nil)
@ -130,7 +205,7 @@ namespace LuaUi
WidgetExtension* ext = dynamic_cast<WidgetExtension*>(widget);
if (!ext)
throw std::runtime_error("Invalid widget!");
ext->initialize(layout.lua_state(), widget);
ext->initialize(layout.lua_state(), widget, depth == 0);
updateWidget(ext, layout, depth);
return ext;
@ -169,7 +244,6 @@ namespace LuaUi
Element::Element(sol::table layout)
: mRoot(nullptr)
, mAttachedTo(nullptr)
, mLayout(std::move(layout))
, mLayer()
, mUpdate(false)
@ -191,7 +265,7 @@ namespace LuaUi
{
mRoot = createWidget(layout(), 0);
mLayer = setLayer(mRoot, layout());
updateAttachment();
updateRootCoord(mRoot);
}
}
@ -201,15 +275,21 @@ namespace LuaUi
{
if (mRoot->widget()->getTypeName() != widgetType(layout()))
{
destroyWidget(mRoot);
destroyRoot(mRoot);
WidgetExtension* parent = mRoot->getParent();
auto children = parent->children();
auto it = std::find(children.begin(), children.end(), mRoot);
mRoot = createWidget(layout(), 0);
*it = mRoot;
parent->setChildren(children);
mRoot->updateCoord();
}
else
{
updateWidget(mRoot, layout(), 0);
}
mLayer = setLayer(mRoot, layout());
updateAttachment();
updateRootCoord(mRoot);
}
mUpdate = false;
}
@ -218,40 +298,10 @@ namespace LuaUi
{
if (mRoot)
{
destroyWidget(mRoot);
destroyRoot(mRoot);
mRoot = nullptr;
mLayout = sol::make_object(mLayout.lua_state(), sol::nil);
}
sAllElements.erase(this);
}
void Element::attachToWidget(WidgetExtension* w)
{
if (mAttachedTo)
throw std::logic_error("A UI element can't be attached to two widgets at once");
mAttachedTo = w;
updateAttachment();
}
void Element::detachFromWidget()
{
if (mRoot)
mRoot->widget()->detachFromWidget();
if (mAttachedTo)
mAttachedTo->setChildren({});
mAttachedTo = nullptr;
}
void Element::updateAttachment()
{
if (!mRoot)
return;
if (mAttachedTo)
{
if (!mLayer.empty())
Log(Debug::Warning) << "Ignoring element's layer " << mLayer << " because it's attached to a widget";
mAttachedTo->setChildren({ mRoot });
mAttachedTo->updateCoord();
}
}
}

View file

@ -17,7 +17,6 @@ namespace LuaUi
}
WidgetExtension* mRoot;
WidgetExtension* mAttachedTo;
sol::object mLayout;
std::string mLayer;
bool mUpdate;
@ -31,14 +30,10 @@ namespace LuaUi
friend void clearUserInterface();
void attachToWidget(WidgetExtension* w);
void detachFromWidget();
private:
Element(sol::table layout);
sol::table layout() { return LuaUtil::cast<sol::table>(mLayout); }
static std::map<Element*, std::shared_ptr<Element>> sAllElements;
void updateAttachment();
};
}

View file

@ -9,6 +9,7 @@ namespace LuaUi
: mForcePosition(false)
, mForceSize(false)
, mPropagateEvents(true)
, mVisible(true)
, mLua(nullptr)
, mWidget(nullptr)
, mSlot(this)
@ -18,13 +19,15 @@ namespace LuaUi
, mExternal(sol::nil)
, mParent(nullptr)
, mTemplateChild(false)
, mElementRoot(false)
{
}
void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self)
void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self, bool isRoot)
{
mLua = lua;
mWidget = self;
mElementRoot = isRoot;
initialize();
updateTemplate();
}
@ -40,8 +43,6 @@ namespace LuaUi
clearCallbacks();
clearEvents(mWidget);
mOnCoordChange.reset();
for (WidgetExtension* w : mChildren)
w->deinitialize();
for (WidgetExtension* w : mTemplateChildren)
@ -92,10 +93,9 @@ namespace LuaUi
{
// workaround for MyGUI bug
// parent visibility doesn't affect added children
MyGUI::Widget* widget = this->widget();
MyGUI::Widget* parent = widget->getParent();
bool inheritedVisible = widget->getVisible() && (parent == nullptr || parent->getInheritedVisible());
widget->setVisible(inheritedVisible);
MyGUI::Widget* parent = widget()->getParent();
bool inheritedVisible = mVisible && (parent == nullptr || parent->getInheritedVisible());
widget()->setVisible(inheritedVisible);
}
void WidgetExtension::attach(WidgetExtension* ext)
@ -262,8 +262,6 @@ namespace LuaUi
if (oldCoord != newCoord)
mWidget->setCoord(newCoord);
updateChildrenCoord();
if (oldCoord != newCoord && mOnCoordChange.has_value())
mOnCoordChange.value()(this, newCoord);
}
void WidgetExtension::setProperties(const sol::object& props)
@ -280,7 +278,8 @@ namespace LuaUi
mRelativeCoord = propertyValue("relativePosition", MyGUI::FloatPoint());
mRelativeCoord = propertyValue("relativeSize", MyGUI::FloatSize());
mAnchor = propertyValue("anchor", MyGUI::FloatSize());
mWidget->setVisible(propertyValue("visible", true));
mVisible = propertyValue("visible", true);
mWidget->setVisible(mVisible);
mWidget->setPointer(propertyValue("pointer", std::string("arrow")));
mWidget->setAlpha(propertyValue("alpha", 1.f));
mWidget->setInheritsAlpha(propertyValue("inheritAlpha", true));

View file

@ -26,13 +26,16 @@ namespace LuaUi
virtual ~WidgetExtension() = default;
// must be called after creating the underlying MyGUI::Widget
void initialize(lua_State* lua, MyGUI::Widget* self);
void initialize(lua_State* lua, MyGUI::Widget* self, bool isRoot);
// must be called after before destroying the underlying MyGUI::Widget
virtual void deinitialize();
MyGUI::Widget* widget() const { return mWidget; }
WidgetExtension* slot() const { return mSlot; }
bool isRoot() const { return mElementRoot; }
WidgetExtension* getParent() const { return mParent; }
void reset();
const std::vector<WidgetExtension*>& children() { return mChildren; }
@ -66,11 +69,6 @@ namespace LuaUi
return parseExternal(mExternal, name, defaultValue);
}
void onCoordChange(const std::optional<std::function<void(WidgetExtension*, MyGUI::IntCoord)>>& callback)
{
mOnCoordChange = callback;
}
virtual MyGUI::IntSize calculateSize();
virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size);
MyGUI::IntCoord calculateCoord();
@ -137,6 +135,7 @@ namespace LuaUi
MyGUI::FloatSize mAnchor;
bool mPropagateEvents;
bool mVisible; // used to implement updateVisible
private:
// use lua_State* instead of sol::state_view because MyGUI requires a default constructor
@ -152,6 +151,7 @@ namespace LuaUi
sol::object mExternal;
WidgetExtension* mParent;
bool mTemplateChild;
bool mElementRoot;
void attach(WidgetExtension* ext);
void attachTemplate(WidgetExtension* ext);
@ -172,8 +172,6 @@ namespace LuaUi
void focusGain(MyGUI::Widget*, MyGUI::Widget*);
void focusLoss(MyGUI::Widget*, MyGUI::Widget*);
std::optional<std::function<void(WidgetExtension*, MyGUI::IntCoord)>> mOnCoordChange;
void updateVisible();
};

View file

@ -164,9 +164,9 @@
---
-- Content. An array-like container, which allows to reference elements by their name.
-- Implements [iterables#List](iterables.html#List) of #Layout and [iterables#Map](iterables.html#Map) of #string to #Layout.
-- Implements [iterables#List](iterables.html#List) of #Layout or #Element and [iterables#Map](iterables.html#Map) of #string to #Layout or #Element.
-- @type Content
-- @list <#Layout>
-- @list <#any>
-- @usage
-- local content = ui.content {
-- { name = 'input' },
@ -200,27 +200,27 @@
-- @function [parent=#Content] __index
-- @param self
-- @param #string name
-- @return #Layout
-- @return #any
---
-- Puts the layout at given index by shifting all the elements after it
-- @function [parent=#Content] insert
-- @param self
-- @param #number index
-- @param #Layout layout
-- @param #any layoutOrElement
---
-- Adds the layout at the end of the Content
-- (same as calling insert with `last index + 1`)
-- @function [parent=#Content] add
-- @param self
-- @param #Layout layout
-- @param #any layoutOrElement
---
-- Finds the index of the given layout. If it is not in the container, returns nil
-- @function [parent=#Content] indexOf
-- @param self
-- @param #Layout layout
-- @param #any layoutOrElement
-- @return #number, #nil index
---
@ -228,10 +228,35 @@
-- @type Element
---
-- Refreshes the rendered element to match the current layout state
-- Refreshes the rendered element to match the current layout state.
-- Refreshes positions and sizes, but not the layout of the child Elements.
-- @function [parent=#Element] update
-- @param self
-- @usage
-- local child = ui.create {
-- type = ui.TYPE.Text,
-- props = {
-- text = 'child 1',
-- },
-- }
-- local parent = ui.create {
-- content = ui.content {
-- child,
-- {
-- type = ui.TYPE.Text,
-- props = {
-- text = 'parent 1',
-- },
-- }
-- }
-- }
-- -- ...
-- child.layout.props.text = 'child 2'
-- parent.layout.content[2].props.text = 'parent 2'
-- parent:update() -- will show 'parent 2', but 'child 1'
---
-- Destroys the element
-- @function [parent=#Element] destroy