Merge branch 'guys_its_called_a_scrollbar' into 'master'

Draft: Lua ScrollView

See merge request OpenMW/openmw!3606
This commit is contained in:
uramer 2025-09-15 21:27:46 +00:00
commit 3db35d7c44
12 changed files with 386 additions and 4 deletions

View File

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

View File

@ -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())
{

View File

@ -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<float>(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<float>(-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<int>(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();
}
}

View File

@ -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<MyGUI::Widget*, int, int, MyGUI::MouseButton>* mScrollGrabDelegate;
MyGUI::delegates::DelegateFunction<MyGUI::Widget*, int, int, MyGUI::MouseButton>* mScrollDragDelegate;
MyGUI::delegates::DelegateFunction<MyGUI::Widget*, int, int, MyGUI::MouseButton>* 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<MyGUI::Widget*, int>* mScrollWheelDelegate;
MyGUI::delegates::DelegateFunction<MyGUI::Widget*>* mScrollUpDelegate;
MyGUI::delegates::DelegateFunction<MyGUI::Widget*>* 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

View File

@ -15,7 +15,7 @@ namespace LuaUi
void LuaTextEdit::deinitialize()
{
mEditBox->eventEditTextChange -= MyGUI::newDelegate(this, &LuaTextEdit::textChange);
mEditBox->eventEditTextChange.clear();
clearEvents(mEditBox);
WidgetExtension::deinitialize();
}

View File

@ -2,6 +2,7 @@
#include <MyGUI_FactoryManager.h>
#include "Scrollview.hpp"
#include "adapter.hpp"
#include "container.hpp"
#include "flex.hpp"
@ -28,6 +29,7 @@ namespace LuaUi
MyGUI::FactoryManager::getInstance().registerFactory<LuaTileRect>("BasisSkin");
MyGUI::FactoryManager::getInstance().registerFactory<LuaContainer>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<LuaFlex>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<LuaScrollView>("Widget");
}
const std::unordered_map<std::string, std::string>& widgetTypeToName()
@ -40,6 +42,7 @@ namespace LuaUi
{ "LuaImage", "Image" },
{ "LuaFlex", "Flex" },
{ "LuaContainer", "Container" },
{ "LuaScrollView", "ScrollView" },
};
return types;
}

View File

@ -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<WidgetExtension*>& children)
@ -220,13 +223,24 @@ namespace LuaUi
updateTemplate();
}
void WidgetExtension::setSlotChildren(const std::vector<WidgetExtension*>& 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; });

View File

@ -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<WidgetExtension*>& children() { return mChildren; }
@ -47,6 +49,9 @@ namespace LuaUi
const std::vector<WidgetExtension*>& templateChildren() { return mTemplateChildren; }
void setTemplateChildren(const std::vector<WidgetExtension*>&);
const std::vector<WidgetExtension*>& slotChildren() { return mSlotChildren; }
void setSlotChildren(const std::vector<WidgetExtension*>&);
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<WidgetExtension*> 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<WidgetExtension*> mChildren;
std::vector<WidgetExtension*> mTemplateChildren;
std::vector<WidgetExtension*> mSlotChildren;
WidgetExtension* mSlot;
std::map<std::string, LuaUtil::Callback, std::less<>> 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<WidgetExtension*>& 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*);

View File

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

View File

@ -10,4 +10,7 @@ return {
thickBorder = 4,
padding = 2,
whiteTexture = ui.texture { path = 'white' },
}
scrollButtonSize = 10,
scrollBarSize = 9,
scrollPadding = 3,
}

View File

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

View File

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