Instantly share code, notes, and snippets.

Embed
What would you like to do?
-- Touch Example
function setup()
-- initialise
touches = Touches()
boxes = {}
local c = {
color(255,0,0),
color(255,255,0),
color(0,255,0),
color(0,255,255),
color(0,0,255),
color(255,0,255)
}
local b
for k=1,6 do
b = Box(
vec2(math.random(1,WIDTH),math.random(1,HEIGHT)),
vec2(math.random(100,200),math.random(100,200)),
c[k]
)
table.insert(boxes,1,b)
touches:pushHandler(b)
end
stuff = Stuff({touchHandler = touches})
end
function draw()
background(101, 87, 87, 255)
touches:draw()
stuff:draw()
for k,b in ipairs(boxes) do
b:draw()
end
end
function touched(touch)
touches:addTouch(touch)
end
Stuff = class()
function Stuff:init(t)
t = t or {}
t.touchHandler:pushHandler(self)
self.colour = color(250, 13, 13, 255)
self.message = "Touch"
end
function Stuff:draw()
pushStyle()
pushMatrix()
resetStyle()
resetMatrix()
fill(self.colour)
noStroke()
rectMode(CENTER)
rect(WIDTH/2,HEIGHT/2,WIDTH/2,HEIGHT/2)
fill(0, 0, 0, 255)
font("Baskerville-SemiBold")
fontSize(32)
textMode(CENTER)
textWrapWidth(WIDTH/2)
text(self.message,WIDTH/2,HEIGHT/2)
popMatrix()
popStyle()
end
function Stuff:isTouchedBy(touch)
if math.abs(touch.x - WIDTH/2) > WIDTH/4 then
return false
end
if math.abs(touch.y - HEIGHT/2) > HEIGHT/4 then
return false
end
return true
end
function Stuff:processTouches(g)
self.colour = color(0, 255, 31, 255)
self.message = "Touch details:\n" ..
"Number: " .. g.num .. "\n" ..
"Tap: " .. booltostring(g.type.tap) .. "\n" ..
"Long: " .. booltostring(g.type.long) .. "\n" ..
"Short: " .. booltostring(g.type.short) .. "\n" ..
"Pinch: " .. booltostring(g.type.pinch) .. "\n" ..
"Updated: " .. booltostring(g.updated) .. "\n" ..
"Ended: " .. booltostring(g.type.ended) .. "\n" ..
"Finished: " .. booltostring(g.type.finished)
if g.type.ended then
self.colour = color(255, 249, 0, 255)
end
if g.type.finished then
self.colour = color(255, 0, 0, 255)
end
g:noted()
end
Box = class()
function Box:init(p,s,c)
self.position = p
self.size = s
self.colour = c
self.ocolour = c
end
function Box:draw()
fill(self.colour)
rect(self.position.x,self.position.y,self.size.x,self.size.y)
end
function Box:isTouchedBy(t)
if t.x > self.position.x
and t.x < self.position.x + self.size.x
and t.y > self.position.y
and t.y < self.position.y + self.size.y
then
return true
end
return false
end
function Box:processTouches(g)
self.colour = self.colour:mix(color(0),.99)
if g.type.ended then
g:reset()
self.colour = self.ocolour
end
end
function booltostring(a)
if a then
return "true"
else
return "false"
end
end
-- Touch handler class
-- Author: Andrew Stacey
-- Website: http://www.math.ntnu.no/~stacey/HowDidIDoThat/iPad/Codea.html
-- Licence: CC0 http://wiki.creativecommons.org/CC0
--[[
The classes in this file are for handling touches. The classes are:
Touches: this is the controller class which gathers touches, figures
out which object they belong to, and passes the information to those
objects in an orderly manner as a "gesture".
Gesture: a gesture is a collection of touches that are "alive" and
claimed by the same object. A gesture is analysed before being passed
to an object to help provide some information as to what type of
gesture it is.
Touch: this represents a touch as a single object (from start to
finish) and is updated by the controller as new information comes in.
--]]
-- cimport "Coordinates"
Touches = class()
Gesture = class()
Touch = class()
--[[
The controlling object has an array of touches, an array of handlers,
and an array of "active touches".
When a "touch" object is registered by Codea, the handler takes it and
tries to work out what to do with it. When a touch begins, the
handler creates a "Touch" object surrounding it. As new information
comes in, via new "touch" objects, this needs to be linked to the
correct "Touch" object. The handler uses the "touch.id" to do this,
but this creates a problem: although the "touch.id" is guaranteed to
be unique in the lifetime of the touch, it might be reused afterwards.
For complicated gestures, it is useful to have "Touch" objects persist
beyond the liftetime of the actual touch. So more than one Touch can
correspond to the same "touch.id". To get round this, we maintain a
list of "active" touches: ie ones that have not officially ended.
These are the ones that accept new information.
--]]
function Touches:init()
self.touches = {}
self.handlers = {}
self.numTouches = 0
self.actives = {}
local img = image(100,100)
setContext(img)
ellipseMode(RADIUS)
fill(255, 223, 0, 21)
strokeWidth(0)
local r
for i=0,90,5 do
r = 10 + 40*math.sin(math.rad(i))
ellipse(50,50,r)
end
self.img = img
setContext()
end
--[[
This is where a touch is initially analysed. If it is a new touch
then a Touch object is created. Then each of the handlers in turn is
asked if it wants to "claim" the touch. The first one to do so is
assigned the touch. If none do, it is consigned to the bin.
There is a slight wrinkle in the above: it is possible for an object
to specify an "interrupt" which is given first priority in the
claimant queue. Sometimes an object might want to take some action
"until the next touch", wherever that touch may be: this is to allow
for that.
If a touch is not new, its information is used to update the
corresponding active Touch object.
--]]
function Touches:addTouch(touch)
local retval = false
if touch.state == BEGAN then
self.numTouches = self.numTouches + 1
self.touches[self.numTouches] = Touch(touch)
self.touches[self.numTouches].container = self
self.touches[self.numTouches].id = self.numTouches
self.actives[touch.id] = self.numTouches
retval = true -- in case the interrupt gets it
if
not self.interrupt
or
not self.interrupt:interruption(
self.touches[self.actives[touch.id]]
)
then
retval = false -- interrupt didn't
retval = self:checkHandlers(touch,self.handlers)
if not self.touches[self.actives[touch.id]].gesture then
self.touches[self.actives[touch.id]] = nil
self.actives[touch.id] = nil
retval = false -- gesture rejected it for some reason
end
else
-- Interrupt got it and used it, now we destroy it
self.touches[self.actives[touch.id]]:destroy()
end
elseif touch.state == MOVING then
if self.actives[touch.id] then
self.touches[self.actives[touch.id]]:update(touch)
retval = true
end
elseif touch.state == ENDED then
if self.actives[touch.id] then
self.touches[self.actives[touch.id]]:update(touch)
retval = true
end
end
return retval
end
--[[
Create a handler for adding to the relevant code.
--]]
function Touches:registerHandler(h)
local g = Gesture()
return {h,g}
end
--[[
This adds a new handler at the end of the list, creating a gesture to
contain its touches.
--]]
function Touches:pushHandler(h)
local g = self:registerHandler(h)
table.insert(self.handlers,g)
return g[2]
end
--[[
This adds a new handler at the start of the list, creating a gesture to
contain its touches.
--]]
function Touches:unshiftHandler(h)
local g = self:registerHandler(h)
table.insert(self.handlers,1,g)
return g[2]
end
function Touches:removeHandler(h)
for k,v in ipairs(self.handlers) do
if v[1] == h then
table.remove(self.handlers,k)
break
end
end
end
--[[
This adds a new table of handlers at the end of the list.
--]]
function Touches:pushHandlers(t)
local t = t or {}
table.insert(self.handlers,{t})
return t
end
--[[
This adds a new table of handlers at the start of the list.
--]]
function Touches:unshiftHandlers(t)
local t = t or {}
table.insert(self.handlers,1,{t})
return t
end
--[[
Check the handlers to see who claims the touch
--]]
function Touches:checkHandlers(touch,t)
for k,v in ipairs(t) do
if v[2] then
if v[1]:isTouchedBy(touch) then
v[2]:addTouch(self.touches[self.actives[touch.id]])
return true
end
else
if self:checkHandlers(touch,v[1]) then
return true
end
end
end
return false
end
--[[
The draw function is used to process the touch information gathered in
the current cycle. Gestures are analysed and then passed to the
corresponding handers.
--]]
function Touches:show()
if self.showtouch then
pushStyle()
pushMatrix()
resetMatrix()
resetStyle()
for k,v in pairs(self.touches) do
v:draw(self.img)
end
popMatrix()
popStyle()
end
end
function Touches:showTouches(s)
self.showtouch = s
end
function Touches:draw()
self:processHandlers(self.handlers)
end
function Touches:processHandlers(t)
if t then
for k,v in pairs(t) do
if v[2] then
if v[2].num > 0 then
v[2]:analyse()
v[1]:processTouches(v[2])
if v[2].type.finished then
v[2]:reset()
end
end
else
self:processHandlers(v[1])
end
end
end
end
function Touches:reset()
self.touches = {}
self.numTouches = 0
self.actives = {}
end
function Touches:pause()
end
--[[
A gesture is a group of touches that are handled by the same object
and are "alive" at the same time. A gesture is analysed before being
passed to its object and certain basic information is gathered that
can be analysed by the handling object. Most of this information is
stored in the "type" array with two notable exceptions. The main list
is as follows (there are some others which are used for implementation
reasons that can nonetheless be used, but the following contains all
the available information):
num: the number of touches in the gesture.
updated: has there been new information since the gesture was last
looked at?
type.tap: this is true if none of the touches have moved
type.long: this is true if all of the touches waited a significant
length of time (currently .5s) between starting and ending or moving.
type.short: this is true if all of the touches were of short duration
(less that .5s). Note that "short" and "long" are not opposites.
type.ended: if all the touches have ended.
type.finished: if all the touches ended at least .5s ago. The
distinction between "ended" and "finished" is to allow for things like
multiple taps to be registered as a single gesture.
type.pinch: if the gesture consists of multiple movements, the gesture
tries to see if they are moving towards each other or parallel. It
does this by looking at the relative movement of the barycentre of the
touch compared to the average magnitude of the movements of the
individual touches.
--]]
--[[
Initialise our data.
--]]
function Gesture:init()
self.touches = {}
self.touchesArr = {}
self.num = 0
self.updated = true
self.updatedat = ElapsedTime
self.type = {}
end
--[[
Add a touch to the list.
--]]
function Gesture:addTouch(touch)
self.num = self.num + 1
self.touches[touch.id] = touch
table.insert(self.touchesArr,touch)
touch.gesture = self
self.updated = true
self.updatedat = ElapsedTime
return true
end
--[[
Remove a touch from the list.
--]]
function Gesture:removeTouch(touch)
for k,v in ipairs(self.touchesArr) do
if v.id == touch.id then
table.remove(self.touchesArr,k)
break
end
end
self.touches[touch.id] = nil
touch.gesture = nil
self.num = self.num - 1
self.updated = true
self.updatedat = ElapsedTime
self.type = {}
return true
end
--[[
Resets us to a "blank" state. Called by the touch controller at the
end of the cycle if we are "finished" but can be called by our object
at an earlier stage.
--]]
function Gesture:reset()
for k,v in pairs(self.touches) do
v:destroy()
end
self.touches = {}
self.touchesArr = {}
self.num = 0
self.updated = true
self.updatedat = ElapsedTime
self.type = {}
end
--[[
The "updated" field is so that the object knows that new information
has come in. So it is for the object to say "I am done with this
information, wake me again when there is new stuff" by calling this
routine.
--]]
function Gesture:noted()
self.updated = false
for k,v in pairs(self.touches) do
v.updated = false
v.utouch = nil
end
end
--[[
This is the analyser that works out the basic information about the
gesture. Most of the information is of the "if all touches are X, so
are we" or "if at least one touch is X, so are we" (which are
equivalent via negation). The exception is the "pinch".
--]]
function Gesture:analyse()
local b = vec2(0,0)
local d = 0
local c
self.actives = {}
local na = 0
self.type.ended = true
self.type.notlong = false
for k,v in ipairs(self.touchesArr) do
if v.updated or v.touch.state ~= ENDED then
table.insert(self.actives,v)
na = na + 1
end
if v.moved then
self.type.moved = true
end
if v.touch.state ~= ENDED then
self.type.ended = false
end
if not v:islong() then
self.type.notlong = true
end
if not v:isshort() then
self.type.notshort = true
end
c = vec2(v.touch.deltaX,v.touch.deltaY)
b = b + c
d = d + c:lenSqr()
end
self.numactives = na
if not self.type.moved then
-- some sort of tap
self.type.tap = true
else
self.type.tap = false
end
if self.type.ended and (ElapsedTime - self.updatedat) > .5 then
self.type.finished = true
end
self.type.long = not self.type.notlong
self.type.short = not self.type.notshort
if self.num > 1 and not self.type.tap then
if d * self.num < 1.5 * b:lenSqr() then
self.type.pinch = false
else
self.type.pinch = true
end
end
end
function Gesture:transformTouches(o)
local f
if type(o) == "function" then
f = o
else
f = function(v)
return TransformTouch(o,v)
end
end
for _,t in pairs(self.touches) do
if t.updated then
t.utouch = t.utouch or t.touch
t.touch = f(t.touch)
if t.touch.state == BEGAN then
t.ufirsttouch = t.ufirsttouch or t.firsttouch
t.firsttouch = f(t.firsttouch)
end
t.velocities[t.ntouches][1] = t.touch.x
t.velocities[t.ntouches][2] = t.touch.y
end
end
end
--[[
This is an extension of the "touch" class, providing a single object
that corresponds to what a user would call a "touch". It also
provides more information than is contained in a single "touch"
object.
--]]
--[[
Initialiser function.
--]]
function Touch:init(touch)
self.id = touch.id
self.touch = touch
self.firsttouch = touch
self.updated = true
self.updatedat = ElapsedTime
self.createdat = ElapsedTime
self.startedat = ElapsedTime
self.deltatime = 0
self.laststate = 0
self.moved = false -- did we move?
self.long = false -- long time before we did anything?
self.short = false -- active lifetime was short?
self.keepalive = false
self.velDelta = .5 -- for computing a more realistic velocity
self.velocities = {{touch.x,touch.y,ElapsedTime}}
self.ntouches = 1
end
--[[
Updates new information from a "touch" object.
--]]
function Touch:update(touch)
-- save previous state
self.laststate = touch.state
self.deltatime = ElapsedTime - self.updatedat
self.updatedat = ElapsedTime
table.insert(self.velocities,{touch.x,touch.y,ElapsedTime})
self.ntouches = self.ntouches + 1
-- Update the touch
self.touch = touch
-- Regard ourselves as "refreshed"
self.updated = true
if self.gesture then
self.gesture.updated = true
self.gesture.updatedat = ElapsedTime
end
if self.laststate == BEGAN then
self.startedat = ElapsedTime
end
-- record whether we've moved
if touch.state == MOVING then
self.moved = true
end
-- if it was a long time since we began, we're long
if self.laststate == BEGAN and self.deltatime > 1 then
self.long = true
end
if touch.state == ENDED then
-- If we've ended and it's less than a second since we
-- actually did something, we're short
if (ElapsedTime - self.startedat) < 1 then
self.short = true
end
end
return true
end
--[[
Regard ourselves as "dealt with" until new information comes in.
--]]
function Touch:handled()
self.updated = false
end
--[[
Do our level best to get rid of ourselves, removing ourself from the
gesture and touch controller.
--]]
function Touch:destroy()
if self.gesture then
self.gesture:removeTouch(self)
end
if self.container then
if self.container.actives[self.touch.id] == self.id then
self.container.actives[self.touch.id] = nil
end
self.container.touches[self.id] = nil
end
end
--[[
Test to find out if we are "long"
--]]
function Touch:islong()
if self.long then
return self.long
elseif self.touch.state == BEGAN and (ElapsedTime - self.createdat) > 1 then
self.long = true
return true
else
return false
end
end
--[[
Test to find out if we are "short"
--]]
function Touch:isshort()
if self.short then
return self.short
elseif (ElapsedTime - self.startedat) < 1 then
return true
else
return false
end
end
function Touch:instVelocity()
if self.deltatime > 0 then
return vec2(
self.touch.deltaX/self.deltatime,
self.touch.deltaY/self.deltatime
)
else
return vec2(0,0)
end
end
function Touch:meanVelocity()
if self.updatedat > self.createdat then
return vec2(
self.touch.x - self.firsttouch.x,
self.touch.y - self.firsttouch.y
)/(self.updatedat - self.createdat)
else
return vec2(0,0)
end
end
function Touch:velocity()
local dt = 0
local dx = 0
local dy = 0
local i
local n = 0
local rec = false
local vel = self.velocities
for k,v in ipairs(vel) do
n = n + 1
if v[3] > ElapsedTime - 2*self.velDelta then
rec = true
end
if i and v[3] > ElapsedTime - self.velDelta then
rec = false
end
if rec then
i = k
end
end
if i == 0 then
i = 1
end
dt = ElapsedTime - vel[i][3]
if dt == 0 then
return vec2(0,0)
end
dx = vel[n][1] - vel[i][1]
dy = vel[n][2] - vel[i][2]
return vec2(dx/dt,dy/dt)
end
function Touch:draw(img)
sprite(img,self.touch.x,self.touch.y)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment