mirror of https://github.com/OpenMW/openmw.git
commit
4fbbb67e98
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
@ -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…
Reference in New Issue