diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f23d54eb56..6baefe60ec 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -388,7 +388,7 @@ add_component_dir (fallback add_component_dir (lua_ui registerscriptsettings scriptsettings properties widget element util layers content alignment resources - adapter text textedit window image container flex + adapter text textedit window image container flex scrollview ) copy_resource_file("lua_ui/content.lua" "${OPENMW_RESOURCES_ROOT}" "resources/lua_libs/content.lua") diff --git a/components/lua_ui/container.cpp b/components/lua_ui/container.cpp index 1999be8169..55f83a2ab5 100644 --- a/components/lua_ui/container.cpp +++ b/components/lua_ui/container.cpp @@ -29,6 +29,12 @@ namespace LuaUi innerSize.width = std::max(innerSize.width, coord.left + coord.width); innerSize.height = std::max(innerSize.height, coord.top + coord.height); } + for (auto w : slotChildren()) + { + 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); + } MyGUI::IntSize outerSize = innerSize; for (const auto w : templateChildren()) { diff --git a/components/lua_ui/scrollview.cpp b/components/lua_ui/scrollview.cpp new file mode 100644 index 0000000000..bcd91ab453 --- /dev/null +++ b/components/lua_ui/scrollview.cpp @@ -0,0 +1,161 @@ +#include "scrollview.hpp" + +namespace LuaUi +{ + void LuaScrollView::initialize() + { + mScrollGrabDelegate = MyGUI::newDelegate(this, &LuaScrollView::scrollGrab); + mScrollDragDelegate = MyGUI::newDelegate(this, &LuaScrollView::scrollDrag); + mScrollReleaseDelegate = MyGUI::newDelegate(this, &LuaScrollView::scrollRelease); + mScrollWheelDelegate = MyGUI::newDelegate(this, &LuaScrollView::scrollWheel); + mScrollUpDelegate = MyGUI::newDelegate(this, &LuaScrollView::scrollUp); + mScrollDownDelegate = MyGUI::newDelegate(this, &LuaScrollView::scrollDown); + } + + void LuaScrollView::updateTemplate() + { + slot()->widget()->eventMouseWheel -= mScrollWheelDelegate; + WidgetExtension::updateTemplate(); + slot()->widget()->eventMouseWheel += mScrollWheelDelegate; + + if (mScrollBar) + { + mScrollBar->widget()->eventMouseButtonPressed -= mScrollGrabDelegate; + mScrollBar->widget()->eventMouseDrag -= mScrollDragDelegate; + mScrollBar->widget()->eventMouseButtonReleased -= mScrollReleaseDelegate; + } + if (mScrollUpButton) + mScrollUpButton->widget()->eventMouseButtonClick -= mScrollUpDelegate; + if (mScrollDownButton) + mScrollDownButton->widget()->eventMouseButtonClick -= mScrollDownDelegate; + + mScrollBar = findDeepInTemplates("scrollBar"); + mScrollUpButton = findDeepInTemplates("scrollUp"); + mScrollDownButton = findDeepInTemplates("scrollDown"); + + if (mScrollBar) + { + mScrollBar->widget()->eventMouseButtonPressed += mScrollGrabDelegate; + mScrollBar->widget()->eventMouseDrag += mScrollDragDelegate; + mScrollBar->widget()->eventMouseButtonReleased += mScrollReleaseDelegate; + } + + if (mScrollUpButton) + mScrollUpButton->widget()->eventMouseButtonClick += mScrollUpDelegate; + + if (mScrollDownButton) + mScrollDownButton->widget()->eventMouseButtonClick += mScrollDownDelegate; + } + + void LuaScrollView::updateProperties() + { + mScrollStep = propertyValue("scrollStep", 0.05f); + WidgetExtension::updateProperties(); + } + + void LuaScrollView::updateChildren() + { + WidgetExtension::updateChildren(); + } + + void LuaScrollView::scrollGrab(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) + { + if (button != MyGUI::MouseButton::Left) + return; + mGrabPoint = MyGUI::IntPoint(left, top); + } + + void LuaScrollView::scrollDrag(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) + { + if (button != MyGUI::MouseButton::Left) + return; + dragScrollbar({ left, top }); + } + + void LuaScrollView::scrollRelease(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) + { + if (button != MyGUI::MouseButton::Left) + return; + dragScrollbar({ left, top }); + } + + void LuaScrollView::dragScrollbar(MyGUI::IntPoint pointerPosition) + { + MyGUI::IntSize barSize = mScrollBar->widget()->getSize(); + MyGUI::IntSize holderSize = mScrollBar->getParent()->widget()->getSize(); + int scrollBarSpace = holderSize.height - barSize.height; + int offset = pointerPosition.top - mGrabPoint.top; + + MyGUI::IntPoint barPosition = mScrollBar->widget()->getPosition(); + barPosition.top = std::clamp(barPosition.top + offset, 0, scrollBarSpace); + mScrollBar->forcePosition(barPosition); + mScrollBar->updateCoord(); + + float ratio = static_cast(barPosition.top + offset) / scrollBarSpace; + scrollTo(ratio); + + mGrabPoint = pointerPosition; + } + + void LuaScrollView::scrollWheel(MyGUI::Widget*, int delta) { + float wheelSteps = delta / 120.0f; + float ratio = currentScrollRatio(); + ratio = std::clamp(ratio - mScrollStep * wheelSteps, 0.0f, 1.0f); + scrollTo(ratio); + updateScrollbarPosition(ratio); + } + + void LuaScrollView::scrollUp(MyGUI::Widget*) + { + float ratio = currentScrollRatio(); + ratio = std::max(0.0f, ratio - mScrollStep); + scrollTo(ratio); + updateScrollbarPosition(ratio); + } + + void LuaScrollView::scrollDown(MyGUI::Widget*) + { + float ratio = currentScrollRatio(); + ratio = std::min(1.0f, ratio + mScrollStep); + scrollTo(ratio); + updateScrollbarPosition(ratio); + } + + float LuaScrollView::currentScrollRatio() + { + if (slot() == this) + return 0.0f; + MyGUI::IntSize containerSize = slot()->getParent()->widget()->getSize(); + MyGUI::IntSize canvasSize = slot()->widget()->getSize(); + MyGUI::IntPoint canvasPosition = slot()->widget()->getPosition(); + + float ratio = static_cast(-canvasPosition.top) / (std::max(1, canvasSize.height - containerSize.height)); + return std::clamp(ratio, 0.0f, 1.0f); + } + + void LuaScrollView::scrollTo(float ratio) + { + int offset = 0; + if (slot() != this) + { + MyGUI::IntSize containerSize = slot()->getParent()->widget()->getSize(); + MyGUI::IntSize canvasSize = slot()->widget()->getSize(); + offset = std::max(0, static_cast(ratio * (canvasSize.height - containerSize.height))); + } + MyGUI::IntPoint canvasPosition = slot()->widget()->getPosition(); + slot()->forcePosition({ canvasPosition.left, -offset }); + slot()->updateCoord(); + } + + void LuaScrollView::updateScrollbarPosition(float ratio) + { + if (!mScrollBar) + return; + MyGUI::IntSize barSize = mScrollBar->widget()->getSize(); + MyGUI::IntSize holderSize = mScrollBar->getParent()->widget()->getSize(); + MyGUI::IntPoint barPostiion = mScrollBar->widget()->getPosition(); + barPostiion.top = ratio * (holderSize.height - barSize.height); + mScrollBar->forcePosition(barPostiion); + mScrollBar->updateCoord(); + } +} diff --git a/components/lua_ui/scrollview.hpp b/components/lua_ui/scrollview.hpp new file mode 100644 index 0000000000..caab929fe8 --- /dev/null +++ b/components/lua_ui/scrollview.hpp @@ -0,0 +1,53 @@ +#ifndef OPENMW_LUAUI_SCROLLVIEW +#define OPENMW_LUAUI_SCROLLVIEW + +#include "widget.hpp" + +namespace LuaUi +{ + class LuaScrollView : public MyGUI::Widget, public WidgetExtension + { + MYGUI_RTTI_DERIVED(LuaScrollView) + + protected: + void initialize() override; + + void updateTemplate() override; + void updateProperties() override; + void updateChildren() override; + + private: + float mScrollStep; + + WidgetExtension* mScrollBar; + WidgetExtension* mScrollUpButton; + WidgetExtension* mScrollDownButton; + + MyGUI::delegates::DelegateFunction* mScrollGrabDelegate; + MyGUI::delegates::DelegateFunction* mScrollDragDelegate; + MyGUI::delegates::DelegateFunction* mScrollReleaseDelegate; + + void scrollGrab(MyGUI::Widget*, int left, int top, MyGUI::MouseButton); + void scrollDrag(MyGUI::Widget*, int left, int top, MyGUI::MouseButton); + void scrollRelease(MyGUI::Widget*, int left, int top, MyGUI::MouseButton); + + void dragScrollbar(MyGUI::IntPoint ponterPosition); + + MyGUI::delegates::DelegateFunction* mScrollWheelDelegate; + MyGUI::delegates::DelegateFunction* mScrollUpDelegate; + MyGUI::delegates::DelegateFunction* mScrollDownDelegate; + + void scrollWheel(MyGUI::Widget*, int step); + void scrollUp(MyGUI::Widget*); + void scrollDown(MyGUI::Widget*); + + void updateScrollbarPosition(float ratio); + + float currentScrollRatio(); + void scrollTo(float ratio); + + MyGUI::IntPoint mGrabPoint; + }; +} + +#endif // OPENMW_LUAUI_SCROLLVIEW diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index 9bd241884a..d54dbdd984 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -15,7 +15,7 @@ namespace LuaUi void LuaTextEdit::deinitialize() { - mEditBox->eventEditTextChange -= MyGUI::newDelegate(this, &LuaTextEdit::textChange); + mEditBox->eventEditTextChange.clear(); clearEvents(mEditBox); WidgetExtension::deinitialize(); } diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index 8dfda3d4cb..408af8c60e 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -2,6 +2,7 @@ #include +#include "Scrollview.hpp" #include "adapter.hpp" #include "container.hpp" #include "flex.hpp" @@ -28,6 +29,7 @@ namespace LuaUi MyGUI::FactoryManager::getInstance().registerFactory("BasisSkin"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); } const std::unordered_map& widgetTypeToName() @@ -40,6 +42,7 @@ namespace LuaUi { "LuaImage", "Image" }, { "LuaFlex", "Flex" }, { "LuaContainer", "Container" }, + { "LuaScrollView", "ScrollView" }, }; return types; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index a126fd9f7b..ec5d402721 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -59,6 +59,7 @@ namespace LuaUi w->eventMouseButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::mouseRelease); w->eventMouseMove += MyGUI::newDelegate(this, &WidgetExtension::mouseMove); w->eventMouseDrag += MyGUI::newDelegate(this, &WidgetExtension::mouseDrag); + w->eventMouseWheel += MyGUI::newDelegate(this, &WidgetExtension::mouseWheel); w->eventMouseSetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain); w->eventMouseLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); @@ -75,6 +76,7 @@ namespace LuaUi w->eventMouseButtonReleased.clear(); w->eventMouseMove.clear(); w->eventMouseDrag.clear(); + w->eventMouseWheel.clear(); w->eventMouseSetFocus.clear(); w->eventMouseLostFocus.clear(); @@ -207,6 +209,7 @@ namespace LuaUi attach(mChildren[i]); } updateChildren(); + slot()->setSlotChildren(children); } void WidgetExtension::setTemplateChildren(const std::vector& children) @@ -220,13 +223,24 @@ namespace LuaUi updateTemplate(); } + void WidgetExtension::setSlotChildren(const std::vector& children) + { + mSlotChildren.resize(children.size()); + for (size_t i = 0; i < children.size(); ++i) + mSlotChildren[i] = children[i]; + updateCoord(); + } + void WidgetExtension::updateTemplate() { + slot()->setSlotChildren({}); + WidgetExtension* slot = findDeepInTemplates("slot"); if (slot == nullptr) mSlot = this; else mSlot = slot->mSlot; + mSlot->setSlotChildren(children()); } void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback) @@ -419,6 +433,11 @@ namespace LuaUi propagateEvent("mouseRelease", [left, top, button](auto w) { return w->mouseEvent(left, top, button); }); } + void mouseWHeel(MyGUI::Widget*, int steps) + { + propagateEvent("mouseWheel", [steps](auto w) { return steps; }); + } + void WidgetExtension::focusGain(MyGUI::Widget*, MyGUI::Widget*) { propagateEvent("focusGain", [](auto) { return sol::nil; }); diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 5fcf86d110..f23de909da 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -39,6 +39,8 @@ namespace LuaUi void detachChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mChildren); } void detachTemplateChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mTemplateChildren); } + WidgetExtension* getParent() const { return mParent; } + void reset(); const std::vector& children() { return mChildren; } @@ -47,6 +49,9 @@ namespace LuaUi const std::vector& templateChildren() { return mTemplateChildren; } void setTemplateChildren(const std::vector&); + const std::vector& slotChildren() { return mSlotChildren; } + void setSlotChildren(const std::vector&); + void setCallback(const std::string&, const LuaUtil::Callback&); void clearCallbacks(); @@ -97,6 +102,7 @@ namespace LuaUi return parseProperty(mProperties, mTemplateProperties, name, defaultValue); } + WidgetExtension* findDeep(std::string_view name); WidgetExtension* findDeepInTemplates(std::string_view flagName); std::vector findAllInTemplates(std::string_view flagName); @@ -146,8 +152,11 @@ namespace LuaUi // use lua_State* instead of sol::state_view because MyGUI requires a default constructor lua_State* mLua; MyGUI::Widget* mWidget; + std::vector mChildren; std::vector mTemplateChildren; + std::vector mSlotChildren; + WidgetExtension* mSlot; std::map> mCallbacks; sol::main_table mLayout; @@ -161,7 +170,6 @@ namespace LuaUi void attach(WidgetExtension* ext); void attachTemplate(WidgetExtension* ext); - WidgetExtension* findDeep(std::string_view name); void findAll(std::string_view flagName, std::vector& result); void updateChildrenCoord(); @@ -174,6 +182,7 @@ namespace LuaUi void mouseDoubleClick(MyGUI::Widget*); void mousePress(MyGUI::Widget*, int, int, MyGUI::MouseButton); void mouseRelease(MyGUI::Widget*, int, int, MyGUI::MouseButton); + void mouseWHeel(MyGUI::Widget*, int); void focusGain(MyGUI::Widget*, MyGUI::Widget*); void focusLoss(MyGUI::Widget*, MyGUI::Widget*); diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index 0e328bb3a5..aa1f370e2b 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -157,6 +157,7 @@ set(BUILTIN_DATA_FILES scripts/omw/mwui/text.lua scripts/omw/mwui/textEdit.lua scripts/omw/mwui/space.lua + scripts/omw/mwui/scrollview.lua scripts/omw/mwui/init.lua scripts/omw/skillhandlers.lua scripts/omw/crimes.lua diff --git a/files/data/scripts/omw/mwui/constants.lua b/files/data/scripts/omw/mwui/constants.lua index 8a02268f77..6cf004b139 100644 --- a/files/data/scripts/omw/mwui/constants.lua +++ b/files/data/scripts/omw/mwui/constants.lua @@ -10,4 +10,7 @@ return { thickBorder = 4, padding = 2, whiteTexture = ui.texture { path = 'white' }, -} \ No newline at end of file + scrollButtonSize = 10, + scrollBarSize = 9, + scrollPadding = 3, +} diff --git a/files/data/scripts/omw/mwui/init.lua b/files/data/scripts/omw/mwui/init.lua index 253260d063..929ce7132c 100644 --- a/files/data/scripts/omw/mwui/init.lua +++ b/files/data/scripts/omw/mwui/init.lua @@ -143,6 +143,11 @@ require('scripts.omw.mwui.textEdit')(templates) -- @field [parent=#Templates] openmw.ui#Template disabled require('scripts.omw.mwui.filters')(templates) +--- +-- Vertical ScrollView with a scrollbar and up/down buttons +-- @field [parent=#Templates] openmw.ui#Template verticalScrollView +require('scripts.omw.mwui.scrollview')(templates) + --- -- Interface version -- @field [parent=#MWUI] #number version diff --git a/files/data/scripts/omw/mwui/scrollview.lua b/files/data/scripts/omw/mwui/scrollview.lua new file mode 100644 index 0000000000..5132decdc0 --- /dev/null +++ b/files/data/scripts/omw/mwui/scrollview.lua @@ -0,0 +1,122 @@ +local ui = require('openmw.ui') +local util = require('openmw.util') + +local constants = require('scripts.omw.mwui.constants') + +local scrollUpIcon = ui.texture { path = 'textures/omw_menu_scroll_up.dds' } +local scrollDownIcon = ui.texture { path = 'textures/omw_menu_scroll_down.dds' } +local scrollBarBody = ui.texture { path = 'textures/omw_menu_scroll_center_v.dds' } + +local scrollPadding = { + props = { + size = util.vector2(1, 1) * constants.scrollPadding, + }, +} + +return function(templates) + local scrollUp = { + template = templates.box, + content = ui.content { + { + type = ui.TYPE.Image, + props = { + resource = scrollUpIcon, + size = util.vector2(1, 1) * constants.scrollButtonSize, + }, + external = { + scrollUp = true, + }, + }, + }, + } + + local scrollDown = { + template = templates.box, + content = ui.content { + { + type = ui.TYPE.Image, + props = { + resource = scrollDownIcon, + size = util.vector2(1, 1) * constants.scrollButtonSize, + }, + external = { + scrollDown = true, + }, + }, + }, + } + + local scrollBar = { + type = ui.TYPE.Image, + props = { + resource = scrollBarBody, + size = util.vector2(constants.scrollBarSize, 0), + relativeSize = util.vector2(0, 0.3), + }, + external = { + scrollBar = true, + }, + } + + local scrollBarContainer = { + template = templates.borders, + content = ui.content { + scrollBar, + }, + external = { + stretch = 1, + grow = 1, + }, + } + + local verticalControls = { + type = ui.TYPE.Flex, + props = { + horizontal = false, + relativeSize = util.vector2(0, 1), + }, + content = ui.content { + scrollUp, + scrollPadding, + scrollBarContainer, + scrollPadding, + scrollDown, + }, + } + + local scrollViewSlot = { + content = ui.content { + { + type = ui.TYPE.Container, + external = { + slot = true, + }, + }, + }, + external = { + stretch = 1, + grow = 1, + }, + } + + local verticalScrollView = { + type = ui.TYPE.ScrollView, + content = ui.content { + { + type = ui.TYPE.Flex, + props = { + horizontal = true, + relativeSize = util.vector2(1, 1), + autoSize = false, + }, + content = ui.content { + scrollViewSlot, + { props = { size = util.vector2(1, 1) * constants.padding } }, + verticalControls, + }, + }, + }, + } + + templates.verticalScrollView = verticalScrollView +end