From cc6dce544386f459c19d414ef3c43cbabcddd6c0 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 21 Feb 2022 19:49:00 +0000 Subject: [PATCH] Support controller touchpads (Resolves https://gitlab.com/OpenMW/openmw/-/issues/6639) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/luamanager.hpp | 15 +- apps/openmw/mwinput/controllermanager.cpp | 18 ++ apps/openmw/mwinput/controllermanager.hpp | 4 + apps/openmw/mwlua/inputbindings.cpp | 12 ++ apps/openmw/mwlua/playerscripts.hpp | 23 ++- components/sdlutil/events.hpp | 32 +++- components/sdlutil/sdlinputwrapper.cpp | 14 ++ .../lua-scripting/engine_handlers.rst | 162 ++++++++++-------- files/lua_api/openmw/input.lua | 8 + 10 files changed, 207 insertions(+), 83 deletions(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3c8a0a4d9a..71c138eee9 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,7 +58,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp actions object worldview userdataserializer eventqueue query - luabindings localscripts objectbindings cellbindings asyncbindings settingsbindings + luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings camerabindings uibindings inputbindings nearbybindings ) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 7fa8b6df58..5ed751ffed 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace MWWorld { class Ptr; @@ -43,8 +45,17 @@ namespace MWBase struct InputEvent { - enum {KeyPressed, KeyReleased, ControllerPressed, ControllerReleased, Action} mType; - std::variant mValue; + enum { + KeyPressed, + KeyReleased, + ControllerPressed, + ControllerReleased, + Action, + TouchPressed, + TouchReleased, + TouchMoved, + } mType; + std::variant mValue; }; virtual void inputEvent(const InputEvent& event) = 0; diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 6c6549face..f4d307e7d0 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -433,4 +433,22 @@ namespace MWInput #endif return std::array({gyro[0], gyro[1], gyro[2]}); } + + void ControllerManager::touchpadMoved(int deviceId, const SDLUtil::TouchEvent& arg) + { + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::TouchMoved, arg }); + } + + void ControllerManager::touchpadPressed(int deviceId, const SDLUtil::TouchEvent& arg) + { + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::TouchPressed, arg }); + } + + void ControllerManager::touchpadReleased(int deviceId, const SDLUtil::TouchEvent& arg) + { + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::TouchReleased, arg }); + } } diff --git a/apps/openmw/mwinput/controllermanager.hpp b/apps/openmw/mwinput/controllermanager.hpp index 8df6ee5c9f..2472128c26 100644 --- a/apps/openmw/mwinput/controllermanager.hpp +++ b/apps/openmw/mwinput/controllermanager.hpp @@ -31,6 +31,10 @@ namespace MWInput void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) override; void controllerRemoved(const SDL_ControllerDeviceEvent &arg) override; + void touchpadMoved(int deviceId, const SDLUtil::TouchEvent& arg) override; + void touchpadPressed(int deviceId, const SDLUtil::TouchEvent& arg) override; + void touchpadReleased(int deviceId, const SDLUtil::TouchEvent& arg) override; + void processChangedSettings(const Settings::CategorySettingVector& changed); void setJoystickLastUsed(bool enabled) { mJoystickLastUsed = enabled; } diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 9ca2d94770..a8487557ba 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "../mwbase/inputmanager.hpp" #include "../mwinput/actions.hpp" @@ -32,6 +34,16 @@ namespace MWLua keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; }); keyEvent["withSuper"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; }); + auto touchpadEvent = context.mLua->sol().new_usertype("TouchpadEvent"); + touchpadEvent["device"] = sol::readonly_property( + [](const SDLUtil::TouchEvent& e) -> int { return e.mDevice; }); + touchpadEvent["finger"] = sol::readonly_property( + [](const SDLUtil::TouchEvent& e) -> int { return e.mFinger; }); + touchpadEvent["position"] = sol::readonly_property( + [](const SDLUtil::TouchEvent& e) -> osg::Vec2f { return osg::Vec2f(e.mX, e.mY);}); + touchpadEvent["pressure"] = sol::readonly_property( + [](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; }); + MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); sol::table api(context.mLua->sol(), sol::create); diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index e8cdd120ac..15ab451792 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -3,6 +3,8 @@ #include +#include + #include "../mwbase/luamanager.hpp" #include "localscripts.hpp" @@ -15,9 +17,12 @@ namespace MWLua public: PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj, ESM::LuaScriptCfg::sPlayer) { - registerEngineHandlers({&mKeyPressHandlers, &mKeyReleaseHandlers, - &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, - &mActionHandlers, &mInputUpdateHandlers}); + registerEngineHandlers({ + &mKeyPressHandlers, &mKeyReleaseHandlers, + &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, + &mActionHandlers, &mInputUpdateHandlers, + &mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved + }); } void processInputEvent(const MWBase::LuaManager::InputEvent& event) @@ -40,6 +45,15 @@ namespace MWLua case InputEvent::Action: callEngineHandlers(mActionHandlers, std::get(event.mValue)); break; + case InputEvent::TouchPressed: + callEngineHandlers(mTouchpadPressed, std::get(event.mValue)); + break; + case InputEvent::TouchReleased: + callEngineHandlers(mTouchpadReleased, std::get(event.mValue)); + break; + case InputEvent::TouchMoved: + callEngineHandlers(mTouchpadMoved, std::get(event.mValue)); + break; } } @@ -52,6 +66,9 @@ namespace MWLua EngineHandlerList mControllerButtonReleaseHandlers{"onControllerButtonRelease"}; EngineHandlerList mActionHandlers{"onInputAction"}; EngineHandlerList mInputUpdateHandlers{"onInputUpdate"}; + EngineHandlerList mTouchpadPressed{ "onTouchPress" }; + EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; + EngineHandlerList mTouchpadMoved{ "onTouchMove" }; }; } diff --git a/components/sdlutil/events.hpp b/components/sdlutil/events.hpp index a0dd11acec..ce71f04457 100644 --- a/components/sdlutil/events.hpp +++ b/components/sdlutil/events.hpp @@ -1,6 +1,7 @@ #ifndef _SFO_EVENTS_H #define _SFO_EVENTS_H +#include #include #include @@ -18,6 +19,24 @@ struct MouseMotionEvent : SDL_MouseMotionEvent { Sint32 z; }; +struct TouchEvent { + int mDevice; + int mFinger; + float mX; + float mY; + float mPressure; + + #if SDL_VERSION_ATLEAST(2, 0, 14) + explicit TouchEvent(const SDL_ControllerTouchpadEvent& arg) + : mDevice(arg.touchpad) + , mFinger(arg.finger) + , mX(arg.x) + , mY(arg.y) + , mPressure(arg.pressure) + {} + #endif +}; + /////////////// // Listeners // @@ -50,25 +69,24 @@ public: virtual void keyReleased(const SDL_KeyboardEvent &arg) = 0; }; + class ControllerListener { public: virtual ~ControllerListener() {} - /** @remarks Joystick button down event */ - virtual void buttonPressed(int deviceID, const SDL_ControllerButtonEvent &evt) = 0; - /** @remarks Joystick button up event */ + virtual void buttonPressed(int deviceID, const SDL_ControllerButtonEvent &evt) = 0; virtual void buttonReleased(int deviceID, const SDL_ControllerButtonEvent &evt) = 0; - /** @remarks Joystick axis moved event */ virtual void axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) = 0; - /** @remarks Joystick Added **/ virtual void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) = 0; - - /** @remarks Joystick Removed **/ virtual void controllerRemoved(const SDL_ControllerDeviceEvent &arg) = 0; + virtual void touchpadMoved(int deviceId, const TouchEvent& arg) = 0; + virtual void touchpadPressed(int deviceId, const TouchEvent& arg) = 0; + virtual void touchpadReleased(int deviceId, const TouchEvent& arg) = 0; + }; class WindowListener diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 42276cc2a1..3bd74f569a 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -146,6 +146,20 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v if(mConListener) mConListener->axisMoved(1, evt.caxis); break; + #if SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_CONTROLLERSENSORUPDATE: + // controller sensor data is received on demand + break; + case SDL_CONTROLLERTOUCHPADDOWN: + mConListener->touchpadPressed(1, TouchEvent(evt.ctouchpad)); + break; + case SDL_CONTROLLERTOUCHPADMOTION: + mConListener->touchpadMoved(1, TouchEvent(evt.ctouchpad)); + break; + case SDL_CONTROLLERTOUCHPADUP: + mConListener->touchpadReleased(1, TouchEvent(evt.ctouchpad)); + break; + #endif case SDL_WINDOWEVENT: handleWindowEvent(evt); break; diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index d247803c08..adc07d25fe 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -3,74 +3,96 @@ Engine handlers reference Engine handler is a function defined by a script, that can be called by the engine. -+---------------------------------------------------------------------------------------------------------+ -| **Can be defined by any script** | -+----------------------------------+----------------------------------------------------------------------+ -| onInit(initData) | | Called once when the script is created (not loaded). `InitData can`| -| | | `be assigned to a script in openmw-cs (not yet implemented)`. | -| | | ``onInterfaceOverride`` can be called before ``onInit``. | -+----------------------------------+----------------------------------------------------------------------+ -| onUpdate(dt) | | Called every frame if the game is not paused. `dt` is the time | -| | | from the last update in seconds. | -+----------------------------------+----------------------------------------------------------------------+ -| onSave() -> savedData | | Called when the game is saving. May be called in inactive | -| | | state, so it shouldn't use `openmw.nearby`. | -+----------------------------------+----------------------------------------------------------------------+ -| onLoad(savedData, initData) | | Called on loading with the data previosly returned by | -| | | onSave. During loading the object is always inactive. initData is | -| | | the same as in onInit. | -| | | Note that onLoad means loading a script rather than loading a game.| -| | | If a script did not exist when a game was saved onLoad will not be | -| | | called, but onInit will. | -+----------------------------------+----------------------------------------------------------------------+ -| onInterfaceOverride(base) | | Called if the current script has an interface and overrides an | -| | | interface (``base``) of another script. | -+----------------------------------+----------------------------------------------------------------------+ -| **Only for global scripts** | -+----------------------------------+----------------------------------------------------------------------+ -| onNewGame() | New game is started | -+----------------------------------+----------------------------------------------------------------------+ -| onPlayerAdded(player) | Player added to the game world. The argument is a `Game object`. | -+----------------------------------+----------------------------------------------------------------------+ -| onActorActive(actor) | Actor (NPC or Creature) becomes active. | -+----------------------------------+----------------------------------------------------------------------+ -| **Only for local scripts** | -+----------------------------------+----------------------------------------------------------------------+ -| onActive() | | Called when the object becomes active (either a player | -| | | came to this cell again, or a save was loaded). | -+----------------------------------+----------------------------------------------------------------------+ -| onInactive() | | Object became inactive. Since it is inactive the handler | -| | | can not access anything nearby, but it is possible to send | -| | | an event to global scripts. | -+----------------------------------+----------------------------------------------------------------------+ -| onActivated(actor) | | Called on an object when an actor activates it. Note that picking | -| | | up an item is also an activation and works this way: (1) a copy of | -| | | the item is placed to the actor's inventory, (2) count of | -| | | the original item is set to zero, (3) and only then onActivated is | -| | | called on the original item, so self.count is already zero. | -+----------------------------------+----------------------------------------------------------------------+ -| onConsume(recordId) | | Called if `recordId` (e.g. a potion) is consumed. | -+----------------------------------+----------------------------------------------------------------------+ -| **Only for local scripts attached to a player** | -+----------------------------------+----------------------------------------------------------------------+ -| onInputUpdate(dt) | | Called every frame (if the game is not paused) right after | -| | | processing user input. Use it only for latency-critical stuff. | -+----------------------------------+----------------------------------------------------------------------+ -| onKeyPress(key) | | `Key `_ is pressed. | -| | | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` | -+----------------------------------+----------------------------------------------------------------------+ -| onKeyRelease(key) | | `Key `_ is released. | -| | | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` | -+----------------------------------+----------------------------------------------------------------------+ -| onControllerButtonPress(id) | | A `button `_ on a game | -| | controller is pressed. Usage example: | -| | | ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` | -+----------------------------------+----------------------------------------------------------------------+ -| onControllerButtonRelease(id) | | A `button `_ on a game | -| | controller is released. Usage example: | -| | | ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` | -+----------------------------------+----------------------------------------------------------------------+ -| onInputAction(id) | | `Game control `_ is pressed. | -| | | Usage example: ``if id == input.ACTION.ToggleWeapon then ...`` | -+----------------------------------+----------------------------------------------------------------------+ + +**Can be defined by any script** + +.. list-table:: + :widths: 20 80 + + * - onInit(initData) + - | Called once when the script is created (not loaded). `InitData can be` + | `assigned to a script in openmw-cs (not yet implemented).` + | ``onInterfaceOverride`` can be called before ``onInit``. + * - onUpdate(dt) + - | Called every frame if the game is not paused. `dt` is the time + | from the last update in seconds. + * - onSave() -> savedData + - | Called when the game is saving. May be called in inactive state, + | so it shouldn't use `openmw.nearby`. + * - onLoad(savedData, initData) + - | Called on loading with the data previosly returned by + | ``onSave``. During loading the object is always inactive. ``initData`` is + | the same as in ``onInit``. + | Note that ``onLoad`` means loading a script rather than loading a game. + | If a script did not exist when a game was saved onLoad will not be + | called, but ``onInit`` will. + * - onInterfaceOverride(base) + - | Called if the current script has an interface and overrides an interface + | (``base``) of another script. + +**Only for global scripts** + +.. list-table:: + :widths: 20 80 + + * - onNewGame() + - New game is started + * - onPlayerAdded(player) + - Player added to the game world. The argument is a `Game object`. + * - onActorActive(actor) + - Actor (NPC or Creature) becomes active. + +**Only for local scripts** + +.. list-table:: + :widths: 20 80 + + * - onActive() + - | Called when the object becomes active + | (either a player came to this cell again, or a save was loaded). + * - onInactive() + - | Object became inactive. Since it is inactive the handler + | can not access anything nearby, but it is possible to send + | an event to global scripts. + * - onActivated(actor) + - | Called on an object when an actor activates it. Note that picking + | up an item is also an activation and works this way: (1) a copy of + | the item is placed to the actor's inventory, (2) count of + | the original item is set to zero, (3) and only then onActivated is + | called on the original item, so self.count is already zero. + * - onConsume(recordId) + - Called if `recordId` (e.g. a potion) is consumed. + +**Only for local scripts attached to a player** + +.. list-table:: + :widths: 20 80 + + * - onInputUpdate(dt) + - | Called every frame (if the game is not paused) right after processing + | user input. Use it only for latency-critical stuff. + * - onKeyPress(key) + - | `Key `_ is pressed. + | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` + * - onKeyRelease(key) + - | `Key `_ is released. + | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` + * - onControllerButtonPress(id) + - | A `button `_ on a game controller is pressed. + | Usage example: ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` + * - onControllerButtonRelease(id) + - | A `button `_ on a game controller is released. + | Usage example: ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` + * - onInputAction(id) + - | `Game control `_ is pressed. + | Usage example: ``if id == input.ACTION.ToggleWeapon then ...`` + * - onTouchPress(touchEvent) + - | A finger pressed on a touch device. + | `Touch event `_. + * - onTouchRelease(touchEvent) + - | A finger released a touch device. + | `Touch event `_. + * - onTouchMove(touchEvent) + - | A finger moved on a touch device. + | `Touch event `_. \ No newline at end of file diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 361073e79d..5ebaa46e1f 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -313,5 +313,13 @@ -- @field [parent=#KeyboardEvent] #boolean withAlt Is `Alt` key pressed. -- @field [parent=#KeyboardEvent] #boolean withSuper Is `Super`/`Win` key pressed. +--- +-- The argument of onTouchPress/onTouchRelease/onTouchMove engine handlers. +-- @type TouchEvent +-- @field [parent=#TouchEvent] #number device Device id (there might be multiple touch devices connected). Note: the specific device ids are not guaranteed. Always use previous user input (onTouch... handlers) to get a valid device id (e. g. in your script's settings page). +-- @field [parent=#TouchEvent] #number finger Finger id (the device might support multitouch). +-- @field [parent=#TouchEvent] openmw.util#Vector2 position Relative position on the touch device (0 to 1 from top left corner), +-- @field [parent=#TouchEvent] #number pressure Pressure of the finger. + return nil