From 27c6a54d827c57c08f0c1d2335b2082a76461ec4 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Thu, 26 Jun 2014 21:33:54 -0600 Subject: [PATCH] Added AddInputCallback and RemoveInputCallback to Lua API for screen. ScreenManager now passes input to the lua side of each screen in addition to the engine side. --- Docs/Luadoc/Lua.xml | 7 ++ Docs/Luadoc/LuaDocumentation.xml | 27 +++++++ src/InputFilter.cpp | 12 +++ src/InputFilter.h | 4 + src/LuaReference.h | 7 ++ src/RageInputDevice.cpp | 2 + src/Screen.cpp | 126 +++++++++++++++++++++++++++++++ src/Screen.h | 13 ++++ src/ScreenManager.cpp | 7 +- 9 files changed, 204 insertions(+), 1 deletion(-) diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 6b0c5f0555..408810a49e 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -1271,11 +1271,13 @@ + + @@ -1986,6 +1988,11 @@ + + + + + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index 6a916c3c27..23b399b883 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -3576,6 +3576,30 @@ save yourself some time, copy this for undocumented things: + + This adds the lua function "callback" to the list of functions the screen will pass input to. Whenever an input event occurs, callback will be passed a table with the details of the event. callback must return a bool to indicate whether the event was handled. If callback returns true, the event will not be passed any further.
+ This should not be used to provide text input because that would not handle localization or different keyboard layouts.
+ The screen and the callbacks will both be passed input events, so be aware of what input the current screen responds to and consider the effects.
+ Details of the table containing the event data:
+ {
+ DeviceInput= { -- The raw details of the event.
+ device= string, -- The type of device. The first half of the string will be "Device_", the second half will be from InputDeviceNames in RageInputDevice.cpp.
+ button= string, -- The button that was pressed. the first half of the string will be "DeviceButton_", the second half will be from InitNames in RageInputDevice.cpp.
+ level= float, -- A floating point value for analog input.
+ z= float, -- Mousewheel input.
+ down= bool, -- Whether the button is down. This is level with a threshold and debouncing applied.
+ ago= float, -- How long ago this input occurred, in seconds.
+ is_joystick= bool, -- True if the device is a joystick.
+ is_mouse= bool -- True if the device is a mouse.
+ },
+ controller= string, -- The game controller this event was mapped to. "GameController_1" or "GameController_2", or nil if the event wasn't mapped to either controller. + button= string, -- The semi-raw button that was pressed. This is what the button was mapped to by the keymap settings, but without the conversions that occur when OnlyDedicatedMenuButtons is true. Will be empty if the button was not mapped.
+ type= string, -- The type of event. "InputEventType_FirstPress", "InputEventType_Repeat", or "InputEventType_Release".
+ GameButton= string, -- The cooked button that was pressed. This is button with mapping that occurs when OnlyDedicatedMenuButtons is true applied. This is nil for unmapped buttons.
+ PlayerNumber= PlayerNumber, -- The player that the controller is mapped to, or nil.
+ MultiPlayer= string, -- Unknown purpose.
+ } +
Returns the name of the next Screen. @@ -3594,6 +3618,9 @@ save yourself some time, copy this for undocumented things: Posts a message with the text sScreenMsg to the Screen after fDelay seconds. + + This removes the callback from the list. + [02 Other.lua] Gets a string from the current Screen in the current language. diff --git a/src/InputFilter.cpp b/src/InputFilter.cpp index bcf6ae1d0d..82bbe14e64 100644 --- a/src/InputFilter.cpp +++ b/src/InputFilter.cpp @@ -1,4 +1,5 @@ #include "global.h" +#include "LocalizedString.h" #include "InputFilter.h" #include "RageLog.h" #include "RageInput.h" @@ -11,6 +12,17 @@ #include "ScreenDimensions.h" #include + +static const char *InputEventTypeNames[] = { + "FirstPress", + "Repeat", + "Release" +}; + +XToString(InputEventType); +XToLocalizedString(InputEventType); +LuaXType(InputEventType); + struct ButtonState { ButtonState(); diff --git a/src/InputFilter.h b/src/InputFilter.h index 33a5e45d20..8a8b7b2494 100644 --- a/src/InputFilter.h +++ b/src/InputFilter.h @@ -22,6 +22,10 @@ enum InputEventType InputEventType_Invalid }; +const RString& InputEventTypeToString(InputEventType cat); +const RString& InputEventTypeToLocalizedString(InputEventType cat); +LuaDeclareType(InputEventType); + struct InputEvent { InputEvent(): type(IET_FIRST_PRESS) {} diff --git a/src/LuaReference.h b/src/LuaReference.h index 7c47bcf2e8..a847d5f743 100644 --- a/src/LuaReference.h +++ b/src/LuaReference.h @@ -17,6 +17,13 @@ public: LuaReference( const LuaReference &cpy ); LuaReference &operator=( const LuaReference &cpy ); + // Convenience constructor. + LuaReference(Lua *L) + :m_iReference(LUA_NOREF) + { + SetFromStack(L); + } + /* Create a reference pointing to the item at the top of the stack, and pop * the stack. */ void SetFromStack( Lua *L ); diff --git a/src/RageInputDevice.cpp b/src/RageInputDevice.cpp index 42861f4d93..39ea7e52e6 100644 --- a/src/RageInputDevice.cpp +++ b/src/RageInputDevice.cpp @@ -15,6 +15,7 @@ static const char *InputDeviceStateNames[] = { }; XToString( InputDeviceState ); XToLocalizedString( InputDeviceState ); +LuaXType(InputDevice); static map g_mapNamesToString; static map g_mapStringToNames; @@ -190,6 +191,7 @@ DeviceButton StringToDeviceButton( const RString& s ) return DeviceButton_Invalid; } +LuaXType(DeviceButton); static const char *InputDeviceNames[] = { "Key", diff --git a/src/Screen.cpp b/src/Screen.cpp index 6e337f98cd..758c163732 100644 --- a/src/Screen.cpp +++ b/src/Screen.cpp @@ -7,6 +7,7 @@ #include "ScreenManager.h" #include "ActorUtil.h" #include "InputEventPlus.h" +#include "InputMapper.h" #define NEXT_SCREEN THEME->GetMetric (m_sName,"NextScreen") #define PREV_SCREEN THEME->GetMetric (m_sName,"PrevScreen") @@ -54,6 +55,8 @@ void Screen::Init() m_smSendOnPop = SM_None; m_bRunning = false; + m_CallingInputCallbacks= false; + ActorUtil::LoadAllCommandsFromName( *this, m_sName, "Screen" ); PlayCommandNoRecurse( Message("Init") ); @@ -284,6 +287,107 @@ void Screen::ClearMessageQueue( const ScreenMessage SM ) m_QueuedMessages.erase( m_QueuedMessages.begin()+i ); } +bool Screen::PassInputToLua(const InputEventPlus& input) +{ + if(m_InputCallbacks.empty()) + { + return false; + } + m_CallingInputCallbacks= true; + bool handled= false; + Lua* L= LUA->Get(); + + // Construct the table once, and reuse it. + lua_createtable(L, 0, 7); + { // This block is meant to improve clarity. A subtable is created for + // storing the DeviceInput member. + lua_createtable(L, 0, 8); + Enum::Push(L, input.DeviceI.device); + lua_setfield(L, -2, "device"); + Enum::Push(L, input.DeviceI.button); + lua_setfield(L, -2, "button"); + lua_pushnumber(L, input.DeviceI.level); + lua_setfield(L, -2, "level"); + lua_pushinteger(L, input.DeviceI.z); + lua_setfield(L, -2, "z"); + lua_pushboolean(L, input.DeviceI.bDown); + lua_setfield(L, -2, "down"); + lua_pushnumber(L, input.DeviceI.ts.Ago()); + lua_setfield(L, -2, "ago"); + lua_pushboolean(L, input.DeviceI.IsJoystick()); + lua_setfield(L, -2, "is_joystick"); + lua_pushboolean(L, input.DeviceI.IsMouse()); + lua_setfield(L, -2, "is_mouse"); + } + lua_setfield(L, -2, "DeviceInput"); + Enum::Push(L, input.GameI.controller); + lua_setfield(L, -2, "controller"); + LuaHelpers::Push(L, GameButtonToString(INPUTMAPPER->GetInputScheme(), input.GameI.button)); + lua_setfield(L, -2, "button"); + Enum::Push(L, input.type); + lua_setfield(L, -2, "type"); + LuaHelpers::Push(L, GameButtonToString(INPUTMAPPER->GetInputScheme(), input.MenuI)); + lua_setfield(L, -2, "GameButton"); + Enum::Push(L, input.pn); + lua_setfield(L, -2, "PlayerNumber"); + Enum::Push(L, input.mp); + lua_setfield(L, -2, "MultiPlayer"); + for(map::iterator callback= m_InputCallbacks.begin(); + callback != m_InputCallbacks.end() && !handled; ++callback) + { + callback->second.PushSelf(L); + lua_pushvalue(L, -2); + RString error; + if(!LuaHelpers::RunScriptOnStack(L, error, 1, 1)) + { + LOG->Warn("Error running input callback: %s", error.c_str()); + } + handled= lua_toboolean(L, -1); + lua_pop(L, 1); + } + lua_pop(L, 1); + LUA->Release(L); + m_CallingInputCallbacks= false; + if(!m_DelayedCallbackRemovals.empty()) + { + for(vector::iterator key= m_DelayedCallbackRemovals.begin(); + key != m_DelayedCallbackRemovals.end(); ++key) + { + InternalRemoveCallback(*key); + } + } + return handled; +} + +void Screen::AddInputCallbackFromStack(lua_State* L) +{ + callback_key_t key= lua_topointer(L, -1); + m_InputCallbacks[key]= LuaReference(L); +} + +void Screen::RemoveInputCallback(lua_State* L) +{ + callback_key_t key= lua_topointer(L, -1); + if(m_CallingInputCallbacks) + { + m_DelayedCallbackRemovals.push_back(key); + } + else + { + InternalRemoveCallback(key); + } +} + +void Screen::InternalRemoveCallback(callback_key_t key) +{ + map::iterator iter= m_InputCallbacks.find(key); + if(iter != m_InputCallbacks.end()) + { + m_InputCallbacks.erase(iter); + } +} + + // lua start #include "LuaBinding.h" @@ -304,6 +408,26 @@ public: return 0; } + static int AddInputCallback(T* p, lua_State* L) + { + if(!lua_isfunction(L, -1)) + { + luaL_error(L, "Input callback must be a function."); + } + p->AddInputCallbackFromStack(L); + return 0; + } + + static int RemoveInputCallback(T* p, lua_State* L) + { + if(!lua_isfunction(L, -1)) + { + luaL_error(L, "Input callback must be a function."); + } + p->RemoveInputCallback(L); + return 0; + } + LunaScreen() { ADD_METHOD( GetNextScreenName ); @@ -311,6 +435,8 @@ public: ADD_METHOD( PostScreenMessage ); ADD_METHOD( lockinput ); ADD_METHOD( GetScreenType ); + ADD_METHOD( AddInputCallback ); + ADD_METHOD( RemoveInputCallback ); } }; diff --git a/src/Screen.h b/src/Screen.h index 31e1939f2d..0de9447f06 100644 --- a/src/Screen.h +++ b/src/Screen.h @@ -124,6 +124,10 @@ public: RString GetNextScreenName() const; RString GetPrevScreen() const; + bool PassInputToLua(const InputEventPlus& input); + void AddInputCallbackFromStack(lua_State* L); + void RemoveInputCallback(lua_State* L); + // let subclass override if they want virtual bool MenuUp(const InputEventPlus &) { return false; } virtual bool MenuDown(const InputEventPlus &) { return false; } @@ -139,6 +143,15 @@ public: //virtual bool MiddleClick(const InputEventPlus &) { } //virtual bool MouseWheelUp(const InputEventPlus &) { } //virtual bool MouseWheelDown(const InputEventPlus &) { } + +private: + // void* is the key so that we can use lua_topointer to find the callback + // to remove when removing a callback. + typedef void const* callback_key_t; + map m_InputCallbacks; + vector m_DelayedCallbackRemovals; + bool m_CallingInputCallbacks; + void InternalRemoveCallback(callback_key_t key); }; #endif diff --git a/src/ScreenManager.cpp b/src/ScreenManager.cpp index 9b68e660c0..67dec582cf 100644 --- a/src/ScreenManager.cpp +++ b/src/ScreenManager.cpp @@ -509,7 +509,11 @@ void ScreenManager::Input( const InputEventPlus &input ) for( unsigned i = 0; i < g_OverlayScreens.size(); ++i ) { Screen *pScreen = g_OverlayScreens[i]; - if( pScreen->Input(input) ) + bool handled= pScreen->Input(input); + // Pass input to the screen and lua. Contention shouldn't be a problem + // because anybody setting an input callback is probably doing it to + // do something in addition to whatever the screen does. + if(pScreen->PassInputToLua(input) || handled) return; } @@ -522,6 +526,7 @@ void ScreenManager::Input( const InputEventPlus &input ) return; g_ScreenStack.back().m_pScreen->Input( input ); + g_ScreenStack.back().m_pScreen->PassInputToLua( input ); } // Just create a new screen; don't do any associated cleanup.