-
-
Save SkyTheCoder/284cdc93b17473e73c17 to your computer and use it in GitHub Desktop.
A library for handling touch taps, swipes, and long presses. No setup required. Just paste as a new project and add it as a dependency. Documentation is at the top of Main.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# Library | |
Flickable | |
= {angleOffset = 0, constant = false, useCorners = true} | |
local touches = {} | |
local debugtouch = {} | |
local sign = function(n) | |
return n / math.abs(n) | |
end | |
local inRange = function(x, y, s, px, py) | |
--return math.abs(x - px) <= w / 2 and math.abs(y - py) <= h / 2 | |
return vec2(x, y):dist(vec2(px, py)) <= s | |
end | |
function getDebugFromID(id) | |
return debugtouch[id] | |
end | |
function getDebugFromTouch(touch) | |
return debugtouch[touch.id] | |
end | |
function getTouchFromID(id) | |
return touches[id] | |
end | |
function getTouchFromDebug(deb) | |
return touches[deb.id] | |
end | |
function getNameFromMotion(x, y, human) | |
local ret = "" | |
if y > 7.5 then | |
ret = ret .. "up" | |
end | |
if y < -7.5 then | |
ret = ret .. "down" | |
end | |
if x > 7.5 then | |
if human and ret ~= "" then | |
ret = ret .. "-" | |
end | |
ret = ret .. "right" | |
end | |
if x < -7.5 then | |
if human and ret ~= "" then | |
ret = ret .. "-" | |
end | |
ret = ret .. "left" | |
end | |
return ret | |
end | |
function getNameFromAngle(angle, human) | |
local ret = "" | |
local a = angle + Flickable.angleOffset | |
local tolerance = 45 | |
if Flickable.useCorners then | |
tolerance = 67.5 | |
end | |
if a > 90 - tolerance and a < 90 + tolerance then | |
ret = ret .. "up" | |
end | |
if a < -90 + tolerance and a > -90 - tolerance then | |
ret = ret .. "down" | |
end | |
if a < tolerance and a > -tolerance then | |
if human and ret ~= "" then | |
ret = ret .. "-" | |
end | |
ret = ret .. "right" | |
end | |
if a > 180 - tolerance or a < -180 + tolerance then | |
if human and ret ~= "" then | |
ret = ret .. "-" | |
end | |
ret = ret .. "left" | |
end | |
return ret | |
end | |
function getBoolFromMotion(x, y) | |
return {up = y > 7.5, down = y < -7.5, right = x > 7.5, left = x < -7.5} | |
end | |
function getBoolFromAngle(angle) | |
local a = angle + Flickable.angleOffset | |
local tolerance = 45 | |
if Flickable.useCorners then | |
tolerance = 67.5 | |
end | |
return {up = a > 90 - tolerance and a < 90 + tolerance, down = a < -90 + tolerance and a > -90 - tolerance, right = a < tolerance and a > -tolerance, left = a > 180 - tolerance or a < -180 + tolerance} | |
end | |
local nextf = function(f) | |
tween.delay(0, function() | |
pcall(f) | |
end) | |
end | |
local handle = function(touch) | |
touches[touch.id] = touch | |
if touch.state == BEGAN and debugtouch[touch.id] == nil then | |
debugtouch[touch.id] = {id = touch.id, start = ElapsedTime, startX = touch.x, startY = touch.y, motX = 0, motY = 0, long = false} | |
end | |
local deb = debugtouch[touch.id] | |
local still = inRange(deb.startX, deb.startY, 20, touch.x, touch.y) and math.abs(deb.motX) <= 15 and math.abs(deb.motY) <= 15 | |
if debugtouch[touch.id] ~= nil then | |
if sign(touch.deltaX) ~= sign(deb.motX) then | |
debugtouch[touch.id].motX = debugtouch[touch.id].motX * 0.2 + 0.8 * touch.deltaX | |
else | |
debugtouch[touch.id].motX = debugtouch[touch.id].motX * 0.9 + 0.1 * touch.deltaX | |
end | |
if sign(touch.deltaY) ~= sign(deb.motY) then | |
debugtouch[touch.id].motY = debugtouch[touch.id].motY * 0.2 + 0.8 * touch.deltaY | |
else | |
debugtouch[touch.id].motY = debugtouch[touch.id].motY * 0.9 + 0.1 * touch.deltaY | |
end | |
local rad = math.atan2(deb.motY, deb.motX) | |
local deg = math.deg(rad) | |
local offX, offY = math.cos(rad), math.sin(rad) | |
debugtouch[touch.id].rad = rad | |
debugtouch[touch.id].deg = deg | |
debugtouch[touch.id].direction = vec2(offX, offY) | |
still = inRange(deb.startX, deb.startY, 20, touch.x, touch.y) and math.abs(deb.motX) <= 15 and math.abs(deb.motY) <= 15 | |
if ElapsedTime - deb.start >= 0.8 then | |
if still and not deb.long then | |
if type(longPressed) == "function" then | |
longPressed(touch, deb) | |
debugtouch[touch.id].long = true | |
end | |
end | |
end | |
if deb.long then | |
if type(longPressing) == "function" then | |
longPressing(touch, deb) | |
end | |
end | |
end | |
if touch.state == ENDED or touch.state == CANCELLED then | |
if still then | |
if ElapsedTime - deb.start <= 0.3 then | |
if type(tapped) == "function" then | |
tapped(touch, deb) | |
end | |
end | |
if ElapsedTime - deb.start >= 0.8 and not deb.long then | |
if type(longPressed) == "function" then | |
longPressed(touch, deb) | |
debugtouch[touch.id].long = true | |
end | |
end | |
end | |
if deb.long then | |
if type(longPressing) == "function" then | |
longPressing(touch, deb) | |
end | |
end | |
if ElapsedTime - deb.start <= 0.5 then | |
if (not inRange(deb.startX, deb.startY, 10, touch.x, touch.y)) and (not (math.abs(deb.motX) + math.abs(deb.motY) <= 10)) then | |
if type(swiped) == "function" then | |
swiped({id = touch.id, x = touch.x, y = touch.y, startX = deb.startX, startY = deb.startY, motX = deb.motX, motY = deb.motY, rad = deb.rad, deg = deb.deg, direction = deb.direction }, deb) | |
end | |
end | |
end | |
nextf(function() | |
touches[touch.id] = nil | |
debugtouch[touch.id] = nil | |
end) | |
end | |
if type(debtouched) == "function" then | |
debtouched(debugtouch[touch.id]) | |
end | |
end | |
tween.delay(0, function() | |
local _touched = touched or function() end | |
touched = function(...) | |
handle(...) | |
_touched(...) | |
end | |
local _draw = draw or function() end | |
draw = function(...) | |
if Flickable.constant then | |
for k, v in pairs(touches) do | |
if v.state == BEGAN or v.state == MOVING then | |
handle(v) | |
end | |
end | |
end | |
_draw(...) | |
end | |
end) | |
--# Main | |
-- Flickable | |
--[[ | |
#### DOCUMENTATION #### | |
## API FEATURES ## | |
# GLOBALS # | |
Flickable: table: | |
// Table for storing values to customize the Flickable API | |
-angleOffset: number, offset of angles when using getNameFromAngle(...), getBoolFromAngle(...) | |
-constant: boolean, whether to update touches every frame rather than when they move (recommended for detecting long presses moving) | |
-useCorners: boolean, whether corners should be a possibility when using getNameFromAngle(...), getBoolFromAngle(...) | |
# FUNCTIONS # | |
getDebugFromID(id): | |
//Get a debug value from a touch ID (see debtouched(deb)) | |
-id: number, ID of the touch | |
getDebugFromTouch(touch): | |
//Get a debug value from a touch (see debtouched(deb)) | |
-touch: default "touch" metatable | |
getTouchFromID(id): | |
//Get a default "touch" metatable from a debug value ID (see debtouched(deb)) | |
-id: number, ID of the debug value | |
getTouchFromDebug(debug): | |
//Get a default "touch" metatable from a debug value (see debtouched(deb)) | |
-debug: see debtouched(deb) | |
getNameFromMotion(x, y, human): | |
// Get the names of the direction of a motion (output order for corners: horizontal-vertical) (simpler, innaccurate) | |
-x, y: numbers, x and y values of the motion | |
-human: boolean, whether the output should be optimized for humans | |
getNameFromAngle(angle, human): | |
// Get the names of the direction of an angle (output order for corners: horizontal-vertical) (complicated, accurate) | |
-angle: number, angle in degrees of the motion | |
-human: boolean, whether the output should be optimized for humans | |
getBoolFromMotion(x, y): | |
// Returns a table of booleans {top, bottom, right, left}, each value determines whether the motion is moving in its direction (simpler, innaccurate) | |
-x, y: numbers, x and y values of the motion | |
getBoolFromAngle(angle): | |
// Returns a table of booleans {top, bottom, right, left}, each value determines whether the angle is pointing in its direction (complicated, accurate) | |
-angle: number, angle in degrees of the motion | |
## USER-DEFINED ## | |
(parameters are supplied by Flickable) | |
debtouched(deb): | |
// Called alongside touched(touch), debug information | |
-deb: table, contains debug information: | |
--id: number, ID of the debug value/touch | |
--start: number, ElapsedTime from when touch started | |
--startX: number, X coordinate from when touch started | |
--startY: number, Y coordinate from when touch started | |
--motX: number, X motion of touch | |
--motY: number, Y motion of touch | |
--long: boolean, whether the touch had been deemed a long press | |
tapped(tap, deb): | |
// Called when a finger has tapped the screen | |
-tap: default "touch" metatable | |
-deb: see debtouched(deb) | |
swiped(swipe, deb): | |
// Called when a finger has swiped the screen | |
-swipe: table, stores information about the swipe | |
--id: number, ID of the touch | |
--x: number, X position from when touch ended | |
--y: number, Y position from when touch ended | |
--startX: number, X position from when touch started | |
--startY: number, Y position from when touch started | |
--motX: number, X motion of touch | |
--motY: number, Y motion of touch | |
--rad: number, angle of the touch's direction in radians | |
--deg: number, angle of the touch's direction in degrees | |
--direction: vec2, UV version of motX and motY, sine/cosine of rad | |
-deb: see debtouched(deb) | |
longPressed(press, deb): | |
// Called when a finger has long pressed the screen (on start of long press qualify) | |
-press: default "touch" metatable | |
-deb: see debtouched(deb) | |
longPressing(press, deb): | |
// Called when a touch qualified as a long press moves (or if Flickable.constant is true, every frame a touch qualified as long is on the screen), useful for long-press-to-move functionality, such as on the app screen of your iPad | |
-press: default "touch" metatable | |
-deb: see debtouched(deb) | |
--]] | |
rectMode(CENTER) | |
-- Use this function to perform your initial setup | |
function setup() | |
print("Tap to make the cube bounce") | |
print("Swipe to make the cube move (diagonally works too!)") | |
print("Long press the cube to move it around") | |
Flickable.constant = true | |
--Flickable.useCorners = false | |
gridSize = 8 | |
pos = {x = WIDTH / 2 + WIDTH / gridSize / 2, y = HEIGHT / 2 + HEIGHT / gridSize / 2} | |
vis = {x = pos.x, y = pos.y} | |
next = {x = pos.x, y = pos.y} | |
size = {s = 1} | |
img = image(512, 512) | |
setContext(img) | |
background(255) | |
fill(0) | |
font("ArialRoundedMTBold") | |
fontSize(36) | |
textMode(CORNER) | |
text("Easy touch library\nBy SkyTheCoder", 8, 8) | |
local w, h = textSize("Easy touch library\nBy SkyTheCoder") | |
strokeWidth(2) | |
stroke(0) | |
line(0, h + 16, 512, h + 16) | |
fontSize(72) | |
textMode(CENTER) | |
text("Flickable", 256, 256 + (h + 16) / 2) | |
setContext() | |
saveImage("Project:Icon", img) | |
end | |
-- This function gets called once every frame | |
function draw() | |
background(255) | |
stroke(0) | |
strokeWidth(1) | |
for i = 1, gridSize - 1 do | |
line(0, i / gridSize * HEIGHT, WIDTH, i / gridSize * HEIGHT) | |
end | |
for i = 1, gridSize - 1 do | |
line(i / gridSize * WIDTH, 0, i / gridSize * WIDTH, HEIGHT) | |
end | |
pushMatrix() | |
--translate(vis.x + WIDTH / gridSize / 2, vis.y + HEIGHT / gridSize / 2) | |
translate(vis.x, vis.y) | |
scale(size.s) | |
noStroke() | |
fill(0) | |
rect(0, 0, WIDTH / gridSize, HEIGHT / gridSize) | |
fill(255) | |
font("ArialRoundedMTBold") | |
fontSize(12) | |
text("Flickable") | |
popMatrix() | |
tint(255, 255 - (ElapsedTime - 4) * 127) | |
sprite(img, WIDTH / 2, HEIGHT / 2) | |
end | |
function swiped(swipe) | |
--print("Swiped: " .. swipe.x .. ", " .. swipe.y) | |
print("Swiped " .. getNameFromAngle(swipe.deg, true)) | |
if tPos ~= nil then | |
tween.stop(tPos) | |
pos.x = next.x | |
pos.y = next.y | |
vis.x = pos.x | |
vis.y = pos.y | |
tPos = nil | |
end | |
local bools = getBoolFromAngle(swipe.deg) | |
local target = {x = pos.x, y = pos.y} | |
if bools.up then | |
target.y = pos.y + HEIGHT / gridSize | |
end | |
if bools.down then | |
target.y = pos.y - HEIGHT / gridSize | |
end | |
if bools.right then | |
target.x = pos.x + WIDTH / gridSize | |
end | |
if bools.left then | |
target.x = pos.x - WIDTH / gridSize | |
end | |
--[[target.x = target.x or pos.x | |
target.y = target.y or pos.y--]] | |
next.x = target.x | |
next.y = target.y | |
tPos = tween(0.25, vis, target, tween.easing.quadIn, function() | |
pos.x, pos.y = vis.x, vis.y | |
tPos = nil | |
end) | |
end | |
function tapped(tap) | |
if tSize ~= nil then | |
tween.stop(tSize) | |
tSize = nil | |
size = {s = 1} | |
end | |
tSize = tween(0.15, size, {s = 0.75}, tween.easing.quadInOut, function() | |
tSize = tween(0.5, size, {s = 1}, tween.easing.elasticOut, function() | |
tSize = nil | |
end) | |
end) | |
end | |
function longPressed(press) | |
vis.x, vis.y = press.x, press.y | |
if tSize ~= nil then | |
tween.stop(tSize) | |
tSize = nil | |
size = {s = 1} | |
end | |
tSize = tween(0.5, size, {s = 1.25}, tween.easing.bounceOut, function() | |
tSize = nil | |
end) | |
end | |
function longPressing(press) | |
if tPos ~= nil then | |
tween.stop(tPos) | |
tPos = nil | |
end | |
vis.x, vis.y = press.x, press.y | |
next.x = math.floor(vis.x / WIDTH * gridSize) * WIDTH / gridSize + WIDTH / gridSize / 2 | |
next.y = math.floor(vis.y / HEIGHT * gridSize) * HEIGHT / gridSize + HEIGHT / gridSize / 2 | |
if press.state == ENDED or press.state == CANCELLED then | |
if tSize ~= nil then | |
tween.stop(tSize) | |
tSize = nil | |
size = {s = 1.25} | |
end | |
tSize = tween(0.5, size, {s = 1}, tween.easing.elasticOut, function() | |
tSize = nil | |
end) | |
tPos = tween(0.25, vis, next, tween.easing.quadIn, function() | |
pos.x, pos.y = vis.x, vis.y | |
tPos = nil | |
end) | |
end | |
end | |
--[[function debtouched(deb) | |
for k, v in pairs(deb) do | |
if type(v) ~= "userdata" then | |
print(k .. ": " .. tostring(v)) | |
end | |
end | |
end--]] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment