Wednesday, October 31, 2012

Creating EasyKeyboard class: Part 13

Today we'll further improve the abilities to remove listeners.

The removeListener() function works fine now, but there's a slight problem with removing TimedListeners. The issue is in the Timer, which doesn't stop working even after the listener was "removed". Aside from just removing it from the listeners array, we also need to reset and nullify the timer inside the listener.

You can witness the bug yourself if you create a timed listener and a key listener which removes the timed listener, then try to hold the key which triggers the timed event. If you remove the listener during the delay, you'll see that the code dispatches the timed event one more time before stopping.

keyboard = new EasyKeyboard(stage);
keyboard.addEasyTimedListener("A", 1000, function() { trace("Key A is pressed!!") }, true, "key1");
keyboard.addEasyListener("S", function () { keyboard.removeListener("key1"); trace("Listener for A key removed!"); } );

Let's fix this. Update the removeListener() function:

/**
 * Remove a single listener.
 * @paramlistenerId Id of the listener, which is specified as String when the listener is created.
 */

public function removeListener(listenerId:String):void {
for (var i:int = listeners.length - 1; i >= 0; i--) {
if (listeners[i].id == listenerId) {
if (listeners[i] is TimedListener) {
listeners[i].delayTimer.reset();
listeners[i].delayTimer = null;
}
listeners.splice(i, 1);
break;
}
}
}

Now we'll add a function which removes all the listeners at once. Right now we have one called kill(), but it didn't actually remove all the listeners from the array - it just stops listening to the keyboard as a whole.

The removeAllListeners() method will loop through all listeners and remove them one by one, also taking care of timers the same way like in removeListener:

/**
 * Remove all listeners. IMPORATNT: If you are removing the EasyKeyboard object as a whole, use the kill() method.
 */

public function removeAllListeners():void {
for (var i:int = listeners.length - 1; i >= 0; i--) {
if (listeners[i] is TimedListener) {
listeners[i].delayTimer.reset();
listeners[i].delayTimer = null;
}
listeners.splice(i, 1);
}
}

We should also call this function in kill():

/**
 * Call this before nullifying the class instance to remove all the listeners.
 */

public function kill():void {
removeAllListeners();
st.removeEventListener(KeyboardEvent.KEY_DOWN, kDown);
st.removeEventListener(KeyboardEvent.KEY_UP, kUp);
st.addEventListener(Event.ENTER_FRAME, frame);
}

So, to remove all the listeners, the user simply writes:

keyboard.removeAllListeners();

Full EasyKeyboard.as class code so far:

