diff --git a/data/meta/Input.lua b/data/meta/Input.lua new file mode 100644 index 0000000000..73896a6e9a --- /dev/null +++ b/data/meta/Input.lua @@ -0,0 +1,201 @@ +-- Copyright © 2008-2024 Pioneer Developers. See AUTHORS.txt for details +-- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +-- This file implements type information about C++ classes for Lua static analysis +-- TODO: document function usage + +---@meta + +---@class Input +local Input = {} + +--- List of input keys to be used with various input functions. +--- The list is a mostly-complete port of common SDL_KeyCode values, indexed by +--- lowercase key names. +---@type table +Input.keys = {} + +--================================== + +---@class Input.ActionBinding +---@field id string +---@field type string +---@field binding table +---@field binding2 table +local ActionBinding = {} + +function ActionBinding:SetPressed() end +function ActionBinding:SetReleased() end + +---@return boolean +function ActionBinding:IsActive() end +---@return boolean +function ActionBinding:IsJustActive() end + +---@param cb fun() +function ActionBinding:OnPressed(cb) end +---@param cb fun() +function ActionBinding:OnReleased(cb) end + +--================================== + +---@class Input.AxisBinding +---@field id string +---@field type string +---@field axis table +---@field positive table +---@field negative table +local AxisBinding = {} + +---@param val number +function AxisBinding:SetValue(val) end +---@return number +function AxisBinding:GetValue() end + +---@param cb fun(val: number) +function AxisBinding:OnValueChanged(cb) end + +--================================== + +---@class Input.JoystickInfo +---@field name string +---@field numButtons integer +---@field numHats integer +---@field numAxes integer +local JoystickInfo = {} + +---@param button integer +---@return boolean +function JoystickInfo:GetButtonState(button) end + +---@param hat integer +---@return boolean +function JoystickInfo:GetHatState(hat) end + +---@param axis integer +---@return number +function JoystickInfo:GetAxisValue(axis) end + +---@param axis integer +---@return number +function JoystickInfo:GetAxisDeadzone(axis) end + +---@param axis integer +---@param deadzone number +function JoystickInfo:SetAxisDeadzone(axis, deadzone) end + +---@param axis integer +---@return number +function JoystickInfo:GetAxisCurve(axis) end + +---@param axis integer +---@param curve number +function JoystickInfo:SetAxisCurve(axis, curve) end + +---@param axis integer +---@return boolean +function JoystickInfo:GetAxisZeroToOne(axis) end +---@param axis integer +---@param zeroToOne boolean +function JoystickInfo:SetAxisZeroToOne(axis, zeroToOne) end + +--================================== + +---@class Input.InputFrame +---@field id string +local InputFrame = {} + +-- Add the given action binding to this frame +---@param action Input.ActionBinding +function InputFrame:AddAction(action) end + +-- Add the given axis binding to this frame +---@param axis Input.AxisBinding +function InputFrame:AddAxis(axis) end + +-- Add the input frame to the stack of active inputs +---@return boolean +function InputFrame:AddToStack() end + +-- Remove the input frame from the stack of active inputs +function InputFrame:RemoveFromStack() end + +--================================== + +---@param enable boolean +function Input.EnableBindings(enable) end + +---@return (Input.AxisBinding|Input.ActionBinding)[][][] +function Input.GetBindingPages() end + +---@param id string +---@return Input.ActionBinding? +function Input.GetActionBinding(id) end + +---@param id string +---@return Input.AxisBinding? +function Input.GetAxisBinding(id) end + +---@param key integer +function Input.GetKeyName(key) end + +---@return number +function Input.GetJoystickCount() end + +---@param joy integer +---@return string +function Input.GetJoystickName(joy) end + +---@param joy integer +---@return boolean +function Input.IsJoystickConnected(joy) end + +---@param joy integer +---@return Input.JoystickInfo? +function Input.GetJoystick(joy) end + +---@param joy integer +function Input.SaveJoystickConfig(joy) end + +---@param binding Input.AxisBinding | Input.ActionBinding +function Input.SaveBinding(binding) end + +---@return boolean +function Input.GetMouseYInverted() end + +---@param inverted boolean +function Input.SetMouseYInverted(inverted) end + +---@return boolean +function Input.GetJoystickEnabled() end + +---@param enabled boolean +function Input.SetJoystickEnabled(enabled) end + +---@return boolean +function Input.GetMouseCaptured() end + +-- Create an InputFrame to add action/axis bindings to the active input stack +---@param id string The debug identifier of this InputFrame +---@param modal boolean? Should this InputFrame be modal? +---@return Input.InputFrame +function Input.CreateInputFrame(id, modal) end + +-- Register an ActionBinding from Lua with the given default key associations +---@param id string The identifier of the action binding. Must be globally unique. +---@param groupId string The page and group this action binding is organized into, in the form "page.group" +---@param b1 table? The primary keychord for this binding +---@param b2 table? The secondary keychord for this binding +---@return Input.ActionBinding +function Input.RegisterActionBinding(id, groupId, b1, b2) end + +-- Register an AxisBinding from Lua with the given default key associations +---@param id string The identifier of the axis binding. Must be globally unique. +---@param groupId string The page and group this axis binding is organized into, in the form "page.group" +---@param pos table? The positive-direction keychord for this axis binding +---@param neg table? The negative-direction keychord for this axis binding +---@param axis table? The joystick axis to associate with this axis binding +---@return Input.AxisBinding +function Input.RegisterAxisBinding(id, groupId, pos, neg, axis) end + +return Input diff --git a/data/modules/Debug/TestInput.lua b/data/modules/Debug/TestInput.lua new file mode 100644 index 0000000000..69dd633151 --- /dev/null +++ b/data/modules/Debug/TestInput.lua @@ -0,0 +1,23 @@ +-- Copyright © 2008-2024 Pioneer Developers. See AUTHORS.txt for details +-- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +-- This file tests the Lua input API + +--[[ +local Input = require 'Input' + +local action_disable = Input.RegisterActionBinding("TestInputDisable", "Debug.TestInput", { activator = { key = Input.keys["return"] } }) + +local frame = Input.CreateInputFrame("TestInputFrame", true) +frame:AddAction(action_disable) + +action_disable:OnPressed(function() + frame:RemoveFromStack() + + print("Test!") +end) + +require 'Event'.Register("onGameStart", function() + frame:AddToStack() +end) +--]] diff --git a/data/pigui/libs/forwarded.lua b/data/pigui/libs/forwarded.lua index 676ee08c67..d5b26a8cc2 100644 --- a/data/pigui/libs/forwarded.lua +++ b/data/pigui/libs/forwarded.lua @@ -4,6 +4,7 @@ -- Stuff from the C++ side that we want available directly in Lua -- without any wrappers local Engine = require 'Engine' +local Input = require 'Input' local pigui = Engine.pigui ---@class ui @@ -120,7 +121,7 @@ ui.setColumnOffset = pigui.SetColumnOffset ui.getColumnWidth = pigui.GetColumnWidth ui.setColumnWidth = pigui.SetColumnWidth ui.getScrollY = pigui.GetScrollY -ui.keys = pigui.keys +ui.keys = Input.keys ui.isKeyReleased = pigui.IsKeyReleased ui.playSfx = pigui.PlaySfx ui.isItemHovered = pigui.IsItemHovered diff --git a/data/pigui/modules/autopilot-window.lua b/data/pigui/modules/autopilot-window.lua index f2e8f44576..4c6c49384b 100644 --- a/data/pigui/modules/autopilot-window.lua +++ b/data/pigui/modules/autopilot-window.lua @@ -133,7 +133,7 @@ local speed_limiter = (function() -- don't show tooltip during animation local tooltip = variant and bindings.limiter.tooltip or anim_active and "" or lui.TURN_OFF if ui.mainMenuButton(icons.speed_limiter, tooltip .. "##speed_limiter_toggle", variant) then - bindings.limiter.action:OnPress() + bindings.limiter.action:SetPressed() end if toggle_limiter then diff --git a/src/Input.cpp b/src/Input.cpp index 146b4a76d1..0ddcbbe432 100644 --- a/src/Input.cpp +++ b/src/Input.cpp @@ -314,7 +314,6 @@ bool Manager::AddInputFrame(InputFrame *frame) m_inputFrames.push_back(frame); frame->active = true; - frame->onFrameAdded.emit(frame); m_frameListChanged = true; return true; @@ -363,7 +362,6 @@ void Manager::RemoveInputFrame(InputFrame *frame) ClearInputFrameState(frame); frame->active = false; - frame->onFrameRemoved.emit(frame); m_frameListChanged = true; } } @@ -414,6 +412,16 @@ InputBindings::Axis *Manager::GetAxisBinding(const std::string &id) return axisBindings.count(id) ? &axisBindings[id] : &Input::nullAxis; } +bool Manager::HasActionBinding(const std::string &id) const +{ + return actionBindings.count(id) > 0; +} + +bool Manager::HasAxisBinding(const std::string &id) const +{ + return axisBindings.count(id) > 0; +} + /* STATE MANAGEMENT diff --git a/src/Input.h b/src/Input.h index c230b3e7fa..d79fba3e13 100644 --- a/src/Input.h +++ b/src/Input.h @@ -49,9 +49,10 @@ namespace Input { using Axis = InputBindings::Axis; using Action = InputBindings::Action; - InputFrame(Input::Manager *man, bool modal = false) : + InputFrame(Input::Manager *man, bool modal = false, std::string_view id = "") : manager(man), - modal(modal) + modal(modal), + id(id) {} std::vector actions; @@ -61,16 +62,11 @@ namespace Input { Manager *manager = nullptr; bool active = false; bool modal = false; + std::string id; // Call this at startup to register all the bindings associated with the frame. virtual void RegisterBindings(){}; - // Called when the frame is added to the stack. - sigc::signal onFrameAdded; - - // Called when the frame is removed from the stack. - sigc::signal onFrameRemoved; - Action *AddAction(const std::string &id); Axis *AddAxis(const std::string &id); }; @@ -160,11 +156,13 @@ class Input::Manager { // The returned binding pointer points to the actual binding. InputBindings::Action *AddActionBinding(const std::string &id, BindingGroup *group, InputBindings::Action &&binding); InputBindings::Action *GetActionBinding(const std::string &id); + bool HasActionBinding(const std::string &id) const; // Creates a new axis binding, copying the provided binding. // The returned binding pointer points to the actual binding. InputBindings::Axis *AddAxisBinding(const std::string &id, BindingGroup *group, InputBindings::Axis &&binding); InputBindings::Axis *GetAxisBinding(const std::string &id); + bool HasAxisBinding(const std::string &id) const; // Call EnableBindings() to temporarily disable handling input bindings while // you're recording a new input binding or are in a modal window. diff --git a/src/lua/Lua.cpp b/src/lua/Lua.cpp index 0059b7838d..92f297901c 100644 --- a/src/lua/Lua.cpp +++ b/src/lua/Lua.cpp @@ -142,6 +142,7 @@ namespace Lua { void UninitModules() { LuaEvent::Uninit(); + LuaInput::Uninit(); delete Pi::luaNameGen; diff --git a/src/lua/LuaInput.cpp b/src/lua/LuaInput.cpp index 6643493cb7..19442857c8 100644 --- a/src/lua/LuaInput.cpp +++ b/src/lua/LuaInput.cpp @@ -2,21 +2,245 @@ // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #include "LuaInput.h" + +#include "ConnectionTicket.h" #include "GameConfig.h" #include "Input.h" #include "InputBindings.h" -#include "Lang.h" #include "LuaMetaType.h" #include "LuaObject.h" #include "LuaUtils.h" #include "LuaWrappable.h" #include "Pi.h" -#include "SDL_keyboard.h" +#include "core/StringUtils.h" + +#include +#include #include using namespace InputBindings; +static std::vector> s_input_keys = { + // Symbol keys + + { "exclaim", SDLK_EXCLAIM }, + { "quotedbl", SDLK_QUOTEDBL }, + { "hash", SDLK_HASH }, + { "percent", SDLK_PERCENT }, + { "dollar", SDLK_DOLLAR }, + { "ampersand", SDLK_AMPERSAND }, + { "quote", SDLK_QUOTE }, + { "leftparen", SDLK_LEFTPAREN }, + { "rightparen", SDLK_RIGHTPAREN }, + { "asterisk", SDLK_ASTERISK }, + { "plus", SDLK_PLUS }, + { "comma", SDLK_COMMA }, + { "minus", SDLK_MINUS }, + { "period", SDLK_PERIOD }, + { "slash", SDLK_SLASH }, + + { "colon", SDLK_COLON }, + { "semicolon", SDLK_SEMICOLON }, + { "less", SDLK_LESS }, + { "equals", SDLK_EQUALS }, + { "greater", SDLK_GREATER }, + { "question", SDLK_QUESTION }, + { "at", SDLK_AT }, + + { "leftbracket", SDLK_LEFTBRACKET }, + { "backslash", SDLK_BACKSLASH }, + { "rightbracket", SDLK_RIGHTBRACKET }, + { "caret", SDLK_CARET }, + { "underscore", SDLK_UNDERSCORE }, + { "backquote", SDLK_BACKQUOTE }, + + // Misc. keys + { "return", SDLK_RETURN }, + { "escape", SDLK_ESCAPE }, + { "backspace", SDLK_BACKSPACE }, + { "tab", SDLK_TAB }, + { "space", SDLK_SPACE }, + + { "capslock", SDLK_CAPSLOCK }, + { "printscreen", SDLK_PRINTSCREEN }, + { "scrolllock", SDLK_SCROLLLOCK }, + { "pause", SDLK_PAUSE }, + { "insert", SDLK_INSERT }, + { "home", SDLK_HOME }, + { "pageup", SDLK_PAGEUP }, + { "delete", SDLK_DELETE }, + { "end", SDLK_END }, + { "pagedown", SDLK_PAGEDOWN }, + { "right", SDLK_RIGHT }, + { "left", SDLK_LEFT }, + { "down", SDLK_DOWN }, + { "up", SDLK_UP }, + + { "numlockclear", SDLK_NUMLOCKCLEAR }, + { "kp_divide", SDLK_KP_DIVIDE }, + { "kp_multiply", SDLK_KP_MULTIPLY }, + { "kp_minus", SDLK_KP_MINUS }, + { "kp_plus", SDLK_KP_PLUS }, + { "kp_enter", SDLK_KP_ENTER }, + { "kp_1", SDLK_KP_1 }, + { "kp_2", SDLK_KP_2 }, + { "kp_3", SDLK_KP_3 }, + { "kp_4", SDLK_KP_4 }, + { "kp_5", SDLK_KP_5 }, + { "kp_6", SDLK_KP_6 }, + { "kp_7", SDLK_KP_7 }, + { "kp_8", SDLK_KP_8 }, + { "kp_9", SDLK_KP_9 }, + { "kp_0", SDLK_KP_0 }, + { "kp_period", SDLK_KP_PERIOD }, + + { "lctrl", SDLK_LCTRL }, + { "lshift", SDLK_LSHIFT }, + { "lalt", SDLK_LALT }, + { "lgui", SDLK_LGUI }, + { "rctrl", SDLK_RCTRL }, + { "rshift", SDLK_RSHIFT }, + { "ralt", SDLK_RALT }, + { "rgui", SDLK_RGUI }, + + // Function keys + + { "f1", SDLK_F1 }, + { "f2", SDLK_F2 }, + { "f3", SDLK_F3 }, + { "f4", SDLK_F4 }, + { "f5", SDLK_F5 }, + { "f6", SDLK_F6 }, + { "f7", SDLK_F7 }, + { "f8", SDLK_F8 }, + { "f9", SDLK_F9 }, + { "f10", SDLK_F10 }, + { "f11", SDLK_F11 }, + { "f12", SDLK_F12 }, + + // letters + + { "a", SDLK_a }, + { "b", SDLK_b }, + { "c", SDLK_c }, + { "d", SDLK_d }, + { "e", SDLK_e }, + { "f", SDLK_f }, + { "g", SDLK_g }, + { "h", SDLK_h }, + { "i", SDLK_i }, + { "j", SDLK_j }, + { "k", SDLK_k }, + { "l", SDLK_l }, + { "m", SDLK_m }, + { "n", SDLK_n }, + { "o", SDLK_o }, + { "p", SDLK_p }, + { "q", SDLK_q }, + { "r", SDLK_r }, + { "s", SDLK_s }, + { "t", SDLK_t }, + { "u", SDLK_u }, + { "v", SDLK_v }, + { "w", SDLK_w }, + { "x", SDLK_x }, + { "y", SDLK_y }, + { "z", SDLK_z }, + + // numbers + + { "0", SDLK_0 }, + { "1", SDLK_1 }, + { "2", SDLK_2 }, + { "3", SDLK_3 }, + { "4", SDLK_4 }, + { "5", SDLK_5 }, + { "6", SDLK_6 }, + { "7", SDLK_7 }, + { "8", SDLK_8 }, + { "9", SDLK_9 }, +}; + +static LuaRef s_connections; + +// Helper wrapping a ConnectionTicket to clean up a Lua callback associated with an InputAction +struct LuaInputCallback : public LuaWrappable { + ConnectionTicket connection; +}; + +// Store a callback ConnectionTicket so it can be cleaned up later +static void store_callback(lua_State *l, sigc::connection &&connection, int cb_idx) +{ + LUA_DEBUG_START(l); + + std::string moduleName = pi_lua_get_caller_module(l); + + s_connections.PushCopyToStack(); + + // Associate the callback with the module name + luaL_getsubtable(l, -1, moduleName.c_str()); + lua_pushvalue(l, cb_idx); + lua_rawseti(l, -2, luaL_len(l, -2) + 1); + lua_pop(l, 1); + + lua_pushvalue(l, cb_idx); // s_connections, callback + lua_rawget(l, -2); + + // Create the list of connections for this callback + if (lua_isnil(l, -1)) { + lua_pop(l, 1); + lua_newtable(l); + lua_pushvalue(l, cb_idx); + lua_pushvalue(l, -2); // s_connections, newtab, callback, newtab(2) + lua_rawset(l, -4); + } + + // Stack: s_connections, connections + LuaObject::PushToLua(LuaInputCallback {}); + LuaInputCallback *cb = LuaPull(l, -1); + + cb->connection = std::move(connection); + lua_rawseti(l, -2, luaL_len(l, -2) + 1); + + lua_pop(l, 2); + LUA_DEBUG_END(l, 0); +} + +// Cleanup a callback ConnectionTicket +static bool unstore_callback(lua_State *l, int cb_idx) +{ + LUA_DEBUG_START(l); + + s_connections.PushCopyToStack(); + lua_pushvalue(l, cb_idx); + lua_rawget(l, -2); + + // No connections, don't need to do anything + if (lua_isnil(l, -1)) { + lua_pop(l, 2); + + return false; + } + + // Iterate the list of connections and disconnect them + // This will destroy the C++ lambda and the associated LuaRef + lua_pushnil(l); + while (lua_next(l, -2)) { + LuaInputCallback *cb = LuaPull(l, -1); + cb->connection.m_connection.disconnect(); + lua_pop(l, 1); + } + + lua_pushvalue(l, cb_idx); + lua_pushnil(l); + lua_rawset(l, -3); + + lua_pop(l, 1); + LUA_DEBUG_END(l, 0); + return true; +} + /* * Class: LuaInputAction * @@ -53,7 +277,7 @@ struct LuaInputAction : public LuaWrappable { } /* - * Function: OnPress + * Function: SetPressed * * Activate the corresponding action * @@ -61,9 +285,9 @@ struct LuaInputAction : public LuaWrappable { * * > action = Input.GetActionBinding("BindToggleSpeedLimiter") * > if ui.isMouseClicked(0) then - * > action:OnPress() + * > action:SetPressed() * > elseif ui.isMouseReleased(0) then - * > action:OnRelease() + * > action:SetReleased() * > end * * Returns: @@ -71,7 +295,7 @@ struct LuaInputAction : public LuaWrappable { * nothing * */ - void onPress() + void setPressed() { auto *action = getAction(); action->binding.m_queuedEvents |= 1; @@ -79,7 +303,7 @@ struct LuaInputAction : public LuaWrappable { } /* - * Function: OnRelease + * Function: SetReleased * * Deactivate the corresponding action * @@ -87,9 +311,9 @@ struct LuaInputAction : public LuaWrappable { * * > action = Input.GetActionBinding("BindToggleSpeedLimiter") * > if ui.isMouseClicked(0) then - * > action:OnPress() + * > action:SetPressed() * > elseif ui.isMouseReleased(0) then - * > action:OnRelease() + * > action:SetReleased() * > end * * Returns: @@ -97,7 +321,7 @@ struct LuaInputAction : public LuaWrappable { * nothing * */ - void onRelease() + void setReleased() { auto *action = getAction(); action->binding.m_queuedEvents |= 2; @@ -147,6 +371,42 @@ struct LuaInputAction : public LuaWrappable { } Action *getAction() const { return Pi::input->GetActionBinding(id); } + + /** + * Function: OnPressed() + * + * Register a Lua callback function to be called when this action is + * pressed while in an active InputFrame. + */ + static int AddOnPressedCallback(lua_State *l, LuaInputAction *action) + { + LuaRef cbRef = LuaRef(l, 2); + auto connection = action->getAction()->onPressed.connect([cbRef]() { + cbRef.PushCopyToStack(); + pi_lua_protected_call(cbRef.GetLua(), 0, 0); + }); + + store_callback(l, connection, 2); + return 0; + } + + /** + * Function: OnReleased() + * + * Register a Lua callback function to be called when this action is + * released while in an active InputFrame. + */ + static int AddOnReleasedCallback(lua_State *l, LuaInputAction *action) + { + LuaRef cbRef = LuaRef(l, 2); + auto connection = action->getAction()->onReleased.connect([cbRef]() { + cbRef.PushCopyToStack(); + pi_lua_protected_call(cbRef.GetLua(), 0, 0); + }); + + store_callback(l, connection, 2); + return 0; + } }; /* @@ -238,8 +498,32 @@ struct LuaInputAxis : public LuaWrappable { } Axis *getAxis() const { return Pi::input->GetAxisBinding(id); } + + /** + * Function: OnValueChanged() + * + * Register a Lua callback function to be called when this axis' value is + * changed while part of an active InputFrame. + */ + static int AddValueCallback(lua_State *l, LuaInputAxis *axis) + { + LuaRef cbRef = LuaRef(l, 2); + auto connection = axis->getAxis()->onAxisValue.connect([cbRef](float value) { + cbRef.PushCopyToStack(); + lua_pushnumber(cbRef.GetLua(), value); + pi_lua_protected_call(cbRef.GetLua(), 1, 0); + }); + + store_callback(l, connection, 2); + return 0; + } }; +/** + * Class: LuaJoystickInfo + * + * Represents information about a specific joystick instance connected to the system. + */ struct LuaJoystickInfo : public LuaWrappable { LuaJoystickInfo(int joystickIndex) : m_id(joystickIndex) @@ -293,23 +577,64 @@ struct LuaJoystickInfo : public LuaWrappable { int m_id; }; -#define GENERIC_COPY_OBJ_DEF(Typename) \ - template <> \ - const char *LuaObject::s_type = #Typename; \ - inline void pi_lua_generic_pull(lua_State *l, int index, Typename &out) \ - { \ - assert(l == Lua::manager->GetLuaState()); \ - out = *LuaObject::CheckFromLua(index); \ - } \ - inline void pi_lua_generic_push(lua_State *l, const Typename &value) \ - { \ - assert(l == Lua::manager->GetLuaState()); \ - LuaObject::PushToLua(value); \ +/** + * Class: LuaInputFrame + * + * Collects a group of input bindings and controls pushing them to the active + * input stack. + */ +struct LuaInputFrame : public LuaWrappable { + LuaInputFrame(Input::Manager *manager, bool modal, std::string_view id) : + m_inputFrame(manager, modal, id) + { + } + + ~LuaInputFrame() + { + if (m_inputFrame.manager->HasInputFrame(&m_inputFrame)) { + Log::Warning("LuaInput: InputFrame {} being removed from input stack in __gc callback.", m_inputFrame.id); + m_inputFrame.manager->RemoveInputFrame(&m_inputFrame); + } + } + + const std::string &getId() const { + return m_inputFrame.id; + } + + bool addToStack() { + return m_inputFrame.manager->AddInputFrame(&m_inputFrame); + } + + void removeFromStack() { + return m_inputFrame.manager->RemoveInputFrame(&m_inputFrame); + } + + void addAction(LuaInputAction *action) { + m_inputFrame.AddAction(action->id); + } + + void addAxis(LuaInputAxis *axis) { + m_inputFrame.AddAxis(axis->id); + } + +private: + Input::InputFrame m_inputFrame; +}; + +#define GENERIC_COPY_OBJ_DEF(Typename) \ + template <> \ + const char *LuaObject::s_type = #Typename; \ + inline void pi_lua_generic_push(lua_State *l, const Typename &value) \ + { \ + assert(l == Lua::manager->GetLuaState()); \ + LuaObject::PushToLua(value); \ } +GENERIC_COPY_OBJ_DEF(LuaInputCallback) GENERIC_COPY_OBJ_DEF(LuaInputAction) GENERIC_COPY_OBJ_DEF(LuaInputAxis) GENERIC_COPY_OBJ_DEF(LuaJoystickInfo) +GENERIC_COPY_OBJ_DEF(LuaInputFrame) /* * Interface: Input @@ -588,6 +913,88 @@ static int l_input_set_joystick_enabled(lua_State *l) return 0; } +static int l_input_create_input_frame(lua_State *l) +{ + std::string_view id = LuaPull(l, 1); + bool modal = LuaPull(l, 2, false); + + LuaObject::PushToLua(LuaInputFrame(Pi::input, modal, id)); + return 1; +} + +static int l_input_register_action_binding(lua_State *l) +{ + std::string id = LuaPull(l, 1); + std::string_view groupId = LuaPull(l, 2); + + if (Pi::input->HasActionBinding(id)) { + Log::Info("LuaInput: action binding {} already exists, not overwriting.", id); + LuaPush(l, id); + return 1; + } + + Input::BindingPage *page = nullptr; + Input::BindingGroup *group = nullptr; + + for (std::string_view id : SplitString(groupId, ".")) { + if (page == nullptr) { + page = Pi::input->GetBindingPage(std::string(id)); + } else if (group == nullptr) { + group = page->GetBindingGroup(std::string(id)); + break; + } + } + + auto primary = LuaPull(l, 3, {}); + auto secondary = LuaPull(l, 4, {}); + + Pi::input->AddActionBinding(id, group, InputBindings::Action(primary, secondary)); + + LuaPush(l, id); + return 1; +} + +static int l_input_register_axis_binding(lua_State *l) +{ + std::string id = LuaPull(l, 1); + std::string_view groupId = LuaPull(l, 2); + + if (Pi::input->HasAxisBinding(id)) { + Log::Info("LuaInput: axis binding {} already exists, not overwriting.", id); + LuaPush(l, id); + return 1; + } + + Input::BindingPage *page = nullptr; + Input::BindingGroup *group = nullptr; + + for (std::string_view id : SplitString(groupId, ".")) { + if (page == nullptr) { + page = Pi::input->GetBindingPage(std::string(id)); + } else if (group == nullptr) { + group = page->GetBindingGroup(std::string(id)); + break; + } + } + + auto positive = LuaPull(l, 3, {}); + auto negative = LuaPull(l, 4, {}); + auto axis = LuaPull(l, 5, {}); + + Pi::input->AddAxisBinding(id, group, InputBindings::Axis(axis, positive, negative)); + + LuaPush(l, id); + return 1; +} + +static int l_input_disconnect_callback(lua_State *l) +{ + unstore_callback(l, 2); + return 0; +} + +//============================================================================== + void pi_lua_generic_push(lua_State *l, InputBindings::JoyAxis axis) { if (!axis.Enabled()) { @@ -685,17 +1092,16 @@ void pi_lua_generic_pull(lua_State *l, int index, InputBindings::KeyChord &out) } LuaTable chordTab(l, index); - if (!chordTab.Get("enabled")) - return; - out.activator = chordTab.Get("activator"); out.modifier1 = chordTab.Get("modifier1"); out.modifier2 = chordTab.Get("modifier2"); } +static LuaMetaType s_inputCallback("LuaInputCallback"); static LuaMetaType s_inputActionBinding("LuaInputAction"); static LuaMetaType s_inputAxisBinding("LuaInputAxis"); static LuaMetaType s_joystickInfoBinding("LuaJoystickInfo"); +static LuaMetaType s_inputFrame("LuaInputFrame"); void LuaInput::Register() { lua_State *l = Lua::manager->GetLuaState(); @@ -721,6 +1127,11 @@ void LuaInput::Register() { "GetMouseCaptured", l_input_get_mouse_captured }, + { "CreateInputFrame", l_input_create_input_frame }, + { "RegisterActionBinding", l_input_register_action_binding }, + { "RegisterAxisBinding", l_input_register_axis_binding }, + { "DisconnectCallback", l_input_disconnect_callback }, + { NULL, NULL } }; @@ -730,19 +1141,45 @@ void LuaInput::Register() lua_getfield(l, LUA_REGISTRYINDEX, "CoreImports"); LuaObjectBase::CreateObject(l_methods, l_attrs, 0); - lua_setfield(l, -2, "Input"); + + // Set as CoreImports.Input + lua_pushvalue(l, -1); + lua_setfield(l, -3, "Input"); + + { + LuaTable keys(l); + for (const auto &pair : s_input_keys) { + keys.Set(pair.first, pair.second); + } + + lua_setfield(l, -2, "keys"); + } + + lua_pop(l, 2); + + LUA_DEBUG_CHECK(l, 0); + + // Create the binding connection storage table + lua_newtable(l); + s_connections = LuaRef(l, -1); lua_pop(l, 1); + LUA_DEBUG_CHECK(l, 0); + + s_inputCallback.CreateMetaType(l); + s_inputActionBinding.CreateMetaType(l); s_inputActionBinding.StartRecording() .AddMember("id", &LuaInputAction::id) .AddMember("type", &LuaInputAction::getType) .AddMember("binding", &LuaInputAction::getBinding, &LuaInputAction::setBinding) .AddMember("binding2", &LuaInputAction::getBinding2, &LuaInputAction::setBinding2) - .AddFunction("OnPress", &LuaInputAction::onPress) - .AddFunction("OnRelease", &LuaInputAction::onRelease) + .AddFunction("SetPressed", &LuaInputAction::setPressed) + .AddFunction("SetReleased", &LuaInputAction::setReleased) .AddFunction("IsActive", &LuaInputAction::isActive) - .AddFunction("IsJustActive", &LuaInputAction::isJustActive); + .AddFunction("IsJustActive", &LuaInputAction::isJustActive) + .AddFunction("OnPressed", &LuaInputAction::AddOnPressedCallback) + .AddFunction("OnReleased", &LuaInputAction::AddOnReleasedCallback); s_inputActionBinding.StopRecording(); s_inputAxisBinding.CreateMetaType(l); @@ -753,7 +1190,8 @@ void LuaInput::Register() .AddMember("positive", &LuaInputAxis::getPositive, &LuaInputAxis::setPositive) .AddMember("negative", &LuaInputAxis::getNegative, &LuaInputAxis::setNegative) .AddFunction("SetValue", &LuaInputAxis::setValue) - .AddFunction("GetValue", &LuaInputAxis::getValue); + .AddFunction("GetValue", &LuaInputAxis::getValue) + .AddFunction("OnValueChanged", &LuaInputAxis::AddValueCallback); s_inputAxisBinding.StopRecording(); s_joystickInfoBinding.CreateMetaType(l); @@ -773,5 +1211,45 @@ void LuaInput::Register() .AddFunction("SetAxisZeroToOne", &LuaJoystickInfo::setAxisZeroToOne); s_joystickInfoBinding.StopRecording(); + s_inputFrame.CreateMetaType(l); + s_inputFrame.StartRecording() + .AddMember("id", &LuaInputFrame::getId) + .AddFunction("AddToStack", &LuaInputFrame::addToStack) + .AddFunction("RemoveFromStack", &LuaInputFrame::removeFromStack) + .AddFunction("AddAction", &LuaInputFrame::addAction) + .AddFunction("AddAxis", &LuaInputFrame::addAxis); + s_inputFrame.StopRecording(); + + LUA_DEBUG_END(l, 0); +} + +void LuaInput::Uninit() +{ + lua_State *l = s_connections.GetLua(); + + LUA_DEBUG_START(l); + + s_connections.PushCopyToStack(); + + // Clear all connection tickets before Lua is shut down + lua_pushnil(l); + while (lua_next(l, -2)) { + if (lua_isfunction(l, -2)) { + + lua_pushnil(l); + while (lua_next(l, -2)) { + auto *cb = LuaPull(l, -1); + cb->connection.m_connection.disconnect(); + lua_pop(l, 1); + } + + } + + lua_pop(l, 1); + } + + lua_pop(l, 1); + s_connections.Unref(); + LUA_DEBUG_END(l, 0); } diff --git a/src/lua/LuaInput.h b/src/lua/LuaInput.h index ea90d0b1f7..bba18b8f90 100644 --- a/src/lua/LuaInput.h +++ b/src/lua/LuaInput.h @@ -13,6 +13,8 @@ namespace InputBindings { namespace LuaInput { void Register(); + + void Uninit(); } void pi_lua_generic_pull(lua_State *l, int index, InputBindings::KeyChord &out); diff --git a/src/lua/LuaPiGui.cpp b/src/lua/LuaPiGui.cpp index 8e48233ba4..0fe3f2ab98 100644 --- a/src/lua/LuaPiGui.cpp +++ b/src/lua/LuaPiGui.cpp @@ -2588,12 +2588,6 @@ static int l_attr_handlers(lua_State *l) return 1; } -static int l_attr_keys(lua_State *l) -{ - PiGui::GetKeys().PushCopyToStack(); - return 1; -} - static int l_attr_event_queue(lua_State *l) { PiGui::GetEventQueue().PushCopyToStack(); @@ -3740,7 +3734,6 @@ void LuaObject::RegisterClass() { "key_none", l_attr_key_none }, { "key_shift", l_attr_key_shift }, { "key_alt", l_attr_key_alt }, - { "keys", l_attr_keys }, { "event_queue", l_attr_event_queue }, { 0, 0 } }; diff --git a/src/lua/LuaUtils.h b/src/lua/LuaUtils.h index 2b1f733763..cd05027766 100644 --- a/src/lua/LuaUtils.h +++ b/src/lua/LuaUtils.h @@ -88,6 +88,8 @@ void pi_lua_warn(lua_State *l, const char *format, ...) __attribute((format(prin bool pi_lua_split_table_path(lua_State *l, const std::string &path); +std::string pi_lua_get_caller_module(lua_State *l, int depth = 1); + int secure_trampoline(lua_State *l); std::string pi_lua_traceback(lua_State *l, int top); diff --git a/src/lua/core/Import.cpp b/src/lua/core/Import.cpp index 05d909d8ca..71b1223cca 100644 --- a/src/lua/core/Import.cpp +++ b/src/lua/core/Import.cpp @@ -446,7 +446,7 @@ static std::string make_module_name(lua_State *L, int idx) return name; } -static std::string get_caller_module_name(lua_State *L, int depth = 1) +std::string pi_lua_get_caller_module(lua_State *L, int depth) { std::string caller = get_caller(L, depth); std::string_view sv(caller); @@ -458,7 +458,7 @@ static std::string get_caller_module_name(lua_State *L, int depth = 1) static int l_reimport_package(lua_State *L) { - std::string name = lua_gettop(L) ? make_module_name(L, 1) : get_caller_module_name(L); + std::string name = lua_gettop(L) ? make_module_name(L, 1) : pi_lua_get_caller_module(L); // Get the module name -> file path mapping std::string path = get_path_cache(L, name); @@ -483,7 +483,7 @@ static int l_reimport_package(lua_State *L) static int l_get_module_name(lua_State *L) { int depth = luaL_optinteger(L, 1, 1); - lua_pushstring(L, get_caller_module_name(L, depth > 1 ? depth : 1).c_str()); + lua_pushstring(L, pi_lua_get_caller_module(L, depth > 1 ? depth : 1).c_str()); return 1; } diff --git a/src/pigui/LuaPiGui.cpp b/src/pigui/LuaPiGui.cpp index b682396541..9d595f53dc 100644 --- a/src/pigui/LuaPiGui.cpp +++ b/src/pigui/LuaPiGui.cpp @@ -8,33 +8,11 @@ #include "Radar.h" #include "lua/LuaPiGuiInternal.h" #include "lua/LuaTable.h" -#include "utils.h" - -static std::vector> m_keycodes = { - { "left", SDLK_LEFT }, - { "right", SDLK_RIGHT }, - { "up", SDLK_UP }, - { "down", SDLK_DOWN }, - { "escape", SDLK_ESCAPE }, - { "delete", SDLK_DELETE }, - { "f1", SDLK_F1 }, - { "f2", SDLK_F2 }, - { "f3", SDLK_F3 }, - { "f4", SDLK_F4 }, - { "f5", SDLK_F5 }, - { "f6", SDLK_F6 }, - { "f7", SDLK_F7 }, - { "f8", SDLK_F8 }, - { "f9", SDLK_F9 }, - { "f10", SDLK_F10 }, - { "f11", SDLK_F11 }, - { "f12", SDLK_F12 }, - { "tab", SDLK_TAB }, -}; + +#include static LuaRef m_handlers; static LuaRef m_themes; -static LuaRef m_keys; static LuaRef m_eventQueue; namespace PiGui { @@ -56,15 +34,6 @@ namespace PiGui { m_themes = LuaRef(l, -1); lua_pop(l, 1); - lua_newtable(l); - m_keys = LuaRef(l, -1); - LuaTable keys(l, -1); - for (auto p : m_keycodes) { - keys.Set(p.first, p.second); - } - - lua_pop(l, 1); - pi_lua_import(l, "Event"); // Create a new event table and store it for UI use @@ -84,14 +53,12 @@ namespace PiGui { { m_handlers.Unref(); m_themes.Unref(); - m_keys.Unref(); m_eventQueue.Unref(); } } // namespace Lua LuaRef GetHandlers() { return m_handlers; } - LuaRef GetKeys() { return m_keys; } LuaRef GetThemes() { return m_themes; } LuaRef GetEventQueue() { return m_eventQueue; } diff --git a/src/pigui/LuaPiGui.h b/src/pigui/LuaPiGui.h index a974282a9b..5d6eaa3f94 100644 --- a/src/pigui/LuaPiGui.h +++ b/src/pigui/LuaPiGui.h @@ -4,7 +4,9 @@ #ifndef PIGUI_LUA_H #define PIGUI_LUA_H -#include "lua/LuaObject.h" +#include "lua/LuaRef.h" + +#include struct ImGuiStyle; @@ -16,9 +18,6 @@ namespace PiGui { // Get registered PiGui themes. LuaRef GetThemes(); - // Get a table of key name to SDL-keycode mappings - LuaRef GetKeys(); - // Get the EventQueue to be used for UI events LuaRef GetEventQueue(); diff --git a/src/pigui/PiGuiSandbox.cpp b/src/pigui/PiGuiSandbox.cpp index 056fa24e6a..af89044aa3 100644 --- a/src/pigui/PiGuiSandbox.cpp +++ b/src/pigui/PiGuiSandbox.cpp @@ -2,15 +2,13 @@ #include #include "LuaPiGui.h" -#include "PiGui.h" +#include "lua/Lua.h" #include "imgui/imgui.h" #include "lua/LuaUtils.h" #include "imgui/imgui_internal.h" -#include - const char *s_meta_name = "PiGui.SavedImguiStackInfo"; // Cleanly recover from all unterminated calls in the current window.