diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 0dcd72d359..716612f745 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -26,7 +26,10 @@ namespace MWLua { - LuaManager::LuaManager(const VFS::Manager* vfs, const std::string& libsDir) : mLua(vfs, &mConfiguration), mI18n(vfs, &mLua) + LuaManager::LuaManager(const VFS::Manager* vfs, const std::string& libsDir) + : mLua(vfs, &mConfiguration) + , mUiResourceManager(vfs) + , mI18n(vfs, &mLua) { Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion(); mLua.addInternalLibSearchPath(libsDir); @@ -248,6 +251,7 @@ namespace MWLua void LuaManager::clear() { LuaUi::clearUserInterface(); + mUiResourceManager.clear(); mActiveLocalScripts.clear(); mLocalEvents.clear(); mGlobalEvents.clear(); @@ -470,7 +474,8 @@ namespace MWLua { Log(Debug::Info) << "Reload Lua"; - LuaUi::clearUserInterface(); + LuaUi::clearUserInterface(); + mUiResourceManager.clear(); mLua.dropScriptCache(); initConfiguration(); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index b292eb3c1b..fed6dc9dc7 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -8,6 +8,8 @@ #include #include +#include + #include "../mwbase/luamanager.hpp" #include "actions.hpp" @@ -89,6 +91,8 @@ namespace MWLua return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); }; } + LuaUi::ResourceManager* uiResourceManager() { return &mUiResourceManager; } + private: void initConfiguration(); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags); @@ -97,6 +101,7 @@ namespace MWLua bool mGlobalScriptsStarted = false; LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::LuaState mLua; + LuaUi::ResourceManager mUiResourceManager; LuaUtil::I18nManager mI18n; sol::table mNearbyPackage; sol::table mUserInterfacePackage; diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 248f82f28a..b384994654 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "context.hpp" #include "actions.hpp" @@ -77,46 +78,46 @@ namespace MWLua std::shared_ptr mElement; }; + class InsertLayerAction final : public Action + { + public: + InsertLayerAction(std::string_view name, std::string_view afterName, + LuaUi::Layers::Options options, LuaUtil::LuaState* state) + : Action(state) + , mName(name) + , mAfterName(afterName) + , mOptions(options) + {} + + void apply(WorldView&) const override + { + size_t index = LuaUi::Layers::indexOf(mAfterName); + if (index == LuaUi::Layers::size()) + throw std::logic_error(std::string("Layer not found")); + LuaUi::Layers::insert(index, mName, mOptions); + } + + std::string toString() const override + { + std::string result("Insert UI layer \""); + result += mName; + result += "\" after \""; + result += mAfterName; + result += "\""; + return result; + } + + private: + std::string mName; + std::string mAfterName; + LuaUi::Layers::Options mOptions; + }; + // Lua arrays index from 1 inline size_t fromLuaIndex(size_t i) { return i - 1; } inline size_t toLuaIndex(size_t i) { return i + 1; } } - class LayerAction final : public Action - { - public: - LayerAction(std::string_view name, std::string_view afterName, - LuaUi::Layers::Options options, LuaUtil::LuaState* state) - : Action(state) - , mName(name) - , mAfterName(afterName) - , mOptions(options) - {} - - void apply(WorldView&) const override - { - size_t index = LuaUi::Layers::indexOf(mAfterName); - if (index == LuaUi::Layers::size()) - throw std::logic_error(std::string("Layer not found")); - LuaUi::Layers::insert(index, mName, mOptions); - } - - std::string toString() const override - { - std::string result("Insert UI layer \""); - result += mName; - result += "\" after \""; - result += mAfterName; - result += "\""; - return result; - } - - private: - std::string mName; - std::string mAfterName; - LuaUi::Layers::Options mOptions; - }; - sol::table initUserInterfacePackage(const Context& context) { auto uiContent = context.mLua->sol().new_usertype("UiContent"); @@ -246,7 +247,7 @@ namespace MWLua { LuaUi::Layers::Options options; options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); - context.mLuaManager->addAction(std::make_unique(name, afterName, options, context.mLua)); + context.mLuaManager->addAction(std::make_unique(name, afterName, options, context.mLua)); }; { auto pairs = [layers](const sol::object&) @@ -278,6 +279,23 @@ namespace MWLua api["registerSettingsPage"] = &LuaUi::registerSettingsPage; + api["texture"] = [luaManager=context.mLuaManager](const sol::table& options) + { + LuaUi::TextureData data; + sol::object path = LuaUtil::getFieldOrNil(options, "path"); + if (path.is()) + data.mPath = path.as(); + if (data.mPath.empty()) + throw std::logic_error("Invalid texture path"); + sol::object offset = LuaUtil::getFieldOrNil(options, "offset"); + if (offset.is()) + data.mOffset = offset.as(); + sol::object size = LuaUtil::getFieldOrNil(options, "size"); + if (size.is()) + data.mSize = size.as(); + return luaManager->uiResourceManager()->registerTexture(data); + }; + return LuaUtil::makeReadOnly(api); } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 929df9b20d..e183d54a95 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -259,7 +259,7 @@ add_component_dir (queries add_component_dir (lua_ui registerscriptsettings scriptsettings - properties widget element util layers content alignment + properties widget element util layers content alignment resources adapter text textedit window image container ) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index d2074ecfc2..47dd0a3d38 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -130,8 +130,7 @@ namespace LuaUi void updateWidget(WidgetExtension* ext, const sol::table& layout) { - ext->resetSlot(); // otherwise if template gets changed, all non-template children will get destroyed - + ext->reset(); ext->setLayout(layout); ext->setExternal(layout.get(LayoutKeys::external)); setTemplate(ext, layout.get(LayoutKeys::templateLayout)); diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp index c72bac21e9..69bccac1ed 100644 --- a/components/lua_ui/image.cpp +++ b/components/lua_ui/image.cpp @@ -2,6 +2,8 @@ #include +#include "resources.hpp" + namespace LuaUi { void LuaTileRect::_setAlign(const MyGUI::IntSize& _oldsize) @@ -25,7 +27,7 @@ namespace LuaUi mTileSize.height = 1e7; } - LuaImage::LuaImage() + void LuaImage::initialize() { changeWidgetSkin("LuaImage"); mTileRect = dynamic_cast(getSubWidgetMain()); @@ -33,19 +35,37 @@ namespace LuaUi void LuaImage::updateProperties() { - setImageTexture(propertyValue("path", std::string())); + TextureResource* resource = propertyValue("resource", nullptr); + MyGUI::IntCoord atlasCoord; + if (resource) + { + atlasCoord = MyGUI::IntCoord( + static_cast(resource->mOffset.x()), + static_cast(resource->mOffset.y()), + static_cast(resource->mSize.x()), + static_cast(resource->mSize.y())); + setImageTexture(resource->mPath); + } 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 )); + if (atlasCoord.width == 0) + atlasCoord.width = textureSize.width; + if (atlasCoord.height == 0) + atlasCoord.height = textureSize.height; + setImageCoord(atlasCoord); + WidgetExtension::updateProperties(); } } diff --git a/components/lua_ui/image.hpp b/components/lua_ui/image.hpp index e841e55fdb..f7df102440 100644 --- a/components/lua_ui/image.hpp +++ b/components/lua_ui/image.hpp @@ -25,10 +25,8 @@ namespace LuaUi { MYGUI_RTTI_DERIVED(LuaImage) - public: - LuaImage(); - protected: + void initialize() override; void updateProperties() override; LuaTileRect* mTileRect; }; diff --git a/components/lua_ui/resources.cpp b/components/lua_ui/resources.cpp new file mode 100644 index 0000000000..605c9ba58b --- /dev/null +++ b/components/lua_ui/resources.cpp @@ -0,0 +1,27 @@ +#include "resources.hpp" + +#include +#include + +namespace LuaUi +{ + std::shared_ptr ResourceManager::registerTexture(TextureData data) + { + std::string normalizedPath = mVfs->normalizeFilename(data.mPath); + if (!mVfs->exists(normalizedPath)) + { + auto error = Misc::StringUtils::format("Texture with path \"%s\" doesn't exist", data.mPath); + throw std::logic_error(error); + } + data.mPath = normalizedPath; + + TextureResources& list = mTextures[normalizedPath]; + list.push_back(std::make_shared(data)); + return list.back(); + } + + void ResourceManager::clear() + { + mTextures.clear(); + } +} diff --git a/components/lua_ui/resources.hpp b/components/lua_ui/resources.hpp new file mode 100644 index 0000000000..cabcd63bf4 --- /dev/null +++ b/components/lua_ui/resources.hpp @@ -0,0 +1,45 @@ +#ifndef OPENMW_LUAUI_RESOURCES +#define OPENMW_LUAUI_RESOURCES + +#include +#include +#include +#include + +#include + +namespace VFS +{ + class Manager; +} + +namespace LuaUi +{ + struct TextureData + { + std::string mPath; + osg::Vec2f mOffset; + osg::Vec2f mSize; + }; + + // will have more/different data when automated atlasing is supported + using TextureResource = TextureData; + + class ResourceManager + { + public: + ResourceManager(const VFS::Manager* vfs) + : mVfs(vfs) + {} + + std::shared_ptr registerTexture(TextureData data); + void clear(); + + private: + const VFS::Manager* mVfs; + using TextureResources = std::vector>; + std::unordered_map mTextures; + }; +} + +#endif // OPENMW_LUAUI_LAYERS diff --git a/components/lua_ui/text.cpp b/components/lua_ui/text.cpp index 3a56ad68af..9ec31834f4 100644 --- a/components/lua_ui/text.cpp +++ b/components/lua_ui/text.cpp @@ -10,7 +10,7 @@ namespace LuaUi void LuaText::initialize() { - changeWidgetSkin("SandText"); + changeWidgetSkin("LuaText"); setEditStatic(true); setVisibleHScroll(false); setVisibleVScroll(false); diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 4364a0c362..96a6d096bd 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -24,9 +24,8 @@ namespace LuaUi { mLua = lua; mWidget = self; - updateTemplate(); - initialize(); + updateTemplate(); } void WidgetExtension::initialize() @@ -72,6 +71,13 @@ namespace LuaUi w->deinitialize(); } + void WidgetExtension::reset() + { + // detach all children from the slot widget, in case it gets destroyed + for (auto& w: mChildren) + w->widget()->detachFromWidget(); + } + void WidgetExtension::attach(WidgetExtension* ext) { ext->mParent = this; @@ -182,8 +188,15 @@ namespace LuaUi else mSlot = slot->mSlot; if (mSlot != oldSlot) - for (WidgetExtension* w : mChildren) - attach(w); + { + MyGUI::IntSize slotSize = mSlot->widget()->getSize(); + MyGUI::IntPoint slotPosition = mSlot->widget()->getAbsolutePosition() - widget()->getAbsolutePosition(); + MyGUI::IntCoord slotCoord(slotPosition, slotSize); + if (mWidget->getSubWidgetMain()) + mWidget->getSubWidgetMain()->setCoord(slotCoord); + if (mWidget->getSubWidgetText()) + mWidget->getSubWidgetText()->setCoord(slotCoord); + } } void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback) diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 9037443676..827993d47d 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -28,6 +28,9 @@ namespace LuaUi virtual void deinitialize(); MyGUI::Widget* widget() const { return mWidget; } + WidgetExtension* slot() const { return mSlot; } + + void reset(); const std::vector& children() { return mChildren; } void setChildren(const std::vector&); @@ -50,7 +53,6 @@ namespace LuaUi const sol::table& getLayout() { return mLayout; } void setLayout(const sol::table& layout) { mLayout = layout; } - void resetSlot() { mSlot = this; } template T externalValue(std::string_view name, const T& defaultValue) diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index e00aa8bf63..71ad61b410 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -80,6 +80,7 @@ Widget types Widget: Base widget type, all the other widgets inherit its properties and events. Text: Displays text. TextEdit: Accepts text input from the user. + Image: Renders a texture. Example ------- diff --git a/docs/source/reference/lua-scripting/widgets/image.rst b/docs/source/reference/lua-scripting/widgets/image.rst new file mode 100644 index 0000000000..bd68687cfe --- /dev/null +++ b/docs/source/reference/lua-scripting/widgets/image.rst @@ -0,0 +1,22 @@ +Image Widget +============ + +Properties +---------- + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type (default value) + - description + * - resource + - ui.texture + - The texture resource to display + * - tileH + - boolean (false) + - Tile the texture horizontally + * - tileV + - boolean (false) + - Tile the texture vertically diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 478b9235a5..6461588a69 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -53,7 +53,15 @@ --- -- Adds a settings page to main menu setting's Scripts tab. -- @function [parent=#ui] registerSettingsPage --- @param #SettingsPage page +-- @param #SettingsPageOptions page + +--- +-- Table with settings page options, passed as an argument to ui.registerSettingsPage +-- @type SettingsPageOptions +-- @field #string name Name of the page, displayed in the list, used for search +-- @field #string searchHints Additional keywords used in search, not displayed anywhere +-- @field #Element element The page's UI, which will be attached to the settings tab. The root widget has to have a fixed size. Set the `size` field in `props`, `relativeSize` is ignored. + --- -- Layout @@ -162,10 +170,33 @@ -- @field #number button Mouse button which triggered the event (could be nil) --- --- Settings page parameters, passed as an argument to ui.registerSettingsPage --- @type SettingsPage --- @field #string name Name of the page, displayed in the list, used for search --- @field #string searchHints Additional keywords used in search, not displayed anywhere --- @field #Element element The page's UI, which will be attached to the settings tab. The root widget has to have a fixed size (set `size` field in `props`, see Widget documentation, `relativeSize` is ignored) +-- Register a new texture resource. Can be used to manually atlas UI textures. +-- @function [parent=#ui] texture #TextureResource +-- @param #TextureResourceOptions options +-- @usage +-- local ui = require('openmw.ui') +-- local vector2 = require('openmw.util').vector2 +-- local myAtlas = 'textures/my_atlas.dds' -- a 128x128 atlas +-- local texture1 = ui.texture { -- texture in the top left corner of the atlas +-- path = myAtlas, +-- offset = vector2(0, 0), +-- size = vector2(64, 64), +-- } +-- local texture2 = ui.texture { -- texture in the top right corner of the atlas +-- path = myAtlas, +-- offset = vector2(64, 0), +-- size = vector2(64, 64), +-- } + +--- +-- A texture ready to be used by UI widgets +-- @type TextureResource + +--- +-- Table with arguments passed to ui.texture. +-- @type TextureResourceOptions +-- @field #string path Path to the texture file. Required +-- @field openmw.util#Vector2 offset Offset of this resource in the texture. (0, 0) by default +-- @field openmw.util#Vector2 size Size of the resource in the texture. (0, 0) by default. 0 means the whole texture size is used. return nil diff --git a/files/mygui/openmw_lua.xml b/files/mygui/openmw_lua.xml index cb0af2b4a9..6e5e232a2a 100644 --- a/files/mygui/openmw_lua.xml +++ b/files/mygui/openmw_lua.xml @@ -4,12 +4,20 @@ - - - - - - + + + + + + + + + + + + + + \ No newline at end of file