Merge branch 'lua_ui' into 'master'

Lua UI API

See merge request OpenMW/openmw!1138
pull/3224/head
Petr Mikheev 3 years ago
commit 4fbbb67e98

@ -53,6 +53,8 @@
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/frameratelimiter.hpp>
#include <components/lua_ui/widgetlist.hpp>
#include "../mwbase/inputmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/soundmanager.hpp"
@ -220,6 +222,7 @@ namespace MWGui
ItemWidget::registerComponents();
SpellView::registerComponents();
Gui::registerAllWidgets();
LuaUi::registerAllWidgets();
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Controllers::ControllerFollowMouse>("Controller");

@ -1,17 +1,182 @@
#include "luabindings.hpp"
#include <components/lua_ui/content.hpp>
#include <components/lua_ui/widgetlist.hpp>
#include <components/lua_ui/element.hpp>
#include "context.hpp"
#include "actions.hpp"
#include "luamanagerimp.hpp"
namespace MWLua
{
namespace
{
class UiAction final : public Action
{
public:
enum Type
{
CREATE = 0,
UPDATE,
DESTROY,
};
UiAction(Type type, std::shared_ptr<LuaUi::Element> element, LuaUtil::LuaState* state)
: Action(state)
, mType{ type }
, mElement{ std::move(element) }
{}
void apply(WorldView&) const override
{
try {
switch (mType)
{
case CREATE:
mElement->create();
break;
case UPDATE:
mElement->update();
break;
case DESTROY:
mElement->destroy();
break;
}
}
catch (std::exception& e)
{
// prevent any actions on a potentially corrupted widget
mElement->mRoot = nullptr;
throw;
}
}
std::string toString() const override
{
std::string result;
switch (mType)
{
case CREATE:
result += "Create";
break;
case UPDATE:
result += "Update";
break;
case DESTROY:
result += "Destroy";
break;
}
result += " UI";
return result;
}
private:
Type mType;
std::shared_ptr<LuaUi::Element> mElement;
};
// 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; }
}
sol::table initUserInterfacePackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
auto uiContent = context.mLua->sol().new_usertype<LuaUi::Content>("UiContent");
uiContent[sol::meta_function::length] = [](const LuaUi::Content& content)
{
return content.size();
};
uiContent[sol::meta_function::index] = sol::overload(
[](const LuaUi::Content& content, size_t index)
{
return content.at(fromLuaIndex(index));
},
[](const LuaUi::Content& content, std::string_view name)
{
return content.at(name);
});
uiContent[sol::meta_function::new_index] = sol::overload(
[](LuaUi::Content& content, size_t index, const sol::table& table)
{
content.assign(fromLuaIndex(index), table);
},
[](LuaUi::Content& content, size_t index, sol::nil_t nil)
{
content.remove(fromLuaIndex(index));
},
[](LuaUi::Content& content, std::string_view name, const sol::table& table)
{
content.assign(name, table);
},
[](LuaUi::Content& content, std::string_view name, sol::nil_t nil)
{
content.remove(name);
});
uiContent["insert"] = [](LuaUi::Content& content, size_t index, const sol::table& table)
{
content.insert(fromLuaIndex(index), table);
};
uiContent["add"] = [](LuaUi::Content& content, const sol::table& table)
{
content.insert(content.size(), table);
};
uiContent["indexOf"] = [](LuaUi::Content& content, const sol::table& table) -> sol::optional<size_t>
{
size_t index = content.indexOf(table);
if (index < content.size())
return toLuaIndex(index);
else
return sol::nullopt;
};
auto element = context.mLua->sol().new_usertype<LuaUi::Element>("Element");
element["layout"] = sol::property(
[](LuaUi::Element& element)
{
return element.mLayout;
},
[](LuaUi::Element& element, const sol::table& layout)
{
element.mLayout = layout;
}
);
element["update"] = [context](const std::shared_ptr<LuaUi::Element>& element)
{
if (element->mDestroy || element->mUpdate)
return;
element->mUpdate = true;
context.mLuaManager->addAction(std::make_unique<UiAction>(UiAction::UPDATE, element, context.mLua));
};
element["destroy"] = [context](const std::shared_ptr<LuaUi::Element>& element)
{
if (element->mDestroy)
return;
element->mDestroy = true;
context.mLuaManager->addAction(std::make_unique<UiAction>(UiAction::DESTROY, element, context.mLua));
};
sol::table api = context.mLua->newTable();
api["showMessage"] = [luaManager=context.mLuaManager](std::string_view message)
{
luaManager->addUIMessage(message);
};
api["content"] = [](const sol::table& table)
{
return LuaUi::Content(table);
};
api["create"] = [context](const sol::table& layout)
{
auto element = std::make_shared<LuaUi::Element>(layout);
context.mLuaManager->addAction(std::make_unique<UiAction>(UiAction::CREATE, element, context.mLua));
return element;
};
sol::table typeTable = context.mLua->newTable();
for (const auto& it : LuaUi::widgetTypeToName())
typeTable.set(it.second, it.first);
api["TYPE"] = LuaUtil::makeReadOnly(typeTable);
return LuaUtil::makeReadOnly(api);
}

@ -24,6 +24,8 @@ if (GTEST_FOUND AND GMOCK_FOUND)
lua/test_querypackage.cpp
lua/test_configuration.cpp
lua/test_ui_content.cpp
misc/test_stringops.cpp
misc/test_endianness.cpp
misc/test_resourcehelpers.cpp

