Home
Blog
Tutorials
Work
Resume
Keyboard
Mouse Buttons
Keyboard States

With the increasing popularity of touch-screen devices, such as smart phones and tablets, keyboard controls are becoming somewhat less prominent. But for more traditional computer interactions, they are still a major part of user input. And there's just something tactile about pushing buttons that touch-screens will never be able to replicate. I ported these keyboard classes to provide my rudimentary programming framework with everything it would need to detect keyboard input. For this tutorial, I have abstracted the classes out to be stand-alone. Let's begin...

Here are some shortcut links to the various sections of this tutorial...
Key
Input
Keyboard
KeyboardTest
Working Example

The Key Class
We're going to start off with a simple Key class. This class isn't entirely necessary. Haxe has the ability to create and use less strictly typed classes. But I didn't see any reason not to define this class, and it's good programming form.

Key.hx

package src;
class Key
{
	private var _keyName:String;
	private var _current:Int;
	private var _last:Int;
	
	public function new(freshName:String, freshCurrent:Int, freshLast:Int)
	{
		_keyName = freshName;
		_current = freshCurrent;
		_last = freshLast;
	}
	
	public var Name(getName, null):String;
	private function getName():String
	{
		return _keyName;
	}
	
	public var Current(getCurrent, setCurrent):Int;
	private function getCurrent():Int
	{
		return _current;
	}
	private function setCurrent(freshCurrent:Int):Int
	{
		_current = freshCurrent;
		return _current;
	}
	
	public var Last(getLast, setLast):Int;
	private function getLast():Int
	{
		return _last;
	}
	private function setLast(freshLast:Int):Int
	{
		_last = freshLast;
		return _last;
	}
}
						

The Key class is quite basic. It has three private variables that we use to store the basic information that each of our defined Keys will need. We have the key's name, a variable to track whether or not the key is currently pressed, and a variable to track whether the key was the last key pressed. The class constructor takes three required arguments to provide our three private variables with values. Then we simply define public properties for manipulating our private variables from outside the class. You'll note that the Name property does not have a function for setting its value. Once assigned, it will not be necessary to change that variable, just retrieve its value.

The Input Class
Now we move onto the Input class. This will be a generalized class that will serve as the basis for the full Keyboard class. It will contain the functions that aren't unique to the keyboard.

Input.hx

package src;
class Input
{
	private var _lookup:Hash<Int>;
	private var _map:Array<Key>;
	private var _total:Int;
	
	public function new()
	{
		_total = 256;
		_lookup = new Hash<Int>();
		_map = new Array<Key>();
		for (i in 0..._total)
		{
			_map[i] = null;
		}
	}

We begin this class off by declaring three private variables, a Hash table, an Array, and an integer. A few things to note here, the Hash table and Array both have their respective types declared. This is necessary in Haxe. Haxe does not allow arrays to store more than one type of variable, so every array has to be declared with what type of variable it is going to use. The same applies for Hash tables. Note that for the _map array, we are going to be using the "Key" class that we already created. The _total integer variable is just a way for us to limit how many items we're going to be storing.

Once again, the constructor function is used to initialize our private variables. This time we are going to be using default values. The _total variable is fed a value of 256. The _lookup Hash table is created without adding any elements to it. The _map array is initialized as empty, but we use a for loop to populate it with null objects. You can't access elements in an array that are outside of its total size. We populate the array with null so that we can be sure those index references will be accessible. Moving on...

	public function Update():Void
	{
		var i:Int = 0;
		while (i < _total)
		{
			var o:Key = _map[i];
			i++;
			if (o == null)
			{
				continue;
			}
			if ((o.Last == -1) && (o.Current == -1))
			{
				o.Current = 0;
			}
			else if ((o.Last == 2) && (o.Current == 2))
			{
				o.Current = 1;
			}
			o.Last = o.Current;
		}
	}

The Update function is the backbone of most loop-driven software. Calling this update function every time the application iterates will allow us to keep the information about our class up-to-date. The core of this Update function is a while loop that cycles through all of the entries in the _map array. It tests the Last and Current properties, and then adjusts them for each Key object that has been added to the _map array. This allows the class to constantly keep track of which key is currently being pressed, and which key was just pressed. Moving on...

	public function Reset():Void
	{
		var i:Int = 0;
		while (i < _total)
		{
			var o:Key = _map[i];
			i++;
			if (o == null)
			{
				continue;
			}
			Reflect.setField(this, o.Name, false);
			o.Current = 0;
			o.Last = 0;
		}
	}

The Reset function is just a little routine for cleaning up the various public variables that our Keyboard class is eventually going to contain. We run another while loop that cycles through all of the objects that have been added to the _map array. This function also includes the first use of the Reflect class. In programming, reflection is when a program can inspect and modify its own structure at runtime. In AS3 this behaviour is built into the syntax itself. In Haxe this sort of functionality is often split off into seperate classes. (another example would be some of the string manipulation functions in the StringTools class) The setField function in the Reflect class lets us change the value of public variables or properties for an object whose type we don't know. In this case we are referencing public variables for whatever class that we extend from the Input class.

	public function Pressed(freshPressed:String):Bool
	{
		return Reflect.field(this, freshPressed);
	}

This is the first of several methods where we will acquire the status of particular variables. This Pressed method returns the true or false value of the public variable whose string name we pass into the function. Once again we see the Reflect class at work. This function is looking for a variable that is defined in later classes that extend from the Input class. As such, those variables don't exist yet. (which is why we have to use the Reflect class here to reference them) We use the field method of the Reflect class because all we need is the returned value of the variable. There is no need to edit the variable here.

	public function JustPressed(freshPressed:String):Bool
	{
		return _map[_lookup.get(freshPressed)].Current == 2;
	}

Here we have a function to retrieve the value of a key that was just pressed. We provide it a string variable, and it returns true if the key was just pressed, and false if it wasn't. Thanks to the Update function, any key's status will only register as just pressed pressed once per cycle. We feed the string variable into the Hash table to retrieve the key's index, and then feed the index into the _map array to get the actual Key object.

	public function JustReleased(freshReleased:String):Bool
	{
		return _map[_lookup.get(freshReleased)].Current == -1;
	}

There's not much different in this function from the previous function. The objective here is to acquire the just released status instead of the just pressed status, but we go about it in the same fashion.

	public function GetKeyCode(freshName:String):Int
	{
		return _lookup.get(freshName);
	}

Another retrieval function, GetKeyCode returns the saved integer code for the desired key. The _lookup Hash table is all that is necessary for this function.

	public function Any():Bool
	{
		var i:Int = 0;
		while (i < _total)
		{
			var o:Key = _map[i];
			i++;
			if ((o != null) && (o.Current > 0))
			{
				return true;
			}
		}
		return false;
	}

The Any function is a useful little method for checking to see if any keys are being currently pressed. It is what you would use if you wanted to "press any key to continue." We have another while loop here that cycles through all of the _map array entries. It checks to see if any of the Current properties for the objects stored in the _map array have a value greater than zero. If they do, then at least one key is being pushed, and the value true is returned.

	public function AddKey(freshName:String, freshCode:Int):Void
	{
		_lookup.set(freshName, freshCode);
		_map[freshCode] = new Key(freshName, 0, 0);
	}

The AddKey function is essential to the Input class. It is the method that populates the _lookup Hash table and the _map array with new entries.

	public function Destroy():Void
	{
		_lookup = null;
		_map = null;
	}
}

Our final function in this class is the Destroy function. This is just here for a little memory management. The majority of the data in this class is in the _lookup and _map objects. Setting them to null will free up that space. Most of the time, you won't need to use this method, but it's there just in case.

The Keyboard Class
Now that we have our Input class under wraps, it's time to get our Keyboard on. The first part of this class is quite long, and involves defining a LOT of public variables. Just be ready to see a lot of code.

Keyboard.hx

package src;

import nme.events.KeyboardEvent;

class Keyboard extends Input
{
	public var ESCAPE:Bool;
	public var ONE:Bool;
	public var TWO:Bool;
	public var THREE:Bool;
	public var FOUR:Bool;
	public var FIVE:Bool;
	public var SIX:Bool;
	public var SEVEN:Bool;
	public var EIGHT:Bool;
	public var NINE:Bool;
	public var ZERO:Bool;
	public var PAGEUP:Bool;
	public var PAGEDOWN:Bool;
	public var HOME:Bool;
	public var END:Bool;
	public var INSERT:Bool;
	public var MINUS:Bool;
	public var PLUS:Bool;
	public var DELETE:Bool;
	public var BACKSPACE:Bool;
	public var TAB:Bool;
	public var Q:Bool;
	public var W:Bool;
	public var E:Bool;
	public var R:Bool;
	public var T:Bool;
	public var Y:Bool;
	public var U:Bool;
	public var I:Bool;
	public var O:Bool;
	public var P:Bool;
	public var LBRACKET:Bool;
	public var RBRACKET:Bool;
	public var BACKSLASH:Bool;
	public var CAPSLOCK:Bool;
	public var A:Bool;
	public var S:Bool;
	public var D:Bool;
	public var F:Bool;
	public var G:Bool;
	public var H:Bool;
	public var J:Bool;
	public var K:Bool;
	public var L:Bool;
	public var SEMICOLON:Bool;
	public var QUOTE:Bool;
	public var ENTER:Bool;
	public var SHIFT:Bool;
	public var Z:Bool;
	public var X:Bool;
	public var C:Bool;
	public var V:Bool;
	public var B:Bool;
	public var N:Bool;
	public var M:Bool;
	public var COMMA:Bool;
	public var PERIOD:Bool;
	public var SLASH:Bool;
	public var CONTROL:Bool;
	public var ALT:Bool;
	public var SPACE:Bool;
	public var UP:Bool;
	public var DOWN:Bool;
	public var LEFT:Bool;
	public var RIGHT:Bool;
	
	public function new()
	{
		super();
		//Letters
		#if cpp
		for (letter in 97...123)
		{
			AddKey(String.fromCharCode(letter - 32), letter);
		}
		#else
		for (letter in 65...91)
		{
			AddKey(String.fromCharCode(letter), letter);
		}
		#end

A bit of explanation is probably required here. The long list of public variables declared near the top of this class are there to store the Bool status of each key we are going to be checking. They need to be public so that the Reflect functions we used in the Input class will be able to detect them. The call to the super() function at the beginning of the constructor is required in Haxe when you extend one class from another. It basically calls the original class constructor. We want the original constructor called before any of the new code is run, so we call it first thing in the constructor.

The pound sign conditionals also need a bit of explanation. While translating these classes into Haxe, I ran across a bug. The integer key codes for the C++ target are different than the ones for other targets. Specifically, there is a bit of overlap between the letter keys, and the function and numpad keys. I believe this has something to do with how SDL maps key codes for keyboard input. (SDL is used in the C++ target for rendering and input) For the time being, I decided to simply not include the function or numpad keys. The letter keys are more common to use for controls and interacting with programs. The conditionals allow for this code to work for all targets. The for loops simply cycle through the letter keycodes and use those integer values to add the desired keys to the _map array and _lookup Hash table.

		//Numbers
		AddKey("ZERO", 48);
		AddKey("ONE", 49);
		AddKey("TWO", 50);
		AddKey("THREE", 51);
		AddKey("FOUR", 52);
		AddKey("FIVE", 53);
		AddKey("SIX", 54);
		AddKey("SEVEN", 55);
		AddKey("EIGHT", 56);
		AddKey("NINE", 57);
		
		AddKey("PAGEUP", 33);
		AddKey("PAGEDOWN", 34);
		AddKey("HOME", 36);
		AddKey("END", 35);
		AddKey("INSERT", 45);
		
		//Special Keys + Punctuation
		AddKey("ESCAPE", 27);
		AddKey("MINUS", 189);
		AddKey("PLUS", 187);
		AddKey("DELETE", 46);
		AddKey("BACKSPACE", 8);
		AddKey("LBRACKET", 219);
		AddKey("RBRACKET", 221);
		AddKey("BACKSLASH", 220);
		AddKey("CAPSLOCK", 20);
		AddKey("SEMICOLON", 186);
		AddKey("QUOTE", 222);
		AddKey("ENTER", 13);
		AddKey("SHIFT", 16);
		AddKey("COMMA", 188);
		AddKey("PERIOD", 190);
		AddKey("SLASH", 191);
		AddKey("CONTROL", 17);
		AddKey("ALT", 18);
		AddKey("SPACE", 32);
		AddKey("UP", 38);
		AddKey("DOWN", 40);
		AddKey("LEFT", 37);
		AddKey("RIGHT", 39);
		AddKey("TAB", 9);
	}

This is where the rest of the Keys get added. Since most of these don't follow any pattern, they have to be manually added.

	public function HandleKeyDown(freshEvent:KeyboardEvent):Void
	{
		var keyObject:Key = _map[freshEvent.keyCode];
		if (keyObject == null)
		{
			return;
		}
		if (keyObject.Current > 0)
		{
			keyObject.Current = 1;
		} else {
			keyObject.Current = 2;
		}
		Reflect.setField(this, keyObject.Name, true);
	}

Experienced AS3 developers will recognize that this HandleKeyDown function conforms to the standard syntax for an event function. It takes a standard flash KeyboardEvent as an argument, and returns Void. The key code from the event helps us to access the correct Key object in the _map array. Then we have a few conditionals to test the present status of the object's properties, and adjust them based on those tests. Finally, we use the Reflect class once again to adjust the status of the corresponding public variable to true.

