diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index cfaf50c4f6..377d6d1e4f 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -63,7 +63,6 @@ namespace MWLua // Implemented in uibindings.cpp sol::table initUserInterfacePackage(const Context&); - void clearUserInterface(); // Implemented in inputbindings.cpp sol::table initInputPackage(const Context&); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 89b27b0d94..131581f7f7 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -162,8 +162,8 @@ add_component_dir (queries ) add_component_dir (lua_ui - widget element util layers content - text textedit window + properties widget element util layers content + text textedit window image ) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index dcb21c32a0..c9e0fc309e 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -7,128 +7,144 @@ namespace LuaUi { - std::string widgetType(const sol::table& layout) + namespace LayoutKeys { - return layout.get_or("type", std::string("LuaWidget")); + 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"; } - Content content(const sol::table& layout) + std::string widgetType(const sol::table& layout) { - auto optional = layout.get>("content"); - if (optional.has_value()) - return optional.value(); - else - return Content(); + return layout.get_or(LayoutKeys::type, std::string("LuaWidget")); } - void setProperties(LuaUi::WidgetExtension* ext, const sol::table& layout) + void destroyWidget(LuaUi::WidgetExtension* ext) { - ext->setProperties(layout.get("props")); + ext->deinitialize(); + MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget()); } - void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::table& layout) + WidgetExtension* createWidget(const sol::table& layout); + void updateWidget(WidgetExtension* ext, const sol::table& layout); + + std::vector updateContent( + const std::vector& children, const sol::object& contentObj) { - ext->clearCallbacks(); - auto events = layout.get>("events"); - if (events.has_value()) + std::vector result; + if (contentObj == sol::nil) { - events.value().for_each([ext](const sol::object& name, const sol::object& callback) - { - if (name.is() && callback.is()) - ext->setCallback(name.as(), callback.as()); - else if (!name.is()) - Log(Debug::Warning) << "UI event key must be a string"; - else if (!callback.is()) - Log(Debug::Warning) << "UI event handler for key \"" << name.as() - << "\" must be an openmw.async.callback"; - }); + for (WidgetExtension* w : children) + destroyWidget(w); + return result; } + if (!contentObj.is()) + throw std::logic_error("Layout content field must be a openmw.ui.content"); + Content content = contentObj.as(); + 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) + && ext->getLayout() == newLayout) + { + updateWidget(ext, newLayout); + } + else + { + destroyWidget(ext); + ext = createWidget(newLayout); + } + 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)); + return result; } - void setLayout(LuaUi::WidgetExtension* ext, const sol::table& layout) + void setTemplate(WidgetExtension* ext, const sol::object& templateLayout) { - ext->setLayout(layout); + // \todo remove when none of the widgets require this workaround + sol::object skin = LuaUtil::getFieldOrNil(templateLayout, "skin"); + if (skin.is()) + ext->widget()->changeWidgetSkin(skin.as()); + + 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)); + } + + void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::object& eventsObj) + { + ext->clearCallbacks(); + if (eventsObj == sol::nil) + return; + if (!eventsObj.is()) + throw std::logic_error("The \"events\" layout field must be a table of callbacks"); + auto events = eventsObj.as(); + events.for_each([ext](const sol::object& name, const sol::object& callback) + { + if (name.is() && callback.is()) + ext->setCallback(name.as(), callback.as()); + else if (!name.is()) + Log(Debug::Warning) << "UI event key must be a string"; + else if (!callback.is()) + Log(Debug::Warning) << "UI event handler for key \"" << name.as() + << "\" must be an openmw.async.callback"; + }); } - LuaUi::WidgetExtension* createWidget(const sol::table& layout, LuaUi::WidgetExtension* parent) + WidgetExtension* createWidget(const sol::table& layout) { std::string type = widgetType(layout); - std::string skin = layout.get_or("skin", std::string()); - std::string name = layout.get_or("name", std::string()); + std::string name = layout.get_or(LayoutKeys::name, std::string()); static auto widgetTypeMap = widgetTypeToName(); if (widgetTypeMap.find(type) == widgetTypeMap.end()) throw std::logic_error(std::string("Invalid widget type ") += type); MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT( - type, skin, + type, "", MyGUI::IntCoord(), MyGUI::Align::Default, std::string(), name); - LuaUi::WidgetExtension* ext = dynamic_cast(widget); + WidgetExtension* ext = dynamic_cast(widget); if (!ext) throw std::runtime_error("Invalid widget!"); - ext->initialize(layout.lua_state(), widget); - if (parent != nullptr) - widget->attachToWidget(parent->widget()); - - setEventCallbacks(ext, layout); - setProperties(ext, layout); - setLayout(ext, layout); - - Content cont = content(layout); - for (size_t i = 0; i < cont.size(); i++) - ext->addChild(createWidget(cont.at(i), ext)); + updateWidget(ext, layout); return ext; } - void destroyWidget(LuaUi::WidgetExtension* ext) - { - ext->deinitialize(); - MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget()); - } - - void updateWidget(const sol::table& layout, LuaUi::WidgetExtension* ext) + void updateWidget(WidgetExtension* ext, const sol::table& layout) { - setEventCallbacks(ext, layout); - setProperties(ext, layout); - setLayout(ext, layout); - - Content newContent = content(layout); - - size_t oldSize = ext->childCount(); - size_t newSize = newContent.size(); - size_t minSize = std::min(oldSize, newSize); - for (size_t i = 0; i < minSize; i++) - { - LuaUi::WidgetExtension* oldWidget = ext->childAt(i); - sol::table newChild = newContent.at(i); - - if (oldWidget->widget()->getTypeName() != widgetType(newChild)) - { - destroyWidget(oldWidget); - ext->assignChild(i, createWidget(newChild, ext)); - } - else - updateWidget(newChild, oldWidget); - } - - for (size_t i = minSize; i < oldSize; i++) - destroyWidget(ext->eraseChild(i)); + ext->setLayout(layout); + ext->setExternal(layout.get(LayoutKeys::external)); + setTemplate(ext, layout.get(LayoutKeys::templateLayout)); + ext->setProperties(layout.get(LayoutKeys::props)); + setEventCallbacks(ext, layout.get(LayoutKeys::events)); - for (size_t i = minSize; i < newSize; i++) - ext->addChild(createWidget(newContent.at(i), ext)); + ext->setChildren(updateContent(ext->children(), layout.get(LayoutKeys::content))); } - void setLayer(const sol::table& layout, LuaUi::WidgetExtension* ext) + void 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("layer", 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"); + throw std::logic_error(std::string("Layer ") + newLayer + " doesn't exist"); else if (newLayer != currentLayer) { MyGUI::LayerManager::getInstance().attachToLayerNode(newLayer, ext->widget()); @@ -157,8 +173,8 @@ namespace LuaUi assert(!mRoot); if (!mRoot) { - mRoot = createWidget(mLayout, nullptr); - setLayer(mLayout, mRoot); + mRoot = createWidget(mLayout); + setLayer(mRoot, mLayout); } } @@ -166,8 +182,8 @@ namespace LuaUi { if (mRoot && mUpdate) { - updateWidget(mLayout, mRoot); - setLayer(mLayout, mRoot); + updateWidget(mRoot, mLayout); + setLayer(mRoot, mLayout); } mUpdate = false; } diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp new file mode 100644 index 0000000000..c8fdafbb66 --- /dev/null +++ b/components/lua_ui/image.cpp @@ -0,0 +1,51 @@ +#include "image.hpp" + +#include + +namespace LuaUi +{ + void LuaTileRect::_setAlign(const MyGUI::IntSize& _oldsize) + { + mCurrentCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight()); + mAlign = MyGUI::Align::Stretch; + MyGUI::TileRect::_setAlign(_oldsize); + mTileSize = mSetTileSize; + + // zero tilesize stands for not tiling + if (mTileSize.width == 0) + mTileSize.width = mCoord.width; + if (mTileSize.height == 0) + mTileSize.height = mCoord.height; + + // mCoord could be zero, prevent division by 0 + // use arbitrary large numbers to prevent performance issues + if (mTileSize.width == 0) + mTileSize.width = 1e7; + if (mTileSize.height == 0) + mTileSize.height = 1e7; + } + + LuaImage::LuaImage() + { + changeWidgetSkin("LuaImage"); + mTileRect = dynamic_cast(getSubWidgetMain()); + } + + void LuaImage::updateProperties() + { + setImageTexture(propertyValue("path", std::string())); + + bool tileH = propertyValue("tileH", false); + bool tileV = propertyValue("tileV", false); + MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(_getTextureName()); + MyGUI::IntSize textureSize; + if (texture != nullptr) + textureSize = MyGUI::IntSize(texture->getWidth(), texture->getHeight()); + mTileRect->updateSize(MyGUI::IntSize( + tileH ? textureSize.width : 0, + tileV ? textureSize.height : 0 + )); + + WidgetExtension::updateProperties(); + } +} diff --git a/components/lua_ui/image.hpp b/components/lua_ui/image.hpp new file mode 100644 index 0000000000..4073d1bd2a --- /dev/null +++ b/components/lua_ui/image.hpp @@ -0,0 +1,37 @@ +#ifndef OPENMW_LUAUI_IMAGE +#define OPENMW_LUAUI_IMAGE + +#include +#include + +#include "widget.hpp" + +namespace LuaUi +{ + class LuaTileRect : public MyGUI::TileRect + { + MYGUI_RTTI_DERIVED(LuaTileRect) + + public: + void _setAlign(const MyGUI::IntSize& _oldsize) override; + + void updateSize(MyGUI::IntSize tileSize) { mSetTileSize = tileSize; } + + protected: + MyGUI::IntSize mSetTileSize; + }; + + class LuaImage : public MyGUI::ImageBox, public WidgetExtension + { + MYGUI_RTTI_DERIVED(LuaImage) + + public: + LuaImage(); + + protected: + virtual void updateProperties() override; + LuaTileRect* mTileRect; + }; +} + +#endif // OPENMW_LUAUI_IMAGE diff --git a/components/lua_ui/properties.hpp b/components/lua_ui/properties.hpp index 7f23a5ce6c..7c47291b84 100644 --- a/components/lua_ui/properties.hpp +++ b/components/lua_ui/properties.hpp @@ -9,56 +9,82 @@ namespace LuaUi { - template - sol::optional getProperty(sol::object from, std::string_view field) { - sol::object value = LuaUtil::getFieldOrNil(from, field); - if (value == sol::nil) - return sol::nullopt; - if (value.is()) - return value.as(); - std::string error("Property \""); - error += field; - error += "\" has an invalid value \""; - error += LuaUtil::toString(value); - error += "\""; - throw std::logic_error(error); + template + constexpr bool isMyGuiVector() { + return + std::is_same() || + std::is_same() || + std::is_same() || + std::is_same(); } - template - T parseProperty(sol::object from, std::string_view field, const T& defaultValue) + template + sol::optional parseValue( + sol::object table, + std::string_view field, + std::string_view errorPrefix) { - sol::optional opt = getProperty(from, field); - if (opt.has_value()) - return opt.value(); + sol::object opt = LuaUtil::getFieldOrNil(table, field); + if (opt != sol::nil && !opt.is()) + { + std::string error(errorPrefix); + error += " \""; + error += field; + error += "\" has an invalid value \""; + error += LuaUtil::toString(opt); + error += "\""; + throw std::logic_error(error); + } + if (!opt.is()) + return sol::nullopt; + + LuaT luaT = opt.as(); + if constexpr (isMyGuiVector()) + return T(luaT.x(), luaT.y()); else - return defaultValue; + return luaT; } template - MyGUI::types::TPoint parseProperty( - sol::object from, + sol::optional parseValue( + sol::object table, std::string_view field, - const MyGUI::types::TPoint& defaultValue) + std::string_view errorPrefix) { - auto v = getProperty(from, field); - if (v.has_value()) - return MyGUI::types::TPoint(v.value().x(), v.value().y()); + if constexpr (isMyGuiVector()) + return parseValue(table, field, errorPrefix); else - return defaultValue; + return parseValue(table, field, errorPrefix); } template - MyGUI::types::TSize parseProperty( - sol::object from, + T parseProperty( + sol::object props, + sol::object templateProps, std::string_view field, - const MyGUI::types::TSize& defaultValue) + const T& defaultValue) { - auto v = getProperty(from, field); - if (v.has_value()) - return MyGUI::types::TSize(v.value().x(), v.value().y()); + auto propOptional = parseValue(props, field, "Property"); + auto templateOptional = parseValue(templateProps, field, "Template property"); + + if (propOptional.has_value()) + return propOptional.value(); + else if (templateOptional.has_value()) + return templateOptional.value(); else return defaultValue; } + + template + T parseExternal( + sol::object external, + std::string_view field, + const T& defaultValue) + { + auto optional = parseValue(external, field, "External value"); + + return optional.value_or(defaultValue); + } } #endif // !OPENMW_LUAUI_PROPERTIES diff --git a/components/lua_ui/text.cpp b/components/lua_ui/text.cpp index f93f8eecf0..47e2b574cc 100644 --- a/components/lua_ui/text.cpp +++ b/components/lua_ui/text.cpp @@ -5,13 +5,22 @@ namespace LuaUi { LuaText::LuaText() : mAutoSized(true) - {} + { + changeWidgetSkin("NormalText"); + } + + void LuaText::updateProperties() + { + setCaption(propertyValue("caption", std::string())); + mAutoSized = propertyValue("autoSize", true); + WidgetExtension::updateProperties(); + } - void LuaText::setProperties(sol::object props) + void LuaText::setCaption(const MyGUI::UString& caption) { - setCaption(parseProperty(props, "caption", std::string())); - mAutoSized = parseProperty(props, "autoSize", true); - WidgetExtension::setProperties(props); + MyGUI::TextBox::setCaption(caption); + if (mAutoSized) + updateCoord(); } MyGUI::IntSize LuaText::calculateSize() diff --git a/components/lua_ui/text.hpp b/components/lua_ui/text.hpp index 533dc4f453..c435f6687a 100644 --- a/components/lua_ui/text.hpp +++ b/components/lua_ui/text.hpp @@ -13,7 +13,8 @@ namespace LuaUi public: LuaText(); - virtual void setProperties(sol::object) override; + virtual void updateProperties() override; + void setCaption(const MyGUI::UString& caption) override; private: bool mAutoSized; diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index 9cdc716ce4..5fbdf3e6d8 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -2,9 +2,10 @@ namespace LuaUi { - void LuaTextEdit::setProperties(sol::object props) + void LuaTextEdit::updateProperties() { - setCaption(parseProperty(props, "caption", std::string())); - WidgetExtension::setProperties(props); + setCaption(propertyValue("caption", std::string())); + + WidgetExtension::updateProperties(); } } diff --git a/components/lua_ui/textedit.hpp b/components/lua_ui/textedit.hpp index d14f54d659..dfc649994a 100644 --- a/components/lua_ui/textedit.hpp +++ b/components/lua_ui/textedit.hpp @@ -11,7 +11,7 @@ namespace LuaUi { MYGUI_RTTI_DERIVED(LuaTextEdit) - virtual void setProperties(sol::object) override; + virtual void updateProperties() override; }; } diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index 8cadbc9cc2..1e13c97a80 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -6,6 +6,7 @@ #include "text.hpp" #include "textedit.hpp" #include "window.hpp" +#include "image.hpp" #include "element.hpp" @@ -18,6 +19,8 @@ namespace LuaUi MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("BasisSkin"); } const std::unordered_map& widgetTypeToName() @@ -27,6 +30,7 @@ namespace LuaUi { "LuaText", "Text" }, { "LuaTextEdit", "TextEdit" }, { "LuaWindow", "Window" }, + { "LuaImage", "Image" }, }; return types; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 50c6a193e6..027adc44ca 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -16,22 +16,15 @@ namespace LuaUi , mAnchor() , mLua{ nullptr } , mWidget{ nullptr } + , mSlot(this) , mLayout{ sol::nil } {} - void WidgetExtension::triggerEvent(std::string_view name, const sol::object& argument = sol::nil) const - { - auto it = mCallbacks.find(name); - if (it != mCallbacks.end()) - it->second(argument, mLayout); - } - void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self) { mLua = lua; mWidget = self; - - mWidget->eventChangeCoord += MyGUI::newDelegate(this, &WidgetExtension::updateChildrenCoord); + updateTemplate(); initialize(); } @@ -71,8 +64,56 @@ namespace LuaUi mWidget->eventKeySetFocus.clear(); mWidget->eventKeyLostFocus.clear(); - for (WidgetExtension* child : mContent) - child->deinitialize(); + for (WidgetExtension* w : mChildren) + w->deinitialize(); + for (WidgetExtension* w : mTemplateChildren) + w->deinitialize(); + } + + void WidgetExtension::attach(WidgetExtension* ext) + { + ext->widget()->attachToWidget(mSlot->widget()); + ext->updateCoord(); + } + + WidgetExtension* WidgetExtension::findFirst(std::string_view flagName) + { + if (externalValue(flagName, false)) + return this; + for (WidgetExtension* w : mChildren) + { + WidgetExtension* result = w->findFirst(flagName); + if (result != nullptr) + return result; + } + return nullptr; + } + + void WidgetExtension::findAll(std::string_view flagName, std::vector& result) + { + if (externalValue(flagName, false)) + result.push_back(this); + for (WidgetExtension* w : mChildren) + w->findAll(flagName, result); + } + + WidgetExtension* WidgetExtension::findFirstInTemplates(std::string_view flagName) + { + for (WidgetExtension* w : mTemplateChildren) + { + WidgetExtension* result = w->findFirst(flagName); + if (result != nullptr) + return result; + } + return nullptr; + } + + std::vector WidgetExtension::findAllInTemplates(std::string_view flagName) + { + std::vector result; + for (WidgetExtension* w : mTemplateChildren) + w->findAll(flagName, result); + return result; } sol::table WidgetExtension::makeTable() const @@ -91,9 +132,9 @@ namespace LuaUi sol::object WidgetExtension::mouseEvent(int left, int top, MyGUI::MouseButton button = MyGUI::MouseButton::None) const { - auto position = osg::Vec2f(left, top); - auto absolutePosition = mWidget->getAbsolutePosition(); - auto offset = position - osg::Vec2f(absolutePosition.left, absolutePosition.top); + osg::Vec2f position(left, top); + MyGUI::IntPoint absolutePosition = mWidget->getAbsolutePosition(); + osg::Vec2f offset = position - osg::Vec2f(absolutePosition.left, absolutePosition.top); sol::table table = makeTable(); table["position"] = position; table["offset"] = offset; @@ -101,31 +142,36 @@ namespace LuaUi return table; } - void WidgetExtension::addChild(WidgetExtension* ext) - { - mContent.push_back(ext); - } - - WidgetExtension* WidgetExtension::childAt(size_t index) const + void WidgetExtension::setChildren(const std::vector& children) { - return mContent.at(index); + mChildren.resize(children.size()); + for (size_t i = 0; i < children.size(); ++i) + { + mChildren[i] = children[i]; + attach(mChildren[i]); + } } - void WidgetExtension::assignChild(size_t index, WidgetExtension* ext) + void WidgetExtension::setTemplateChildren(const std::vector& children) { - if (mContent.size() <= index) - throw std::logic_error("Invalid widget child index"); - mContent[index] = ext; + mTemplateChildren.resize(children.size()); + for (size_t i = 0; i < children.size(); ++i) + { + mTemplateChildren[i] = children[i]; + mTemplateChildren[i]->widget()->attachToWidget(mWidget); + } + updateTemplate(); } - WidgetExtension* WidgetExtension::eraseChild(size_t index) + void WidgetExtension::updateTemplate() { - if (mContent.size() <= index) - throw std::logic_error("Invalid widget child index"); - auto it = mContent.begin() + index; - WidgetExtension* ext = *it; - mContent.erase(it); - return ext; + WidgetExtension* oldSlot = mSlot; + mSlot = findFirstInTemplates("slot"); + if (mSlot == nullptr) + mSlot = this; + if (mSlot != oldSlot) + for (WidgetExtension* w : mChildren) + attach(w); } void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback) @@ -150,25 +196,39 @@ namespace LuaUi void WidgetExtension::updateCoord() { - mWidget->setCoord(calculateCoord()); + MyGUI::IntCoord oldCoord = mWidget->getCoord(); + MyGUI::IntCoord newCoord = calculateCoord(); + + if (oldCoord != newCoord) + mWidget->setCoord(newCoord); + if (oldCoord.size() != newCoord.size()) + updateChildrenCoord(); } void WidgetExtension::setProperties(sol::object props) { - mAbsoluteCoord = parseProperty(props, "position", MyGUI::IntPoint()); - mAbsoluteCoord = parseProperty(props, "size", MyGUI::IntSize()); - mRelativeCoord = parseProperty(props, "relativePosition", MyGUI::FloatPoint()); - mRelativeCoord = parseProperty(props, "relativeSize", MyGUI::FloatSize()); - mAnchor = parseProperty(props, "anchor", MyGUI::FloatSize()); - mWidget->setVisible(parseProperty(props, "visible", true)); - + mProperties = props; + updateProperties(); updateCoord(); } - void WidgetExtension::updateChildrenCoord(MyGUI::Widget* _widget) + void WidgetExtension::updateProperties() + { + mAbsoluteCoord = propertyValue("position", MyGUI::IntPoint()); + mAbsoluteCoord = propertyValue("size", MyGUI::IntSize()); + mRelativeCoord = propertyValue("relativePosition", MyGUI::FloatPoint()); + mRelativeCoord = propertyValue("relativeSize", MyGUI::FloatSize()); + mAnchor = propertyValue("anchor", MyGUI::FloatSize()); + mWidget->setVisible(propertyValue("visible", true)); + mWidget->setPointer(propertyValue("pointer", std::string("arrow"))); + } + + void WidgetExtension::updateChildrenCoord() { - for (auto& child : mContent) - child->updateCoord(); + for (WidgetExtension* w : mTemplateChildren) + w->updateCoord(); + for (WidgetExtension* w : mChildren) + w->updateCoord(); } MyGUI::IntSize WidgetExtension::calculateSize() @@ -199,6 +259,13 @@ namespace LuaUi return newCoord; } + void WidgetExtension::triggerEvent(std::string_view name, const sol::object& argument = sol::nil) const + { + auto it = mCallbacks.find(name); + if (it != mCallbacks.end()) + it->second(argument, mLayout); + } + void WidgetExtension::keyPress(MyGUI::Widget*, MyGUI::KeyCode code, MyGUI::Char ch) { if (code == MyGUI::KeyCode::None) diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index f8ba105be5..4085963137 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -26,35 +26,57 @@ namespace LuaUi // must be called after before destroying the underlying MyGUI::Widget virtual void deinitialize(); - void addChild(WidgetExtension* ext); - WidgetExtension* childAt(size_t index) const; - void assignChild(size_t index, WidgetExtension* ext); - WidgetExtension* eraseChild(size_t index); - size_t childCount() const { return mContent.size(); } - MyGUI::Widget* widget() const { return mWidget; } + const std::vector& children() { return mChildren; } + void setChildren(const std::vector&); + + const std::vector& templateChildren() { return mTemplateChildren; } + void setTemplateChildren(const std::vector&); + void setCallback(const std::string&, const LuaUtil::Callback&); void clearCallbacks(); - virtual void setProperties(sol::object); + void setProperties(sol::object); + void setTemplateProperties(sol::object props) { mTemplateProperties = props; } + + void setExternal(sol::object external) { mExternal = external; } MyGUI::IntCoord forcedCoord(); void setForcedCoord(const MyGUI::IntCoord& offset); void updateCoord(); + const sol::table& getLayout() { return mLayout; } void setLayout(const sol::table& layout) { mLayout = layout; } + template + T externalValue(std::string_view name, const T& defaultValue) + { + return parseExternal(mExternal, name, defaultValue); + } + protected: virtual void initialize(); sol::table makeTable() const; sol::object keyEvent(MyGUI::KeyCode) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; - + virtual MyGUI::IntSize calculateSize(); virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); MyGUI::IntCoord calculateCoord(); + template + T propertyValue(std::string_view name, const T& defaultValue) + { + return parseProperty(mProperties, mTemplateProperties, name, defaultValue); + } + + WidgetExtension* findFirstInTemplates(std::string_view flagName); + std::vector findAllInTemplates(std::string_view flagName); + + virtual void updateTemplate(); + virtual void updateProperties(); + void triggerEvent(std::string_view name, const sol::object& argument) const; // offsets the position and size, used only in C++ widget code @@ -71,12 +93,21 @@ namespace LuaUi // use lua_State* instead of sol::state_view because MyGUI requires a default constructor lua_State* mLua; MyGUI::Widget* mWidget; - - std::vector mContent; + std::vector mChildren; + std::vector mTemplateChildren; + WidgetExtension* mSlot; std::map> mCallbacks; sol::table mLayout; + sol::object mProperties; + sol::object mTemplateProperties; + sol::object mExternal; + + void attach(WidgetExtension* ext); + + WidgetExtension* findFirst(std::string_view name); + void findAll(std::string_view flagName, std::vector& result); - void updateChildrenCoord(MyGUI::Widget*); + void updateChildrenCoord(); void keyPress(MyGUI::Widget*, MyGUI::KeyCode, MyGUI::Char); void keyRelease(MyGUI::Widget*, MyGUI::KeyCode); diff --git a/components/lua_ui/window.cpp b/components/lua_ui/window.cpp index 5d34bf8212..6e6b80bf22 100644 --- a/components/lua_ui/window.cpp +++ b/components/lua_ui/window.cpp @@ -11,46 +11,40 @@ namespace LuaUi , mMoveResize() {} - void LuaWindow::initialize() + void LuaWindow::updateTemplate() { - WidgetExtension::initialize(); - - assignWidget(mCaption, "Caption"); - if (mCaption) + for (auto& [w, _] : mActionWidgets) { - mCaption->eventMouseButtonPressed += MyGUI::newDelegate(this, &LuaWindow::notifyMousePress); - mCaption->eventMouseDrag += MyGUI::newDelegate(this, &LuaWindow::notifyMouseDrag); - } - for (auto w : getSkinWidgetsByName("Action")) - { - w->eventMouseButtonPressed += MyGUI::newDelegate(this, &LuaWindow::notifyMousePress); - w->eventMouseDrag += MyGUI::newDelegate(this, &LuaWindow::notifyMouseDrag); + w->eventMouseButtonPressed.clear(); + w->eventMouseDrag.m_event.clear(); } - } + mActionWidgets.clear(); - void LuaWindow::deinitialize() - { - WidgetExtension::deinitialize(); + WidgetExtension* captionWidget = findFirstInTemplates("caption"); + mCaption = dynamic_cast(captionWidget); if (mCaption) + mActionWidgets.emplace(mCaption->widget(), mCaption); + for (WidgetExtension* ext : findAllInTemplates("action")) + mActionWidgets.emplace(ext->widget(), ext); + + for (auto& [w, _] : mActionWidgets) { - mCaption->eventMouseButtonPressed.clear(); - mCaption->eventMouseDrag.m_event.clear(); - } - for (auto w : getSkinWidgetsByName("Action")) - { - w->eventMouseButtonPressed.clear(); - w->eventMouseDrag.m_event.clear(); + w->eventMouseButtonPressed += MyGUI::newDelegate(this, &LuaWindow::notifyMousePress); + w->eventMouseDrag += MyGUI::newDelegate(this, &LuaWindow::notifyMouseDrag); } + + WidgetExtension::updateTemplate(); } - void LuaWindow::setProperties(sol::object props) + void LuaWindow::updateProperties() { if (mCaption) - mCaption->setCaption(parseProperty(props, "caption", std::string())); + mCaption->setCaption(propertyValue("caption", std::string())); mMoveResize = MyGUI::IntCoord(); setForcedCoord(mMoveResize); - WidgetExtension::setProperties(props); + + WidgetExtension::updateProperties(); } void LuaWindow::notifyMousePress(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) @@ -61,10 +55,11 @@ namespace LuaUi mPreviousMouse.left = left; mPreviousMouse.top = top; - if (sender->isUserString("Scale")) - mChangeScale = MyGUI::IntCoord::parse(sender->getUserString("Scale")); - else - mChangeScale = MyGUI::IntCoord(1, 1, 0, 0); + WidgetExtension* ext = mActionWidgets[sender]; + + mChangeScale = MyGUI::IntCoord( + ext->externalValue("move", MyGUI::IntPoint(1, 1)), + ext->externalValue("resize", MyGUI::IntSize(0, 0))); } void LuaWindow::notifyMouseDrag(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) diff --git a/components/lua_ui/window.hpp b/components/lua_ui/window.hpp index f34779c8f7..be3b3e2c23 100644 --- a/components/lua_ui/window.hpp +++ b/components/lua_ui/window.hpp @@ -3,9 +3,8 @@ #include -#include - #include "widget.hpp" +#include "text.hpp" namespace LuaUi { @@ -15,20 +14,18 @@ namespace LuaUi public: LuaWindow(); - virtual void setProperties(sol::object) override; + virtual void updateTemplate() override; + virtual void updateProperties() override; private: - // \todo replace with LuaText when skins are properly implemented - MyGUI::TextBox* mCaption; + LuaText* mCaption; + std::map mActionWidgets; MyGUI::IntPoint mPreviousMouse; MyGUI::IntCoord mChangeScale; MyGUI::IntCoord mMoveResize; protected: - virtual void initialize() override; - virtual void deinitialize() override; - void notifyMousePress(MyGUI::Widget*, int, int, MyGUI::MouseButton); void notifyMouseDrag(MyGUI::Widget*, int, int, MyGUI::MouseButton); }; diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index 73ca57d1a7..13420d318c 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -22,7 +22,9 @@ Every widget is defined by a layout, which is a Lua table with the following fie 4. `content`: a Content (`openmw.ui.content`), which contains layouts for the children of this widget. 5. | `name`: an arbitrary string, the only limitatiion is it being unique within a `Content`. | Helpful for navigatilng through the layouts. -6. `layer`: only applies for the root widget. +6. `layer`: only applies for the root widget. (Windows, HUD, etc) +7. `template`: a Lua table which pre-defines a layout for this widget. See Templates below for more details. +8. `external`: similar to properties, but they affect how other widgets interact with this one. See the widget pages for details. Layers ------ @@ -57,7 +59,14 @@ A container holding all the widget's children. It has a few important difference | While there is nothing preventing you from changing the `name` of a table inside a content, it is not supported, and will lead to undefined behaviour. | If you have to change the name, assign a new table to the index instead. -.. TODO: Talk about skins/templates here when they are ready +Templates +--------- + +Templates are Lua tables with the following (optional) fields: + +1. `props`: Same as in layouts, defines the behaviour of this widget. Can be overwritten by `props` values in the layout. +2. | `content`: Extra children to add to the widget. For example, the frame and caption for Window widgets. + | Contains normal layouts Events ------ @@ -97,7 +106,7 @@ Example local layout = { layers = 'Windows', type = ui.TYPE.Window, - skin = 'MW_Window', -- TODO: replace all skins here when they are properly implemented + template = { skin = 'MW_Window' }, -- TODO: replace all skins here when they are re-implemented in Lua props = { size = v2(200, 250), -- put the window in the middle of the screen @@ -107,7 +116,7 @@ Example content = ui.content { { type = ui.TYPE.Text, - skin = 'SandText', + template = { skin = 'SandText' }, props = { caption = 'Input password', relativePosition = v2(0.5, 0), @@ -117,7 +126,7 @@ Example { name = 'input', type = ui.TYPE.TextEdit, - skin = "MW_TextEdit", + template = { skin = "MW_TextEdit" }, props = { caption = '', relativePosition = v2(0.5, 0.5), @@ -129,7 +138,7 @@ Example { name = 'submit', type = ui.TYPE.Text, -- TODO: replace with button when implemented - skin = "MW_Button", + template = { skin = "MW_Button" }, props = { caption = 'Submit', -- position at the bottom diff --git a/docs/source/reference/lua-scripting/widgets/widget.rst b/docs/source/reference/lua-scripting/widgets/widget.rst index 49058ee278..36cc0917d9 100644 --- a/docs/source/reference/lua-scripting/widgets/widget.rst +++ b/docs/source/reference/lua-scripting/widgets/widget.rst @@ -33,6 +33,8 @@ Properties - boolean (true) - Defines if the widget is visible +.. TODO: document the mouse pointer property, when API for reading / adding pointer types is available + Events ------ @@ -75,3 +77,18 @@ Events * - textInput - string - Text input with this widget in focus + +External +-------- +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type (default value) + - description + * - slot + - bool (false) + - | Only applies for template content (ignored in layout content). + | If true, all the widgets defined in layout content will be rendered as children of this widget. + | Only one widget per template can have slot = true (others will be ignored). \ No newline at end of file diff --git a/files/mygui/core.skin b/files/mygui/core.skin index ee9135554e..5cf02a99e5 100644 --- a/files/mygui/core.skin +++ b/files/mygui/core.skin @@ -15,4 +15,8 @@ + + + +