@ -0,0 +1,97 @@
#include <gtest/gtest.h>
#include <sol/sol.hpp>
#include <components/lua_ui/content.hpp>
namespace
{
using namespace testing;
sol::state state;
sol::table makeTable()
{
return sol::table(state, sol::create);
}
sol::table makeTable(std::string name)
{
auto result = makeTable();
result["name"] = name;
return result;
}
TEST(LuaUiContentTest, Create)
{
auto table = makeTable();
table.add(makeTable());
table.add(makeTable());
table.add(makeTable());
LuaUi::Content content(table);
EXPECT_EQ(content.size(), 3);
}
TEST(LuaUiContentTest, CreateWithHole)
{
auto table = makeTable();
table.add(makeTable());
table.add(makeTable());
table[4] = makeTable();
EXPECT_ANY_THROW(LuaUi::Content content(table));
}
TEST(LuaUiContentTest, WrongType)
{
auto table = makeTable();
table.add(makeTable());
table.add("a");
table.add(makeTable());
EXPECT_ANY_THROW(LuaUi::Content content(table));
}
TEST(LuaUiContentTest, NameAccess)
{
auto table = makeTable();
table.add(makeTable());
table.add(makeTable("a"));
LuaUi::Content content(table);
EXPECT_NO_THROW(content.at("a"));
content.remove("a");
content.assign(content.size(), makeTable("b"));
content.assign("b", makeTable());
EXPECT_ANY_THROW(content.at("b"));
EXPECT_EQ(content.size(), 2);
content.assign(content.size(), makeTable("c"));
content.assign(content.size(), makeTable("c"));
content.remove("c");
EXPECT_ANY_THROW(content.at("c"));
}
TEST(LuaUiContentTest, IndexOf)
{
auto table = makeTable();
table.add(makeTable());
table.add(makeTable());
table.add(makeTable());
LuaUi::Content content(table);
auto child = makeTable();
content.assign(2, child);
EXPECT_EQ(content.indexOf(child), 2);
EXPECT_EQ(content.indexOf(makeTable()), content.size());
}
TEST(LuaUiContentTest, BoundsChecks)
{
auto table = makeTable();
LuaUi::Content content(table);
EXPECT_ANY_THROW(content.at(0));
content.assign(content.size(), makeTable());
content.assign(content.size(), makeTable());
content.assign(content.size(), makeTable());
EXPECT_ANY_THROW(content.at(3));
EXPECT_ANY_THROW(content.remove(3));
EXPECT_NO_THROW(content.remove(1));
EXPECT_NO_THROW(content.at(1));
EXPECT_EQ(content.size(), 2);
}
}

