diff --git a/components/lua_ui/container.cpp b/components/lua_ui/container.cpp index 11b9c1b360..52fea684d7 100644 --- a/components/lua_ui/container.cpp +++ b/components/lua_ui/container.cpp @@ -7,11 +7,6 @@ namespace LuaUi void LuaContainer::updateChildren() { WidgetExtension::updateChildren(); - for (auto w : children()) - { - w->onCoordChange([this](WidgetExtension* child, MyGUI::IntCoord coord) - { updateSizeToFit(); }); - } updateSizeToFit(); } @@ -20,16 +15,39 @@ namespace LuaUi return MyGUI::IntSize(); } + MyGUI::IntSize LuaContainer::templateScalingSize() + { + return mInnerSize; + } + void LuaContainer::updateSizeToFit() { - MyGUI::IntSize size; + MyGUI::IntSize innerSize = MyGUI::IntSize(); 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); + MyGUI::IntCoord coord = w->calculateCoord(); + innerSize.width = std::max(innerSize.width, coord.left + coord.width); + innerSize.height = std::max(innerSize.height, coord.top + coord.height); } - forceSize(size); - updateCoord(); + MyGUI::IntSize outerSize = innerSize; + for (auto w : templateChildren()) + { + MyGUI::IntCoord coord = w->calculateCoord(); + outerSize.width = std::max(outerSize.width, coord.left + coord.width); + outerSize.height = std::max(outerSize.height, coord.top + coord.height); + } + mInnerSize = innerSize; + mOuterSize = outerSize; + } + + MyGUI::IntSize LuaContainer::calculateSize() + { + return mOuterSize; + } + + void LuaContainer::updateCoord() + { + updateSizeToFit(); + WidgetExtension::updateCoord(); } } diff --git a/components/lua_ui/container.hpp b/components/lua_ui/container.hpp index 664fb08ea2..1a8adee89f 100644 --- a/components/lua_ui/container.hpp +++ b/components/lua_ui/container.hpp @@ -9,12 +9,18 @@ namespace LuaUi { MYGUI_RTTI_DERIVED(LuaContainer) + MyGUI::IntSize calculateSize() override; + void updateCoord() override; + protected: void updateChildren() override; MyGUI::IntSize childScalingSize() override; + MyGUI::IntSize templateScalingSize() override; private: void updateSizeToFit(); + MyGUI::IntSize mInnerSize; + MyGUI::IntSize mOuterSize; }; } diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 9f70cfc1da..b55d89e613 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -24,7 +24,18 @@ namespace LuaUi std::string widgetType(const sol::table& layout) { - return layout.get_or(LayoutKeys::type, defaultWidgetType); + sol::object typeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::type); + std::string type = LuaUtil::getValueOrDefault(typeField, defaultWidgetType); + sol::object templateTypeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::templateLayout, LayoutKeys::type); + if (templateTypeField != sol::nil) + { + std::string templateType = LuaUtil::getValueOrDefault(templateTypeField, defaultWidgetType); + if (typeField != sol::nil && templateType != type) + throw std::logic_error(std::string("Template layout type ") + type + + std::string(" doesn't match template type ") + templateType); + type = templateType; + } + return type; } void destroyWidget(LuaUi::WidgetExtension* ext) @@ -103,18 +114,8 @@ namespace LuaUi WidgetExtension* createWidget(const sol::table& layout) { - sol::object typeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::type); - std::string type = LuaUtil::getValueOrDefault(typeField, defaultWidgetType); - sol::object templateTypeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::templateLayout, LayoutKeys::type); - if (templateTypeField != sol::nil) - { - std::string templateType = LuaUtil::getValueOrDefault(templateTypeField, defaultWidgetType); - if (typeField != sol::nil && templateType != type) - throw std::logic_error(std::string("Template layout type ") + type - + std::string(" doesn't match template type ") + templateType); - type = templateType; - } static auto widgetTypeMap = widgetTypeToName(); + std::string type = widgetType(layout); if (widgetTypeMap.find(type) == widgetTypeMap.end()) throw std::logic_error(std::string("Invalid widget type ") += type); @@ -242,7 +243,7 @@ namespace LuaUi if (!mLayer.empty()) Log(Debug::Warning) << "Ignoring element's layer " << mLayer << " because it's attached to a widget"; mAttachedTo->setChildren({ mRoot }); - mRoot->updateCoord(); + mAttachedTo->updateCoord(); } } } diff --git a/components/lua_ui/flex.cpp b/components/lua_ui/flex.cpp index dfe943a02f..b54120e78b 100644 --- a/components/lua_ui/flex.cpp +++ b/components/lua_ui/flex.cpp @@ -56,7 +56,7 @@ namespace LuaUi MyGUI::IntSize flexSize = calculateSize(); int growSize = 0; float growFactor = 0; - if (totalGrow > 0 && !mAutoSized) + if (totalGrow > 0) { growSize = primary(flexSize) - primary(childrenSize); growFactor = growSize / totalGrow; @@ -67,22 +67,32 @@ namespace LuaUi for (auto* w : children()) { MyGUI::IntSize size = w->calculateSize(); + primary(size) += static_cast(growFactor * getGrow(w)); + float stretch = std::clamp(w->externalValue("stretch", 0.0f), 0.0f, 1.0f); + secondary(size) = std::max(secondary(size), static_cast(stretch * secondary(flexSize))); secondary(childPosition) = alignSize(secondary(flexSize), secondary(size), mArrange); w->forcePosition(childPosition); - primary(size) += static_cast(growFactor * getGrow(w)); w->forceSize(size); w->updateCoord(); primary(childPosition) += primary(size); - w->updateCoord(); } WidgetExtension::updateChildren(); } + MyGUI::IntSize LuaFlex::childScalingSize() + { + // Call the base method to prevent relativeSize feedback loop + MyGUI::IntSize size = WidgetExtension::calculateSize(); + if (mAutoSized) + primary(size) = 0; + return size; + } + MyGUI::IntSize LuaFlex::calculateSize() { MyGUI::IntSize size = WidgetExtension::calculateSize(); if (mAutoSized) { - primary(size) = primary(mChildrenSize); + primary(size) = std::max(primary(size), primary(mChildrenSize)); secondary(size) = std::max(secondary(size), secondary(mChildrenSize)); } return size; diff --git a/components/lua_ui/flex.hpp b/components/lua_ui/flex.hpp index 7c583dc3c3..50a3404425 100644 --- a/components/lua_ui/flex.hpp +++ b/components/lua_ui/flex.hpp @@ -14,10 +14,8 @@ namespace LuaUi MyGUI::IntSize calculateSize() override; void updateProperties() override; void updateChildren() override; - MyGUI::IntSize childScalingSize() override - { - return MyGUI::IntSize(); - } + MyGUI::IntSize childScalingSize() override; + void updateCoord() override; private: diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp index 6bcc4a5846..8319780b23 100644 --- a/components/lua_ui/image.cpp +++ b/components/lua_ui/image.cpp @@ -31,6 +31,7 @@ namespace LuaUi { changeWidgetSkin("LuaImage"); mTileRect = dynamic_cast(getSubWidgetMain()); + WidgetExtension::initialize(); } void LuaImage::updateProperties() diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index ac44872f21..0441eb4e5d 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -6,38 +6,57 @@ namespace LuaUi { void LuaTextEdit::initialize() { - changeWidgetSkin("LuaTextEdit"); - - eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); - + mEditBox = createWidget("LuaTextEdit", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); + mEditBox->eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); + registerEvents(mEditBox); WidgetExtension::initialize(); } void LuaTextEdit::deinitialize() { - eventEditTextChange -= MyGUI::newDelegate(this, &LuaTextEdit::textChange); + mEditBox->eventEditTextChange -= MyGUI::newDelegate(this, &LuaTextEdit::textChange); + clearEvents(mEditBox); WidgetExtension::deinitialize(); } void LuaTextEdit::updateProperties() { - setCaption(propertyValue("text", std::string())); - setFontHeight(propertyValue("textSize", 10)); - setTextColour(propertyValue("textColor", MyGUI::Colour(0, 0, 0, 1))); - setEditMultiLine(propertyValue("multiline", false)); - setEditWordWrap(propertyValue("wordWrap", false)); + mEditBox->setCaption(propertyValue("text", std::string())); + mEditBox->setFontHeight(propertyValue("textSize", 10)); + mEditBox->setTextColour(propertyValue("textColor", MyGUI::Colour(0, 0, 0, 1))); + mEditBox->setEditMultiLine(propertyValue("multiline", false)); + mEditBox->setEditWordWrap(propertyValue("wordWrap", false)); Alignment horizontal(propertyValue("textAlignH", Alignment::Start)); Alignment vertical(propertyValue("textAlignV", Alignment::Start)); - setTextAlign(alignmentToMyGui(horizontal, vertical)); + mEditBox->setTextAlign(alignmentToMyGui(horizontal, vertical)); - setEditStatic(propertyValue("readOnly", false)); + mEditBox->setEditStatic(propertyValue("readOnly", false)); WidgetExtension::updateProperties(); } void LuaTextEdit::textChange(MyGUI::EditBox*) { - triggerEvent("textChanged", sol::make_object(lua(), getCaption().asUTF8())); + triggerEvent("textChanged", sol::make_object(lua(), mEditBox->getCaption().asUTF8())); + } + + void LuaTextEdit::updateCoord() + { + WidgetExtension::updateCoord(); + { + MyGUI::IntSize slotSize = slot()->calculateSize(); + MyGUI::IntPoint slotPosition = slot()->widget()->getAbsolutePosition() - widget()->getAbsolutePosition(); + MyGUI::IntCoord slotCoord(slotPosition, slotSize); + mEditBox->setCoord(slotCoord); + } + } + + void LuaTextEdit::updateChildren() + { + WidgetExtension::updateChildren(); + // otherwise it won't be focusable + mEditBox->detachFromWidget(); + mEditBox->attachToWidget(this); } } diff --git a/components/lua_ui/textedit.hpp b/components/lua_ui/textedit.hpp index 208240283c..1158e91e2c 100644 --- a/components/lua_ui/textedit.hpp +++ b/components/lua_ui/textedit.hpp @@ -7,7 +7,7 @@ namespace LuaUi { - class LuaTextEdit : public MyGUI::EditBox, public WidgetExtension + class LuaTextEdit : public MyGUI::Widget, public WidgetExtension { MYGUI_RTTI_DERIVED(LuaTextEdit) @@ -15,9 +15,13 @@ namespace LuaUi void initialize() override; void deinitialize() override; void updateProperties() override; + void updateCoord() override; + void updateChildren() override; private: void textChange(MyGUI::EditBox*); + + MyGUI::EditBox* mEditBox; }; } diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index d7e7390647..f3cb0d288c 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -39,6 +39,7 @@ namespace LuaUi { "LuaWindow", "Window" }, { "LuaImage", "Image" }, { "LuaFlex", "Flex" }, + { "LuaContainer", "Container" }, }; return types; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 641f34c9ca..bd9e47ab34 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -35,37 +35,13 @@ namespace LuaUi void WidgetExtension::initialize() { // \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); + registerEvents(mWidget); } void WidgetExtension::deinitialize() { clearCallbacks(); - 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(); + clearEvents(mWidget); mOnCoordChange.reset(); @@ -75,6 +51,39 @@ namespace LuaUi 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.m_event.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 @@ -188,25 +197,11 @@ namespace LuaUi void WidgetExtension::updateTemplate() { - WidgetExtension* oldSlot = mSlot; WidgetExtension* slot = findDeepInTemplates("slot"); if (slot == nullptr) mSlot = this; else mSlot = slot->mSlot; - if (mSlot != oldSlot) - { - MyGUI::IntSize slotSize = mSlot->widget()->getSize(); - MyGUI::IntPoint slotPosition = mSlot->widget()->getAbsolutePosition() - widget()->getAbsolutePosition(); - MyGUI::IntCoord slotCoord(slotPosition, slotSize); - MyGUI::Widget* clientWidget = mWidget->getClientWidget(); - if (!clientWidget) - clientWidget = mWidget; - if (clientWidget->getSubWidgetMain()) - clientWidget->getSubWidgetMain()->setCoord(slotCoord); - if (clientWidget->getSubWidgetText()) - clientWidget->getSubWidgetText()->setCoord(slotCoord); - } } void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback) @@ -290,10 +285,12 @@ namespace LuaUi MyGUI::IntSize WidgetExtension::parentSize() { - if (mParent && !mTemplateChild) - return mParent->childScalingSize(); + if (!mParent) + return widget()->getParentSize(); // size of the layer + if (mTemplateChild) + return mParent->templateScalingSize(); else - return widget()->getParentSize(); + return mParent->childScalingSize(); } MyGUI::IntSize WidgetExtension::calculateSize() @@ -334,6 +331,11 @@ namespace LuaUi return mSlot->widget()->getSize(); } + MyGUI::IntSize WidgetExtension::templateScalingSize() + { + return widget()->getSize(); + } + void WidgetExtension::triggerEvent(std::string_view name, sol::object argument) const { auto it = mCallbacks.find(name); diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 239d2e6bb3..b6bef55235 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -70,16 +70,20 @@ namespace LuaUi virtual MyGUI::IntSize calculateSize(); virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); + MyGUI::IntCoord calculateCoord(); protected: virtual void initialize(); + void registerEvents(MyGUI::Widget* w); + void clearEvents(MyGUI::Widget* w); + sol::table makeTable() const; sol::object keyEvent(MyGUI::KeyCode) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; MyGUI::IntSize parentSize(); - MyGUI::IntCoord calculateCoord(); virtual MyGUI::IntSize childScalingSize(); + virtual MyGUI::IntSize templateScalingSize(); template T propertyValue(std::string_view name, const T& defaultValue) diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index a6009edf1a..0c850d3676 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -111,3 +111,6 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid * - :ref:`Settings ` - by player and global scripts - Save, display and track changes of setting values. + * - :ref:`MWUI ` + - by player scripts + - Morrowind-style UI templates. diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index e21469a53f..a075d9228e 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -476,6 +476,9 @@ The order in which the scripts are started is important. So if one mod should ov * - :ref:`Settings ` - by player and global scripts - Save, display and track changes of setting values. + * - :ref:`MWUI ` + - by player scripts + - Morrowind-style UI templates. Event system ============ diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index 394c53e688..d1d3162a4b 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -82,6 +82,7 @@ Widget types TextEdit: Accepts text input from the user. Image: Renders a texture. Flex: Aligns children in a column/row + Container: Wraps around its children Example ------- diff --git a/docs/source/reference/lua-scripting/widgets/container.rst b/docs/source/reference/lua-scripting/widgets/container.rst new file mode 100644 index 0000000000..6b15f25110 --- /dev/null +++ b/docs/source/reference/lua-scripting/widgets/container.rst @@ -0,0 +1,8 @@ +Container Widget +================ + +Wraps around its children. Convenient for creating border-type templates. + +Relative size and position don't work for children. + +For template children, relative size and position depend on the children's combined size. \ No newline at end of file diff --git a/docs/source/reference/lua-scripting/widgets/flex.rst b/docs/source/reference/lua-scripting/widgets/flex.rst index 2648e88996..468cb93958 100644 --- a/docs/source/reference/lua-scripting/widgets/flex.rst +++ b/docs/source/reference/lua-scripting/widgets/flex.rst @@ -15,7 +15,8 @@ Properties - description * - horizontal - bool (false) - - Flex aligns its children in a row if true, otherwise in a column. + - | Flex aligns its children in a row (main axis is horizontal) if true, + | otherwise in a column (main axis is vertical). * - autoSize - bool (true) - | If true, Flex will automatically resize to fit its contents. @@ -41,3 +42,6 @@ External - | Grow factor for the child. If there is unused space in the Flex, | it will be split between widgets according to this value. | Has no effect if `autoSize` is `true`. + * - stretch + - float (0) + - | Stretches the child to a percentage of the Flex's cross axis size. diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index 318e389585..103351d044 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -21,6 +21,7 @@ set(LUA_BUILTIN_FILES scripts/omw/settings/global.lua scripts/omw/settings/common.lua scripts/omw/settings/render.lua + scripts/omw/settings/renderers.lua l10n/Calendar/en.yaml @@ -29,6 +30,7 @@ set(LUA_BUILTIN_FILES scripts/omw/mwui/box.lua scripts/omw/mwui/text.lua scripts/omw/mwui/textEdit.lua + scripts/omw/mwui/space.lua scripts/omw/mwui/init.lua ) diff --git a/files/builtin_scripts/scripts/omw/mwui/borders.lua b/files/builtin_scripts/scripts/omw/mwui/borders.lua index d0d3e50cdf..bd8d99a7b7 100644 --- a/files/builtin_scripts/scripts/omw/mwui/borders.lua +++ b/files/builtin_scripts/scripts/omw/mwui/borders.lua @@ -1,120 +1,203 @@ local ui = require('openmw.ui') local util = require('openmw.util') +local auxUi = require('openmw_aux.ui') + local constants = require('scripts.omw.mwui.constants') local v2 = util.vector2 +local whiteTexture = ui.texture{ path = 'white' } +local menuTransparency = ui._getMenuTransparency() local sideParts = { - left = util.vector2(0, 0.5), - right = util.vector2(1, 0.5), - top = util.vector2(0.5, 0), - bottom = util.vector2(0.5, 1), + left = v2(0, 0), + right = v2(1, 0), + top = v2(0, 0), + bottom = v2(0, 1), } local cornerParts = { - top_left_corner = util.vector2(0, 0), - top_right_corner = util.vector2(1, 0), - bottom_left_corner = util.vector2(0, 1), - bottom_right_corner = util.vector2(1, 1), + top_left = v2(0, 0), + top_right = v2(1, 0), + bottom_left = v2(0, 1), + bottom_right = v2(1, 1), } -local resources = {} +local borderSidePattern = 'textures/menu_thin_border_%s.dds' +local borderCornerPattern = 'textures/menu_thin_border_%s_corner.dds' + +local borderResources = {} do - local boxBorderPattern = 'textures/menu_thin_border_%s.dds' - for k, _ in pairs(sideParts) do - resources[k] = ui.texture{ path = boxBorderPattern:format(k) } + for k in pairs(sideParts) do + borderResources[k] = ui.texture{ path = borderSidePattern:format(k) } end - for k, _ in pairs(cornerParts) do - resources[k] = ui.texture{ path = boxBorderPattern:format(k) } + for k in pairs(cornerParts) do + borderResources[k] = ui.texture{ path = borderCornerPattern:format(k) } end end local borderPieces = {} -for k, align in pairs(sideParts) do - local resource = resources[k] - local horizontal = align.x ~= 0.5 - borderPieces[#borderPieces + 1] = { +for k in pairs(sideParts) do + local horizontal = k == 'top' or k == 'bottom' + borderPieces[k] = { type = ui.TYPE.Image, props = { - resource = resource, - relativePosition = align, - anchor = align, - relativeSize = horizontal and v2(0, 1) or v2(1, 0), - size = (horizontal and v2(1, -2) or v2(-2, 1)) * constants.borderSize, - tileH = not horizontal, - tileV = horizontal, + resource = borderResources[k], + tileH = horizontal, + tileV = not horizontal, + }, + } +end +for k in pairs(cornerParts) do + borderPieces[k] = { + type = ui.TYPE.Image, + props = { + resource = borderResources[k], }, } end -for k, align in pairs(cornerParts) do - local resource = resources[k] - borderPieces[#borderPieces + 1] = { + + +local function borderTemplates(borderSize) + local borderV = v2(1, 1) * borderSize + local result = {} + result.horizontalLine = { type = ui.TYPE.Image, props = { - resource = resource, - relativePosition = align, - anchor = align, - size = v2(1, 1) * constants.borderSize, + resource = borderResources.top, + tileH = true, + tileV = false, + size = v2(0, borderSize), + relativeSize = v2(1, 0), }, } + + result.verticalLine = { + type = ui.TYPE.Image, + props = { + resource = borderResources.left, + tileH = false, + tileV = true, + size = v2(borderSize, 0), + relativeSize = v2(0, 1), + }, + } + + result.borders = { + content = ui.content {}, + } + for k, v in pairs(sideParts) do + local horizontal = k == 'top' or k == 'bottom' + local direction = horizontal and v2(1, 0) or v2(0, 1) + result.borders.content:add { + template = borderPieces[k], + props = { + position = (direction - v) * borderSize, + relativePosition = v, + size = (v2(1, 1) - direction * 3) * borderSize, + relativeSize = direction, + } + } + end + for k, v in pairs(cornerParts) do + result.borders.content:add { + template = borderPieces[k], + props = { + position = -v * borderSize, + relativePosition = v, + size = borderV, + }, + } + end + result.borders.content:add { + external = { slot = true }, + props = { + position = borderV, + size = borderV * -2, + relativeSize = v2(1, 1), + } + } + + result.box = { + type = ui.TYPE.Container, + content = ui.content{}, + } + for k, v in pairs(sideParts) do + local horizontal = k == 'top' or k == 'bottom' + local direction = horizontal and v2(1, 0) or v2(0, 1) + result.box.content:add { + template = borderPieces[k], + props = { + position = (direction + v) * borderSize, + relativePosition = v, + size = (v2(1, 1) - direction) * borderSize, + relativeSize = direction, + } + } + end + for k, v in pairs(cornerParts) do + result.box.content:add { + template = borderPieces[k], + props = { + position = v * borderSize, + relativePosition = v, + size = borderV, + }, + } + end + result.box.content:add { + external = { slot = true }, + props = { + position = borderV, + relativeSize = v2(1, 1), + } + } + + local backgroundTransparent = { + type = ui.TYPE.Image, + props = { + resource = whiteTexture, + color = util.color.rgb(0, 0, 0), + alpha = menuTransparency, + }, + } + local backgroundSolid = { + type = ui.TYPE.Image, + props = { + resource = whiteTexture, + color = util.color.rgb(0, 0, 0), + }, + } + + result.boxTransparent = auxUi.deepLayoutCopy(result.box) + result.boxTransparent.content:insert(1, { + template = backgroundTransparent, + props = { + relativeSize = v2(1, 1), + size = borderV * 2, + }, + }) + + result.boxSolid = auxUi.deepLayoutCopy(result.box) + result.boxSolid.content:insert(1, { + template = backgroundSolid, + props = { + relativeSize = v2(1, 1), + size = borderV * 2, + }, + }) + + return result end -borderPieces[#borderPieces + 1] = { - external = { - slot = true, - }, - props = { - position = v2(1, 1) * (constants.borderSize + constants.padding), - size = v2(-2, -2) * (constants.borderSize + constants.padding), - relativeSize = v2(1, 1), - }, -} - -local borders = { - content = ui.content(borderPieces) -} -borders.content:add({ - external = { - slot = true, - }, - props = { - size = v2(-2, -2) * constants.borderSize, - }, -}) - -local horizontalLine = { - content = ui.content { - { - type = ui.TYPE.Image, - props = { - resource = resources.top, - tileH = true, - tileV = false, - size = v2(0, constants.borderSize), - relativeSize = v2(1, 0), - }, - }, - }, -} - -local verticalLine = { - content = ui.content { - { - type = ui.TYPE.Image, - props = { - resource = resources.left, - tileH = false, - tileV = true, - size = v2(constants.borderSize, 0), - relativeSize = v2(0, 1), - }, - }, - }, -} +local thinBorders = borderTemplates(constants.border) +local thickBorders = borderTemplates(constants.thickBorder) return function(templates) - templates.borders = borders - templates.horizontalLine = horizontalLine - templates.verticalLine = verticalLine + for k, t in pairs(thinBorders) do + templates[k] = t + end + for k, t in pairs(thickBorders) do + templates[k .. 'Thick'] = t + end end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/constants.lua b/files/builtin_scripts/scripts/omw/mwui/constants.lua index ddfe5c9214..3791025717 100644 --- a/files/builtin_scripts/scripts/omw/mwui/constants.lua +++ b/files/builtin_scripts/scripts/omw/mwui/constants.lua @@ -2,7 +2,10 @@ local util = require('openmw.util') return { textNormalSize = 16, - sandColor = util.color.rgb(202 / 255, 165 / 255, 96 / 255), - borderSize = 4, + textHeaderSize = 16, + headerColor = util.color.rgb(223 / 255, 201 / 255, 159 / 255), + normalColor = util.color.rgb(202 / 255, 165 / 255, 96 / 255), + border = 2, + thickBorder = 4, padding = 2, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/init.lua b/files/builtin_scripts/scripts/omw/mwui/init.lua index af41bece98..d95a07040b 100644 --- a/files/builtin_scripts/scripts/omw/mwui/init.lua +++ b/files/builtin_scripts/scripts/omw/mwui/init.lua @@ -58,20 +58,26 @@ end local templates = {} --- --- Standard rectangular border --- @field [parent=#Templates] openmw.ui#Layout border -require('scripts.omw.mwui.borders')(templates) +-- Container that adds padding around its content. +-- @field [parent=#MWUI] #table padding +--- +-- Standard spacing interval +-- @field [parent=#MWUI] #number interval +require('scripts.omw.mwui.space')(templates) --- --- Border combined with a transparent background +-- Standard rectangular border +-- @field [parent=#Templates] openmw.ui#Layout border +--- +-- Container wrapping the content with borders -- @field [parent=#Templates] openmw.ui#Layout box --- --- A transparent background --- @field [parent=#Templates] openmw.ui#Layout backgroundTransparent +-- Same as box, but with a semi-transparent background +-- @field [parent=#Templates] openmw.ui#Layout boxTransparent --- --- A solid, non-transparent background --- @field [parent=#Templates] openmw.ui#Layout backgroundSolid -require('scripts.omw.mwui.box')(templates) +-- Same as box, but with a solid background +-- @field [parent=#Templates] openmw.ui#Layout boxSolid +require('scripts.omw.mwui.borders')(templates) --- -- Standard "sand" colored text diff --git a/files/builtin_scripts/scripts/omw/mwui/space.lua b/files/builtin_scripts/scripts/omw/mwui/space.lua new file mode 100644 index 0000000000..1f65a98e7f --- /dev/null +++ b/files/builtin_scripts/scripts/omw/mwui/space.lua @@ -0,0 +1,39 @@ +local ui = require('openmw.ui') +local util = require('openmw.util') + +local constants = require('scripts.omw.mwui.constants') + +local borderV = util.vector2(1, 1) * constants.border + +return function(templates) + templates.padding = { + type = ui.TYPE.Container, + content = ui.content { + { + props = { + size = borderV, + }, + }, + { + external = { slot = true }, + props = { + position = borderV, + relativeSize = util.vector2(1, 1), + }, + }, + { + props = { + position = borderV, + relativePosition = util.vector2(1, 1), + size = borderV, + }, + }, + } + } + templates.interval = { + type = ui.TYPE.Widget, + props = { + size = borderV, + }, + } +end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/text.lua b/files/builtin_scripts/scripts/omw/mwui/text.lua index f62e4384c1..8fc1133000 100644 --- a/files/builtin_scripts/scripts/omw/mwui/text.lua +++ b/files/builtin_scripts/scripts/omw/mwui/text.lua @@ -6,10 +6,19 @@ local textNormal = { type = ui.TYPE.Text, props = { textSize = constants.textNormalSize, - textColor = constants.sandColor, + textColor = constants.normalColor, + }, +} + +local textHeader = { + type = ui.TYPE.Text, + props = { + textSize = constants.textHeaderSize, + textColor = constants.headerColor, }, } return function(templates) templates.textNormal = textNormal + templates.textHeader = textHeader end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/textEdit.lua b/files/builtin_scripts/scripts/omw/mwui/textEdit.lua index 1fbc619611..4f86aa356f 100644 --- a/files/builtin_scripts/scripts/omw/mwui/textEdit.lua +++ b/files/builtin_scripts/scripts/omw/mwui/textEdit.lua @@ -3,6 +3,8 @@ local ui = require('openmw.ui') local constants = require('scripts.omw.mwui.constants') +local borderOffset = util.vector2(1, 1) * constants.border + return function(templates) local borderContent = ui.content { { @@ -12,10 +14,14 @@ return function(templates) }, content = ui.content { { + props = { + position = borderOffset, + relativeSize = util.vector2(1, 1), + }, external = { slot = true, }, - }, + } } }, } @@ -23,10 +29,9 @@ return function(templates) templates.textEditLine = { type = ui.TYPE.TextEdit, props = { + size = util.vector2(150, constants.textNormalSize) + borderOffset * 4, textSize = constants.textNormalSize, - textColor = constants.sandColor, - textAlignH = ui.ALIGNMENT.Start, - textAlignV = ui.ALIGNMENT.Center, + textColor = constants.normalColor, multiline = false, }, content = borderContent, @@ -35,10 +40,9 @@ return function(templates) templates.textEditBox = { type = ui.TYPE.TextEdit, props = { + size = util.vector2(150, 5 * constants.textNormalSize) + borderOffset * 4, textSize = constants.textNormalSize, - textColor = constants.sandColor, - textAlignH = ui.ALIGNMENT.Start, - textAlignV = ui.ALIGNMENT.Start, + textColor = constants.normalColor, multiline = true, wordWrap = true, }, diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index 1e87dfd62b..8150f2cc61 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -21,8 +21,8 @@ local function validateSettingOptions(options) if type(options.name) ~= 'string' then error('Setting must have a name localization key') end - if type(options.description) ~= 'string' then - error('Setting must have a descripiton localization key') + if options.description ~= nil and type(options.description) ~= 'string' then + error('Setting description key must be a string') end end @@ -49,8 +49,8 @@ local function validateGroupOptions(options) if type(options.name) ~= 'string' then error('Group must have a name localization key') end - if type(options.description) ~= 'string' then - error('Group must have a description localization key') + if options.description ~= nil and type(options.description) ~= 'string' then + error('Group description key must be a string') end if type(options.settings) ~= 'table' then error('Group must have a table of settings') @@ -89,8 +89,9 @@ local function registerGroup(options) settings = {}, } local valueSection = contextSection(options.key) - for _, opt in ipairs(options.settings) do + for i, opt in ipairs(options.settings) do local setting = registerSetting(opt) + setting.order = i if group.settings[setting.key] then error(('Duplicate setting key %s'):format(options.key)) end @@ -123,7 +124,7 @@ return { local section = contextSection(groupKey) saved[groupKey] = {} for key, value in pairs(section:asTable()) do - if not group.settings[key].permanentStorage then + if group.settings[key] and not group.settings[key].permanentStorage then saved[groupKey][key] = value end end diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 612ba3ec7b..a4201bc8a8 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -1,39 +1,21 @@ -local ui = require('openmw.ui') -local async = require('openmw.async') -local util = require('openmw.util') - local common = require('scripts.omw.settings.common') local render = require('scripts.omw.settings.render') -render.registerRenderer('text', function(value, set, arg) - return { - type = ui.TYPE.TextEdit, - props = { - size = util.vector2(arg and arg.size or 150, 30), - text = value, - textColor = util.color.rgb(1, 1, 1), - textSize = 15, - textAlignV = ui.ALIGNMENT.End, - }, - events = { - textChanged = async:callback(function(s) set(s) end), - }, - } -end) +require('scripts.omw.settings.renderers')(render.registerRenderer) --- -- @type PageOptions -- @field #string key A unique key -- @field #string l10n A localization context (an argument of core.l10n) -- @field #string name A key from the localization context --- @field #string description A key from the localization context +-- @field #string description A key from the localization context (optional, can be `nil`) --- -- @type GroupOptions -- @field #string key A unique key, starts with "Settings" by convention -- @field #string l10n A localization context (an argument of core.l10n) -- @field #string name A key from the localization context --- @field #string description A key from the localization context +-- @field #string description A key from the localization context (optional, can be `nil`) -- @field #string page Key of a page which will contain this group -- @field #number order Groups within the same page are sorted by this number, or their key for equal values. -- Defaults to 0. @@ -43,7 +25,7 @@ end) -- @type SettingOptions -- @field #string key A unique key -- @field #string name A key from the localization context --- @field #string description A key from the localization context +-- @field #string description A key from the localization context (optional, can be `nil`) -- @field default A default value -- @field #string renderer A renderer key -- @field argument An argument for the renderer @@ -57,26 +39,33 @@ return { -- -- In a player script -- local storage = require('openmw.storage') -- local I = require('openmw.interfaces') - -- I.Settings.registerGroup({ + -- I.Settings.registerPage { + -- key = 'MyModPage', + -- l10n = 'MyMod', + -- name = 'My Mod Name', + -- description = 'My Mod Description', + -- } + -- I.Settings.registerGroup { -- key = 'SettingsPlayerMyMod', - -- page = 'MyPage', - -- l10n = 'mymod', - -- name = 'modName', - -- description = 'modDescription', + -- page = 'MyModPage', + -- l10n = 'MyMod', + -- name = 'My Group Name', + -- description = 'My Group Description', -- settings = { -- { -- key = 'Greeting', - -- renderer = 'text', - -- name = 'greetingName', - -- description = 'greetingDescription', + -- renderer = 'textLine', + -- name = 'Greeting', + -- description = 'Text to display when the game starts', -- default = 'Hello, world!', - -- argument = { - -- size = 200, - -- }, + -- permanentStorage = false, -- }, -- }, - -- }) + -- } -- local playerSettings = storage.playerSection('SettingsPlayerMyMod') + -- ... + -- ui.showMessage(playerSettings:get('Greeting')) + -- -- ... -- -- access a setting page registered by a global script -- local globalSettings = storage.globalSection('SettingsGlobalMyMod') interface = { @@ -132,22 +121,19 @@ return { -- settings = { -- { -- key = 'Greeting', - -- saveOnly = true, + -- permanentStorage = true, -- default = 'Hi', - -- renderer = 'text', - -- argument = { - -- size = 200, - -- }, + -- renderer = 'textLine', -- name = 'Text Input', -- description = 'Short text input', -- }, -- { - -- key = 'Key', - -- saveOnly = false, - -- default = input.KEY.LeftAlt, - -- renderer = 'keybind', - -- name = 'Key', - -- description = 'Bind Key', + -- key = 'Flag', + -- permanentStorage = false, + -- default = false, + -- renderer = 'yeNo', + -- name = 'Flag', + -- description = 'Flag toggle', -- }, -- } -- } diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index c072d20e94..8274f04670 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -3,6 +3,7 @@ local util = require('openmw.util') local async = require('openmw.async') local core = require('openmw.core') local storage = require('openmw.storage') +local I = require('openmw.interfaces') local common = require('scripts.omw.settings.common') @@ -15,34 +16,68 @@ local pages = {} local groups = {} local pageOptions = {} -local padding = function(size) +local interval = { template = I.MWUI.templates.interval } +local growingIntreval = { + template = I.MWUI.templates.interval, + external = { + grow = 1, + }, +} +local spacer = { + props = { + size = util.vector2(0, 10), + }, +} +local bigSpacer = { + props = { + size = util.vector2(0, 50), + }, +} +local stretchingLine = { + template = I.MWUI.templates.horizontalLine, + external = { + stretch = 1, + }, +} +local spacedLines = function(count) + local content = {} + table.insert(content, spacer) + table.insert(content, stretchingLine) + for i = 2, count do + table.insert(content, interval) + table.insert(content, stretchingLine) + end + table.insert(content, spacer) return { - props = { - size = util.vector2(size, size), - } + type = ui.TYPE.Flex, + external = { + stretch = 1, + }, + content = ui.content(content), } end -local smallPadding = padding(10) -local bigPadding = padding(25) -local pageHeader = { - props = { - textColor = util.color.rgb(1, 1, 1), - textSize = 30, - }, -} -local groupHeader = { - props = { - textColor = util.color.rgb(1, 1, 1), - textSize = 25, - }, -} -local normal = { - props = { - textColor = util.color.rgb(1, 1, 1), - textSize = 20, - }, -} +local function interlaceSeparator(layouts, separator) + local result = {} + result[1] = layouts[1] + for i = 2, #layouts do + table.insert(result, separator) + table.insert(result, layouts[i]) + end + return result +end + +local function setSettingValue(global, groupKey, settingKey, value) + if global then + core.sendGlobalEvent(common.setGlobalEvent, { + groupKey = groupKey, + settingKey = settingKey, + value = value, + }) + else + storage.playerSection(groupKey):set(settingKey, value) + end +end local function renderSetting(group, setting, value, global) local renderFunction = renderers[setting.renderer] @@ -50,55 +85,47 @@ local function renderSetting(group, setting, value, global) error(('Setting %s of %s has unknown renderer %s'):format(setting.key, group.key, setting.renderer)) end local set = function(value) - if global then - core.sendGlobalEvent(common.setGlobalEvent, { - groupKey = group.key, - settingKey = setting.key, - value = value, - }) - else - storage.playerSection(group.key):set(setting.key, value) - end + setSettingValue(global, group.key, setting.key, value) end local l10n = core.l10n(group.l10n) - return { - name = setting.key, + local titleLayout = { type = ui.TYPE.Flex, content = ui.content { { - type = ui.TYPE.Flex, + template = I.MWUI.templates.textNormal, props = { - horizontal = true, - align = ui.ALIGNMENT.Start, - arrange = ui.ALIGNMENT.End, - }, - content = ui.content { - { - type = ui.TYPE.Text, - template = normal, - props = { - text = l10n(setting.name), - }, - }, - smallPadding, - renderFunction(value, set, setting.argument), - smallPadding, - { - type = ui.TYPE.Text, - template = normal, - props = { - text = 'Reset', - }, - events = { - mouseClick = async:callback(function() - set(setting.default) - end), - }, - }, + text = l10n(setting.name), + textSize = 18, }, }, }, } + if setting.description then + titleLayout.content:add(interval) + titleLayout.content:add { + template = I.MWUI.templates.textNormal, + props = { + text = l10n(setting.description), + textSize = 16, + }, + } + end + return { + name = setting.key, + type = ui.TYPE.Flex, + props = { + horizontal = true, + arrange = ui.ALIGNMENT.Center, + }, + external = { + stretch = 1, + }, + content = ui.content { + titleLayout, + growingIntreval, + renderFunction(value, set, setting.argument), + }, + } end local groupLayoutName = function(key, global) @@ -107,52 +134,101 @@ end local function renderGroup(group, global) local l10n = core.l10n(group.l10n) - local layout = { + + local valueSection = common.getSection(global, group.key) + local settingLayouts = {} + local sortedSettings = {} + for _, setting in pairs(group.settings) do + sortedSettings[setting.order] = setting + end + for _, setting in ipairs(sortedSettings) do + table.insert(settingLayouts, renderSetting(group, setting, valueSection:get(setting.key), global)) + end + local settingsContent = ui.content(interlaceSeparator(settingLayouts, spacedLines(1))) + + local resetButtonLayout = { + template = I.MWUI.templates.box, + content = ui.content { + { + template = I.MWUI.templates.padding, + content = ui.content { + { + template = I.MWUI.templates.textNormal, + props = { + text = 'Reset', + }, + events = { + mouseClick = async:callback(function() + for _, setting in pairs(group.settings) do + setSettingValue(global, group.key, setting.key, setting.default) + end + end), + }, + }, + }, + }, + }, + } + + local titleLayout = { + type = ui.TYPE.Flex, + external = { + stretch = 1, + }, + content = ui.content { + { + template = I.MWUI.templates.textHeader, + props = { + text = l10n(group.name), + textSize = 20, + }, + } + }, + } + if group.description then + titleLayout.content:add(interval) + titleLayout.content:add { + template = I.MWUI.templates.textHeader, + props = { + text = l10n(group.description), + textSize = 18, + }, + } + end + + return { name = groupLayoutName(group.key, global), type = ui.TYPE.Flex, + external = { + stretch = 1, + }, content = ui.content { { type = ui.TYPE.Flex, props = { horizontal = true, - align = ui.ALIGNMENT.Start, - arrange = ui.ALIGNMENT.End, + arrange = ui.ALIGNMENT.Center, + }, + external = { + stretch = 1, }, content = ui.content { - { - name = 'name', - type = ui.TYPE.Text, - template = groupHeader, - props = { - text = l10n(group.name), - }, - }, - smallPadding, - { - name = 'description', - type = ui.TYPE.Text, - template = normal, - props = { - text = l10n(group.description), - }, - }, + titleLayout, + growingIntreval, + resetButtonLayout, }, }, - smallPadding, + spacedLines(2), { name = 'settings', type = ui.TYPE.Flex, - content = ui.content{}, + content = settingsContent, + external = { + stretch = 1, + }, }, - bigPadding, }, } - local settingsContent = layout.content.settings.content - local valueSection = common.getSection(global, group.key) - for _, setting in pairs(group.settings) do - settingsContent:add(renderSetting(group, setting, valueSection:get(setting.key), global)) - end - return layout end local function pageGroupComparator(a, b) @@ -165,16 +241,22 @@ local function generateSearchHints(page) local hints = {} local l10n = core.l10n(page.l10n) table.insert(hints, l10n(page.name)) - table.insert(hints, l10n(page.description)) + if page.description then + table.insert(hints, l10n(page.description)) + end local pageGroups = groups[page.key] for _, pageGroup in pairs(pageGroups) do local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) local l10n = core.l10n(group.l10n) table.insert(hints, l10n(group.name)) - table.insert(hints, l10n(group.description)) + if group.description then + table.insert(hints, l10n(group.description)) + end for _, setting in pairs(group.settings) do table.insert(hints, l10n(setting.name)) - table.insert(hints, l10n(setting.description)) + if setting.description then + table.insert(hints, l10n(setting.description)) + end end end return table.concat(hints, ' ') @@ -182,55 +264,60 @@ end local function renderPage(page) local l10n = core.l10n(page.l10n) + local sortedGroups = {} + for i, v in ipairs(groups[page.key]) do sortedGroups[i] = v end + table.sort(sortedGroups, pageGroupComparator) + local groupLayouts = {} + for _, pageGroup in ipairs(sortedGroups) do + local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) + table.insert(groupLayouts, renderGroup(group, pageGroup.global)) + end + local groupsLayout = { + name = 'groups', + type = ui.TYPE.Flex, + external = { + stretch = 1, + }, + content = ui.content(interlaceSeparator(groupLayouts, bigSpacer)), + } + local titleLayout = { + type = ui.TYPE.Flex, + external = { + stretch = 1, + }, + content = ui.content { + { + template = I.MWUI.templates.textHeader, + props = { + text = l10n(page.name), + textSize = 22, + }, + }, + spacedLines(3), + }, + } + if page.description then + titleLayout.content:add { + template = I.MWUI.templates.textNormal, + props = { + text = l10n(page.description), + textSize = 20, + }, + } + end local layout = { name = page.key, type = ui.TYPE.Flex, + props = { + position = util.vector2(10, 10), + }, content = ui.content { - smallPadding, - { - type = ui.TYPE.Flex, - props = { - horizontal = true, - align = ui.ALIGNMENT.Start, - arrange = ui.ALIGNMENT.End, - }, - content = ui.content { - { - name = 'name', - type = ui.TYPE.Text, - template = pageHeader, - props = { - text = l10n(page.name), - }, - }, - smallPadding, - { - name = 'description', - type = ui.TYPE.Text, - template = normal, - props = { - text = l10n(page.description), - }, - }, - }, - }, - bigPadding, - { - name = 'groups', - type = ui.TYPE.Flex, - content = ui.content {}, - }, + titleLayout, + bigSpacer, + groupsLayout, + bigSpacer, }, } - local groupsContent = layout.content.groups.content - local pageGroups = groups[page.key] - local sortedGroups = {} - for i, v in ipairs(pageGroups) do sortedGroups[i] = v end - table.sort(sortedGroups, pageGroupComparator) - for _, pageGroup in ipairs(sortedGroups) do - local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) - groupsContent:add(renderGroup(group, pageGroup.global)) - end return { name = l10n(page.name), element = ui.create(layout), @@ -241,13 +328,15 @@ end local function onSettingChanged(global) return async:callback(function(groupKey, settingKey) local group = common.getSection(global, common.groupSectionKey):get(groupKey) - if not pageOptions[group.page] then return end + if not group or not pageOptions[group.page] then return end + + local value = common.getSection(global, group.key):get(settingKey) local element = pageOptions[group.page].element - local groupLayout = element.layout.content.groups.content[groupLayoutName(group.key, global)] - local settingsLayout = groupLayout.content.settings - local value = common.getSection(global, group.key):get(settingKey) - settingsLayout.content[settingKey] = renderSetting(group, group.settings[settingKey], value, global) + local groupsLayout = element.layout.content.groups + local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] + local settingsContent = groupLayout.content.settings.content + settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) element:update() end) end @@ -293,8 +382,8 @@ local function registerPage(options) if type(options.name) ~= 'string' then error('Page must have a name') end - if type(options.description) ~= 'string' then - error('Page must have a description') + if options.description ~= nil and type(options.description) ~= 'string' then + error('Page description key must be a string') end local page = { key = options.key, diff --git a/files/builtin_scripts/scripts/omw/settings/renderers.lua b/files/builtin_scripts/scripts/omw/settings/renderers.lua new file mode 100644 index 0000000000..9b862c3b4b --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/renderers.lua @@ -0,0 +1,39 @@ +local ui = require('openmw.ui') +local async = require('openmw.async') +local I = require('openmw.interfaces') + +return function(registerRenderer) + registerRenderer('textLine', function(value, set) + return { + template = I.MWUI.templates.textEditLine, + props = { + text = tostring(value), + }, + events = { + textChanged = async:callback(function(s) set(s) end), + }, + } + end) + + registerRenderer('yesNo', function(value, set) + return { + template = I.MWUI.templates.box, + content = ui.content { + { + template = I.MWUI.templates.padding, + content = ui.content { + { + template = I.MWUI.templates.textNormal, + props = { + text = value and 'Yes' or 'No', + }, + events = { + mouseClick = async:callback(function() set(not value) end), + }, + }, + }, + }, + }, + } + end) +end \ No newline at end of file diff --git a/files/mygui/openmw_lua.xml b/files/mygui/openmw_lua.xml index 6e5e232a2a..b792b1c21f 100644 --- a/files/mygui/openmw_lua.xml +++ b/files/mygui/openmw_lua.xml @@ -12,7 +12,7 @@ - +