	public function HandleKeyUp(freshEvent:KeyboardEvent):Void
	{
		var keyObject:Key = _map[freshEvent.keyCode];
		if (keyObject == null)
		{
			return;
		}
		if (keyObject.Current > 0)
		{
			keyObject.Current = -1;
		} else {
			keyObject.Current = 0;
		}
		Reflect.setField(this, keyObject.Name, false);
	}
}

Our last function for this class is the HandleKeyUp. It behaves exactly as the HandleKeyDown function, but at cross purposes.

The KeyboardTest Class
All the required classes are now constructed. But this is object-oriented design. And nothing in OOP truly exists until you create an instance of it. The KeyboardTest class is simple entry-point class for Haxe and NME so that we can give our newly created solution a spin.

KeyboardTest.hx

package ;

import nme.display.Sprite;
import nme.display.StageAlign;
import nme.display.StageScaleMode;
import nme.events.Event;
import nme.events.KeyboardEvent;
import nme.Lib;
import src.Keyboard;

This class is located in the root folder, so it requires no extra package definition. As this is going to be our main-stage class, it does require a few more library imports. The root objects in flash always have to extend the Sprite class, and the same holds true here. StageAlign and StageScaleMode aren't strictly required, but I like to include them in most of my projects. Event, KeyboardEvent, and Lib are all must-haves. And down at the bottom we import our own Keyboard class.

class KeyboardTest extends Sprite
{
	private var _freshKeyboard:Keyboard;
	
	public function new()
	{
		super ();
		initialize ();
	}
	
	private function initialize():Void
	{
		Lib.current.stage.align = StageAlign.TOP_LEFT;
		Lib.current.stage.scaleMode = StageScaleMode.NO_SCALE;
		
		_freshKeyboard = new Keyboard();
		Lib.current.stage.addEventListener(KeyboardEvent.KEY_DOWN, _freshKeyboard.HandleKeyDown);
		Lib.current.stage.addEventListener(KeyboardEvent.KEY_UP, _freshKeyboard.HandleKeyUp);
		
		Lib.current.stage.addEventListener(Event.ENTER_FRAME, Update);
	}

To start off, we declare our _freshKeyboard variable, which will be the instance of our Keyboard class. For the constructor, we call the super constructor, as is required in any extended class. We also call the initialize function. FlashDevelop adds this initialize function by default in it's projects. It's not required, but I saw no reason to remove it. Within the intialize function, we start getting down to brass tacks. The first two lines access the Lib.current.stage object. This is how you access the root stage in Haxe. We adjust the align and scaleMode properties as desired. I favor TOP_LEFT alignment and NO_SCALE scaleMode for most of my projects. Next, we create the actual instance of our Keyboard class.

Now it's time for the event listeners. All three of our event listeners need to be added to the root stage object as well. For the KeyboardEvent.KEY_DOWN, and KeyboardEvent.KEY_UP, we use the HandleKeyDown and HandleKeyUp functions from our instance of the Keyboard class. For the enter frame listener, we use the Update function that we are goign to create below...

	private function Update(freshEvent:Event):Void
	{
		_freshKeyboard.Update();
		if (_freshKeyboard.JustPressed("X"))
		{
			trace("Just Pressed the X Key");
		}
		if (_freshKeyboard.JustReleased("C"))
		{
			trace("Just Released the C Key");
		}
	}

Here in the update function, we start off by calling the Update function in our _freshKeyboard Keyboard object. This will insure that our _freshKeyboard object's status will be updated every rendered frame, before we do any testing on it. The two if statements that we throw in after that are there to test out the functionality of what we've created. You can change the keys you check for to be whatever you please. Do make sure that you spell the name of the keys correctly though, your application will crash if you feed a null reference.

	// Entry point
	public static function main()
	{
		Lib.current.addChild (new KeyboardTest());
	}
}

Our final function is the true entry point for the application. Like C#, Haxe requires each program to have a static "main" function as an entry point. In our instance of this function, we abide by the flash display list, and add a new instance of our KeyboardTest class to the Lib.current object. We could have also performed this step in the constructor, or in the initialize function, but this approach makes for convenient shorthand.

Working Example - HTML5
Press the "X" key to display prompt
Release the "C" key to display prompt



Source Code
Click Here To Download the Source Code
The source code for this project includes all of the original source files, the folder structure, and the NMML command file that I used to compile the project. This software is based on Adam Saltsman's open source Flixel framework. It is distributed here without any license attached. You may do with it as you please. Will Hawthorne makes no promises and claims no liability for what is done with this software. Please use responsibly.

EMail
Google+
LinkedIn
Facebook
© 2008 - 2017 William Hawthorne