Sunday, October 28, 2012

Creating EasyKeyboard class: Part 10

In this tutorial we'll add the ability to listen to key sequences.

Go to EasyKeyboard class and add a new function called addSequenceListener. Add 2 parameters - keyCodes array and handler Function. In the function, check each keyCode for validity (check if its a number), then apply the keyCodes and handler values to a SequenceListener object and push it to the listeners.

/**
 * 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 combination is held.
 */

public function addSequenceListener(keyCodes:Array, handler:Function):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));
}

Create a new class SequenceListener.as. Receive the two values and apply them to public keyCodes and handler variables. Also add a new public property called sequenceIndex, set it to 0:

package com.kircode.EasyKeyboard 
{
/**
 * ...
 * @author Kirill Poletaev
 */
public class SequenceListener 
{

public var keyCodes:Array;
public var handler:Function;
public var sequenceIndex:int = 0;

public function SequenceListener(codes:Array, func:Function) 
{
keyCodes = codes;
handler = func;
}

}

}

Now go to kDown() function and add an if...statement for SequenceListeners.

We use the sequenceIndex value of each Listener to get to the keyCode array's element which is then compared it to the recently pressed key code. If the keys match, then we advance in the sequence and increase the sequenceIndex by 1. If it doesn't match, reset the sequenceIndex to 0. If the index reaches length of the keyCodes array, then we call the handler and reset the index.

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;
}
}

So, basically, if we have A+S+D sequence to listen to, we listen to each key press and increase the index if the keys were correct. If the user, however, does't finish the sequence (for example, presses A+S+F instead of A+S+D), the sequence index is reset and they will have to start over.

Full kDown() function:

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;
}
}
}
}

Full EasyKeyboard class:

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 {
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.
 */

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

/**
 * 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.
 */

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


/**
 * 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.
 */

public function addComboListener(keyCodes:Array, handler:Function):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));
}

/**
 * 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.
 */

public function addTimedListener(keyCode:int, delay:int, handler:Function, repeat:Boolean = true):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));
}

/**
 * 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 combination is held.
 */

public function addSequenceListener(keyCodes:Array, handler:Function):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));
}

/**
 * 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.
 */

public function addEasyListener(keyName:String, handlerDown:Function = null, handlerUp:Function = null, alt:Boolean = false, ctrl:Boolean = false, shift:Boolean = false):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);
}

/**
 * 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.
 */

public function addEasyHoldListener(keyName:String, handler:Function):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);
}

/**
 * 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.
 */

public function addEasyComboListener(keyNames:Array, handler:Function):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;
break;
}
}
if (code == -1) {
throw new Error('Incorrect key string value specified - no "' + keyName + '" key found.');
return;
}
}

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

/**
 * 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.
 */

public function addEasyTimedListener(keyName:String, delay:int, handler:Function, repeat:Boolean = true):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));
}

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;
}
}
}
}

}

}

Example of usage of this feature in main.as:

keyboard = new EasyKeyboard(stage);
keyboard.addSequenceListener([65, 83, 68], function() { trace("Sequence A+S+D registered!") } );

Thanks for reading!

No comments:

Post a Comment