How do I implement a multi-touch pinch motion?
The pinch motion that you find in many iPhone apps is known as a 'gesture'. Apple don't provide support for specific gestures such as pinch leaving us as developers to implement them ourselves from touch messages. This does mean we have to do a little math but we'll work through that together in this lesson.
The pinch gesture is often used to shrink and grow items like photos. In this lesson we'll use the pinch gesture to scale a plain graphic.
* The image scaling algorithms in LiveCode have not been optimized for the iPhone yet meaning that dynamic image scaling is quite slow on the device. So in this example we are scaling a graphic which is very fast.
The stack used in this lesson can be found here: https://tinyurl.com/s3wnvnfa
Setup our stack
1) Create a new stack, name it and save it
2) Stop the stack from being resized
3) Set the width and height of the stack to 320 x 460
Add the graphic we're going to resize together
1) Create a square graphic
2) Set its name to 'square'
3) Go to the colors & patterns inspector and set the 'backgroundColor' to one you like
Basic Multi-touch Message
We're going to leave the interface for a moment and look at the basic messages we receive from Rev when touches start, end and move.
on touchStart pTouchId
# Sent each time a new touch is started
end touchStart
on touchEnd pTouchId
# Sent each time a touch ends
end touchEnd
on touchMove pTouchId, pX, pY
# Sent when any touch moves
end touchMove
If a user places two fingers on the device, two touch starts messages will be sent to your stack. You will also be sent a touch ID so you can tell they are different.
The Final Script
Copy this script to the card or stack.
1) Looks for two touches
2) Calculates the distance between the two touches to work out if they are getting closer or further away
3) Scales the image according to the distance of the two touches,
local sTouchArray, sFRAMEWIDTH, sFRAMEHEIGHT
local tempimagex, tempimagey, focusx, focusy, pinchflag
on touchStart pId, pX, pY
put the width of graphic "square" into sFRAMEWIDTH
put the height of graphic "square" into sFRAMEHEIGHT
put item 1 of the loc of graphic "square" into tempimagex
put item 2 of the loc of graphic "square" into tempimagey
end touchStart
on touchEnd pId
if the number of lines of the keys of sTouchArray is 1 then
put false into pinchflag
end if
delete variable sTouchArray[pId]
end touchEnd
on touchMove pId, pX, pY
if sTouchArray[pId]["startloc"] is empty then
put (pX & comma & pY) into sTouchArray[pId]["startloc"]
end if
put (pX & comma & pY) into sTouchArray[pId]["currentloc"]
-- script if user wants to move image around:
if the number of lines of the keys of sTouchArray is 1 then
--First lets get the data out of the array
put line 1 of the keys of sTouchArray into tPointOne
put sTouchArray[tPointOne]["startloc"] into tStartLoc
if tStartLoc1 is not empty then
put item 1 of tStartLoc into tempstartx
put item 2 of tStartLoc into tempstarty
put tempstartx-pX into tempdiffx
put tempstarty-pY into tempdiffy
put tempimagex-tempdiffx into tempimagecurrentx
put tempimagey-tempdiffy into tempimagecurrenty
if pinchflag is not true then
set the loc of graphic "square" to tempimagecurrentx, tempimagecurrenty
end if
end if
end if
-- script when user want to pinch and zoom:
if the number of lines of the keys of sTouchArray is 2 then
# First lets get the data out of the array
put line 1 of the keys of sTouchArray into tPointOne
put line 2 of the keys of sTouchArray into tPointTwo
# First lets calculate the size of the picture base on the distance
# between the two touch points
put sTouchArray[tPointOne]["startloc"] into tStartLoc1
put sTouchArray[tPointTwo]["startloc"] into tStartLoc2
if tStartLoc1 is not empty and tStartLoc2 is not empty then
put true into pinchflag -- to avoid a jump in location when 1 finger is removed
-- calculate the focus point of the zoom, it is located between the two starting touchpoints
put item 1 of tStartLoc1 into focusx1
put item 2 of tStartLoc1 into focusy1
put item 1 of tStartLoc2 into focusx2
put item 2 of tStartLoc2 into focusy2
put focusx1 + round ((focusx2-focusx1)/2) into focusx
put focusy1 + round ((focusy2-focusy1)/2) into focusy
put resizeDistance(tStartLoc1, tStartLoc2) into tStartDistance
put resizeDistance(sTouchArray[tPointOne]["currentloc"], sTouchArray[tPointTwo]["currentloc"]) into tCurrentDistance
resizeGraphic tStartDistance, tCurrentDistance
end if
end if
end touchMove
function resizeDistance pLoc1, pLoc2
local dy, dx, tDistance
put item 2 of pLoc1 - item 2 of pLoc2 into dy
put item 1 of pLoc1 - item 1 of pLoc2 into dx
put sqrt((dy*dy) + (dx*dx)) into tDistance
return tDistance
end resizeDistance
on resizeGraphic pStartDistance, pNewDistance
lock screen
# Work out the percentage change between the old and new image
put round((pNewDistance / pStartDistance) * 100) into tPercentage
# Calculate the new width and height
set the width of graphic "square" to round(sFRAMEWIDTH * (tPercentage / 100))
set the height of graphic "square" to round(sFRAMEHEIGHT * (tPercentage / 100))
-- first scale the coordinates of the focus point
put tempimagex + round((focusx-tempimagex) * (tPercentage / 100)) into focusxnew
put tempimagey + round((focusy-tempimagey) * (tPercentage / 100)) into focusynew
-- getting the difference between original and scaled focuspoint
put focusx - focusxnew into focusxdiff
put focusy - focusynew into focusydiff
-- calculate the new location of the graphic
put tempimagex + focusxdiff into locxnew
put tempimagey + focusydiff into locynew
set the loc of graphic "square" to locxnew,locynew
unlock screen
end resizeGraphic
Test in the Simulator
1) Load the stack into the simulator
2) Hold the 'alt' key to bring up the multi-touch pointers (two grey circles in the screenshots above)
3) Click and drag to simulate touches moving apart / closer. The square will shrink and grow accordingly.
Jack
Does LiveCode support the use of the alt key when trying to simulate multitouch in the simulator? I cannot get it working. Thanks.
Hanson Schmidt-Cornelius
Hi Jack,
yes you can simulate multi-touch pinch using the alt key in LiveCode on the iOS simulator. If you download the sample stack that accompanies this lesson and run it without modification, then you should get the results shown in the last section of the lesson. The white circles are the locations at which the two touches are simulated.
Kind Regards,
Hanson
Chipp Walters
Looks like you're missing a lock screen in your resizeGraphic handler.
Jerry
Works ok on mobile, but how do we do this on a windows platform which native supports multi-touch? I exported as a windows exe, but it does not work with a multi-touch 4K screen. Only a single mouse touch. So how can we do this? Thanks for any help.
Elanor Buchanan
Hi Jerry
We have an enhancement request for this in our Quality Control Center
http://quality.livecode.com/show_bug.cgi?id=8446
If you would like to you can add your email address to the CC list to get email updates when the status of the bug changes.
Kind regards
Elanor
Roger Mepham
This works really well, but is it possible please to modify the code so that dragging 2 fingers moves the square instead? i.e dragging moves and pinching scales (and for bonus points twisting 2 fingers rotates)?
Elanor Buchanan
Hi Roger
The code could be modified to behave this way, you can change the logic in the touchMove handler to decide what type of gesture it is.
- A two finger drag if both the touch positions have changed the the same amount in the same direction
- A pinch/zoom if the touch positions have moved together or apart along a straight line
- A rotation if the touch positions have stayed the same distance apart but rotated around a central point
I hope that helps.
Kind regards
Elanor
Roger Mepham
Thanks Elanor for your feedback.
I have managed to get scaling and moving by adding just before the resizeGraphic call an IF statement to grab the object if the resize distance is unchanged, i.e.
if tStartDistance = tCurrentDistance then
grab graphic "square" of me
end if
resizeGraphic tStartDistance, tCurrentDistance
A bit glitchy but it works at least :-) Do you have a better approach?
Thanks for your help so far.
Roger
Elanor Buchanan
Hi Roger
That seems like a good approach! You could only call resizeGraphic if the size has actually changed, but thats the only change I would make e.g.
if tStartDistance = tCurrentDistance then
grab graphic "square" of me
else
resizeGraphic tStartDistance, tCurrentDistance
end if
Kind regards
Elanor
Kind
Andrés
Hello friends, I share a library to handle other gestures in addition to those provided by LiveCode.
https://github.com/torocruzand/touchGestures
Richard Gaskin
This tutorial has a bug: it only works well if you happen to initiate the zoom with fingertips placed on either side of the object's center. Any other position creates a zoom from an improper origin point.
This example stack is better - can someone update this tutorial to use the much more useful example stack script?
http://forums.livecode.com/viewtopic.php?f=49&t=11229#p52594
Torsten
Even simpler is to add a mousedown handler :-)
on mousedown
if the name of the target contains "graphic" then grab the target
end mousedown
Oliver Kalleinen
As Richard noted the zoom in the script focuses on the center of the graphic, no matter where you pinch. I modified the script, it's not especially elegant but it seems to get the job done, it focusses the zoom now on the second touch point.
local sTouchArray, sFRAMEWIDTH, sFRAMEHEIGHT
local locx, locy, focusx, focusy
on touchStart pId, pX, pY
put the width of graphic "square" into sFRAMEWIDTH
put the height of graphic "square" into sFRAMEHEIGHT
put the loc of graphic "square" into tLoc
put item 1 of tLoc into locx
put item 2 of tLoc into locy
end touchStart
on touchEnd pId
delete variable sTouchArray[pId]
end touchEnd
on touchMove pId, pX, pY
if sTouchArray[pId]["startloc"] is empty then
put (pX & comma & pY) into sTouchArray[pId]["startloc"]
end if
put (pX & comma & pY) into sTouchArray[pId]["currentloc"]
if the number of lines of the keys of sTouchArray is 2 then
# First lets get the data out of the array
put line 1 of the keys of sTouchArray into tPointOne
put line 2 of the keys of sTouchArray into tPointTwo
# First lets calculate the size of the picture base on the distance
# between the two touch points
put sTouchArray[tPointOne]["startloc"] into tStartLoc1
put sTouchArray[tPointTwo]["startloc"] into tStartLoc2
if tStartLoc1 is not empty and tStartLoc2 is not empty then
put item 1 of tStartLoc1 into focusx
put item 2 of tStartLoc1 into focusy
put resizeDistance(tStartLoc1, tStartLoc2) into tStartDistance
put resizeDistance(sTouchArray[tPointOne]["currentloc"], sTouchArray[tPointTwo]["currentloc"]) into tCurrentDistance
resizeGraphic tStartDistance, tCurrentDistance
end if
end if
end touchMove
function resizeDistance pLoc1, pLoc2
local dy, dx, tDistance
put item 2 of pLoc1 - item 2 of pLoc2 into dy
put item 1 of pLoc1 - item 1 of pLoc2 into dx
put sqrt((dy*dy) + (dx*dx)) into tDistance
return tDistance
end resizeDistance
on resizeGraphic pStartDistance, pNewDistance
lock screen
# Work out the percentage change between the old and new image
put round((pNewDistance / pStartDistance) * 100) into tPercentage
# Store the original location of the graphic
put the loc of graphic "square" into tLoc
# Calculate the new width and height
set the width of graphic "square" to round(sFRAMEWIDTH * (tPercentage / 100))
set the height of graphic "square" to round(sFRAMEHEIGHT * (tPercentage / 100))
-- first scale the coordinates of the focus point
put locx + round((focusx-locx) * (tPercentage / 100)) into focusxnew
put locy + round((focusy-locy) * (tPercentage / 100)) into focusynew
-- getting the difference between original and scaled focuspoint
put focusx - focusxnew into focusxdiff
put focusy - focusynew into focusydiff
-- calculate the new location of the graphic
put locx + focusxdiff into locxnew
put locy + focusydiff into locynew
--
put "focus=" & focusx && "focusnew=" & focusxnew && "diff=" & focusxdiff into line 1 of field "info"
put "focus=" & focusy && "focusnew=" & focusynew && "diff=" & focusydiff into line 2 of field "info"
put locx && locxnew into line 3 of field "info"
put locy && locynew into line 4 of field "info"
set the loc of graphic "square" to locxnew,locynew
unlock screen
end resizeGraphic