Thursday, February 5, 2015

Creating EasyKeyboard class Part 9

In this tutorial well add the ability to configure the Timed listeners more.

Right now, if you test the timed listeners, youll see that as long as you hold the needed key, the timed event gets dispatched forever until you release the key. Sometimes, it might be needed to only dispatch the event once.

To enable this, well add a new parameter to the addTimedListener function, that is a repeat boolean.

Based on the booleans value, well pass another value to TimedListener object. That value will not be a boolean, though - its going to be an integer. Lets refer to this value as repeat stage from now on. There are 3 possible values - 0, 1 and 2. If the repeat flag is true, then the repeat stage will be 0 and stay 0 forever.

If repeat is set to false, the stage becomes 1 at first, and stays like that until the first timed event gets dispatched. When the event fires, we change the stage to 2 and keep it like that until the key is released. The event wont be fired as long as the stage is 2.

So, the repeat stages are:

0 - repeat is true - allow repeating every second, the flag doesnt change.
1 - repeat is false - allow listening to the even for the first delay period, then set to 2.
2 - the event doesnt trigger, but once the key is released - the value becomes 1 once again.

Back in addTimedListener() function, check if repeat value is true, and pass 0 if so. If it is false, pass 1:

/**
* 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));
}

Go to TimedListener class now. Add a new property "repeat", set its value to the received fourth parameters value. In the onTimer() function, only execute the handler if repeat is 0 or 1. Then, check if repeat is 1 - and set it to 2.

package com.kircode.EasyKeyboard 
{
import flash.events.TimerEvent;
import flash.utils.Timer;
/**
* ...
* @author Kirill Poletaev
*/
public class TimedListener
{

public var keyCode:int;
public var handler:Function;
public var delayTimer:Timer;
public var repeat:int;

public function TimedListener(code:int, timer:Timer, func:Function, rep:int)
{
repeat = rep;
keyCode = code;
handler = func;
delayTimer = timer;
delayTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimer);
}

private function onTimer(evt:TimerEvent):void {
if (repeat==0 || repeat==1){
if (handler != null) handler.call();
if (repeat == 1) repeat = 2;
}
}

}

}

Back in EasyKeyboard.as we need to go to kUp() function and add a new if..statement inside the TimedListener if..statement, which checks if repeat value of the listener is 2, in which case we set it to 1.

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

Now add a function addEasyTimedListener, where key name is passed instead of key code. Like usual, we compare the retreived key name with each label from the array, and retreive the key code:

/**
* 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));
}

Full EasyKeyboard.as code now:

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

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

}

}

Now, you can go to main.as and write this:

keyboard = new EasyKeyboard(stage);
keyboard.addEasyTimedListener("A", 1000, function() { trace("A key held for a second!"); }, false);

When you test the code, youll see that holding A key for a second dispatches an event, but if you keep holding it - no more events are dispatched.

Thanks for reading!