CTRL

CTRL - Control & Trigger Relays Library - LÖVE INPUT

Simple for basic usage and at the same time robust and advanced for extended use, this library provides all necessary facilities to handle user input, and with enough skill, can be extended to handle even the input devices not native supported by the framework.

Basic Use

To get started, an instance of CTRL class needs to be created (so if for whatever reason you need multiple different CTRL instances - you can do that), and event handler callback functions need to be defined. They largely work the same way as normal LÖVE input callbacks, except CTRL strips input events of all information that's not relevant, and refines the rest to the most usable state. Then, some input bindings need to be made. And finally, CTRL instance needs to be hooked up to LÖVE input events.

Example Code

local ctrl = require ( 'ctrl' ) ( )
function ctrl:inputpressed ( name, value ) print ( "pressed", name, value ) end
function ctrl:inputreleased ( name, value ) print ( "released", name, value ) end
function ctrl:inputmoved ( name, value ) print ( "moved", name, value ) end
ctrl:bind ( "fire", { "keyboard", "space" } )
ctrl:bind ( "fire", { "joystick", "default", "button", 1 } )
ctrl:bind ( "fire", { "mouse", "left" } )
ctrl:hookup ( )

List of Valid Inputs

  • "keyboard"
    • all standard LÖVE keyboard constants
  • "mouse"
    • "x", "y", "wheelx", "wheely", "left", "right", "middle"
    • 4, 5, 6, etc.
  • "gamepad"
    • "default" or any existing mapping for a Joystick
      • "axis"
        • "leftx", "rightx", "lefty", "righty", "triggerleft", "triggerright"
      • "button"
        • "a", "b", "x", "y", "back", "guide", "start", "leftstick", "rightstick", "leftshoulder", "rightshoulder"
      • "hat"
        • "up", "down", "left", "right"
  • "joystick"
    • "default" or any existing device mapping for a Joystick
      • "axis"
        • 1, 2, 3, etc.
      • "button"
        • 1, 2, 3, etc.
      • "hat"
        • 1, 2, 3, etc.
          • "up", "down", "left", "right"

By default all joysticks and gamepads map to "default", this is what you want most of the time.

Advanced

CTRL includes substantial assortment of features (hence its hefty size) on top of its basic functionality.

Automatic Input Grab

Most useful for input settings screens. Using grab function CTRL can be set to automatically bind whichever input user had pressed to a select action, and it can be set to filter out only desired inputs out of all incoming signals. All three arguments are optional, but either callback or autobind name should be present (otherwise it will not do anything useful).

local bindname = "foo"
callback = function ( input )
	--cancel input if user have pressed "escape", otherwise bind
	if input[ 1 ] == "keyboard" and input[ 2 ] == "escape" then ctrl:grab ( )
	else ctrl:bind ( bindname, input ) end
end
ctrl:grab ( callback )


--set CTRL to autobind any next input to "foo" action, and only allow input from keyboard and gamepads buttons
ctrl:grab ( "foo", { { "keyboard" }, { "gamepad", nil, "button" } } )

It would make sense to save acquired input bindings. CTRL has facilities for that, the saveData and loadData functions. You can pass a filename in it and it will load/save data to it. If you pass nothing to saveData, it returns saved data string. You can pass such string into loadData instead of filename.

Custom Bindings

One of the most important bits of functionality is binding customization. This is achieved by passing third argument into bind function, the bind options table. It can contain mapper table that contains information about raw value re-mapping function attached to that binding, filter table that contains similar information about real-time final value mapping from raw values, and events table that has a list of events that this binding shall generate.


Mapper table contains two named values:

  • func: function that will do the mapping
  • args (optional): table of additional arguments that will be passed into that function

