Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
22 kB · 630 lines
C++
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631#include "widget.hpp"#include "components/lua/utilpackage.hpp"#include "components/lua_ui/util.hpp"#include "element.hpp"#include "resources.hpp"
#include <SDL_events.h>#include <components/sdlutil/sdlmappings.hpp>#include <ranges>
#include <MyGUI_InputManager.h>#include <MyGUI_PointerManager.h>
namespace{ // Arbitrary large number caps to prevent performance issues constexpr int sExtCalcSizeWidthCap = 10000000; constexpr int sExtCalcSizeHeightCap = 10000000; constexpr int sExtCalcPositionLeftCap = 10000000; constexpr int sExtCalcPositionTopCap = 10000000;}
namespace LuaUi{ WidgetExtension::WidgetExtension() : mForcePosition(false) , mForceSize(false) , mPropagateEvents(true) , mVisible(true) , mLua(nullptr) , mWidget(nullptr) , mSlot(this) , mLayout(sol::nil) , mProperties(sol::nil) , mTemplateProperties(sol::nil) , mExternal(sol::nil) , mParent(nullptr) , mTemplateChild(false) , mElementRoot(false) , mContentWidget(nullptr) , mCursor("arrow") , mHasCursor(false) { }
void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self, bool isRoot) { mLua = lua; mWidget = self; mElementRoot = isRoot; initialize(); updateTemplate(); }
void WidgetExtension::initialize() { // \todo might be more efficient to only register these if there are Lua callbacks registerEvents(mWidget);
mContentWidget = mWidget->createWidget<MyGUI::Widget>("", MyGUI::IntCoord(), MyGUI::Align::Default); mContentWidget->setNeedMouseFocus(false); mContentWidget->setNeedKeyFocus(false); mContentWidget->setInheritsPick(true); }
void WidgetExtension::deinitialize() { clearCallbacks(); clearEvents(mWidget);
for (WidgetExtension* w : mChildren) w->deinitialize(); for (WidgetExtension* w : mTemplateChildren) w->deinitialize(); }
void WidgetExtension::registerEvents(MyGUI::Widget* w) { w->eventKeyButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::keyPress); w->eventKeyButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::keyRelease); w->eventMouseButtonClick += MyGUI::newDelegate(this, &WidgetExtension::mouseClick); w->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WidgetExtension::mouseDoubleClick); w->eventMouseButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::mousePress); w->eventMouseButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::mouseRelease); w->eventMouseMove += MyGUI::newDelegate(this, &WidgetExtension::mouseMove); w->eventMouseDrag += MyGUI::newDelegate(this, &WidgetExtension::mouseDrag);
w->eventMouseSetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain); w->eventMouseLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); w->eventKeySetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain); w->eventKeyLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); } void WidgetExtension::clearEvents(MyGUI::Widget* w) { w->eventKeyButtonPressed.clear(); w->eventKeyButtonReleased.clear(); w->eventMouseButtonClick.clear(); w->eventMouseButtonDoubleClick.clear(); w->eventMouseButtonPressed.clear(); w->eventMouseButtonReleased.clear(); w->eventMouseMove.clear(); w->eventMouseDrag.clear();
w->eventMouseSetFocus.clear(); w->eventMouseLostFocus.clear(); w->eventKeySetFocus.clear(); w->eventKeyLostFocus.clear(); }
void WidgetExtension::reset() { // detach all children from the slot widget, in case it gets destroyed for (auto& w : mChildren) w->widget()->detachFromWidget(); }
void WidgetExtension::updateVisible() { // workaround for MyGUI bug // parent visibility doesn't affect added children MyGUI::Widget* parent = widget()->getParent(); bool inheritedVisible = mVisible && (parent == nullptr || parent->getInheritedVisible()); widget()->setVisible(inheritedVisible); }
void WidgetExtension::attach(WidgetExtension* ext) { if (ext->mParent != this) { if (ext->mParent) { auto children = ext->mParent->children(); std::erase(children, ext); ext->mParent->setChildren(children); } ext->detachFromParent(); } ext->mParent = this; ext->mTemplateChild = false; ext->widget()->attachToWidget(mSlot->contentWidget()); ext->setInheritedCursor(mCursor); }
void WidgetExtension::attachTemplate(WidgetExtension* ext) { ext->mParent = this; ext->mTemplateChild = true; ext->widget()->attachToWidget(widget()); ext->setInheritedCursor(mCursor); }
void WidgetExtension::detachFromParent() { mParent = nullptr; widget()->detachFromWidget(); widget()->detachFromLayer(); }
WidgetExtension* WidgetExtension::findDeep(std::string_view flagName) { for (WidgetExtension* w : mChildren) { WidgetExtension* result = w->findDeep(flagName); if (result != nullptr) return result; } if (externalValue(flagName, false)) return this; return nullptr; }
void WidgetExtension::findAll(std::string_view flagName, std::vector<WidgetExtension*>& result) { if (externalValue(flagName, false)) result.push_back(this); for (WidgetExtension* w : mChildren) w->findAll(flagName, result); }
WidgetExtension* WidgetExtension::findDeepInTemplates(std::string_view flagName) { for (WidgetExtension* w : mTemplateChildren) { WidgetExtension* result = w->findDeep(flagName); if (result != nullptr) return result; } return nullptr; }
std::vector<WidgetExtension*> WidgetExtension::findAllInTemplates(std::string_view flagName) { std::vector<WidgetExtension*> result; for (WidgetExtension* w : mTemplateChildren) w->findAll(flagName, result); return result; }
sol::object WidgetExtension::keyEvent(LuaUtil::LuaView& view, MyGUI::KeyCode code) const { auto keySym = SDL_Keysym(); keySym.sym = SDLUtil::myGuiKeyToSdl(code); keySym.scancode = SDL_GetScancodeFromKey(keySym.sym); keySym.mod = static_cast<Uint16>(SDL_GetModState()); return sol::make_object(view.sol(), keySym); }
sol::object WidgetExtension::mouseEvent( LuaUtil::LuaView& view, int left, int top, MyGUI::MouseButton button = MyGUI::MouseButton::None) const { osg::Vec2f position(static_cast<float>(left), static_cast<float>(top)); MyGUI::IntPoint absolutePosition = mWidget->getAbsolutePosition(); osg::Vec2f offset = position - osg::Vec2f(static_cast<float>(absolutePosition.left), static_cast<float>(absolutePosition.top)); sol::table table = view.newTable(); int sdlButton = SDLUtil::myGuiMouseButtonToSdl(button); table["position"] = position; table["offset"] = offset; if (sdlButton != 0) // nil if no button was pressed table["button"] = sdlButton; return table; }
void WidgetExtension::setChildren(const std::vector<WidgetExtension*>& children) { mChildren.resize(children.size()); for (size_t i = 0; i < children.size(); ++i) { mChildren[i] = children[i]; attach(mChildren[i]); } updateChildren(); }
void WidgetExtension::setTemplateChildren(const std::vector<WidgetExtension*>& children) { mTemplateChildren.resize(children.size()); for (size_t i = 0; i < children.size(); ++i) { mTemplateChildren[i] = children[i]; attachTemplate(mTemplateChildren[i]); } updateTemplate();
// Template children attach directly to this widget, while normal content is // kept in mContentWidget to apply padding and clipping. Reattach the wrapper // after templates so normal content remains the top-most sibling. mContentWidget->detachFromWidget(); mContentWidget->attachToWidget(widget()); }
void WidgetExtension::updateTemplate() { WidgetExtension* slot = findDeepInTemplates("slot"); if (slot == nullptr) mSlot = this; else mSlot = slot->mSlot; }
void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback) { mCallbacks[name] = callback; }
void WidgetExtension::clearCallbacks() { mCallbacks.clear(); }
MyGUI::IntCoord WidgetExtension::forcedCoord() { return mForcedCoord; }
void WidgetExtension::forceCoord(const MyGUI::IntCoord& offset) { mForcePosition = true; mForceSize = true; mForcedCoord = offset; }
void WidgetExtension::forcePosition(const MyGUI::IntPoint& pos) { mForcePosition = true; mForcedCoord = pos; }
void WidgetExtension::forceSize(const MyGUI::IntSize& size) { mForceSize = true; mForcedCoord = size; }
void WidgetExtension::clearForced() { mForcePosition = false; mForceSize = false; }
void WidgetExtension::updateCoord() { updateVisible();
MyGUI::IntCoord oldCoord = mWidget->getCoord(); MyGUI::IntCoord newCoord = calculateCoord();
if (oldCoord != newCoord) mWidget->setCoord(newCoord);
if (mContentWidget) mContentWidget->setCoord(MyGUI::IntCoord(getContentOffset(), getContentSize()));
updateChildrenCoord(); }
void WidgetExtension::setProperties(const sol::main_object& props) { mProperties = props; updateProperties(); }
void WidgetExtension::updateProperties() { mPropagateEvents = propertyValue("propagateEvents", true); mAbsoluteCoord = propertyValue("position", MyGUI::IntPoint()); mAbsoluteCoord = propertyValue("size", MyGUI::IntSize()); mRelativeCoord = propertyValue("relativePosition", MyGUI::FloatPoint()); mRelativeCoord = propertyValue("relativeSize", MyGUI::FloatSize()); mAnchor = propertyValue("anchor", MyGUI::FloatSize()); mVisible = propertyValue("visible", true); mWidget->setVisible(mVisible); mWidget->setNeedMouseFocus(!propertyValue("ignorePointerEvents", false)); CursorResource* cursor = propertyValue<CursorResource*>("cursor", nullptr); mHasCursor = cursor != nullptr; if (mHasCursor) setCursor(cursor->mName); else setInheritedCursor(mParent ? mParent->mCursor : "arrow"); mWidget->setAlpha(propertyValue("alpha", 1.f)); mWidget->setInheritsAlpha(propertyValue("inheritAlpha", true)); parsePadding(); }
void WidgetExtension::parsePadding() { mPadding = Padding(); const LuaUtil::Vec4 value = propertyValue("padding", LuaUtil::Vec4()); mPadding = Padding{ static_cast<int>(value.x()), static_cast<int>(value.y()), static_cast<int>(value.z()), static_cast<int>(value.w()) }; }
bool WidgetExtension::setCursor(const std::string& cursor) { if (mCursor == cursor) return false;
mCursor = cursor; mWidget->setPointer(mCursor); for (WidgetExtension* child : mTemplateChildren) child->setInheritedCursor(mCursor); for (WidgetExtension* child : mChildren) child->setInheritedCursor(mCursor); return true; }
bool WidgetExtension::setInheritedCursor(const std::string& cursor) { return setCursor(mHasCursor ? mCursor : cursor); }
void WidgetExtension::refreshCursor() { MyGUI::Widget* focused = MyGUI::InputManager::getInstance().getMouseFocusWidget(); if (WidgetExtension* extension = dynamic_cast<WidgetExtension*>(focused)) MyGUI::PointerManager::getInstance().setPointer(extension->mCursor); }
void WidgetExtension::updateChildrenCoord() { for (WidgetExtension* w : mTemplateChildren) w->updateCoord(); for (WidgetExtension* w : mChildren) w->updateCoord(); }
MyGUI::IntSize WidgetExtension::parentSize() const { if (!mParent) return widget()->getParentSize(); // size of the layer if (mTemplateChild) return mParent->templateScalingSize(); else return mParent->childScalingSize(); }
MyGUI::IntPoint WidgetExtension::getContentOffset() const { return MyGUI::IntPoint(mPadding.mLeft, mPadding.mTop); }
MyGUI::IntSize WidgetExtension::getContentSize() const { MyGUI::IntSize fullSize = calculateSize(); return MyGUI::IntSize(std::max(0, fullSize.width - (mPadding.mLeft + mPadding.mRight)), std::max(0, fullSize.height - (mPadding.mTop + mPadding.mBottom))); }
MyGUI::IntSize WidgetExtension::calculateSize() const { MyGUI::IntSize newSize; if (mForceSize) newSize = mForcedCoord.size(); else { MyGUI::IntSize pSize = parentSize(); newSize = mAbsoluteCoord.size(); newSize.width += static_cast<int>(mRelativeCoord.width * pSize.width); newSize.height += static_cast<int>(mRelativeCoord.height * pSize.height); } newSize.width = std::clamp(newSize.width, -sExtCalcSizeWidthCap, sExtCalcSizeWidthCap); newSize.height = std::clamp(newSize.height, -sExtCalcSizeHeightCap, sExtCalcSizeHeightCap); return newSize; }
MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size) const { MyGUI::IntPoint newPosition; if (mForcePosition) newPosition = mForcedCoord.point(); else { MyGUI::IntSize pSize = parentSize(); newPosition = mAbsoluteCoord.point(); newPosition.left += static_cast<int>(mRelativeCoord.left * pSize.width - mAnchor.width * size.width); newPosition.top += static_cast<int>(mRelativeCoord.top * pSize.height - mAnchor.height * size.height); } newPosition.left = std::clamp(newPosition.left, -sExtCalcPositionLeftCap, sExtCalcPositionLeftCap); newPosition.top = std::clamp(newPosition.top, -sExtCalcPositionTopCap, sExtCalcPositionTopCap); return newPosition; }
MyGUI::IntCoord WidgetExtension::calculateCoord() const { MyGUI::IntCoord newCoord; newCoord = calculateSize(); newCoord = calculatePosition(newCoord.size()); return newCoord; }
MyGUI::Widget* WidgetExtension::contentWidget() const { return mContentWidget; }
MyGUI::IntSize WidgetExtension::childScalingSize() const { return mSlot->getContentSize(); }
MyGUI::IntSize WidgetExtension::templateScalingSize() const { return widget()->getSize(); }
void WidgetExtension::triggerEvent(std::string_view name, sol::object argument) const { auto it = mCallbacks.find(name); if (it != mCallbacks.end()) it->second.call(argument, mLayout); }
bool WidgetExtension::collectWarnings(Warnings& warnings, int depth, bool generateWarningStrings) const { auto beginningSize = warnings.size(); if (collectUnusedWarnings(warnings, generateWarningStrings) && !generateWarningStrings) return true;
if (depth > 0) { std::ranges::transform(warnings, warnings.begin(), [&](const std::string& warning) { return std::string((depth + 1) * 2, ' ') + warning; }); }
for (size_t i = 0; i < mChildren.size(); i++) { Warnings childWarnings; if (mChildren[i]->isRoot()) continue; if (!mChildren[i]->collectWarnings(childWarnings, depth + 1, generateWarningStrings)) continue; if (!generateWarningStrings) return true; warnings.emplace_back(std::string((depth + 1) * 2, ' ') + "in content[" + std::to_string(i) + "]:"); std::ranges::move(childWarnings, std::back_inserter(warnings)); } if (!warnings.empty()) { warnings.insert(warnings.begin(), "Warnings generated for " + diagnosticName() + ":"); if (depth > 0) warnings.front() = std::string(depth * 2, ' ') + warnings.front(); }
return warnings.size() != beginningSize; }
bool WidgetExtension::collectUnusedWarnings(std::vector<std::string>& warnings, bool generateWarningStrings) const { const auto& usedPropsKeys = allUsedProperties(); const auto& usedLayoutKeys = LuaUi::Element::allLayoutProperties(); bool layoutWarn = warnUnused(warnings, mLayout, "layout", usedLayoutKeys, generateWarningStrings); if (layoutWarn && !generateWarningStrings) // We can skip checking props return true; bool propsWarn = warnUnused(warnings, mProperties, "props", usedPropsKeys, generateWarningStrings);
return layoutWarn || propsWarn; }
std::string WidgetExtension::diagnosticName() const { const std::string& name = mWidget->getName(); const std::string typeName(mWidget->getTypeName()); if (name.empty()) return "unnamed " + typeName; return typeName + " named '" + name + "'"; }
const std::vector<std::string_view>& WidgetExtension::allUsedProperties() const { static std::vector<std::string_view> usedProps = { "propagateEvents", "position", "size", "relativePosition", "relativeSize", "anchor", "visible", "ignorePointerEvents", "cursor", "alpha", "inheritAlpha", "padding", }; return usedProps; }
void WidgetExtension::keyPress(MyGUI::Widget*, MyGUI::KeyCode code, MyGUI::Char ch) { protectedCall([=, this](LuaUtil::LuaView& view) { if (code == MyGUI::KeyCode::None) { propagateEvent("textInput", [&](auto w) { MyGUI::UString uString; uString.push_back(static_cast<MyGUI::UString::unicode_char>(ch)); return sol::make_object(view.sol(), uString.asUTF8()); }); } else propagateEvent("keyPress", [&](auto w) { return w->keyEvent(view, code); }); }); }
void WidgetExtension::keyRelease(MyGUI::Widget*, MyGUI::KeyCode code) { protectedCall([=, this](LuaUtil::LuaView& view) { propagateEvent("keyRelease", [&](auto w) { return w->keyEvent(view, code); }); }); }
void WidgetExtension::mouseMove(MyGUI::Widget*, int left, int top) { protectedCall([=, this](LuaUtil::LuaView& view) { propagateEvent("mouseMove", [&](auto w) { return w->mouseEvent(view, left, top); }); }); }
void WidgetExtension::mouseDrag(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) { protectedCall([=, this](LuaUtil::LuaView& view) { propagateEvent("mouseMove", [&](auto w) { return w->mouseEvent(view, left, top, button); }); }); }
void WidgetExtension::mouseClick(MyGUI::Widget* /*widget*/) { propagateEvent("mouseClick", [](auto) { return sol::nil; }); }
void WidgetExtension::mouseDoubleClick(MyGUI::Widget* /*widget*/) { propagateEvent("mouseDoubleClick", [](auto) { return sol::nil; }); }
void WidgetExtension::mousePress(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) { protectedCall([=, this](LuaUtil::LuaView& view) { propagateEvent("mousePress", [&](auto w) { return w->mouseEvent(view, left, top, button); }); }); }
void WidgetExtension::mouseRelease(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) { protectedCall([=, this](LuaUtil::LuaView& view) { propagateEvent("mouseRelease", [&](auto w) { return w->mouseEvent(view, left, top, button); }); }); }
void WidgetExtension::mouseWheel(MyGUI::Widget*, float left, float top, float x, float y) { protectedCall([=, this](LuaUtil::LuaView& view) { propagateEvent("mouseWheel", [&](auto w) { sol::table event = view.newTable(); event["position"] = osg::Vec2f(left, top); event["delta"] = osg::Vec2f(x, y); return event; }); }); }
void WidgetExtension::focusGain(MyGUI::Widget*, MyGUI::Widget*) { propagateEvent("focusGain", [](auto) { return sol::nil; }); }
void WidgetExtension::focusLoss(MyGUI::Widget*, MyGUI::Widget*) { propagateEvent("focusLoss", [](auto) { return sol::nil; }); }}