@ -160,6 +160,11 @@ add_component_dir (fallback
add_component_dir (queries
query luabindings
)
add_component_dir (lua_ui
widget widgetlist element content text textedit window
)
if(WIN32)
add_component_dir (crashcatcher

@ -0,0 +1,106 @@
#include "content.hpp"
namespace LuaUi
{
Content::Content(const sol::table& table)
{
size_t size = table.size();
for (size_t index = 0; index < size; ++index)
{
sol::object value = table.get<sol::object>(index + 1);
if (value.is<sol::table>())
assign(index, value.as<sol::table>());
else
throw std::logic_error("UI Content children must all be tables.");
}
}
void Content::assign(size_t index, const sol::table& table)
{
if (mOrdered.size() < index)
throw std::logic_error("Can't have gaps in UI Content.");
if (index == mOrdered.size())
mOrdered.push_back(table);
else
{
sol::optional<std::string> oldName = mOrdered[index]["name"];
if (oldName.has_value())
mNamed.erase(oldName.value());
mOrdered[index] = table;
}
sol::optional<std::string> name = table["name"];
if (name.has_value())
mNamed[name.value()] = index;
}
void Content::assign(std::string_view name, const sol::table& table)
{
auto it = mNamed.find(name);
if (it != mNamed.end())
assign(it->second, table);
else
throw std::logic_error(std::string("Can't find a UI Content child with name ") += name);
}
void Content::insert(size_t index, const sol::table& table)
{
size_t size = mOrdered.size();
if (size < index)
throw std::logic_error("Can't have gaps in UI Content.");
mOrdered.insert(mOrdered.begin() + index, table);
for (size_t i = index; i < size; ++i)
{
sol::optional<std::string> name = mOrdered[i]["name"];
if (name.has_value())
mNamed[name.value()] = index;
}
}
sol::table Content::at(size_t index) const
{
if (index > size())
throw std::logic_error("Invalid UI Content index.");
return mOrdered.at(index);
}
sol::table Content::at(std::string_view name) const
{
auto it = mNamed.find(name);
if (it == mNamed.end())
throw std::logic_error("Invalid UI Content name.");
return mOrdered.at(it->second);
}
size_t Content::remove(size_t index)
{
sol::table table = at(index);
sol::optional<std::string> name = table["name"];
if (name.has_value())
{
auto it = mNamed.find(name.value());
if (it != mNamed.end())
mNamed.erase(it);
}
mOrdered.erase(mOrdered.begin() + index);
return index;
}
size_t Content::remove(std::string_view name)
{
auto it = mNamed.find(name);
if (it == mNamed.end())
throw std::logic_error("Invalid UI Content name.");
size_t index = it->second;
remove(index);
return index;
}
size_t Content::indexOf(const sol::table& table)
{
auto it = std::find(mOrdered.begin(), mOrdered.end(), table);
if (it == mOrdered.end())
return size();
else
return it - mOrdered.begin();
}
}

@ -0,0 +1,41 @@
#ifndef COMPONENTS_LUAUI_CONTENT
#define COMPONENTS_LUAUI_CONTENT
#include <map>
#include <string>
#include <sol/sol.hpp>
namespace LuaUi
{
class Content
{
public:
using iterator = std::vector<sol::table>::iterator;
Content() {}
// expects a Lua array - a table with keys from 1 to n without any nil values in between
// any other keys are ignored
explicit Content(const sol::table&);
size_t size() const { return mOrdered.size(); }
void assign(std::string_view name, const sol::table& table);
void assign(size_t index, const sol::table& table);
void insert(size_t index, const sol::table& table);
sol::table at(size_t index) const;
sol::table at(std::string_view name) const;
size_t remove(size_t index);
size_t remove(std::string_view name);
size_t indexOf(const sol::table& table);
private:
std::map<std::string, size_t, std::less<>> mNamed;
std::vector<sol::table> mOrdered;
};
}
#endif // COMPONENTS_LUAUI_CONTENT

@ -0,0 +1,151 @@
#include "element.hpp"
#include <MyGUI_Gui.h>
#include "content.hpp"
#include "widgetlist.hpp"
namespace LuaUi
{
std::string widgetType(const sol::table& layout)
{
return layout.get_or("type", std::string("LuaWidget"));
}
Content content(const sol::table& layout)
{
auto optional = layout.get<sol::optional<Content>>("content");
if (optional.has_value())
return optional.value();
else
return Content();
}
void setProperties(LuaUi::WidgetExtension* ext, const sol::table& layout)
{
auto props = layout.get<sol::optional<sol::table>>("props");
if (props.has_value())
{
props.value().for_each([ext](const sol::object& key, const sol::object& value)
{
if (key.is<std::string_view>())
ext->setProperty(key.as<std::string_view>(), value);
else
Log(Debug::Warning) << "UI property key must be a string";
});
ext->updateCoord();
}
}
void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::table& layout)
{
ext->clearCallbacks();
auto events = layout.get<sol::optional<sol::table>>("events");
if (events.has_value())
{
events.value().for_each([ext](const sol::object& name, const sol::object& callback)
{
if (name.is<std::string>() && callback.is<LuaUtil::Callback>())
ext->setCallback(name.as<std::string>(), callback.as<LuaUtil::Callback>());
else if (!name.is<std::string>())
Log(Debug::Warning) << "UI event key must be a string";
else if (!callback.is<LuaUtil::Callback>())
Log(Debug::Warning) << "UI event handler for key \"" << name.as<std::string>()
<< "\" must be an openmw.async.callback";
});
}
}
LuaUi::WidgetExtension* createWidget(const sol::table& layout, LuaUi::WidgetExtension* parent)
{
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();
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,
MyGUI::IntCoord(), MyGUI::Align::Default,
layer, name);
LuaUi::WidgetExtension* ext = dynamic_cast<LuaUi::WidgetExtension*>(widget);
if (!ext)
throw std::runtime_error("Invalid widget!");
ext->create(layout.lua_state(), widget);
if (parent != nullptr)
widget->attachToWidget(parent->widget());
setEventCallbacks(ext, layout);
setProperties(ext, layout);
Content cont = content(layout);
for (size_t i = 0; i < cont.size(); i++)
ext->addChild(createWidget(cont.at(i), ext));
return ext;
}
void destroyWidget(LuaUi::WidgetExtension* ext)
{
ext->destroy();
MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget());
}
void updateWidget(const sol::table& layout, LuaUi::WidgetExtension* ext)
{
setEventCallbacks(ext, layout);
setProperties(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));
for (size_t i = minSize; i < newSize; i++)
ext->addChild(createWidget(newContent.at(i), ext));
}
void Element::create()
{
assert(!mRoot);
if (!mRoot)
mRoot = createWidget(mLayout, nullptr);
}
void Element::update()
{
if (mRoot && mUpdate)
updateWidget(mLayout, mRoot);
mUpdate = false;
}
void Element::destroy()
{
if (mRoot)
destroyWidget(mRoot);
mRoot = nullptr;
}
}

@ -0,0 +1,31 @@
#ifndef OPENMW_LUAUI_ELEMENT
#define OPENMW_LUAUI_ELEMENT
#include "widget.hpp"
namespace LuaUi
{
struct Element
{
Element(sol::table layout)
: mRoot{ nullptr }
, mLayout{ layout }
, mUpdate{ false }
, mDestroy{ false }
{
}
LuaUi::WidgetExtension* mRoot;
sol::table mLayout;
bool mUpdate;
bool mDestroy;
void create();
void update();
void destroy();
};
}
#endif // !OPENMW_LUAUI_ELEMENT

@ -0,0 +1,40 @@
#include "text.hpp"
namespace LuaUi
{
void LuaText::initialize()
{
WidgetExtension::initialize();
mAutoSized = true;
}
bool LuaText::setPropertyRaw(std::string_view name, sol::object value)
{
if (name == "caption")
{
if (!value.is<std::string>())
return false;
setCaption(value.as<std::string>());
}
else if (name == "autoSize")
{
if (!value.is<bool>())
return false;
mAutoSized = value.as<bool>();
}
else
{
return WidgetExtension::setPropertyRaw(name, value);
}
return true;
}
MyGUI::IntSize LuaText::calculateSize()
{
if (mAutoSized)
return getTextSize();
else
return WidgetExtension::calculateSize();
}
}

@ -0,0 +1,26 @@
#ifndef OPENMW_LUAUI_TEXT
#define OPENMW_LUAUI_TEXT
#include <MyGUI_TextBox.h>
#include "widget.hpp"
namespace LuaUi
{
class LuaText : public MyGUI::TextBox, public WidgetExtension
{
MYGUI_RTTI_DERIVED(LuaText)
public:
virtual void initialize() override;
private:
bool mAutoSized;
protected:
virtual MyGUI::IntSize calculateSize() override;
bool setPropertyRaw(std::string_view name, sol::object value) override;
};
}
#endif // OPENMW_LUAUI_TEXT

@ -0,0 +1,19 @@
#include "textedit.hpp"
namespace LuaUi
{
bool LuaTextEdit::setPropertyRaw(std::string_view name, sol::object value)
{
if (name == "caption")
{
if (!value.is<std::string>())
return false;
setCaption(value.as<std::string>());
}
else
{
return WidgetExtension::setPropertyRaw(name, value);
}
return true;
}
}

@ -0,0 +1,19 @@
#ifndef OPENMW_LUAUI_TEXTEDIT
#define OPENMW_LUAUI_TEXTEDIT
#include <MyGUI_EditBox.h>
#include "widget.hpp"
namespace LuaUi
{
class LuaTextEdit : public MyGUI::EditBox, public WidgetExtension
{
MYGUI_RTTI_DERIVED(LuaTextEdit)
protected:
bool setPropertyRaw(std::string_view name, sol::object value) override;
};
}
#endif // OPENMW_LUAUI_TEXTEDIT

@ -0,0 +1,307 @@
#include "widget.hpp"
#include <SDL_events.h>
#include "text.hpp"
#include "textedit.hpp"
#include "window.hpp"
namespace LuaUi
{
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);
}
void WidgetExtension::create(lua_State* lua, MyGUI::Widget* self)
{
mLua = lua;
mWidget = self;
mWidget->eventChangeCoord += MyGUI::newDelegate(this, &WidgetExtension::updateChildrenCoord);
initialize();
}
void WidgetExtension::initialize()
{
mAbsoluteCoord = MyGUI::IntCoord();
mRelativeCoord = MyGUI::FloatCoord();
mAnchor = MyGUI::FloatSize();
mForcedCoord = MyGUI::IntCoord();
// \todo might be more efficient to only register these if there are Lua callbacks
mWidget->eventKeyButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::keyPress);
mWidget->eventKeyButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::keyRelease);
mWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &WidgetExtension::mouseClick);
mWidget->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WidgetExtension::mouseDoubleClick);
mWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::mousePress);
mWidget->eventMouseButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::mouseRelease);
mWidget->eventMouseMove += MyGUI::newDelegate(this, &WidgetExtension::mouseMove);
mWidget->eventMouseDrag += MyGUI::newDelegate(this, &WidgetExtension::mouseDrag);
mWidget->eventMouseSetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain);
mWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss);
mWidget->eventKeySetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain);
mWidget->eventKeyLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss);
}
void WidgetExtension::destroy()
{
clearCallbacks();
deinitialize();
for (WidgetExtension* child : mContent)
child->destroy();
}
void WidgetExtension::deinitialize()
{
mWidget->eventKeyButtonPressed.clear();
mWidget->eventKeyButtonReleased.clear();
mWidget->eventMouseButtonClick.clear();
mWidget->eventMouseButtonDoubleClick.clear();
mWidget->eventMouseButtonPressed.clear();
mWidget->eventMouseButtonReleased.clear();
mWidget->eventMouseMove.clear();
mWidget->eventMouseDrag.m_event.clear();
mWidget->eventMouseSetFocus.clear();
mWidget->eventMouseLostFocus.clear();
mWidget->eventKeySetFocus.clear();
mWidget->eventKeyLostFocus.clear();
}
sol::table WidgetExtension::makeTable() const
{
return sol::table(mLua, sol::create);
}
sol::object WidgetExtension::keyEvent(MyGUI::KeyCode code) const
{
SDL_Keysym keySym;
// MyGUI key codes are not one to one with SDL key codes
// \todo refactor sdlmappings.cpp to map this back to SDL correctly
keySym.sym = static_cast<SDL_Keycode>(code.getValue());
keySym.scancode = SDL_GetScancodeFromKey(keySym.sym);
keySym.mod = SDL_GetModState();
return sol::make_object(mLua, keySym);
}
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);
sol::table table = makeTable();
table["position"] = position;
table["offset"] = offset;
// \todo refactor sdlmappings.cpp to map this back to SDL properly
table["button"] = button.getValue() + 1;
return table;
}
void WidgetExtension::addChild(WidgetExtension* ext)
{
mContent.push_back(ext);
}
WidgetExtension* WidgetExtension::childAt(size_t index) const
{
return mContent.at(index);
}
void WidgetExtension::assignChild(size_t index, WidgetExtension* ext)
{
if (mContent.size() <= index)
throw std::logic_error("Invalid widget child index");
mContent[index] = ext;
}
WidgetExtension* WidgetExtension::eraseChild(size_t index)
{
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;
}
void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback)
{
mCallbacks[name] = callback;
}
void WidgetExtension::clearCallbacks()
{
mCallbacks.clear();
}
void WidgetExtension::setProperty(std::string_view name, sol::object value)
{
if (!setPropertyRaw(name, value))
Log(Debug::Error) << "Invalid value of property " << name
<< ": " << LuaUtil::toString(value);
}
MyGUI::IntCoord WidgetExtension::forcedOffset()
{
return mForcedCoord;
}
void WidgetExtension::setForcedOffset(const MyGUI::IntCoord& offset)
{
mForcedCoord = offset;
}
void WidgetExtension::updateCoord()
{
mWidget->setCoord(calculateCoord());
}
bool WidgetExtension::setPropertyRaw(std::string_view name, sol::object value)
{
if (name == "position")
{
if (!value.is<osg::Vec2f>())
return false;
auto v = value.as<osg::Vec2f>();
mAbsoluteCoord.left = v.x();
mAbsoluteCoord.top = v.y();
}
else if (name == "size")
{
if (!value.is<osg::Vec2f>())
return false;
auto v = value.as<osg::Vec2f>();
mAbsoluteCoord.width = v.x();
mAbsoluteCoord.height = v.y();
}
else if (name == "relativePosition")
{
if (!value.is<osg::Vec2f>())
return false;
auto v = value.as<osg::Vec2f>();
mRelativeCoord.left = v.x();
mRelativeCoord.top = v.y();
}
else if (name == "relativeSize")
{
if (!value.is<osg::Vec2f>())
return false;
auto v = value.as<osg::Vec2f>();
mRelativeCoord.width = v.x();
mRelativeCoord.height = v.y();
}
else if (name == "anchor")
{
if (!value.is<osg::Vec2f>())
return false;
auto v = value.as<osg::Vec2f>();
mAnchor.width = v.x();
mAnchor.height = v.y();
}
else if (name == "visible")
{
if (!value.is<bool>())
return false;
mWidget->setVisible(value.as<bool>());
}
return true;
}
void WidgetExtension::updateChildrenCoord(MyGUI::Widget* _widget)
{
for (auto& child : mContent)
child->updateCoord();
}
MyGUI::IntSize WidgetExtension::calculateSize()
{
const MyGUI::IntSize& parentSize = mWidget->getParentSize();
MyGUI::IntSize newSize;
newSize = mAbsoluteCoord.size() + mForcedCoord.size();
newSize.width += mRelativeCoord.width * parentSize.width;
newSize.height += mRelativeCoord.height * parentSize.height;
return newSize;
}
MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size)
{
const MyGUI::IntSize& parentSize = mWidget->getParentSize();
MyGUI::IntPoint newPosition;
newPosition = mAbsoluteCoord.point() + mForcedCoord.point();
newPosition.left += mRelativeCoord.left * parentSize.width - mAnchor.width * size.width;
newPosition.top += mRelativeCoord.top * parentSize.height - mAnchor.height * size.height;
return newPosition;
}
MyGUI::IntCoord WidgetExtension::calculateCoord()
{
MyGUI::IntCoord newCoord;
newCoord = calculateSize();
newCoord = calculatePosition(newCoord.size());
return newCoord;
}
void WidgetExtension::keyPress(MyGUI::Widget*, MyGUI::KeyCode code, MyGUI::Char ch)
{
if (code == MyGUI::KeyCode::None)
{
// \todo decide how to handle unicode strings in Lua
MyGUI::UString uString;
uString.push_back(static_cast<MyGUI::UString::unicode_char>(ch));
triggerEvent("textInput", sol::make_object(mLua, uString.asUTF8()));
}
else
triggerEvent("keyPress", keyEvent(code));
}
void WidgetExtension::keyRelease(MyGUI::Widget*, MyGUI::KeyCode code)
{
triggerEvent("keyRelease", keyEvent(code));
}
void WidgetExtension::mouseMove(MyGUI::Widget*, int left, int top)
{
triggerEvent("mouseMove", mouseEvent(left, top));
}
void WidgetExtension::mouseDrag(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button)
{
triggerEvent("mouseMove", mouseEvent(left, top, button));
}
void WidgetExtension::mouseClick(MyGUI::Widget* _widget)
{
triggerEvent("mouseClick");
}
void WidgetExtension::mouseDoubleClick(MyGUI::Widget* _widget)
{
triggerEvent("mouseDoubleClick");
}
void WidgetExtension::mousePress(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button)
{
triggerEvent("mousePress", mouseEvent(left, top, button));
}
void WidgetExtension::mouseRelease(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button)
{
triggerEvent("mouseRelease", mouseEvent(left, top, button));
}
void WidgetExtension::focusGain(MyGUI::Widget*, MyGUI::Widget*)
{
triggerEvent("focusGain");
}
void WidgetExtension::focusLoss(MyGUI::Widget*, MyGUI::Widget*)
{
triggerEvent("focusLoss");
}
}

