diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 69d09f7260..e79132f0ab 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -1,18 +1,20 @@ #include "settingswindow.hpp" +#include +#include +#include +#include + #include #include #include #include #include #include +#include #include -#include -#include -#include - #include #include #include @@ -21,6 +23,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -33,7 +36,6 @@ namespace { - std::string textureMipmappingToStr(const std::string& val) { if (val == "linear") return "Trilinear"; @@ -205,9 +207,9 @@ namespace MWGui } } - SettingsWindow::SettingsWindow() : - WindowBase("openmw_settings_window.layout"), - mKeyboardMode(true) + SettingsWindow::SettingsWindow() : WindowBase("openmw_settings_window.layout") + , mKeyboardMode(true) + , mCurrentPage(-1) { bool terrain = Settings::Manager::getBool("distant terrain", "Terrain"); const std::string widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider"; @@ -236,6 +238,12 @@ namespace MWGui getWidget(mLightingMethodButton, "LightingMethodButton"); getWidget(mLightsResetButton, "LightsResetButton"); getWidget(mMaxLights, "MaxLights"); + getWidget(mScriptFilter, "ScriptFilter"); + getWidget(mScriptList, "ScriptList"); + getWidget(mScriptBox, "ScriptBox"); + getWidget(mScriptView, "ScriptView"); + getWidget(mScriptAdapter, "ScriptAdapter"); + getWidget(mScriptDisabled, "ScriptDisabled"); #ifndef WIN32 // hide gamma controls since it currently does not work under Linux @@ -321,6 +329,9 @@ namespace MWGui mKeyboardSwitch->setStateSelected(true); mControllerSwitch->setStateSelected(false); + + mScriptFilter->eventEditTextChange += MyGUI::newDelegate(this, &SettingsWindow::onScriptFilterChange); + mScriptList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SettingsWindow::onScriptListSelection); } void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t /*index*/) @@ -699,6 +710,118 @@ namespace MWGui mControlsBox->setVisibleVScroll(true); } + void SettingsWindow::resizeScriptSettings() + { + constexpr int minListWidth = 150; + constexpr float relativeListWidth = 0.2f; + constexpr int padding = 2; + constexpr int outerPadding = padding * 2; + MyGUI::IntSize parentSize = mScriptFilter->getParent()->getClientCoord().size(); + int listWidth = std::max(minListWidth, static_cast(parentSize.width * relativeListWidth)); + int filterHeight = mScriptFilter->getSize().height; + int listHeight = parentSize.height - mScriptList->getPosition().top - outerPadding; + mScriptFilter->setSize({ listWidth, filterHeight }); + mScriptList->setSize({ listWidth, listHeight }); + mScriptBox->setPosition({ listWidth + padding, 0 }); + mScriptBox->setSize({ parentSize.width - listWidth - padding, parentSize.height - outerPadding }); + mScriptDisabled->setPosition({0, 0}); + mScriptDisabled->setSize(parentSize); + } + + namespace + { + std::string escapeRegex(const std::string& str) + { + static const std::regex specialChars(R"r([\^\.\[\$\(\)\|\*\+\?\{])r", std::regex_constants::extended); + return std::regex_replace(str, specialChars, R"(\$&)"); + } + + std::regex wordSearch(const std::string& query) + { + static const std::regex wordsRegex(R"([^[:space:]]+)", std::regex_constants::extended); + auto wordsBegin = std::sregex_iterator(query.begin(), query.end(), wordsRegex); + auto wordsEnd = std::sregex_iterator(); + std::string searchRegex("("); + for (auto it = wordsBegin; it != wordsEnd; ++it) + { + if (it != wordsBegin) + searchRegex += '|'; + searchRegex += escapeRegex(query.substr(it->position(), it->length())); + } + searchRegex += ')'; + // query had only whitespace characters + if (searchRegex == "()") + searchRegex = "^(.*)$"; + return std::regex(searchRegex, std::regex_constants::extended | std::regex_constants::icase); + } + + double weightedSearch(const std::regex& regex, const std::string& text) + { + std::smatch matches; + std::regex_search(text, matches, regex); + // need a signed value, so cast to double (not an integer type to guarantee no overflow) + return static_cast(matches.size()); + } + } + + void SettingsWindow::renderScriptSettings() + { + mScriptAdapter->detach(); + mCurrentPage = -1; + mScriptList->removeAllItems(); + mScriptView->setCanvasSize({0, 0}); + + struct WeightedPage { + size_t mIndex; + std::string mName; + double mNameWeight; + double mHintWeight; + + constexpr auto tie() const { return std::tie(mNameWeight, mHintWeight, mName); } + + constexpr bool operator<(const WeightedPage& rhs) const { return tie() < rhs.tie(); } + }; + + std::regex searchRegex = wordSearch(mScriptFilter->getCaption()); + std::vector weightedPages; + weightedPages.reserve(LuaUi::scriptSettingsPageCount()); + for (size_t i = 0; i < LuaUi::scriptSettingsPageCount(); ++i) + { + LuaUi::ScriptSettingsPage page = LuaUi::scriptSettingsPageAt(i); + double nameWeight = weightedSearch(searchRegex, page.mName); + double hintWeight = weightedSearch(searchRegex, page.mSearchHints); + if ((nameWeight + hintWeight) > 0) + weightedPages.push_back({ i, page.mName, -nameWeight, -hintWeight }); + } + std::sort(weightedPages.begin(), weightedPages.end()); + for (const WeightedPage& weightedPage : weightedPages) + mScriptList->addItem(weightedPage.mName, weightedPage.mIndex); + + // Hide script settings tab when the game world isn't loaded and scripts couldn't add their settings + bool disabled = LuaUi::scriptSettingsPageCount() == 0; + mScriptDisabled->setVisible(disabled); + mScriptFilter->setVisible(!disabled); + mScriptList->setVisible(!disabled); + mScriptBox->setVisible(!disabled); + } + + void SettingsWindow::onScriptFilterChange(MyGUI::EditBox*) + { + renderScriptSettings(); + } + + void SettingsWindow::onScriptListSelection(MyGUI::ListBox*, size_t index) + { + mScriptAdapter->detach(); + mCurrentPage = -1; + if (index < mScriptList->getItemCount()) + { + mCurrentPage = *mScriptList->getItemDataAt(index); + LuaUi::attachPageAt(mCurrentPage, mScriptAdapter); + } + mScriptView->setCanvasSize(mScriptAdapter->getSize()); + } + void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) { int actionId = *_sender->getUserData(); @@ -744,12 +867,15 @@ namespace MWGui updateControlsBox(); updateLightSettings(); resetScrollbars(); + renderScriptSettings(); + resizeScriptSettings(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); } void SettingsWindow::onWindowResize(MyGUI::Window *_sender) { layoutControlsBox(); + resizeScriptSettings(); } void SettingsWindow::computeMinimumWindowSize() diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index d58e94e84a..1a888257a8 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -1,6 +1,8 @@ #ifndef MWGUI_SETTINGS_H #define MWGUI_SETTINGS_H +#include + #include "windowbase.hpp" namespace MWGui @@ -44,6 +46,14 @@ namespace MWGui MyGUI::Button* mControllerSwitch; bool mKeyboardMode; //if true, setting up the keyboard. Otherwise, it's controller + MyGUI::EditBox* mScriptFilter; + MyGUI::ListBox* mScriptList; + MyGUI::Widget* mScriptBox; + MyGUI::ScrollView* mScriptView; + LuaUi::LuaAdapter* mScriptAdapter; + MyGUI::EditBox* mScriptDisabled; + int mCurrentPage; + void onTabChanged(MyGUI::TabControl* _sender, size_t index); void onOkButtonClicked(MyGUI::Widget* _sender); void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos); @@ -71,12 +81,17 @@ namespace MWGui void onWindowResize(MyGUI::Window* _sender); + void onScriptFilterChange(MyGUI::EditBox*); + void onScriptListSelection(MyGUI::ListBox*, size_t index); + void apply(); void configureWidgets(MyGUI::Widget* widget, bool init); void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value); void layoutControlsBox(); + void resizeScriptSettings(); + void renderScriptSettings(); void computeMinimumWindowSize(); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 61e7c471c1..6f1d34072a 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "context.hpp" #include "actions.hpp" @@ -43,7 +44,7 @@ namespace MWLua break; } } - catch (std::exception& e) + catch (std::exception&) { // prevent any actions on a potentially corrupted widget mElement->mRoot = nullptr; @@ -238,6 +239,8 @@ namespace MWLua typeTable.set(it.second, it.first); api["TYPE"] = LuaUtil::makeReadOnly(typeTable); + api["registerSettingsPage"] = &LuaUi::registerSettingsPage; + return LuaUtil::makeReadOnly(api); } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 2d986660bc..9a1ffc847c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -165,8 +165,9 @@ add_component_dir (queries ) add_component_dir (lua_ui + registerscriptsettings scriptsettings properties widget element util layers content - text textedit window image + adapter text textedit window image container ) diff --git a/components/lua_ui/adapter.cpp b/components/lua_ui/adapter.cpp new file mode 100644 index 0000000000..aa04f4c374 --- /dev/null +++ b/components/lua_ui/adapter.cpp @@ -0,0 +1,60 @@ +#include "adapter.hpp" + +#include + +#include "element.hpp" +#include "container.hpp" + +namespace LuaUi +{ + namespace + { + sol::state luaState; + } + + LuaAdapter::LuaAdapter() + : mElement(nullptr) + , mContainer(nullptr) + { + mContainer = MyGUI::Gui::getInstancePtr()->createWidget( + "", MyGUI::IntCoord(), MyGUI::Align::Default, "", ""); + mContainer->initialize(luaState, mContainer); + mContainer->onCoordChange([this](WidgetExtension* ext, MyGUI::IntCoord coord) + { + setSize(coord.size()); + }); + mContainer->widget()->attachToWidget(this); + } + + void LuaAdapter::attach(const std::shared_ptr& element) + { + detachElement(); + mElement = element; + attachElement(); + setSize(mContainer->widget()->getSize()); + + // workaround for MyGUI bug + // parent visibility doesn't affect added children + setVisible(!getVisible()); + setVisible(!getVisible()); + } + + void LuaAdapter::detach() + { + detachElement(); + setSize({ 0, 0 }); + } + + void LuaAdapter::attachElement() + { + if (mElement.get()) + mElement->attachToWidget(mContainer); + } + + void LuaAdapter::detachElement() + { + if (mElement.get()) + mElement->detachFromWidget(); + mElement = nullptr; + } +} diff --git a/components/lua_ui/adapter.hpp b/components/lua_ui/adapter.hpp new file mode 100644 index 0000000000..2cb08b7367 --- /dev/null +++ b/components/lua_ui/adapter.hpp @@ -0,0 +1,31 @@ +#ifndef OPENMW_LUAUI_ADAPTER +#define OPENMW_LUAUI_ADAPTER + +#include + +#include + +namespace LuaUi +{ + class LuaContainer; + struct Element; + class LuaAdapter : public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(LuaAdapter) + + public: + LuaAdapter(); + + void attach(const std::shared_ptr& element); + void detach(); + + private: + std::shared_ptr mElement; + LuaContainer* mContainer; + + void attachElement(); + void detachElement(); + }; +} + +#endif // !OPENMW_LUAUI_ADAPTER diff --git a/components/lua_ui/container.cpp b/components/lua_ui/container.cpp new file mode 100644 index 0000000000..867378744b --- /dev/null +++ b/components/lua_ui/container.cpp @@ -0,0 +1,35 @@ +#include "container.hpp" + +#include + +namespace LuaUi +{ + void LuaContainer::updateChildren() + { + WidgetExtension::updateChildren(); + for (auto w : children()) + { + w->onCoordChange([this](WidgetExtension* child, MyGUI::IntCoord coord) + { updateSizeToFit(); }); + } + updateSizeToFit(); + } + + MyGUI::IntSize LuaContainer::childScalingSize() + { + return MyGUI::IntSize(); + } + + void LuaContainer::updateSizeToFit() + { + MyGUI::IntSize size; + for (auto w : children()) + { + MyGUI::IntCoord coord = w->widget()->getCoord(); + size.width = std::max(size.width, coord.left + coord.width); + size.height = std::max(size.height, coord.top + coord.height); + } + setForcedSize(size); + updateCoord(); + } +} diff --git a/components/lua_ui/container.hpp b/components/lua_ui/container.hpp new file mode 100644 index 0000000000..a005b7ae53 --- /dev/null +++ b/components/lua_ui/container.hpp @@ -0,0 +1,21 @@ +#ifndef OPENMW_LUAUI_CONTAINER +#define OPENMW_LUAUI_CONTAINER + +#include "widget.hpp" + +namespace LuaUi +{ + class LuaContainer : public WidgetExtension, public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(LuaContainer) + + protected: + virtual void updateChildren() override; + virtual MyGUI::IntSize childScalingSize() override; + + private: + void updateSizeToFit(); + }; +} + +#endif // !OPENMW_LUAUI_CONTAINER diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index c9e0fc309e..59a3b13d55 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -4,6 +4,7 @@ #include "content.hpp" #include "util.hpp" +#include "widget.hpp" namespace LuaUi { @@ -138,7 +139,7 @@ namespace LuaUi ext->setChildren(updateContent(ext->children(), layout.get(LayoutKeys::content))); } - void setLayer(WidgetExtension* ext, const sol::table& layout) + std::string setLayer(WidgetExtension* ext, const sol::table& layout) { MyGUI::ILayer* layerNode = ext->widget()->getLayer(); std::string currentLayer = layerNode ? layerNode->getName() : std::string(); @@ -149,15 +150,18 @@ namespace LuaUi { MyGUI::LayerManager::getInstance().attachToLayerNode(newLayer, ext->widget()); } + return newLayer; } std::map> Element::sAllElements; Element::Element(sol::table layout) - : mRoot{ nullptr } - , mLayout{ std::move(layout) } - , mUpdate{ false } - , mDestroy{ false } + : mRoot(nullptr) + , mAttachedTo(nullptr) + , mLayout(std::move(layout)) + , mLayer() + , mUpdate(false) + , mDestroy(false) {} @@ -174,7 +178,8 @@ namespace LuaUi if (!mRoot) { mRoot = createWidget(mLayout); - setLayer(mRoot, mLayout); + mLayer = setLayer(mRoot, mLayout); + updateAttachment(); } } @@ -182,8 +187,17 @@ namespace LuaUi { if (mRoot && mUpdate) { - updateWidget(mRoot, mLayout); - setLayer(mRoot, mLayout); + if (mRoot->widget()->getTypeName() != widgetType(mLayout)) + { + destroyWidget(mRoot); + mRoot = createWidget(mLayout); + } + else + { + updateWidget(mRoot, mLayout); + } + mLayer = setLayer(mRoot, mLayout); + updateAttachment(); } mUpdate = false; } @@ -195,4 +209,34 @@ namespace LuaUi mRoot = nullptr; sAllElements.erase(this); } + + void Element::attachToWidget(WidgetExtension* w) + { + if (mAttachedTo) + throw std::logic_error("A UI element can't be attached to two widgets at once"); + mAttachedTo = w; + updateAttachment(); + } + + void Element::detachFromWidget() + { + if (mRoot) + mRoot->widget()->detachFromWidget(); + if (mAttachedTo) + mAttachedTo->setChildren({}); + mAttachedTo = nullptr; + } + + void Element::updateAttachment() + { + if (!mRoot) + return; + if (mAttachedTo) + { + if (!mLayer.empty()) + Log(Debug::Warning) << "Ignoring element's layer " << mLayer << " because it's attached to a widget"; + mAttachedTo->setChildren({ mRoot }); + mRoot->updateCoord(); + } + } } diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp index daaf340660..13f4ea052d 100644 --- a/components/lua_ui/element.hpp +++ b/components/lua_ui/element.hpp @@ -9,8 +9,10 @@ namespace LuaUi { static std::shared_ptr make(sol::table layout); - LuaUi::WidgetExtension* mRoot; + WidgetExtension* mRoot; + WidgetExtension* mAttachedTo; sol::table mLayout; + std::string mLayer; bool mUpdate; bool mDestroy; @@ -22,9 +24,13 @@ namespace LuaUi friend void clearUserInterface(); + void attachToWidget(WidgetExtension* w); + void detachFromWidget(); + private: Element(sol::table layout); static std::map> sAllElements; + void updateAttachment(); }; } diff --git a/components/lua_ui/registerscriptsettings.hpp b/components/lua_ui/registerscriptsettings.hpp new file mode 100644 index 0000000000..fb794468da --- /dev/null +++ b/components/lua_ui/registerscriptsettings.hpp @@ -0,0 +1,13 @@ +#ifndef OPENMW_LUAUI_REGISTERSCRIPTSETTINGS +#define OPENMW_LUAUI_REGISTERSCRIPTSETTINGS + +#include + +namespace LuaUi +{ + // implemented in scriptsettings.cpp + void registerSettingsPage(const sol::table& options); + void clearSettings(); +} + +#endif // !OPENMW_LUAUI_REGISTERSCRIPTSETTINGS diff --git a/components/lua_ui/scriptsettings.cpp b/components/lua_ui/scriptsettings.cpp new file mode 100644 index 0000000000..f2ab84bde0 --- /dev/null +++ b/components/lua_ui/scriptsettings.cpp @@ -0,0 +1,60 @@ +#include "scriptsettings.hpp" + +#include +#include + +#include "registerscriptsettings.hpp" +#include "element.hpp" +#include "adapter.hpp" + +namespace LuaUi +{ + namespace + { + std::vector allPages; + ScriptSettingsPage parse(const sol::table& options) + { + auto name = options.get_or("name", std::string()); + auto searchHints = options.get_or("searchHints", std::string()); + auto element = options.get_or>("element", nullptr); + if (name.empty()) + Log(Debug::Warning) << "A script settings page has an empty name"; + if (!element.get()) + Log(Debug::Warning) << "A script settings page has no UI element assigned"; + return { + name, searchHints, element + }; + } + } + + size_t scriptSettingsPageCount() + { + return allPages.size(); + } + + ScriptSettingsPage scriptSettingsPageAt(size_t index) + { + return parse(allPages[index]); + } + + void registerSettingsPage(const sol::table& options) + { + allPages.push_back(options); + } + + void clearSettings() + { + allPages.clear(); + } + + void attachPageAt(size_t index, LuaAdapter* adapter) + { + if (index < allPages.size()) + { + ScriptSettingsPage page = parse(allPages[index]); + adapter->detach(); + if (page.mElement.get()) + adapter->attach(page.mElement); + } + } +} diff --git a/components/lua_ui/scriptsettings.hpp b/components/lua_ui/scriptsettings.hpp new file mode 100644 index 0000000000..c468f9eb0b --- /dev/null +++ b/components/lua_ui/scriptsettings.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_LUAUI_SCRIPTSETTINGS +#define OPENMW_LUAUI_SCRIPTSETTINGS + +#include +#include +#include + +#include + +namespace LuaUi +{ + class LuaAdapter; + struct Element; + struct ScriptSettingsPage + { + std::string mName; + std::string mSearchHints; + std::shared_ptr mElement; + }; + size_t scriptSettingsPageCount(); + ScriptSettingsPage scriptSettingsPageAt(size_t index); + void attachPageAt(size_t index, LuaAdapter* adapter); +} + +#endif // !OPENMW_LUAUI_SCRIPTSETTINGS diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index 1e13c97a80..cd2cb2c077 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -2,24 +2,29 @@ #include +#include "adapter.hpp" #include "widget.hpp" #include "text.hpp" #include "textedit.hpp" #include "window.hpp" #include "image.hpp" +#include "container.hpp" #include "element.hpp" +#include "registerscriptsettings.hpp" namespace LuaUi { void registerAllWidgets() { + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("BasisSkin"); } @@ -37,6 +42,7 @@ namespace LuaUi void clearUserInterface() { + clearSettings(); while (!Element::sAllElements.empty()) Element::sAllElements.begin()->second->destroy(); } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 027adc44ca..70ed6b64fe 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -14,10 +14,11 @@ namespace LuaUi , mAbsoluteCoord() , mRelativeCoord() , mAnchor() - , mLua{ nullptr } - , mWidget{ nullptr } + , mLua(nullptr) + , mWidget(nullptr) , mSlot(this) - , mLayout{ sol::nil } + , mLayout(sol::nil) + , mParent(nullptr) {} void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self) @@ -64,6 +65,8 @@ namespace LuaUi mWidget->eventKeySetFocus.clear(); mWidget->eventKeyLostFocus.clear(); + mOnCoordChange.reset(); + for (WidgetExtension* w : mChildren) w->deinitialize(); for (WidgetExtension* w : mTemplateChildren) @@ -72,6 +75,7 @@ namespace LuaUi void WidgetExtension::attach(WidgetExtension* ext) { + ext->mParent = this; ext->widget()->attachToWidget(mSlot->widget()); ext->updateCoord(); } @@ -150,6 +154,7 @@ namespace LuaUi mChildren[i] = children[i]; attach(mChildren[i]); } + updateChildren(); } void WidgetExtension::setTemplateChildren(const std::vector& children) @@ -194,6 +199,11 @@ namespace LuaUi mForcedCoord = offset; } + void WidgetExtension::setForcedSize(const MyGUI::IntSize& size) + { + mForcedCoord = size; + } + void WidgetExtension::updateCoord() { MyGUI::IntCoord oldCoord = mWidget->getCoord(); @@ -203,6 +213,8 @@ namespace LuaUi mWidget->setCoord(newCoord); if (oldCoord.size() != newCoord.size()) updateChildrenCoord(); + if (oldCoord != newCoord && mOnCoordChange.has_value()) + mOnCoordChange.value()(this, newCoord); } void WidgetExtension::setProperties(sol::object props) @@ -231,23 +243,31 @@ namespace LuaUi w->updateCoord(); } + MyGUI::IntSize WidgetExtension::parentSize() + { + if (mParent) + return mParent->childScalingSize(); + else + return widget()->getParentSize(); + } + MyGUI::IntSize WidgetExtension::calculateSize() { - const MyGUI::IntSize& parentSize = mWidget->getParentSize(); + MyGUI::IntSize pSize = parentSize(); MyGUI::IntSize newSize; newSize = mAbsoluteCoord.size() + mForcedCoord.size(); - newSize.width += mRelativeCoord.width * parentSize.width; - newSize.height += mRelativeCoord.height * parentSize.height; + newSize.width += mRelativeCoord.width * pSize.width; + newSize.height += mRelativeCoord.height * pSize.height; return newSize; } MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size) { - const MyGUI::IntSize& parentSize = mWidget->getParentSize(); + MyGUI::IntSize pSize = parentSize(); 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; + newPosition.left += mRelativeCoord.left * pSize.width - mAnchor.width * size.width; + newPosition.top += mRelativeCoord.top * pSize.height - mAnchor.height * size.height; return newPosition; } @@ -259,6 +279,11 @@ namespace LuaUi return newCoord; } + MyGUI::IntSize WidgetExtension::childScalingSize() + { + return widget()->getSize(); + } + void WidgetExtension::triggerEvent(std::string_view name, const sol::object& argument = sol::nil) const { auto it = mCallbacks.find(name); diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 4085963137..63af5b3cae 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -2,6 +2,7 @@ #define OPENMW_LUAUI_WIDGET #include +#include #include #include @@ -44,6 +45,7 @@ namespace LuaUi MyGUI::IntCoord forcedCoord(); void setForcedCoord(const MyGUI::IntCoord& offset); + void setForcedSize(const MyGUI::IntSize& size); void updateCoord(); const sol::table& getLayout() { return mLayout; } @@ -55,15 +57,22 @@ namespace LuaUi return parseExternal(mExternal, name, defaultValue); } + void onCoordChange(const std::optional>& callback) + { + mOnCoordChange = callback; + } + protected: virtual void initialize(); sol::table makeTable() const; sol::object keyEvent(MyGUI::KeyCode) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; + MyGUI::IntSize parentSize(); virtual MyGUI::IntSize calculateSize(); virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); MyGUI::IntCoord calculateCoord(); + virtual MyGUI::IntSize childScalingSize(); template T propertyValue(std::string_view name, const T& defaultValue) @@ -76,6 +85,7 @@ namespace LuaUi virtual void updateTemplate(); virtual void updateProperties(); + virtual void updateChildren() {}; void triggerEvent(std::string_view name, const sol::object& argument) const; @@ -101,6 +111,7 @@ namespace LuaUi sol::object mProperties; sol::object mTemplateProperties; sol::object mExternal; + WidgetExtension* mParent; void attach(WidgetExtension* ext); @@ -119,6 +130,8 @@ namespace LuaUi void mouseRelease(MyGUI::Widget*, int, int, MyGUI::MouseButton); void focusGain(MyGUI::Widget*, MyGUI::Widget*); void focusLoss(MyGUI::Widget*, MyGUI::Widget*); + + std::optional> mOnCoordChange; }; class LuaWidget : public MyGUI::Widget, public WidgetExtension diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index c60a4e1abd..8049df860a 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -36,6 +36,11 @@ -- @param #Layout layout -- @return #Element +--- +-- Adds a settings page to main menu setting's Scripts tab. +-- @function [parent=#ui] registerSettingsPage +-- @param #SettingsPage page + --- -- Layout -- @type Layout @@ -137,4 +142,11 @@ -- @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) +--- +-- Settings page parameters, passed as an argument to ui.registerSettingsPage +-- @type SettingsPage +-- @field #string name Name of the page, displayed in the list, used for search +-- @field #string searchHints Additional keywords used in search, not displayed anywhere +-- @field #Element element The page's UI, which will be attached to the settings tab. The root widget has to have a fixed size (set `size` field in `props`, see Widget documentation, `relativeSize` is ignored) + return nil diff --git a/files/mygui/openmw_scroll.skin.xml b/files/mygui/openmw_scroll.skin.xml index f946a61ac9..a2c2cd9150 100644 --- a/files/mygui/openmw_scroll.skin.xml +++ b/files/mygui/openmw_scroll.skin.xml @@ -14,4 +14,11 @@ + + + + + + + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 8e81764f60..13f5ada24b 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -656,6 +656,26 @@ --> + + + + + + + + + + + + + + + + + + + +