From c865114b9b74fde4a38ed47ef4cb0b0b1e0f589d Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 14 Dec 2021 17:38:06 +0000 Subject: [PATCH] Lua UI Layers --- apps/openmw/mwlua/uibindings.cpp | 64 ++++++++++++++++++- components/CMakeLists.txt | 2 +- components/lua/luastate.hpp | 11 ++++ components/lua_ui/element.cpp | 22 ++++++- components/lua_ui/layers.hpp | 55 ++++++++++++++++ .../lua-scripting/user_interface.rst | 17 ++++- files/lua_api/openmw/ui.lua | 25 ++++++++ 7 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 components/lua_ui/layers.hpp diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 9982b857c8..987304d4c3 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -1,6 +1,7 @@ -#include #include #include +#include +#include #include "context.hpp" #include "actions.hpp" @@ -81,6 +82,41 @@ namespace MWLua 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"); @@ -175,6 +211,32 @@ namespace MWLua return element; }; + sol::table layers = context.mLua->newTable(); + layers[sol::meta_function::length] = []() + { + return LuaUi::Layers::size(); + }; + layers[sol::meta_function::index] = [](size_t index) + { + index = fromLuaIndex(index); + return LuaUi::Layers::at(index); + }; + layers["indexOf"] = [](std::string_view name) -> sol::optional + { + size_t index = LuaUi::Layers::indexOf(name); + if (index == LuaUi::Layers::size()) + return sol::nullopt; + else + return toLuaIndex(index); + }; + layers["insertAfter"] = [context](std::string_view afterName, std::string_view name, const sol::object& opt) + { + LuaUi::Layers::Options options; + options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); + context.mLuaManager->addAction(std::make_unique(name, afterName, options, context.mLua)); + }; + api["layers"] = LuaUtil::makeReadOnly(layers); + sol::table typeTable = context.mLua->newTable(); for (const auto& it : LuaUi::widgetTypeToName()) typeTable.set(it.second, it.first); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f969ac1265..3b38489d7a 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -162,7 +162,7 @@ add_component_dir (queries ) add_component_dir (lua_ui - widget widgetlist element content + widget widgetlist element layers content text textedit window ) diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index 71cdb4f75d..32c180c987 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -119,6 +119,17 @@ namespace LuaUtil // String representation of a Lua object. Should be used for debugging/logging purposes only. std::string toString(const sol::object&); + template + T getValueOrDefault(const sol::object& obj, const T& defaultValue) + { + if (obj == sol::nil) + return defaultValue; + if (obj.is()) + return obj.as(); + else + throw std::logic_error(std::string("Value \"") + toString(obj) + std::string("\" has unexpected type")); + } + // Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata. // Needed to forbid any changes in common resources that can be accessed from different sandboxes. sol::table makeReadOnly(sol::table); diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 63ae0e7d5e..5147068a7e 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -55,7 +55,6 @@ namespace LuaUi { std::string type = widgetType(layout); std::string skin = layout.get_or("skin", std::string()); - std::string layer = layout.get_or("layer", std::string("Windows")); std::string name = layout.get_or("name", std::string()); static auto widgetTypeMap = widgetTypeToName(); @@ -65,7 +64,7 @@ namespace LuaUi MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT( type, skin, MyGUI::IntCoord(), MyGUI::Align::Default, - layer, name); + std::string(), name); LuaUi::WidgetExtension* ext = dynamic_cast(widget); if (!ext) @@ -124,17 +123,36 @@ namespace LuaUi ext->addChild(createWidget(newContent.at(i), ext)); } + void setLayer(const sol::table& layout, LuaUi::WidgetExtension* ext) + { + MyGUI::ILayer* layerNode = ext->widget()->getLayer(); + std::string currentLayer = layerNode ? layerNode->getName() : std::string(); + std::string newLayer = layout.get_or("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()); + } + } + void Element::create() { assert(!mRoot); if (!mRoot) + { mRoot = createWidget(mLayout, nullptr); + setLayer(mLayout, mRoot); + } } void Element::update() { if (mRoot && mUpdate) + { updateWidget(mLayout, mRoot); + setLayer(mLayout, mRoot); + } mUpdate = false; } diff --git a/components/lua_ui/layers.hpp b/components/lua_ui/layers.hpp new file mode 100644 index 0000000000..6fe7fc9c16 --- /dev/null +++ b/components/lua_ui/layers.hpp @@ -0,0 +1,55 @@ +#ifndef OPENMW_LUAUI_LAYERS +#define OPENMW_LUAUI_LAYERS + +#include +#include + +#include +#include + +namespace LuaUi +{ + namespace Layers + { + struct Options { + bool mInteractive; + }; + + size_t size() + { + return MyGUI::LayerManager::getInstance().getLayerCount(); + } + + std::string at(size_t index) + { + if (index >= size()) + throw std::logic_error("Invalid layer index"); + return MyGUI::LayerManager::getInstance().getLayer(index)->getName(); + } + + size_t indexOf(std::string_view name) + { + for (size_t i = 0; i < size(); i++) + if (at(i) == name) + return i; + return size(); + } + + void insert(size_t index, std::string_view name, Options options) + { + if (index > size()) + throw std::logic_error("Invalid layer index"); + if (indexOf(name) < size()) + Log(Debug::Error) << "Layer \"" << name << "\" already exists"; + else + { + auto layer = MyGUI::LayerManager::getInstance() + .createLayerAt(std::string(name), "OverlappedLayer", index); + auto overlappedLayer = dynamic_cast(layer); + overlappedLayer->setPick(options.mInteractive); + } + } + } +} + +#endif // OPENMW_LUAUI_LAYERS diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index 88233dceb9..73ca57d1a7 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -22,9 +22,22 @@ 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. (Windows, HUD, etc) +6. `layer`: only applies for the root widget. -.. TODO: Write a more detailed documentation for layers when they are finished +Layers +------ +Layers control how widgets overlap - layers with higher indexes cover render over layers with lower indexes. +Widgets within the same layer which were added later overlap the ones created earlier. +A layer can also be set as non-interactive, which prevents all mouse interactions with the widgets in that layer. + +.. TODO: Move this list when layers are de-hardcoded + +Pre-defined OpenMW layers: + +1. `HUD` interactive +2. `Windows` interactive +3. `Notification` non-interactive +4. `MessageBox` interactive Elements -------- diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 593cbf8043..c60a4e1abd 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -8,6 +8,10 @@ --- -- @field [parent=#ui] #WIDGET_TYPE WIDGET_TYPE +--- +-- Tools for working with layers +-- @field [parent=#ui] #Layers layers + --- -- @type WIDGET_TYPE -- @field [parent=#WIDGET_TYPE] Widget Base widget type @@ -40,6 +44,27 @@ -- @field #table events Optional table of event callbacks -- @field #Content content Optional @{openmw.ui#Content} of children layouts +--- +-- Layers +-- @type Layers +-- @usage +-- ui.layers.insertAfter('HUD', 'NewLayer', { interactive = true }) +-- local fourthLayerName = ui.layers[4] +-- local windowsIndex = ui.layers.indexOf('Windows') + +--- +-- Index of the layer with the givent name. Returns nil if the layer doesn't exist +-- @function [parent=#Layers] indexOf +-- @param #string name Name of the layer +-- @return #number, #nil index + +--- +-- Creates a layer and inserts it after another layer (shifts indexes of some other layers). +-- @function [parent=#Layers] insertAfter +-- @param #string afterName Name of the layer after which the new layer will be inserted +-- @param #string name Name of the new layer +-- @param #table options Table with a boolean `interactive` field (default is true). Layers with interactive = false will ignore all mouse interactions. + --- -- Content. An array-like container, which allows to reference elements by their name -- @type Content