From 36c46ada6f89500ccd597daa3a828edafb6aafe9 Mon Sep 17 00:00:00 2001
From: uramer <antonuramer@gmail.com>
Date: Mon, 14 Mar 2022 19:07:23 +0000
Subject: [PATCH] Pass unhandled Lua UI events to the parent

---
 components/lua/scriptscontainer.hpp           |  5 ++-
 components/lua_ui/widget.cpp                  | 45 +++++++++++--------
 components/lua_ui/widget.hpp                  | 27 ++++++++++-
 .../lua-scripting/widgets/widget.rst          |  7 +++
 4 files changed, 61 insertions(+), 23 deletions(-)

diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp
index fcbd2ba0b7..d968c13a45 100644
--- a/components/lua/scriptscontainer.hpp
+++ b/components/lua/scriptscontainer.hpp
@@ -250,13 +250,14 @@ namespace LuaUtil
         sol::table mHiddenData;  // same object as Script::mHiddenData in ScriptsContainer
 
         template <typename... Args>
-        void operator()(Args&&... args) const
+        sol::object operator()(Args&&... args) const
         {
             if (mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil)
-                LuaUtil::call(mFunc, std::forward<Args>(args)...);
+                return LuaUtil::call(mFunc, std::forward<Args>(args)...);
             else
                 Log(Debug::Debug) << "Ignored callback to the removed script "
                                   << mHiddenData.get<std::string>(ScriptsContainer::sScriptDebugNameKey);
+            return sol::nil;
         }
     };
 
diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp
index 96a6d096bd..a11d3db852 100644
--- a/components/lua_ui/widget.cpp
+++ b/components/lua_ui/widget.cpp
@@ -10,7 +10,8 @@
 namespace LuaUi
 {
     WidgetExtension::WidgetExtension()
-        : mLua(nullptr)
+        : mPropagateEvents(true)
+        , mLua(nullptr)
         , mWidget(nullptr)
         , mSlot(this)
         , mLayout(sol::nil)
@@ -18,6 +19,7 @@ namespace LuaUi
         , mTemplateProperties(sol::nil)
         , mExternal(sol::nil)
         , mParent(nullptr)
+        , mTemplateChild(false)
     {}
 
     void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self)
@@ -81,12 +83,15 @@ namespace LuaUi
     void WidgetExtension::attach(WidgetExtension* ext)
     {
         ext->mParent = this;
+        ext->mTemplateChild = false;
         ext->widget()->attachToWidget(mSlot->widget());
         ext->updateCoord();
     }
 
     void WidgetExtension::attachTemplate(WidgetExtension* ext)
     {
+        ext->mParent = this;
+        ext->mTemplateChild = true;
         ext->widget()->attachToWidget(widget());
         ext->updateCoord();
     }
@@ -133,7 +138,7 @@ namespace LuaUi
 
     sol::table WidgetExtension::makeTable() const
     {
-        return sol::table(mLua, sol::create);
+        return sol::table(lua(), sol::create);
     }
 
     sol::object WidgetExtension::keyEvent(MyGUI::KeyCode code) const
@@ -142,7 +147,7 @@ namespace LuaUi
         keySym.sym = SDLUtil::myGuiKeyToSdl(code);
         keySym.scancode = SDL_GetScancodeFromKey(keySym.sym);
         keySym.mod = SDL_GetModState();
-        return sol::make_object(mLua, keySym);
+        return sol::make_object(lua(), keySym);
     }
 
     sol::object WidgetExtension::mouseEvent(int left, int top, MyGUI::MouseButton button = MyGUI::MouseButton::None) const
@@ -246,6 +251,7 @@ namespace LuaUi
 
     void WidgetExtension::updateProperties()
     {
+        mPropagateEvents = propertyValue("propagateEvents", true);
         mAbsoluteCoord = propertyValue("position", MyGUI::IntPoint());
         mAbsoluteCoord = propertyValue("size", MyGUI::IntSize());
         mRelativeCoord = propertyValue("relativePosition", MyGUI::FloatPoint());
@@ -265,7 +271,7 @@ namespace LuaUi
 
     MyGUI::IntSize WidgetExtension::parentSize()
     {
-        if (mParent)
+        if (mParent && !mTemplateChild)
             return mParent->childScalingSize();
         else
             return widget()->getParentSize();
@@ -304,7 +310,7 @@ namespace LuaUi
         return mSlot->widget()->getSize();
     }
 
-    void WidgetExtension::triggerEvent(std::string_view name, const sol::object& argument = sol::nil) const
+    void WidgetExtension::triggerEvent(std::string_view name, sol::object argument) const
     {
         auto it = mCallbacks.find(name);
         if (it != mCallbacks.end())
@@ -315,57 +321,58 @@ namespace LuaUi
     {
         if (code == MyGUI::KeyCode::None)
         {
-            // \todo decide how to handle unicode strings in Lua
-            MyGUI::UString uString;
-            uString.push_back(static_cast<MyGUI::UString::unicode_char>(ch));
-            triggerEvent("textInput", sol::make_object(mLua, uString.asUTF8()));
+            propagateEvent("textInput", [ch](auto w) {
+                MyGUI::UString uString;
+                uString.push_back(static_cast<MyGUI::UString::unicode_char>(ch));
+                return sol::make_object(w->lua(), uString.asUTF8());
+            });
         }
         else
-            triggerEvent("keyPress", keyEvent(code));
+            propagateEvent("keyPress", [code](auto w){ return w->keyEvent(code); });
     }
 
     void WidgetExtension::keyRelease(MyGUI::Widget*, MyGUI::KeyCode code)
     {
-        triggerEvent("keyRelease", keyEvent(code));
+        propagateEvent("keyRelease", [code](auto w) { return w->keyEvent(code); });
     }
 
     void WidgetExtension::mouseMove(MyGUI::Widget*, int left, int top)
     {
-        triggerEvent("mouseMove", mouseEvent(left, top));
+        propagateEvent("mouseMove", [left, top](auto w) { return w->mouseEvent(left, top); });
     }
 
     void WidgetExtension::mouseDrag(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button)
     {
-        triggerEvent("mouseMove", mouseEvent(left, top, button));
+        propagateEvent("mouseMove", [left, top, button](auto w) { return w->mouseEvent(left, top, button); });
     }
 
     void WidgetExtension::mouseClick(MyGUI::Widget* _widget)
     {
-        triggerEvent("mouseClick");
+        propagateEvent("mouseClick", [](auto){ return sol::nil; });
     }
 
     void WidgetExtension::mouseDoubleClick(MyGUI::Widget* _widget)
     {
-        triggerEvent("mouseDoubleClick");
+        propagateEvent("mouseDoubleClick", [](auto){ return sol::nil; });
     }
 
     void WidgetExtension::mousePress(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button)
     {
-        triggerEvent("mousePress", mouseEvent(left, top, button));
+        propagateEvent("mousePress", [left, top, button](auto w) { return w->mouseEvent(left, top, button); });
     }
 
     void WidgetExtension::mouseRelease(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button)
     {
-        triggerEvent("mouseRelease", mouseEvent(left, top, button));
+        propagateEvent("mouseRelease", [left, top, button](auto w) { return w->mouseEvent(left, top, button); });
     }
 
     void WidgetExtension::focusGain(MyGUI::Widget*, MyGUI::Widget*)
     {
-        triggerEvent("focusGain");
+        propagateEvent("focusGain", [](auto){ return sol::nil; });
     }
 
     void WidgetExtension::focusLoss(MyGUI::Widget*, MyGUI::Widget*)
     {
-        triggerEvent("focusLoss");
+        propagateEvent("focusLoss", [](auto){ return sol::nil; });
     }
 }
diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp
index 827993d47d..c712d18ccd 100644
--- a/components/lua_ui/widget.hpp
+++ b/components/lua_ui/widget.hpp
@@ -90,8 +90,28 @@ namespace LuaUi
         virtual void updateProperties();
         virtual void updateChildren() {};
 
-        lua_State* lua() { return mLua; }
-        void triggerEvent(std::string_view name, const sol::object& argument) const;
+        lua_State* lua() const { return mLua; }
+
+        void triggerEvent(std::string_view name, sol::object argument) const;
+        template<class ArgFactory>
+        void propagateEvent(std::string_view name, const ArgFactory& argumentFactory) const
+        {
+            const WidgetExtension* w = this;
+            while (w)
+            {
+                bool shouldPropagate = true;
+                auto it = w->mCallbacks.find(name);
+                if (it != w->mCallbacks.end())
+                {
+                    sol::object res = it->second(argumentFactory(w), w->mLayout);
+                    shouldPropagate = res.is<bool>() && res.as<bool>();
+                }
+                if (w->mParent && w->mPropagateEvents && shouldPropagate)
+                    w = w->mParent;
+                else
+                    w = nullptr;
+            }
+        }
 
         // offsets the position and size, used only in C++ widget code
         MyGUI::IntCoord mForcedCoord;
@@ -103,6 +123,8 @@ namespace LuaUi
         // used in combination with relative coord to align the widget, e. g. center it
         MyGUI::FloatSize mAnchor;
 
+        bool mPropagateEvents;
+
     private:
         // use lua_State* instead of sol::state_view because MyGUI requires a default constructor
         lua_State* mLua;
@@ -116,6 +138,7 @@ namespace LuaUi
         sol::object mTemplateProperties;
         sol::object mExternal;
         WidgetExtension* mParent;
+        bool mTemplateChild;
 
         void attach(WidgetExtension* ext);
         void attachTemplate(WidgetExtension* ext);
diff --git a/docs/source/reference/lua-scripting/widgets/widget.rst b/docs/source/reference/lua-scripting/widgets/widget.rst
index 36cc0917d9..d114d0957b 100644
--- a/docs/source/reference/lua-scripting/widgets/widget.rst
+++ b/docs/source/reference/lua-scripting/widgets/widget.rst
@@ -32,12 +32,19 @@ Properties
   * - visible
     - boolean (true)
     - Defines if the widget is visible
+  * - propagateEvents
+    - boolean (true)
+    - Allows base widget events to propagate to the widget's parent.
 
 .. TODO: document the mouse pointer property, when API for reading / adding pointer types is available
 
 Events
 ------
 
+Base widget events are special, they can propagate up to the parent widget.
+This can be prevented by changing the `propagateEvents` property, or by assigning an  event handler.
+The event is still allowed to propagate if the event handler returns `true`.
+
 .. list-table::
   :header-rows: 1
   :widths: 20 20 60