1362 行
42 KiB
JavaScript

// Copyright 2011 David Galles, University of San Francisco. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other materials
// provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation are those of the
// authors and should not be interpreted as representing official policies, either expressed
// or implied, of the University of San Francisco
// Global timer used for doing animation callbacks.
// TODO: Make this an instance variable of Animation Manager.
var timer;
var swapped = false;
function reorderSibling(node1, node2)
{
node1.parentNode.replaceChild(node1, node2);
node1.parentNode.insertBefore(node2, node1);
}
function swapControlDiv()
{
swapped = !swapped;
if (swapped) {
reorderSibling(document.getElementById('canvas'), document.getElementById('generalAnimationControlSection'));
setCookie("VisualizationControlSwapped", "true", 30);
} else {
reorderSibling(document.getElementById('generalAnimationControlSection'), document.getElementById('canvas'));
setCookie("VisualizationControlSwapped", "false", 30);
}
}
// Utility funciton to read a cookie
function getCookie(cookieName)
{
var i, x, y;
var cookies=document.cookie.split(";");
for (i=0; i < cookies.length; i++)
{
x=cookies[i].substr(0,cookies[i].indexOf("="));
y=cookies[i].substr(cookies[i].indexOf("=")+1);
x=x.replace(/^\s+|\s+$/g,"");
if (x==cookieName)
{
return unescape(y);
}
}
}
// Utility funciton to write a cookie
function setCookie(cookieName,value,expireDays)
{
var exdate=new Date();
exdate.setDate(exdate.getDate() + expireDays);
var cookieValue=escape(value) + ((expireDays==null) ? "" : "; expires="+exdate.toUTCString());
document.cookie=cookieName + "=" + value;
}
var ANIMATION_SPEED_DEFAULT = 75;
// TODO: Move these out of global space into animation manager?
var objectManager;
var animationManager;
var canvas;
var paused = false;
var playPauseBackButton;
var skipBackButton;
var stepBackButton;
var stepForwardButton;
var skipForwardButton;
var widthEntry;
var heightEntry;
var sizeButton;
function returnSubmit(field, funct, maxsize, intOnly)
{
if (maxsize != undefined)
{
field.size = maxsize;
}
return function(event)
{
var keyASCII = 0;
if(window.event) // IE
{
keyASCII = event.keyCode
}
else if (event.which) // Netscape/Firefox/Opera
{
keyASCII = event.which
}
if (keyASCII == 13)
{
funct();
return false;
}
else if (keyASCII == 59 || keyASCII == 45 || keyASCII == 46 || keyASCII == 190 || keyASCII == 173)
{
return false;
}
else if (maxsize != undefined && field.value.length >= maxsize ||
intOnly && (keyASCII < 48 || keyASCII > 57))
{
if (!controlKey(keyASCII))
return false;
}
return true;
}
}
function animWaiting()
{
stepForwardButton.disabled = false;
if (skipBackButton.disabled == false)
{
stepBackButton.disabled = false;
}
objectManager.statusReport.setText("动画暂停");
objectManager.statusReport.setForegroundColor("#FF0000");
}
function animStarted()
{
skipForwardButton.disabled = false;
skipBackButton.disabled = false;
stepForwardButton.disabled = true;
stepBackButton.disabled = true;
objectManager.statusReport.setText("动画执行中...");
objectManager.statusReport.setForegroundColor("#009900");
}
function animEnded()
{
skipForwardButton.disabled = true;
stepForwardButton.disabled = true;
if (skipBackButton.disabled == false && paused)
{
stepBackButton.disabled = false;
}
objectManager.statusReport.setText("动画执行完毕");
objectManager.statusReport.setForegroundColor("#000000");
}
function anumUndoUnavailable()
{
skipBackButton.disabled = true;
stepBackButton.disabled = true;
}
function timeout()
{
// We need to set the timeout *first*, otherwise if we
// try to clear it later, we get behavior we don't want ...
timer = setTimeout('timeout()', 30);
animationManager.update();
objectManager.draw();
}
function doStep()
{
animationManager.step();
}
function doSkip()
{
animationManager.skipForward();
}
function doSkipBack()
{
animationManager.skipBack();
}
function doStepBack()
{
animationManager.stepBack();
}
function doPlayPause()
{
paused = !paused;
if (paused)
{
playPauseBackButton.setAttribute("value", "play");
if (skipBackButton.disabled == false)
{
stepBackButton.disabled = false;
}
}
else
{
playPauseBackButton.setAttribute("value", "pause");
}
animationManager.SetPaused(paused);
}
function addControl(type, name, location) {
var element = document.createElement("input");
element.setAttribute("type", type);
element.setAttribute("value", name);
var tableEntry = document.createElement("td");
tableEntry.appendChild(element);
var controlBar = document.getElementById(tableEntry);
//Append the element in page (in span).
controlBar.appendChild(element);
return element;
}
function addControlToAnimationBar(type,name,containerType)
{
if (containerType == undefined)
{
containerType = "input";
}
var element = document.createElement(containerType);
element.setAttribute("type", type);
element.setAttribute("value", name);
var tableEntry = document.createElement("td");
tableEntry.appendChild(element);
var controlBar = document.getElementById("GeneralAnimationControls");
//Append the element in page (in span).
controlBar.appendChild(tableEntry);
return element;
}
function initCanvas()
{
canvas = document.getElementById("canvas");
objectManager = new ObjectManager();
animationManager = new AnimationManager(objectManager);
skipBackButton = addControlToAnimationBar("Button", "快退");
skipBackButton.onclick = animationManager.skipBack.bind(animationManager);
stepBackButton = addControlToAnimationBar("Button", "后退");
stepBackButton.onclick = animationManager.stepBack.bind(animationManager);
playPauseBackButton = addControlToAnimationBar("Button", "暂停");
playPauseBackButton.onclick = doPlayPause ;
stepForwardButton = addControlToAnimationBar("Button", "前进");
stepForwardButton.onclick = animationManager.step.bind(animationManager) ;
skipForwardButton = addControlToAnimationBar("Button", "快进");
skipForwardButton.onclick = animationManager.skipForward.bind(animationManager);
var element = document.createElement("div");
element.setAttribute("display", "inline-block");
element.setAttribute("float", "left");
var tableEntry = document.createElement("td");
var controlBar = document.getElementById("GeneralAnimationControls");
var newTable = document.createElement("table");
var midLevel = document.createElement("tr");
var bottomLevel = document.createElement("td");
midLevel.appendChild(bottomLevel);
bottomLevel.appendChild(element);
newTable.appendChild(midLevel);
midLevel = document.createElement("tr");
bottomLevel = document.createElement("td");
bottomLevel.align = "center";
var txtNode = document.createTextNode("动画速率");
midLevel.appendChild(bottomLevel);
bottomLevel.appendChild(txtNode);
newTable.appendChild(midLevel);
tableEntry.appendChild(newTable);
//Append the element in page (in span).
controlBar.appendChild(tableEntry);
//tableEntry.appendChild(element);
var speed = getCookie("VisualizationSpeed");
if (speed == null || speed == "")
{
speed = ANIMATION_SPEED_DEFAULT;
}
else
{
speed = parseInt(speed);
}
$(element).slider({
animate: true,
value: speed,
change: function(e, ui)
{
setCookie("VisualizationSpeed", String(ui.value), 30);
},
slide : function(e, ui){
animationManager.SetSpeed(ui.value);
}
});
animationManager.SetSpeed(speed);
element.setAttribute("style", "width:200px");
var width=getCookie("VisualizationWidth");
if (width == null || width == "")
{
width = canvas.width;
}
else
{
width = parseInt(width);
}
var height=getCookie("VisualizationHeight");
if (height == null || height == "")
{
height = canvas.height;
}
else
{
height = parseInt(height);
}
var swappedControls=getCookie("VisualizationControlSwapped");
swapped = swappedControls == "true"
if (swapped)
{
reorderSibling(document.getElementById('canvas'), document.getElementById('generalAnimationControlSection'));
}
canvas.width = width;
canvas.height = height;
tableEntry = document.createElement("td");
txtNode = document.createTextNode(" 宽度 :");
tableEntry.appendChild(txtNode);
controlBar.appendChild(tableEntry);
widthEntry = addControlToAnimationBar("Text", canvas.width);
widthEntry.size = 4;
widthEntry.onkeydown = this.returnSubmit(widthEntry, animationManager.changeSize.bind(animationManager), 4, true);
tableEntry = document.createElement("td");
txtNode = document.createTextNode(" 高度 :");
tableEntry.appendChild(txtNode);
controlBar.appendChild(tableEntry);
heightEntry = addControlToAnimationBar("Text", canvas.height);
heightEntry.onkeydown = this.returnSubmit(heightEntry, animationManager.changeSize.bind(animationManager), 4, true);
// heightEntry.size = 4;
sizeButton = addControlToAnimationBar("Button", "更改画布大小");
sizeButton.onclick = animationManager.changeSize.bind(animationManager) ;
swapButton = addControlToAnimationBar("Button", "挪动控制面板");
swapButton.onclick = swapControlDiv;
animationManager.addListener("AnimationStarted", this, animStarted);
animationManager.addListener("AnimationEnded", this, this.animEnded);
animationManager.addListener("AnimationWaiting", this, this.animWaiting);
animationManager.addListener("AnimationUndoUnavailable", this, this.anumUndoUnavailable);
objectManager.width = canvas.width;
objectManager.height = canvas.height;
return animationManager;
}
function AnimationManager(objectManager)
{
// Holder for all animated objects.
// All animation is done by manipulating objects in\
// this container
this.animatedObjects = objectManager;
// Control variables for stopping / starting animation
this.animationPaused = false;
this.awaitingStep = false;
this.currentlyAnimating = false;
// Array holding the code for the animation. This is
// an array of strings, each of which is an animation command
// currentAnimation is an index into this array
this.AnimationSteps = [];
this.currentAnimation = 0;
this.previousAnimationSteps = [];
// Control variables for where we are in the current animation block.
// currFrame holds the frame number of the current animation block,
// while animationBlockLength holds the length of the current animation
// block (in frame numbers).
this.currFrame = 0;
this.animationBlockLength = 0;
// The animation block that is currently running. Array of singleAnimations
this.currentBlock = null;
/////////////////////////////////////
// Variables for handling undo.
////////////////////////////////////
// A stack of UndoBlock objects (subclassed, UndoBlock is an abstract base class)
// each of which can undo a single animation element
this.undoStack = [];
this.doingUndo = false;
// A stack containing the beginning of each animation block, as an index
// into the AnimationSteps array
this.undoAnimationStepIndices = [];
this.undoAnimationStepIndicesStack = [];
this.animationBlockLength = 10;
this.lerp = function(from, to, percent)
{
return (to - from) * percent + from;
}
// Pause / unpause animation
this.SetPaused = function(pausedValue)
{
this.animationPaused = pausedValue;
if (!this.animationPaused)
{
this.step();
}
}
// Set the speed of the animation, from 0 (slow) to 100 (fast)
this.SetSpeed = function(newSpeed)
{
this.animationBlockLength = Math.floor((100-newSpeed) / 2);
}
this.parseBool = function(str)
{
var uppercase = str.toUpperCase();
var returnVal = !(uppercase == "False" || uppercase == "f" || uppercase == " 0" || uppercase == "0" || uppercase == "");
return returnVal;
}
this.parseColor = function(clr)
{
if (clr.charAt(0) == "#")
{
return clr;
}
else if (clr.substring(0,2) == "0x")
{
return "#" + clr.substring(2);
}
}
this.changeSize = function()
{
var width = parseInt(widthEntry.value);
var height = parseInt(heightEntry.value);
if (width > 100)
{
canvas.width = width;
this.animatedObjects.width = width;
setCookie("VisualizationWidth", String(width), 30);
}
if (height > 100)
{
canvas.height = height;
this.animatedObjects.height = height;
setCookie("VisualizationHeight", String(height), 30);
}
width.value = canvas.width;
heightEntry.value = canvas.height;
this.animatedObjects.draw();
this.fireEvent("CanvasSizeChanged",{width:canvas.width, height:canvas.height});
}
this.startNextBlock = function()
{
this.awaitingStep = false;
this.currentBlock = [];
var undoBlock = []
if (this.currentAnimation == this.AnimationSteps.length )
{
this.currentlyAnimating = false;
this.awaitingStep = false;
this.fireEvent("AnimationEnded","NoData");
clearTimeout(timer);
this.animatedObjects.update();
this.animatedObjects.draw();
return;
}
this.undoAnimationStepIndices.push(this.currentAnimation);
var foundBreak= false;
var anyAnimations= false;
while (this.currentAnimation < this.AnimationSteps.length && !foundBreak)
{
var nextCommand = this.AnimationSteps[this.currentAnimation].split("<;>");
if (nextCommand[0].toUpperCase() == "CREATECIRCLE")
{
this.animatedObjects.addCircleObject(parseInt(nextCommand[1]), nextCommand[2]);
if (nextCommand.length > 4)
{
this.animatedObjects.setNodePosition(parseInt(nextCommand[1]), parseInt(nextCommand[3]), parseInt(nextCommand[4]));
}
undoBlock.push(new UndoCreate(parseInt(nextCommand[1])));
}
else if (nextCommand[0].toUpperCase() == "CONNECT")
{
if (nextCommand.length > 7)
{
this.animatedObjects.connectEdge(parseInt(nextCommand[1]),
parseInt(nextCommand[2]),
this.parseColor(nextCommand[3]),
parseFloat(nextCommand[4]),
this.parseBool(nextCommand[5]),
nextCommand[6],
parseInt(nextCommand[7]));
}
else if (nextCommand.length > 6)
{
this.animatedObjects.connectEdge(parseInt(nextCommand[1]),
parseInt(nextCommand[2]),
this.parseColor(nextCommand[3]),
parseFloat(nextCommand[4]),
this.parseBool(nextCommand[5]),
nextCommand[6],
0);
}
else if (nextCommand.length > 5)
{
this.animatedObjects.connectEdge(parseInt(nextCommand[1]),
parseInt(nextCommand[2]),
this.parseColor(nextCommand[3]),
parseFloat(nextCommand[4]),
this.parseBool(nextCommand[5]),
"",
0);
}
else if (nextCommand.length > 4)
{
this.animatedObjects.connectEdge(parseInt(nextCommand[1]),
parseInt(nextCommand[2]),
this.parseColor(nextCommand[3]),
parseFloat(nextCommand[4]),
true,
"",
0);
}
else if (nextCommand.length > 3)
{
this.animatedObjects.connectEdge(parseInt(nextCommand[1]),
parseInt(nextCommand[2]),
this.parseColor(nextCommand[3]),
0.0,
true,
"",
0);
}
else
{
this.animatedObjects.connectEdge(parseInt(nextCommand[1]),
parseInt(nextCommand[2]),
"#000000",
0.0,
true,
"",
0);
}
undoBlock.push(new UndoConnect(parseInt(nextCommand[1]), parseInt (nextCommand[2]), false));
}
else if (nextCommand[0].toUpperCase() == "CREATERECTANGLE")
{
if (nextCommand.length == 9)
{
this.animatedObjects.addRectangleObject(parseInt(nextCommand[1]), // ID
nextCommand[2], // Label
parseInt(nextCommand[3]), // w
parseInt(nextCommand[4]), // h
nextCommand[7], // xJustify
nextCommand[8],// yJustify
"#ffffff", // background color
"#000000"); // foreground color
}
else
{
this.animatedObjects.addRectangleObject(parseInt(nextCommand[1]), // ID
nextCommand[2], // Label
parseInt(nextCommand[3]), // w
parseInt(nextCommand[4]), // h
"center", // xJustify
"center",// yJustify
"#ffffff", // background color
"#000000"); // foreground color
}
if (nextCommand.length > 6)
{
this.animatedObjects.setNodePosition(parseInt(nextCommand[1]), parseInt(nextCommand[5]), parseInt(nextCommand[6]));
}
undoBlock.push(new UndoCreate(parseInt(nextCommand[1])));
}
else if (nextCommand[0].toUpperCase() == "MOVE")
{
var objectID = parseInt(nextCommand[1]);
var nextAnim = new SingleAnimation(objectID,
this.animatedObjects.getNodeX(objectID),
this.animatedObjects.getNodeY(objectID),
parseInt(nextCommand[2]),
parseInt(nextCommand[3]));
this.currentBlock.push(nextAnim);
undoBlock.push(new UndoMove(nextAnim.objectID, nextAnim.toX, nextAnim.toY, nextAnim.fromX, nextAnim.fromY));
anyAnimations = true;
}
else if (nextCommand[0].toUpperCase() == "MOVETOALIGNRIGHT")
{
var id = parseInt(nextCommand[1]);
var otherId = parseInt(nextCommand[2]);
var newXY = this.animatedObjects.getAlignRightPos(id, otherId);
var nextAnim = new SingleAnimation(id,
this.animatedObjects.getNodeX(id),
this.animatedObjects.getNodeY(id),
newXY[0],
newXY[1]);
this.currentBlock.push(nextAnim);
undoBlock.push(new UndoMove(nextAnim.objectID, nextAnim.toX, nextAnim.toY, nextAnim.fromX, nextAnim.fromY));
anyAnimations = true;
}
else if (nextCommand[0].toUpperCase() == "STEP")
{
foundBreak = true;
}
else if (nextCommand[0].toUpperCase() == "SETFOREGROUNDCOLOR")
{
var id = parseInt(nextCommand[1]);
var oldColor = this.animatedObjects.foregroundColor(id);
this.animatedObjects.setForegroundColor(id, this.parseColor(nextCommand[2]));
undoBlock.push(new UndoSetForegroundColor(id, oldColor));
}
else if (nextCommand[0].toUpperCase() == "SETBACKGROUNDCOLOR")
{
id = parseInt(nextCommand[1]);
oldColor = this.animatedObjects.backgroundColor(id);
this.animatedObjects.setBackgroundColor(id, this.parseColor(nextCommand[2]));
undoBlock.push(new UndoSetBackgroundColor(id, oldColor));
}
else if (nextCommand[0].toUpperCase() == "SETHIGHLIGHT")
{
var newHighlight = this.parseBool(nextCommand[2]);
this.animatedObjects.setHighlight( parseInt(nextCommand[1]), newHighlight);
undoBlock.push(new UndoHighlight( parseInt(nextCommand[1]), !newHighlight));
}
else if (nextCommand[0].toUpperCase() == "DISCONNECT")
{
var undoConnect = this.animatedObjects.disconnect(parseInt(nextCommand[1]), parseInt(nextCommand[2]));
if (undoConnect != null)
{
undoBlock.push(undoConnect);
}
}
else if (nextCommand[0].toUpperCase() == "SETALPHA")
{
var oldAlpha = this.animatedObjects.getAlpha(parseInt(nextCommand[1]));
this.animatedObjects.setAlpha(parseInt(nextCommand[1]), parseFloat(nextCommand[2]));
undoBlock.push(new UndoSetAlpha(parseInt(nextCommand[1]), oldAlpha));
}
else if (nextCommand[0].toUpperCase() == "SETTEXT")
{
if (nextCommand.length > 3)
{
var oldText = this.animatedObjects.getText(parseInt(nextCommand[1]), parseInt(nextCommand[3]));
this.animatedObjects.setText(parseInt(nextCommand[1]), nextCommand[2], parseInt(nextCommand[3]));
if (oldText != undefined)
{
undoBlock.push(new UndoSetText(parseInt(nextCommand[1]), oldText, parseInt(nextCommand[3]) ));
}
}
else
{
oldText = this.animatedObjects.getText(parseInt(nextCommand[1]), 0);
this.animatedObjects.setText(parseInt(nextCommand[1]), nextCommand[2], 0);
if (oldText != undefined)
{
undoBlock.push(new UndoSetText(parseInt(nextCommand[1]), oldText, 0));
}
}
}
else if (nextCommand[0].toUpperCase() == "DELETE")
{
var objectID = parseInt(nextCommand[1]);
var i;
var removedEdges = this.animatedObjects.deleteIncident(objectID);
if (removedEdges.length > 0)
{
undoBlock = undoBlock.concat(removedEdges);
}
var obj = this.animatedObjects.getObject(objectID);
if (obj != null)
{
undoBlock.push(obj.createUndoDelete());
this.animatedObjects.removeObject(objectID);
}
}
else if (nextCommand[0].toUpperCase() == "CREATEHIGHLIGHTCIRCLE")
{
if (nextCommand.length > 5)
{
this.animatedObjects.addHighlightCircleObject(parseInt(nextCommand[1]), this.parseColor(nextCommand[2]), parseFloat(nextCommand[5]));
}
else
{
this.animatedObjects.addHighlightCircleObject(parseInt(nextCommand[1]), this.parseColor(nextCommand[2]), 20);
}
if (nextCommand.length > 4)
{
this.animatedObjects.setNodePosition(parseInt(nextCommand[1]), parseInt(nextCommand[3]), parseInt(nextCommand[4]));
}
undoBlock.push(new UndoCreate(parseInt(nextCommand[1])));
}
else if (nextCommand[0].toUpperCase() == "CREATELABEL")
{
if (nextCommand.length == 6)
{
this.animatedObjects.addLabelObject(parseInt(nextCommand[1]), nextCommand[2], this.parseBool(nextCommand[5]));
}
else
{
this.animatedObjects.addLabelObject(parseInt(nextCommand[1]), nextCommand[2], true);
}
if (nextCommand.length >= 5)
{
this.animatedObjects.setNodePosition(parseInt(nextCommand[1]), parseFloat(nextCommand[3]), parseFloat(nextCommand[4]));
}
undoBlock.push(new UndoCreate(parseInt(nextCommand[1])));
}
else if (nextCommand[0].toUpperCase() == "SETEDGECOLOR")
{
var from = parseInt(nextCommand[1]);
var to = parseInt(nextCommand[2]);
var newColor = this.parseColor(nextCommand[3]);
var oldColor = this.animatedObjects.setEdgeColor(from, to, newColor);
undoBlock.push(new UndoSetEdgeColor(from, to, oldColor));
}
else if (nextCommand[0].toUpperCase() == "SETEDGEALPHA")
{
var from = parseInt(nextCommand[1]);
var to = parseInt(nextCommand[2]);
var newAlpha = parseFloat(nextCommand[3]);
var oldAplpha = this.animatedObjects.setEdgeAlpha(from, to, newAlpha);
undoBlock.push(new UndoSetEdgeAlpha(from, to, oldAplpha));
}
else if (nextCommand[0].toUpperCase() == "SETEDGEHIGHLIGHT")
{
var newHighlight = this.parseBool(nextCommand[3]);
var from = parseInt(nextCommand[1]);
var to = parseInt(nextCommand[2]);
var oldHighlight = this.animatedObjects.setEdgeHighlight(from, to, newHighlight);
undoBlock.push(new UndoHighlightEdge(from, to, oldHighlight));
}
else if (nextCommand[0].toUpperCase() == "SETHEIGHT")
{
id = parseInt(nextCommand[1]);
var oldHeight = this.animatedObjects.getHeight(id);
this.animatedObjects.setHeight(id, parseInt(nextCommand[2]));
undoBlock.push(new UndoSetHeight(id, oldHeight));
}
else if (nextCommand[0].toUpperCase() == "SETLAYER")
{
this.animatedObjects.setLayer(parseInt(nextCommand[1]), parseInt(nextCommand[2]));
//TODO: Add undo information here
}
else if (nextCommand[0].toUpperCase() == "CREATELINKEDLIST")
{
if (nextCommand.length == 11)
{
this.animatedObjects.addLinkedListObject(parseInt(nextCommand[1]), nextCommand[2],
parseInt(nextCommand[3]), parseInt(nextCommand[4]), parseFloat(nextCommand[7]),
this.parseBool(nextCommand[8]), this.parseBool(nextCommand[9]),parseInt(nextCommand[10]), "#FFFFFF", "#000000");
}
else
{
this.animatedObjects.addLinkedListObject(parseInt(nextCommand[1]), nextCommand[2], parseInt(nextCommand[3]), parseInt(nextCommand[4]), 0.25, true, false, 1, "#FFFFFF", "#000000");
}
if (nextCommand.length > 6)
{
this.animatedObjects.setNodePosition(parseInt(nextCommand[1]), parseInt(nextCommand[5]), parseInt(nextCommand[6]));
undoBlock.push(new UndoCreate(parseInt(nextCommand[1])));
}
}
else if (nextCommand[0].toUpperCase() == "SETNULL")
{
var oldNull = this.animatedObjects.getNull(parseInt(nextCommand[1]));
this.animatedObjects.setNull(parseInt(nextCommand[1]), this.parseBool(nextCommand[2]));
undoBlock.push(new UndoSetNull(parseInt(nextCommand[1]), oldNull));
}
else if (nextCommand[0].toUpperCase() == "SETTEXTCOLOR")
{
if (nextCommand.length > 3)
{
oldColor = this.animatedObjects.getTextColor(parseInt(nextCommand[1]), parseInt(nextCommand[3]));
this.animatedObjects.setTextColor(parseInt(nextCommand[1]), this.parseColor(nextCommand[2]), parseInt(nextCommand[3]));
undoBlock.push(new UndoSetTextColor(parseInt(nextCommand[1]), oldColor, parseInt(nextCommand[3]) ));
}
else
{
oldColor = this.animatedObjects.getTextColor(parseInt(nextCommand[1]), 0);
this.animatedObjects.setTextColor(parseInt(nextCommand[1]),this.parseColor(nextCommand[2]), 0);
undoBlock.push(new UndoSetTextColor(parseInt(nextCommand[1]), oldColor, 0));
}
}
else if (nextCommand[0].toUpperCase() == "CREATEBTREENODE")
{
this.animatedObjects.addBTreeNode(parseInt(nextCommand[1]), parseFloat(nextCommand[2]), parseFloat(nextCommand[3]),
parseInt(nextCommand[4]),this.parseColor(nextCommand[7]), this.parseColor(nextCommand[8]));
this.animatedObjects.setNodePosition(parseInt(nextCommand[1]), parseInt(nextCommand[5]), parseInt(nextCommand[6]));
undoBlock.push(new UndoCreate(parseInt(nextCommand[1])));
}
else if (nextCommand[0].toUpperCase() == "SETWIDTH")
{
var id = parseInt(nextCommand[1]);
this.animatedObjects.setWidth(id, parseInt(nextCommand[2]));
var oldWidth = this.animatedObjects.getWidth(id);
undoBlock.push(new UndoSetWidth(id, oldWidth));
}
else if (nextCommand[0].toUpperCase() == "SETNUMELEMENTS")
{
var oldElem = this.animatedObjects.getObject(parseInt(nextCommand[1]));
undoBlock.push(new UndoSetNumElements(oldElem, parseInt(nextCommand[2])));
this.animatedObjects.setNumElements(parseInt(nextCommand[1]), parseInt(nextCommand[2]));
}
else if (nextCommand[0].toUpperCase() == "SETPOSITION")
{
var id = parseInt(nextCommand[1])
var oldX = this.animatedObjects.getNodeX(id);
var oldY = this.animatedObjects.getNodeY(id);
undoBlock.push(new UndoSetPosition(id, oldX, oldY));
this.animatedObjects.setNodePosition(id, parseInt(nextCommand[2]), parseInt(nextCommand[3]));
}
else if (nextCommand[0].toUpperCase() == "ALIGNRIGHT")
{
var id = parseInt(nextCommand[1])
var oldX = this.animatedObjects.getNodeX(id);
var oldY = this.animatedObjects.getNodeY(id);
undoBlock.push(new UndoSetPosition(id, oldX. oldY));
this.animatedObjects.alignRight(id, parseInt(nextCommand[2]));
}
else if (nextCommand[0].toUpperCase() == "ALIGNLEFT")
{
var id = parseInt(nextCommand[1])
var oldX = this.animatedObjects.getNodeX(id);
var oldY = this.animatedObjects.getNodeY(id);
undoBlock.push(new UndoSetPosition(id, oldX. oldY));
this.animatedObjects.alignLeft(id, parseInt(nextCommand[2]));
}
else if (nextCommand[0].toUpperCase() == "ALIGNTOP")
{
var id = parseInt(nextCommand[1])
var oldX = this.animatedObjects.getNodeX(id);
var oldY = this.animatedObjects.getNodeY(id);
undoBlock.push(new UndoSetPosition(id, oldX. oldY));
this.animatedObjects.alignTop(id, parseInt(nextCommand[2]));
}
else if (nextCommand[0].toUpperCase() == "ALIGNBOTTOM")
{
var id = parseInt(nextCommand[1])
var oldX = this.animatedObjects.getNodeX(id);
var oldY = this.animatedObjects.getNodeY(id);
undoBlock.push(new UndoSetPosition(id, oldX. oldY));
this.animatedObjects.alignBottom(id, parseInt(nextCommand[2]));
}
else if (nextCommand[0].toUpperCase() == "SETHIGHLIGHTINDEX")
{
var id = parseInt(nextCommand[1]);
var index = parseInt(nextCommand[2]);
var oldIndex = this.animatedObjects.getHighlightIndex(id)
undoBlock.push(new UndoSetHighlightIndex(id, oldIndex));
this.animatedObjects.setHighlightIndex(id,index);
}
else
{
// throw "Unknown command: " + nextCommand[0];
}
this.currentAnimation = this.currentAnimation+1;
}
this.currFrame = 0;
// Hack: If there are not any animations, and we are currently paused,
// then set the current frame to the end of the anumation, so that we will
// advance immediagely upon the next step button. If we are not paused, then
// animate as normal.
if (!anyAnimations && this.animationPaused || (!anyAnimations && this.currentAnimation == this.AnimationSteps.length) )
{
this.currFrame = this.animationBlockLength;
}
this.undoStack.push(undoBlock);
}
// Start a new animation. The input parameter commands is an array of strings,
// which represents the animation to start
this.StartNewAnimation = function(commands)
{
clearTimeout(timer);
if (this.AnimationSteps != null)
{
this.previousAnimationSteps.push(this.AnimationSteps);
this.undoAnimationStepIndicesStack.push(this.undoAnimationStepIndices);
}
if (commands == undefined || commands.length == 0)
{
this.AnimationSteps = ["Step"];
}
else
{
this.AnimationSteps = commands;
}
this.undoAnimationStepIndices = new Array();
this.currentAnimation = 0;
this.startNextBlock();
this.currentlyAnimating = true;
this.fireEvent("AnimationStarted","NoData");
timer = setTimeout('timeout()', 30);
}
// Step backwards one step. A no-op if the animation is not currently paused
this.stepBack = function()
{
if (this.awaitingStep && this.undoStack != null && this.undoStack.length != 0)
{
// TODO: Get events working correctly!
this.fireEvent("AnimationStarted","NoData");
clearTimeout(timer);
this.awaitingStep = false;
this.undoLastBlock();
// Re-kick thie timer. The timer may or may not be running at this point,
// so to be safe we'll kill it and start it again.
clearTimeout(timer);
timer = setTimeout('timeout()', 30);
}
else if (!this.currentlyAnimating && this.animationPaused && this.undoAnimationStepIndices != null)
{
this.fireEvent("AnimationStarted","NoData");
this.currentlyAnimating = true;
this.undoLastBlock();
// Re-kick thie timer. The timer may or may not be running at this point,
// so to be safe we'll kill it and start it again.
clearTimeout(timer);
timer = setTimeout('timeout()', 30);
}
}
// Step forwards one step. A no-op if the animation is not currently paused
this.step = function()
{
if (this.awaitingStep)
{
this.startNextBlock();
this.fireEvent("AnimationStarted","NoData");
this.currentlyAnimating = true;
// Re-kick thie timer. The timer should be going now, but we've had some difficulty with
// it timing itself out, so we'll be safe and kick it now.
clearTimeout(timer);
timer = setTimeout('timeout()', 30);
}
}
/// WARNING: Could be dangerous to call while an animation is running ...
this.clearHistory = function()
{
this.undoStack = [];
this.undoAnimationStepIndices = null;
this.previousAnimationSteps = [];
this.undoAnimationStepIndicesStack = [];
this.AnimationSteps = null;
this.fireEvent("AnimationUndoUnavailable","NoData");
clearTimeout(timer);
this.animatedObjects.update();
this.animatedObjects.draw();
}
this.skipBack = function()
{
var keepUndoing = this.undoAnimationStepIndices != null && this. undoAnimationStepIndices.length != 0;
if (keepUndoing)
{
var i;
for (i = 0; this.currentBlock != null && i < this.currentBlock.length; i++)
{
var objectID = this.currentBlock[i].objectID;
this.animatedObjects.setNodePosition(objectID,
this.currentBlock[i].toX,
this.currentBlock[i].toY);
}
if (this.doingUndo)
{
this.finishUndoBlock(this.undoStack.pop())
}
while (keepUndoing)
{
this.undoLastBlock();
for (i = 0; i < this.currentBlock.length; i++)
{
objectID = this.currentBlock[i].objectID;
this.animatedObjects.setNodePosition(objectID,
this.currentBlock[i].toX,
this.currentBlock[i].toY);
}
keepUndoing = this.finishUndoBlock(this.undoStack.pop());
}
clearTimeout(timer);
this.animatedObjects.update();
this.animatedObjects.draw();
if (this.undoStack == null || this.undoStack.length == 0)
{
this.fireEvent("AnimationUndoUnavailable","NoData");
}
}
}
this.resetAll = function()
{
this.clearHistory();
this.animatedObjects.clearAllObjects();
this.animatedObjects.draw();
clearTimeout(timer);
}
this.skipForward = function()
{
if (this.currentlyAnimating)
{
this.animatedObjects.runFast = true;
while (this.AnimationSteps != null && this.currentAnimation < this.AnimationSteps.length)
{
var i;
for (i = 0; this.currentBlock != null && i < this.currentBlock.length; i++)
{
var objectID = this.currentBlock[i].objectID;
this.animatedObjects.setNodePosition(objectID,
this.currentBlock[i].toX,
this.currentBlock[i].toY);
}
if (this.doingUndo)
{
this.finishUndoBlock(this.undoStack.pop())
}
this.startNextBlock();
for (i= 0; i < this.currentBlock.length; i++)
{
var objectID = this.currentBlock[i].objectID;
this.animatedObjects.setNodePosition(objectID,
this.currentBlock[i].toX,
this.currentBlock[i].toY);
}
}
this.animatedObjects.update();
this.currentlyAnimating = false;
this.awaitingStep = false;
this.doingUndo = false;
this.animatedObjects.runFast = false;
this.fireEvent("AnimationEnded","NoData");
clearTimeout(timer);
this.animatedObjects.update();
this.animatedObjects.draw();
}
}
this.finishUndoBlock = function(undoBlock)
{
for (var i = undoBlock.length - 1; i >= 0; i--)
{
undoBlock[i].undoInitialStep(this.animatedObjects);
}
this.doingUndo = false;
// If we are at the final end of the animation ...
if (this.undoAnimationStepIndices.length == 0)
{
this.awaitingStep = false;
this.currentlyAnimating = false;
this.undoAnimationStepIndices = this.undoAnimationStepIndicesStack.pop();
this.AnimationSteps = this.previousAnimationSteps.pop();
this.fireEvent("AnimationEnded","NoData");
this.fireEvent("AnimationUndo","NoData");
this.currentBlock = [];
if (this.undoStack == null || this.undoStack.length == 0)
{
this.currentlyAnimating = false;
this.awaitingStep = false;
this.fireEvent("AnimationUndoUnavailable","NoData");
}
clearTimeout(timer);
this.animatedObjects.update();
this.animatedObjects.draw();
return false;
}
return true;
}
this.undoLastBlock = function()
{
if (this.undoAnimationStepIndices.length == 0)
{
// Nothing on the undo stack. Return
return;
}
if (this.undoAnimationStepIndices.length > 0)
{
this.doingUndo = true;
var anyAnimations = false;
this.currentAnimation = this.undoAnimationStepIndices.pop();
this.currentBlock = [];
var undo = this.undoStack[this.undoStack.length - 1];
var i;
for (i = undo.length - 1; i >= 0; i--)
{
var animateNext = undo[i].addUndoAnimation(this.currentBlock);
anyAnimations = anyAnimations || animateNext;
}
this.currFrame = 0;
// Hack: If there are not any animations, and we are currently paused,
// then set the current frame to the end of the animation, so that we will
// advance immediagely upon the next step button. If we are not paused, then
// animate as normal.
if (!anyAnimations && this.animationPaused )
{
this.currFrame = this.animationBlockLength;
}
this.currentlyAnimating = true;
}
}
this.setLayer = function(shown, layers)
{
this.animatedObjects.setLayer(shown, layers)
// Drop in an extra draw call here, just in case we are not
// in the middle of an update loop when this changes
this.animatedObjects.draw();
}
this.setAllLayers = function(layers)
{
this.animatedObjects.setAllLayers(layers);
// Drop in an extra draw call here, just in case we are not
// in the middle of an update loop when this changes
this.animatedObjects.draw();
}
this.update = function()
{
if (this.currentlyAnimating)
{
this.currFrame = this.currFrame + 1;
var i;
for (i = 0; i < this.currentBlock.length; i++)
{
if (this.currFrame == this.animationBlockLength || (this.currFrame == 1 && this.animationBlockLength == 0))
{
this.animatedObjects.setNodePosition(this.currentBlock[i].objectID,
this.currentBlock[i].toX,
this.currentBlock[i].toY);
}
else if (this.currFrame < this.animationBlockLength)
{
var objectID = this.currentBlock[i].objectID;
var percent = 1 / (this.animationBlockLength - this.currFrame);
var oldX = this.animatedObjects.getNodeX(objectID);
var oldY = this.animatedObjects.getNodeY(objectID);
var targetX = this.currentBlock[i].toX;
var targety = this.currentBlock[i].toY;
var newX = this.lerp(this.animatedObjects.getNodeX(objectID), this.currentBlock[i].toX, percent);
var newY = this.lerp(this.animatedObjects.getNodeY(objectID), this.currentBlock[i].toY, percent);
this.animatedObjects.setNodePosition(objectID, newX, newY);
}
}
if (this.currFrame >= this.animationBlockLength)
{
if (this.doingUndo)
{
if (this.finishUndoBlock(this.undoStack.pop()))
{
this.awaitingStep = true;
this.fireEvent("AnimationWaiting","NoData");
}
}
else
{
if (this.animationPaused && (this.currentAnimation < this.AnimationSteps.length))
{
this.awaitingStep = true;
this.fireEvent("AnimationWaiting","NoData");
this.currentBlock = [];
}
else
{
this.startNextBlock();
}
}
}
this.animatedObjects.update();
}
}
}
AnimationManager.prototype = new EventListener();
AnimationManager.prototype.constructor = AnimationManager;
function SingleAnimation(id, fromX, fromY, toX, toY)
{
this.objectID = id;
this.fromX = fromX;
this.fromY = fromY;
this.toX = toX;
this.toY = toY;
}