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 add_component_dir (lua_ui
registerscriptsettings scriptsettings registerscriptsettings scriptsettings
properties widget element util layers content alignment resources 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") 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.width = std::max(innerSize.width, coord.left + coord.width);
innerSize.height = std::max(innerSize.height, coord.top + coord.height); 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; MyGUI::IntSize outerSize = innerSize;
for (const auto w : templateChildren()) 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() void LuaTextEdit::deinitialize()
{ {
mEditBox->eventEditTextChange -= MyGUI::newDelegate(this, &LuaTextEdit::textChange); mEditBox->eventEditTextChange.clear();
clearEvents(mEditBox); clearEvents(mEditBox);
WidgetExtension::deinitialize(); WidgetExtension::deinitialize();
} }

View File

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

View File

@ -59,6 +59,7 @@ namespace LuaUi
w->eventMouseButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::mouseRelease); w->eventMouseButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::mouseRelease);
w->eventMouseMove += MyGUI::newDelegate(this, &WidgetExtension::mouseMove); w->eventMouseMove += MyGUI::newDelegate(this, &WidgetExtension::mouseMove);
w->eventMouseDrag += MyGUI::newDelegate(this, &WidgetExtension::mouseDrag); w->eventMouseDrag += MyGUI::newDelegate(this, &WidgetExtension::mouseDrag);
w->eventMouseWheel += MyGUI::newDelegate(this, &WidgetExtension::mouseWheel);
w->eventMouseSetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain); w->eventMouseSetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain);
w->eventMouseLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); w->eventMouseLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss);
@ -75,6 +76,7 @@ namespace LuaUi
w->eventMouseButtonReleased.clear(); w->eventMouseButtonReleased.clear();
w->eventMouseMove.clear(); w->eventMouseMove.clear();
w->eventMouseDrag.clear(); w->eventMouseDrag.clear();
w->eventMouseWheel.clear();
w->eventMouseSetFocus.clear(); w->eventMouseSetFocus.clear();
w->eventMouseLostFocus.clear(); w->eventMouseLostFocus.clear();
@ -207,6 +209,7 @@ namespace LuaUi
attach(mChildren[i]); attach(mChildren[i]);
} }
updateChildren(); updateChildren();
slot()->setSlotChildren(children);
} }
void WidgetExtension::setTemplateChildren(const std::vector<WidgetExtension*>& children) void WidgetExtension::setTemplateChildren(const std::vector<WidgetExtension*>& children)
@ -220,13 +223,24 @@ namespace LuaUi
updateTemplate(); 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() void WidgetExtension::updateTemplate()
{ {
slot()->setSlotChildren({});
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;
mSlot->setSlotChildren(children());
} }
void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback) 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); }); 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*) void WidgetExtension::focusGain(MyGUI::Widget*, MyGUI::Widget*)
{ {
propagateEvent("focusGain", [](auto) { return sol::nil; }); propagateEvent("focusGain", [](auto) { return sol::nil; });

View File

@ -39,6 +39,8 @@ namespace LuaUi
void detachChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mChildren); } void detachChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mChildren); }
void detachTemplateChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mTemplateChildren); } void detachTemplateChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mTemplateChildren); }
WidgetExtension* getParent() const { return mParent; }
void reset(); void reset();
const std::vector<WidgetExtension*>& children() { return mChildren; } const std::vector<WidgetExtension*>& children() { return mChildren; }
@ -47,6 +49,9 @@ namespace LuaUi
const std::vector<WidgetExtension*>& templateChildren() { return mTemplateChildren; } const std::vector<WidgetExtension*>& templateChildren() { return mTemplateChildren; }
void setTemplateChildren(const std::vector<WidgetExtension*>&); 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 setCallback(const std::string&, const LuaUtil::Callback&);
void clearCallbacks(); void clearCallbacks();
@ -97,6 +102,7 @@ namespace LuaUi
return parseProperty(mProperties, mTemplateProperties, name, defaultValue); return parseProperty(mProperties, mTemplateProperties, name, defaultValue);
} }
WidgetExtension* findDeep(std::string_view name);
WidgetExtension* findDeepInTemplates(std::string_view flagName); WidgetExtension* findDeepInTemplates(std::string_view flagName);
std::vector<WidgetExtension*> findAllInTemplates(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 // use lua_State* instead of sol::state_view because MyGUI requires a default constructor
lua_State* mLua; lua_State* mLua;
MyGUI::Widget* mWidget; MyGUI::Widget* mWidget;
std::vector<WidgetExtension*> mChildren; std::vector<WidgetExtension*> mChildren;
std::vector<WidgetExtension*> mTemplateChildren; std::vector<WidgetExtension*> mTemplateChildren;
std::vector<WidgetExtension*> mSlotChildren;
WidgetExtension* mSlot; WidgetExtension* mSlot;
std::map<std::string, LuaUtil::Callback, std::less<>> mCallbacks; std::map<std::string, LuaUtil::Callback, std::less<>> mCallbacks;
sol::main_table mLayout; sol::main_table mLayout;
@ -161,7 +170,6 @@ namespace LuaUi
void attach(WidgetExtension* ext); void attach(WidgetExtension* ext);
void attachTemplate(WidgetExtension* ext); void attachTemplate(WidgetExtension* ext);
WidgetExtension* findDeep(std::string_view name);
void findAll(std::string_view flagName, std::vector<WidgetExtension*>& result); void findAll(std::string_view flagName, std::vector<WidgetExtension*>& result);
void updateChildrenCoord(); void updateChildrenCoord();
@ -174,6 +182,7 @@ namespace LuaUi
void mouseDoubleClick(MyGUI::Widget*); void mouseDoubleClick(MyGUI::Widget*);
void mousePress(MyGUI::Widget*, int, int, MyGUI::MouseButton); void mousePress(MyGUI::Widget*, int, int, MyGUI::MouseButton);
void mouseRelease(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 focusGain(MyGUI::Widget*, MyGUI::Widget*);
void focusLoss(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/text.lua
scripts/omw/mwui/textEdit.lua scripts/omw/mwui/textEdit.lua
scripts/omw/mwui/space.lua scripts/omw/mwui/space.lua
scripts/omw/mwui/scrollview.lua
scripts/omw/mwui/init.lua scripts/omw/mwui/init.lua
scripts/omw/skillhandlers.lua scripts/omw/skillhandlers.lua
scripts/omw/crimes.lua scripts/omw/crimes.lua

View File

@ -10,4 +10,7 @@ return {
thickBorder = 4, thickBorder = 4,
padding = 2, padding = 2,
whiteTexture = ui.texture { path = 'white' }, 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 -- @field [parent=#Templates] openmw.ui#Template disabled
require('scripts.omw.mwui.filters')(templates) 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 -- Interface version
-- @field [parent=#MWUI] #number 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