DAN ZEN EXPO - CODE EXHIBIT -
GOOSE
package samples {
// GOOSE INTRODUCTION
// Goose lets you work with multiple cursor inputs - like multitouch
// http://gooseflash.wordpress.com - by inventor Dan Zen - http://www.danzen.com
// if you are using Goose for commercial purposes, you are welcome to donate to Dan Zen
// donations can be made to agency@danzen.com at http://www.paypal.com
// INSTALLING CLASSES
// suggested installation:
// create a "classes" folder on your hard drive - for example c:\classes
// add the classes folder to your Flash class path:
// Flash menu choose Edit > Preferences > ActionScript - ActionScript 3 Settings
// then use the + sign for the source path box to add the path to your classes folder
// put the provided com/ directory with its folders and files in the classes folder
// the readme has more information if you need it
// GOOSE OVERVIEW
// Goose splits into two parts:
// 1. A multitouch emulator (GooseData, GooseRobin, Online Mouse Nodes)
// with the Goose Emulator you run the online Mouse Nodes on two or more computers
// each mouse then feeds data through GooseRobin to GooseData
// you then feed the data from GooseData into Goose (part 2)
// 2. A multitouch processor (Goose, GooseEvent)
// Goose takes in multitouch data (like from the emulator in part 1)
// and shows multiple cursors in your application
// if you use the Goose Emulator then each mouse shows up as a cursor
// Goose and GooseEvent provide a set of methods and events for using the cursors
// You continue to use Goose when going from an emulator to real multitouch data
// USING GOOSE
// below is a sample that uses Goose
// to make it work you have to run two mouse nodes at
// http://www.danzen.com/goose/node.html
// enter the name of the application that you pass to GooseData() below
// you should change this to a relatively unique name so you do not conflict with others ;-)
// see the videos on the Goose site for a guide to how to make all this work
// the steps you can see below are as follows
// import com.danzen.interfaces.goose.* in your document class
// create a new GooseData object and pass it the name of your application
// create a Goose object in your document class
// add an Event.CHANGE event to your GooseData object and have it call a method
// in the method pass the GooseData's xmlData property to the Goose update() method
// this separation allows you to easily change from the emulator to real multitouch data
// when you are ready to do so, just pass the real data to the Goose update() method
// the data must be in the form of ManyML:
//
//
//
//
// if z >= 0 then the item is treated as a touch in Goose
// if z < 0 then the item is ignored by all events in Goose
// CONSTRUCTOR
// public function Goose(theShowCursors:Boolean = true):void
// constructor to start your multitouch processor
// PARAMETERS:
// theShowCursors:Boolean
// do you want to see the cursors - defaults to true
// but remember, in multitouch you do not see cursors usually
// as they are not there for rollovers and under the fingers for presses
// EVENTS
// GooseEvent.TOUCH
// a touch up and down (see the touchDelay property) - cursor z property will be >= 0
// GooseEvent.TOUCH_DOWN
// a touch down (like a MOUSE_DOWN) - cursor z property will be >= 0
// GooseEvent.TOUCH_UP
// a touch up (like a MOUSE_UP) - cursor z property will be < 0
// GooseEvent.TOUCH_MOVE
// moving while touching (like a MOUSE_MOVE)
// will be automatically removed on TOUCH_UP
// GooseEvent.PRESS
// a press up and down on a specified item (see the pressDelay property) - cursor z >= 0
// GooseEvent.PRESS_DOWN
// a press down on a specified item - cursor z >= 0
// GooseEvent.PRESS_UP
// a press up on a specified item - cursor z < 0
// GooseEvent.PRESS_MOVE
// moving after pressing down on a specified item
// continues to register even if cursor is no longer on item
// will be automatically removed on PRESS_UP
// GooseEvent.PICK_UP
// the first time an item has been picked up for a follow or size
// GooseEvent.PUT_DOWN
// the last time an item has been put down for last follow or size
// METHODS
// update(d:XML):void
// receives the XML data in the form of ManyML (http://manyml.wordpress.com)
// makes the cursors move and dispatches the touch and press events
// addPressListener(e:String, obj:InteractiveObject, fun:Function):void
// e:String - a GooseEvent press-type event
// obj:InteractiveObject - a MovieClip for instance on which you want to track the event
// fun:Function - the function you want to call when the event on the obj happens
// this will pass to the function a GooseObject with the following properties:
// cursor:Sprite - a reference to the cursor object
// cursorZ:Number - the z value of the cursor
// cursorID:String - the id value of the cursor
// obj:InteractiveObject - a reference to the object that triggered the event
// removePressListener(e:String, obj:InteractiveObject, fun:Function):void
// removes the specified event from the specified object having related function
// startFollow(cursor:Sprite, obj:InteractiveObject, damp:Number=.1, type:String=Goose.DRAG_AVERAGE):void
// make the specified cursor partake in the moving of specified object
// when the cursor is no longer pressing, its effect on the follow is removed
// damp is the speed at which the object follows - 1 instant, 0 not at all (.1 is nice)
// type is the rules for your follow:
// Goose.DRAG_LOCKED exclusive drag for life of cursor except if already locked
// Goose.DRAG_OVERRIDE latest drag is the only drag (unless DRAG_LOCKED)
// Goose.DRAG_AVERAGE (default) all drags are averaged (unless DRAG_OVERRIDE or DRAG_LOCKED)
// stopFollow(cursor:Sprite, obj:InteractiveObject):void
// stop following the specified cursor with the specified object
// stopAllFollows():void
// stop all current following - future follows will still run
// startScale(cursor:Sprite, obj:InteractiveObject, damp:Number=.1, proportional:Boolean=true, registration:String=Goose.REGISTRATION_AVERAGE):void
// make the specified cursor partake in the scaling of the specified object
// when the cursor is no longer pressing, its effect on the scaling is removed
// damp is the speed at which the object scales - 1 instant, 0 not at all (.1 is nice)
// the Boolean is proportional scaling (true - default) or not (false)
// the last parameter is where it scales from
// Goose.REGISTRATION_CENTER - from the object center
// Goose.REGISTRATION_POINT - from the registration point
// Goose.REGISTRATION_AVERAGE (default) - from the average starting point of the cursors
// stopScale(cursor:Sprite, obj:InteractiveObject):void
// stop scaling the specified object with the specified cursor
// stopAllScales():void
// stop all current scaling - future scales will still run
// dispose():void
// removes listeners, closes socket, deletes data objects
// PROPERTIES
// showCursors:Boolean - do you want to see the cursors (true) or not (false)
// touchDelay:Number = .5 - max seconds until a touch is not a touch
// pressDelay:Number = .5 - max seconds until a press is not a press
// touchRadius:Number = 10 - max pixel movement until a touch is not a touch
// pressRadius:Number = 10 - max pixel movement until a press is not a press
// STATIC CONSTANTS
// Goose.DRAG_LOCKED - see startFollow() method
// Goose.DRAG_OVERRIDE - see startFollow() method
// Goose.DRAG_AVERAGE - see startFollow() method
// Goose.REGISTRATION_CENTER - see startScale() method
// Goose.REGISTRATION_POINT - see startScale() method
// Goose.REGISTRATION_AVERAGE - see startScale() method
import flash.display.MovieClip;
import flash.text.*;
import flash.events.*
import flash.ui.Mouse;
import com.danzen.interfaces.goose.*;
public class GooseExample extends MovieClip {
// this sample lets you move and resize a picture using two or more cursors
// if you scale the picture down enough then it turns into a crumple
// you can then put the crumple into the recycle bin
// press the restore button (shows up once you throw away) to restore the picture
// it was quite the feeling to be able to use multiuser data with Flash
// Goose helps designers and developers work with multitouch without external systems
// for those experimenting with blob detection or multitouch devices
// you can take your data, convert it to ManyML and pass it to the Goose update() method
// this you will do rather than run the emulator
// then you can make use of the methods and events that Goose provides
// to touch, press, scale and follow with multiple cursors
// these were not paricularly easy to write and one day a rotate method may come to being
// if you happen to write a rotate method, please let us know
private var myGoose:Goose;
private var myGooseData:GooseData;
public function GooseExample() {
trace ("hi from GooseExample");
// set up receiving data from the emulator
myGooseData = new GooseData("default"); // please change this to your application name
myGooseData.addEventListener(Event.CHANGE, feedGoose);
// set up Goose multiuser data processor that will show you cursors
myGoose = new Goose();
addChild(myGoose);
// here are the events we use - there is a PRESS event too later on (like a CLICK)
// the press events go on specified Interactive Objects (MovieClips and Sprites, etc.)
// so you will note that there is one more parameter in the middle than the addEventListener()
myGoose.addPressListener(GooseEvent.PRESS_DOWN, myPic, callPressDown); // there is a PRESS_UP too
myGoose.addPressListener(GooseEvent.PRESS_MOVE, myPic, callPressMove);
myGoose.addPressListener(GooseEvent.PUT_DOWN, myPic, callPutDown); // there is a PICK_UP too
// here are samples of generic listeners - for any touch
// these will give you a reference to the cursor that caused the event
// they are not used in the example but you can play around with them
myGoose.addEventListener(GooseEvent.TOUCH, callTouch);
myGoose.addEventListener(GooseEvent.TOUCH_DOWN, callTouchDown);
myGoose.addEventListener(GooseEvent.TOUCH_MOVE, callTouchMove);
myGoose.addEventListener(GooseEvent.TOUCH_UP, callTouchUp);
// set up start picture properties for later reset
myPic.inner.mouseEnabled = false;
myPic.startScale = myPic.scaleX;
myPic.startX = myPic.x;
myPic.startY = myPic.y;
myGarbage.startIndex = getChildIndex(myGarbage);
myGarbage.activeIndex = getChildIndex(myPic);
}
private function feedGoose(e:Event) {
// pass the data from GooseData into the update() method of Goose
// this is what constantly feeds the multiple cursor data into Goose
// you can pass in your own multitouch data in the form of ManyML
// http://manyml.wordpress.com - a very simple XML language
myGoose.update(e.target.xmlData);
}
private function callPressDown(e:GooseEvent) {
// startFollow() parameters:
// the first two are what cursor to follow and with what object
// the number is a damping - 1 instant, 0 not at all (.1 is nice)
// the next is the type of follow:
// Goose.DRAG_LOCKED exclusive drag for life of cursor except if already locked
// Goose.DRAG_OVERRIDE latest drag is the only drag (unless DRAG_LOCKED)
// Goose.DRAG_AVERAGE (default) all drags are averaged (unless DRAG_OVERRIDE or DRAG_LOCKED)
myGoose.startFollow(e.cursor, e.obj, .1, Goose.DRAG_AVERAGE);
// no need for a PRESS_UP as the follow method automatically stops
// following when the cursor is gone and so does the PRESS_MOVE
// startScale() parameters:
// the first two are what cursor to scale with and with what object
// the number is the damping - 1 instant, 0 not at all (.1 is nice)
// the Boolean is proportional scaling (true - default) or not (false)
// the last parameter is where it scales from
// Goose.REGISTRATION_CENTER - from the object center
// Goose.REGISTRATION_POINT - from the registration point
// Goose.REGISTRATION_AVERAGE (default) - from the average starting point of the cursors
myGoose.startScale(e.cursor, e.obj, .1, true, Goose.REGISTRATION_AVERAGE);
}
private function callPressMove(e:GooseEvent) {
// if the scale is small enough then turn picture to crumple
if (myPic.scaleX < .25) {
myPic.gotoAndStop(2);
setChildIndex(myGarbage,myGarbage.activeIndex);
} else {
myPic.gotoAndStop(1);
}
}
private function callPutDown(e:GooseEvent) {
if (e.obj.hitTestObject(myGarbage) && MovieClip(e.obj).currentFrame == 2) {
// hide picture and show restoreBut
e.obj.alpha = 0;
e.obj.mouseEnabled = false;
restoreBut.alpha = .7;
myGoose.addPressListener(GooseEvent.PRESS, restoreBut, restorePic);
}
}
private function restorePic(e:GooseEvent) {
// reset picture and hide restoreBut
myPic.alpha = 1;
myPic.mouseEnabled = true;
myPic.scaleX = myPic.scaleY = myPic.startScale;
myPic.gotoAndStop(1);
myPic.x = myPic.startX;
myPic.y = myPic.startY;
setChildIndex(myGarbage,myGarbage.startIndex);
restoreBut.alpha = 0;
myGoose.removePressListener(GooseEvent.PRESS, restoreBut, restorePic);
}
private function callTouch(e:GooseEvent) {
//trace ("touch ID = " + e.cursorID);
}
private function callTouchDown(e:GooseEvent) {
//trace ("touch down ID = " + e.cursorID);
}
private function callTouchMove(e:GooseEvent) {
//trace ("touch move ID = " + e.cursorID);
}
private function callTouchUp(e:GooseEvent) {
//trace ("touch up ID = " + e.cursorID);
}
private function callPicUp(e:GooseEvent) {
//trace ("Pick up ID = " + e.cursorID);
}
}
}
package com.danzen.interfaces.goose {
// GOOSE INTRODUCTION
// Goose lets you work with multiple cursor inputs - like multitouch
// http://gooseflash.wordpress.com - by inventor Dan Zen - http://www.danzen.com
// if you are using Goose for commercial purposes, you are welcome to donate to Dan Zen
// donations can be made to agency@danzen.com at http://www.paypal.com
// INSTALLING CLASSES
// suggested installation:
// create a "classes" folder on your hard drive - for example c:\classes
// add the classes folder to your Flash class path:
// Flash menu choose Edit > Preferences > ActionScript - ActionScript 3 Settings
// then use the + sign for the source path box to add the path to your classes folder
// put the provided com/ directory with its folders and files in the classes folder
// the readme has more information if you need it
// GOOSE OVERVIEW
// Goose splits into two parts:
// 1. A multitouch emulator (GooseData, GooseRobin, Online Mouse Nodes)
// with the Goose Emulator you run the online Mouse Nodes on two or more computers
// each mouse then feeds data through GooseRobin to GooseData
// you then feed the data from GooseData into Goose (part 2)
// 2. A multitouch processor (Goose, GooseEvent)
// Goose takes in multitouch data (like from the emulator in part 1)
// and shows multiple cursors in your application
// if you use the Goose Emulator then each mouse shows up as a cursor
// Goose and GooseEvent provide a set of methods and events for using the cursors
// You continue to use Goose when going from an emulator to real multitouch data
// USING GOOSE
// see the sample as file for an example of how to use Goose
// import com.danzen.interfaces.goose.* in your document class
// create a new GooseData object and pass it the name of your application
// create a Goose object in your document class
// add an Event.CHANGE event to your GooseData object and have it call a method
// in the method pass the GooseData's xmlData property to the Goose update() method
// this separation allows you to easily change from the emulator to real multitouch data
// when you are ready to do so, just pass the real data to the Goose update() method
// the data must be in the form of ManyML:
// <manyml>
// <item id="1000" x="984" y="1" z="-100" />
// <item id="2000" x="243" y="7" z="0" />
// </manyml>
// if z >= 0 then the item is treated as a touch in Goose
// if z < 0 then the item is ignored by all events in Goose
// Goose has a few methods and many properties described below
import flash.display.Sprite;
import flash.events.*;
import flash.display.InteractiveObject;
import flash.utils.getTimer;
import flash.utils.Dictionary;
import flash.geom.Matrix;
import fl.motion.MatrixTransformer;
public class Goose extends Sprite {
// CONSTRUCTOR
// public function Goose(theShowCursors:Boolean = true):void
// constructor to start your multitouch processor
// PARAMETERS:
// theShowCursors:Boolean
// do you want to see the cursors - defaults to true
// but remember, in multitouch you do not see cursors usually
// as they are not there for rollovers and under the fingers for presses
// EVENTS
// GooseEvent.TOUCH
// a touch up and down (see the touchDelay property) - cursor z property will be >= 0
// GooseEvent.TOUCH_DOWN
// a touch down (like a MOUSE_DOWN) - cursor z property will be >= 0
// GooseEvent.TOUCH_UP
// a touch up (like a MOUSE_UP) - cursor z property will be < 0
// GooseEvent.TOUCH_MOVE
// moving while touching (like a MOUSE_MOVE)
// will be automatically removed on TOUCH_UP
// GooseEvent.PRESS
// a press up and down on a specified item (see the pressDelay property) - cursor z >= 0
// GooseEvent.PRESS_DOWN
// a press down on a specified item - cursor z >= 0
// GooseEvent.PRESS_UP
// a press up on a specified item - cursor z < 0
// GooseEvent.PRESS_MOVE
// moving after pressing down on a specified item
// continues to register even if cursor is no longer on item
// will be automatically removed on PRESS_UP
// GooseEvent.PICK_UP
// the first time an item has been picked up for a follow or size
// GooseEvent.PUT_DOWN
// the last time an item has been put down for last follow or size
// METHODS
// update(d:XML):void
// receives the XML data in the form of ManyML (http://manyml.wordpress.com)
// makes the cursors move and dispatches the touch and press events
// addPressListener(e:String, obj:InteractiveObject, fun:Function):void
// e:String - a GooseEvent press-type event
// obj:InteractiveObject - a MovieClip for instance on which you want to track the event
// fun:Function - the function you want to call when the event on the obj happens
// this will pass to the function a GooseObject with the following properties:
// cursor:Sprite - a reference to the cursor object
// cursorZ:Number - the z value of the cursor
// cursorID:String - the id value of the cursor
// obj:InteractiveObject - a reference to the object that triggered the event
// removePressListener(e:String, obj:InteractiveObject, fun:Function):void
// removes the specified event from the specified object having related function
// startFollow(cursor:Sprite, obj:InteractiveObject, damp:Number=.1, type:String=Goose.DRAG_AVERAGE):void
// make the specified cursor partake in the moving of specified object
// when the cursor is no longer pressing, its effect on the follow is removed
// damp is the speed at which the object follows - 1 instant, 0 not at all (.1 is nice)
// type is the rules for your follow:
// Goose.DRAG_LOCKED exclusive drag for life of cursor except if already locked
// Goose.DRAG_OVERRIDE latest drag is the only drag (unless DRAG_LOCKED)
// Goose.DRAG_AVERAGE (default) all drags are averaged (unless DRAG_OVERRIDE or DRAG_LOCKED)
// stopFollow(cursor:Sprite, obj:InteractiveObject):void
// stop following the specified cursor with the specified object
// stopAllFollows():void
// stop all current following - future follows will still run
// startScale(cursor:Sprite, obj:InteractiveObject, damp:Number=.1, proportional:Boolean=true, registration:String=Goose.REGISTRATION_AVERAGE):void
// make the specified cursor partake in the scaling of the specified object
// when the cursor is no longer pressing, its effect on the scaling is removed
// damp is the speed at which the object scales - 1 instant, 0 not at all (.1 is nice)
// the Boolean is proportional scaling (true - default) or not (false)
// the last parameter is where it scales from
// Goose.REGISTRATION_CENTER - from the object center
// Goose.REGISTRATION_POINT - from the registration point
// Goose.REGISTRATION_AVERAGE (default) - from the average starting point of the cursors
// stopScale(cursor:Sprite, obj:InteractiveObject):void
// stop scaling the specified object with the specified cursor
// stopAllScales():void
// stop all current scaling - future scales will still run
// dispose():void
// removes listeners, closes socket, deletes data objects
// PROPERTIES
// showCursors:Boolean - do you want to see the cursors (true) or not (false)
// touchDelay:Number = .5 - max seconds until a touch is not a touch
// pressDelay:Number = .5 - max seconds until a press is not a press
// touchRadius:Number = 10 - max pixel movement until a touch is not a touch
// pressRadius:Number = 10 - max pixel movement until a press is not a press
// STATIC CONSTANTS
// Goose.DRAG_LOCKED - see startFollow() method
// Goose.DRAG_OVERRIDE - see startFollow() method
// Goose.DRAG_AVERAGE - see startFollow() method
// Goose.REGISTRATION_CENTER - see startScale() method
// Goose.REGISTRATION_POINT - see startScale() method
// Goose.REGISTRATION_AVERAGE - see startScale() method
public static const DRAG_LOCKED:String = "dragLocked";
public static const DRAG_OVERRIDE:String = "dragOverride";
public static const DRAG_AVERAGE:String = "dragAverage";
public static const REGISTRATION_CENTER:String = "registrationCenter";
public static const REGISTRATION_POINT:String = "registrationPoint";
public static const REGISTRATION_AVERAGE:String = "registrationAverage";
public var xmlData:XML;
public var touchDelay:Number = .5;// max seconds until a touch is not a touch
public var pressDelay:Number = .5;// max seconds until a press is not a press
public var touchRadius:Number = 10;// max pixel movement until a touch is not a touch
public var pressRadius:Number = 10;// max pixel movement until a press is not a press
private var myShowCursors:Boolean;
private var myCursors:Sprite;
private var currentCursors:Object = {};
// Dictionaries to record and track addPressEvent listeners
private var press:Dictionary = new Dictionary(true);
private var pressDown:Dictionary = new Dictionary(true);
private var pressUp:Dictionary = new Dictionary(true);
private var pressMove:Dictionary = new Dictionary(true);
private var pickUp:Dictionary = new Dictionary(true);
private var putDown:Dictionary = new Dictionary(true);
private var currentPresses:Dictionary = new Dictionary(true);
private var cursorLookup:Dictionary = new Dictionary(true);
private var follow:Dictionary = new Dictionary(true);
private var scale:Dictionary = new Dictionary(true);
public function Goose(theShowCursors:Boolean = true) {
trace("hi from Goose");
myCursors = new Sprite();
addChild(myCursors);
showCursors = theShowCursors;
addEventListener(Event.ENTER_FRAME, runFollow);
addEventListener(Event.ENTER_FRAME, runScale);
}
public function update(d:XML) {
/*var d:XML = <manyml>
<item id="1000" x="50" y="20" z="-100" />
<item id="2000" x="500" y="80" z="0" />
</manyml>;*/
// move cursors and determine cursor joins and leaves
// dispatch cursor events
var newCursor:Sprite;
var newCursors:Object = {};
var theseCursors:Object = {};
var deltaX:Number;
var deltaY:Number;
var downCheck:Boolean = false;
for each (var i:XML in d.item) {
theseCursors[i.@id] = 1;
if (! currentCursors[i.@id]) {
newCursor = new Sprite();
newCursor.graphics.lineStyle(2,0x888888,1,false,"none");
newCursor.graphics.beginFill(0xffff00, .7);
newCursor.graphics.drawCircle(0,0,10);
newCursor.x = i.@x;
newCursor.y = i.@y;
myCursors.addChild(newCursor);
newCursors[i.@id] = [newCursor,i.@z,getTimer(),i.@x,i.@y];
cursorLookup[newCursor] = i.@id;
if (i.@z >= 0) {
newCursor.scaleX = newCursor.scaleY = 2;
// -------------------TOUCH_DOWN PRESS_DOWN
dispatchEvent(new GooseEvent(GooseEvent.TOUCH_DOWN, newCursor, Number(i.@z), i.@id));
dispatchPressEvent(GooseEvent.PRESS_DOWN, newCursor, Number(i.@z), i.@id);
downCheck = true;
}
} else {
currentCursors[i.@id][0].x = i.@x;
currentCursors[i.@id][0].y = i.@y;
if (currentCursors[i.@id][1] < 0 && i.@z >= 0) {
// -------------------TOUCH_DOWN PRESS_DOWN
dispatchEvent(new GooseEvent(GooseEvent.TOUCH_DOWN, currentCursors[i.@id][0], Number(i.@z), i.@id));
dispatchPressEvent(GooseEvent.PRESS_DOWN, currentCursors[i.@id][0], Number(i.@z), i.@id);
currentCursors[i.@id][2] = getTimer();
currentCursors[i.@id][3] = i.@x;
currentCursors[i.@id][4] = i.@y;
downCheck = true;
} else if (currentCursors[i.@id][1] >= 0 && i.@z < 0) {
// -------------------TOUCH TOUCH_UP PRESS PRESS_UP
if ((getTimer() - currentCursors[i.@id][2]) < touchDelay * 1000) {
deltaX = Math.pow((currentCursors[i.@id][3]-currentCursors[i.@id][0].x),2);
deltaY = Math.pow((currentCursors[i.@id][4]-currentCursors[i.@id][0].y),2);
if (Math.sqrt(deltaX + deltaY) < touchRadius) {
dispatchEvent(new GooseEvent(GooseEvent.TOUCH, currentCursors[i.@id][0], Number(i.@z), i.@id));
dispatchPressEvent(GooseEvent.PRESS, currentCursors[i.@id][0], Number(i.@z), i.@id);
}
}
dispatchEvent(new GooseEvent(GooseEvent.TOUCH_UP, currentCursors[i.@id][0], Number(i.@z), i.@id));
dispatchPressEvent(GooseEvent.PRESS_UP, currentCursors[i.@id][0], Number(i.@z), i.@id);
}
currentCursors[i.@id][1] = i.@z;
// -------------------TOUCH_MOVE PRESS_MOVE
if (currentCursors[i.@id][1] >= 0) {
currentCursors[i.@id][0].scaleX = currentCursors[i.@id][0].scaleY = 2;
dispatchEvent(new GooseEvent(GooseEvent.TOUCH_MOVE, currentCursors[i.@id][0], Number(i.@z), i.@id));
dispatchPressEvent(GooseEvent.PRESS_MOVE, currentCursors[i.@id][0], Number(i.@z), i.@id);
downCheck = true;
} else {
currentCursors[i.@id][0].scaleX = currentCursors[i.@id][0].scaleY = 1;
}
}
}
if (! downCheck) {
// reset the current presses in case the cursor just left
// this is important for real blob detection
// when every press might get a unique id
// the Dictionary should clear itself as cursors are deleted
for (var p:* in currentPresses) {
currentPresses[p] = {};
}
}
// remove any missing cursors and add any new ones
for (var c:String in currentCursors) {
if (! theseCursors[c]) {
delete cursorLookup[currentCursors[c][0]];
myCursors.removeChild(currentCursors[c][0]);
delete currentCursors[c];
}
}
for (var n:String in newCursors) {
currentCursors[n] = newCursors[n].concat();
}
}
private function dispatchPressEvent(e:String, c:Sprite, cZ:Number, cID:String) {
var o:*;
// watch out here as they may not have registered a PRESS_DOWN
// yet we still need to know for the other events if we started on the object
// this stores that we have a press on the object
if (e == GooseEvent.PRESS_DOWN) {
for (o in currentPresses) {
if (o.hitTestPoint(c.x,c.y)) {
currentPresses[o][cID] = 1;
}
}
}
// here we dispatch the event with one amazing line ;-)
for (o in this[e]) {
if (!o.mouseEnabled) {continue;}
if (!currentPresses[o]) {continue;}
if (currentPresses[o][cID]!=1) {continue;}
if (o.hitTestPoint(c.x,c.y)) {
// call the function stored in the dictionary for the object
this[e][o](new GooseEvent(e,c,cZ,cID,o)); // beautiful
}
}
// remove the tracking of the press on the object
if (e == GooseEvent.PRESS_UP) {
for (o in currentPresses) {
delete currentPresses[o][cID];
stopFollow(c, o);
stopScale(c, o);
}
}
}
public function addPressListener(e:String, obj:InteractiveObject, fun:Function) {
this[e][obj] = fun;
if (!currentPresses[obj]) {
currentPresses[obj] = {};
}
}
public function removePressListener(e:String, obj:InteractiveObject, fun:Function) {
delete this[e][obj];
}
//---- FOLLOW ---------------------------------------------------
public function startFollow(cursor:Sprite, obj:InteractiveObject, damp:Number=.1, type:String=Goose.DRAG_AVERAGE) {
// might have nesting problems here unless do local to global...
// write into follow array or object to loop through in the enter frame
damp = Math.max(0, Math.min(1, damp));
if (follow[obj]) {
if (follow[obj][0][2] == Goose.DRAG_LOCKED) {return;}
if (type == Goose.DRAG_LOCKED || type == Goose.DRAG_OVERRIDE) {
// overwrite the current followings whatever they are
follow[obj] = [[cursor, damp, type, obj.x, obj.y, cursor.x, cursor.y]];
} else {
follow[obj].push([cursor, damp, type, obj.x, obj.y, cursor.x, cursor.y]);
}
} else {
follow[obj] = [[cursor, damp, type, obj.x, obj.y, cursor.x, cursor.y]];
if (scale[obj]) {return;}
if (!this[GooseEvent.PICK_UP]) {return;}
if (!this[GooseEvent.PICK_UP][obj]) {return;}
var cID:String = cursorLookup[cursor];
var cZ:Number = currentCursors[cID][1];
this[GooseEvent.PICK_UP][obj](new GooseEvent(GooseEvent.PICK_UP,cursor,cZ,cID,obj));
}
}
private function runFollow(e:Event) { // called by ENTER_FRAME
var curs:Sprite;
var damp:Number;
var objStartX:Number;
var objStartY:Number;
var cursStartX:Number;
var cursStartY:Number;
var cursEndX:Number;
var cursEndY:Number;
var totDamp:Number;
var totObjStartX:Number;
var totObjStartY:Number;
var totCursStartX:Number;
var totCursStartY:Number;
var totCursX:Number;
var totCursY:Number;
var newX:Number;
var newY:Number;
var diffX:Number;
var diffY:Number;
var i:uint;
var len:Number;
for (var f:* in follow) {
totDamp=0;
totObjStartX=0;
totObjStartY=0;
totCursStartX=0;
totCursStartY=0;
totCursX=0;
totCursY=0;
// take averages
len = follow[f].length;
for (i=0; i<len; i++) {
curs = follow[f][i][0];
totCursX+=curs.x;
totCursY+=curs.y;
totDamp+=follow[f][i][1];
totObjStartX+=follow[f][i][3];
totObjStartY+=follow[f][i][4];
totCursStartX+=follow[f][i][5];
totCursStartY+=follow[f][i][6];
}
damp = totDamp/len;
objStartX = totObjStartX/len;
objStartY = totObjStartY/len;
cursStartX = totCursStartX/len;
cursStartY = totCursStartY/len;
cursEndX = totCursX/len;
cursEndY = totCursY/len;
newX = objStartX + (cursEndX - cursStartX);
newY = objStartY + (cursEndY - cursStartY);
diffX = newX - f.x;
diffY = newY - f.y;
if (Math.abs(diffX) >= 2) {
f.x = f.x + diffX * damp;
}
if (Math.abs(diffY) >= 2) {
f.y = f.y + diffY * damp;
}
}
}
public function stopFollow(cursor:Sprite, obj:InteractiveObject) {
// update the follow array or object
if (follow[obj]) {
var len:Number = follow[obj].length;
for (var i:uint=0; i<len; i++) {
if (follow[obj][i][0] == cursor) {
follow[obj].splice(i,1);
if (follow[obj].length == 0) {
delete follow[obj];
if (scale[obj]) {return;}
if (!this[GooseEvent.PUT_DOWN]) {return;}
if (!this[GooseEvent.PUT_DOWN][obj]) {return;}
var cID:String = cursorLookup[cursor];
var cZ:Number = currentCursors[cID][1];
this[GooseEvent.PUT_DOWN][obj](new GooseEvent(GooseEvent.PUT_DOWN,cursor,cZ,cID,obj));
}
break;
}
}
}
}
public function stopAllFollows() {
// clear the follow array or object
if (!this[GooseEvent.PUT_DOWN]) {
follow = new Dictionary();
return;
}
var o:*;
var c:Sprite;
var cID:String;
var cZ:Number;
for (o in follow) {
if (scale[o]) {return;}
if (!this[GooseEvent.PUT_DOWN][o]) {continue;}
c = follow[o][0][0]; // just get the first cursor as the cursor that puts down
cID = cursorLookup[c];
cZ = currentCursors[cID][1];
this[GooseEvent.PUT_DOWN][o](new GooseEvent(GooseEvent.PUT_DOWN,c,cZ,cID,o));
}
follow = new Dictionary();
}
//---- SCALE ---------------------------------------------------
public function startScale(cursor:Sprite, obj:InteractiveObject, damp:Number=.1, proportional:Boolean=true, registration:String=Goose.REGISTRATION_AVERAGE) {
damp = Math.max(0, Math.min(1, damp));
if (scale[obj]) {
scale[obj].push([cursor, damp, proportional, registration, obj.x, obj.y, cursor.x, cursor.y, obj.width, obj.height]);
} else {
scale[obj] = [[cursor, damp, proportional, registration, obj.x, obj.y, cursor.x, cursor.y, obj.width, obj.height]];
if (follow[obj]) {return;}
if (!this[GooseEvent.PICK_UP]) {return;}
if (!this[GooseEvent.PICK_UP][obj]) {return;}
var cID:String = cursorLookup[cursor];
var cZ:Number = currentCursors[cID][1];
this[GooseEvent.PICK_UP][obj](new GooseEvent(GooseEvent.PICK_UP,cursor,cZ,cID,obj));
}
}
private function runScale(e:Event) { // called by ENTER_FRAME
var curs:Sprite;
var damp:Number;
var prop:Boolean;
var reg:String;
var objStartX:Number;
var objStartY:Number;
var objStartW:Number;
var objStartH:Number;
var cursStartX:Number;
var cursStartY:Number;
var totDamp:Number;
var totObjStartX:Number;
var totObjStartY:Number;
var totObjStartW:Number;
var totObjStartH:Number;
var totCursStartX:Number;
var totCursStartY:Number;
var totCursX:Number;
var totCursY:Number;
var newW:Number;
var newH:Number;
var diffW:Number;
var diffH:Number;
var i:uint;
var len:Number;
for (var obj:* in scale) {
totDamp=0;
totObjStartX=0;
totObjStartY=0;
totObjStartW=0;
totObjStartH=0;
totCursStartX=0;
totCursStartY=0;
totCursX=0;
totCursY=0;
// take averages
len = scale[obj].length;
if (len <2) {continue;}
for (i=0; i<len; i++) {
totDamp+=scale[obj][i][1];
totObjStartX+=scale[obj][i][4];
totObjStartY+=scale[obj][i][5];
totCursStartX+=scale[obj][i][6];
totCursStartY+=scale[obj][i][7];
totObjStartW+=scale[obj][i][8];
totObjStartH+=scale[obj][i][9];
// take the last prop and reg
prop = scale[obj][i][2];
reg = scale[obj][i][3];
}
damp = totDamp/len;
objStartX = totObjStartX/len;
objStartY = totObjStartY/len;
cursStartX = totCursStartX/len;
cursStartY = totCursStartY/len;
objStartW = totObjStartW/len;
objStartH = totObjStartH/len;
var maxDiffX:Number = -100000;
var minDiffX:Number = 100000;
var maxDiffY:Number = -100000;
var minDiffY:Number = 100000;
var currentMaxX:Number;
var currentMaxY:Number;
var currentMinX:Number;
var currentMinY:Number;
for (i=0; i<len; i++) {
curs = scale[obj][i][0];
if (curs.x-scale[obj][i][6] > maxDiffX) {
maxDiffX = curs.x-scale[obj][i][6];
currentMaxX = scale[obj][i][6];
}
if (curs.y-scale[obj][i][7] > maxDiffY) {
maxDiffY = curs.y-scale[obj][i][7];
currentMaxY = scale[obj][i][7];
}
if (curs.x-scale[obj][i][6] < minDiffX) {
minDiffX = curs.x-scale[obj][i][6];
currentMinX = scale[obj][i][6];
}
if (curs.y-scale[obj][i][7] < minDiffY) {
minDiffY = curs.y-scale[obj][i][7];
currentMinY = scale[obj][i][7];
}
}
var diffWidth:Number;
var diffHeight:Number;
if (currentMaxX >= currentMinX) {
diffWidth = maxDiffX - minDiffX;
} else {
diffWidth = minDiffX - maxDiffX;
}
if (currentMaxY >= currentMinY) {
diffHeight = maxDiffY - minDiffY;
} else {
diffHeight = minDiffY - maxDiffY;
}
if (prop) { // find largest proportional difference
if (Math.abs(diffWidth / obj.width) > Math.abs(diffHeight / obj.height)) {
diffHeight = diffWidth / obj.width * obj.height;
} else {
diffWidth = diffHeight / obj.height * obj.width;
}
}
newW = objStartW + diffWidth;
newH = objStartH + diffHeight;
diffW = newW - obj.width;
diffH = newH - obj.height;
if (obj.width + diffW * damp < 10 || obj.height + diffH * damp < 10) {
continue;
}
if (obj.width + diffW * damp > 2880 || obj.height + diffH * damp > 2880) {
continue;
}
if (Math.abs(diffW) >= 2) {
obj.width = obj.width + diffW * damp;
}
if (Math.abs(diffH) >= 2) {
obj.height = obj.height + diffH * damp;
}
if (reg == Goose.REGISTRATION_CENTER) {
obj.x -= diffW/2 * damp;
obj.y -= diffH/2 * damp;
} else if (reg == Goose.REGISTRATION_AVERAGE) {
obj.x -= diffW * (cursStartX-objStartX) / objStartW * damp;
obj.y -= diffH * (cursStartY-objStartY) / objStartH * damp;
}
}
}
public function stopScale(cursor:Sprite, obj:InteractiveObject) {
// update the scale array or object
if (scale[obj]) {
var len:Number = scale[obj].length;
for (var i:uint=0; i<len; i++) {
if (scale[obj][i][0] == cursor) {
scale[obj].splice(i,1);
if (scale[obj].length == 0) {
delete scale[obj];
if (follow[obj]) {return;}
if (!this[GooseEvent.PUT_DOWN]) {return;}
if (!this[GooseEvent.PUT_DOWN][obj]) {return;}
var cID:String = cursorLookup[cursor];
var cZ:Number = currentCursors[cID][1];
this[GooseEvent.PUT_DOWN][obj](new GooseEvent(GooseEvent.PUT_DOWN,cursor,cZ,cID,obj));
}
break;
}
}
}
}
public function stopAllScales() {
// clear the scale array or object
if (!this[GooseEvent.PUT_DOWN]) {
scale = new Dictionary();
return;
}
var o:*;
var c:Sprite;
var cID:String;
var cZ:Number;
for (o in scale) {
if (follow[o]) {return;}
if (!this[GooseEvent.PUT_DOWN][o]) {continue;}
c = scale[o][0][0]; // just get the first cursor as the cursor that puts down
cID = cursorLookup[c];
cZ = currentCursors[cID][1];
this[GooseEvent.PUT_DOWN][o](new GooseEvent(GooseEvent.PUT_DOWN,c,cZ,cID,o));
}
scale = new Dictionary();
}
//-------------------------------------------------------
public function get showCursors():Boolean {
return myShowCursors;
}
public function set showCursors(b:Boolean) {
myShowCursors = b;
if (myShowCursors) {
myCursors.alpha = 1;
} else {
myCursors.alpha = 0;
}
}
public function dispose() {
removeEventListener(Event.ENTER_FRAME, runFollow);
removeEventListener(Event.ENTER_FRAME, runScale);
press=null;
pressDown=null;
pressUp=null;
pressMove=null;
pickUp=null;
putDown=null;
currentPresses=null;
cursorLookup=null;
follow=null;
scale=null;
removeChild(myCursors);
myCursors=null;
currentCursors=null;
}
}
}
package com.danzen.interfaces.goose {
import flash.display.Sprite;
import flash.events.*;
import flash.utils.Timer;
// GOOSE OVERVIEW
// In general, Goose lets you work with multiple cursor inputs - like multitouch
// Goose splits into two parts:
// 1. A multitouch emulator (GooseData, GooseRobin, Online Mouse Nodes)
// with the Goose Emulator you run the online Mouse Nodes on two or more computers
// each mouse then feeds data through GooseRobin to GooseData
// you then feed the data from GooseData into Goose (part 2)
// 2. A custom class to process the data (Goose, GooseEvent)
// Goose takes in multitouch data (like from the emulator in part 1)
// and shows multiple cursors in your application
// so if you use the Goose Emulator then each mouse shows up as a cursor
// Goose and GooseEvent provide a set of methods and events for using the cursors
// You continue to use Goose when going from an emulator to real multitouch data
// GOOSEDATA
// GooseData uses GooseRobin to receive multiple mouse data from different computers
// mouse nodes are available at http://www.danzen.com/goose/node.html
// or if you set up Robin yourself then you can make and administer your own nodes
// Robin is available at http://robinflash.wordpress.com
// if you do use your own Robin then you need to change the server address
// in GooseRobin and the MultiuserEmulator
// this would make sure that you have control over maintenance of the system
// it also lets you try out Robin - the PHP multiuser server
// you can also write your own data class to send data into Goose
// Goose is set up so that once you emulate, you can switch over to real data
// so if you run your own Blob Detect table, you can feed that data in to Goose
// see http://dodoflash.wordpress.com for a Blob Detection option
// the format of the data is quite simple and is available at http://multiml.wordpress.com
// if you have a different data format, it will be quite easy for you to convert it
// USING GOOSEDATA
// make sure that you have the provided com folder in a folder that is in your class path
// import com.danzen.interfaces.goose.* in your document class
// create a new GooseData object and pass it the name of your application
// create a Goose object in your document class
// add an Event.CHANGE event to your GooseData object and have it call a method
// in the method pass the GooseData's xmlData property to the Goose update() method
// this separation allows you to easily change from the emulator to real multitouch data
// when you are ready to do so, just pass the real data to the Goose update() method
// the data must be in the form of ManyML:
// <manyml>
// <item id="1000" x="984" y="1" z="-100" />
// <item id="2000" x="243" y="7" z="0" />
// </manyml>
// if z >= 0 then the item is treated as a touch in Goose
// if z < 0 then the item is ignored by all events in Goose
// see Goose for more information on how to use Goose
public class GooseData extends Sprite {
public var xmlData:XML;
private var myRobin:GooseRobin;
private var myAppName:String = "goose"; // leave this as goose
private var lastX:Number=0;
private var lastY:Number=0;
private var lastType
private var myTimer:Timer;
private var dataPath:String;
private var period:Number = 200;
public function GooseData(theAppName:String) {
trace ("hi from GooseData");
myAppName += "_" + theAppName;
myAppName = myAppName.replace(/[\n\r]/g,"");
myAppName = myAppName.replace(/\s/g,"");
myRobin = new GooseRobin(myAppName);
myRobin.addEventListener(Event.CONNECT, myRobinConnected);
myRobin.addEventListener(DataEvent.DATA, myRobinChange);
}
private function myRobinConnected(e:Event) {
trace ("connected");
dispatchEvent(new Event(Event.CONNECT));
}
private function myRobinChange(e:Event) {
var theXML:String = "<manyml>\n";
if (myRobin.getProperties("x")) {
for (var i:uint = 0; i < myRobin.getProperties("x").length; i++) {
if (myRobin.getProperties("id")[i] == "") {continue;}
theXML += '<item id="' + myRobin.getProperties("id")[i] + '" x="' + myRobin.getProperties("x")[i] + '" y="' + myRobin.getProperties("y")[i] + '" z="' + myRobin.getProperties("z")[i] + '" />\n';
}
}
theXML += "</manyml>";
xmlData = XML(theXML);
dispatchEvent(new Event(Event.CHANGE));
}
public function dispose() {
myRobin.removeEventListener(Event.CONNECT, myRobinConnected);
myRobin.removeEventListener(DataEvent.DATA, myRobinChange);
myRobin.dispose();
}
}
}
package com.danzen.interfaces.goose {
import flash.display.Sprite;
import flash.display.InteractiveObject;
import flash.events.*;
public class GooseEvent extends Event {
public static const TOUCH:String = "touch";
public static const DOUBLE_TOUCH:String = "doubleTouch"; // not active
public static const TOUCH_DOWN:String = "touchDown";
public static const TOUCH_UP:String = "touchUp";
public static const TOUCH_MOVE:String = "touchMove";
public static const PRESS:String = "press";
public static const DOUBLE_PRESS:String = "doublePress"; // not active
public static const PRESS_DOWN:String = "pressDown";
public static const PRESS_UP:String = "pressUp";
public static const PRESS_MOVE:String = "pressMove";
public static const PICK_UP:String = "pickUp"; // for first follow or size
public static const PUT_DOWN:String = "putDown"; // for last follow or size
public var cursor:Sprite;
public var cursorZ:Number;
public var cursorID:String;
public var obj:InteractiveObject;
private var t:String;
private var b:Boolean;
private var c:Boolean;
public function GooseEvent(type:String,
theCursor:Sprite,
theCursorZ:Number,
theCursorID:String,
theObject:InteractiveObject = null,
bubbles:Boolean = false,
cancelable:Boolean = false) {
t = type;
b = bubbles;
c = cancelable;
super (t,b,c);
cursor = theCursor;
cursorZ = theCursorZ;
cursorID = theCursorID;
obj = theObject;
}
public override function clone():Event {
return new GooseEvent(t,cursor,cursorZ,cursorID,obj,b,c);
}
public override function toString():String {
return formatToString("GooseEvent","type","theCursor","theCursorZ","theCursorID","theObject","bubbles","cancelable","eventPhase");
}
}
}
package com.danzen.interfaces.goose {
// ROBIN INTRODUCTION
// Robin is a custom Flash AS3 class with supporting PHP to handle realtime communication
// realtime communication is used for chats and multiuser games with avatars, etc.
// http://robinflash.wordpress.com - by inventor Dan Zen - http://www.danzen.com
// if you are using Robin for commercial purposes, you are welcome to donate to Dan Zen
// donations can be made to agency@danzen.com at http://www.paypal.com
// this is a modified Robin to call the same server as the Goose Emulator nodes
// the Goose Emulator Nodes are at http://www.danzen.com/goose/node.html
// if you want more control over your system - say to hard code the application name
// or make sure that if Robin goes down that you can restart it yourself
// then please consider downloading and installing Robin (the URL is above)
// you would then make a simple change in the server address below and in the emulator
// if you do not use your own version of Robin then you do not need to adjust anything here
// Note that your Flash application will not work on the Web with Goose Emulator Data
// to make your application work you need to substitute the Goose data with real data
// the Goose Emulator is only for you to build and test your application
import flash.display.Sprite;
import flash.events.*;
import flash.net.XMLSocket;
import flash.utils.Timer;
public class GooseRobin extends Sprite {
// just put your server domain without http:// for example www.danzen.com
private var myServer:String = "www.danzen.com";
private var myPort:Number = 9998;
// CONSTRUCTOR
// Robin(theApplicationName:String, theMaxPeople:uint = 0, theFill:Boolean = true, theStartProperties:Object = null):void
// constructor to connect your application to the robin.php socket for multiuser functionality
// robin.php must be running for this to work. It works in your Flash authoring tool too (AS3)
// PARAMETERS:
// theApplicationName:String
// you make this up - it should be one word (or camel case) and probably unique for your app
// theMaxPeople:uint
// how many people are allowed per "room" - there are as many rooms as needed - 0 is virtually unlimited
// theFill:Boolean
// if someone leaves a room - set to true to fill in their location with the next person to join
// theStartProperties:Object
// you can start off with properties set - send them in an object {prop1:numValue, prop2:"String Value, etc."}
// EXAMPLES:
// at its simplest making a new Robin object might look like this:
// var myRobin:Robin = new Robin("FreezeTag");
// or you could add some limitations and initial conditions:
// var myRobin:Robin = new Robin("FreezeTag", 10, false, {x:200, y:300, clan:"Cyborg Rabbits"});
// EVENTS
// IOErrorEvent.IO_ERROR
// trouble connecting - make sure robin.php is running and you have the right domain and port (see CONFIGURATION)
// Event.CONNECT
// connected to socket - you can set up your application to submit and receive data, etc.
// DataEvent.DATA
// dispatched when someone in the room makes a change (not including you)
// SyncEvent.SYNC
// dispatched when any data is changed including yours (use for latest properties - equivalent to Flash remote SharedObject)
// Event.CLOSE
// the socket is closed - could be that robin.php stops for some reason - all data on the server will be lost
// METHODS
// setProperty(propertyName:String, propertyValue:Object):void
// sets your property to the value and sends out change to all in room (distributes)
// setProperties(objectOfPropertiesToSet:Object):void
// pass in an object with properties and values and it sets yours to match and distributes them
// getProperty(propertyName:String):String
// returns your value for the property you pass to it
// getProperties(propertyName:String):Array
// returns an array of everyone in the rooms values for the property you pass to it
// getPropertyNames():Array
// returns an array of all property names that have been distributed
// getSenderProperty(propertyName:String):String
// returns the value of the property you pass it that belongs to the last person to distribute (not you)
// getLatestValue(propertyName:String):String
// returns the last distributed value for the property you pass to it - could be yours
// getLatestValueIndex(propertyName:String):Number
// returns the index number of the last person to distribute a value for the property you pass to it
// getLatestProperties(propertyName:String):Array
// returns an array of the last properties to be distributed (sometimes multiple properties are distributed at once)
// appendToHistory(someText:String=""):void
// adds the text passed to it to the history file for the room (deleted if room is empty)
// clearHistory():void
// deletes the history file for the room
// dispose():void
// removes listeners, closes socket, deletes data objects
// PROPERTIES (READ ONLY)
// applicationName:String - the name of your application
// server:String - the server you set in the CONFIGURATION
// port:Number - the port you set in the CONFIGURATION
// maxPeople:Number - see CONSTRUCTOR
// fill:Boolean - see CONSTRUCTOR
// startProperties:Object - see CONSTRUCTOR
// senderIndex:Number - the index number of the last person to send out data
// history:String - the history text for your room at the time of application start
// PRIVATE VARIABLES
// getter methods allow you to get these properties (less the "my" prefix)
private var myApplicationName:String;
private var myMaxPeople:uint;
private var myFill:Boolean;
private var myStartProperties:Object;
private var mySenderIndex:Number;
private var myHistory:String = "";
// public methods are available to get this data
private var myData:Object = new Object();
private var theirData:Object = new Object();
private var latestValues:Object = new Object();
private var latestIndexes:Object = new Object();
private var latestProperties:Array = [];
// internal variables
private var mySocket:XMLSocket;
private var initializationCheck:Boolean = false;
private var myTimer:Timer;
private var connectCheck:Boolean = false;
public function GooseRobin (
theApplicationName:String,
theMaxPeople:uint = 0,
theFill:Boolean = true,
theStartProperties:Object = null
) {
trace ("hi from GooseRobin");
myApplicationName = theApplicationName;
if (!myApplicationName.match(/^[a-zA-Z0-9_-]+$/)) {
trace ("----------------------------------");
trace ("Robin Application Name: \""+myApplicationName+"\"");
trace ("Sorry - your application name must include only a-z, A-Z, numbers, _ or -");
trace ("----------------------------------");
return;
}
myMaxPeople = (theMaxPeople == 0) ? 1000000 : theMaxPeople;
myFill = theFill;
myStartProperties = theStartProperties;
mySocket = new XMLSocket();
getPolicy();
makeConnection();
}
// -------------------- PRIVATE METHODS -------------------------------
private function getPolicy() {
mySocket.connect(myServer, myPort);
mySocket.addEventListener(IOErrorEvent.IO_ERROR, function(e:Event){}); // nothing to do
mySocket.close();
}
private function makeConnection() {
mySocket.connect(myServer, myPort);
mySocket.addEventListener(Event.CONNECT, handleMeta);
mySocket.addEventListener(Event.CLOSE, handleMeta);
mySocket.addEventListener(IOErrorEvent.IO_ERROR, handleMeta);
mySocket.addEventListener(DataEvent.DATA, incomingData);
}
private function handleMeta(e:Event) {
switch(e.type){
case 'ioError':
trace ("error");
dispatchEvent(new Event(IOErrorEvent.IO_ERROR));
break;
case 'connect':
if (!initializationCheck) {
initializationCheck = true;
sendData();
myTimer = new Timer(50, 1); // need to double send for some reason
myTimer.addEventListener(TimerEvent.TIMER, init);
myTimer.start();
}
break;
case 'close':
trace ("close");
dispatchEvent(new Event(Event.CLOSE));
break;
}
}
private function init(e:TimerEvent) {
if (myStartProperties) {
setProperties(myStartProperties);
} else {
sendData();
}
}
private function incomingData(e:DataEvent) {
// first time data is the history file
// followed by -~^~-\n line then the latest users for the properties
// _u_^1 // the second user was last to update
// text^0 // the first user was the last to update the text
// x^1 // the second user was the last to update x and y
// y^1
// followed by -~^~- line then the user data with -1 as the mySenderIndex
var latestData:String
var incomingData:String;
if (!connectCheck) {
var feed:Array = String(e.data).split("-~^~-\n");
myHistory = decode(feed[0]);
latestData = feed[1];
incomingData = feed[2];
} else {
incomingData = String(e.data);
}
// 0
// x|y
// _u_^12|22
// text^test|hello
// x^27|30
// y^25|200
var lines:Array = incomingData.split("\n");
mySenderIndex = lines.shift();
var updatedProperties:String = lines.shift();
if (connectCheck == false || Number(mySenderIndex) != -1) {
theirData = {};
var temp:Array;
var prop:String;
for (var i:uint=0; i<lines.length; i++) {
if (lines[i] == "") {continue;}
temp = lines[i].split("^");
prop = temp[0];
if (temp[1] != "") {
theirData[prop] = decode(temp[1]).split("|");
}
}
}
// set the latest information
// note... the initial variables sent when instantiating Robin are not used for latest information
if (connectCheck == false) {
latestProperties = [];
var latestLines:Array = latestData.split("\n");
var latestVV:Array;
for (var iii:uint = 0; iii<latestLines.length; iii++) {
if (latestLines[iii] != "") {
latestVV = latestLines[iii].split("^");
latestIndexes[latestVV[0]] = Number(latestVV[1]);
latestValues[latestVV[0]] = getProperties(latestVV[0])[Number(latestVV[1])];
}
}
} else {
if (updatedProperties == "") {
// somebody left and may want to set any latestIndexes to -2
} else {
latestProperties = updatedProperties.split("|");
for (var ii:uint=0; ii<latestProperties.length; ii++) {
if (mySenderIndex == -1) { // this is your own property
latestValues[latestProperties[ii]] = getProperty(latestProperties[ii]);
} else {
latestValues[latestProperties[ii]] = getSenderProperty(latestProperties[ii]);
}
latestIndexes[latestProperties[ii]] = mySenderIndex;
}
}
}
dispatchEvent(new SyncEvent(SyncEvent.SYNC, false, false, []));
if (connectCheck == false) {
dispatchEvent(new Event(Event.CONNECT));
connectCheck = true;
} else if (mySenderIndex != -1) {
dispatchEvent(new DataEvent(DataEvent.DATA, false, false, String(e.data)));
}
}
private function sendData(s:String="") {
var f:Number;
if (myFill) {f=1;} else {f=0;}
var prefix:String = "%^%"+myApplicationName+"|"+myMaxPeople+"|"+f;
// %^%sample9|3|1^y=5^x=20
// the %^% makes sure that if there are multiple entries in the socket buffer
// that they can be separated in the robin.php code
mySocket.send(prefix+s);
}
private function encode(w:String):String {
w = w.replace(/\r\n/g,"\n");
w = w.replace(/\n\r/g,"\n");
w = w.replace(/\n/g,"`~`");
w = w.replace(/\|/g,"^~^");
return w;
}
private function decode(w:String):String {
w = w.replace(/`~`/g,"\n");
w = w.replace(/^~^/g,"|");
return w;
}
// -------------------- PUBLIC METHODS -------------------------------
public function setProperty(n:String, v:Object) {
// set one of my properties at a time
myData[n] = v;
sendData("^" + n + "=" + encode(String(v)));
}
public function setProperties(o:Object) {
// set a bunch of my properties at once with an object
var vars = "";
for (var i:Object in o) {
myData[i] = String(o[i]);
vars += "^" + i + "=" + encode(String(o[i]));
}
sendData(vars);
}
public function getProperty(w:String):String {
// get one of my properties
return myData[w];
}
public function getProperties(w:String):Array {
// get an array of their properties
return theirData[w];
}
public function getPropertyNames():Array {
// get a list of their property names
var props:Array =[];
for (var i:String in theirData) {
props.push(i);
}
return props;
}
public function getSenderProperty(w:String):String {
if (theirData[w] && theirData[w][mySenderIndex]) {
return theirData[w][mySenderIndex];
} else {
return null;
}
}
public function getLatestValue(w:String):String {
if (latestValues[w]) {
return latestValues[w];
} else {
return null;
}
}
public function getLatestValueIndex(w:String):Number {
return Number(latestIndexes[w]);
}
public function getLatestProperties(w:String):Array {
return latestProperties;
}
public function appendToHistory(t:String="") {
sendData("^_history_=" + encode(String(t)));
}
public function clearHistory() {
sendData("^_clearhistory_=");
}
public function dispose() {
mySocket.removeEventListener(Event.CONNECT, handleMeta);
mySocket.removeEventListener(IOErrorEvent.IO_ERROR, handleMeta);
mySocket.removeEventListener(DataEvent.DATA, incomingData);
mySocket.close();
mySocket.removeEventListener(Event.CLOSE, handleMeta);
mySocket = null;
myData = null;
theirData = null;
latestValues = null;
latestIndexes = null;
latestProperties = null;
myHistory = null;
trace ("bye from Robin");
}
// ----------------------- PUBLIC READ ONLY PROPERTIES ---------------------
public function get applicationName():String {
return myApplicationName;
}
public function get server():String {
return myServer;
}
public function get port():Number {
return myPort;
}
public function get maxPeople():Number {
return myMaxPeople;
}
public function get fill():Boolean {
return myFill;
}
public function get startProperties():Object {
return myStartProperties;
}
public function get senderIndex():Number {
return mySenderIndex;
}
public function get history():String {
return myHistory;
}
}
}