In the previous article by a similar name, I discussed the firing of arbitrary function at run-time. This is something which is genuinely useful and I hope it has been of use to some. I know for certain that it has been a help to me. But, it would be far more useful (for those using it, at least) if it had a basic command line built into it and that is one of the purposes of this article. Before we continue, you're going to want to download the FLA (and, if you are content to completely ignore the previous article, the source for runTime.Tools.debugger), just because it is easier that way. If you don't want to do that (say, for some reason, you're convinced that corporal mortification is useful and you would like to have everything drawn dynamically (A genuine headache if you'd really like it to look its best)), then I will start by explaining what everything is.

The FLA is pretty self-evident. The first frame has the console on it with a stop action. The console has a minimize button, a close button, a "previous commands" field, and an input text. The second frame has the same thing, only now the "previous commands" field is very small and hidden behind the input text (this prevents errors in the script). The second frame of the root has a black box. Once you have everything (including the source below) set up the right way, you will be able to test the console by simply firing 'root.gotoAndStop(2)'

Most of the code for the console is pretty self-evident.

package runTime.Tools {
import runTime.Tools.debugger;
import flash.display.*;
import flash.text.*;
import flash.events.*;

/*
* Yes, I imported a lot of stuff. That is largely because this will eventually (I expect)
* be a compiled clip. And since these will realistically only slow me down during that
* process, I don't really care. I have probably wasted more time explaining just now
* than it would have taken me to actually think about it, but I have principles.
*/


public class console extends MovieClip{ /*
* Not Sprite because I'm using two frames to
* handle the minimization, because I'm lazy
* and do not believe that corporal mortification
* has a place in code. It makes things unclear
* to others. Besides, I think Sprite tastes funny.
*/


private var dbg:debugger;
private var _history:Array; // Array of all of the previous commands
private var _pos:int; // position the up arrow is relative to the history

public function console(){
_history = new Array();
dbg = new debugger();


There was a problem the last time with how the debugger could find its origin. 'root' is no longer a global variable, nor is Stage. There needs to be a way for the debugger to reference other items. There are two solutions. You could either pass the root into the debugger's constructor (what happened last time), or you could have the debugger subclass DisplayObject. Because I find that having that functionality is actually a lot more useful, I decided to make the debugger a subclass of DisplayObject.




Continuing with the constructor, we have:
public function console(){
_history = new Array();
dbg = new debugger();
addChild(dbg);

// And here begin the listeners!
// minimize is my name for my minimizing button.
minimize.addEventListener(MouseEvent.MOUSE_UP, toggleMinimize);

// closeButton toggles visibility.
closeButton.addEventListener(MouseEvent.MOUSE_UP, toggleVisible);

// This will be useful later.
background.addEventListener(MouseEvent.MOUSE_DOWN, startMove);
background.addEventListener(MouseEvent.MOUSE_UP, stopMove);


Hopefully that is pretty clear. You press the minimize button, and it gets smaller. You click the x and the debugger goes away. startMove and stopMove are both wrappers around startDrag and stopDrag. This way, your console does not always have to be in the top, left (something I forgot to add to my alpha version...). The code for each of those functions is:

public function toggleVisible(a = null):void{
this.visible = !this.visible; // duh.

// You can make this function more extensive. You have a lot of
// listeners in this thing, that will take up memory.
// For now, however, this functions.
}

private function startMove(a = null):void{
this.startDrag(false);
}

private function stopMove(a = null):void{
this.stopDrag();
}


You might notice all of the (a = null)'s listed above. They are there because of AS3's tendency to disallow functions to accept too many parameters.




So now we have something which will move and become invisible. But there is no way to make it visible again, and it doesn't really DO anything yet. That is where the key processing comes in. Add the following to the constructor:

stage.addEventListener(KeyboardEvent.KEY_DOWN, keyHandler);
this.addEventListener(KeyboardEvent.KEY_UP,keyup);


The stage listener is to ensure that a persistent object is listening for the necessary key strokes. This way visible can be toggled at will. The function source:

// Ctrl-Shift-~

private function keyHandler(key:KeyboardEvent = null):void{
if (key.ctrlKey && key.shiftKey && (key.keyCode == 192)) {
toggleVisible();
}
}


The other listener is to simulate the command-line interface. The up arrow will go backwards through commands, and the enter will run the command from the input device.

private function keyup (key:KeyboardEvent = null):void{
if (key.keyCode == 38) {
if (_history.length == 0) return;
inputText.text = _history[_pos%_history.length];
_pos++;
} else if (key.keyCode == 13) {
evalText();
}
}


All that is left is the command which sends everything to the debugger.

private function evalText(a = null):void{
// Add the current command to the displayed commands
// txtFld holds all of the formerly fired commands.
// inputText has the new ones.
txtFld.text = inputText.text + '\n' + txtFld.text

try {
dbg.runString(inputText.text);
} catch (e:Error) {
// In case a null reference is passed, this will cause it to be displayed.
txtFld.appendText(e.getStackTrace());
}

// since this should be "last in, first out"
_history.unshift(inputText.text);

// Your position in the history is now 0
_pos = 0;

// reset the command line.
inputText.text = '';
}





Finally, the code en masse:

package runTime.Tools {

import runTime.Tools.debugger;
import flash.display.*;
import flash.text.*;
import flash.events.*;

public class console extends MovieClip{
private var dbg:debugger;
private var _history:Array;
private var _pos:int;

public function console(){
_history = new Array();
toggleVisible();
dbg = new debugger();
addChild(dbg);

minimize.addEventListener(MouseEvent.MOUSE_UP, toggleMinimize);
closeButton.addEventListener(MouseEvent.MOUSE_UP, toggleVisible);
background.addEventListener(MouseEvent.MOUSE_DOWN, startMove);
background.addEventListener(MouseEvent.MOUSE_UP, stopMove);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyHandler);
this.addEventListener(KeyboardEvent.KEY_UP,keyup);
}

public function toggleVisible(a = null):void{
this.visible = !this.visible;
}

private function startMove(a = null):void{
this.startDrag(false);
}

private function stopMove(a = null):void{
this.stopDrag();
}

private function evalText(a = null):void{
txtFld.text = inputText.text + '\n' + txtFld.text

try {
dbg.runString(inputText.text);
} catch (e:Error) {
txtFld.appendText(e.getStackTrace());
}

_history.unshift(inputText.text);
_pos = 0;
inputText.text = '';
}

private function toggleMinimize (a = null):void{
background.removeEventListener(MouseEvent.MOUSE_DOWN, startMove);
background.removeEventListener(MouseEvent.MOUSE_UP, stopMove);

var go:int = (this.currentFrame == 1)?2:1;

this.gotoAndStop(go);
minimize.gotoAndStop(go);

background.addEventListener(MouseEvent.MOUSE_DOWN, startMove);
background.addEventListener(MouseEvent.MOUSE_UP, stopMove);
}

private function keyHandler(key:KeyboardEvent = null):void{
if (key.ctrlKey && key.shiftKey && (key.keyCode == 192)) {
toggleVisible();
}
}

private function keyup (key:KeyboardEvent = null):void{
if (key.keyCode == 38) {
if (_history.length == 0) return;
inputText.text = _history[_pos%_history.length];
_pos++;
} else if (key.keyCode == 13) {
evalText();
}
}
}
}