The mapper function should be either string name of a registered mapper function (see below) or a function value. When it's called, it's passed raw, last mapped, args, ... arguments. First argument is a raw value from the input device. Second is the value that it had output the last time it was called. Third is the args optional table that can contain anything or not exist at all. Finally, a vararg is passed. It contains all, if any, additional raw event parameters passed into general event handler function. For standard LÖVE events it's empty except for mouse events, then it's dx dy and isTouch from the mouse callbacks. You can define custom raw input events that pass variety of raw input information through this.

local fooMapper = function ( raw, last, args, ... )
	return raw * args.foo
end
ctrl:bind ( "foo", { "keyboard", "f" }, { mapper = { func = fooMapper, args = { foo = 1 } } } )


Filter table contains two named values:

  • func: function that will do the mapping
  • args (optional): table of additional arguments that will be passed into that function

As with mapper, filter function should be either string name of a registered filter function (see below) or a function value. When it's called, it's passed dt, mapped or raw, last filtered, args arguments. First argument is dt from the update function. Second is the current mapped (or raw, if no mapper set) value. Third is the value that it had output the last time it was called. Fourth is the args optional table that can contain anything or not exist at all.

local fooFilter = function ( dt, raw, last, args )
	return last + raw * dt
end
ctrl:bind ( "foo", { "keyboard", "f" }, { filter = { func = fooFilter } } )


Events table contains any list of event tables. Each of those tables contains three named values:

  • trigger: function that will decide whether or not an input event shall be dispatched
  • handler: callback function that will be called to handle the input event
  • args (optional): table of additional arguments that will be passed into trigger function

The trigger function should be either string name of a registered trigger function (see below) or a function value. When it's called, it's passed current, previous, args arguments. First argument is the current value. Second is the previous value. Third is the args optional table that can contain anything or not exist at all. Handler function should be either string name of existing event handler function (simply defined as normal) or a function value. When it's called, it's passed ctrl, name, value arguments. First argument is the ctrl instance that triggered this event. Second is the bound action name that triggered this event. Third is the current value.

local fooTrigger = function ( curr, last, args )
	return curr > last
end
local inputFooBar = function ( ctrl, name, value )
	print ( "foobar", name, value )
end
ctrl:bind ( "foo", { "keyboard", "f" }, { events = { foobar = { trigger = fooTrigger, handler = inputFooBar } } } )


Mapper, filter and trigger functions can be "registered" and be referenced by their string name in the binding code. While not immediately beneficial, it allows the data saver to store your bindings exactly, without losing any bits of data: your attached function code cannot be saved to a file, but a string name can be. The event handler callbacks do not need to be explicitly registered, you simply define them on a CTRL instance the same way you define LÖVE input callbacks. The reason to do it that way instead of passing function itself into the binding options is the same as with mappers, filters and triggers.

ctrl:addMapperFunction ( "fooMapper", fooMapper )
ctrl:addFilterFunction ( "fooFilter", fooFilter )
ctrl:addTriggerFunction ( "fooTrigger", fooTrigger )
function ctrl:inputFooBar ( name, value )
	print ( "foobar", name, value )
end

CTRL already includes by default a number of mappers, filters and triggers:

  • deadzone: clamps lower region of analog input to 0 (args = { deadzone = 0.1 })
  • remap: maps entire analog input region to a different output region (args = { rawmin = 0, rawmax = 1, mapmin = 0, mapmax = 1, min = 0, max = 1 })


  • smooth: smoothly moves output value towards raw value (args = { speed = 1 })
  • ramp: moves output value up or down depending on raw value (args = { speed = 1, min = 0, max = 1 })


  • pressed: triggered when value drops under 0.25
  • released: triggered when value raises over 0.75
  • moved: triggered when value changes by any amount

Gamepad Mapping

It works the same way as LÖVE's gamepadMapping functionality, except it accepts CTRL-fashion input table, so you can set gamepad mapping using grab function.

local joystick = love.joystick.getJoysticks[ 1 ]
local inputtype = "start"
local callback = function ( input )
	ctrl:setGamepadMapping ( joystick, inputtype, input )
end
--start grab, filter in only joystick buttons
ctrl:grab ( callback, { "joystick", nil, "button" } )

Custom Input Method

This allows to use custom input devices as well as purely virtual devices such as virtual keyboard and virtual joystick commonly found on mobile. There are two ways of doing it. First is piping custom input device events into standard events. Then custom device will be indistinguishable from normal device which it shadows. Second is defining new input type and manually calling general input handler function.

ctrl:addInputs ( { "virtualkeyboard", "A" }, { "w", "s", "a", "d" } )
ctrl:addInputs ( { "virtualkeyboard", "B" }, { "u", "j", "h", "k" } )

ctrl:bind ( "foo", { "virtualkeyboard", "A", "w" } )
ctrl:bind ( "bar", { "virtualkeyboard", "B", "u" } )
 . . .
function virtualkeyboard:pressed ( section, key )
	ctrl:handleUpdate ( { "virtualkeyboard", section, key }, 1 )
end
function virtualkeyboard:released ( section, key )
	ctrl:handleUpdate ( { "virtualkeyboard", section, key }, 0 )
end

Device Mapping

If input type cannot be uniquely and persistently identified (like keyboards and mice), then its ID needs to be produced in run-time. There are many ways to do it and yet none are silver bullet be all end all type. This is why CTRL opts out of automatically trying to identify devices, instead defaulting to mapping them all to "default", so you will need to implement your own ID generator function. Simply using defaults is what you need most of the time, but some scenarios, such as local multiplayer, demand differentiation between similar or even identical devices. One common strategy is to simply assign each gamepad their index number, and refer to them by that number. Another strategy involves acquiring GUID or literal name of the gamepad and, if it's not unique, adding a differentiating digit to it. Once you're done producing ID, you can assign joystick to this ID using mapDevice function:

local id = produceId ( joystick )
ctrl:mapDevice ( joystick, id )

Reference Manual

ctrl ( ), ctrl.new ( )
	creates and returns new input handler instance
ctrl:hookup ( )
	hooks up input handlers to leech off of default löve input callbacks
ctrl:bind ( name, input, [options] )
	binds specified input to an input name
	* name - input name to bind to
	* input - ctrl input address to bind
	* options (optional) - options table, can contain following fields:
		* mapper (optional) - table describing a raw value mapper
			* func - callback mapper function, returns mapped raw value
			* args (optional) - callback function arguments table
		* filter (optional) - table describing a value filter
			* func - callback filter function, returns filtered value
			* args (optional) - callback function arguments table
		* events (optional) - table containing list of events description tables. Each table contains:
			* trigger - callback trigger function, returns true if event should be triggered
			* handler - callback handler function, called if event is triggered
			* args (optional) - callback trigger arguments table
ctrl:unbind ( [name], [input] )
	unbinds specified inputs
	* name (optional) - input name to unbind
	* input (optional) - ctrl input address to unbind
	if called without arguments, unbinds everything
	if called without input address, unbinds everything bound to a specific name
	if called without name, unbinds everything bound to a specific address
	input address can be partial address, then it acts like a filter, unbinds everything that matches
ctrl:grab ( [callback], [autobind], [filters] )
	starts tracking input devices for any activity, grabs the first one that changes
	* callback (optional) - function that will be called when activity is detected
		shall return true if it should ignore current input and track the next one
		called with the following arguments:
			* input - ctrl input address table
			* name - the autobind argument unmodified
	* autobind (optional) - string input name to which it would automatically bind anything it finds
	* filters (optional) - list of ctrl input addresses that it will match against during tracking
ctrl:getBindings ( [name], [input] )
	finds and returns list of all input addresses bound to a specified input name
	* name (optional) - name to look bindings for
	* input (optional) - ctrl input path to look in
	if name is not passed, finds all bindings
	input can be partial path, then it acts as a filter
	list entries have the following format:
		* name - string name of the input
		* input - ctrl input address table
		* options - table of input options (mapper, filter, events)
ctrl:isUp ( name )
ctrl:wasUp ( name )
	returns true if value of an input is under 0.25
	* name - input name to look for
ctrl:isDown ( name )
ctrl:wasDown ( name )
	returns true if value of an input is above 0.75
	* name - input name to look for
ctrl:getValue ( name )
ctrl:getLastValue ( name )
	returns value of an input
	* name - input name to look for
ctrl:resetValues ( )
	resets all values to 0
	focusing out and in a window combined with some ways of input handling can cause input glitches,
	resetting all values solves that problem
ctrl:mapDevice ( device, value )
	maps userdata or table device to a string, number or boolean value
	* device - the input device userdata, table or any other value by which it's referenced
	* value - string, number or boolean value by which its input would be addressed
	should be used with joysticks and gamepads and other devices without persistent ID
ctrl:getMapping ( device )
	returns previously mapped value for this device, defaults to "default"
	* device - the input device to look for
ctrl:setGamepadMapping ( joy, control, input )
	mirros love.joystick.setGamepadMapping, but provides seamless integraiton with the library
	* joy - löve Joystick object to map
	* control - löve Gamepad control (button, hat, axis)
	* input - ctrl joystick input path table
ctrl:getGamepadMapping ( joy, control )
	mirros love.joystick.getGamepadMapping, but provides seamless integraiton with the library
	* joy - löve Joystick object to get mapping from
	* control - löve Gamepad control (button, hat, axis)
	returns ctrl joystick input address table for selected Gamepad control
ctrl:addInputs ( input, new )
	adds new values to the valid inputs list
	* input - ctrl input path to add to
	* new - list of additional valid inputs, can contain a metatable
ctrl:handleUpdate ( input, raw, ... )
	processes input event and dispatches callbacks
	* input - ctrl input address table
	* raw - raw value
	* vararg - any additional values, will be passed into raw mapper function
ctrl:addTriggerFunction ( name, func )
	adds new trigger function to the list
	* name - string name of new event trigger function
	* func - callback function, shall return true if event should be triggered
		called with the following arguments:
			* current value
			* previous value
			* function arguments table
ctrl:addFilterFunction ( name, func )
	adds new filter function to the list
	* name - string name of new value filter function
	* func - callback function, shall return filtered value
		called with the following arguments:
			* time elapsed since last call
			* raw input value
			* previous filtered value
			* function arguments table
ctrl:addMapperFunction ( name, func )
	adds new raw value mapper function to the list
	* name - string name of new raw value mapper function
	* func - callback function, shall return mapped raw value
		called with the following arguments:
			* raw value
			* previous mapped value
			* function arguments table
			* vararg passed into input event handler function
ctrl:saveData ( [filename] )
	saves all internal data to a file, returns data string if file is not provided
	* filename (optional) - filename to store data in
ctrl:loadData ( filename )
	loads data from a file or a string
	* filename - filename to load data from, or data string
ctrl:handleUpdate ( dt )
ctrl:handleKeypressed ( key, code, repeated )
ctrl:handleKeyreleased ( key, code, repeated )
ctrl:handleMousepressed ( x, y, button, touch )
ctrl:handleMousereleased ( x, y, button, touch )
ctrl:handleMousemoved ( x, y, dx, dy, touch )
ctrl:handleWheelmoved ( x, y )
ctrl:handleGamepadaxis ( joy, axis, value )
ctrl:handleGamepadpressed ( joy, button )
ctrl:handleGamepadreleased ( joy, button )
ctrl:handleJoystickaxis ( joy, axis, value )
ctrl:handleJoystickhat ( joy, hat, dir )
ctrl:handleJoystickpressed ( joy, button )
ctrl:handleJoystickreleased ( joy, button )
ctrl:handleJoystickadded ( joy )
ctrl:handleJoystickremoved ( joy )
	LÖVE input event handlers. To be called manually as appropriate if CTRL instance is not "hooked" up.

Links

Other Languages