1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-22 03:40:49 +00:00

Merge branch 'controller_touchpads' into 'master'

Support controller touchpads (Resolves https://gitlab.com/OpenMW/openmw/-/issues/6639)

Closes #6639

See merge request OpenMW/openmw!1684
This commit is contained in:
Petr Mikheev 2022-02-21 19:49:00 +00:00
commit 23962f113c
10 changed files with 207 additions and 83 deletions

View File

@ -58,7 +58,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua add_openmw_dir (mwlua
luamanagerimp actions object worldview userdataserializer eventqueue query luamanagerimp actions object worldview userdataserializer eventqueue query
luabindings localscripts objectbindings cellbindings asyncbindings settingsbindings luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings
camerabindings uibindings inputbindings nearbybindings camerabindings uibindings inputbindings nearbybindings
) )

View File

@ -4,6 +4,8 @@
#include <variant> #include <variant>
#include <SDL_events.h> #include <SDL_events.h>
#include <components/sdlutil/events.hpp>
namespace MWWorld namespace MWWorld
{ {
class Ptr; class Ptr;
@ -43,8 +45,17 @@ namespace MWBase
struct InputEvent struct InputEvent
{ {
enum {KeyPressed, KeyReleased, ControllerPressed, ControllerReleased, Action} mType; enum {
std::variant<SDL_Keysym, int> mValue; KeyPressed,
KeyReleased,
ControllerPressed,
ControllerReleased,
Action,
TouchPressed,
TouchReleased,
TouchMoved,
} mType;
std::variant<SDL_Keysym, int, SDLUtil::TouchEvent> mValue;
}; };
virtual void inputEvent(const InputEvent& event) = 0; virtual void inputEvent(const InputEvent& event) = 0;

View File

@ -433,4 +433,22 @@ namespace MWInput
#endif #endif
return std::array<float, 3>({gyro[0], gyro[1], gyro[2]}); return std::array<float, 3>({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 });
}
} }

View File

@ -31,6 +31,10 @@ namespace MWInput
void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) override; void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) override;
void controllerRemoved(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 processChangedSettings(const Settings::CategorySettingVector& changed);
void setJoystickLastUsed(bool enabled) { mJoystickLastUsed = enabled; } void setJoystickLastUsed(bool enabled) { mJoystickLastUsed = enabled; }

View File

@ -4,6 +4,8 @@
#include <SDL_gamecontroller.h> #include <SDL_gamecontroller.h>
#include <SDL_mouse.h> #include <SDL_mouse.h>
#include <components/sdlutil/events.hpp>
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"
#include "../mwinput/actions.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["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; }); keyEvent["withSuper"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; });
auto touchpadEvent = context.mLua->sol().new_usertype<SDLUtil::TouchEvent>("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(); MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
sol::table api(context.mLua->sol(), sol::create); sol::table api(context.mLua->sol(), sol::create);

View File

@ -3,6 +3,8 @@
#include <SDL_events.h> #include <SDL_events.h>
#include <components/sdlutil/events.hpp>
#include "../mwbase/luamanager.hpp" #include "../mwbase/luamanager.hpp"
#include "localscripts.hpp" #include "localscripts.hpp"
@ -15,9 +17,12 @@ namespace MWLua
public: public:
PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj, ESM::LuaScriptCfg::sPlayer) PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj, ESM::LuaScriptCfg::sPlayer)
{ {
registerEngineHandlers({&mKeyPressHandlers, &mKeyReleaseHandlers, registerEngineHandlers({
&mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers,
&mActionHandlers, &mInputUpdateHandlers}); &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers,
&mActionHandlers, &mInputUpdateHandlers,
&mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved
});
} }
void processInputEvent(const MWBase::LuaManager::InputEvent& event) void processInputEvent(const MWBase::LuaManager::InputEvent& event)
@ -40,6 +45,15 @@ namespace MWLua
case InputEvent::Action: case InputEvent::Action:
callEngineHandlers(mActionHandlers, std::get<int>(event.mValue)); callEngineHandlers(mActionHandlers, std::get<int>(event.mValue));
break; break;
case InputEvent::TouchPressed:
callEngineHandlers(mTouchpadPressed, std::get<SDLUtil::TouchEvent>(event.mValue));
break;
case InputEvent::TouchReleased:
callEngineHandlers(mTouchpadReleased, std::get<SDLUtil::TouchEvent>(event.mValue));
break;
case InputEvent::TouchMoved:
callEngineHandlers(mTouchpadMoved, std::get<SDLUtil::TouchEvent>(event.mValue));
break;
} }
} }
@ -52,6 +66,9 @@ namespace MWLua
EngineHandlerList mControllerButtonReleaseHandlers{"onControllerButtonRelease"}; EngineHandlerList mControllerButtonReleaseHandlers{"onControllerButtonRelease"};
EngineHandlerList mActionHandlers{"onInputAction"}; EngineHandlerList mActionHandlers{"onInputAction"};
EngineHandlerList mInputUpdateHandlers{"onInputUpdate"}; EngineHandlerList mInputUpdateHandlers{"onInputUpdate"};
EngineHandlerList mTouchpadPressed{ "onTouchPress" };
EngineHandlerList mTouchpadReleased{ "onTouchRelease" };
EngineHandlerList mTouchpadMoved{ "onTouchMove" };
}; };
} }

