From c68dee214ee20b11ad03a7b4a50b15c7dc156246 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 10 Feb 2024 22:53:29 +0100 Subject: [PATCH 1/3] Mouse input engine handlers --- apps/openmw/mwbase/luamanager.hpp | 11 ++++++++++- apps/openmw/mwinput/mousemanager.cpp | 11 +++++++++++ apps/openmw/mwlua/inputprocessor.hpp | 15 ++++++++++++++- .../reference/lua-scripting/engine_handlers.rst | 9 +++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index e865756408..69693d47a2 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -81,6 +81,12 @@ namespace MWBase struct InputEvent { + struct WheelChange + { + int x; + int y; + }; + enum { KeyPressed, @@ -91,8 +97,11 @@ namespace MWBase TouchPressed, TouchReleased, TouchMoved, + MouseButtonPressed, + MouseButtonReleased, + MouseWheel, } mType; - std::variant mValue; + std::variant mValue; }; virtual void inputEvent(const InputEvent& event) = 0; diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 55b50b91ae..91ccd4e0a7 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -10,6 +10,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -118,6 +119,8 @@ namespace MWInput mBindingsManager->setPlayerControlsEnabled(!guiMode); mBindingsManager->mouseReleased(arg, id); + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonReleased, arg.button }); } } @@ -125,7 +128,11 @@ namespace MWInput { MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); if (mBindingsManager->isDetectingBindingState() || !input->controlsDisabled()) + { mBindingsManager->mouseWheelMoved(arg); + MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::MouseWheel, + MWBase::LuaManager::InputEvent::WheelChange{ arg.x, arg.y } }); + } input->setJoystickLastUsed(false); } @@ -161,7 +168,11 @@ namespace MWInput const MWGui::SettingsWindow* settingsWindow = MWBase::Environment::get().getWindowManager()->getSettingsWindow(); if ((!settingsWindow || !settingsWindow->isVisible()) && !input->controlsDisabled()) + { mBindingsManager->mousePressed(arg, id); + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonPressed, arg.button }); + } } void MouseManager::updateCursorMode() diff --git a/apps/openmw/mwlua/inputprocessor.hpp b/apps/openmw/mwlua/inputprocessor.hpp index e005183098..dcd19ae8cd 100644 --- a/apps/openmw/mwlua/inputprocessor.hpp +++ b/apps/openmw/mwlua/inputprocessor.hpp @@ -18,7 +18,7 @@ namespace MWLua { mScriptsContainer->registerEngineHandlers({ &mKeyPressHandlers, &mKeyReleaseHandlers, &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mTouchpadPressed, - &mTouchpadReleased, &mTouchpadMoved }); + &mTouchpadReleased, &mTouchpadMoved, &mMouseButtonPress, &mMouseButtonRelease, &mMouseWheel }); } void processInputEvent(const MWBase::LuaManager::InputEvent& event) @@ -53,6 +53,16 @@ namespace MWLua case InputEvent::TouchMoved: mScriptsContainer->callEngineHandlers(mTouchpadMoved, std::get(event.mValue)); break; + case InputEvent::MouseButtonPressed: + mScriptsContainer->callEngineHandlers(mMouseButtonPress, std::get(event.mValue)); + break; + case InputEvent::MouseButtonReleased: + mScriptsContainer->callEngineHandlers(mMouseButtonRelease, std::get(event.mValue)); + break; + case InputEvent::MouseWheel: + auto wheelEvent = std::get(event.mValue); + mScriptsContainer->callEngineHandlers(mMouseWheel, wheelEvent.y, wheelEvent.x); + break; } } @@ -66,6 +76,9 @@ namespace MWLua typename Container::EngineHandlerList mTouchpadPressed{ "onTouchPress" }; typename Container::EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; typename Container::EngineHandlerList mTouchpadMoved{ "onTouchMove" }; + typename Container::EngineHandlerList mMouseButtonPress{ "onMouseButtonPress" }; + typename Container::EngineHandlerList mMouseButtonRelease{ "onMouseButtonRelease" }; + typename Container::EngineHandlerList mMouseWheel{ "onMouseWheel" }; }; } diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 754a63b314..2b5e99e6ae 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -124,6 +124,15 @@ Engine handler is a function defined by a script, that can be called by the engi * - onTouchMove(touchEvent) - | A finger moved on a touch device. | `Touch event `_. + * - onMouseButtonPress(button) + - | A mouse button was pressed + | Button id + * - onMouseButtonRelease(button) + - | A mouse button was released + | Button id + * - onMouseWheel(vertical, horizontal) + - | Mouse wheel was scrolled + | vertical and horizontal mouse wheel change * - | onConsoleCommand( | mode, command, selectedObject) - | User entered `command` in in-game console. Called if either From f4fed4ca5f9729c94e98118afb464918e952e490 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 11 Feb 2024 16:27:54 +0100 Subject: [PATCH 2/3] Simplify inputBinding renderer, allow binding controller buttons --- .../lua-scripting/setting_renderers.rst | 7 +- .../data/scripts/omw/input/actionbindings.lua | 130 ++++++++++++------ files/data/scripts/omw/input/settings.lua | 92 ++++++++++--- 3 files changed, 166 insertions(+), 63 deletions(-) diff --git a/docs/source/reference/lua-scripting/setting_renderers.rst b/docs/source/reference/lua-scripting/setting_renderers.rst index 7f40eb08bd..f817b789bf 100644 --- a/docs/source/reference/lua-scripting/setting_renderers.rst +++ b/docs/source/reference/lua-scripting/setting_renderers.rst @@ -143,10 +143,9 @@ Table with the following fields: * - name - type (default) - description - * - type - - 'keyboardPress', 'keyboardHold' - - The type of input that's allowed to be bound * - key - #string - Key of the action or trigger to which the input is bound - + * - type + - 'action', 'trigger' + - Type of the key diff --git a/files/data/scripts/omw/input/actionbindings.lua b/files/data/scripts/omw/input/actionbindings.lua index 35a467fb52..bc871a3934 100644 --- a/files/data/scripts/omw/input/actionbindings.lua +++ b/files/data/scripts/omw/input/actionbindings.lua @@ -1,11 +1,7 @@ -local core = require('openmw.core') local input = require('openmw.input') local util = require('openmw.util') local async = require('openmw.async') local storage = require('openmw.storage') -local ui = require('openmw.ui') - -local I = require('openmw.interfaces') local actionPressHandlers = {} local function onActionPress(id, handler) @@ -89,48 +85,87 @@ end local bindingSection = storage.playerSection('OMWInputBindings') -local keyboardPresses = {} -local keybordHolds = {} -local boundActions = {} +local devices = { + keyboard = true, + mouse = true, + controller = true +} -local function bindAction(action) - if boundActions[action] then return end - boundActions[action] = true - input.bindAction(action, async:callback(function() - if keybordHolds[action] then - for _, binding in pairs(keybordHolds[action]) do - if input.isKeyPressed(binding.code) then return true end +local function invalidBinding(binding) + if not binding.key then + return 'has no key' + elseif binding.type ~= 'action' and binding.type ~= 'trigger' then + return string.format('has invalid type', binding.type) + elseif binding.type == 'action' and not input.actions[binding.key] then + return string.format("action %s doesn't exist", binding.key) + elseif binding.type == 'trigger' and not input.triggers[binding.key] then + return string.format("trigger %s doesn't exist", binding.key) + elseif not binding.device or not devices[binding.device] then + return string.format("invalid device %s", binding.device) + elseif not binding.button then + return 'has no button' + end +end + +local boundActions = {} +local actionBindings = {} + +local function bindAction(binding, id) + local action = binding.key + actionBindings[action] = actionBindings[action] or {} + actionBindings[action][id] = binding + if not boundActions[action] then + boundActions[binding.key] = true + input.bindAction(action, async:callback(function() + for _, binding in pairs(actionBindings[action] or {}) do + if binding.device == 'keyboard' then + if input.isKeyPressed(binding.button) then + return true + end + elseif binding.device == 'mouse' then + if input.isMouseButtonPressed(binding.button) then + return true + end + elseif binding.device == 'controller' then + if input.isControllerButtonPressed(binding.button) then + return true + end + end end - end - return false - end), {}) + return false + end), {}) + end +end + +local triggerBindings = {} +for device in pairs(devices) do triggerBindings[device] = {} end + +local function bindTrigger(binding, id) + local deviceBindings = triggerBindings[binding.device] + deviceBindings[binding.button] = deviceBindings[binding.button] or {} + deviceBindings[binding.button][id] = binding end local function registerBinding(binding, id) - if not input.actions[binding.key] and not input.triggers[binding.key] then - print(string.format('Skipping binding for unknown action or trigger: "%s"', binding.key)) - return - end - if binding.type == 'keyboardPress' then - local bindings = keyboardPresses[binding.code] or {} - bindings[id] = binding - keyboardPresses[binding.code] = bindings - elseif binding.type == 'keyboardHold' then - local bindings = keybordHolds[binding.key] or {} - bindings[id] = binding - keybordHolds[binding.key] = bindings - bindAction(binding.key) - else - error('Unknown binding type "' .. binding.type .. '"') + local invalid = invalidBinding(binding) + if invalid then + print(string.format('Skipping invalid binding %s: %s', id, invalid)) + elseif binding.type == 'action' then + bindAction(binding, id) + elseif binding.type == 'trigger' then + bindTrigger(binding, id) end end function clearBinding(id) - for _, boundTriggers in pairs(keyboardPresses) do - boundTriggers[id] = nil + for _, deviceBindings in pairs(triggerBindings) do + for _, buttonBindings in pairs(deviceBindings) do + buttonBindings[id] = nil + end end - for _, boundKeys in pairs(keybordHolds) do - boundKeys[id] = nil + + for _, bindings in pairs(actionBindings) do + bindings[id] = nil end end @@ -170,11 +205,24 @@ return { end end, onKeyPress = function(e) - local bindings = keyboardPresses[e.code] - if bindings then - for _, binding in pairs(bindings) do - input.activateTrigger(binding.key) - end + local buttonTriggers = triggerBindings.keyboard[e.code] + if not buttonTriggers then return end + for _, binding in pairs(buttonTriggers) do + input.activateTrigger(binding.key) + end + end, + onMouseButtonPress = function(button) + local buttonTriggers = triggerBindings.mouse[button] + if not buttonTriggers then return end + for _, binding in pairs(buttonTriggers) do + input.activateTrigger(binding.key) + end + end, + onControllerButtonPress = function(id) + local buttonTriggers = triggerBindings.controller[id] + if not buttonTriggers then return end + for _, binding in pairs(buttonTriggers) do + input.activateTrigger(binding.key) end end, } diff --git a/files/data/scripts/omw/input/settings.lua b/files/data/scripts/omw/input/settings.lua index 6c1b857131..3c1ba4d6b9 100644 --- a/files/data/scripts/omw/input/settings.lua +++ b/files/data/scripts/omw/input/settings.lua @@ -44,14 +44,63 @@ local bindingSection = storage.playerSection('OMWInputBindings') local recording = nil +local mouseButtonNames = { + [1] = 'Left', + [2] = 'Middle', + [3] = 'Right', + [4] = '4', + [5] = '5', +} + +-- TODO: support different controllers, use icons to render controller buttons +local controllerButtonNames = { + [-1] = 'Invalid', + [input.CONTROLLER_BUTTON.A] = "A", + [input.CONTROLLER_BUTTON.B] = "B", + [input.CONTROLLER_BUTTON.X] = "X", + [input.CONTROLLER_BUTTON.Y] = "Y", + [input.CONTROLLER_BUTTON.Back] = "Back", + [input.CONTROLLER_BUTTON.Guide] = "Guide", + [input.CONTROLLER_BUTTON.Start] = "Start", + [input.CONTROLLER_BUTTON.LeftStick] = "Left Stick", + [input.CONTROLLER_BUTTON.RightStick] = "Right Stick", + [input.CONTROLLER_BUTTON.LeftShoulder] = "LB", + [input.CONTROLLER_BUTTON.RightShoulder] = "RB", + [input.CONTROLLER_BUTTON.DPadUp] = "D-pad Up", + [input.CONTROLLER_BUTTON.DPadDown] = "D-pad Down", + [input.CONTROLLER_BUTTON.DPadLeft] = "D-pad Left", + [input.CONTROLLER_BUTTON.DPadRight] = "D-pad Right", +} + +local function bindingLabel(recording, binding) + if recording then + return interfaceL10n('N/A') + elseif not binding or not binding.button then + return interfaceL10n('None') + elseif binding.device == 'keyboard' then + return input.getKeyName(binding.button) + elseif binding.device == 'mouse' then + return string.format('Mouse %s', mouseButtonNames[binding.button] or 'Unknown') + elseif binding.device == 'controller' then + return string.format('Controller %s', controllerButtonNames[binding.button] or 'Unknown') + else + return 'Unknown' + end +end + +local inputTypes = { + action = input.actions, + trigger = input.triggers, +} I.Settings.registerRenderer('inputBinding', function(id, set, arg) if type(id) ~= 'string' then error('inputBinding: must have a string default value') end if not arg then error('inputBinding: argument with "key" and "type" is required') end if not arg.type then error('inputBinding: type argument is required') end + if not inputTypes[arg.type] then error('inputBinding: type must be "action" or "trigger"') end if not arg.key then error('inputBinding: key argument is required') end - local info = input.actions[arg.key] or input.triggers[arg.key] - if not info then return {} end + local info = inputTypes[arg.type][arg.key] + if not info then print(string.format('inputBinding: %s %s not found', arg.type, arg.key)) return end local l10n = core.l10n(info.key) @@ -70,9 +119,7 @@ I.Settings.registerRenderer('inputBinding', function(id, set, arg) } local binding = bindingSection:get(id) - local label = interfaceL10n('None') - if binding then label = input.getKeyName(binding.code) end - if recording and recording.id == id then label = interfaceL10n('N/A') end + local label = bindingLabel(recording and recording.id == id, binding) local recorder = { template = I.MWUI.templates.textNormal, @@ -115,22 +162,31 @@ I.Settings.registerRenderer('inputBinding', function(id, set, arg) return column end) +local function bindButton(device, button) + if recording == nil then return end + local binding = { + device = device, + button = button, + type = recording.arg.type, + key = recording.arg.key, + } + bindingSection:set(recording.id, binding) + local refresh = recording.refresh + recording = nil + refresh() +end + return { engineHandlers = { onKeyPress = function(key) - if recording == nil then return end - local binding = { - code = key.code, - type = recording.arg.type, - key = recording.arg.key, - } - if key.code == input.KEY.Escape then -- TODO: prevent settings modal from closing - binding.code = nil - end - bindingSection:set(recording.id, binding) - local refresh = recording.refresh - recording = nil - refresh() + bindButton(key.code ~= input.KEY.Escape and 'keyboard' or nil, key.code) + end, + -- TODO: currently never triggers, because mouse events are disabled while inside settings + onMouseButtonPress = function(button) + bindButton('mouse', button) + end, + onControllerButtonPress = function(id) + bindButton('controller', id) end, } } From 63a1bbb88d7829152f1b3ef17c3cc7c7b007f542 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 11 Feb 2024 23:49:26 +0100 Subject: [PATCH 3/3] Enable Lua mouse engine handlers while in UI --- apps/openmw/mwinput/mousemanager.cpp | 13 +++++++------ files/data/scripts/omw/input/settings.lua | 1 - 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 91ccd4e0a7..ffbe40a2db 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -119,9 +119,10 @@ namespace MWInput mBindingsManager->setPlayerControlsEnabled(!guiMode); mBindingsManager->mouseReleased(arg, id); - MWBase::Environment::get().getLuaManager()->inputEvent( - { MWBase::LuaManager::InputEvent::MouseButtonReleased, arg.button }); } + + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonReleased, arg.button }); } void MouseManager::mouseWheelMoved(const SDL_MouseWheelEvent& arg) @@ -130,11 +131,11 @@ namespace MWInput if (mBindingsManager->isDetectingBindingState() || !input->controlsDisabled()) { mBindingsManager->mouseWheelMoved(arg); - MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::MouseWheel, - MWBase::LuaManager::InputEvent::WheelChange{ arg.x, arg.y } }); } input->setJoystickLastUsed(false); + MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::MouseWheel, + MWBase::LuaManager::InputEvent::WheelChange{ arg.x, arg.y } }); } void MouseManager::mousePressed(const SDL_MouseButtonEvent& arg, Uint8 id) @@ -170,9 +171,9 @@ namespace MWInput if ((!settingsWindow || !settingsWindow->isVisible()) && !input->controlsDisabled()) { mBindingsManager->mousePressed(arg, id); - MWBase::Environment::get().getLuaManager()->inputEvent( - { MWBase::LuaManager::InputEvent::MouseButtonPressed, arg.button }); } + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonPressed, arg.button }); } void MouseManager::updateCursorMode() diff --git a/files/data/scripts/omw/input/settings.lua b/files/data/scripts/omw/input/settings.lua index 3c1ba4d6b9..5243a86844 100644 --- a/files/data/scripts/omw/input/settings.lua +++ b/files/data/scripts/omw/input/settings.lua @@ -181,7 +181,6 @@ return { onKeyPress = function(key) bindButton(key.code ~= input.KEY.Escape and 'keyboard' or nil, key.code) end, - -- TODO: currently never triggers, because mouse events are disabled while inside settings onMouseButtonPress = function(button) bindButton('mouse', button) end,