package com.kircode.EasyKeyboard 
{
import flash.display.Stage;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.utils.Timer;

/**
 * Utility for easy keyboard listener management.
 * @author Kirill Poletaev
 */
public class EasyKeyboard 
{
public var listeners:Array = [];
private var keyLabels:Array = ["0","1","2","3","4","5","6","7","Backspace","Tab","10","11","Center","Enter","14","15","Shift","Control","Alt","Pause","Caps Lock","21","22","23","24","25","26","27","28","29","30","31","Space","Page Up","Page Down","End","Home","Left","Up","Right","Down","41","42","43","44","Insert","Delete","47","0","1","2","3","4","5","6","7","8","9","58","59","60","61","62","63","64","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","Windows","Windows","Menu","94","95","Num 0","Num 1","Num 2","Num 3","Num 4","Num 5","Num 6","Num 7","Num 8","Num 9","Num *","Num +","108","Num -","Num .","Num /","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","124","125","126","127","128","129","130","131","132","133","134","135","136","137","138","139","140","141","142","143","Num Lock","Scroll Lock","146","147","148","149","150","151","152","153","154","155","156","157","158","159","160","161","162","163","164","165","166","167","168","169","170","171","172","173","174","175","176","177","178","179","180","181","182","183","184","185",";","+",",","-",".","/","~","193","194","195","196","197","198","199","200","201","202","203","204","205","206","207","208","209","210","211","212","213","214","215","216","217","218","[","\\","]","'","223","224","225","226","227","228","229","230","231","232","233","234","235","236","237","238","239","240","241","242","243","244","245","246","247","248","249","250","251","252","253","254","255"];
private var st:Stage;

public function EasyKeyboard(stage:Stage) 
{
stage.addEventListener(KeyboardEvent.KEY_DOWN, kDown);
stage.addEventListener(KeyboardEvent.KEY_UP, kUp);
stage.addEventListener(Event.ENTER_FRAME, frame);
st = stage;
}

private function frame(evt:Event):void {
for (var i:int = 0; i < listeners.length; i++) {
if (listeners[i] is HoldListener && listeners[i].flag) {
if (listeners[i].handler != null) listeners[i].handler.call();
}
}
}

/**
 * Call this before nullifying the class instance to remove all the listeners.
 */

public function kill():void {
removeAllListeners();
st.removeEventListener(KeyboardEvent.KEY_DOWN, kDown);
st.removeEventListener(KeyboardEvent.KEY_UP, kUp);
st.addEventListener(Event.ENTER_FRAME, frame);
}

/**
 * Add event listener for a single key using a keycode.
 * @paramkeyCode Key code of the key.
 * @paramhandlerDown Function to be called when the key is pressed down.
 * @paramhandlerUp Function to be called when the key is released.
 * @paramalt Used in combination with the alt key.
 * @paramctrl Used in combination with the ctrl key.
 * @paramshift Used in combination with the shift key.
 * @paramid ID of the listener. Set this to be able to remove the listener dynamically later using the removeListener() method.
 */

public function addListener(keyCode:int, handlerDown:Function = null, handlerUp:Function = null, alt:Boolean = false, ctrl:Boolean = false, shift:Boolean = false, id:String = ""):void {
listeners.push(new KeyListener(keyCode, handlerDown, handlerUp, alt, ctrl, shift, id));
}

/**
 * Add event listener for a single key using a keycode.
 * @paramkeyCode Key code of the key.
 * @paramhandler Function to execute while the key is held every frame.
 * @paramid ID of the listener. Set this to be able to remove the listener dynamically later using the removeListener() method.
 */

public function addHoldListener(keyCode:int, handler:Function, id:String = ""):void {
listeners.push(new HoldListener(keyCode, handler, false, id));
}


/**
 * Add event listener for a combination of keys using key codes.
 * @paramkeyCodes Array of key codes for the combination.
 * @paramhandler Function to execute when the combination is held.
 * @paramid ID of the listener. Set this to be able to remove the listener dynamically later using the removeListener() method.
 */

public function addComboListener(keyCodes:Array, handler:Function, id:String = ""):void {
var flags:Array = [];
for (var i:int = 0; i < keyCodes.length; i++) {
if (isNaN(keyCodes[i])) {
throw new Error('Incorrect key code value specified - "' + keyCodes[i] + '" is not a number.');
return;
}
flags.push(false);
}
listeners.push(new ComboListener(keyCodes, handler, flags, id));
}

/**
 * Add event timed listener for a single key using a keycode.
 * @paramkeyCode Key code of the key.
 * @paramdelay Delay in milliseconds of how long the key needs to be held down for the event to be registered.
 * @paramhandler Function to execute when the key has been held for the specified amount of time.
 * @paramrepeat Set to true if you want to dispatch the event forever until the key is released. If you only want to dispatch it once, set to false.
 * @paramid ID of the listener. Set this to be able to remove the listener dynamically later using the removeListener() method.
 */

public function addTimedListener(keyCode:int, delay:int, handler:Function, repeat:Boolean = true, id:String = ""):void {
var rep:int = 1;
if (repeat == true) rep = 0;
var delayTimer:Timer = new Timer(delay, 1);
listeners.push(new TimedListener(keyCode, delayTimer, handler, rep, id));
}

/**
 * Add event listener for a sequence of keys to be pressed.
 * @paramkeyCodes Array of key codes in the sequence.
 * @paramhandler Function to execute when the keys are pressed in the specific order.
 * @paramid ID of the listener. Set this to be able to remove the listener dynamically later using the removeListener() method.
 */

public function addSequenceListener(keyCodes:Array, handler:Function, id:String = ""):void {
for (var i:int = 0; i < keyCodes.length; i++) {
if (isNaN(keyCodes[i])) {
throw new Error('Incorrect key code value specified - "' + keyCodes[i] + '" is not a number.');
return;
}
}
listeners.push(new SequenceListener(keyCodes, handler, id));
}

/**
 * Add event listener for a single key using key string value.
 * @paramkeyName String name of the key.
 * @paramhandlerDown Function to be called when the key is pressed down.
 * @paramhandlerUp Function to be called when the key is released.
 * @paramalt Used in combination with the alt key.
 * @paramctrl Used in combination with the ctrl key.
 * @paramshift Used in combination with the shift key.
 * @paramid ID of the listener. Set this to be able to remove the listener dynamically later using the removeListener() method.
 */

public function addEasyListener(keyName:String, handlerDown:Function = null, handlerUp:Function = null, alt:Boolean = false, ctrl:Boolean = false, shift:Boolean = false, id:String = ""):void {
var code:int = -1;
for (var i:int = 0; i < keyLabels.length; i++) {
if (keyLabels[i] == keyName) {
code = i;
break;
}
}
if (code == -1) {
throw new Error('Incorrect key string value specified - no "' + keyName + '" key found.');
return;
}
addListener(code, handlerDown, handlerUp, alt, ctrl, shift, id);
}

/**
 * Add hold listener for a single key using key string value.
 * @paramkeyName String name of the key.
 * @paramhandler Function to execute while the key is held every frame.
 * @paramid ID of the listener. Set this to be able to remove the listener dynamically later using the removeListener() method.
 */

public function addEasyHoldListener(keyName:String, handler:Function, id:String = ""):void {
var code:int = -1;
for (var i:int = 0; i < keyLabels.length; i++) {
if (keyLabels[i] == keyName) {
code = i;
break;
}
}
if (code == -1) {
throw new Error('Incorrect key string value specified - no "' + keyName + '" key found.');
return;
}
addHoldListener(code, handler, id);
}

/**
 * Add event listener for a combination of keys using key names.
 * @paramkeyNames Array of key names for the combination.
 * @paramhandler Function to execute when the combination is held.
 * @paramid ID of the listener. Set this to be able to remove the listener dynamically later using the removeListener() method.
 */

public function addEasyComboListener(keyNames:Array, handler:Function, id:String = ""):void {
var flags:Array = [];
var keyCodes:Array = [];
var u:int;
var i:int;
var code:int = -1;
var keyName:String;

for (u = 0; u < keyNames.length; u++) {
flags.push(false);
code = -1;
keyName = keyNames[u];
for (i = 0; i < keyLabels.length; i++) {
if (keyLabels[i] == keyName) {
code = i;
keyCodes.push(code);
break;
}
}
if (code == -1) {
throw new Error('Incorrect key string value specified - no "' + keyName + '" key found.');
return;
}
}

listeners.push(new ComboListener(keyCodes, handler, flags, id));
}

/**
 * Add event listener for a sequence of keys to be pressed using key names.
 * @paramkeyNames Array of key names in the sequence.
 * @paramhandler Function to execute when the keys are pressed in the specific order.
 * @paramid ID of the listener. Set this to be able to remove the listener dynamically later using the removeListener() method.
 */

public function addEasySequenceListener(keyNames:Array, handler:Function, id:String = ""):void {
var keyCodes:Array = [];
var u:int;
var i:int;
var code:int = -1;
var keyName:String;

for (u = 0; u < keyNames.length; u++) {
code = -1;
keyName = keyNames[u];
for (i = 0; i < keyLabels.length; i++) {
if (keyLabels[i] == keyName) {
code = i;
keyCodes.push(code);
break;
}
}
if (code == -1) {
throw new Error('Incorrect key string value specified - no "' + keyName + '" key found.');
return;
}
}
listeners.push(new SequenceListener(keyCodes, handler, id));
}

/**
 * Add event timed listener for a single key using a keyname.
 * @paramkeyName Name of the key.
 * @paramdelay Delay in milliseconds of how long the key needs to be held down for the event to be registered.
 * @paramhandler Function to execute when the key has been held for the specified amount of time.
 * @paramrepeat Set to true if you want to dispatch the event forever until the key is released. If you only want to dispatch it once, set to false.
 * @paramid ID of the listener. Set this to be able to remove the listener dynamically later using the removeListener() method.
 */

public function addEasyTimedListener(keyName:String, delay:int, handler:Function, repeat:Boolean = true, id:String = ""):void {
var rep:int = 1;
if (repeat == true) rep = 0;
var delayTimer:Timer = new Timer(delay, 1);
var code:int = -1;
for (var i:int = 0; i < keyLabels.length; i++) {
if (keyLabels[i] == keyName) {
code = i;
break;
}
}
if (code == -1) {
throw new Error('Incorrect key string value specified - no "' + keyName + '" key found.');
return;
}
listeners.push(new TimedListener(code, delayTimer, handler, rep, id));
}

private function kDown(evt:KeyboardEvent):void {
var u:int = 0;
var comboKeys:int = 0;
for (var i:int = 0; i < listeners.length; i++) {
if (listeners[i] is KeyListener && evt.keyCode == listeners[i].keyCode && evt.altKey == listeners[i].alt && evt.ctrlKey == listeners[i].ctrl && evt.shiftKey == listeners[i].shift) {
if (listeners[i].handlerD) listeners[i].handlerD.call();
}
if (listeners[i] is HoldListener && evt.keyCode == listeners[i].keyCode) {
listeners[i].flag = true;
}
if (listeners[i] is ComboListener) {
comboKeys = 0;
for (u = 0; u < listeners[i].keyCodes.length; u++) {
if (listeners[i].keyCodes[u] == evt.keyCode) {
listeners[i].flags[u] = true;
}
if (listeners[i].flags[u] == true) comboKeys++;
}
if (comboKeys == listeners[i].keyCodes.length) {
if (listeners[i].handler) listeners[i].handler.call();
}
}
if (listeners[i] is TimedListener && evt.keyCode == listeners[i].keyCode) {
if (!listeners[i].delayTimer.running) {
listeners[i].delayTimer.start();
}
}
if (listeners[i] is SequenceListener) {
if (evt.keyCode == listeners[i].keyCodes[listeners[i].sequenceIndex]) {
listeners[i].sequenceIndex++;
if (listeners[i].sequenceIndex == listeners[i].keyCodes.length) {
if (listeners[i].handler) listeners[i].handler.call();
listeners[i].sequenceIndex = 0;
}
}else {
listeners[i].sequenceIndex = 0;
}
}
}
}

private function kUp(evt:KeyboardEvent):void {
var u:int = 0;
for (var i:int = 0; i < listeners.length; i++) {
if (listeners[i] is KeyListener && evt.keyCode == listeners[i].keyCode && evt.altKey == listeners[i].alt && evt.ctrlKey == listeners[i].ctrl && evt.shiftKey == listeners[i].shift) {
if (listeners[i].handlerU) listeners[i].handlerU.call();
}
if (listeners[i] is HoldListener && evt.keyCode == listeners[i].keyCode) {
listeners[i].flag = false;
}
if (listeners[i] is ComboListener) {
for (u = 0; u < listeners[i].keyCodes.length; u++) {
if (listeners[i].keyCodes[u] == evt.keyCode) {
listeners[i].flags[u] = false;
}
}
}
if (listeners[i] is TimedListener && evt.keyCode == listeners[i].keyCode) {
if (listeners[i].delayTimer.running) {
listeners[i].delayTimer.reset();
if (listeners[i].repeat == 2) listeners[i].repeat = 1;
}
}
}
}

/**
 * Remove a single listener.
 * @paramlistenerId Id of the listener, which is specified as String when the listener is created.
 */

public function removeListener(listenerId:String):void {
for (var i:int = listeners.length - 1; i >= 0; i--) {
if (listeners[i].id == listenerId) {
if (listeners[i] is TimedListener) {
listeners[i].delayTimer.reset();
listeners[i].delayTimer = null;
}
listeners.splice(i, 1);
break;
}
}
}

/**
 * Remove all listeners. IMPORATNT: If you are removing the EasyKeyboard object as a whole, use the kill() method.
 */

public function removeAllListeners():void {
for (var i:int = listeners.length - 1; i >= 0; i--) {
if (listeners[i] is TimedListener) {
listeners[i].delayTimer.reset();
listeners[i].delayTimer = null;
}
listeners.splice(i, 1);
}
}

}

}

Thanks for reading!

No comments:

Post a Comment