├── .gitignore ├── res ├── axes.png ├── logo.png ├── mouse.png └── xbox_360_controller.png ├── .gitmodules ├── run.sh ├── LICENSE ├── main.lua ├── README.md ├── MANUAL.md └── Lynput.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *~* 2 | *#* 3 | *.love -------------------------------------------------------------------------------- /res/axes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lydzje/lynput/HEAD/res/axes.png -------------------------------------------------------------------------------- /res/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lydzje/lynput/HEAD/res/logo.png -------------------------------------------------------------------------------- /res/mouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lydzje/lynput/HEAD/res/mouse.png -------------------------------------------------------------------------------- /res/xbox_360_controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lydzje/lynput/HEAD/res/xbox_360_controller.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/classic"] 2 | path = lib/classic 3 | url = git@github.com:Lydzje/classic.git 4 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./build.sh 3 | 4 | printf "\n[RUNNING LOVE PROJECT...]\n\n" 5 | love lynput.love 6 | printf "\n[LOVE PROJECT TERMINATED]\n\n" 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Lydzje 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | function love.load() 2 | Lynput = require("Lynput") 3 | Lynput.load_key_callbacks() 4 | Lynput.load_mouse_callbacks() 5 | Lynput.load_gamepad_callbacks() 6 | lynput = Lynput() 7 | 8 | lynput:bind("exit", "release escape") 9 | 10 | lynput:attachGamepad("GPAD_1") 11 | 12 | lynput:bind("pressing", {"press p", "press LMB", "press G_A"}) 13 | lynput:bind("releasing", {"release r", "release RMB", "release G_B"}) 14 | lynput:bind("holding", {"hold h", "hold MMB", "hold G_X"}) 15 | 16 | lynput:unbindAll("holding") 17 | 18 | lynput:bind("moveLeft", {"-100:-50 G_LEFTSTICK_X"}) 19 | lynput:bind("moveRight", {"50:100 G_LEFTSTICK_X"}) 20 | 21 | lynput:unbind("moveRight", "50:100 G_LEFTSTICK_X") 22 | 23 | lynput:bind("RTing", "0:100 G_RT") 24 | 25 | lynput:bind("pressAny", "press any") 26 | lynput:bind("releaseAny", "release any") 27 | lynput:bind("holdAny", "hold any") 28 | end 29 | 30 | 31 | function love.update(dt) 32 | if lynput.exit then 33 | love.event.quit() 34 | end -- if exit 35 | 36 | -- if lynput.pressAny then 37 | -- print("Pressed ANY") 38 | -- end -- 39 | 40 | -- if lynput.releaseAny then 41 | -- print("Released ANY") 42 | -- end -- 43 | 44 | -- if lynput.holdAny then 45 | -- print("Holding ANY") 46 | -- end 47 | 48 | print(lynput:getAxis("righty")) 49 | 50 | if lynput.pressing then 51 | print("Pressing") 52 | end -- if pressing 53 | 54 | if lynput.releasing then 55 | print("Releasing") 56 | lynput:unbind("releasing", {"release r", "release RMB", "release G_B"}) 57 | end -- if releasing 58 | 59 | if lynput.holding then 60 | print("Holding") 61 | end -- if holding 62 | 63 | if lynput.moveLeft then 64 | print("moving left") 65 | end -- if moveLeft 66 | 67 | if lynput.moveRight then 68 | print("Moving right") 69 | end -- if moveRight 70 | 71 | if lynput.RTing then 72 | print("RTing") 73 | end -- if RTing 74 | 75 | 76 | Lynput.update_(dt) 77 | end 78 | 79 | 80 | function love.draw() 81 | love.graphics.setColor(255, 255, 255) 82 | end 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lynput 2 | 3 | [![LÖVE VERSION](https://img.shields.io/badge/L%C3%96VE-0.10.0%2B-%23E0539A.svg)](https://love2d.org/wiki/Category:Versions) 4 | [![MIT LICENSE](https://img.shields.io/badge/license-MIT-%233DCE7A.svg)](LICENSE) 5 | 6 | ![lynput logo](res/logo.png) 7 | 8 | ## Index 9 | - [What is Lynput?](#what-is-lynput) 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [Devices supported](#devices-supported) 13 | - [Features](#features) 14 | - [What does Lynput mean?](#what-does-lynput-mean) 15 | - [I've found a bug, what do I do?](#ive-found-a-bug-what-do-i-do) 16 | - [Contact](#contact) 17 | - [License](#license) 18 | 19 | ## What is Lynput? 20 | **Lynput** is an input library for [LÖVE](https://love2d.org/) that makes input handling very easy and intuitive 💙. It will make you able to do things like this: 21 | 22 | ```lua 23 | function love.load() 24 | Lynput = require("Lynput") -- Load Lynput 25 | Lynput.load_key_callbacks() -- Load keyboard callbacks 26 | control = Lynput() -- Create a Lynput object 27 | 28 | control:bind( 29 | "moveLeft", 30 | { 31 | "hold left", 32 | "hold a", 33 | } 34 | ) 35 | control:bind( 36 | "moveRight", 37 | { 38 | "hold right", 39 | "hold d", 40 | } 41 | ) 42 | control:bind("action", "press f") 43 | control:bind("obey", "release any") 44 | end 45 | 46 | function love.update(dt) 47 | if control.moveLeft then x = x - speed * dt end 48 | if control.moveRight then x = x + speed * dt end 49 | if control.action then triggerAction() end 50 | if control.obey then obey() end 51 | 52 | Lynput.update_(dt) -- Update Lynput 53 | end 54 | ``` 55 | 56 | ## Installation 57 | Just download the [latest release version of Lynput.lua](https://github.com/Lydzje/lynput/releases/latest) and extract the contents from the zip file. Place the Lynput.lua file anywhere you want inside your game folder, just be careful with the path when requiring the library. Also remember that this file name starts with a capital letter. 58 | 59 | ## Usage 60 | See [MANUAL](MANUAL.md) for more information. 61 | 62 | ## Devices supported 63 | - [x] Keyboard 64 | - [x] Mouse buttons 65 | - [x] Gamepad buttons 66 | - [x] Gamepad analog input 67 | - [ ] Touch screen 68 | - [ ] ... 69 | 70 | ## Features 71 | - [x] Multiple independent input objects 72 | - [x] Easy and intuitive input binding and unbindig 73 | - [ ] Saving and loading input configuration files 74 | - [ ] Things like this: lynput:bind("superPunch", "press G_RB+G_X") 75 | - [ ] ... 76 | 77 | ## What does Lynput mean? 78 | ```lua 79 | if not creativity then 80 | name = Lydzje + input 81 | print(name) 82 | end -- if not creativity 83 | ``` 84 | > **Output:** 85 | > 86 | > Lynput 87 | 88 | ## I've found a bug, what do I do? 89 | If you want to report a bug (please do!), [open a new issue](https://github.com/Lydzje/lynput/issues). As another option just [contact me](#contact). 90 | 91 | ## Contact 92 | If you need to contact me don't hesitate to [send me an email](mailto:to.lydzje@gmail.com). If you preffer other way, please visit the contact section in my website [lydzje.com](https://lydzje.com). 93 | 94 | ## License 95 | This software is licensed under the MIT license. Check the details by clicking [here](LICENSE). 96 | -------------------------------------------------------------------------------- /MANUAL.md: -------------------------------------------------------------------------------- 1 | # Lynput Manual 2 | 3 | [![LÖVE VERSION](https://img.shields.io/badge/L%C3%96VE-0.10.0%2B-%23E0539A.svg)](https://love2d.org/wiki/Category:Versions) 4 | [![MIT LICENSE](https://img.shields.io/badge/license-MIT-%233DCE7A.svg)](LICENSE) 5 | 6 | ![lynput logo](res/logo.png) 7 | 8 | ## Index 9 | - [Requirements](#requirements) 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [Basics](#basics) 13 | - [Lynput callbacks](#lynput-callbacks) 14 | - [Input states](#input-states) 15 | - [Button states](#buttons-states) 16 | - [Axis states](#axis-states) 17 | - [Keyboard](#keyboard) 18 | - [Mouse](#mouse) 19 | - [Gamepad](#gamepad) 20 | - [Configuring multiple inputs at once](#configuring-multiple-inputs-at-once) 21 | - [The "any" input](#the-any-input) 22 | - [I've bind my controls, what's next?](#ive-bind-my-controls-whats-next) 23 | - [Names that can't be used as an action](#names-that-cant-be-used-as-an-action) 24 | - [All available functions](#all-available-functions) 25 | - [I've found a bug, what do I do?](#ive-found-a-bug-what-do-i-do) 26 | - [Contact](#contact) 27 | - [License](#license) 28 | 29 | ## Requirements 30 | Lynput requires LÖVE version 0.10.0 or later. 31 | 32 | ## Installation 33 | Just download the [latest release version of Lynput.lua](https://github.com/Lydzje/lynput/releases/latest) and extract the contents from the zip file. Place the Lynput.lua file anywhere you want inside your game folder, just be careful with the path when requiring the library. Also remember that this file name starts with a capital letter. 34 | 35 | ## Usage 36 | ### Basics 37 | First you need to load the library: 38 | ```lua 39 | Lynput = require("path.to.Lynput") -- Notice that the file starts with a capital letter 40 | ``` 41 | Then, you need to create a Lynput object: 42 | ```lua 43 | control = Lynput() 44 | ``` 45 | Lynput objects need to be updated in order to work: 46 | ```lua 47 | -- put this after all your game logic happens, for example at the bottom of love.update(dt) 48 | Lynput.update_(dt) -- Notice the underscore 49 | ``` 50 | Once you don't need the object anymore, destroy it with: 51 | ```lua 52 | control:remove() 53 | ``` 54 | 55 | ### Lynput callbacks 56 | To make Lynput able to check your computer input, it's necessary to set its callbacks. You can set them by yourself, or you can let Lynput do this job. Do it yourself if you need to override the LÖVE callbacks with more stuff aside from Lynput callbacks. 57 | 58 | Only set those that will be used for better performance. 59 | 60 | #### Keyboard callbacks 61 | To make Lynput load the keyboard callbacks: 62 | ```lua 63 | Lynput.load_key_callbacks() 64 | ``` 65 | To load them by yourself, override this LÖVE functions as indicated: 66 | ```lua 67 | function love.keypressed(key) 68 | -- your stuff 69 | Lynput.onkeypressed(key) 70 | -- your stuff 71 | end 72 | 73 | function love.keyreleased(key) 74 | -- your stuff 75 | Lynput.onkeyreleased(key) 76 | -- your stuff 77 | end 78 | ``` 79 | 80 | #### Mouse callbacks 81 | To make Lynput load the mouse callbacks: 82 | ```lua 83 | Lynput.load_mouse_callbacks() 84 | ``` 85 | To load them by yourself, override this LÖVE functions as indicated: 86 | ```lua 87 | function love.mousepressed(x, y, button, istouch) 88 | -- your stuff 89 | Lynput.onmousepressed(button) 90 | -- your stuff 91 | end 92 | 93 | function love.mousereleased(x, y, button, istouch) 94 | -- your stuff 95 | Lynput.onmousereleased(button) 96 | -- your stuff 97 | end 98 | ``` 99 | 100 | #### Gamepad callbacks 101 | To make Lynput load the gamepad callbacks: 102 | ```lua 103 | Lynput.load_gamepad_callbacks() 104 | ``` 105 | To load them by yourself, override this LÖVE functions as indicated: 106 | ```lua 107 | function love.gamepadpressed(joystick, button) 108 | -- your stuff 109 | Lynput.ongamepadpressed(joystick:getID(), button) 110 | -- your stuff 111 | end 112 | 113 | function love.gamepadreleased(joystick, button) 114 | -- your stuff 115 | Lynput.ongamepadreleased(joystick:getID(), button) 116 | -- your stuff 117 | end 118 | 119 | function love.joystickadded(joystick) 120 | -- your stuff 121 | Lynput.ongamepadadded(joystick) 122 | -- your stuff 123 | end 124 | ``` 125 | 126 | ### Input states 127 | For now, there are two kinds of states for inputs: **button states** and **axis states**. 128 | 129 | #### Button states 130 | Button states indicates the state of a button :sweat_smile:, and those can be: press, release or hold. 131 | 132 | So if you want the player to move left when holding left arrow, you'd do: 133 | ```lua 134 | playerControl:bind("moveLeft", "hold left") 135 | ``` 136 | 137 | #### Axis states 138 | Axis states indicates the state of an axis :unamused:, and since its state is a number and it depends on how much you move your sticks or triggers, going from -1 to +1 in LÖVE (0 to +1 for triggers like G_LT or G_RT), there are infinite states :fearful:. 139 | 140 | But don't worry, we are not going to specify an specific state, but an interval, and we are going to multiply it by 100 because it's easier to read. 141 | 142 | ![axes](res/axes.png) 143 | 144 | So, if you want the player to move left when moving the left stick of a gamepad along the x axis, you'd do: 145 | ```lua 146 | playerControl:bind("moveLeft", "-100:0 G_LEFTSTICK_X") -- It won't be -100:0, but -100:-30 because Lynput has a default dead zone of 30 147 | ``` 148 | To make the player move right: 149 | ```lua 150 | playerControl:bind("moveRight", "0:100 G_LEFTSTICK_X") -- It won't be 0:100, but -100:-30 because Lynput has a default dead zone of 30 151 | ``` 152 | 153 | ### Keyboard 154 | To specify a key you just need to know [how LÖVE names that key](https://love2d.org/wiki/KeyConstant). Then if you want the player to jump by pressing space, you'd do: 155 | ```lua 156 | playerControl:bind("jump", "press space") 157 | ``` 158 | 159 | ### Mouse 160 | To specify a mouse button you just need to know how Lynput names them (see image below), since LÖVE uses numbers and Lynput can't because they are for keyboard keys. 161 | 162 | ![mouse](res/mouse.png) 163 | 164 | Then if you want the player to shoot by pressing left click, you'd do: 165 | ```lua 166 | playerControl:bind("shoot", "press LMB") 167 | ``` 168 | 169 | ### Gamepad 170 | In order to use gamepads, we have to bind a Lynput object with a gamepad. You can do this by: 171 | 172 | ```lua 173 | playerControl:attachGamepad("GPAD_1") -- This will be the gamepad number 1, for the second one use 2, third 3, and so on 174 | ``` 175 | 176 | Once it's done, you can start to configure your controls. 177 | 178 | To specify a gamepad button or axis you just need to know how Lynput names them (see image below), since LÖVE uses names that Lynput can't because they are for keyboard keys. 179 | 180 | ![xbox 360 controller](res/xbox_360_controller.png) 181 | 182 | Then if you want the player to jump by pressing A and move up with the left stick Y axis, you'd do: 183 | ```lua 184 | playerControl:bind("jump", "press G_A") 185 | playerControl:bind("moveUp", "-100:0 G_LEFTSTICK_Y") 186 | ``` 187 | 188 | But if G_LEFTSTICK_Y has a value of -10, moveUp will be false, why? Because Lynput sets a default dead zone of 30 for gamepads when creating a Lynput object. You can change the dead zone doing this: 189 | ```lua 190 | playerControl.gpadDeadZone = 0 191 | ``` 192 | 193 | ### Configuring multiple inputs at once 194 | If you want the player to move down using down arrow, key s, dpad down and left stick Y axis, you don't need to make four bindings. You can just do: 195 | ```lua 196 | playerControl:bind( 197 | "moveDown", 198 | { 199 | "hold down", 200 | "hold s", 201 | "hold G_DPAD_DOWN", 202 | "0:100 G_LEFTSTICK_Y" 203 | } 204 | ) 205 | ``` 206 | 207 | ### The "any" input 208 | Imagine you are making a game that starts with a intro with some logos, clips and other stuff, and then you want a screen title saying "Press any button to start". How would you do that with Lynput? It's really easy, you just need to bind all keys to an action called any :alien:. I'm just joking lol, for that kind of purposes there is an input called any. To make that screen title control you'd do: 209 | 210 | ```lua 211 | control:bind("start", "press any") 212 | ``` 213 | 214 | ### I've bind my controls, what's next? 215 | Now, you need to check if your controls have been triggered. You can do this in your update functions as follows: 216 | 217 | ```lua 218 | if playerControl.moveLeft then moveLeft() end 219 | if playerControl.moveRight then moveRight() end 220 | if playerControl.moveUp then moveUp() end 221 | if playerControl.moveDown then moveDown() end 222 | if playerControl.jump then jump() end 223 | if playerControl.shoot then shoot() end 224 | if control.start then goToMainMenu() end 225 | ``` 226 | 227 | ### Names that can't be used as an action 228 | What's an action? Well, for Lynput an action is the name you give to the thing you want the player to do. If you want your player to pause the game by pressing p you'd do: 229 | 230 | ```lua 231 | playerControls:bind("pause", "press p") 232 | ``` 233 | 234 | In the case above, pause, the first argument, is the action. There is no problem if you call your action "pause", but you cannot use [words or characters reserved by Lua](https://www.lua.org/manual/5.1/manual.html#2.1) or Lynput (see table below). 235 | 236 | | Reserved by Lynput | 237 | |:--------------------------: | 238 | | inputsSet | 239 | | gpad | 240 | | gpadDeadZone | 241 | | id | 242 | | remove | 243 | | attachGamepad | 244 | | bind | 245 | | unbind | 246 | | unbindAll | 247 | | removeAction | 248 | | update | 249 | 250 | ## All available functions 251 | | Function | Description | Example | 252 | |---------------------------------------------- |-------------------------------------------------------------------------------- |----------------------------------------------------- | 253 | | Lynput.load_key_callbacks() | Sets all keyboard callbacks | Lynput.load_key_callbacks() | 254 | | Lynput.load_mouse_callbacks() | Sets all mouse callbacks | Lynput.load_mouse_callbacks() | 255 | | Lynput.load_gamepad_callbacks() | Sets all gamepad callbacks | Lynput.load_gamepad_callbacks() | 256 | | Lynput.update_(dt) | Update all Lynput objects | Lynput.update_(dt) | 257 | | Lynput:remove() | Remove the calling Lynput object | controls:remove() | 258 | | Lynput:attachGamepad(gamepad) | Attachs a gamepad to the calling Lynput object | controls:attachGamepad("GPAD_1") | 259 | | Lynput:getAxis(axis) | Returns the value of the specified axis. LÖVE and Lynput axes names can be both used. The value is the one LÖVE returns but multiplied by 100. | controls:getAxis("G_LEFTSTICK_X") | 260 | | Lynput:bind(action, commands) | Binds commands to an action for the calling Lynput object | controls:bind("jump", "press space") | 261 | | Lynput:unbind(action, commands) | Unbinds commands from an action for the calling Lynput object | controls:unbind("jump", "press space") | 262 | | Lynput:unbindAll(action) | Unbinds all commands from an action for the calling Lynput object | controls:unbindAll("jump") | 263 | | Lynput:removeAction(action) | Removes an action of the calling Lynput object | controls:removeAction("jump") | 264 | 265 | ## I've found a bug, what do I do? 266 | If you want to report a bug (please do!), [open a new issue](https://github.com/Lydzje/lynput/issues). As another option just [contact me](#contact). 267 | 268 | ## Contact 269 | If you need to contact me don't hesitate to [send me an email](mailto:to.lydzje@gmail.com). If you preffer other way, please visit the contact section in my website [lydzje.com](https://lydzje.com). 270 | 271 | ## License 272 | This software is licensed under the MIT license. Check the details by clicking [here](LICENSE). 273 | -------------------------------------------------------------------------------- /Lynput.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Lynput 3 | -- 4 | -- Copyright (c) 2019, Lydzje 5 | -- 6 | -- This module is free software; you can redistribute it and/or modify it under 7 | -- the terms of the MIT license. See LICENSE for details. 8 | -- 9 | 10 | local Lynput = {} 11 | Lynput.__index = Lynput 12 | 13 | setmetatable( 14 | Lynput, 15 | { 16 | __call = function (cls, ...) 17 | return cls.new(...) 18 | end, 19 | } 20 | ) 21 | 22 | Lynput.s_lynputs = {} 23 | Lynput.s_idCount = 0 24 | Lynput.s_count = 0 25 | 26 | Lynput.s_reservedNames = { 27 | -- Reserved by Lua 28 | "and", "break", "do", "else", "elseif", "end", "false", "for", 29 | "function", "if", "in", "local", "nil", "not", "or", "repeat", 30 | "return", "then", "true", "until", "while", 31 | -- Reserved by Lynput 32 | "inputsSet", "gpad", "gpadDeadZone", "id", "remove", "attachGamepad", 33 | "bind", "unbind", "unbindAll", "removeAction", "update" 34 | } 35 | 36 | Lynput.s_reservedCharacters = { 37 | "+", "-", "*", "/", "%", "^", "#", "==", "~=", "<=", ">=", "<", ">", 38 | "=", "(", ")", "{", "}", "[", "]", ";", ":", ",", ".", "..", "..." 39 | } 40 | 41 | ---------------- 42 | -- DICTIONARIES 43 | ---------------- 44 | Lynput.s_mouseButtons = { 45 | ["1"]="LMB", ["2"]="RMB", ["3"]="MMB", ["x1"]="MB4", ["x2"]="MB5" 46 | } 47 | 48 | Lynput.s_gamepadButtons = { 49 | ["a"]="G_A", ["b"]="G_B", ["x"]="G_X", ["y"]="G_Y", 50 | ["back"]="G_BACK", ["guide"]="G_GUIDE", ["start"]="G_START", 51 | ["leftstick"]="G_LEFTSTICK", ["rightstick"]="G_RIGHTSTICK", 52 | ["leftshoulder"]="G_LB", ["rightshoulder"]="G_RB", 53 | ["dpup"]="G_DPAD_UP", ["dpdown"]="G_DPAD_DOWN", 54 | ["dpleft"]="G_DPAD_LEFT", ["dpright"]="G_DPAD_RIGHT" 55 | } 56 | 57 | Lynput.s_gamepadAxes = { 58 | ["leftx"]="G_LEFTSTICK_X", ["lefty"]="G_LEFTSTICK_Y", 59 | ["rightx"]="G_RIGHTSTICK_X", ["righty"]="G_RIGHTSTICK_Y", 60 | ["triggerleft"]="G_LT", ["triggerright"]="G_RT" 61 | } 62 | 63 | ----------------------- 64 | -- FUTURE DICTIONARIES 65 | ----------------------- 66 | -- Lynput.s_mouse_axes = { 67 | -- "wd", "wu" 68 | -- } 69 | 70 | 71 | function Lynput.new() 72 | local self = setmetatable({}, Lynput) 73 | -- Maps Lynput inputs and states to actions, inputsSet[state][action] 74 | self.inputsSet = {} 75 | 76 | self.gpad = nil 77 | -- TODO: Different deadzones for joysticks and triggers 78 | self.gpadDeadZone = 30 79 | 80 | self.id = tostring(Lynput.s_idCount) 81 | Lynput.s_lynputs[self.id] = self 82 | Lynput.s_idCount = Lynput.s_idCount + 1 83 | Lynput.s_count = Lynput.s_count + 1 84 | 85 | return self 86 | end 87 | 88 | 89 | function Lynput.load_key_callbacks() 90 | function love.keypressed(key) Lynput.onkeypressed(key) end 91 | function love.keyreleased(key) Lynput.onkeyreleased(key) end 92 | end 93 | 94 | 95 | function Lynput.load_mouse_callbacks() 96 | function love.mousepressed(x, y, button, istouch) Lynput.onmousepressed(button) end 97 | function love.mousereleased(x, y, button, istouch) Lynput.onmousereleased(button) end 98 | end 99 | 100 | 101 | function Lynput.load_gamepad_callbacks() 102 | function love.gamepadpressed(joystick, button) Lynput.ongamepadpressed(joystick:getID(), button) end 103 | function love.gamepadreleased(joystick, button) Lynput.ongamepadreleased(joystick:getID(), button) end 104 | function love.joystickadded(joystick) Lynput.ongamepadadded(joystick) end 105 | end 106 | 107 | 108 | function Lynput.update_(dt) 109 | for _, lynput in pairs(Lynput.s_lynputs) do 110 | lynput:update(dt) 111 | end -- for each lynput 112 | end 113 | 114 | 115 | local function _isActionValid(action) 116 | if type(action) ~= "string" then 117 | return false 118 | end -- if not string 119 | 120 | for _, reservedName in ipairs(Lynput.s_reservedNames) do 121 | if reservedName == action then 122 | return false 123 | end -- if action name is reserved 124 | end -- for each reserved name 125 | 126 | for _, reservedChar in ipairs(Lynput.s_reservedCharacters) do 127 | if string.find(action, "%" .. reservedChar) then 128 | return false 129 | end -- if action name contains reserved characters 130 | end -- for each reserved character 131 | 132 | return true 133 | end 134 | 135 | 136 | local function _isCommandValid(command) 137 | local stateValid, inputValid = false, false 138 | local state, input = string.match(command, "(.+)%s(.+)") 139 | 140 | if not state or not input then 141 | goto exit 142 | end -- if state or input are nil 143 | 144 | -- Process state 145 | stateValid = 146 | state == "release" or 147 | state == "press" or 148 | state == "hold" 149 | 150 | if not stateValid then 151 | local min, max = string.match(state, "(.+)%:(.+)") 152 | 153 | min = tonumber(min) 154 | max = tonumber(max) 155 | 156 | if not min or not max then 157 | goto exit 158 | end -- if min or max are nil 159 | 160 | stateValid = 161 | min < max and 162 | min >= -100 and 163 | max <= 100 164 | end -- if state is not meant for buttons 165 | 166 | -- Process input 167 | if input == "any" then 168 | inputValid = true 169 | goto exit 170 | end -- if input == any 171 | 172 | for _, button in pairs(Lynput.s_mouseButtons) do 173 | if button == input then 174 | inputValid = true 175 | goto exit 176 | end -- if button == input 177 | end -- for each Lynput mouse button 178 | 179 | for _, button in pairs(Lynput.s_gamepadButtons) do 180 | if button == input then 181 | inputValid = true 182 | goto exit 183 | end -- if button == input 184 | end -- for each Lynput gamepad button 185 | 186 | for _, axis in pairs(Lynput.s_gamepadAxes) do 187 | if axis == input then 188 | inputValid = true 189 | goto exit 190 | end -- if axis == input 191 | end -- for each Lynput gamepad axis 192 | 193 | -- TODO: Touch screen 194 | 195 | inputValid = pcall(love.keyboard.getScancodeFromKey, input) 196 | 197 | ::exit:: 198 | 199 | return stateValid and inputValid, state, input 200 | end 201 | 202 | 203 | function Lynput:remove() 204 | Lynput.s_lynputs[self.id] = nil 205 | Lynput.s_count = Lynput.s_count - 1 206 | end 207 | 208 | 209 | -- @param gamepad is a Lynput gamepad string name (e.g., "GPAD_1", "GPAD_2", ..., "GPAD_N") 210 | function Lynput:attachGamepad(gamepad) 211 | -- TODO: More code, this needs to check if the parameter given is like expected 212 | 213 | self.gpad = gamepad 214 | end 215 | 216 | 217 | function Lynput:getAxis(axis) 218 | for loveAxis, lynputAxis in pairs(Lynput.s_gamepadAxes) do 219 | if axis == loveAxis or axis == lynputAxis then 220 | return Lynput[self.gpad]:getGamepadAxis(loveAxis) * 100 221 | end 222 | end 223 | 224 | error( 225 | "Axis->" .. axis .. " is not a valid name. Use LÖVE or Lynput names for axes." 226 | ) 227 | end 228 | 229 | 230 | function Lynput:bind(action, commands) 231 | -- Type checking for argument #1 232 | assert( 233 | type(action) == "string", 234 | "bad argument #1 to 'Lynput:bind' (string expected, got " .. type(action) .. ")" .. 235 | "\nCheck the stack traceback to know where the invalid arguments have been passed" 236 | ) 237 | 238 | -- Transforms 1 command to a table 239 | if type(commands) ~= "table" then 240 | local command = commands 241 | commands = {} 242 | commands[1] = command 243 | end -- if only one command was given 244 | 245 | -- Type checking for argument #2 246 | for i, command in ipairs(commands) do 247 | assert( 248 | type(command) == "string", 249 | "bad argument #2 to 'Lynput:bind' (string or table of strings expected, got " .. 250 | type(command) .. " in element #" .. i .. ")" .. 251 | "\nCheck the stack traceback to know where the invalid arguments have been passed" 252 | ) 253 | end -- for each element in commands table 254 | 255 | -- Process command 256 | for _, command in ipairs(commands) do 257 | -- Is action valid? 258 | assert( 259 | _isActionValid(action), 260 | "Could not bind command->" .. command .. 261 | -- TODO: Check documentation for valid actions message 262 | " to action->" .. action .. ", the action is not valid." 263 | ) 264 | -- Is command valid? 265 | local commandValid, state, input = _isCommandValid(command) 266 | -- TODO: Use a "check the manual for commands format" instead of explaining it here 267 | assert( 268 | commandValid, 269 | "Could not bind command->" .. command .. 270 | " to action->" .. action .. ", the command is not valid." .. 271 | "\n\nCommands should be formated as follows: \n\n" .. 272 | " COMMAND = STATE INPUT (with a space in between)\n" .. 273 | " STATE =\n" .. 274 | " for buttons -> press, release or hold\n" .. 275 | " for analog inputs -> x:y (with a colon in between) where x and y are numbers\n" .. 276 | " INPUT = check the manual for all availible inputs\n\n" 277 | ) 278 | 279 | if self[action] == nil then 280 | self[action] = false 281 | end -- if action not set 282 | 283 | if not self.inputsSet[input] then 284 | self.inputsSet[input] = {} 285 | end -- if input hasn't already been set 286 | 287 | self.inputsSet[input][state] = action 288 | end -- for each command 289 | end 290 | 291 | 292 | function Lynput:unbind(action, commands) 293 | -- Type checking for argument #1 294 | assert( 295 | type(action) == "string", 296 | "bad argument #1 to 'Lynput:unbind' (string expected, got " .. type(action) .. ")" .. 297 | "\nCheck the stack traceback to know where the invalid arguments have been passed" 298 | ) 299 | 300 | -- Transforms 1 command to a table 301 | if type(commands) ~= "table" then 302 | local command = commands 303 | commands = {} 304 | commands[1] = command 305 | end -- if only one command was given 306 | 307 | -- Type checking for argument #2 308 | for i, command in ipairs(commands) do 309 | assert( 310 | type(command) == "string", 311 | "bad argument #2 to 'Lynput:unbind' (string or table of strings expected, got " .. 312 | type(command) .. " in element #" .. i .. ")" .. 313 | "\nCheck the stack traceback to know where the invalid arguments have been passed" 314 | ) 315 | end -- for each element in commands table 316 | 317 | -- Process command 318 | for _, command in ipairs(commands) do 319 | -- Is action set? 320 | -- FIXME: Exception when indexing nil values are not being handled, fix everywhere 321 | assert( 322 | self[action] ~= nil, 323 | "Could not unbind command->" .. command .. 324 | " to action->" .. action .. ", the action is not set." 325 | ) 326 | -- Is command set? 327 | local state, input = string.match(command, "(.+)%s(.+)") 328 | assert( 329 | self.inputsSet[input][state], 330 | "Could not unbind command->" .. command .. 331 | " to action->" .. action .. ", the command is not set." 332 | ) 333 | 334 | self.inputsSet[input][state] = nil 335 | local inputStates = self.inputsSet[input] 336 | 337 | local statesNum = 0 338 | for state, _ in pairs(self.inputsSet[input]) do 339 | statesNum = statesNum + 1 340 | end -- for each state 341 | 342 | if statesNum == 0 then 343 | self.inputsSet[input] = nil 344 | end -- if there are no more states set 345 | 346 | self[action] = false 347 | end -- for each command 348 | end 349 | 350 | 351 | function Lynput:unbindAll(action) 352 | -- Type checking for argument #1 353 | assert( 354 | type(action) == "string", 355 | "bad argument #1 to 'Lynput:unbindAll' (string expected, got " .. type(action) .. ")" .. 356 | "\nCheck the stack traceback to know where the invalid arguments have been passed" 357 | ) 358 | 359 | -- Is action set? 360 | assert( 361 | self[action] ~= nil, 362 | "Could not unbind all commands in action->" .. action .. 363 | ", the action is not set." 364 | ) 365 | 366 | for inputSet, states in pairs(self.inputsSet) do 367 | for state, actionSet in pairs(states) do 368 | if actionSet == action then 369 | self.inputsSet[inputSet][state] = nil 370 | end -- if actionSet == action 371 | end -- for each inputSet state 372 | end -- for each input set 373 | 374 | self[action] = false 375 | end 376 | 377 | 378 | function Lynput:removeAction(action) 379 | -- Type checking for argument #1 380 | assert( 381 | type(action) == "string", 382 | "bad argument #1 to 'Lynput:removeAction' (string expected, got " .. type(action) .. ")" .. 383 | "\nCheck the stack traceback to know where the invalid arguments have been passed" 384 | ) 385 | 386 | -- Is action set? 387 | assert( 388 | self[action] ~= nil, 389 | "Could not remove action->" .. action .. 390 | ", this action does not exist." 391 | ) 392 | 393 | self:unbindAll(action) 394 | self[action] = nil 395 | end 396 | 397 | 398 | function Lynput:update(dt) 399 | -- It's not possible to iterate actions through "self" because it 400 | -- also contains the inputsSet table and other data 401 | for _, states in pairs(self.inputsSet) do 402 | for state, actionSet in pairs(states) do 403 | if state == "press" or state == "release" then 404 | self[actionSet] = false 405 | end -- if state ~= hold 406 | end -- for each state 407 | end -- for each input set 408 | 409 | if Lynput[self.gpad] then 410 | for loveAxis, lynputAxis in pairs(Lynput.s_gamepadAxes) do 411 | if self.inputsSet[lynputAxis] then 412 | local val = Lynput[self.gpad]:getGamepadAxis(loveAxis) * 100 413 | 414 | for interval, action in pairs(self.inputsSet[lynputAxis]) do 415 | local min, max = string.match(interval, "(.+)%:(.+)") 416 | min = tonumber(min) 417 | max = tonumber(max) 418 | if val >= min and (math.abs(val) > self.gpadDeadZone) and val <= max then 419 | self[action] = true 420 | else 421 | self[action] = false 422 | end -- if val is in interval 423 | end -- for each interval 424 | end -- if the axis is set 425 | end -- for each axis 426 | end -- if the gamepad has been added 427 | end 428 | 429 | 430 | --------------------------------- 431 | -- KEYBOARD CALLBACKS 432 | --------------------------------- 433 | function Lynput.onkeypressed(key) 434 | for _, lynput in pairs(Lynput.s_lynputs) do 435 | if lynput.inputsSet["any"] then 436 | if lynput.inputsSet["any"]["press"] then 437 | local action = lynput.inputsSet["any"]["press"] 438 | lynput[action] = true 439 | end -- if press_any is set 440 | if lynput.inputsSet["any"]["hold"] then 441 | local action = lynput.inputsSet["any"]["hold"] 442 | lynput[action] = true 443 | end -- if hold_any is set 444 | end -- if "any" is set 445 | 446 | if lynput.inputsSet[key] then 447 | if lynput.inputsSet[key]["press"] then 448 | local action = lynput.inputsSet[key]["press"] 449 | lynput[action] = true 450 | end -- if press_key is set 451 | if lynput.inputsSet[key]["hold"] then 452 | local action = lynput.inputsSet[key]["hold"] 453 | lynput[action] = true 454 | end -- if hold_key is set 455 | end -- if key set 456 | end -- for each lynput 457 | end 458 | 459 | 460 | function Lynput.onkeyreleased(key) 461 | for _, lynput in pairs(Lynput.s_lynputs) do 462 | if lynput.inputsSet["any"] then 463 | if lynput.inputsSet["any"]["release"] then 464 | local action = lynput.inputsSet["any"]["release"] 465 | lynput[action] = true 466 | end -- if release_any is set 467 | if lynput.inputsSet["any"]["hold"] then 468 | local action = lynput.inputsSet["any"]["hold"] 469 | lynput[action] = false 470 | end -- if hold_any is set 471 | end -- if "any" is set 472 | 473 | if lynput.inputsSet[key] then 474 | if lynput.inputsSet[key]["release"] then 475 | local action = lynput.inputsSet[key]["release"] 476 | lynput[action] = true 477 | end -- if release_key is set 478 | if lynput.inputsSet[key]["hold"] then 479 | local action = lynput.inputsSet[key]["hold"] 480 | lynput[action] = false 481 | end -- if hold_key is set 482 | end -- if key is set 483 | end -- for each lynput 484 | end 485 | 486 | 487 | -------------------------------------- 488 | -- MOUSE CALLBACKS 489 | -------------------------------------- 490 | function Lynput.onmousepressed(button) 491 | -- Translate LÖVE button to Lynput button 492 | button = Lynput.s_mouseButtons[tostring(button)] 493 | -- Process button 494 | for _, lynput in pairs(Lynput.s_lynputs) do 495 | if lynput.inputsSet["any"] then 496 | if lynput.inputsSet["any"]["press"] then 497 | local action = lynput.inputsSet["any"]["press"] 498 | lynput[action] = true 499 | end -- if press_any is set 500 | if lynput.inputsSet["any"]["hold"] then 501 | local action = lynput.inputsSet["any"]["hold"] 502 | lynput[action] = true 503 | end -- if hold_any is set 504 | end -- if "any" is set 505 | 506 | if lynput.inputsSet[button] then 507 | if lynput.inputsSet[button]["press"] then 508 | local action = lynput.inputsSet[button]["press"] 509 | lynput[action] = true 510 | end -- if press_button is set 511 | if lynput.inputsSet[button]["hold"] then 512 | local action = lynput.inputsSet[button]["hold"] 513 | lynput[action] = true 514 | end -- if hold_button is set 515 | end -- if button is set 516 | end -- for each lynput 517 | end 518 | 519 | 520 | function Lynput.onmousereleased(button) 521 | -- Translate LÖVE button to Lynput button 522 | button = Lynput.s_mouseButtons[tostring(button)] 523 | -- Process button 524 | for _, lynput in pairs(Lynput.s_lynputs) do 525 | if lynput.inputsSet["any"] then 526 | if lynput.inputsSet["any"]["release"] then 527 | local action = lynput.inputsSet["any"]["release"] 528 | lynput[action] = true 529 | end -- if release_any is set 530 | if lynput.inputsSet["any"]["hold"] then 531 | local action = lynput.inputsSet["any"]["hold"] 532 | lynput[action] = false 533 | end -- if hold_any is set 534 | end -- if "any" is set 535 | 536 | if lynput.inputsSet[button] then 537 | if lynput.inputsSet[button]["release"] then 538 | local action = lynput.inputsSet[button]["release"] 539 | lynput[action] = true 540 | end -- if press_button is set 541 | if lynput.inputsSet[button]["hold"] then 542 | local action = lynput.inputsSet[button]["hold"] 543 | lynput[action] = false 544 | end -- if hold_button is set 545 | end -- if button is set 546 | end -- for each lynput 547 | end 548 | 549 | 550 | --------------------------------------------------- 551 | -- GAMEPAD CALLBACKS 552 | --------------------------------------------------- 553 | function Lynput.ongamepadpressed(gamepadID, button) 554 | -- Translate LÖVE button to Lynput button 555 | button = Lynput.s_gamepadButtons[button] 556 | -- Process Lynput button 557 | for _, lynput in pairs(Lynput.s_lynputs) do 558 | if lynput.gpad then 559 | if Lynput[lynput.gpad]:getID() == gamepadID then 560 | if lynput.inputsSet["any"] then 561 | if lynput.inputsSet["any"]["press"] then 562 | local action = lynput.inputsSet["any"]["press"] 563 | lynput[action] = true 564 | end -- if press_any is set 565 | if lynput.inputsSet["any"]["hold"] then 566 | local action = lynput.inputsSet["any"]["hold"] 567 | lynput[action] = true 568 | end -- if hold_any is set 569 | end -- if "any" is set 570 | 571 | 572 | if lynput.inputsSet[button] then 573 | if lynput.inputsSet[button]["press"] then 574 | local action = lynput.inputsSet[button]["press"] 575 | lynput[action] = true 576 | end -- if press_button is set 577 | if lynput.inputsSet[button]["hold"] then 578 | local action = lynput.inputsSet[button]["hold"] 579 | lynput[action] = true 580 | end -- if hold_button is set 581 | end -- if button is set 582 | end -- if gamepad is set 583 | end -- if lynput has a gamepad attached 584 | end -- for each lynput 585 | end 586 | 587 | 588 | function Lynput.ongamepadreleased(gamepadID, button) 589 | -- Translate LÖVE button to Lynput button 590 | button = Lynput.s_gamepadButtons[button] 591 | -- Process Lynput button 592 | for _, lynput in pairs(Lynput.s_lynputs) do 593 | if lynput.gpad then 594 | if Lynput[lynput.gpad]:getID() == gamepadID then 595 | if lynput.inputsSet["any"] then 596 | if lynput.inputsSet["any"]["release"] then 597 | local action = lynput.inputsSet["any"]["release"] 598 | lynput[action] = true 599 | end -- if release_any is set 600 | if lynput.inputsSet["any"]["hold"] then 601 | local action = lynput.inputsSet["any"]["hold"] 602 | lynput[action] = false 603 | end -- if hold_any is set 604 | end -- if "any" is set 605 | 606 | if lynput.inputsSet[button] then 607 | if lynput.inputsSet[button]["release"] then 608 | local action = lynput.inputsSet[button]["release"] 609 | lynput[action] = true 610 | end -- if release_button is set 611 | if lynput.inputsSet[button]["hold"] then 612 | local action = lynput.inputsSet[button]["hold"] 613 | lynput[action] = false 614 | end -- if hold_button is set 615 | end -- if button is set 616 | end -- if gamepad is set 617 | end -- if lynput has a gamepad attached 618 | end -- for each lynput 619 | end 620 | 621 | 622 | function Lynput.ongamepadadded(gamepad) 623 | local gamepadID = gamepad:getID() 624 | local i = 1 625 | local gpad = "GPAD_1" 626 | while Lynput[gpad] do 627 | if Lynput[gpad]:getID() == gamepadID then 628 | return 629 | end -- if gamepadID is already assigned 630 | 631 | i = i +1 632 | gpad = "GPAD_" .. i 633 | end -- while gpad exists 634 | 635 | -- gpad does no exists, so we assign the new gamepad to it 636 | Lynput[gpad] = gamepad 637 | end 638 | 639 | 640 | return Lynput 641 | --------------------------------------------------------------------------------