@ -0,0 +1,91 @@
#ifndef OPENMW_LUAUI_WIDGET
#define OPENMW_LUAUI_WIDGET
#include <map>
#include <MyGUI_Widget.h>
#include <sol/sol.hpp>
#include <osg/Vec2>
#include <components/lua/scriptscontainer.hpp>
namespace LuaUi
{
/*
* extends MyGUI::Widget and its child classes
* memory ownership is controlled by MyGUI
* it is important not to call any WidgetExtension methods after destroying the MyGUI::Widget
*/
class WidgetExtension
{
public:
// must be called after creating the underlying MyGUI::Widget
void create(lua_State* lua, MyGUI::Widget* self);
// must be called after before destroying the underlying MyGUI::Widget
void destroy();
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; }
void setCallback(const std::string&, const LuaUtil::Callback&);
void clearCallbacks();
void setProperty(std::string_view, sol::object value);
MyGUI::IntCoord forcedOffset();
void setForcedOffset(const MyGUI::IntCoord& offset);
void updateCoord();
protected:
~WidgetExtension() {}
sol::table makeTable() const;
sol::object keyEvent(MyGUI::KeyCode) const;
sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const;
virtual bool setPropertyRaw(std::string_view name, sol::object value);
virtual void initialize();
virtual void deinitialize();
virtual MyGUI::IntSize calculateSize();
virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size);
MyGUI::IntCoord calculateCoord();
void triggerEvent(std::string_view name, const sol::object& argument) const;
MyGUI::IntCoord mForcedCoord;
MyGUI::IntCoord mAbsoluteCoord;
MyGUI::FloatCoord mRelativeCoord;
MyGUI::FloatSize mAnchor;
private:
// use lua_State* instead of sol::state_view because MyGUI requires a default constructor
lua_State* mLua;
MyGUI::Widget* mWidget;
std::vector<WidgetExtension*> mContent;
std::map<std::string, LuaUtil::Callback, std::less<>> mCallbacks;
void updateChildrenCoord(MyGUI::Widget*);
void keyPress(MyGUI::Widget*, MyGUI::KeyCode, MyGUI::Char);
void keyRelease(MyGUI::Widget*, MyGUI::KeyCode);
void mouseMove(MyGUI::Widget*, int, int);
void mouseDrag(MyGUI::Widget*, int, int, MyGUI::MouseButton);
void mouseClick(MyGUI::Widget*);
void mouseDoubleClick(MyGUI::Widget*);
void mousePress(MyGUI::Widget*, int, int, MyGUI::MouseButton);
void mouseRelease(MyGUI::Widget*, int, int, MyGUI::MouseButton);
void focusGain(MyGUI::Widget*, MyGUI::Widget*);
void focusLoss(MyGUI::Widget*, MyGUI::Widget*);
};
class LuaWidget : public MyGUI::Widget, public WidgetExtension
{
MYGUI_RTTI_DERIVED(LuaWidget)
};
}
#endif // !OPENMW_LUAUI_WIDGET

