1
0
Fork 0
mirror of https://github.com/Reuh/ubiquitousse.git synced 2025-10-27 09:09:30 +00:00

input: Add multidimensional inputs to replace pointer special case, improve documentation

This commit is contained in:
Étienne Fildadut 2022-09-20 01:10:39 +09:00
parent 7ad5c2d641
commit 8b994608a2
18 changed files with 1490 additions and 264 deletions

View file

@ -5,7 +5,7 @@ Set of various libraries I use for game development, mainly with LÖVE. Most of
This provides, sorting the one with the fewest existing alterative as far as I know first: This provides, sorting the one with the fewest existing alterative as far as I know first:
* `ldtk` provides a [LDtk](https://ldtk.io/) level importer * `ldtk` provides a [LDtk](https://ldtk.io/) level importer
* `gltf` provides a [glTF](https://www.khronos.org/gltf/) model loader * `gltf` provides a [glTF](https://www.khronos.org/gltf/) model loader (documentation WIP)
* `ecs` provides [ECS](https://en.wikipedia.org/wiki/Entity_component_system) facilities * `ecs` provides [ECS](https://en.wikipedia.org/wiki/Entity_component_system) facilities
* `input` provides input management facilities * `input` provides input management facilities
* `timer` provides time management facilities * `timer` provides time management facilities

View file

@ -113,7 +113,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-09-16 20:07:07 </i> <i style="float:right;">Last updated 2022-09-20 01:08:14 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -337,7 +337,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-09-16 20:07:07 </i> <i style="float:right;">Last updated 2022-09-20 01:08:14 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -1729,7 +1729,7 @@ its sibling systems (i.e. completely stop the propagation of the event).</li>
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-09-16 20:07:07 </i> <i style="float:right;">Last updated 2022-09-20 01:08:14 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

File diff suppressed because it is too large Load diff

View file

@ -2066,7 +2066,7 @@ end
Level background. </p> Level background. </p>
<p> If there is a background image, <code>background.image</code> contains a table <code>{image=image, x=number, y=number, sx=number, sy=number}</code> <p> If there is a background image, <code>background.image</code> contains a table <code>{image=image, x=number, y=number, sx=number, sy=number}</code>
where <a href="../modules/ldtk.html#Tileset.image">image</a> is the LÖVE image (or image filepath if LÖVE not available) <a href="../modules/ldtk.html#Entity.x">x</a> and <a href="../modules/ldtk.html#Tile.y">y</a> are the top-left position, where <a href="../modules/ldtk.html#Tileset.image">image</a> is the LÖVE image (or image filepath if LÖVE not available) <a href="../modules/ldtk.html#Entity.x">x</a> and <a href="../modules/ldtk.html#IntTile.y">y</a> are the top-left position,
and <a href="../modules/ldtk.html#Entity.sx">sx</a> and <a href="../modules/ldtk.html#Entity.sy">sy</a> the horizontal and vertical scale factors. and <a href="../modules/ldtk.html#Entity.sx">sx</a> and <a href="../modules/ldtk.html#Entity.sy">sy</a> the horizontal and vertical scale factors.
</ul> </ul>
@ -2142,7 +2142,7 @@ end
<li>Enum are converted into a Lua string giving the currently selected enum value.</li> <li>Enum are converted into a Lua string giving the currently selected enum value.</li>
<li>Filepath are converted into a Lua string giving the file path.</li> <li>Filepath are converted into a Lua string giving the file path.</li>
<li>Arrays are converted into a Lua table with the elements in it as a list.</li> <li>Arrays are converted into a Lua table with the elements in it as a list.</li>
<li>Points are converted into a Lua table with the fields <a href="../modules/ldtk.html#Entity.x">x</a> and <a href="../modules/ldtk.html#Tile.y">y</a>, in pixels: <code>{ x=number, y=number }</code>.</li> <li>Points are converted into a Lua table with the fields <a href="../modules/ldtk.html#Entity.x">x</a> and <a href="../modules/ldtk.html#IntTile.y">y</a>, in pixels: <code>{ x=number, y=number }</code>.</li>
<li>Colors are converted into a Lua table with the red, green and blue components in [0-1] as a list: <code>{r,g,b}</code>.</li> <li>Colors are converted into a Lua table with the red, green and blue components in [0-1] as a list: <code>{r,g,b}</code>.</li>
<li>Tiles are converted into a Lua table { tileset = associated tileset object, quad = associated quad } where <a href="../modules/ldtk.html#Tile.quad">quad</a> is a LÖVE Quad if LÖVE is available, otherwise a table <code>{ x, y, width, height }</code>.</li> <li>Tiles are converted into a Lua table { tileset = associated tileset object, quad = associated quad } where <a href="../modules/ldtk.html#Tile.quad">quad</a> is a LÖVE Quad if LÖVE is available, otherwise a table <code>{ x, y, width, height }</code>.</li>
<li>EntityRef are converted into a Lua table { level = level, layerIid = layer IID, entityIid = entity IID, entity = see explanation }. If the entity being refernced belongs to another level and this level is not loaded, <code>entity</code> will be nil; otherwise (same level or the other level is also loaded), it will contain the entity.</li> <li>EntityRef are converted into a Lua table { level = level, layerIid = layer IID, entityIid = entity IID, entity = see explanation }. If the entity being refernced belongs to another level and this level is not loaded, <code>entity</code> will be nil; otherwise (same level or the other level is also loaded), it will contain the entity.</li>
@ -2170,7 +2170,7 @@ end
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-09-16 20:07:07 </i> <i style="float:right;">Last updated 2022-09-20 01:08:14 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -703,7 +703,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-09-16 20:07:07 </i> <i style="float:right;">Last updated 2022-09-20 01:08:14 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -788,7 +788,7 @@ signal.event:bind(&quot;keypressed&quot;, function(key, scancode) print(&quot;pr
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-09-16 20:07:07 </i> <i style="float:right;">Last updated 2022-09-20 01:08:14 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -1154,7 +1154,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-09-16 20:07:07 </i> <i style="float:right;">Last updated 2022-09-20 01:08:14 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -238,7 +238,6 @@ the repository to save you a few seconds.</p>
</dt> </dt>
<dd> <dd>
Input management, if available. Input management, if available.
TODO: documentation not currently generated with LDoc.
</ul> </ul>
</ul> </ul>
@ -395,7 +394,7 @@ the repository to save you a few seconds.</p>
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-09-16 20:07:07 </i> <i style="float:right;">Last updated 2022-09-20 01:08:14 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -785,7 +785,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-09-16 20:07:07 </i> <i style="float:right;">Last updated 2022-09-20 01:08:14 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -65,7 +65,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-09-16 20:07:07 </i> <i style="float:right;">Last updated 2022-09-20 01:08:14 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -63,7 +63,7 @@
<ul> <ul>
<li><a href="../modules/ldtk.html#">ldtk</a> provides a <a href="https://ldtk.io/">LDtk</a> level importer</li> <li><a href="../modules/ldtk.html#">ldtk</a> provides a <a href="https://ldtk.io/">LDtk</a> level importer</li>
<li><code>gltf</code> provides a <a href="https://www.khronos.org/gltf/">glTF</a> model loader</li> <li><code>gltf</code> provides a <a href="https://www.khronos.org/gltf/">glTF</a> model loader (documentation WIP)</li>
<li><a href="../modules/ecs.html#">ecs</a> provides <a href="https://en.wikipedia.org/wiki/Entity_component_system">ECS</a> facilities</li> <li><a href="../modules/ecs.html#">ecs</a> provides <a href="https://en.wikipedia.org/wiki/Entity_component_system">ECS</a> facilities</li>
<li><a href="../modules/input.html#">input</a> provides input management facilities</li> <li><a href="../modules/input.html#">input</a> provides input management facilities</li>
<li><a href="../modules/timer.html#">timer</a> provides time management facilities</li> <li><a href="../modules/timer.html#">timer</a> provides time management facilities</li>
@ -87,7 +87,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-09-16 20:07:07 </i> <i style="float:right;">Last updated 2022-09-20 01:08:14 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -62,7 +62,6 @@ ubiquitousse = {
-- @see ecs -- @see ecs
ecs = nil, ecs = nil,
--- Input management, if available. --- Input management, if available.
-- TODO: documentation not currently generated with LDoc.
-- @see input -- @see input
input = nil, input = nil,
--- LDtk level import, if available. --- LDtk level import, if available.

View file

@ -1,15 +1,11 @@
return { return {
move = { move = {
horizontal = { "clamped(child.right - child.left, child.down - child.up)",
"child.positive - child.negative", dimension = 2,
positive = { "scancode.right", "scancode.d", "axis.leftx.p", "button.dpright" }, right = { "scancode.right", "scancode.d", "axis.leftx.p", "button.dpright" },
negative = { "scancode.left", "scancode.a", "axis.leftx.n", "button.dpleft" }, left = { "scancode.left", "scancode.a", "axis.leftx.n", "button.dpleft" },
}, down = { "scancode.down", "scancode.s", "axis.lefty.p", "button.dpdown" },
vertical = { up = { "scancode.up", "scancode.w", "axis.lefty.n", "button.dpup" },
"child.positive - child.negative",
positive = { "scancode.down", "scancode.s", "axis.lefty.p", "button.dpdown" },
negative = { "scancode.up", "scancode.w", "axis.lefty.n", "button.dpup" },
},
}, },
confirm = { "scancode['return']", "scancode.space", "scancode.e", "button.a" }, confirm = { "scancode['return']", "scancode.space", "scancode.e", "button.a" },
cancel = { "scancode.escape", "scancode.backspace", "button.b" }, cancel = { "scancode.escape", "scancode.backspace", "button.b" },

View file

@ -4,13 +4,13 @@ local max, min = math.max, math.min
--- This event registry is where every input object will listen for source events. --- This event registry is where every input object will listen for source events.
-- --
-- Available events: -- Available events:
-- * `"source.name"`: triggered when source.name (example name) is updated. -- * `"source.name"`: triggered when source.name (example input source name, replace with your own) is updated.
-- Will pass the arguments _new value_ (number), _filter_ (optional), _..._ (additional arguments for the filter, optional). -- Must pass the arguments _new value_ (number), _filter_ (optional), _..._ (additional arguments for the filter, optional).
-- `filter` is an optional filter function that will be called by the listening inputs with arguments filter(input object, new value, ...), -- `filter` is an optional filter function that will be called by the listening inputs with arguments `filter(input object, new value, ...)`,
-- and should return the (eventually modified) new value. If it returns `nil`, the input will ignore the event (for example if the event concerns -- and should return the (eventually modified) new value. If it returns `nil`, the input will ignore the event (for example if the event concerns
-- a joystick that is not linked with the input). -- a joystick that is not linked with the input).
-- * `"_active"`: triggered when any input is active, used for input detection in `onActiveNextSource`. -- * `"_active"`: triggered when any input is active, used for input detection in `onActiveNextSource`.
-- Will pass arguments _source name_ (string), _new value_, _filter_, _..._ (same arguments as other source updates, with source name added). -- Must pass arguments _source name_ (string), _new value_, _filter_, _..._ (same arguments as other source updates, with source name added).
local event = signal.new() local event = signal.new()
local function update(source, new, filter, ...) local function update(source, new, filter, ...)

View file

@ -1,20 +1,74 @@
--- Input management facilities. --[[- Input management facilities.
--
-- The module returns a single function, `input`. The module returns a single function, `input`.
--
-- **Requires** ubiquitousse.signal. You can find in the module `uqt.input.default` (in file `input/default.lua`) some common input configuration for
-- @module input 2D movement, confirmation and cancellation (for both keyboard and joystick). Feel free to
-- @usage use them as-is, or as a base for your own inputs.
-- TODO
**Requires** ubiquitousse.signal.
@module input
@usage
local input = require("ubiquitousse.input")
-- Joystick-only control for player 1
local player1 = {
move = {
-- 2D movement, clamped on a circle
"clamped(child.right - child.left, child.down - child.up)",
dimension = 2,
-- All the directions we can go, using both the left joystick and the D-pad
right = { "axis.leftx.p", "button.dpright" },
left = { "axis.leftx.n", "button.dpleft" },
down = { "axis.lefty.p", "button.dpdown" },
up = { "axis.lefty.n", "button.dpup" }
},
fire = { "button.a" }
}
-- Only consider inputs from the first joystick (in practice you will want to check if the joystick exists before running this line)
player1:setJoystick(love.joystick.getJoysticks()[1])
-- Player 2, using a second gamepad!
local player2 = player1:clone()
player2:setJoystick(love.joystick.getJoysticks()[2])
-- Define input callbacks.
player1.fire.event:bind("pressed", function() print("player 1 starts firing!") end)
player1.fire.event:bind("released", function() print("player 1 stop firing...") end)
function love.update()
-- Get player 1's 2D movement
local x, y = player1.move:value()
movePlayer1(x, y)
-- Check current state of the firing input
if player1.fire:down() then
-- currently firing!
end
-- Update inputs.
player1:update()
player2:update()
end
--]]
local signal = require((...):gsub("input%.input$", "signal")) local signal = require((...):gsub("input%.input$", "signal"))
local event = require((...):gsub("input$", "event")) local event = require((...):gsub("input$", "event"))
local abs, sqrt, floor, ceil, min, max = math.abs, math.sqrt, math.floor, math.ceil, math.min, math.max local abs, sqrt, floor, ceil, min, max = math.abs, math.sqrt, math.floor, math.ceil, math.min, math.max
-- TODO: -- TODO: friendly name for sources
-- friendly name for sources
-- write doc, incl how to define your own source and source expressions, default inputs -- TODO: way to handle text input
-- don't want to change how everything is number based here (it's clean), but would be ok to eg give an additionnal metdatat (text string) along with the "text key pressed" input
-- Table to contain temporary calculations (mainly to compute input deltas without needing to allocate a new table).
-- Note that this table is never cleared; don't fill it with large data and don't assume its length.
local tmp = {}
-- Always returns 0. -- Always returns 0.
local function zero() return 0 end local function zero() return 0 end
@ -30,74 +84,28 @@ local function loadexp(exp, env)
return fn return fn
end end
-- Set a value in a table using its path string.
local function setPath(t, path, val)
for part in path:gmatch("(.-)%.") do
assert(t[part])
t = t[part]
end
t[path:match("[^%.]+$")] = val
end
local function ensurePath(t, path, default)
for part in path:gmatch("(.-)%.") do
if not t[part] then t[part] = {} end
t = t[part]
end
local final = path:match("[^%.]+$")
if not t[final] then
t[final] = default
end
end
-- Functions available in input expressions.
local expressionEnv
expressionEnv = {
floor = floor,
ceil = ceil,
abs = abs,
clamp = function(x, xmin, xmax)
return min(max(x, xmin), xmax)
end,
min = function(x, y, ...)
local m = min(x, y)
if ... then
return expressionEnv.min(m, ...)
else
return m
end
end,
max = function(x, y, ...)
local m = max(x, y)
if ... then
return expressionEnv.max(m, ...)
else
return m
end
end,
deadzone = function(x, deadzone)
if abs(x) < deadzone then
return 0
end
return x
end
}
-- List of modifiers that can be applied to a source in an expression
local sourceModifiers = { "passive", "active" }
for _, mod in ipairs(sourceModifiers) do
expressionEnv[mod] = function(...) return ... end
end
local input_mt local input_mt
--- Make a new input object. --- Make a new input object.
-- t: input configuration table (optional) --
-- This constructor is returned by the `uqt.input` module.
--
-- @tparam[opt] table config input configuration table, see `Input.config`
-- @treturn Input Input object
-- @usage
-- local player = uqt.input {
-- fire = { "key.a" },
-- jump = { "key.space" }
-- }
-- player.fire.event:bind("pressed", function() print("pew pew") end)
-- @function input -- @function input
local function make_input(t) local function make_input(t)
local self = setmetatable({ local self = setmetatable({
config = t or {}, config = t or {},
children = {}, children = {},
event = signal.new(), event = signal.new(),
_value = { 0 },
_prevValue = { 0 },
_sourceCache = {}, _sourceCache = {},
_event = signal.group(), _event = signal.group(),
_afterFilterEvent = signal.new(), _afterFilterEvent = signal.new(),
@ -107,68 +115,255 @@ local function make_input(t)
return self return self
end end
--- Input expressions.
--
-- Each `Input` is associated with a list of *input expressions*.
--
-- An input expression is a `string` representing a valid Lua expression.
-- These Lua expressions are used to compute the values returned by the input.
-- An input expression should returns as many values as the dimension of its input.
--
-- When referring to a variable in an expression, it will be interpreted as an *input source*.
-- An input source is where the initial input data comes from: for example, an input source could be the current state of
-- the Q key: `"key.q"` (=1 when the key is down, 0 otherwise).
-- See [sources](#Input_sources) for details on how to define sources and built-in sources.
--
-- When a source that is present is the expression is updated, the input will be automatically updated in a reactive way, unless
-- the input is *passive* - that is, it can not trigger input update.
--
-- Additionally, you can also define your own variables to be used in input expression by giving arguments.
--
-- In input expression, you have no access to the Lua standard library; some other, more specific functions are available instead
-- and described below.
--
-- @usage
-- -- Example input configs for various input expression features.
-- fire = {
-- -- Basic input expression: 1 when Q is pressed, 0 otherwise. Will automatically update when Q is pressed.
-- "key.q",
-- -- Example using two inputs sources: when either A or Q is pressed, this will update, and returns 1 is at least one of them is pressed.
-- "max(key.q, key.a)",
-- -- This input has two input expression: it will be updated when either of them is updated and keep the value of the expression that was updated last.
-- }
-- horizontal = {
-- -- Another example, typically used to define an input for horizontal movement: returns 1 when pressing only right, -1 when pressing only left, and 0 otherwise.
-- "key.right - key.left",
-- }
-- arguments = {
-- -- You can give arguments to an expression by wrapping the expression in a table containing the arguments.
-- { "axis.leftx + offset", offset = 1 }
-- }
-- passive = {
-- -- Same as the example above, but this will only update when Q is pressed - pressing A will not update the input on its own.
-- -- Passive input are typically used for modifiers keys (shift, alt, etc.) that should not trigger an update when pressed on their own.
-- "max(key.q, passive(key.a))"
-- }
-- dimension = {
-- -- A two-dimensional input should have input expressions that return two values.
-- -- Here a common example that could be used to move something in 2D.
-- "key.right - key.left, key.down - key.up",
-- dimension = 2
-- }
-- special = {
-- -- A special case: if the `dt` source is present in an expression, it will make every other input source passive by default in the expression.
-- -- This is the case since `dt` will trigger an update every frame, and is therefore mostly relevant for input that is used once per frame only
-- -- (while other input sources might cause the input to update several times per frame).
-- "axis.leftx * dt"
-- }
-- child = {
-- -- Children input can be used as input sources using the `child.name` syntax.
-- -- If the children input has more than one dimension, you will need to specify it using a numeric index like `child.fire[2]` (for the dimension 2 of the child).
-- "child.fire",
-- fire = { "key.q" }
--}
-- @section Expressions
local expressionEnv
expressionEnv = {
--- Same as Lua's `math.floor`.
-- @function floor
-- @tparam number x number to round
-- @treturn number floored value
floor = floor,
--- Same as Lua's `math.ceil`.
-- @function floor
-- @tparam number x number to round
-- @treturn number ceiled value
ceil = ceil,
--- Same as Lua's `math.abs`.
-- @function floor
-- @tparam number x number to absolute
-- @treturn number absolute value
abs = abs,
--- Clamp x between xmin and xmax.
-- @tparam number x number clamp
-- @tparam number xmin minimal value
-- @tparam number xmax maximal value
-- @treturn number clamped value
clamp = function(x, xmin, xmax)
return min(max(x, xmin), xmax)
end,
--- Returns the minimal value among all parameters.
-- @tparam number x first value
-- @tparam number y second value
-- @tparam number ... other values
-- @treturn number smallest value among the arguments
min = function(x, y, ...)
local m = min(x, y)
if ... then
return expressionEnv.min(m, ...)
else
return m
end
end,
--- Returns the maximal value among all parameters.
-- @tparam number x first value
-- @tparam number y second value
-- @tparam number ... other values
-- @treturn number biggest value among the arguments
max = function(x, y, ...)
local m = max(x, y)
if ... then
return expressionEnv.max(m, ...)
else
return m
end
end,
--- If x < deadzone, returns 0; otherwise returns the value.
-- @tparam number x value
-- @tparam number deadzone deadzone
-- @treturn number 0 if x < deadzone; x otherwise
deadzone = function(x, deadzone)
if abs(x) < deadzone then
return 0
end
return x
end,
--- Returns a normalized version of the vector (x,y), i.e. "clamp" the returned x,y coordinates into a circle of radius 1.
-- Typically used to avoid faster movement on diagonals, as if both horizontal and vertical values are 1, the (1,1) vector has √2 magnitude, higher than the 1 magnitude of a purely vertical or horizontal movement.
-- @tparam number x value
-- @tparam number y value
-- @treturn number clamped x value
-- @treturn number clamped y value
clamped = function(x, y)
local mag = x*x + y*y
if mag > 1 then
local d = sqrt(mag)
return x/d, y/d
else
return x, y
end
end,
--- Mark an input source as passive.
-- @function passive
-- @tparam InputSource source input source to mark as passive
-- @treturn InputSource the same input source
passive = nil,
--- Mark an input source as active.
-- Note that input sources are active by default in most cases.
-- @function passive
-- @tparam InputSource source input source to mark as active
-- @treturn InputSource the same input source
active = nil
}
-- List of modifiers that can be applied to a source in an expression
local sourceModifiers = { "passive", "active" }
for _, mod in ipairs(sourceModifiers) do
expressionEnv[mod] = function(...) return ... end
end
--- Input methods. --- Input methods.
--
-- Methods and attributes available on Input objects. See `input` to create such an object.
-- @type Input -- @type Input
input_mt = { input_mt = {
--- Input configuration table. --- Input configuration table.
--
-- It can be used to recreate this input object later (by passing the table as an argument for the input constructor). -- It can be used to recreate this input object later (by passing the table as an argument for the input constructor).
-- This table does not contain any userdata and should be easily serializable (e.g. to save custom input binding config). -- This table does not contain any userdata and should be easily serializable (e.g. to save custom input binding config).
-- This doesn't include input state, grab state, the event registry and the selected joystick since they may change often during runtime. -- This doesn't include input state, grab state, the event registry and the selected joystick since they may change often during runtime.
--
-- Can be changed anytime, but you may need to call `reload` to apply changes. -- Can be changed anytime, but you may need to call `reload` to apply changes.
--
-- See [expressions](#Input_expressions) for an explanation on how to write input expressions.
-- @usage -- @usage
-- player.config = { -- player.config = {
-- "key.a", "key.d - key.a", {"key.left + x", x=0.5}, -- list of input sources expressions -- -- list of input sources expressions: either a string, or a table to specify some arguments for the expression
-- jump = {...}, -- children input -- "key.a", "key.d - key.a", {"key.left + x", x=0.5},
-- deadzone = 0.05, -- The deadzone for analog inputs (e.g. joystick axes): if the input absolute value is strictly below this, it will be considered as 0. -- -- children input: the table take the same fields as this
-- threshold = 0.05 -- The pressed threshold: an input is considered down if above or equal to this value. -- jump = {...},
-- -- The deadzone for analog inputs (e.g. joystick axes): if the input absolute value is strictly below this, it will be considered as 0. 0.05 by default.
-- -- This is applied automatically after the evaluation of input expressions.
-- deadzone = 0.05,
-- -- The pressed threshold: an input is considered down if above or equal to this value. 0.05 by default.
-- -- This is considered when determining if the input is pressed, odwn and released.
-- threshold = 0.05,
-- -- Dimension of the input (i.e. the number of values returned by this input). 1 by default.
-- dimension = 1
-- } -- }
config = {}, config = {},
--- List and map of children inputs. --- List and map of children `Input`s.
-- {[child1.name]=child1, [child2.name]=child2, child1, child2...} --
-- Takes the form `{[child1.name]=child1, [child2.name]=child2, child1, child2...}`.
-- Each child input is present both an element of this list and as the value associated with its name in the table.
--
-- Note that children are *also* set directly on the input object for easier access.
-- @usage
-- local player = input{ fire = "button.a" }
-- local fire = player.fire
-- -- Is the same as:
-- local fire = player.children.fire
-- @ro
children = {}, children = {},
--- Name of the input. --- Name of the input.
-- Defined on children inputs only. -- Defined on children inputs only.
-- @ftype string
-- @ro
name = nil, name = nil,
--- False if the input is currently not grabbed, a subinput otherwise. --- `false` if the input is currently not grabbed, a sub`Input` otherwise.
-- This may be different between each subinput. -- This may be different between each subinput.
-- @ro
grabbed = false, grabbed = false,
--- False if the input is not a subinput, the input it grabbed otherwise. --- `false` if the input is not a subinput, the `Input` it was grabbed from otherwise.
-- This may be different between each subinput. -- This may be different between each subinput.
-- @ro
grabbing = false, grabbing = false,
--- Input event registry. --- Input event registry.
-- The following events are available: -- The following events are available:
-- --
-- * `"moved"`: called when the input value change, with arguments (new value, delta since last event) -- * `"moved"`: called when the input value change, with arguments `(new value, delta since last event)`. For inputs with dimension > 1, arguments are `(new value[1], new value[2], ..., delta[1], delta[2], ...)`.
-- * `"pressed"`: called when the input is pressed
-- * `"released"`: called when the input is released
-- --
-- For pointer inputs (have a "horizontal" and "vertical" children inputs) is also avaible: -- * `"pressed"`: called when the input is pressed, with arguments `(1, new value, delta since last event)`. For inputs with dimension > 1, arguments are `(dimensions that was pressed, new value[1], new value[2], ..., delta[1], delta[2], ...)`.
-- --
-- * `"pointer moved"`: called when the pointer position change, with arguments (new pointer x, new pointer y, delta x since last event, delta y since last event) -- * `"released"`: called when the input is released, with arguments `(1, new value, delta since last event)`. For inputs with dimension > 1, arguments are `(dimensions that was pressed, new value[1], new value[2], ..., delta[1], delta[2], ...)`.
-- --
-- Each subinput has a different event registry. -- Each subinput has a different event registry.
event = nil, event = nil,
-- Input state, independendant between each grab. Reset by :neutralize(). -- Input state, independendant between each grab. Reset by :neutralize().
_state = "none", -- none, pressed or released _state = "none", -- none, pressed or released
_value = 0, -- input value _value = { 0 }, -- input value
_prevValue = 0, -- value last frame _prevValue = { 0 }, -- value last frame
-- Input state, shared between grabs. -- Input state, shared between grabs.
_event = nil, -- Event group for all event binded by this input. _event = nil, -- Event group for all event binded by this input.
_sourceCache = {}, -- Map of the values currently taken by every source this input use. _sourceCache = {}, -- Map of the values currently taken by every source this input use. Sources are expected to return a single number value.
_afterFilterEvent = nil, -- Event registry that resend the source events after applying the eventual filter function. _afterFilterEvent = nil, -- Event registry that resend the source events after applying the eventual filter function.
_boundSourceEvents = {}, -- Map of sources events that are binded (and thus will send events to _afterFilterEvent). _boundSourceEvents = {}, -- Map of sources events that are binded (and thus will send events to _afterFilterEvent).
_joystick = nil, -- Currently selected joystick for this player. Also shared with children inputs. _joystick = nil, -- Currently selected joystick for this player. Also shared with children inputs.
_dimension = 1, -- Dimension of the input.
--- Update the input and its children. --- Update the input and its children.
-- Should be called every frame, typically _after_ you've done all your input handling -- Should be called every frame, typically _after_ you've done all your input handling
-- (otherwise `pressed` and `released` may never return true and `delta` might be wrong). -- (otherwise `pressed` and `released` may never return true and `delta` might be wrong).
--
-- (Note: this should not be called on subinputs) -- (Note: this should not be called on subinputs)
update = function(self) update = function(self)
self:_update() self:_update()
self._prevValue = self._value for i=1, self._dimension do
self._prevValue[i] = self._value[i]
end
for _, i in ipairs(self.children) do for _, i in ipairs(self.children) do
i:update() i:update()
end end
@ -179,9 +374,22 @@ input_mt = {
return make_input(self.config) return make_input(self.config)
end, end,
--- Relond the input `config`, and do the same for its children. --- Reload the input `config`, and do the same for its children.
-- This will reenable the input if it was disabled using `disable`. -- This will reenable the input if it was disabled using `disable`.
reload = function(self) reload = function(self)
-- resize dimensions
self._dimension = self.config.dimension or 1
if #self._value > self._dimension then
for i=self._dimension+1, #self._value do
self._value[i] = nil
self._prevValue[i] = nil
end
elseif #self._value < self._dimension then
for i=#self._value+1, self._dimension do
self._value[i] = 0
self._prevValue[i] = 0
end
end
-- clear all events we bounded previously -- clear all events we bounded previously
self._event:clear() self._event:clear()
self._boundSourceEvents = {} self._boundSourceEvents = {}
@ -191,6 +399,8 @@ input_mt = {
if not self.config[c.name] then if not self.config[c.name] then
c:disable() c:disable()
table.remove(self.children, i) table.remove(self.children, i)
self.children[c.name] = nil
self[c.name] = nil
end end
end end
-- reload children -- reload children
@ -228,7 +438,7 @@ input_mt = {
return self._sourceCache[key] or expressionEnv[key] return self._sourceCache[key] or expressionEnv[key]
end end
}) })
-- extract sources -- extract sources and set initial values in _sourceCache
local sources = {} local sources = {}
local srcmt local srcmt
srcmt = { -- metamethods of sources values during the scanning process srcmt = { -- metamethods of sources values during the scanning process
@ -237,11 +447,29 @@ input_mt = {
__mod = zero, __pow = zero, __mod = zero, __pow = zero,
__unm = zero, __idiv = zero, __unm = zero, __idiv = zero,
__index = function(t, key) __index = function(t, key)
local i = rawget(t, 1) local i = rawget(t, "_")
if i then sources[i][1] = sources[i][1] .. "." .. key if i then -- has a parent source key
else table.insert(sources, { key }) local source = sources[i]
local parentKey = source[#source]
-- turn parent source into a table in the cache
if source.parentCache[parentKey] == 0 then
source.parentCache[parentKey] = {}
end end
return setmetatable({ i or #sources }, srcmt) source.parentCache = source.parentCache[parentKey]
-- set value in the cache for this source
if not source.parentCache[key] then
source.parentCache[key] = 0
end
-- add key to source name for this current object
table.insert(source, key)
else -- new root source key
table.insert(sources, { key, parentCache = self._sourceCache })
-- set value in the cache for this source
if not self._sourceCache[key] then
self._sourceCache[key] = 0
end
end
return setmetatable({ _ = i or #sources }, srcmt)
end end
} }
local scanEnv = setmetatable({ value = 0 }, { __index = srcmt.__index }) -- value is not a source local scanEnv = setmetatable({ value = 0 }, { __index = srcmt.__index }) -- value is not a source
@ -250,19 +478,33 @@ input_mt = {
for _, mod in ipairs(sourceModifiers) do -- add modifiers functions for _, mod in ipairs(sourceModifiers) do -- add modifiers functions
scanEnv[mod] = function(source) scanEnv[mod] = function(source)
assert(getmetatable(source) == srcmt, ("trying to apply %s modifier on a non-source value"):format(mod)) assert(getmetatable(source) == srcmt, ("trying to apply %s modifier on a non-source value"):format(mod))
sources[rawget(source, 1)][mod] = true sources[rawget(source, "_")][mod] = true
return source return source
end end
end end
loadexp(exp, scanEnv)() -- scan! loadexp(exp, scanEnv)() -- scan!
-- build source names
for _, s in ipairs(sources) do
s.lastKey = s[#s] -- keep last key to allow setting the value in cache using parentCache[lastKey] = value
for i, p in ipairs(s) do
if type(p) == "string" then
if i > 1 then
s[i] = "." .. p
end
else
s[i] = ("[%s]"):format(tostring(p))
end
end
s.name = table.concat(s)
end
-- set every source to passive if there is a dt source -- set every source to passive if there is a dt source
local hasDt = false local hasDt = false
for _, s in ipairs(sources) do for _, s in ipairs(sources) do
if s[1] == "dt" then hasDt = true break end if s.name == "dt" then hasDt = true break end
end end
if hasDt then if hasDt then
for _, s in ipairs(sources) do for _, s in ipairs(sources) do
if s[1] ~= "dt" and not s.active then if s.name ~= "dt" and not s.active then
s.passive = true s.passive = true
end end
end end
@ -271,25 +513,36 @@ input_mt = {
local fn = loadexp(exp, env) local fn = loadexp(exp, env)
-- init sources and bind to source events -- init sources and bind to source events
local boundAfterFilterEvent = {} local boundAfterFilterEvent = {}
local function onAfterFilterEvent(new) self:_update(fn()) end local function onAfterFilterEvent(new) self:_update{fn()} end
for _, s in ipairs(sources) do for _, s in ipairs(sources) do
local sname = s[1] local sname = s.name
ensurePath(self._sourceCache, sname, 0)
if not self._boundSourceEvents[sname] then if not self._boundSourceEvents[sname] then
if sname:match("^child%.") then if sname:match("^child%.") then
local cname = sname:match("^child%.(.*)$") local cname, index = sname:match("^child%.(.*)%[(%d+)%]$")
assert(self.children[cname], ("input expression refer to %s but this input has no child named %s"):format(sname, cname)) if not cname then cname = sname:match("^child%.(.*)$") end
self._event:bind(self.children[cname].event, "moved", function(new) -- child event -> self._afterFilterEvent link local child = self.children[cname]
setPath(self._sourceCache, sname, new) assert(child, ("input expression refer to %s but this input has no child named %s"):format(sname, cname))
if child._dimension > 1 then
assert(index, ("input expression refer to %s but this child only has more than one dimension"):format(sname))
self._event:bind(self.children[cname].event, "moved", function(...) -- child event -> self._afterFilterEvent link
local new = select(tonumber(index), ...)
s.parentCache[s.lastKey] = new
self._afterFilterEvent:emit(sname, new) self._afterFilterEvent:emit(sname, new)
end) end)
else
assert(not index, ("input expression refer to %s but this child only has a single dimension"):format(sname))
self._event:bind(self.children[cname].event, "moved", function(new) -- child event -> self._afterFilterEvent link
s.parentCache[s.lastKey] = new
self._afterFilterEvent:emit(sname, new)
end)
end
else else
self._event:bind(event, sname, function(new, filter, ...) -- event source -> self._afterFilterEvent link self._event:bind(event, sname, function(new, filter, ...) -- event source -> self._afterFilterEvent link
if filter then if filter then
new = filter(self, new, ...) new = filter(self, new, ...)
if not new then return end -- filtered out if not new then return end -- filtered out
end end
setPath(self._sourceCache, sname, new) s.parentCache[s.lastKey] = new
self._afterFilterEvent:emit(sname, new) self._afterFilterEvent:emit(sname, new)
end) end)
end end
@ -301,11 +554,6 @@ input_mt = {
end end
end end
end end
-- rebind pointer events
if self.config.horizontal and self.config.horizontal then
self._event:bind(self.horizontal.event, "moved", function(new, delta) self.event:emit("pointer moved", new, self.vertical:value(), delta, 0) end)
self._event:bind(self.vertical.event, "moved", function(new, delta) self.event:emit("pointer moved", self.horizontal:value(), new, 0, delta) end)
end
end, end,
--- Disable the input and its children, preventing further updates and events. --- Disable the input and its children, preventing further updates and events.
-- The input can be reenabled using `reload`. -- The input can be reenabled using `reload`.
@ -316,7 +564,7 @@ input_mt = {
self._event:clear() self._event:clear()
end, end,
--- Will call fn(source) on the next activated source (including sources not currently used by this input). --- Will call `fn(source)` on the next activated source (including sources not currently used by this input).
-- Typically used to detect an input in your game input binding settings. -- Typically used to detect an input in your game input binding settings.
-- @param fn function that will be called on the next activated source matching the filter -- @param fn function that will be called on the next activated source matching the filter
-- @param[opt] filter list of string patterns that sources must start with (example `{"button", "key"}` to only get buttons and key sources) -- @param[opt] filter list of string patterns that sources must start with (example `{"button", "key"}` to only get buttons and key sources)
@ -349,7 +597,7 @@ input_mt = {
-- A grabbed input will no longer update and instead pass all new update to the subinput. -- A grabbed input will no longer update and instead pass all new update to the subinput.
-- This is typically used for contextual action or pause menus: by grabbing the player input, all the direct use of -- This is typically used for contextual action or pause menus: by grabbing the player input, all the direct use of
-- this input in the game will stop (can't move caracter, ...) and instead you can use the subinput to handle input in the pause menu. -- this input in the game will stop (can't move caracter, ...) and instead you can use the subinput to handle input in the pause menu.
-- To stop grabbing an input, you will need to `:release` the subinput. -- To stop grabbing an input, you will need to `release` the subinput.
-- --
-- This will also reset the input to a neutral state. The subinput will share everything with this input, except -- This will also reset the input to a neutral state. The subinput will share everything with this input, except
-- `grabbed`, `grabbing`, `event` (a new event registry is created), and of course its current state. -- `grabbed`, `grabbing`, `event` (a new event registry is created), and of course its current state.
@ -358,11 +606,19 @@ input_mt = {
grabbed = false, grabbed = false,
grabbing = self, grabbing = self,
event = signal.new(), event = signal.new(),
children = {} children = {},
_value = {},
_prevValue = {},
} }
for i=1, self._dimension do
g._value[i] = self._value[i]
g._prevValue[i] = self._prevValue[i]
end
for _, c in ipairs(self.children) do for _, c in ipairs(self.children) do
g[c.name] = c:grab() local gc = c:grab()
table.insert(g.children, g[c.name]) table.insert(g.children, gc)
g.children[c.name] = gc
g[c.name] = gc
end end
self:neutralize() self:neutralize()
self.grabbed = setmetatable(g, { __index = self }) self.grabbed = setmetatable(g, { __index = self })
@ -380,16 +636,20 @@ input_mt = {
self.grabbing = false self.grabbing = false
end, end,
--- Set the state of this input to a neutral position (i.e. value = 0). --- Set the state of this input to a neutral position (i.e. value = 0 for every dimension).
neutralize = function(self) neutralize = function(self)
self:_update(0) local zeros = { 0 }
for i=2, self._dimension do zeros[i] = 0 end
self:_update(zeros)
self._state = "none" self._state = "none"
self._value = 0 for i=1, self._dimension do
self._prevValue = 0 self._value[i] = 0
self._prevValue[i] = 0
end
end, end,
--- Set the joystick associated with this input. --- Set the joystick associated with this input.
-- The input will ignore every other joystick. -- This input will then ignore every other joystick.
-- Set joystick to `nil` to disable and get input from every connected joystick. -- Set joystick to `nil` to disable and get input from every connected joystick.
-- @param joystick LÖVE jostick object to associate -- @param joystick LÖVE jostick object to associate
setJoystick = function(self, joystick) setJoystick = function(self, joystick)
@ -399,51 +659,44 @@ input_mt = {
end end
end, end,
--- Returns the currently selected joystick. --- Returns the currently selected joystick.
-- @treturn joystick LÖVE jostick object
getJoystick = function(self) getJoystick = function(self)
return self._joystick return self._joystick
end, end,
--- Returns true if the input is currently down. --- Returns `true` if the input is currently down, `false` otherwise.
-- @treturn boolean if input is down on at least one dimensions
down = function(self) down = function(self)
return self._state == "down" or self._state == "pressed" return self._state == "down" or self._state == "pressed"
end, end,
--- Returns true if the input has just been pressed. --- Returns `true` if the input has just been pressed, `false` otherwise.
-- @treturn boolean if input has just been pressed on at least one dimensions
pressed = function(self) pressed = function(self)
return self._state == "pressed" return self._state == "pressed"
end, end,
--- Returns true if the input has just been released. --- Returns `true` if the input has just been released, `false` otherwise.
-- @treturn boolean if input has just been released on at least one dimensions
released = function(self) released = function(self)
return self._state == "released" return self._state == "released"
end, end,
--- Returns the current value of the input. --- Returns the current value of the input.
-- If dimension > 1, this will return several values, one per dimension.
-- @treturn number,... current value of the input for every dimension
value = function(self) value = function(self)
return self._value return unpack(self._value)
end, end,
--- Returns the delta value of the input since the last call to `update`. --- Returns the delta value of the input since the last call to `update`.
-- If dimension > 1, this will return several values, one per dimension.
-- @treturn number,... delta of the input for every dimension
delta = function(self) delta = function(self)
return self._value - self._prevValue for i=1, self._dimension do
end, tmp[i] = self._value[i] - self._prevValue[i]
--- If there is a horizontal and vertical children inputs, this returns the horizontal value and the vertical value.
-- Typically used for movement/axes pairs (e.g. to get x,y of a stick or directional pad).
pointer = function(self)
return self.horizontal:value(), self.vertical:value()
end,
--- Same as `pointer`, but normalize the returned vector, i.e. "clamp" the returned x,y coordinates into a circle of radius 1.
-- Typically used to avoid faster movement on diagonals
-- (as if both horizontal and vertical values are 1, the pointer vector has √2 magnitude, higher than the 1 magnitude of a purely vertical or horizontal movement).
clamped = function(self)
local x, y = self:pointer()
local mag = x*x + y*y
if mag > 1 then
local d = sqrt(mag)
return x/d, y/d
else
return x, y
end end
return unpack(tmp, 1, self._dimension)
end, end,
-- Update the state of the input: called at least on every input value change and on :update(). -- Update the state of the input: called at least on every input value change and on :update().
-- new: new value of the input if it has changed (number, can be anything, but typically in [0-1]) (optional) -- new: new value of the input if it has changed (list of numbers of size config.dimension, can be anything, but typically in [0-1]) (optional)
_update = function(self, new) _update = function(self, new)
if self.grabbed then if self.grabbed then
self.grabbed:_update(new) -- pass onto grabber self.grabbed:_update(new) -- pass onto grabber
@ -453,27 +706,36 @@ input_mt = {
new = new or self._value new = new or self._value
local old = self._value local old = self._value
self._value = new self._value = new
-- update state and emit events -- compute delta (in tmp)
local delta = new - old for i=1, self._dimension do
if delta ~= 0 then tmp[self._dimension + i] = new[i] - old[i]
self.event:emit("moved", new, delta) tmp[i] = new[i]
end end
if abs(new) >= threshold then -- update state and emit events
if abs(old) < threshold then for i=self._dimension, self._dimension * 2 do
if tmp[i] ~= 0 then
self.event:emit("moved", unpack(tmp, 1, self._dimension * 2))
break
end
end
for i=1, self._dimension do
if abs(new[i]) >= threshold then
if abs(old[i]) < threshold then
self._state = "pressed" self._state = "pressed"
self.event:emit("pressed") self.event:emit("pressed", i, unpack(tmp, 1, self._dimension * 2))
else else
self._state = "down" self._state = "down"
end end
else else
if abs(old) >= threshold then if abs(old[i]) >= threshold then
self._state = "released" self._state = "released"
self.event:emit("released") self.event:emit("released", i, unpack(tmp, 1, self._dimension * 2))
else else
self._state = "none" self._state = "none"
end end
end end
end end
end
end, end,
-- Returns the deadzone of the input. -- Returns the deadzone of the input.
_deadzone = function(self) _deadzone = function(self)
@ -486,4 +748,81 @@ input_mt = {
} }
input_mt.__index = input_mt input_mt.__index = input_mt
--- Input sources.
-- Input sources are the initial source of input data. They are identified by a Lua identifier name.
-- See [expressions](#Input_expressions) on how to use them in expressions.
--
-- Input sources are provided for common input methods (keyboard, mouse, gamepad) by default; see below for a list of built-in input sources.
--
-- Additionally, you can define your own input sources, by emitting events in the SignalRegistry returned by `uqt.input.event`.
-- See the file `input/event.lua` to see a description of the events you will need to emit. The file also contains the definition
-- of the built-in input sources that you may use as an example.
--
-- @section Sources
--- Keyboard input: 1 if the key X is down, 0 otherwise.
-- X can be any of LÖVE's [KeyConstant](https://love2d.org/wiki/KeyConstant).
-- @field key.X
--- Keyboard input: 1 if the key with scancode X is down, 0 otherwise.
-- X can be any of LÖVE's [Scancode](https://love2d.org/wiki/Scancode).
-- @field scancode.X
--- Text input: 1 if the text X was entered, 0 otherwise.
-- X can be any text.
-- @field text.X
--- Mouse input: 1 if the mouse button is down, 0 otherwise.
-- N is either 1 for the primary mouse button, 2 for secondary or 3 for middle button.
-- @field mouse.N
--- Mouse input: X position of the mouse cursor.
-- @field mouse.x
--- Mouse input: Y position of the mouse cursor.
-- @field mouse.y
--- Mouse input: latest X movement of the mouse cursor.
--
-- `mouse.dx.p` and `mouse.dx.n` will respectively only report movement in the positive or negative direction and return absolute value.
-- @field mouse.dx
--- Mouse input: latest Y movement of the mouse cursor.
--
-- `mouse.dy.p` and `mouse.dy.n` will respectively only report movement in the positive or negative direction and return absolute value.
-- @field mouse.dy
--- Mouse input: latest X movement of the mouse wheel.
--
-- `wheel.dx.p` and `wheel.dx.n` will respectively only report movement in the positive or negative direction and return absolute value.
-- @field wheel.dx
--- Mouse input: latest Y movement of the mouse wheel.
--
-- `wheel.dy.p` and `wheel.dy.n` will respectively only report movement in the positive or negative direction and return absolute value.
-- @field wheel.dy
--- Gamepad input: 1 if the button X is down, 0 otherwise.
-- X can be any of LÖVE's [GamepadButton](https://love2d.org/wiki/GamepadButton).
-- @field button.X
--- Gamepad input: current value of the gamepad axis (between -1 and 1).
-- X can be any of LÖVE's [GamepadAxis](https://love2d.org/wiki/GamepadAxis).
--
-- `axis.X.p` and `axis.X.n` will respectively only report movement in the positive or negative direction and return absolute value.
-- @field axis.X
--- On new frame: current delta time value since last frame. Updated on each call to `love.update`.
-- Note that if this input source is present in an expression, the other input sources in the same expression will be set as passive by default.
-- @field dt
--- Children inputs: current value of a child input of the current input.
-- For child input of dimension 1.
-- Replace X with the name of the child input.
--
-- If the child input is of dimension at least 2, instead use `child.X[N]`, which gives the
-- current value of the Nth dimension of a child input of the current input.
-- Replace X with the name of the child input, and N with the index of the dimension you want.
-- @field child.X
return make_input return make_input

View file

@ -43,7 +43,7 @@
-- TODO: handle nineSliceBorders when drawing entities -- TODO: handle nineSliceBorders when drawing entities
-- TODO: Once stable in LDtk: handle parallax when drawing layers, multiple worlds per file -- TODO: Once stable in LDtk: handle parallax when drawing layers, multiple worlds per file
--- LÖVE wrappers/placeholder -- LÖVE wrappers/placeholder
let lg = (love or {}).graphics let lg = (love or {}).graphics
let newQuad let newQuad
if lg then if lg then
@ -56,7 +56,7 @@ end
let cache let cache
--- json helpers -- json helpers
let json_decode let json_decode
do do
let r, json = pcall(require, "json") let r, json = pcall(require, "json")
@ -70,7 +70,7 @@ let readJson = (file)
return t return t
end end
--- color helpers -- color helpers
let parseColor = (str) let parseColor = (str)
local r, g, b = str:match("^#(..)(..)(..)") local r, g, b = str:match("^#(..)(..)(..)")
r, g, b = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16) r, g, b = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16)