mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-11-04 10:56:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			257 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			257 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include "element.hpp"
 | 
						|
 | 
						|
#include <MyGUI_Gui.h>
 | 
						|
 | 
						|
#include "content.hpp"
 | 
						|
#include "util.hpp"
 | 
						|
#include "widget.hpp"
 | 
						|
 | 
						|
namespace LuaUi
 | 
						|
{
 | 
						|
    namespace
 | 
						|
    {
 | 
						|
        namespace LayoutKeys
 | 
						|
        {
 | 
						|
            constexpr std::string_view type = "type";
 | 
						|
            constexpr std::string_view name = "name";
 | 
						|
            constexpr std::string_view layer = "layer";
 | 
						|
            constexpr std::string_view templateLayout = "template";
 | 
						|
            constexpr std::string_view props = "props";
 | 
						|
            constexpr std::string_view events = "events";
 | 
						|
            constexpr std::string_view content = "content";
 | 
						|
            constexpr std::string_view external = "external";
 | 
						|
        }
 | 
						|
 | 
						|
        const std::string defaultWidgetType = "LuaWidget";
 | 
						|
 | 
						|
        constexpr uint64_t maxDepth = 250;
 | 
						|
 | 
						|
        std::string widgetType(const sol::table& layout)
 | 
						|
        {
 | 
						|
            sol::object typeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::type);
 | 
						|
            std::string type = LuaUtil::getValueOrDefault(typeField, defaultWidgetType);
 | 
						|
            sol::object templateTypeField
 | 
						|
                = LuaUtil::getFieldOrNil(layout, LayoutKeys::templateLayout, LayoutKeys::type);
 | 
						|
            if (templateTypeField != sol::nil)
 | 
						|
            {
 | 
						|
                std::string templateType = LuaUtil::getValueOrDefault(templateTypeField, defaultWidgetType);
 | 
						|
                if (typeField != sol::nil && templateType != type)
 | 
						|
                    throw std::logic_error(std::string("Template layout type ") + type
 | 
						|
                        + std::string(" doesn't match template type ") + templateType);
 | 
						|
                type = templateType;
 | 
						|
            }
 | 
						|
            return type;
 | 
						|
        }
 | 
						|
 | 
						|
        void destroyWidget(LuaUi::WidgetExtension* ext)
 | 
						|
        {
 | 
						|
            ext->deinitialize();
 | 
						|
            MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget());
 | 
						|
        }
 | 
						|
 | 
						|
        WidgetExtension* createWidget(const sol::table& layout, uint64_t depth);
 | 
						|
        void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth);
 | 
						|
 | 
						|
        std::vector<WidgetExtension*> updateContent(
 | 
						|
            const std::vector<WidgetExtension*>& children, const sol::object& contentObj, uint64_t depth)
 | 
						|
        {
 | 
						|
            ++depth;
 | 
						|
            std::vector<WidgetExtension*> result;
 | 
						|
            if (contentObj == sol::nil)
 | 
						|
            {
 | 
						|
                for (WidgetExtension* w : children)
 | 
						|
                    destroyWidget(w);
 | 
						|
                return result;
 | 
						|
            }
 | 
						|
            ContentView content(LuaUtil::cast<sol::table>(contentObj));
 | 
						|
            result.resize(content.size());
 | 
						|
            size_t minSize = std::min(children.size(), content.size());
 | 
						|
            for (size_t i = 0; i < minSize; i++)
 | 
						|
            {
 | 
						|
                WidgetExtension* ext = children[i];
 | 
						|
                sol::table newLayout = content.at(i);
 | 
						|
                if (ext->widget()->getTypeName() == widgetType(newLayout))
 | 
						|
                {
 | 
						|
                    updateWidget(ext, newLayout, depth);
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    destroyWidget(ext);
 | 
						|
                    ext = createWidget(newLayout, depth);
 | 
						|
                }
 | 
						|
                result[i] = ext;
 | 
						|
            }
 | 
						|
            for (size_t i = minSize; i < children.size(); i++)
 | 
						|
                destroyWidget(children[i]);
 | 
						|
            for (size_t i = minSize; i < content.size(); i++)
 | 
						|
                result[i] = createWidget(content.at(i), depth);
 | 
						|
            return result;
 | 
						|
        }
 | 
						|
 | 
						|
        void setTemplate(WidgetExtension* ext, const sol::object& templateLayout, uint64_t depth)
 | 
						|
        {
 | 
						|
            ++depth;
 | 
						|
            sol::object props = LuaUtil::getFieldOrNil(templateLayout, LayoutKeys::props);
 | 
						|
            ext->setTemplateProperties(props);
 | 
						|
            sol::object content = LuaUtil::getFieldOrNil(templateLayout, LayoutKeys::content);
 | 
						|
            ext->setTemplateChildren(updateContent(ext->templateChildren(), content, depth));
 | 
						|
        }
 | 
						|
 | 
						|
        void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::object& eventsObj)
 | 
						|
        {
 | 
						|
            ext->clearCallbacks();
 | 
						|
            if (eventsObj == sol::nil)
 | 
						|
                return;
 | 
						|
            if (!eventsObj.is<sol::table>())
 | 
						|
                throw std::logic_error("The \"events\" layout field must be a table of callbacks");
 | 
						|
            auto events = eventsObj.as<sol::table>();
 | 
						|
            events.for_each([ext](const sol::object& name, const sol::object& callback) {
 | 
						|
                if (name.is<std::string>() && LuaUtil::Callback::isLuaCallback(callback))
 | 
						|
                    ext->setCallback(name.as<std::string>(), LuaUtil::Callback::fromLua(callback));
 | 
						|
                else if (!name.is<std::string>())
 | 
						|
                    Log(Debug::Warning) << "UI event key must be a string";
 | 
						|
                else
 | 
						|
                    Log(Debug::Warning) << "UI event handler for key \"" << name.as<std::string>()
 | 
						|
                                        << "\" must be an openmw.async.callback";
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        WidgetExtension* createWidget(const sol::table& layout, uint64_t depth)
 | 
						|
        {
 | 
						|
            static auto widgetTypeMap = widgetTypeToName();
 | 
						|
            std::string type = widgetType(layout);
 | 
						|
            if (widgetTypeMap.find(type) == widgetTypeMap.end())
 | 
						|
                throw std::logic_error(std::string("Invalid widget type ") += type);
 | 
						|
 | 
						|
            std::string name = layout.get_or(LayoutKeys::name, std::string());
 | 
						|
            MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT(
 | 
						|
                type, "", MyGUI::IntCoord(), MyGUI::Align::Default, std::string(), name);
 | 
						|
 | 
						|
            WidgetExtension* ext = dynamic_cast<WidgetExtension*>(widget);
 | 
						|
            if (!ext)
 | 
						|
                throw std::runtime_error("Invalid widget!");
 | 
						|
            ext->initialize(layout.lua_state(), widget);
 | 
						|
 | 
						|
            updateWidget(ext, layout, depth);
 | 
						|
            return ext;
 | 
						|
        }
 | 
						|
 | 
						|
        void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth)
 | 
						|
        {
 | 
						|
            if (depth >= maxDepth)
 | 
						|
                throw std::runtime_error("Maximum layout depth exceeded, probably caused by a circular reference");
 | 
						|
            ext->reset();
 | 
						|
            ext->setLayout(layout);
 | 
						|
            ext->setExternal(layout.get<sol::object>(LayoutKeys::external));
 | 
						|
            setTemplate(ext, layout.get<sol::object>(LayoutKeys::templateLayout), depth);
 | 
						|
            ext->setProperties(layout.get<sol::object>(LayoutKeys::props));
 | 
						|
            setEventCallbacks(ext, layout.get<sol::object>(LayoutKeys::events));
 | 
						|
            ext->setChildren(updateContent(ext->children(), layout.get<sol::object>(LayoutKeys::content), depth));
 | 
						|
            ext->updateCoord();
 | 
						|
        }
 | 
						|
 | 
						|
        std::string setLayer(WidgetExtension* ext, const sol::table& layout)
 | 
						|
        {
 | 
						|
            MyGUI::ILayer* layerNode = ext->widget()->getLayer();
 | 
						|
            std::string currentLayer = layerNode ? layerNode->getName() : std::string();
 | 
						|
            std::string newLayer = layout.get_or(LayoutKeys::layer, std::string());
 | 
						|
            if (!newLayer.empty() && !MyGUI::LayerManager::getInstance().isExist(newLayer))
 | 
						|
                throw std::logic_error(std::string("Layer ") + newLayer + " doesn't exist");
 | 
						|
            else if (newLayer != currentLayer)
 | 
						|
            {
 | 
						|
                MyGUI::LayerManager::getInstance().attachToLayerNode(newLayer, ext->widget());
 | 
						|
            }
 | 
						|
            return newLayer;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    std::map<Element*, std::shared_ptr<Element>> Element::sAllElements;
 | 
						|
 | 
						|
    Element::Element(sol::table layout)
 | 
						|
        : mRoot(nullptr)
 | 
						|
        , mAttachedTo(nullptr)
 | 
						|
        , mLayout(std::move(layout))
 | 
						|
        , mLayer()
 | 
						|
        , mUpdate(false)
 | 
						|
        , mDestroy(false)
 | 
						|
    {
 | 
						|
    }
 | 
						|
 | 
						|
    std::shared_ptr<Element> Element::make(sol::table layout)
 | 
						|
    {
 | 
						|
        std::shared_ptr<Element> ptr(new Element(std::move(layout)));
 | 
						|
        sAllElements[ptr.get()] = ptr;
 | 
						|
        return ptr;
 | 
						|
    }
 | 
						|
 | 
						|
    void Element::create()
 | 
						|
    {
 | 
						|
        assert(!mRoot);
 | 
						|
        if (!mRoot)
 | 
						|
        {
 | 
						|
            mRoot = createWidget(layout(), 0);
 | 
						|
            mLayer = setLayer(mRoot, layout());
 | 
						|
            updateAttachment();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    void Element::update()
 | 
						|
    {
 | 
						|
        if (mRoot && mUpdate)
 | 
						|
        {
 | 
						|
            if (mRoot->widget()->getTypeName() != widgetType(layout()))
 | 
						|
            {
 | 
						|
                destroyWidget(mRoot);
 | 
						|
                mRoot = createWidget(layout(), 0);
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                updateWidget(mRoot, layout(), 0);
 | 
						|
            }
 | 
						|
            mLayer = setLayer(mRoot, layout());
 | 
						|
            updateAttachment();
 | 
						|
        }
 | 
						|
        mUpdate = false;
 | 
						|
    }
 | 
						|
 | 
						|
    void Element::destroy()
 | 
						|
    {
 | 
						|
        if (mRoot)
 | 
						|
        {
 | 
						|
            destroyWidget(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();
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |