Created
October 30, 2012 07:10
-
-
Save trentbrooks/3978737 to your computer and use it in GitHub Desktop.
ProcessingJS Multitouch Demo
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
<html> | |
<head> | |
<title>Multitouch ProcessingJS Test</title> | |
<meta content='True' name='HandheldFriendly' /> | |
<meta content='width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;' name='viewport' /> | |
<meta name="viewport" content="width=device-width" /> | |
<script type="text/javascript" src="http://trentbrooks.com/files/MultitouchHitTestDemo/processing-1.4.1.js"></script> | |
<script type="text/processing" data-processing-target="targetcanvas"> | |
/* @pjs crisp="true"; | |
font="http://trentbrooks.com/files/MultitouchHitTestDemo/data/AmaticSC.ttf"; | |
preload="http://trentbrooks.com/files/MultitouchHitTestDemo/data/sky.jpg,http://trentbrooks.com/files/MultitouchHitTestDemo/data/rathausFrog.jpg,http://trentbrooks.com/files/MultitouchHitTestDemo/data/img.jpg,http://trentbrooks.com/files/MultitouchHitTestDemo/data/sky.jpg,http://trentbrooks.com/files/MultitouchHitTestDemo/data/jelly.jpg"; | |
*/ | |
//-------------------------------------------------------------- | |
/* | |
ProcessingJS Multitouch example by Trent Brooks. | |
I couldn't find much info on the TouchEvents api related to ProcessingJS, so I made this demo | |
which uses native browser touch events. The examples that I did find, were not using the | |
correct lists (changedTouches) for what I needed. | |
- Plenty of todos... | |
*/ | |
//-------------------------------------------------------------- | |
HashMap<int, TouchCursor> touchCursors = new HashMap<int, TouchCursor>(); | |
int touchPosX = 0; // debug | |
int touchPosY = 0; // debug | |
int touchId = -1; // debug | |
String touchStatus = "NONE"; // debug | |
boolean bringTouchedObjectsToFront = true; | |
// display list | |
ArrayList<InteractiveObject> touchableObjects = new ArrayList<InteractiveObject>(); | |
InteractiveObject bg; | |
void setup() | |
{ | |
size(screenWidth, screenHeight); | |
frameRate(60); | |
background(180); | |
smooth(); | |
PFont myfont = createFont("http://trentbrooks.com/files/MultitouchHitTestDemo/data/AmaticSC.ttf");//,24);// loadFont("data/AmaticSC.ttf"); // custom font | |
textFont(myfont, 24); | |
// make some movable things | |
InteractiveObject obj1 = new InteractiveObject(550,100);//int(width * .5), int(height * .5)); | |
obj1.loadImg("http://trentbrooks.com/files/MultitouchHitTestDemo/data/jelly.jpg"); | |
touchableObjects.add(obj1); | |
InteractiveObject obj2 = new InteractiveObject(400,250); | |
obj2.loadImg("http://trentbrooks.com/files/MultitouchHitTestDemo/data/sky.jpg"); | |
touchableObjects.add(obj2); | |
InteractiveObject obj3 = new InteractiveObject(450,350); | |
obj3.loadImg("http://trentbrooks.com/files/MultitouchHitTestDemo/data/rathausFrog.jpg"); | |
touchableObjects.add(obj3); | |
// blank object | |
InteractiveObject obj4 = new InteractiveObject(325,175); | |
touchableObjects.add(obj4); | |
// sub layers | |
//InteractiveObject obj3SubLayer = new InteractiveObject(0,0); | |
//obj3SubLayer.loadImg("data/sky.jpg"); | |
//obj3.addLayer(obj3SubLayer); | |
// lets not add this one to the touchableObjects list and update manually | |
bg = new InteractiveObject(0,0); | |
bg.loadImg("http://trentbrooks.com/files/MultitouchHitTestDemo/data/img.jpg"); | |
} | |
void draw() | |
{ | |
background(180); | |
fill(255); | |
// draw interactive bg | |
bg.draw(); | |
// draw all interactive objects | |
for (int k = 0; k < touchableObjects.size(); k++) { | |
InteractiveObject io = (InteractiveObject) touchableObjects.get(k); | |
//io.rotation += .01; | |
io.draw(); | |
} | |
// draw touches (for debug) | |
int prevKey = -1; // note keys could be anything- on ios devices they are long numbers, in chrome win7 they start at 0 | |
for(int key : touchCursors.keySet()) { | |
TouchCursor tc = (TouchCursor) touchCursors.get(key); | |
tc.debugDraw(); | |
if(prevKey != -1) { | |
TouchCursor prevTc = (TouchCursor) touchCursors.get(prevKey); | |
line(prevTc.x, prevTc.y, tc.x, tc.y); | |
} | |
prevKey = key; | |
} | |
// show info about the last touch + object | |
fill(255); | |
text("Multitouch Hit Test demo" + | |
"\nFrame rate: " + int(frameRate) + | |
"\nTouch Status: " + touchStatus + | |
"\nTotal touches: " + touchCursors.size() + | |
"\nCurrent touch id: " + touchId + | |
"\nCurrent touch position: " + touchPosX + ", " + touchPosY + | |
"\nTotal objects: " + touchableObjects.size(), 30, 40); | |
} | |
//-------------------------------------------------------------- | |
/* | |
Touch events. | |
- Lists in TouchEvent: changedTouches, targetTouches, touches | |
- For description see: http://www.sitepen.com/blog/2011/12/07/touching-and-gesturing-on-iphone-android-and-more/ | |
*/ | |
void touchStart(TouchEvent touchEvent) { | |
touchStatus = "start"; | |
for (int i = 0; i < touchEvent.changedTouches.length; i++) { | |
// info we need from the event | |
touchPosX = touchEvent.changedTouches[i].clientX; | |
touchPosY = touchEvent.changedTouches[i].clientY; | |
touchId = touchEvent.changedTouches[i].identifier; | |
// create a new TouchCursor for every touch and colour them for debuging: yellow 1st touch, blue 2nd touch, pink 3rd touch, and random 4th + | |
// could store the event itself, but this is a clearer for debugging and can be simulated by mouse | |
TouchCursor tc = new TouchCursor(touchPosX, touchPosY, touchId); | |
int totalTouchesAdded = touchCursors.size(); | |
tc.clr = (totalTouchesAdded == 0) ? color(255, 255, 0) : (totalTouchesAdded == 1) ? color(0, 255, 255) : (totalTouchesAdded == 2) ? color(255, 0, 255) : color(random(255), random(255),random(255)); | |
touchCursors.put(touchId, tc); | |
// update interactive objects with the TouchCursor | |
// all in reverse order, so top items get touches first | |
// the touched item can move to the front of the display list | |
int touchedObjectIndex = -1; | |
for (int k = touchableObjects.size() - 1; k >= 0; k--) { | |
InteractiveObject io = (InteractiveObject) touchableObjects.get(k); | |
if(io.onTouchedDown(tc)) { | |
touchedObjectIndex = k; | |
} | |
} | |
// update bg seperately | |
bg.onTouchedDown(tc); | |
// move object to front | |
if(bringTouchedObjectsToFront) { | |
if(touchedObjectIndex != -1) { | |
InteractiveObject frontObj = (InteractiveObject) touchableObjects.get(touchedObjectIndex); | |
touchableObjects.remove(touchedObjectIndex); | |
touchableObjects.add(frontObj); | |
} | |
} | |
} | |
} | |
void touchMove(TouchEvent touchEvent) { | |
touchStatus = "move"; | |
for (int i = 0; i < touchEvent.changedTouches.length; i++) { | |
touchPosX = touchEvent.changedTouches[i].clientX; | |
touchPosY = touchEvent.changedTouches[i].clientY; | |
touchId = touchEvent.changedTouches[i].identifier; | |
TouchCursor tc = (TouchCursor) touchCursors.get(touchId); | |
if(tc != null) { | |
tc.update(touchPosX, touchPosY); | |
// update ALL interactive objects with active TouchCursor | |
for (int k = touchableObjects.size() - 1; k >= 0; k--) { | |
InteractiveObject io = (InteractiveObject) touchableObjects.get(k); | |
io.onTouchedMove(tc); | |
} | |
// update bg seperately | |
bg.onTouchedMove(tc); | |
} | |
} | |
} | |
void touchEnd(TouchEvent touchEvent) { | |
touchStatus = "end"; | |
for (int i = 0; i < touchEvent.changedTouches.length; i++) { | |
touchId = touchEvent.changedTouches[i].identifier; | |
TouchCursor tc = (TouchCursor) touchCursors.get(touchId); | |
if(tc != null) { | |
// update ALL interactive objects with active TouchCursor | |
for (int k = touchableObjects.size() - 1; k >= 0; k--) { | |
InteractiveObject io = (InteractiveObject) touchableObjects.get(k); | |
io.onTouchedUp(tc); | |
} | |
// update bg seperately | |
bg.onTouchedUp(tc); | |
// delete current touch cursor from the list | |
touchCursors.remove(touchId); | |
} | |
} | |
} | |
void touchCancel(TouchEvent touchEvent) { | |
touchEnd(touchEvent); | |
} | |
//-------------------------------------------------------------- | |
/* Simulating touch events. | |
- for debugging only, id for the fake touch cursor is always 0. | |
- delete this code soon | |
*/ | |
void mousePressed() { | |
//simulate touch start | |
touchStatus = "mouse start"; | |
touchPosX = mouseX; | |
touchPosY = mouseY; | |
touchId = 0; | |
TouchCursor tc = new TouchCursor(touchPosX, touchPosY, touchId); | |
touchCursors.put(touchId, tc); | |
int touchedObjectIndex = -1; | |
for (int k = touchableObjects.size() - 1; k >= 0; k--) { | |
InteractiveObject io = (InteractiveObject) touchableObjects.get(k); | |
if(io.onTouchedDown(tc)) { | |
touchedObjectIndex = k; | |
} | |
} | |
bg.onTouchedDown(tc); | |
// move to front | |
if(bringTouchedObjectsToFront) { | |
if(touchedObjectIndex != -1) { | |
InteractiveObject frontObj = (InteractiveObject) touchableObjects.get(touchedObjectIndex); | |
touchableObjects.remove(touchedObjectIndex); | |
touchableObjects.add(frontObj); | |
} | |
} | |
} | |
void mouseDragged() { | |
//simulate touch moved | |
touchStatus = "mouse move"; | |
touchPosX = mouseX; | |
touchPosY = mouseY; | |
touchId = 0; | |
TouchCursor tc = (TouchCursor) touchCursors.get(touchId); | |
if(tc != null) { | |
tc.update(touchPosX, touchPosY); | |
// update ALL interactive objects with active TouchCursor | |
for (int k = touchableObjects.size() - 1; k >= 0; k--) { | |
InteractiveObject io = (InteractiveObject) touchableObjects.get(k); | |
io.onTouchedMove(tc); | |
} | |
bg.onTouchedMove(tc); | |
} | |
} | |
void mouseReleased() { | |
//simulate touch end | |
touchStatus = "mouse end"; | |
touchId = 0; | |
TouchCursor tc = (TouchCursor) touchCursors.get(touchId); | |
if(tc != null) { | |
// update ALL interactive objects with active TouchCursor | |
for (int k = touchableObjects.size() - 1; k >= 0; k--) { | |
InteractiveObject io = (InteractiveObject) touchableObjects.get(k); | |
io.onTouchedUp(tc); | |
} | |
bg.onTouchedUp(tc); | |
// delete current touch cursor from the list | |
touchCursors.remove(touchId); | |
} | |
} | |
//-------------------------------------------------------------- | |
/* | |
TouchCursor class to store the important info from TouchEvents. | |
*/ | |
//-------------------------------------------------------------- | |
class TouchCursor { | |
int x = 0; | |
int y = 0; | |
int id = -1; | |
boolean busy; // we don't want one cursor to control multiple interactive objects | |
color clr = color(255); // default white | |
TouchCursor(int tx, int ty, int identifier) { | |
x = tx; | |
y = ty; | |
id = identifier; | |
} | |
void update(int tx, int ty) { | |
x = tx; | |
y = ty; | |
} | |
void debugDraw() { | |
fill(clr); | |
ellipse(x, y, 60, 60); // draw a 60px circle | |
fill(0); | |
text("id: " + id, x - 15, y); // display the cursor id | |
} | |
} | |
//-------------------------------------------------------------- | |
/* | |
InteractiveObjects: Simple display object. | |
- requires a TouchCursor for hit testing | |
- automatic tweening | |
- can add other InteractiveObjects to itself (display lists) | |
Todo: | |
- make scalable rotatable | |
- test adding layers, add TouchCursors to sub layers | |
- ability to toggle the moving/dragging functionality so you can use as normal gui element eg. button | |
*/ | |
//-------------------------------------------------------------- | |
class InteractiveObject { | |
// positional/size/rotation | |
PVector position = new PVector(); | |
PVector offset = new PVector(); | |
PVector currentPosition = new PVector(); | |
float width = 100, height = 100; | |
float currentWidth = 100, currentHeight = 100; | |
float rotation = 0; | |
float currentRotation = 0; | |
// display | |
PImage img; | |
boolean imgLoaded = false; | |
float tweenSpeed = 0.25; | |
color clr = color(255); | |
// display list- add other InteractiveObjects to this | |
ArrayList layers = new ArrayList(); | |
InteractiveObject parentLayer = null; | |
// 2 cursors can touch the object for scale/rotate | |
int[] touchIds = {-1,-1}; | |
boolean isPressed = false; | |
InteractiveObject(float posX, float posY) { | |
setPosition(posX, posY); | |
setOffset(posX, posY); | |
setCurrentPosition(posX, posY); | |
} | |
void loadImg(String path) { | |
img = loadImage(path); | |
width = currentWidth = img.width; | |
height = currentHeight = img.height; | |
imgLoaded = true; | |
} | |
void addLayer(InteractiveObject layer) { | |
layers.add(layer); | |
layer.parentLayer = this; // does this work? | |
layer.setPosition(-currentWidth * .5, -currentHeight * .5); // need it to draw from top left | |
//layer.width = layer.parentLayer.width; | |
} | |
void updateTransform() { | |
// ease to position, scale, rotation | |
currentPosition.x += (position.x - currentPosition.x) * tweenSpeed; | |
currentPosition.y += (position.y - currentPosition.y) * tweenSpeed; | |
currentWidth += (width - currentWidth) * tweenSpeed; | |
currentHeight += (height - currentHeight) * tweenSpeed; | |
currentHeight += (height - currentHeight) * tweenSpeed; | |
currentRotation += (rotation - currentRotation) * tweenSpeed; | |
} | |
void draw() { | |
updateTransform(); | |
pushMatrix(); | |
translate(currentPosition.x + (currentWidth * .5),currentPosition.y + (currentHeight * .5)); | |
rotate(currentRotation); | |
fill(clr); | |
if(imgLoaded) { | |
image(img, -currentWidth * .5, -currentHeight * .5, currentWidth, currentHeight); | |
} else { | |
rect(-currentWidth * .5, -currentHeight * .5, currentWidth, currentHeight); | |
} | |
// update/draw all sublayers | |
for(int i = 0; i < layers.size(); i++) { | |
InteractiveObject io = (InteractiveObject) layers.get(i); | |
io.draw(); | |
} | |
popMatrix(); | |
} | |
void setPosition(float dx, float dy) { | |
position.x = dx; | |
position.y = dy; | |
} | |
void setCurrentPosition(float dx, float dy) { | |
currentPosition.x = dx; | |
currentPosition.y = dy; | |
} | |
void setOffset(float dx, float dy) { | |
offset.x = dx; | |
offset.y = dy; | |
} | |
int getObjectTouchCursorIndex(int touchId) { | |
for(int i = 0; i < touchIds.length; i++) { | |
if(touchIds[i] == touchId) return i; | |
} | |
return -1; | |
} | |
boolean onTouchedDown(TouchCursor tc) { | |
if(tc.busy) return false; | |
if (hitTest(tc.x, tc.y)) { | |
isPressed = true; | |
if(touchIds[0] == -1) { | |
touchIds[0] = tc.id; // first touch | |
} else { | |
touchIds[1] = tc.id; // any other touches (note- only uses 2) | |
} | |
setOffset(tc.x - position.x, tc.y - position.y); | |
tc.busy = true; | |
return true; | |
} | |
return false; | |
} | |
boolean onTouchedMove(TouchCursor tc) { | |
if (isPressed) { | |
int objectTouchCursorIndex = getObjectTouchCursorIndex(tc.id); | |
if(objectTouchCursorIndex >= 0) { | |
setPosition(tc.x - offset.x, tc.y - offset.y); | |
return true; | |
} | |
} | |
return false; | |
} | |
boolean onTouchedUp(TouchCursor tc) { | |
if (isPressed ) { | |
int objectTouchCursorIndex = getObjectTouchCursorIndex(tc.id); | |
if(objectTouchCursorIndex >= 0) { | |
touchIds[objectTouchCursorIndex] = -1; | |
if(touchIds[0] == -1 && touchIds[1] == -1) { | |
isPressed = false; | |
} | |
} | |
return true; | |
} | |
return false; | |
} | |
boolean hitTest(float tx, float ty) { | |
return tx >= currentPosition.x && tx <= currentPosition.x + currentWidth && ty >= currentPosition.y && ty <= currentPosition.y + currentHeight; | |
} | |
} | |
</script> | |
</head> | |
<body style="margin:0px;padding:0px;"> | |
<pre></pre> | |
<canvas id="targetcanvas" style="margin:0px;padding:0px;"> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment