1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-06-03 01:41:32 +00:00

Styling for Settings

This commit is contained in:
uramer 2022-05-14 10:27:30 +00:00 committed by Petr Mikheev
parent 91c32e65c2
commit 52d05be04b
28 changed files with 738 additions and 394 deletions

View file

@ -7,11 +7,6 @@ namespace LuaUi
void LuaContainer::updateChildren() void LuaContainer::updateChildren()
{ {
WidgetExtension::updateChildren(); WidgetExtension::updateChildren();
for (auto w : children())
{
w->onCoordChange([this](WidgetExtension* child, MyGUI::IntCoord coord)
{ updateSizeToFit(); });
}
updateSizeToFit(); updateSizeToFit();
} }
@ -20,16 +15,39 @@ namespace LuaUi
return MyGUI::IntSize(); return MyGUI::IntSize();
} }
MyGUI::IntSize LuaContainer::templateScalingSize()
{
return mInnerSize;
}
void LuaContainer::updateSizeToFit() void LuaContainer::updateSizeToFit()
{ {
MyGUI::IntSize size; MyGUI::IntSize innerSize = MyGUI::IntSize();
for (auto w : children()) for (auto w : children())
{ {
MyGUI::IntCoord coord = w->widget()->getCoord(); MyGUI::IntCoord coord = w->calculateCoord();
size.width = std::max(size.width, coord.left + coord.width); innerSize.width = std::max(innerSize.width, coord.left + coord.width);
size.height = std::max(size.height, coord.top + coord.height); innerSize.height = std::max(innerSize.height, coord.top + coord.height);
} }
forceSize(size); MyGUI::IntSize outerSize = innerSize;
updateCoord(); 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();
} }
} }

View file

@ -9,12 +9,18 @@ namespace LuaUi
{ {
MYGUI_RTTI_DERIVED(LuaContainer) MYGUI_RTTI_DERIVED(LuaContainer)
MyGUI::IntSize calculateSize() override;
void updateCoord() override;
protected: protected:
void updateChildren() override; void updateChildren() override;
MyGUI::IntSize childScalingSize() override; MyGUI::IntSize childScalingSize() override;
MyGUI::IntSize templateScalingSize() override;
private: private:
void updateSizeToFit(); void updateSizeToFit();
MyGUI::IntSize mInnerSize;
MyGUI::IntSize mOuterSize;
}; };
} }

View file

@ -24,7 +24,18 @@ namespace LuaUi
std::string widgetType(const sol::table& layout) 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) void destroyWidget(LuaUi::WidgetExtension* ext)
@ -103,18 +114,8 @@ namespace LuaUi
WidgetExtension* createWidget(const sol::table& layout) 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(); static auto widgetTypeMap = widgetTypeToName();
std::string type = widgetType(layout);
if (widgetTypeMap.find(type) == widgetTypeMap.end()) if (widgetTypeMap.find(type) == widgetTypeMap.end())
throw std::logic_error(std::string("Invalid widget type ") += type); throw std::logic_error(std::string("Invalid widget type ") += type);
@ -242,7 +243,7 @@ namespace LuaUi
if (!mLayer.empty()) if (!mLayer.empty())
Log(Debug::Warning) << "Ignoring element's layer " << mLayer << " because it's attached to a widget"; Log(Debug::Warning) << "Ignoring element's layer " << mLayer << " because it's attached to a widget";
mAttachedTo->setChildren({ mRoot }); mAttachedTo->setChildren({ mRoot });
mRoot->updateCoord(); mAttachedTo->updateCoord();
} }
} }
} }

View file

@ -56,7 +56,7 @@ namespace LuaUi
MyGUI::IntSize flexSize = calculateSize(); MyGUI::IntSize flexSize = calculateSize();
int growSize = 0; int growSize = 0;
float growFactor = 0; float growFactor = 0;
if (totalGrow > 0 && !mAutoSized) if (totalGrow > 0)
{ {
growSize = primary(flexSize) - primary(childrenSize); growSize = primary(flexSize) - primary(childrenSize);
growFactor = growSize / totalGrow; growFactor = growSize / totalGrow;
@ -67,22 +67,32 @@ namespace LuaUi
for (auto* w : children()) for (auto* w : children())
{ {
MyGUI::IntSize size = w->calculateSize(); MyGUI::IntSize size = w->calculateSize();
primary(size) += static_cast<int>(growFactor * getGrow(w));
float stretch = std::clamp(w->externalValue("stretch", 0.0f), 0.0f, 1.0f);
secondary(size) = std::max(secondary(size), static_cast<int>(stretch * secondary(flexSize)));
secondary(childPosition) = alignSize(secondary(flexSize), secondary(size), mArrange); secondary(childPosition) = alignSize(secondary(flexSize), secondary(size), mArrange);
w->forcePosition(childPosition); w->forcePosition(childPosition);
primary(size) += static_cast<int>(growFactor * getGrow(w));
w->forceSize(size); w->forceSize(size);
w->updateCoord(); w->updateCoord();
primary(childPosition) += primary(size); primary(childPosition) += primary(size);
w->updateCoord();
} }
WidgetExtension::updateChildren(); 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 LuaFlex::calculateSize()
{ {
MyGUI::IntSize size = WidgetExtension::calculateSize(); MyGUI::IntSize size = WidgetExtension::calculateSize();
if (mAutoSized) { if (mAutoSized) {
primary(size) = primary(mChildrenSize); primary(size) = std::max(primary(size), primary(mChildrenSize));
secondary(size) = std::max(secondary(size), secondary(mChildrenSize)); secondary(size) = std::max(secondary(size), secondary(mChildrenSize));
} }
return size; return size;

View file

@ -14,10 +14,8 @@ namespace LuaUi
MyGUI::IntSize calculateSize() override; MyGUI::IntSize calculateSize() override;
void updateProperties() override; void updateProperties() override;
void updateChildren() override; void updateChildren() override;
MyGUI::IntSize childScalingSize() override MyGUI::IntSize childScalingSize() override;
{
return MyGUI::IntSize();
}
void updateCoord() override; void updateCoord() override;
private: private:

View file

@ -31,6 +31,7 @@ namespace LuaUi
{ {
changeWidgetSkin("LuaImage"); changeWidgetSkin("LuaImage");
mTileRect = dynamic_cast<LuaTileRect*>(getSubWidgetMain()); mTileRect = dynamic_cast<LuaTileRect*>(getSubWidgetMain());
WidgetExtension::initialize();
} }
void LuaImage::updateProperties() void LuaImage::updateProperties()

View file

@ -6,38 +6,57 @@ namespace LuaUi
{ {
void LuaTextEdit::initialize() void LuaTextEdit::initialize()
{ {
changeWidgetSkin("LuaTextEdit"); mEditBox = createWidget<MyGUI::EditBox>("LuaTextEdit", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default);
mEditBox->eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange);
eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); registerEvents(mEditBox);
WidgetExtension::initialize(); WidgetExtension::initialize();
} }
void LuaTextEdit::deinitialize() void LuaTextEdit::deinitialize()
{ {
eventEditTextChange -= MyGUI::newDelegate(this, &LuaTextEdit::textChange); mEditBox->eventEditTextChange -= MyGUI::newDelegate(this, &LuaTextEdit::textChange);
clearEvents(mEditBox);
WidgetExtension::deinitialize(); WidgetExtension::deinitialize();
} }
void LuaTextEdit::updateProperties() void LuaTextEdit::updateProperties()
{ {
setCaption(propertyValue("text", std::string())); mEditBox->setCaption(propertyValue("text", std::string()));
setFontHeight(propertyValue("textSize", 10)); mEditBox->setFontHeight(propertyValue("textSize", 10));
setTextColour(propertyValue("textColor", MyGUI::Colour(0, 0, 0, 1))); mEditBox->setTextColour(propertyValue("textColor", MyGUI::Colour(0, 0, 0, 1)));
setEditMultiLine(propertyValue("multiline", false)); mEditBox->setEditMultiLine(propertyValue("multiline", false));
setEditWordWrap(propertyValue("wordWrap", false)); mEditBox->setEditWordWrap(propertyValue("wordWrap", false));
Alignment horizontal(propertyValue("textAlignH", Alignment::Start)); Alignment horizontal(propertyValue("textAlignH", Alignment::Start));
Alignment vertical(propertyValue("textAlignV", 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(); WidgetExtension::updateProperties();
} }
void LuaTextEdit::textChange(MyGUI::EditBox*) 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);
} }
} }

View file

@ -7,7 +7,7 @@
namespace LuaUi namespace LuaUi
{ {
class LuaTextEdit : public MyGUI::EditBox, public WidgetExtension class LuaTextEdit : public MyGUI::Widget, public WidgetExtension
{ {
MYGUI_RTTI_DERIVED(LuaTextEdit) MYGUI_RTTI_DERIVED(LuaTextEdit)
@ -15,9 +15,13 @@ namespace LuaUi
void initialize() override; void initialize() override;
void deinitialize() override; void deinitialize() override;
void updateProperties() override; void updateProperties() override;
void updateCoord() override;
void updateChildren() override;
private: private:
void textChange(MyGUI::EditBox*); void textChange(MyGUI::EditBox*);
MyGUI::EditBox* mEditBox;
}; };
} }

View file

@ -39,6 +39,7 @@ namespace LuaUi
{ "LuaWindow", "Window" }, { "LuaWindow", "Window" },
{ "LuaImage", "Image" }, { "LuaImage", "Image" },
{ "LuaFlex", "Flex" }, { "LuaFlex", "Flex" },
{ "LuaContainer", "Container" },
}; };
return types; return types;
} }

View file

@ -35,37 +35,13 @@ namespace LuaUi
void WidgetExtension::initialize() void WidgetExtension::initialize()
{ {
// \todo might be more efficient to only register these if there are Lua callbacks // \todo might be more efficient to only register these if there are Lua callbacks
mWidget->eventKeyButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::keyPress); registerEvents(mWidget);
mWidget->eventKeyButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::keyRelease);
mWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &WidgetExtension::mouseClick);
mWidget->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WidgetExtension::mouseDoubleClick);
mWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::mousePress);
mWidget->eventMouseButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::mouseRelease);
mWidget->eventMouseMove += MyGUI::newDelegate(this, &WidgetExtension::mouseMove);
mWidget->eventMouseDrag += MyGUI::newDelegate(this, &WidgetExtension::mouseDrag);
mWidget->eventMouseSetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain);
mWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss);
mWidget->eventKeySetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain);
mWidget->eventKeyLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss);
} }
void WidgetExtension::deinitialize() void WidgetExtension::deinitialize()
{ {
clearCallbacks(); clearCallbacks();
mWidget->eventKeyButtonPressed.clear(); clearEvents(mWidget);
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();
mOnCoordChange.reset(); mOnCoordChange.reset();
@ -75,6 +51,39 @@ namespace LuaUi
w->deinitialize(); 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() void WidgetExtension::reset()
{ {
// detach all children from the slot widget, in case it gets destroyed // detach all children from the slot widget, in case it gets destroyed
@ -188,25 +197,11 @@ namespace LuaUi
void WidgetExtension::updateTemplate() void WidgetExtension::updateTemplate()
{ {
WidgetExtension* oldSlot = mSlot;
WidgetExtension* slot = findDeepInTemplates("slot"); WidgetExtension* slot = findDeepInTemplates("slot");
if (slot == nullptr) if (slot == nullptr)
mSlot = this; mSlot = this;
else else
mSlot = slot->mSlot; 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) void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback)
@ -290,10 +285,12 @@ namespace LuaUi
MyGUI::IntSize WidgetExtension::parentSize() MyGUI::IntSize WidgetExtension::parentSize()
{ {
if (mParent && !mTemplateChild) if (!mParent)
return mParent->childScalingSize(); return widget()->getParentSize(); // size of the layer
if (mTemplateChild)
return mParent->templateScalingSize();
else else
return widget()->getParentSize(); return mParent->childScalingSize();
} }
MyGUI::IntSize WidgetExtension::calculateSize() MyGUI::IntSize WidgetExtension::calculateSize()
@ -334,6 +331,11 @@ namespace LuaUi
return mSlot->widget()->getSize(); return mSlot->widget()->getSize();
} }
MyGUI::IntSize WidgetExtension::templateScalingSize()
{
return widget()->getSize();
}
void WidgetExtension::triggerEvent(std::string_view name, sol::object argument) const void WidgetExtension::triggerEvent(std::string_view name, sol::object argument) const
{ {
auto it = mCallbacks.find(name); auto it = mCallbacks.find(name);

View file

@ -70,16 +70,20 @@ namespace LuaUi
virtual MyGUI::IntSize calculateSize(); virtual MyGUI::IntSize calculateSize();
virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size);
MyGUI::IntCoord calculateCoord();
protected: protected:
virtual void initialize(); virtual void initialize();
void registerEvents(MyGUI::Widget* w);
void clearEvents(MyGUI::Widget* w);
sol::table makeTable() const; sol::table makeTable() const;
sol::object keyEvent(MyGUI::KeyCode) const; sol::object keyEvent(MyGUI::KeyCode) const;
sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const;
MyGUI::IntSize parentSize(); MyGUI::IntSize parentSize();
MyGUI::IntCoord calculateCoord();
virtual MyGUI::IntSize childScalingSize(); virtual MyGUI::IntSize childScalingSize();
virtual MyGUI::IntSize templateScalingSize();
template<typename T> template<typename T>
T propertyValue(std::string_view name, const T& defaultValue) T propertyValue(std::string_view name, const T& defaultValue)

View file

@ -111,3 +111,6 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid
* - :ref:`Settings <Interface Settings>` * - :ref:`Settings <Interface Settings>`
- by player and global scripts - by player and global scripts
- Save, display and track changes of setting values. - Save, display and track changes of setting values.
* - :ref:`MWUI <Interface MWUI>`
- by player scripts
- Morrowind-style UI templates.

View file

@ -476,6 +476,9 @@ The order in which the scripts are started is important. So if one mod should ov
* - :ref:`Settings <Interface Settings>` * - :ref:`Settings <Interface Settings>`
- by player and global scripts - by player and global scripts
- Save, display and track changes of setting values. - Save, display and track changes of setting values.
* - :ref:`MWUI <Interface MWUI>`
- by player scripts
- Morrowind-style UI templates.
Event system Event system
============ ============

View file

@ -82,6 +82,7 @@ Widget types
TextEdit: Accepts text input from the user. <widgets/textedit> TextEdit: Accepts text input from the user. <widgets/textedit>
Image: Renders a texture. <widgets/image> Image: Renders a texture. <widgets/image>
Flex: Aligns children in a column/row <widgets/flex> Flex: Aligns children in a column/row <widgets/flex>
Container: Wraps around its children <widgets/container>
Example Example
------- -------

View file

@ -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.

View file

@ -15,7 +15,8 @@ Properties
- description - description
* - horizontal * - horizontal
- bool (false) - 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 * - autoSize
- bool (true) - bool (true)
- | If true, Flex will automatically resize to fit its contents. - | 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, - | Grow factor for the child. If there is unused space in the Flex,
| it will be split between widgets according to this value. | it will be split between widgets according to this value.
| Has no effect if `autoSize` is `true`. | Has no effect if `autoSize` is `true`.
* - stretch
- float (0)
- | Stretches the child to a percentage of the Flex's cross axis size.

View file

@ -21,6 +21,7 @@ set(LUA_BUILTIN_FILES
scripts/omw/settings/global.lua scripts/omw/settings/global.lua
scripts/omw/settings/common.lua scripts/omw/settings/common.lua
scripts/omw/settings/render.lua scripts/omw/settings/render.lua
scripts/omw/settings/renderers.lua
l10n/Calendar/en.yaml l10n/Calendar/en.yaml
@ -29,6 +30,7 @@ set(LUA_BUILTIN_FILES
scripts/omw/mwui/box.lua scripts/omw/mwui/box.lua
scripts/omw/mwui/text.lua scripts/omw/mwui/text.lua
scripts/omw/mwui/textEdit.lua scripts/omw/mwui/textEdit.lua
scripts/omw/mwui/space.lua
scripts/omw/mwui/init.lua scripts/omw/mwui/init.lua
) )

View file

@ -1,120 +1,203 @@
local ui = require('openmw.ui') local ui = require('openmw.ui')
local util = require('openmw.util') local util = require('openmw.util')
local auxUi = require('openmw_aux.ui')
local constants = require('scripts.omw.mwui.constants') local constants = require('scripts.omw.mwui.constants')
local v2 = util.vector2 local v2 = util.vector2
local whiteTexture = ui.texture{ path = 'white' }
local menuTransparency = ui._getMenuTransparency()
local sideParts = { local sideParts = {
left = util.vector2(0, 0.5), left = v2(0, 0),
right = util.vector2(1, 0.5), right = v2(1, 0),
top = util.vector2(0.5, 0), top = v2(0, 0),
bottom = util.vector2(0.5, 1), bottom = v2(0, 1),
} }
local cornerParts = { local cornerParts = {
top_left_corner = util.vector2(0, 0), top_left = v2(0, 0),
top_right_corner = util.vector2(1, 0), top_right = v2(1, 0),
bottom_left_corner = util.vector2(0, 1), bottom_left = v2(0, 1),
bottom_right_corner = util.vector2(1, 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 do
local boxBorderPattern = 'textures/menu_thin_border_%s.dds' for k in pairs(sideParts) do
for k, _ in pairs(sideParts) do borderResources[k] = ui.texture{ path = borderSidePattern:format(k) }
resources[k] = ui.texture{ path = boxBorderPattern:format(k) }
end end
for k, _ in pairs(cornerParts) do for k in pairs(cornerParts) do
resources[k] = ui.texture{ path = boxBorderPattern:format(k) } borderResources[k] = ui.texture{ path = borderCornerPattern:format(k) }
end end
end end
local borderPieces = {} local borderPieces = {}
for k, align in pairs(sideParts) do for k in pairs(sideParts) do
local resource = resources[k] local horizontal = k == 'top' or k == 'bottom'
local horizontal = align.x ~= 0.5 borderPieces[k] = {
borderPieces[#borderPieces + 1] = {
type = ui.TYPE.Image, type = ui.TYPE.Image,
props = { props = {
resource = resource, resource = borderResources[k],
relativePosition = align, tileH = horizontal,
anchor = align, tileV = not horizontal,
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, end
tileV = horizontal, for k in pairs(cornerParts) do
borderPieces[k] = {
type = ui.TYPE.Image,
props = {
resource = borderResources[k],
}, },
} }
end 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, type = ui.TYPE.Image,
props = { props = {
resource = resource, resource = borderResources.top,
relativePosition = align, tileH = true,
anchor = align, tileV = false,
size = v2(1, 1) * constants.borderSize, 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 end
borderPieces[#borderPieces + 1] = { local thinBorders = borderTemplates(constants.border)
external = { local thickBorders = borderTemplates(constants.thickBorder)
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),
},
},
},
}
return function(templates) return function(templates)
templates.borders = borders for k, t in pairs(thinBorders) do
templates.horizontalLine = horizontalLine templates[k] = t
templates.verticalLine = verticalLine end
for k, t in pairs(thickBorders) do
templates[k .. 'Thick'] = t
end
end end

View file

@ -2,7 +2,10 @@ local util = require('openmw.util')
return { return {
textNormalSize = 16, textNormalSize = 16,
sandColor = util.color.rgb(202 / 255, 165 / 255, 96 / 255), textHeaderSize = 16,
borderSize = 4, 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, padding = 2,
} }

View file

@ -58,20 +58,26 @@ end
local templates = {} local templates = {}
--- ---
-- Standard rectangular border -- Container that adds padding around its content.
-- @field [parent=#Templates] openmw.ui#Layout border -- @field [parent=#MWUI] #table padding
require('scripts.omw.mwui.borders')(templates) ---
-- 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 -- @field [parent=#Templates] openmw.ui#Layout box
--- ---
-- A transparent background -- Same as box, but with a semi-transparent background
-- @field [parent=#Templates] openmw.ui#Layout backgroundTransparent -- @field [parent=#Templates] openmw.ui#Layout boxTransparent
--- ---
-- A solid, non-transparent background -- Same as box, but with a solid background
-- @field [parent=#Templates] openmw.ui#Layout backgroundSolid -- @field [parent=#Templates] openmw.ui#Layout boxSolid
require('scripts.omw.mwui.box')(templates) require('scripts.omw.mwui.borders')(templates)
--- ---
-- Standard "sand" colored text -- Standard "sand" colored text

View file

@ -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

View file

@ -6,10 +6,19 @@ local textNormal = {
type = ui.TYPE.Text, type = ui.TYPE.Text,
props = { props = {
textSize = constants.textNormalSize, 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) return function(templates)
templates.textNormal = textNormal templates.textNormal = textNormal
templates.textHeader = textHeader
end end

View file

@ -3,6 +3,8 @@ local ui = require('openmw.ui')
local constants = require('scripts.omw.mwui.constants') local constants = require('scripts.omw.mwui.constants')
local borderOffset = util.vector2(1, 1) * constants.border
return function(templates) return function(templates)
local borderContent = ui.content { local borderContent = ui.content {
{ {
@ -12,10 +14,14 @@ return function(templates)
}, },
content = ui.content { content = ui.content {
{ {
props = {
position = borderOffset,
relativeSize = util.vector2(1, 1),
},
external = { external = {
slot = true, slot = true,
}, },
}, }
} }
}, },
} }
@ -23,10 +29,9 @@ return function(templates)
templates.textEditLine = { templates.textEditLine = {
type = ui.TYPE.TextEdit, type = ui.TYPE.TextEdit,
props = { props = {
size = util.vector2(150, constants.textNormalSize) + borderOffset * 4,
textSize = constants.textNormalSize, textSize = constants.textNormalSize,
textColor = constants.sandColor, textColor = constants.normalColor,
textAlignH = ui.ALIGNMENT.Start,
textAlignV = ui.ALIGNMENT.Center,
multiline = false, multiline = false,
}, },
content = borderContent, content = borderContent,
@ -35,10 +40,9 @@ return function(templates)
templates.textEditBox = { templates.textEditBox = {
type = ui.TYPE.TextEdit, type = ui.TYPE.TextEdit,
props = { props = {
size = util.vector2(150, 5 * constants.textNormalSize) + borderOffset * 4,
textSize = constants.textNormalSize, textSize = constants.textNormalSize,
textColor = constants.sandColor, textColor = constants.normalColor,
textAlignH = ui.ALIGNMENT.Start,
textAlignV = ui.ALIGNMENT.Start,
multiline = true, multiline = true,
wordWrap = true, wordWrap = true,
}, },

View file

@ -21,8 +21,8 @@ local function validateSettingOptions(options)
if type(options.name) ~= 'string' then if type(options.name) ~= 'string' then
error('Setting must have a name localization key') error('Setting must have a name localization key')
end end
if type(options.description) ~= 'string' then if options.description ~= nil and type(options.description) ~= 'string' then
error('Setting must have a descripiton localization key') error('Setting description key must be a string')
end end
end end
@ -49,8 +49,8 @@ local function validateGroupOptions(options)
if type(options.name) ~= 'string' then if type(options.name) ~= 'string' then
error('Group must have a name localization key') error('Group must have a name localization key')
end end
if type(options.description) ~= 'string' then if options.description ~= nil and type(options.description) ~= 'string' then
error('Group must have a description localization key') error('Group description key must be a string')
end end
if type(options.settings) ~= 'table' then if type(options.settings) ~= 'table' then
error('Group must have a table of settings') error('Group must have a table of settings')
@ -89,8 +89,9 @@ local function registerGroup(options)
settings = {}, settings = {},
} }
local valueSection = contextSection(options.key) local valueSection = contextSection(options.key)
for _, opt in ipairs(options.settings) do for i, opt in ipairs(options.settings) do
local setting = registerSetting(opt) local setting = registerSetting(opt)
setting.order = i
if group.settings[setting.key] then if group.settings[setting.key] then
error(('Duplicate setting key %s'):format(options.key)) error(('Duplicate setting key %s'):format(options.key))
end end
@ -123,7 +124,7 @@ return {
local section = contextSection(groupKey) local section = contextSection(groupKey)
saved[groupKey] = {} saved[groupKey] = {}
for key, value in pairs(section:asTable()) do 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 saved[groupKey][key] = value
end end
end end

View file

@ -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 common = require('scripts.omw.settings.common')
local render = require('scripts.omw.settings.render') local render = require('scripts.omw.settings.render')
render.registerRenderer('text', function(value, set, arg) require('scripts.omw.settings.renderers')(render.registerRenderer)
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)
--- ---
-- @type PageOptions -- @type PageOptions
-- @field #string key A unique key -- @field #string key A unique key
-- @field #string l10n A localization context (an argument of core.l10n) -- @field #string l10n A localization context (an argument of core.l10n)
-- @field #string name A key from the localization context -- @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 -- @type GroupOptions
-- @field #string key A unique key, starts with "Settings" by convention -- @field #string key A unique key, starts with "Settings" by convention
-- @field #string l10n A localization context (an argument of core.l10n) -- @field #string l10n A localization context (an argument of core.l10n)
-- @field #string name A key from the localization context -- @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 #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. -- @field #number order Groups within the same page are sorted by this number, or their key for equal values.
-- Defaults to 0. -- Defaults to 0.
@ -43,7 +25,7 @@ end)
-- @type SettingOptions -- @type SettingOptions
-- @field #string key A unique key -- @field #string key A unique key
-- @field #string name A key from the localization context -- @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 default A default value
-- @field #string renderer A renderer key -- @field #string renderer A renderer key
-- @field argument An argument for the renderer -- @field argument An argument for the renderer
@ -57,26 +39,33 @@ return {
-- -- In a player script -- -- In a player script
-- local storage = require('openmw.storage') -- local storage = require('openmw.storage')
-- local I = require('openmw.interfaces') -- 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', -- key = 'SettingsPlayerMyMod',
-- page = 'MyPage', -- page = 'MyModPage',
-- l10n = 'mymod', -- l10n = 'MyMod',
-- name = 'modName', -- name = 'My Group Name',
-- description = 'modDescription', -- description = 'My Group Description',
-- settings = { -- settings = {
-- { -- {
-- key = 'Greeting', -- key = 'Greeting',
-- renderer = 'text', -- renderer = 'textLine',
-- name = 'greetingName', -- name = 'Greeting',
-- description = 'greetingDescription', -- description = 'Text to display when the game starts',
-- default = 'Hello, world!', -- default = 'Hello, world!',
-- argument = { -- permanentStorage = false,
-- size = 200,
-- },
-- }, -- },
-- }, -- },
-- }) -- }
-- local playerSettings = storage.playerSection('SettingsPlayerMyMod') -- local playerSettings = storage.playerSection('SettingsPlayerMyMod')
-- ...
-- ui.showMessage(playerSettings:get('Greeting'))
-- -- ...
-- -- access a setting page registered by a global script -- -- access a setting page registered by a global script
-- local globalSettings = storage.globalSection('SettingsGlobalMyMod') -- local globalSettings = storage.globalSection('SettingsGlobalMyMod')
interface = { interface = {
@ -132,22 +121,19 @@ return {
-- settings = { -- settings = {
-- { -- {
-- key = 'Greeting', -- key = 'Greeting',
-- saveOnly = true, -- permanentStorage = true,
-- default = 'Hi', -- default = 'Hi',
-- renderer = 'text', -- renderer = 'textLine',
-- argument = {
-- size = 200,
-- },
-- name = 'Text Input', -- name = 'Text Input',
-- description = 'Short text input', -- description = 'Short text input',
-- }, -- },
-- { -- {
-- key = 'Key', -- key = 'Flag',
-- saveOnly = false, -- permanentStorage = false,
-- default = input.KEY.LeftAlt, -- default = false,
-- renderer = 'keybind', -- renderer = 'yeNo',
-- name = 'Key', -- name = 'Flag',
-- description = 'Bind Key', -- description = 'Flag toggle',
-- }, -- },
-- } -- }
-- } -- }

View file

@ -3,6 +3,7 @@ local util = require('openmw.util')
local async = require('openmw.async') local async = require('openmw.async')
local core = require('openmw.core') local core = require('openmw.core')
local storage = require('openmw.storage') local storage = require('openmw.storage')
local I = require('openmw.interfaces')
local common = require('scripts.omw.settings.common') local common = require('scripts.omw.settings.common')
@ -15,34 +16,68 @@ local pages = {}
local groups = {} local groups = {}
local pageOptions = {} 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 { return {
props = { type = ui.TYPE.Flex,
size = util.vector2(size, size), external = {
} stretch = 1,
},
content = ui.content(content),
} }
end end
local smallPadding = padding(10)
local bigPadding = padding(25)
local pageHeader = { local function interlaceSeparator(layouts, separator)
props = { local result = {}
textColor = util.color.rgb(1, 1, 1), result[1] = layouts[1]
textSize = 30, for i = 2, #layouts do
}, table.insert(result, separator)
} table.insert(result, layouts[i])
local groupHeader = { end
props = { return result
textColor = util.color.rgb(1, 1, 1), end
textSize = 25,
}, local function setSettingValue(global, groupKey, settingKey, value)
} if global then
local normal = { core.sendGlobalEvent(common.setGlobalEvent, {
props = { groupKey = groupKey,
textColor = util.color.rgb(1, 1, 1), settingKey = settingKey,
textSize = 20, value = value,
}, })
} else
storage.playerSection(groupKey):set(settingKey, value)
end
end
local function renderSetting(group, setting, value, global) local function renderSetting(group, setting, value, global)
local renderFunction = renderers[setting.renderer] 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)) error(('Setting %s of %s has unknown renderer %s'):format(setting.key, group.key, setting.renderer))
end end
local set = function(value) local set = function(value)
if global then setSettingValue(global, group.key, setting.key, value)
core.sendGlobalEvent(common.setGlobalEvent, {
groupKey = group.key,
settingKey = setting.key,
value = value,
})
else
storage.playerSection(group.key):set(setting.key, value)
end
end end
local l10n = core.l10n(group.l10n) local l10n = core.l10n(group.l10n)
return { local titleLayout = {
name = setting.key,
type = ui.TYPE.Flex, type = ui.TYPE.Flex,
content = ui.content { content = ui.content {
{ {
type = ui.TYPE.Flex, template = I.MWUI.templates.textNormal,
props = { props = {
horizontal = true, text = l10n(setting.name),
align = ui.ALIGNMENT.Start, textSize = 18,
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),
},
},
}, },
}, },
}, },
} }
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 end
local groupLayoutName = function(key, global) local groupLayoutName = function(key, global)
@ -107,52 +134,101 @@ end
local function renderGroup(group, global) local function renderGroup(group, global)
local l10n = core.l10n(group.l10n) 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), name = groupLayoutName(group.key, global),
type = ui.TYPE.Flex, type = ui.TYPE.Flex,
external = {
stretch = 1,
},
content = ui.content { content = ui.content {
{ {
type = ui.TYPE.Flex, type = ui.TYPE.Flex,
props = { props = {
horizontal = true, horizontal = true,
align = ui.ALIGNMENT.Start, arrange = ui.ALIGNMENT.Center,
arrange = ui.ALIGNMENT.End, },
external = {
stretch = 1,
}, },
content = ui.content { content = ui.content {
{ titleLayout,
name = 'name', growingIntreval,
type = ui.TYPE.Text, resetButtonLayout,
template = groupHeader,
props = {
text = l10n(group.name),
},
},
smallPadding,
{
name = 'description',
type = ui.TYPE.Text,
template = normal,
props = {
text = l10n(group.description),
},
},
}, },
}, },
smallPadding, spacedLines(2),
{ {
name = 'settings', name = 'settings',
type = ui.TYPE.Flex, 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 end
local function pageGroupComparator(a, b) local function pageGroupComparator(a, b)
@ -165,16 +241,22 @@ local function generateSearchHints(page)
local hints = {} local hints = {}
local l10n = core.l10n(page.l10n) local l10n = core.l10n(page.l10n)
table.insert(hints, l10n(page.name)) 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] local pageGroups = groups[page.key]
for _, pageGroup in pairs(pageGroups) do for _, pageGroup in pairs(pageGroups) do
local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key)
local l10n = core.l10n(group.l10n) local l10n = core.l10n(group.l10n)
table.insert(hints, l10n(group.name)) 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 for _, setting in pairs(group.settings) do
table.insert(hints, l10n(setting.name)) 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
end end
return table.concat(hints, ' ') return table.concat(hints, ' ')
@ -182,55 +264,60 @@ end
local function renderPage(page) local function renderPage(page)
local l10n = core.l10n(page.l10n) 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 = { local layout = {
name = page.key, name = page.key,
type = ui.TYPE.Flex, type = ui.TYPE.Flex,
props = {
position = util.vector2(10, 10),
},
content = ui.content { content = ui.content {
smallPadding, titleLayout,
{ bigSpacer,
type = ui.TYPE.Flex, groupsLayout,
props = { bigSpacer,
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 {},
},
}, },
} }
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 { return {
name = l10n(page.name), name = l10n(page.name),
element = ui.create(layout), element = ui.create(layout),
@ -241,13 +328,15 @@ end
local function onSettingChanged(global) local function onSettingChanged(global)
return async:callback(function(groupKey, settingKey) return async:callback(function(groupKey, settingKey)
local group = common.getSection(global, common.groupSectionKey):get(groupKey) 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 element = pageOptions[group.page].element
local groupLayout = element.layout.content.groups.content[groupLayoutName(group.key, global)] local groupsLayout = element.layout.content.groups
local settingsLayout = groupLayout.content.settings local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)]
local value = common.getSection(global, group.key):get(settingKey) local settingsContent = groupLayout.content.settings.content
settingsLayout.content[settingKey] = renderSetting(group, group.settings[settingKey], value, global) settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global)
element:update() element:update()
end) end)
end end
@ -293,8 +382,8 @@ local function registerPage(options)
if type(options.name) ~= 'string' then if type(options.name) ~= 'string' then
error('Page must have a name') error('Page must have a name')
end end
if type(options.description) ~= 'string' then if options.description ~= nil and type(options.description) ~= 'string' then
error('Page must have a description') error('Page description key must be a string')
end end
local page = { local page = {
key = options.key, key = options.key,

View file

@ -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

View file

@ -12,7 +12,7 @@
</Resource> </Resource>
<Resource type="ResourceSkin" name="LuaTextEditClient" size="16 16"> <Resource type="ResourceSkin" name="LuaTextEditClient" size="16 16">
<BasisSkin type="EditText" offset="0 0 16 16" align="Stretch" name="Client" /> <BasisSkin type="EditText" offset="0 0 16 16" align="Stretch" />
</Resource> </Resource>
<Resource type="ResourceSkin" name="LuaTextEdit" size="16 16"> <Resource type="ResourceSkin" name="LuaTextEdit" size="16 16">