@ -0,0 +1,31 @@
#include "widgetlist.hpp"
#include <MyGUI_FactoryManager.h>
#include "widget.hpp"
#include "text.hpp"
#include "textedit.hpp"
#include "window.hpp"
namespace LuaUi
{
void registerAllWidgets()
{
MyGUI::FactoryManager::getInstance().registerFactory<LuaWidget>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<LuaText>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<LuaTextEdit>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<LuaWindow>("Widget");
}
const std::unordered_map<std::string, std::string>& widgetTypeToName()
{
static std::unordered_map<std::string, std::string> types{
{ "LuaWidget", "Widget" },
{ "LuaText", "Text" },
{ "LuaTextEdit", "TextEdit" },
{ "LuaWindow", "Window" },
};
return types;
}
}

@ -0,0 +1,14 @@
#ifndef OPENMW_LUAUI_WIDGETLIST
#define OPENMW_LUAUI_WIDGETLIST
#include <unordered_map>
#include <string>
namespace LuaUi
{
void registerAllWidgets();
const std::unordered_map<std::string, std::string>& widgetTypeToName();
}
#endif // OPENMW_LUAUI_WIDGETLIST

@ -0,0 +1,94 @@
#include "window.hpp"
#include <MyGUI_InputManager.h>
namespace LuaUi
{
void LuaWindow::initialize()
{
WidgetExtension::initialize();
assignWidget(mCaption, "Caption");
if (mCaption)
{
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);
}
}
void LuaWindow::deinitialize()
{
WidgetExtension::deinitialize();
if (mCaption)
{
mCaption->eventMouseButtonPressed.clear();
mCaption->eventMouseDrag.m_event.clear();
}
for (auto w : getSkinWidgetsByName("Action"))
{
w->eventMouseButtonPressed.clear();
w->eventMouseDrag.m_event.clear();
}
}
bool LuaWindow::setPropertyRaw(std::string_view name, sol::object value)
{
if (name == "caption")
{
if (!value.is<std::string>())
return false;
if (mCaption)
mCaption->setCaption(value.as<std::string>());
}
else
{
return WidgetExtension::setPropertyRaw(name, value);
}
return true;
}
void LuaWindow::notifyMousePress(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id)
{
if (id != MyGUI::MouseButton::Left)
return;
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);
}
void LuaWindow::notifyMouseDrag(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id)
{
if (id != MyGUI::MouseButton::Left)
return;
MyGUI::IntCoord change = mChangeScale;
change.left *= (left - mPreviousMouse.left);
change.top *= (top - mPreviousMouse.top);
change.width *= (left - mPreviousMouse.left);
change.height *= (top - mPreviousMouse.top);
setForcedOffset(forcedOffset() + change.size());
MyGUI::IntPoint positionOffset = change.point() + getPosition() - calculateCoord().point();
setForcedOffset(forcedOffset() + positionOffset);
updateCoord();
mPreviousMouse.left = left;
mPreviousMouse.top = top;
sol::table table = makeTable();
table["position"] = osg::Vec2f(mCoord.left, mCoord.top);
table["size"] = osg::Vec2f(mCoord.width, mCoord.height);
triggerEvent("windowDrag", table);
}
}

@ -0,0 +1,33 @@
#ifndef OPENMW_LUAUI_WINDOW
#define OPENMW_LUAUI_WINDOW
#include <optional>
#include <MyGUI_TextBox.h>
#include "widget.hpp"
namespace LuaUi
{
class LuaWindow : public MyGUI::Widget, public WidgetExtension
{
MYGUI_RTTI_DERIVED(LuaWindow)
private:
// \todo replace with LuaText when skins are properly implemented
MyGUI::TextBox* mCaption;
MyGUI::IntPoint mPreviousMouse;
MyGUI::IntCoord mChangeScale;
protected:
virtual void initialize() override;
virtual void deinitialize() override;
bool setPropertyRaw(std::string_view name, sol::object value) override;
void notifyMousePress(MyGUI::Widget*, int, int, MyGUI::MouseButton);
void notifyMouseDrag(MyGUI::Widget*, int, int, MyGUI::MouseButton);
};
}
#endif // OPENMW_LUAUI_WINDOW

