Touch Monitor Class

Monkey Forums/Monkey Code/Touch Monitor Class

Anatol(Posted 2012) [#1]
Hi,

That's going to be a long post, but I thought my touch monitor class might be useful for some other Monkey coders.

The class monitors any number of touch indices that are supported by the target and records things such as

* the duration of the touch
* the position
* the first position/origin (at what coordinates did the touch begin)
* the offset relative to the origin
* the touch velocity (useful to drag things, etc.)
* obviously what touch indices are touching
* if a touch has just been released

And some useful values for multi touch, e.g.

* the centre position (calculates the centre of multiple touch coordinates)
* the average position (usually equals the centre position, but e.g. when one finger is on the left of the screen and three are on the right the average position is more weighted towards the right)
* the size (if using 2 or more fingers, what's the size/distance between them)
* the size factor (how much increased/decreased the size; useful for pinch gestures)

Checking if the touch is in a certain area

* is one or are multiple touches in a certain area?
* works with rectangular, circular and elliptic areas (rectangles and ellipses can be rotated)
* tons of different methods to use for a lot of different purposes

The class also has an optional coordinate offset and adjustment ratio which is useful when working with a virtual device size. Enter the correct adjustment ratio and you can work with the virtual values (if your virtual device is 1536x2048 but your actual device is 768x1024 then the adjustment ratio is 2.0 and the TouchMonitor will return virtual values). Set an offset if you have borders, which may happen if the aspect ratio of the virtual device and actual device are different.

[EDIT] Download (updated version): http://attic.nugob.org/communities/monkeycoder.co.nz/forum/attachments/touchMonitorTest_2012-12-11.zip

Screenshot of some information that the class can give you:


Here's some sample code that demonstrates how to use it. I usually create a Global TouchMonitor instance that can be used for any purpose within the app. Ideally test this with a touch device and use multiple touches:
[monkeycode]
Strict

Import mojo
Import vector
Import touchMonitor


Global Touch:TouchMonitor


Function Main:Int()
New MyApp
Return 0
End


Class MyApp Extends App

Field circleRadius:Float = 50
Field circleVelocity:Vector
Field circleCoord:Vector

Field rectCoord:Vector
Field showTapInfo:Bool
Field showTouchHoldInfo:Bool


Method OnCreate:Int()
SetUpdateRate(60)
Touch = New TouchMonitor(5) ' monitors five touch indices; change the number to monitor more or less indices

circleVelocity = New Vector
circleCoord = New Vector(DeviceWidth()/2, DeviceHeight()/2)

rectCoord = New Vector(DeviceWidth()-200, DeviceHeight()-200)

Return 0
End

Method OnUpdate:Int()
' must call this!!!
Touch.Update()


'''''''''''''''''''''''''''''''
' SOME EXAMPLES


' MONITORING A RECTANGULART AREA ''''''''''''''''''''

' checks if the user quickly tapped on the rectangle (tap and release in under 0.1 secs)
If Touch.FirstTouchInArea(rectCoord.X, rectCoord.Y, 100, 100) And Touch.Released And Touch.Duration < 100
showTapInfo = Not showTapInfo ' toggle show info
Endif

' checks if the user touches and holds the touch (for more than 0.5 secs
If Touch.FirstTouchInArea(rectCoord.X, rectCoord.Y, 100, 100) And Touch.Duration > 500
showTouchHoldInfo = True
Endif

' reset a value on touch release
If Touch.Released
showTouchHoldInfo = False
Endif


' MONITORING A CIRCULAR AREA ''''''''''''''''''''

' to drag an element: if the touch is in a circular area then add the touch velocity to the coordinates
If Touch.IsInCircle(circleCoord, circleRadius)
circleVelocity.Set(Touch.Velocity)
Else
' gradually decrease the element velocity when the user isn't dragging the element
circleVelocity.Multiply(0.99)
EndIf

' add the velocity to the current coordinates (moves the element)
circleCoord.Add(circleVelocity)

' keep the element within the boundaries of the screen
circleCoord.X = Clamp(circleCoord.X, circleRadius, DeviceWidth() - circleRadius)
circleCoord.Y = Clamp(circleCoord.Y, circleRadius, DeviceHeight() - circleRadius)

' check if the element is at the edge; if so then reverse the velocity (make it bounce off the edge)
If circleVelocity.X <> 0 And circleCoord.X = circleRadius Or circleCoord.X = DeviceWidth() - circleRadius
circleVelocity.X = -circleVelocity.X
Endif
If circleVelocity.Y <> 0 And circleCoord.Y = circleRadius Or circleCoord.Y = DeviceHeight() - circleRadius
circleVelocity.Y = -circleVelocity.Y
Endif


Return 0
End

Method OnRender:Int()
Cls()


' DRAW RECTANGLE AND RELATED INFO '''''''''''''''''''''

' draw a simple rectangle that changes color if touched/clicked
If Touch.IsInArea(rectCoord.X, rectCoord.Y, 100, 100) Then SetColor(255,0,0)
DrawRect(rectCoord.X, rectCoord.Y, 100, 100)
DrawText("tap quickly", rectCoord.X + 10, rectCoord.Y + 10)
DrawText("or", rectCoord.X + 10, rectCoord.Y + 30)
DrawText("touch hold", rectCoord.X + 10, rectCoord.Y + 50)

SetColor(255, 255, 255)

' display something if the user tapped the rectangle
If showTapInfo Then DrawText("Tap the rectangle again to hide this text.", rectCoord.X - 100, rectCoord.Y - 30)
If showTouchHoldInfo Then DrawText("Release the touch to hide this text.", rectCoord.X - 100, rectCoord.Y - 30)


' DRAW CIRCLE AND RELATED INFO '''''''''''''''''''''

' draw a circle (this circle can be dragged according to the code in OnUpdate)
SetColor(255, 128, 0)
DrawCircle(circleCoord.X, circleCoord.Y, 50)
DrawText("drag", circleCoord.X, circleCoord.Y)


' VISUALIZE TOUCH POSITIONS ETC. '''''''''''''''''''''

' draw a circle that shows the touch size (for multi touch only)
SetColor(255, 255, 0)
SetAlpha(0.2)
DrawCircle(Touch.CenterPosition.X, Touch.CenterPosition.Y, Touch.Size/2)

' center position of multi touch
SetAlpha(1)
DrawText("center position", Touch.CenterPosition.X, Touch.CenterPosition.Y - 30)
SetAlpha(0.3)
DrawCircle(Touch.CenterPosition.X, Touch.CenterPosition.Y, 10)

' average position of multi touch (this equals the center position above if two fingers are touching the screen, but it is different
' for 3 or more touch indices as it calculates the average position, and not the centre between the minimum and maximum positions;
' e.g, if one finger is touching on the left and three are touching more on the right, the average position will be more weighted to the right)
SetColor(0, 255, 255)
SetAlpha(1)
DrawText("average position", Touch.AveragePosition.X, Touch.AveragePosition.Y + 20)
DrawCircle(Touch.AveragePosition.X, Touch.AveragePosition.Y, 10)

SetColor(255, 255, 255)
SetAlpha(1)

' draw some info about each touch index
Local textPositionY:Float = 10
For Local index:= Eachin Touch.TouchDownIndices
DrawText("TOUCH INDEX " + index, 10, textPositionY)
DrawText(" first position: " + Touch.FirstPosition(index).X + ", " + Touch.FirstPosition(index).Y, 10, textPositionY + 25)
DrawText(" position: " + Touch.Position(index).X + ", " + Touch.Position(index).Y, 10, textPositionY + 40)
DrawText(" offset: " + Touch.Offset(index).X + ", " + Touch.Offset(index).Y, 10, textPositionY + 55)
DrawText(" velocity: " + Touch.Velocity(index).X + ", " + Touch.Velocity(index).Y, 10, textPositionY + 70)
DrawText(" duration: " + Touch.Duration(index), 10, textPositionY + 85)
textPositionY += 100

' visualize each touch index
DrawText("index " + index, Touch.Position(index).X, Touch.Position(index).Y - 40)
SetAlpha(0.3)
DrawCircle(Touch.Position(index).X, Touch.Position(index).Y, 30)
SetAlpha(1)
Next

' draw touch info (this is mainly useful for multi touch)
If Touch.AnyTouchDown
DrawText("TOUCH INFO (try with multi touch gestures)", 300, 10)
DrawText(" touch size: " + Touch.Size + " (size factor: " + Touch.SizeFactor + ")", 300, 25)
DrawText(" Average position: " + Touch.AveragePosition.X + ", " + Touch.AveragePosition.Y, 300, 40)
DrawText(" Center position: " + Touch.CenterPosition.X + ", " + Touch.CenterPosition.Y, 300, 55)
DrawText(" Average offset: " + Touch.AverageOffset.X + ", " + Touch.AverageOffset.Y, 300, 70)
DrawText(" Center offset: " + Touch.CenterOffset.X + ", " + Touch.CenterOffset.Y, 300, 85)
DrawText(" Average velocity: " + Touch.AverageVelocity.X + ", " + Touch.AverageVelocity.Y, 300, 100)
Endif

Return 0
End

End
[/monkeycode]

I wrote this class very early on when I was quite new to Monkey, so there are a few things I would probably solve in a different way now, but overall it works very well.

Also, I have a separate swipe gesture class which extends the Touch class, but I discovered some bugs. I might post this at a later stage.

(Before I posted the touch monitor class here I found a few bugs which should be fixed now. If you find some more, please let me know.)

Classes required:

touchMonitor.monkey [EDIT] updated code


vector.monkey by Tibit*



* The class works a lot with vectors because I find this is much more useful. I borrowed and slightly adjusted a vector class from Tibit (Thank you!!!): http://www.monkeycoder.co.nz/Community/posts.php?topic=568#4319

I'm sure there are some other similar and useful classes around, e.g. in diddy. If this class suits you, feel free to use and modify it.

Cheers!
Anatol


Tibit(Posted 2012) [#2]
Great Helper class!

I think I'll have great use for this when implementing proper pinch zoom

And happy that you had use of the Vector class :)

Just curious, what can your swipe gesture class be used for? Sounds cool.


Anatol(Posted 2012) [#3]
Thanks Tibit, and thanks for the work on the excellent vector class which is extremely useful. When I first started using Monkey I did everything without vectors, but I scrapped it all when I found your class in the forum - vectors just save so much time!

A pinch gesture should be easy with Touch.SizeFactor that monitors the change in size of a multi touch relative to the original size when the multi touch began.

I will definitely post the swipe class extensions here but I probably won't have the time this week to clean them up.

The swipes I've written so far simply check if a swipe goes in a specific direction that can be defined with an angle, and how long the minimum swipe length needs to be in order to validate. Optional you can also set a "velocity coefficient" which just means that if you do a quick flick then the swipe gesture also validates if it's shorter than the defined minimum length. Swipes can also be defined to only validate if they start in a certain area, and if it requires one, two or more fingers to validate.

I guess it can be extended to more complex gestures, but I didn't need that so far.

I probably have some more code to share here over the next couple of weeks, one of which is a 3D vector class based on your vector class (thanks again!). I just want to clean it up and add proper comments so that it's easier to read here in the forum. It's just a matter of finding the time...


ordigdug(Posted 2012) [#4]
@Anatol -- Any luck finding time? Thanks for sharing.


Anatol(Posted 2012) [#5]
Hi. Thanks for the interest. I'm on a conference this week, and Wellington is full of Hobbits, which I have to admit is a bit distracting. I'll post the swipe extensions when I'm back, some time next week.


pantson(Posted 2012) [#6]
Good stuff.
I was looking at implementing a gesture class too.
Was going to approach it from a completely different angle so I could detect odd shaped gestures (swirls, ticks etc)
May see if I can extend this class, but feel I may have to start again

If I get anywhere I'll post that up.


Anatol(Posted 2012) [#7]
Hi,

OK, here are my swipe gesture classes that extend the touch monitor class above. These are "simple" swipe gestures. You can define any angle and direction for a gesture (with a vector that describes the swipe) and define if the gesture validates with one or more touch indices (fingers). As mentioned earlier, optional you can also set a "velocity coefficient" which just means that if you do a quick flick then the swipe gesture also validates if it's shorter than the defined minimum length. Swipes can also be defined to only validate if they start in a certain area, and if it requires one, two or more fingers to validate.

PLEASE NOTE: If you earlier downloaded the TouchMonitor from the post above you need to update it. I had to change a few lines to fix a bug in the SwipeGesture class. This archive contains all the updated classes, sample code and the new swipe gestures:

Download: http://attic.nugob.org/communities/monkeycoder.co.nz/forum/attachments/touchMonitorTest_2012-12-11.zip

Screenshot of the swipe gesture sample code (see download or below):


Here's some sample code that demonstrate how to set up a gesture that has to start in a rectangular area, one that has to start in a circular area and one multi touch gesture.
[monkeycode]
Strict

Import mojo
Import vector
Import swipeGestures


Global Touch:TouchMonitor


Function Main:Int()
New MyApp
Return 0
End


Class MyApp Extends App

Field swipeGestureRectangularArea:SwipeGestureWithRectangularArea
Field swipeGestureCircularArea:SwipeGestureWithCircularArea
Field multiTouchSwipeGesture:SwipeGesture

Field showSwipeGestureRectangularAreaInfo:Bool
Field showSwipeGestureCircularAreaInfo:Bool
Field showMultiTouchSwipeGestureInfo:Bool


Method OnCreate:Int()
SetUpdateRate(60)

' define a swipe gesture with a rectangular start area that validates if the user swipes a minimum of 200 pixels to the left;
' the 0.1 value in the line below means that swipes that are shorter than 200 pixels can validate if the swipe is quick (a fast flick)
swipeGestureRectangularArea = New SwipeGestureWithRectangularArea(New Vector(-200, 0), 0, DeviceHeight()-50, DeviceWidth(), 50, 0.1)

' define a swipe gesture with a circular start area that validates if the user swipes a minimum of 100 pixels down
swipeGestureCircularArea = New SwipeGestureWithCircularArea(New Vector(0, 100), DeviceWidth() / 2, DeviceHeight()/2, 100)

' define a multi touch swipe gesture that requires 2 touch indices to validate (without start area, so this gesture validates anywhere as long as the direction and length validate)
multiTouchSwipeGesture = New SwipeGesture(New Vector(200, 0), , 2) ' 2 defines the number of touch indices (fingers) that need to touch the screen to validate

Return 0
End

Method OnUpdate:Int()

' MONITORING SWIPE GESTURES ''''''''''''''''''''''

' swipe gesture with rectangular start area
swipeGestureRectangularArea.Update() ' must call this
If swipeGestureRectangularArea.ValidOnRelease
showSwipeGestureRectangularAreaInfo = Not showSwipeGestureRectangularAreaInfo ' toggle the value of a Bool if the swipe gesture validates
Endif

' swipe gesture with circular start area
swipeGestureCircularArea.Update() ' must call this
If swipeGestureCircularArea.ValidOnRelease
showSwipeGestureCircularAreaInfo = Not showSwipeGestureCircularAreaInfo ' toggle the value of a Bool if the swipe gesture validates
Endif

' multi touch swipe gesture
multiTouchSwipeGesture.Update() ' must call this
If multiTouchSwipeGesture.ValidOnRelease
showMultiTouchSwipeGestureInfo = Not showMultiTouchSwipeGestureInfo ' toggle the value of a Bool if the swipe gesture validates
Endif

Return 0
End

Method OnRender:Int()
Cls()

' VISUALIZE THE RECTANGULAR SWIPE GESTURE AREA ''''''''''''''''''''
SetColor(255, 0, 255)
SetAlpha(0.3)
DrawRect(swipeGestureRectangularArea.StartAreaPosition.X, swipeGestureRectangularArea.StartAreaPosition.Y, swipeGestureRectangularArea.StartAreaWidth, swipeGestureRectangularArea.StartAreaHeight)
SetAlpha(1)
DrawText("swipe 200 pixels to the left (or less if you swipe quickly)", swipeGestureRectangularArea.StartAreaPosition.X + 200, swipeGestureRectangularArea.StartAreaPosition.Y + 10)

' display something if the rectangular swipe gesture validated on release
If showSwipeGestureRectangularAreaInfo Then DrawText("Swipe to the left again to hide this text.", swipeGestureRectangularArea.StartAreaPosition.X + 200, swipeGestureRectangularArea.StartAreaPosition.Y + 30)


' VISUALIZE THE CIRCULAR SWIPE GESTURE AREA ''''''''''''''''''''
SetColor(255, 255, 0)
SetAlpha(0.3)
DrawCircle(swipeGestureCircularArea.StartAreaPosition.X, swipeGestureCircularArea.StartAreaPosition.Y, swipeGestureCircularArea.Radius)
SetAlpha(1)
DrawText("swipe 100 pixels down", swipeGestureCircularArea.StartAreaPosition.X - 50, swipeGestureCircularArea.StartAreaPosition.Y)

' display something if the rectangular swipe gesture validated on release
If showSwipeGestureCircularAreaInfo Then DrawText("Swipe down again to hide this text.", swipeGestureCircularArea.StartAreaPosition.X - 50, swipeGestureCircularArea.StartAreaPosition.Y + 20)


' INFOR FOR THE MULTI TOUCH SWIPE GESTURE AREA ''''''''''''''''''''
SetColor(0, 255, 0)
DrawText("swipe with 2 fingers 200 pixels to the right (anywhere)", DeviceWidth() - 400, 10)

' display something if the rectangular swipe gesture validated on release
If showMultiTouchSwipeGestureInfo Then DrawText("Swipe with 2 fingers right again to hide this text.", DeviceWidth() - 400, 25)


' DRAW SWIPE INFOS

' swipe gesture with rectangular start area
If swipeGestureRectangularArea.IsSwiping
SetColor(255, 0, 255)
DrawText("RECTANGULAR SWIPE GESTURE INFO", 10, 10)
DrawText(" Length: " + swipeGestureRectangularArea.Offset.Length, 10, 25)
DrawText(" Angle: " + swipeGestureRectangularArea.Offset.Direction, 10, 40)
DrawText(" Center offset: " + swipeGestureRectangularArea.CenterOffset.X + ", " + swipeGestureRectangularArea.CenterOffset.Y, 10, 55)
If swipeGestureRectangularArea.HasValidStartArea
DrawText("valid start area: YES", 10, 70)
Else
DrawText("valid start area: NO ", 10, 70)
Endif
Endif

' swipe gesture with circular start area
If swipeGestureCircularArea.IsSwiping
SetColor(255, 255, 0)
DrawText("CIRCULAR SWIPE GESTURE INFO", 10, 10)
DrawText(" Length: " + swipeGestureCircularArea.Offset.Length, 10, 25)
DrawText(" Angle: " + swipeGestureCircularArea.Offset.Direction, 10, 40)
DrawText(" Center offset: " + swipeGestureCircularArea.CenterOffset.X + ", " + swipeGestureCircularArea.CenterOffset.Y, 10, 55)
If swipeGestureCircularArea.HasValidStartArea
DrawText("valid start area: YES", 10, 70)
Else
DrawText("valid start area: NO ", 10, 70)
Endif
Endif

' multi touch swipe gesture
If multiTouchSwipeGesture.IsSwiping
SetColor(0, 255, 0)
DrawText("MULTI TOUCH SWIPE GESTURE INFO", DeviceWidth() - 300, 55)
DrawText(" Length: " + multiTouchSwipeGesture.Offset.Length, DeviceWidth() - 300, 70)
DrawText(" Angle: " + multiTouchSwipeGesture.Offset.Direction, DeviceWidth() - 300, 85)
DrawText(" Center offset: " + multiTouchSwipeGesture.CenterOffset.X + ", " + multiTouchSwipeGesture.CenterOffset.Y, DeviceWidth() - 300, 100)
Endif

Return 0
End

End
[/monkeycode]

The new swipe gesture classes


I hope this is helpful for some people. Feel free to post any bugs, improvements, replacements here.

Cheers!
Anatol


RobB(Posted 2013) [#8]
Anatol,

Thank you for a very useful post even for beginners. You have commented so thoroughly, and provided a working example so that even if one doesn't understand how all the code works yet, he can see where to "plug in" to make things "work". I'd suggest in your zip file that you just add some instructions so beginners know to put "vector" and "touchMonitor" in the modules file and the runnable examples in the bananas file. As one who is still learning slowly, but gets lost in the assumed basics sometimes, I appreciate all the help I can get. Your examples make things workable for a beginner and encourage further learning by showing how it all works.

Thanks!


Samah(Posted 2013) [#9]
Looks kinda like Diddy's InputCache in terms of functionality.


rIKmAN(Posted 2013) [#10]
Anatol, thanks for this - very helpful! :)