View File

@ -1,6 +1,7 @@
#ifndef _SFO_EVENTS_H #ifndef _SFO_EVENTS_H
#define _SFO_EVENTS_H #define _SFO_EVENTS_H
#include <SDL_version.h>
#include <SDL_types.h> #include <SDL_types.h>
#include <SDL_events.h> #include <SDL_events.h>
@ -18,6 +19,24 @@ struct MouseMotionEvent : SDL_MouseMotionEvent {
Sint32 z; 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 // // Listeners //
@ -50,25 +69,24 @@ public:
virtual void keyReleased(const SDL_KeyboardEvent &arg) = 0; virtual void keyReleased(const SDL_KeyboardEvent &arg) = 0;
}; };
class ControllerListener class ControllerListener
{ {
public: public:
virtual ~ControllerListener() {} 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; 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; virtual void axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) = 0;
/** @remarks Joystick Added **/
virtual void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) = 0; virtual void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) = 0;
/** @remarks Joystick Removed **/
virtual void controllerRemoved(const SDL_ControllerDeviceEvent &arg) = 0; 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 class WindowListener

View File

@ -146,6 +146,20 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr<osgViewer::Viewer> v
if(mConListener) if(mConListener)
mConListener->axisMoved(1, evt.caxis); mConListener->axisMoved(1, evt.caxis);
break; 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: case SDL_WINDOWEVENT:
handleWindowEvent(evt); handleWindowEvent(evt);
break; break;

View File

@ -3,74 +3,96 @@ Engine handlers reference
Engine handler is a function defined by a script, that can be called by the engine. 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 <openmw_input.html##(KeyboardEvent)>`_ is pressed. |
| | | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` |
+----------------------------------+----------------------------------------------------------------------+
| onKeyRelease(key) | | `Key <openmw_input.html##(KeyboardEvent)>`_ is released. |
| | | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` |
+----------------------------------+----------------------------------------------------------------------+
| onControllerButtonPress(id) | | A `button <openmw_input.html##(CONTROLLER_BUTTON)>`_ on a game |
| | controller is pressed. Usage example: |
| | | ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` |
+----------------------------------+----------------------------------------------------------------------+
| onControllerButtonRelease(id) | | A `button <openmw_input.html##(CONTROLLER_BUTTON)>`_ on a game |
| | controller is released. Usage example: |
| | | ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` |
+----------------------------------+----------------------------------------------------------------------+
| onInputAction(id) | | `Game control <openmw_input.html##(ACTION)>`_ 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 <openmw_input.html##(KeyboardEvent)>`_ is pressed.
| Usage example: ``if key.symbol == 'z' and key.withShift then ...``
* - onKeyRelease(key)
- | `Key <openmw_input.html##(KeyboardEvent)>`_ is released.
| Usage example: ``if key.symbol == 'z' and key.withShift then ...``
* - onControllerButtonPress(id)
- | A `button <openmw_input.html##(CONTROLLER_BUTTON)>`_ on a game controller is pressed.
| Usage example: ``if id == input.CONTROLLER_BUTTON.LeftStick then ...``
* - onControllerButtonRelease(id)
- | A `button <openmw_input.html##(CONTROLLER_BUTTON)>`_ on a game controller is released.
| Usage example: ``if id == input.CONTROLLER_BUTTON.LeftStick then ...``
* - onInputAction(id)
- | `Game control <openmw_input.html##(ACTION)>`_ is pressed.
| Usage example: ``if id == input.ACTION.ToggleWeapon then ...``
* - onTouchPress(touchEvent)
- | A finger pressed on a touch device.
| `Touch event <openmw_input.html##(TouchEvent)>`_.
* - onTouchRelease(touchEvent)
- | A finger released a touch device.
| `Touch event <openmw_input.html##(TouchEvent)>`_.
* - onTouchMove(touchEvent)
- | A finger moved on a touch device.
| `Touch event <openmw_input.html##(TouchEvent)>`_.

View File

@ -313,5 +313,13 @@
-- @field [parent=#KeyboardEvent] #boolean withAlt Is `Alt` key pressed. -- @field [parent=#KeyboardEvent] #boolean withAlt Is `Alt` key pressed.
-- @field [parent=#KeyboardEvent] #boolean withSuper Is `Super`/`Win` 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 return nil