@ -8,6 +8,18 @@
# luarocks --local pack openmwluadocumentor-0.1.1-1.rockspec
# luarocks --local install openmwluadocumentor-0.1.1-1.src.rock
# How to install on Windows:
# install LuaRocks (heavily recommended to use the standalone package)
# https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Windows
# git clone https://gitlab.com/ptmikheev/openmw-luadocumentor.git
# cd openmw-luadocumentor/luarocks
# open "Developer Command Prompt for VS <2017/2019>" in this directory and run:
# luarocks --local pack openmwluadocumentor-0.1.1-1.rockspec
# luarocks --local install openmwluadocumentor-0.1.1-1.src.rock
# open "Git Bash" in the same directory and run script:
# ./generate_luadoc.sh
if [ -f /.dockerenv ]; then
# We are inside readthedocs pipeline
echo "Install lua 5.1"
@ -32,7 +44,6 @@ if [ -f /.dockerenv ]; then
cd ~
git clone https://gitlab.com/ptmikheev/openmw-luadocumentor.git
cd openmw-luadocumentor/luarocks
luarocks --local install checks
luarocks --local pack openmwluadocumentor-0.1.1-1.rockspec
luarocks --local install openmwluadocumentor-0.1.1-1.src.rock
fi
@ -40,12 +51,18 @@ fi
DOCS_SOURCE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
FILES_DIR=$DOCS_SOURCE_DIR/../../files
OUTPUT_DIR=$DOCS_SOURCE_DIR/reference/lua-scripting/generated_html
DOCUMENTOR_PATH=~/.luarocks/bin/openmwluadocumentor
if [ ! -x $DOCUMENTOR_PATH ]; then
# running on Windows?
DOCUMENTOR_PATH="$APPDATA/LuaRocks/bin/openmwluadocumentor.bat"
fi
rm -f $OUTPUT_DIR/*.html
cd $FILES_DIR/lua_api
~/.luarocks/bin/openmwluadocumentor -f doc -d $OUTPUT_DIR openmw/*lua
$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw/*lua
cd $FILES_DIR/builtin_scripts
~/.luarocks/bin/openmwluadocumentor -f doc -d $OUTPUT_DIR openmw_aux/*lua
$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw_aux/*lua

@ -6,6 +6,7 @@ Lua API reference
:hidden:
engine_handlers
user_interface
openmw_util
openmw_settings
openmw_core
@ -20,6 +21,7 @@ Lua API reference
- :ref:`Engine handlers reference`
- :ref:`User interface reference <User interface reference>`
- `Game object reference <openmw_core.html##(GameObject)>`_
- `Cell reference <openmw_core.html##(Cell)>`_
@ -56,7 +58,7 @@ Player scripts are local scripts that are attached to a player.
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.input <Package openmw.input>` | by player scripts | | User input |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.ui <Package openmw.ui>` | by player scripts | | Controls user interface |
|:ref:`openmw.ui <Package openmw.ui>` | by player scripts | | Controls :ref:`user interface <User interface reference>` |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|openmw.camera | by player scripts | | Controls camera (not implemented) |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
@ -71,4 +73,3 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid
+=========================================================+====================+===============================================================+
|:ref:`openmw_aux.util <Package openmw_aux.util>` | everywhere | | Miscellaneous utils |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+

@ -350,7 +350,7 @@ Player scripts are local scripts that are attached to a player.
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.input <Package openmw.input>` | by player scripts | | User input |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.ui <Package openmw.ui>` | by player scripts | | Controls user interface |
|:ref:`openmw.ui <Package openmw.ui>` | by player scripts | | Controls :ref:`user interface <User interface reference>` |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|openmw.camera | by player scripts | | Controls camera (not implemented) |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
@ -750,5 +750,3 @@ You can add special hints to give LDT more information:
.. image:: https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/reference/lua-scripting/_static/lua-ide-code-completion2.png
See `LDT Documentation Language <https://wiki.eclipse.org/LDT/User_Area/Documentation_Language>`__ for more details.

@ -0,0 +1,151 @@
User interface reference
========================
.. toctree::
:hidden:
widgets/widget
Layouts
-------
Every widget is defined by a layout, which is a Lua table with the following fields (all of them are optional):
1. `type`: One of the available widget types from `openmw.ui.TYPE`.
2. | `props`: A Lua table, containing all the properties values.
| Properties define most of the information about the widget: its position, data it displays, etc.
| See the widget pages (table below) for details on specific properties.
| Properties of the basic Widget are inherited by all the other widgets.
3. | `events`: A Lua table, containing `openmw.async.callback` values, which trigger on various interactions with the widget.
| See the Widget pages for details on specific events.
| Events of the basic Widget are inherited by all the other widgets.
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)
.. TODO: Write a more detailed documentation for layers when they are finished
Elements
--------
Element is the root widget of a layout.
It is an independent part of the UI, connected only to a specific layer, but not any other layouts.
Creating or destroying an element also creates/destroys all of its children.
Content
-------
A container holding all the widget's children. It has a few important differences from a Lua table:
1. All the keys are integers, i. e. it is an "array"
2. Holes are not allowed. At any point all keys from `1` to the highest `n` must contain a value.
3. | You can access the values by their `name` field as a `Content` key.
| 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
Widget types
------------
.. list-table::
:widths: 30 70
* - :ref:`Widget`
- Base widget type, all the other widget inherit its properties and events.
* - `Text`
- Displays text.
* - EditText
- Accepts text input from the user.
* - Window
- Can be moved and resized by the user.
Example
-------
*scripts/requirePassword.lua*
.. code-block:: Lua
local core = require('openmw.core')
local async = require('openmw.async')
local ui = require('openmw.ui')
local v2 = require('openmw.util').vector2
local layout = {
layers = 'Windows',
type = ui.TYPE.Window,
skin = 'MW_Window', -- TODO: replace all skins here when they are properly implemented
props = {
size = v2(200, 250),
-- put the window in the middle of the screen
relativePosition = v2(0.5, 0.5),
anchor = v2(0.5, 0.5),
},
content = ui.content {
{
type = ui.TYPE.Text,
skin = 'SandText',
props = {
caption = 'Input password',
relativePosition = v2(0.5, 0),
anchor = v2(0.5, 0),
},
},
{
name = 'input',
type = ui.TYPE.TextEdit,
skin = "MW_TextEdit",
props = {
caption = '',
relativePosition = v2(0.5, 0.5),
anchor = v2(0.5, 0.5),
size = v2(125, 50),
},
events = {}
},
{
name = 'submit',
type = ui.TYPE.Text, -- TODO: replace with button when implemented
skin = "MW_Button",
props = {
caption = 'Submit',
-- position at the bottom
relativePosition = v2(0.5, 1.0),
anchor = v2(0.5, 1.0),
autoSize = false,
size = v2(75, 50),
},
events = {},
},
},
}
local element = nil
local input = layout.content.input
-- TODO: replace with a better event when TextEdit is finished
input.events.textInput = async:callback(function(text)
input.props.caption = input.props.caption .. text
end)
local submit = layout.content.submit
submit.events.mouseClick = async:callback(function()
if input.props.caption == 'very secret password' then
if element then
element:destroy()
end
else
print('wrong password', input.props.caption)
core.quit()
end
end)
element = ui.create(layout)
*requirePassword.omwscripts*
::
PLAYER: scripts/requirePassword.lua

@ -0,0 +1,77 @@
Widget
======
Properties
----------
.. list-table::
:header-rows: 1
:widths: 20 20 60
* - name
- type (default value)
- description
* - position
- util.vector2 (0, 0)
- | Offsets the position of the widget from its parent's
| top-left corner in pixels.
* - size
- util.vector2 (0, 0)
- Increases the widget's size in pixels.
* - relativePosition
- util.vector2 (0, 0)
- | Offsets the position of the widget from its parent's
| top-left corner as a fraction of the parent's size.
* - relativeSize
- util.vector2 (0, 0)
- Increases the widget's size by a fraction of its parent's size.
* - anchor
- util.vector2 (0, 0)
- | Offsets the widget's position by a fraction of its size.
| Useful for centering or aligning to a corner.
* - visible
- boolean (true)
- Defines if the widget is visible
Events
------
.. list-table::
:header-rows: 1
:widths: 20 20 60
* - name
- type
- description
* - keyPress
- `KeyboardEvent <../openmw_input.html##(KeyboardEvent)>`_
- A key was pressed with this widget in focus
* - keyRelease
- `KeyboardEvent <../openmw_input.html##(KeyboardEvent)>`_
- A key was released with this widget in focus
* - mouseMove
- `MouseEvent <../openmw_ui.html##(MouseEvent)>`_
- | Mouse cursor moved on this widget
| `MouseEvent.button` is the mouse button being held
| (nil when simply moving, and not dragging)
* - mouseClick
- nil
- Widget was clicked with left mouse button
* - mouseDoubleClick
- nil
- Widget was double clicked with left mouse button
* - mousePress
- `MouseEvent <../openmw_ui.html##(MouseEvent)>`_
- A mouse button was pressed on this widget
* - mouseRelease
- `MouseEvent <../openmw_ui.html##(MouseEvent)>`_
- A mouse button was released on this widget
* - focusGain
- nil
- Widget gained focus (either through mouse or keyboard)
* - focusLoss
- nil
- Widget lost focus
* - textInput
- string
- Text input with this widget in focus

@ -1,15 +1,115 @@
-------------------------------------------------------------------------------
---
-- `openmw.ui` controls user interface.
-- Can be used only by local scripts, that are attached to a player.
-- @module ui
-- @usage local ui = require('openmw.ui')
-- @usage
-- local ui = require('openmw.ui')
---
-- @field [parent=#ui] #WIDGET_TYPE WIDGET_TYPE
---
-- @type WIDGET_TYPE
-- @field [parent=#WIDGET_TYPE] Widget Base widget type
-- @field [parent=#WIDGET_TYPE] Text Display text
-- @field [parent=#WIDGET_TYPE] TextEdit Accepts user text input
-- @field [parent=#WIDGET_TYPE] Window Can be moved and resized by the user
-------------------------------------------------------------------------------
---
-- Shows given message at the bottom of the screen.
-- @function [parent=#ui] showMessage
-- @param #string msg
return nil
---
-- Converts a given table of tables into an @{openmw.ui#Content}
-- @function [parent=#ui] content
-- @param #table table
-- @return #Content
---
-- Creates a UI element from the given layout table
-- @function [parent=#ui] create
-- @param #Layout layout
-- @return #Element
---
-- Layout
-- @type Layout
-- @field #string name Optional name of the layout. Allows access by name from Content
-- @field #table props Optional table of widget properties
-- @field #table events Optional table of event callbacks
-- @field #Content content Optional @{openmw.ui#Content} of children layouts
---
-- Content. An array-like container, which allows to reference elements by their name
-- @type Content
-- @list <#Layout>
-- @usage
-- local content = ui.content {
-- { name = 'input' },
-- }
-- -- bad idea!
-- -- content[1].name = 'otherInput'
-- -- do this instead:
-- content.input = { name = 'otherInput' }
-- @usage
-- local content = ui.content {
-- { name = 'display' },
-- { name = 'submit' },
-- }
-- -- allowed, but shifts all the items after it "up" the array
-- content.display = nil
-- -- still no holes after this!
-- @usage
-- -- iterate over a Content
-- for i = 1, #content do
-- print('widget',content[i].name,'at',i)
-- end
---
-- 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
---
-- 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
---
-- 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
-- @return #number, #nil index
---
-- Element. An element of the user interface
-- @type Element
---
-- Refreshes the rendered element to match the current layout state
-- @function [parent=#Element] update
-- @param self
---
-- Destroys the element
-- @function [parent=#Element] destroy
-- @param self
---
-- Access or replace the element's layout
-- @field [parent=#Element] #Layout layout
---
-- Mouse event, passed as an argument to relevant UI events
-- @type MouseEvent
-- @field openmw.util#Vector2 position Absolute position of the mouse cursor
-- @field openmw.util#Vector2 offset Position of the mouse cursor relative to the widget
-- @field #number button Mouse button which triggered the event (could be nil)
return nil

Loading…
Cancel
Save