Some difficulties with touch properties and consistency

hi, I encountered some complications working with touches.
version: latest beta / any
• touch.timestamp doesn’t play nice with other time functions that I know of
• when tapping a touch multiple times touch.tapCount goes up but the touch.id is not guaranteed to stay the same (which gives complications with advanced ways in which I want to use touches)

for the time issue my workaround of saving the difference in time is prone to error and needs occasional recalibrations, I can probably do better just ignoring touch.timestamp entirely and saving a start timestamp along with my “touchStarts”
I can’t easily come up with a solution to the inconsistency with touch.id over multiple taps without access to lower level code (which I wouldn’t understand anyway, lol)

-- goal: keep track of the time a touch has been held ...over multiple taps(this second part isn't implemented here)
-- complications:
-- touch.timestamp is a different time format from both os.time() and ElapsedTime
-- no consistent touch.id over multiple taps (touch.tapCount)

function setup()
    touches = {}
    touchStarts = {}   
    --find the difference by using the first touch as a calibration point
    calibrated = false ; timeDifference = 0
    parameter.watch("recallibrations") ; recallibrations = 0
end

function draw()
    background(40, 40, 50)   
    if not calibrated then
        pushStyle() ; fontSize(40) ; fill(255,128) ; text("touch to calibrate", WIDTH/2,HEIGHT/2) ; popStyle()
    end
    
    for k,touch in pairs(touches) do
        math.randomseed(touch.id)
        local touchColor = color(math.random(64,160),math.random(64,160),math.random(64,160))
        local x,y = touch.pos.x, touch.pos.y
        fill(touchColor) ; strokeWidth(0) ; ellipse(x,y, 100)
        local info = "  touch id: "..touch.id.."\ntapCount: "..touch.tapCount--.."\ntimestamp:"..touch.timestamp
        if calibrated then
            local touchTime = (ElapsedTime+timeDifference)-touchStarts[k].timestamp --calculate the current age of the touch
            if touchTime < 0 then calibrateTime(touch.timestamp) end --needs to recallibrate if negative
            info = info.."\n duration: "..touchTime
            fill(0,0) ; stroke(touchColor*2) ; strokeWidth(3) ; ellipse(x,y, 100+(touchTime*20))
            local hand = vec2(0,47+(touchTime*10)):rotate(-touchTime*(math.pi*2)) 
            strokeWidth(4) ; line(x,y,x+hand.x,y+hand.y)
        else --first calibration
            calibrateTime(touch.timestamp)
            print("os.time():\n"..os.time())--this is not the same time as touch uses
            print("ElapsedTime:\n"..ElapsedTime)--and neither is ElapsedTime
            print("touch.timestamp:\n"..touch.timestamp)  
        end
        fill(255) ; text(info, x, y+100)
    end  
end

function touched(touch)
    if touch.state == BEGAN then
        touches[touch.id] = touch ; touchStarts[touch.id] = touch
    elseif touch.state == CHANGED then
        if touch.id == touches[touch.id].id then touches[touch.id] = touch end       
    else--if touch.state == ENDED or touch.state == CANCELLED then
        --delete touch
        touches[touch.id] = nil ; touchStarts[touch.id] = nil    
    end
end

function calibrateTime(touchTimestamp)
    timeDifference = touchTimestamp-ElapsedTime
    calibrated = true
    recallibrations = recallibrations+1
end

ps:
a small bug I found: vector:rotate() asks for “angle in degrees” but actually needs an angle given in radians.

I’m not positive about this, but I think The touch ID issue with multiple taps may be an iOS thing not a Codea thing.

@Amber FWIW here is a set of code to manage touches - if you turn on the includeDuration flag, this will track the length of time of the touch. Not tried in the new beta, but this might give you an alternative if it works.

-- Touchtracker
-- by West 
-- Use this function to perform your initial setup
function setup()
  touches={}
  tsup={} --tsup contains the supplementary info about the start position of the touch
  points={}
  pulse={}
  parameter.boolean("includeTarget",false)
  parameter.boolean("includeTrail",true)
  parameter.boolean("includeStart",false)
  parameter.boolean("includeDuration",false)
  parameter.boolean("includeHistory",false)
  parameter.boolean("joinHistory",false)
  parameter.boolean("includePulse",false)
  parameter.boolean("includeClassification",true)
end

-- This function gets called once every frame
function draw()
  -- This sets a background color 
  background(106, 164, 152, 255)
  noStroke()
  --draw any active touch pulses
  for i,p in pairs(pulse) do
    local pulsesize=500 --the maximum radius of the touch circle pulse
    local fade=100-(p.r/pulsesize)*100 --calculate the
    fill(255,255,255,fade)
    ellipse(p.x,p.y,p.r)
    p.rate = p.rate + 1
    p.r = p.r + p.rate
    if p.r>pulsesize then
      table.remove(pulse,i)
    end
  end
  
  fill(255)
  --draw a circle at each of the touched points
  if includeHistory==true then
    for i,pt in pairs(points) do
      ellipse(pt.x,pt.y,10)
      if joinHistory==true then
        --draw lines between all historic touch points
        strokeWidth(2)
        if i>1 then
          stroke(255)
          line(pt.x,pt.y,prevx,prevy)
        end
        prevx=pt.x
        prevy=pt.y
      end
    end
  end
  --draw start and stop circles and connect them with lines for all active touches
  strokeWidth(2)
  for i,t in pairs(touches) do
    fill(255)
    stroke(255)
    if includeStart==true then
      line(tsup[i].tstartx,tsup[i].tstarty,t.x,t.y)
      ellipse(tsup[i].tstartx,tsup[i].tstarty,10)
    end
    fill(255)
    if includeDuration==true then
      local formattime=math.floor(10*(ElapsedTime-tsup[i].starttime))/10
      text(formattime,t.x,t.y+50)
    end
    ellipse(t.x,t.y,10)
    if includeTarget==true then
      local endpt=math.max(1200-800*(ElapsedTime-tsup[i].starttime),50+10*math.sin(5*ElapsedTime))
      local spin=ElapsedTime*40
      pushMatrix()
      translate(t.x,t.y)
      rotate(spin)
      translate(-endpt,0)
      sprite(asset.builtin.Cargo_Bot.How_Arrow,0,0)
      translate(endpt,0)
      rotate(90)
      translate(-endpt,0)
      sprite(asset.builtin.Cargo_Bot.How_Arrow,0,0)
      translate(endpt,0)
      rotate(90)
      translate(-endpt,0)
      sprite(asset.builtin.Cargo_Bot.How_Arrow,0,0)
      translate(endpt,0)
      rotate(90)
      translate(-endpt,0)
      sprite(asset.builtin.Cargo_Bot.How_Arrow,0,0)
      translate(endpt,0)
      popMatrix()
    end
    --draw path of touch
    if includeTrail==true then
      fadespeed=500
      for j,p in pairs(tsup[i].path) do
        trailfade=math.max((p.age-ElapsedTime)*fadespeed,-255)
        stroke(255,255,255,255+trailfade)
        fill(255,255,255,255+trailfade)
        if trailfade>-255 then
          -- ellipse(p.pos.x,p.pos.y,5)
          if j>1 then
            line(prevx,prevy,p.pos.x,p.pos.y)
          end
        end
        prevx=p.pos.x
        prevy=p.pos.y
      end
    end
  end
end

function touched(touch)
  if touch.state==MOVING then
    --record path
    if tsup[touch.id]~=nil then
      table.insert(tsup[touch.id].path,{pos=vec2(touch.x,touch.y),age=ElapsedTime})
    end
  end
  if touch.state==ENDED or touch.state==CANCELLED then
    processTouch(touch)
    touches[touch.id] = nil
    tsup[touch.id]=nil
  else
    touches[touch.id] = touch
    --if there is no supplementary info associated with the current touch then add it
    if tsup[touch.id]==nil then 
      tsup[touch.id]={tstartx=touch.x,tstarty=touch.y,starttime=ElapsedTime,path={}} 
    end
  end
end

function processTouch(touch)
  if includeHistory==true then
    table.insert(points,vec2(touch.x,touch.y)) --add a point to the points table
  end
  if includePulse==true then
    table.insert(pulse,{x=touch.x,y=touch.y,r=8,rate=1}) --add a new pulse
  end
  if includeClassification==true then
    if ElapsedTime-tsup[touch.id].starttime<0.2 then
      --very short event
      if tsup[touch.id]==nil then
        print("tap")
      elseif vec2(touch.x,touch.y):dist(vec2(tsup[touch.id].tstartx,tsup[touch.id].tstarty))<10 then
        print("tap")
      else
        print("dash")
      end
    elseif ElapsedTime-tsup[touch.id].starttime<1 then  
      --a slightly longer gesture
      if touch.x>tsup[touch.id].tstartx+25 and math.abs(touch.y-tsup[touch.id].tstarty)<50 then
        print("swipe right")
      elseif touch.x<tsup[touch.id].tstartx-25 and math.abs(touch.y-tsup[touch.id].tstarty)<50 then
        print("swipe left")
      elseif touch.y>tsup[touch.id].tstarty+25 and math.abs(touch.x-tsup[touch.id].tstartx)<50 then
        print("swipe up")
      elseif touch.y<tsup[touch.id].tstarty-25 and math.abs(touch.x-tsup[touch.id].tstartx)<50 then
        print("swipe down")
      end
    end
  end
end

That’s a fantastic touch tracker! And the code is so short and clear. If there were such a thing as nominations for projects to include in the official Codea examples, I’d nominate this for inclusion in the official Codea examples! Maybe there could be some kind of fusion of this and the current touch-demonstration project? @sim @John

Is ok if I submit it to WebRepo for you?

@UberGoober thanks! Feel free to upload to the WebRepo - it’s been kicking around on this forum for around 10 years now :open_mouth:

@west - thanks for the touch tracker, very impressive. Love the target add on.

Thanks @Bri_G hope you find it useful