├── README.md ├── mac-experimental-fullscreen └── conf.lua ├── FilesystemSymlinks └── symlink.lua ├── OpenSaveFolder └── opensavefolder.lua ├── MessageBox └── messagebox.lua ├── SoundData-FFI └── sounddata-ffi.lua ├── ImageData-FFI └── imagedata-ffi.lua └── Touch └── touch.lua /README.md: -------------------------------------------------------------------------------- 1 | love-snippets 2 | ============= 3 | 4 | Snippets of Lua code useful in the LÖVE game framework. Most are hacks which use LuaJIT's FFI. 5 | -------------------------------------------------------------------------------- /mac-experimental-fullscreen/conf.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This software is in the public domain. Where that dedication is not recognized, 3 | you are granted a perpetual, irrevokable license to copy and modify this file 4 | as you see fit. 5 | ]] 6 | 7 | 8 | --[[ 9 | LÖVE 0.9.0 for OS X uses a development version of SDL in between 2.0.1 and 2.0.2. 10 | It includes experimental support for Mac OS 10.7's Spaces-aware fullscreen 11 | (including the button on the top-right of the window.) This snippet enables the 12 | experimental fullscreen support via LuaJIT's FFI. 13 | 14 | LÖVE 0.9.1 for OS X uses SDL 2.0.2, which has a much more robust version of this 15 | enabled by default, so this snippet only works in 0.9.0. 16 | 17 | This *needs* to be in conf.lua because it has to be executed before the window 18 | module is loaded. 19 | ]] 20 | 21 | 22 | -- Check for 0.9.0+, LuaJIT, and OS X. 23 | if (love._version_minor or 7) >= 9 and jit and jit.os == "OSX" then 24 | local ffi = require("ffi") 25 | 26 | ffi.cdef "int SDL_SetHint(const char *name, const char *value);" 27 | ffi.C.SDL_SetHint("SDL_VIDEO_FULLSCREEN_SPACES", "1") 28 | end 29 | 30 | --[[ 31 | -- love.conf goes here: 32 | function love.conf(t) 33 | -- etc. 34 | end 35 | ]] 36 | -------------------------------------------------------------------------------- /FilesystemSymlinks/symlink.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This software is in the public domain. Where that dedication is not recognized, 3 | you are granted a perpetual, irrevokable license to copy and modify this file 4 | as you see fit. 5 | ]] 6 | 7 | --[[ 8 | Functions for enabling the usage of symlinks in love.filesystem. 9 | ]] 10 | 11 | assert(jit, "LuaJIT is required") 12 | 13 | local ffi = require("ffi") 14 | 15 | -- Windows... 16 | local liblove = jit.os == "Windows" and ffi.load("love") or ffi.C 17 | 18 | -- physfs.h 19 | ffi.cdef[[ 20 | void PHYSFS_permitSymbolicLinks(int allow); 21 | int PHYSFS_symbolicLinksPermitted(void); 22 | int PHYSFS_isSymbolicLink(const char *fname); 23 | ]] 24 | 25 | -- Toggles whether symlinks in love.filesystem are enabled. 26 | function EnableSymbolicLinks(enable) 27 | assert(type(enable) == "boolean") 28 | liblove.PHYSFS_permitSymbolicLinks(enable and 1 or 0) 29 | end 30 | 31 | -- Returns true if symlinks in love.filesystem are currently enabled. 32 | function SymbolicLinksEnabled() 33 | return liblove.PHYSFS_symbolicLinksPermitted() ~= 0 34 | end 35 | 36 | -- Returns true if a filepath in love.filesystem is really a symbolic link. 37 | function IsSymbolicLink(filename) 38 | assert(type(filename) == "string") 39 | return liblove.PHYSFS_isSymbolicLink(filename) ~= 0 40 | end 41 | -------------------------------------------------------------------------------- /OpenSaveFolder/opensavefolder.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This software is in the public domain. Where that dedication is not recognized, 3 | you are granted a perpetual, irrevokable license to copy and modify this file 4 | as you see fit. 5 | ]] 6 | 7 | -- Opens the game's save directory (or a subfolder wthin it) in the system's 8 | -- file browser. Written for LÖVE 0.9.0+. 9 | -- LÖVE 0.9.1 includes love.system.openURL which can accomplish this as well. 10 | function OpenSaveFolder(subfolder) 11 | subfolder = subfolder or "" 12 | 13 | -- If we have LÖVE 0.9.1+, we use love.system.openURL. It works better. 14 | if love.system and love.system.openURL then 15 | local url = "file://"..love.filesystem.getSaveDirectory().."/"..subfolder 16 | return love.system.openURL(url) 17 | end 18 | 19 | local osname = love.system.getOS() 20 | local path = love.filesystem.getSaveDirectory().."/"..subfolder 21 | local cmdstr 22 | 23 | if osname == "Windows" then 24 | cmdstr = "Explorer %s" 25 | subfolder = subfolder:gsub("/", "\\") 26 | --hardcoded to fix ISO characters in usernames and made sure release mode doesn't mess anything up -saso 27 | if love.filesystem.isFused() then 28 | path = "%appdata%\\" 29 | else 30 | path = "%appdata%\\LOVE\\" 31 | end 32 | path = path..love.filesystem.getIdentity().."\\"..subfolder 33 | elseif osname == "OS X" then 34 | cmdstr = "open -R \"%s\"" 35 | elseif osname == "Linux" then 36 | cmdstr = "xdg-open \"%s\"" 37 | end 38 | 39 | if cmdstr then 40 | os.execute(cmdstr:format(path)) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /MessageBox/messagebox.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | FFI wrapper for SDL 2.0's simple message box functionality. 3 | Assumes the SDL library's functions are already accessible by the program 4 | (e.g. SDL is dynamically linked to it.) This is the case in LÖVE 0.9.0+. 5 | ]] 6 | 7 | assert(jit, "LuaJIT is required") 8 | 9 | local ffi = require("ffi") 10 | 11 | -- SDL_video.h and SDL_messagebox.h (SDL 2.0.1): 12 | ffi.cdef[[ 13 | typedef struct SDL_Window SDL_Window; 14 | SDL_Window *SDL_GL_GetCurrentWindow(void); 15 | 16 | typedef enum 17 | { 18 | SDL_MESSAGEBOX_ERROR = 0x00000010, 19 | SDL_MESSAGEBOX_WARNING = 0x00000020, 20 | SDL_MESSAGEBOX_INFORMATION = 0x00000040 21 | } SDL_MessageBoxFlags; 22 | 23 | int SDL_ShowSimpleMessageBox(uint32_t flags, const char *title, const char *message, SDL_Window *window); 24 | ]] 25 | 26 | -- Windows... 27 | local sdl = jit.os == "Windows" and ffi.load("SDL2") or ffi.C 28 | 29 | local typeconstants = { 30 | info = ffi.C.SDL_MESSAGEBOX_INFORMATION, 31 | warning = ffi.C.SDL_MESSAGEBOX_WARNING, 32 | error = ffi.C.SDL_MESSAGEBOX_ERROR, 33 | } 34 | 35 | --[[ 36 | Shows a simple message box with a title, some text, and a close button. 37 | NOTE: this function does not return until the user closes the messagebox! 38 | 39 | Arguments: 40 | mtype (string): The type of message box: "info", "warning", or "error". 41 | title (string): The title of the message box. 42 | message (string): The text in the main message area of the message box. 43 | standalone (boolean): Whether the message box is a standalone window or attached to the game's main window. 44 | NOTE: using a standalone message box while in fullscreen can cause a hard lock in Mac OS X! 45 | ]] 46 | function ShowSimpleMessageBox(mtype, title, message, standalone) 47 | local flags = typeconstants[mtype] 48 | 49 | assert(flags, "Invalid message box type") 50 | assert(type(title) == "string", "Invalid message box title type (expecting string)") 51 | assert(type(message) == "string", "Invalid message box message type (expecting string)") 52 | 53 | local window = nil 54 | if not standalone then 55 | window = sdl.SDL_GL_GetCurrentWindow() 56 | end 57 | 58 | local result = sdl.SDL_ShowSimpleMessageBox(flags, title, message, window) 59 | return result >= 0 60 | end 61 | -------------------------------------------------------------------------------- /SoundData-FFI/sounddata-ffi.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This software is in the public domain. Where that dedication is not recognized, 3 | you are granted a perpetual, irrevokable license to copy and modify this file 4 | as you see fit. 5 | ]] 6 | 7 | --[[ 8 | Replaces several SoundData methods with FFI implementations. This can result 9 | in a performance increase of up to 10x when calling the methods, especially 10 | in simple code which had to fall back to interpreted mode specifically because 11 | the normal SoundData methods couldn't be compiled by the JIT. 12 | ]] 13 | 14 | --[[ 15 | NOTE: This was written specifically for LÖVE 0.9.0 and 0.9.1. Future versions 16 | of LÖVE may change SoundData (either internally or externally) enough to cause 17 | these replacements to break horribly. 18 | ]] 19 | 20 | assert(love and love.sound, "love.sound is required") 21 | 22 | if type(jit) ~= "table" or not jit.status() then 23 | -- LuaJIT's FFI is *much* slower than LOVE's regular methods when the JIT 24 | -- compiler is disabled. 25 | return 26 | end 27 | 28 | local tonumber, assert = tonumber, assert 29 | 30 | local ffi = require("ffi") 31 | 32 | local datatypes = {ffi.typeof("uint8_t *"), ffi.typeof("int16_t *")} 33 | local typemaxvals = {0x7F, 0x7FFF} 34 | 35 | 36 | local sounddata_mt 37 | if debug then 38 | sounddata_mt = debug.getregistry()["SoundData"] 39 | else 40 | sounddata_mt = getmetatable(love.sound.newSoundData(1)) 41 | end 42 | 43 | local _getBitDepth = sounddata_mt.__index.getBitDepth 44 | local _getSampleCount = sounddata_mt.__index.getSampleCount 45 | local _getChannels = sounddata_mt.__index.getChannels 46 | 47 | -- Holds SoundData objects as keys, and information about the objects as values. 48 | -- Uses weak keys so the SoundData objects can still be GC'd properly. 49 | local sd_registry = {__mode = "k"} 50 | 51 | function sd_registry:__index(sounddata) 52 | local bytedepth = _getBitDepth(sounddata) / 8 53 | local pointer = ffi.cast(datatypes[bytedepth], sounddata:getPointer()) 54 | 55 | local p = { 56 | bytedepth=bytedepth, 57 | pointer=pointer, 58 | size=sounddata:getSize(), 59 | maxvalue = typemaxvals[bytedepth], 60 | samplecount = _getSampleCount(sounddata), 61 | channels = _getChannels(sounddata), 62 | } 63 | 64 | self[sounddata] = p 65 | return p 66 | end 67 | 68 | setmetatable(sd_registry, sd_registry) 69 | 70 | 71 | -- FFI version of SoundData:getSample 72 | local function SoundData_FFI_getSample(sounddata, i) 73 | local p = sd_registry[sounddata] 74 | assert(i >= 0 and i < p.size/p.bytedepth, "Attempt to get out-of-range sample!") 75 | if p.bytedepth == 2 then 76 | -- 16-bit data is stored as signed values internally. 77 | return tonumber(p.pointer[i]) / p.maxvalue 78 | else 79 | -- 8-bit data is stored as unsigned values internally. 80 | return (tonumber(p.pointer[i]) - 128) / 127 81 | end 82 | end 83 | 84 | -- FFI version of SoundData:setSample 85 | local function SoundData_FFI_setSample(sounddata, i, value) 86 | local p = sd_registry[sounddata] 87 | assert(i >= 0 and i < p.size/p.bytedepth, "Attempt to set out-of-range sample!") 88 | if p.bytedepth == 2 then 89 | -- 16-bit data is stored as signed values internally. 90 | p.pointer[i] = value * p.maxvalue 91 | else 92 | -- 8-bit data is stored as unsigned values internally. 93 | p.pointer[i] = (value * 127) + 128 94 | end 95 | end 96 | 97 | -- FFI version of SoundData:getSampleCount 98 | local function SoundData_FFI_getSampleCount(sounddata) 99 | local p = sd_registry[sounddata] 100 | return p.samplecount 101 | end 102 | 103 | -- FFI version of SoundData:getChannels 104 | local function SoundData_FFI_getChannels(sounddata) 105 | local p = sd_registry[sounddata] 106 | return p.channels 107 | end 108 | 109 | -- Overwrite love's functions with the new FFI versions. 110 | sounddata_mt.__index.getSample = SoundData_FFI_getSample 111 | sounddata_mt.__index.setSample = SoundData_FFI_setSample 112 | sounddata_mt.__index.getSampleCount = SoundData_FFI_getSampleCount 113 | sounddata_mt.__index.getChannels = SoundData_FFI_getChannels 114 | 115 | -------------------------------------------------------------------------------- /ImageData-FFI/imagedata-ffi.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This software is in the public domain. Where that dedication is not recognized, 3 | you are granted a perpetual, irrevokable license to copy and modify this file 4 | as you see fit. 5 | ]] 6 | 7 | --[[ 8 | Replaces several ImageData methods with FFI implementations. This can result 9 | in a performance increase of up to 60x when calling the methods, especially 10 | in simple code which had to fall back to interpreted mode specifically because 11 | the normal ImageData methods couldn't be compiled by the JIT. 12 | ]] 13 | 14 | --[[ 15 | NOTE: This was written specifically for LÖVE 0.9.0 and 0.9.1. Future versions 16 | of LÖVE may change ImageData (either internally or externally) enough to cause 17 | these replacements to break horribly. 18 | ]] 19 | 20 | --[[ 21 | Unlike LÖVE's regular ImageData methods, these are *NOT THREAD-SAFE!* 22 | You *need* to do your own synchronization if you want to use ImageData in 23 | threads with these methods. 24 | ]] 25 | 26 | assert(love and love.image, "love.image is required") 27 | 28 | if type(jit) ~= "table" or not jit.status() then 29 | -- LuaJIT's FFI is *much* slower than LOVE's regular methods when the JIT 30 | -- compiler is disabled. 31 | return 32 | end 33 | 34 | local tonumber, assert = tonumber, assert 35 | 36 | local ffi = require("ffi") 37 | 38 | pcall(ffi.cdef, [[ 39 | typedef struct ImageData_Pixel 40 | { 41 | uint8_t r, g, b, a; 42 | } ImageData_Pixel; 43 | ]]) 44 | 45 | local pixelptr = ffi.typeof("ImageData_Pixel *") 46 | 47 | local function inside(x, y, w, h) 48 | return x >= 0 and x < w and y >= 0 and y < h 49 | end 50 | 51 | local imagedata_mt 52 | if debug then 53 | imagedata_mt = debug.getregistry()["ImageData"] 54 | else 55 | imagedata_mt = getmetatable(love.image.newImageData(1,1)) 56 | end 57 | 58 | local _getWidth = imagedata_mt.__index.getWidth 59 | local _getHeight = imagedata_mt.__index.getHeight 60 | local _getDimensions = imagedata_mt.__index.getDimensions 61 | 62 | -- Holds ImageData objects as keys, and information about the objects as values. 63 | -- Uses weak keys so the ImageData objects can still be GC'd properly. 64 | local id_registry = {__mode = "k"} 65 | 66 | function id_registry:__index(imagedata) 67 | local width, height = _getDimensions(imagedata) 68 | local pointer = ffi.cast(pixelptr, imagedata:getPointer()) 69 | local p = {width=width, height=height, pointer=pointer} 70 | self[imagedata] = p 71 | return p 72 | end 73 | 74 | setmetatable(id_registry, id_registry) 75 | 76 | 77 | -- FFI version of ImageData:mapPixel, with no thread-safety. 78 | local function ImageData_FFI_mapPixel(imagedata, func, ix, iy, iw, ih) 79 | local p = id_registry[imagedata] 80 | local idw, idh = p.width, p.height 81 | 82 | ix = ix or 0 83 | iy = iy or 0 84 | iw = iw or idw 85 | ih = ih or idh 86 | 87 | assert(inside(ix, iy, idw, idh) and inside(ix+iw-1, iy+ih-1, idw, idh), "Invalid rectangle dimensions") 88 | 89 | local pixels = p.pointer 90 | 91 | for y=iy, iy+ih-1 do 92 | for x=ix, ix+iw-1 do 93 | local p = pixels[y*idw+x] 94 | local r, g, b, a = func(x, y, tonumber(p.r), tonumber(p.g), tonumber(p.b), tonumber(p.a)) 95 | pixels[y*idw+x].r = r 96 | pixels[y*idw+x].g = g 97 | pixels[y*idw+x].b = b 98 | pixels[y*idw+x].a = a == nil and 255 or a 99 | end 100 | end 101 | end 102 | 103 | -- FFI version of ImageData:getPixel, with no thread-safety. 104 | local function ImageData_FFI_getPixel(imagedata, x, y) 105 | local p = id_registry[imagedata] 106 | assert(inside(x, y, p.width, p.height), "Attempt to get out-of-range pixel!") 107 | 108 | local pixel = p.pointer[y * p.width + x] 109 | return tonumber(pixel.r), tonumber(pixel.g), tonumber(pixel.b), tonumber(pixel.a) 110 | end 111 | 112 | -- FFI version of ImageData:setPixel, with no thread-safety. 113 | local function ImageData_FFI_setPixel(imagedata, x, y, r, g, b, a) 114 | a = a == nil and 255 or a 115 | local p = id_registry[imagedata] 116 | assert(inside(x, y, p.width, p.height), "Attempt to set out-of-range pixel!") 117 | 118 | local pixel = p.pointer[y * p.width + x] 119 | pixel.r = r 120 | pixel.g = g 121 | pixel.b = b 122 | pixel.a = a 123 | end 124 | 125 | -- FFI version of ImageData:getWidth. 126 | local function ImageData_FFI_getWidth(imagedata) 127 | return id_registry[imagedata].width 128 | end 129 | 130 | -- FFI version of ImageData:getHeight. 131 | local function ImageData_FFI_getHeight(imagedata) 132 | return id_registry[imagedata].height 133 | end 134 | 135 | -- FFI version of ImageData:getDimensions. 136 | local function ImageData_FFI_getDimensions(imagedata) 137 | local p = id_registry[imagedata] 138 | return p.width, p.height 139 | end 140 | 141 | 142 | -- Overwrite love's functions with the new FFI versions. 143 | imagedata_mt.__index.mapPixel = ImageData_FFI_mapPixel 144 | imagedata_mt.__index.getPixel = ImageData_FFI_getPixel 145 | imagedata_mt.__index.setPixel = ImageData_FFI_setPixel 146 | imagedata_mt.__index.getWidth = ImageData_FFI_getWidth 147 | imagedata_mt.__index.getHeight = ImageData_FFI_getHeight 148 | imagedata_mt.__index.getDimensions = ImageData_FFI_getDimensions 149 | -------------------------------------------------------------------------------- /Touch/touch.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This software is in the public domain. Where that dedication is not recognized, 3 | you are granted a perpetual, irrevokable license to copy and modify this file 4 | as you see fit. 5 | 6 | 7 | FFI wrapper for SDL's touch input functionality. 8 | Example usage: 9 | 10 | local touch = require("touch") 11 | 12 | -- The ID of a touch press is unique only for the duration of the press. Note 13 | -- that it is not an index. 14 | -- Touch x and y coordinates and pressure values are normalized to [0, 1]. 15 | -- Currently (as of SDL 2.0.3) there is a bug where touch coordinates are not 16 | -- normalized in Linux - they are in the range of [0, windowsize) instead: 17 | -- https://bugzilla.libsdl.org/show_bug.cgi?id=2307 18 | 19 | function touch.pressed(id, x, y, pressure) 20 | print(string.format("new touch press with id %d at (%.3f, %.3f)", id, x, y)) 21 | end 22 | 23 | function touch.moved(id, x, y, pressure) 24 | print(string.format("touch with id %d moved to (%.3f, %.3f)", id, x, y)) 25 | end 26 | 27 | function touch.released(id, x, y, pressure) 28 | print(string.format("touch with id %d released at (%.3f, %.3f)", id, x, y)) 29 | end 30 | 31 | function love.draw() 32 | for i=1, touch.getTouchCount() do 33 | local id, x, y, pressure = touch.getTouch(i) 34 | 35 | local sx, sy = x*love.graphics.getWidth(), y*love.graphics.getHeight() 36 | 37 | love.graphics.point(sx, sy) 38 | love.graphics.print(("id: %d"):format(id), sx, sy + love.graphics.getPointSize()) 39 | end 40 | end 41 | ]] 42 | 43 | assert(jit, "LuaJIT is required") 44 | 45 | local tonumber = tonumber 46 | local ffi = require("ffi") 47 | 48 | local touch = {} 49 | 50 | ffi.cdef[[ 51 | /* SDL_touch.h */ 52 | 53 | typedef int64_t SDL_TouchID; 54 | typedef int64_t SDL_FingerID; 55 | 56 | typedef struct SDL_Finger 57 | { 58 | SDL_FingerID id; 59 | float x; 60 | float y; 61 | float pressure; 62 | } SDL_Finger; 63 | 64 | int SDL_GetNumTouchDevices(void); 65 | SDL_TouchID SDL_GetTouchDevice(int index); 66 | int SDL_GetNumTouchFingers(SDL_TouchID touchID); 67 | SDL_Finger *SDL_GetTouchFinger(SDL_TouchID touchID, int index); 68 | 69 | 70 | /* SDL_events.h */ 71 | 72 | /* The real enum has far more values, but we only need FingerEvent types. */ 73 | typedef enum 74 | { 75 | SDL_FINGERDOWN = 0x700, 76 | SDL_FINGERUP, 77 | SDL_FINGERMOTION 78 | } SDL_FingerEventType; 79 | 80 | typedef struct SDL_TouchFingerEvent 81 | { 82 | uint32_t type; 83 | uint32_t timestamp; 84 | SDL_TouchID touchId; 85 | SDL_FingerID fingerId; 86 | float x; 87 | float y; 88 | float dx; 89 | float dy; 90 | float pressure; 91 | } SDL_TouchFingerEvent; 92 | 93 | /* The real SDL_Event union has far more event structs, but we only need the 94 | * SDL_TouchFingerEvent. */ 95 | typedef union SDL_Event 96 | { 97 | uint32_t type; 98 | SDL_TouchFingerEvent tfinger; 99 | uint8_t padding[56]; 100 | } SDL_Event; 101 | 102 | typedef int (*SDL_EventFilter) (void *userdata, SDL_Event *event); 103 | void SDL_AddEventWatch(SDL_EventFilter filter, void *userdata); 104 | ]] 105 | 106 | -- Windows... 107 | local sdl = ffi.os == "Windows" and ffi.load("SDL2") or ffi.C 108 | 109 | local eventnames = { 110 | [tonumber(ffi.C.SDL_FINGERDOWN)] = "pressed", 111 | [tonumber(ffi.C.SDL_FINGERUP)] = "released", 112 | [tonumber(ffi.C.SDL_FINGERMOTION)] = "moved", 113 | } 114 | 115 | local function EventFilterFunc(userdata, e) 116 | local eventname = eventnames[tonumber(e.type)] 117 | if eventname and type(touch[eventname]) == "function" then 118 | local id = tonumber(e.tfinger.fingerId) 119 | local x = tonumber(e.tfinger.x) 120 | local y = tonumber(e.tfinger.y) 121 | local pressure = tonumber(e.tfinger.pressure) 122 | touch[eventname](id, x, y, pressure) 123 | end 124 | return 1 125 | end 126 | 127 | sdl.SDL_AddEventWatch(EventFilterFunc, nil) 128 | 129 | 130 | -- Gets the number of currently active touches. 131 | function touch.getTouchCount() 132 | local count = 0 133 | for i=1, sdl.SDL_GetNumTouchDevices() do 134 | count = count + sdl.SDL_GetNumTouchFingers(sdl.SDL_GetTouchDevice(i-1)) 135 | end 136 | return count 137 | end 138 | 139 | -- Gets information about a currently active touch. Note that the index is *not stable*. 140 | -- Returns an ID, normalized x and y coordinates, and the pressure of the touch. 141 | -- The ID is only guaranteed to be unique for the duration of the touch press. 142 | function touch.getTouch(index) 143 | assert(type(index) == "number") 144 | index = index - 1 145 | 146 | local deviceID 147 | local touchcount = 0 148 | local fingerindex = -1 149 | 150 | -- Find the device and finger index from the given external index. 151 | for i=1, sdl.SDL_GetNumTouchDevices() do 152 | deviceID = sdl.SDL_GetTouchDevice(i-1) 153 | local fingercount = sdl.SDL_GetNumTouchFingers(deviceID) 154 | 155 | if index < touchcount + fingercount then 156 | fingerindex = index - touchcount 157 | break 158 | end 159 | 160 | touchcount = touchcount + fingercount 161 | end 162 | 163 | if fingerindex < 0 then 164 | return error("Invalid touch index", 2) 165 | end 166 | 167 | local finger = sdl.SDL_GetTouchFinger(deviceID, fingerindex) 168 | 169 | if finger == nil then 170 | return error("Cannot get touch info", 2) 171 | end 172 | 173 | return tonumber(finger.id), tonumber(finger.x), tonumber(finger.y), tonumber(finger.pressure) 174 | end 175 | 176 | return touch 177 | --------------------------------------------------------------------------------