What’s an event and event handling in programming?
Event handling is a major concept in game and application programming. It allows for the user to interact with your program. Whenever you move your mouse cursor, press or release a key on the keyboard or use a touch screen, all these interactions are recognized as so-called events.
Events in SDL 2.0, SDL_Event
In SDL 2.0 whenever an event occurs, for instance a key gets pressed on the keyboard, all the information related to that specific event is stored in a SDL_Event record. Depending on the event type (e.g. mouse motion, key pressed on a keyboard, maximizing the application window) there are very different fields which can be accessed. For a mouse motion you can read out the x and y position of the cursor whereas there is no sense in having x and y values for a pressed key on the keyboard, but to know which specific key has been pressed on the keyboard.
Event types available in SDL 2.0
There are many more types of events than a mouse motion and a key being pressed on a keyboard. Think of using a joystick, using a touchscreen, minimizing/maximizing the application window, and so forth. There are plenty of different events that could occur.
All these event types have certain names, e.g. the event type which indicates a mouse motion is called SDL_MOUSEMOTION. The full list according to the official SDL 2.0 documentation is:
|Event type||Event structure||SDL_Event field|
|Other events||SDL_CommonEvent||none use .type|
|Source: SDL 2.0 Documentation: SDL_Event|
This list is overwhelmingly long but don’t worry, as soon as you get the concept behind events you will easily understand which of these event types will play a role for the applications you like to develop. The most important events are covered by this tutorial in detail anyway. You will be able to work with the remaining events once you got the concept.
In contrast to SDL 1.2 there are some event types gone and many new types available in SDL 2.0 which are useful to use new forms of interaction between the user and the application (e.g. touch screen technology).
What is the difference between event type, event structure and the event field?
In the table above you’ll notice the first column covers the event type. It determines which type of event occured, e.g. a key is pressed down on the keyboard (SDL_KEYDOWN).
The event structure in the second column is the record structure which is dependend upon the event type. As discussed before, a pressed key will need a record structure which stores the key identifier rather than x,y-coordinated (which would in turn be necessary for a mouse motion). Each event type has a certain apropriate (event) record structure to hold the event information.
Many event types can share the same event structure. The event types SDL_KEYDOWN and SDL_KEYUP which are generated by pressing or releasing a key share the same event structure SDL_KeyboardEvent since the information are the same, e.g. the key identifier.
The third column shows the SDL_Event field name to access the event specific fields. In case of an event type SDL_KEYDOWN the event structure is SDL_KeyboardEvent. The specific information, e.g. the key identifier, is accessible via the field key in the SDL_Event record.
This may sound confusing. Later on the relation is discussed in more detail. Let’s right proceed into the code.
program Chapter8_SDL2; uses SDL2; var sdlWindow1: PSDL_Window; sdlEvent: PSDL_Event; exitloop: boolean = false; text1: string = ''; begin //initilization of video subsystem if SDL_Init( SDL_INIT_VIDEO ) < 0 then HALT; sdlWindow1 := SDL_CreateWindow( 'Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN ); if sdlWindow1 = nil then HALT; new( sdlEvent ); while exitloop = false do begin while SDL_PollEvent( sdlEvent ) = 1 do begin write( 'Event detected: ' ); case sdlEvent^.type_ of //keyboard events SDL_KEYDOWN: begin writeln( 'Key pressed:'); writeln( ' Key code: ', sdlEvent^.key.keysym.sym ); writeln( ' Key name: "', SDL_GetKeyName( sdlEvent^.key.keysym.sym ), '"' ); writeln( ' Scancode: ', sdlEvent^.key.keysym.scancode ); writeln( ' Scancode name: "', SDL_GetScancodeName( sdlEvent^.key.keysym.scancode ), '"' ); writeln( ' Key modifiers: ', sdlEvent^.key.keysym._mod ); case sdlEvent^.key.keysym.sym of SDLK_ESCAPE: exitloop := true; // exit on pressing ESC key //switching text input mode on/off SDLK_F1: begin if SDL_IsTextInputActive = SDL_True then SDL_StopTextInput else SDL_StartTextInput; writeln(' Text Input Mode switched' ); end; end; end; SDL_KEYUP: writeln( 'Key released ' ); SDL_TEXTINPUT: begin writeln( 'Text input: "', sdlEvent^.text.text, '"' ); text1 := text1 + sdlEvent^.text.text; writeln( 'Full string: ' + text1 ); end; //mouse events SDL_MOUSEMOTION: begin writeln( 'X: ', sdlEvent^.motion.x, ' Y: ', sdlEvent^.motion.y, ' dX: ', sdlEvent^.motion.xrel, ' dY: ', sdlEvent^.motion.yrel ); end; SDL_MOUSEBUTTONDOWN: writeln( 'Mouse button pressed: Button index: ', sdlEvent^.button.button ); SDL_MOUSEBUTTONUP: writeln( 'Mouse button released' ); SDL_MOUSEWHEEL: begin write( 'Mouse wheel scrolled: ' ); if sdlEvent^.wheel.y > 0 then writeln( 'Scroll forward, Y: ', sdlEvent^.wheel.y ) else writeln( 'Scroll backward, Y: ', sdlEvent^.wheel.y ); end; //window events SDL_WINDOWEVENT: begin write( 'Window event: ' ); case sdlEvent^.window.event of SDL_WINDOWEVENT_SHOWN: writeln( 'Window shown' ); SDL_WINDOWEVENT_MOVED: writeln( 'Window moved' ); SDL_WINDOWEVENT_MINIMIZED: writeln( 'Window minimized' ); SDL_WINDOWEVENT_MAXIMIZED: writeln( 'Window maximized' ); SDL_WINDOWEVENT_ENTER: writeln( 'Window got mouse focus' ); SDL_WINDOWEVENT_LEAVE: writeln( 'Window lost mouse focus' ); end; end; end; end; SDL_Delay( 20 ); end; dispose( sdlEvent ); SDL_DestroyWindow ( sdlWindow1 ); //shutting down video subsystem SDL_Quit; end.
The result will not be seen in the actually SDL 2.0 window but in the command line window (which usually is showing up along with the SDL 2.0 window on Windows environments).
The initial lines of code are:
program Chapter8_SDL2; uses SDL2; var sdlWindow1: PSDL_Window; sdlEvent: PSDL_Event; exitloop: boolean = false; text1: string = ''; begin //initilization of video subsystem if SDL_Init( SDL_INIT_VIDEO ) < 0 then HALT; sdlWindow1 := SDL_CreateWindow( 'Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN ); if sdlWindow1 = nil then HALT; new( sdlEvent );
The program is called “Chapter8_SDL2”. Since event handling is a basic feature of SDL 2.0, no further units except for SDL2 itself necessary.
We need three variables. “sdlWindow1” is necessary to create a window as known from previous chapters. This time we won’t draw anything to it but use it to recognize events (e.g. mouse clicks into the window).
The SDL_Event variable “sdlEvent” stores the events generated by the user of the application. It is of pointer type PSDL_Event. Then there is a simple Pascal boolean variable “exitloop” which is set to false since we don’t want to leave the program loop initially. Also we have a “text1” string variable we will need to demonstrate text input later.
Nothing new for the following lines, SDL 2.0 is initilised and the SDL 2.0 window is created.
Since “sdlEvent” is a pointer type variable we need to allocate some memory. This is done by the new command, as known.
while exitloop = false do begin while SDL_PollEvent( sdlEvent ) = 1 do begin write( 'Event detected: ' );
First a while-do loop is started which will run as long as the variable “exitloop” is false. If it is changed to true the loop will be exited. (This is triggered by pressing ESC, later this is discussed in detail.)
Within the outer loop the first thing is to poll for an event, which is done by function SDL_PollEvent.
SDL_PollEvent(event: PSDL_Event): SInt32
This function returns integer value 1 if one or more events are in the queue. The event data is allocated to the SDL_Event variable and deleted from the queue. If there are no events waiting, it returns 0 and the event variable is filled with nil instead of specific event data.
If there is an event waiting, its information are fed to “sdlEvent” and 1 is returned. The inner while loop is running until all events in the queue are treated. This will print out a text saying “Event detected” and, more important, check for the event type!
Don’t use an if-clause (instead of the inner while loop) to check for the events because that means we only check once a event each every cycle of the outer loop. By combining two while loops and check for all the events in the inner loop we can treat every event occured for this program loop cycle.
The type_ field
The event type can be read out from the field type_, hence we check for the type in sdlEvent^.type_ by a case-statement.
case sdlEvent^.type_ of //keyboard events SDL_KEYDOWN: begin writeln( 'Key pressed:'); writeln( ' Key code: ', sdlEvent^.key.keysym.sym ); writeln( ' Key name: "', SDL_GetKeyName( sdlEvent^.key.keysym.sym ), '"' ); writeln( ' Scancode: ', sdlEvent^.key.keysym.scancode ); writeln( ' Scancode name: "', SDL_GetScancodeName( sdlEvent^.key.keysym.scancode ), '"' ); writeln( ' Key modifiers: ', sdlEvent^.key.keysym._mod );
If _type is a SDL_KEYDOWN event a begin-end block is entered. There are several writeln output lines. Let’s discuss them one after another.
In the first line the SDL (virtual) key code is returned, which is represented by an integer value. For special keys (e.g. F-keys, Insert, …) these values often range beyond the scope of SmallInt (-32768 to 32767) or Word (0 to 65535) variables, which you should keep in mind if you intend to return these values to variables. The SDL virtual key code can be accessed by
Let’s try to break this down a little bit. The event is stored in sdlEvent which is of pointer type so to access the content we need sdlEvent^. In the keyboard event there is a field keysym which itself is a record. The SDL virtual key code is stored in the field sym of the keysym record. Don’t worry if this sounds kind of confusing, in the next part we treat these two records (SDL_KeyboardEvent record and keysym record) in detail.
Most of the key codes have a name, e.g. “Escape” for the escape key. In the next line of the code, the function SDL_GetKeyName is used to get the name of the key whose key code we found:
function SDL_GetKeyName(key: TSDL_ScanCode): PAnsiChar
The other way round it is also possible by this function:
function SDL_GetKeyFromName(const name: PAnsiChar): TSDL_KeyCode
This is not shown in the code though.
The scancode of a key is another representation. The details about the difference between key codes and scancodes are discussed a little bit later. Anyway, the scancode is stored in the scancode field of the keysym record. So it can be accessed by:
and its name can be read out by
function SDL_GetScancodeName(scancode: TSDL_ScanCode): PAnsiChar.
Also here you can do it the other way round by
function SDL_GetScancodeFromName(const name: PAnsiChar): TSDL_ScanCode.
For completion and your information, it is possible to get the key code from the scancode and vice versa. The functions to use are:
function SDL_GetKeyFromScancode(scancode: TSDL_ScanCode): TSDL_KeyCode
function SDL_GetScancodeFromKey(key: TSDL_KeyCode): TSDL_ScanCode.
You see, there is a strong relation between the two but they are not the same. Lets have a short example of what happens if you press a key:
The tutorial code returns these key- and scancodes for the “Q”-key, the escape key and the return key. As you can see the codes do not only differ between different keys but also for the same key, e.g. the key code for the escape key is 27 but the scanscode is 41. Anyway, the key names seems to be consistent. Later we will see an example where even the names differ.
The last line returns the code value of key modifiers. Key modifiers are keys which literally modify them. E.g., if you press a letter key while holding the shift key, usually you modify the letter to be the capital letter. Typical key modifiers are shift, ctrl and alt. The field which stores the key modifier value is called _mod. Later we will discuss this field more in detail.
case sdlEvent^.key.keysym.sym of SDLK_ESCAPE: exitloop := true; // exit on pressing ESC key //switching text input mode on/off SDLK_F1: begin if SDL_IsTextInputActive = SDL_True then SDL_StopTextInput else SDL_StartTextInput; writeln(' Text Input Mode switched' ); end; end; end;
We check for the value of the key code by a case-statement with sdlEvent^.key.keysym.sym. If we know the key code of a certain key, we may check for this key and react accordingly. First, we check if the event’s key code is SDLK_ESCAPE since this is the key code of the escape key (ESC). If so, the variable exitloop is set to true to stop the outer while loop and exiting the program.
Every key code is represented by the key code constant (e.g. SDLK_ESCAPE), a decimal value (27 here) and a hexadecimal value ($001B here). You may check the key codes in the following (official) SDL 2.0 key code lookup table:
In the fifth row the escape key key code is found.
In this table you find additionally to the decimal value, the hexadecimal value. You may try to use $001B instead of 27. This will do the trick either :-)! Also there is a character representation shown if possible. If the the F1-key is pressed we would like to turn on or off the text input mode. This time we do not use a decimal key code to recognize the key but rather a constant representation (SDLK_F1). You could guess you have the choice and could use either the decimal value, the constant representation (what about SDLK_ESCAPE in the case before?) or the hexadecimal representation. – SDLK_ESCAPE even exists(!) but guess what, that doesn’t work. For some special keys this works like shown for the F1-key, for most of the other keys it doesn’t work (you will find that most keys are stored as string constants so the compiler will complain that they are of wrong type). So for most keys you have to look up the decimal representation and do as shown.
In the same way we check if the F1 key got pressed by checking for SDLK_F1. If F1 got pressed it is checked if the so called Text input mode is active by
function SDL_IsTextInputActive: TSDL_Bool.
If it is active, it gets deactivated by
and if it is not active, it gets activated by
Finally a short text is returned which states that the Text input mode has been changed. More about the input mode later.
Last but not least, if _type is a SDL_KEYUP event, we know a key has been released, hence it physically moves up on the keyboard. We just print out “Key released”, we don’t care what exactly key is released here.
The keyboard events SDL_KEYDOWN and SDL_KEYUP
Let’s have a look at the structure of the keyboard event record and discuss the fields from top to bottom:
TSDL_KeyboardEvent = record type_: UInt32; // SDL_KEYDOWN or SDL_KEYUP timestamp: UInt32; windowID: UInt32; // The window with keyboard focus, if any state: UInt8; // SDL_PRESSED or SDL_RELEASED _repeat: UInt8; // Non-zero if this is a key repeat padding2: UInt8; padding3: UInt8; keysym: TSDL_KeySym; // The key that was pressed or released end;
type_ is an unsigned 32 bit integer (hence UInt32) value which determines what exact type of event you have. As usual the integer values are represented by constants, here SDL_KEYDOWN (if pressed down) and SDL_KEYUP (if key has been released). They share the same overall event record structure (SDL_KeyboardEvent). As a sidenote, all the SDL 2.0 events have a type_ field for obvious reason.
The next field timestamp obviously contains a timestamp of integer type which is used internally by SDL 2.0 to resolute the sequence in which all the events were triggered. All SDL 2.0 events have the timestamp field.
The windowID field is necessary to distinguish between events raised from different SDL 2.0 windows if there is more than one. Let’s assume your program has two SDL 2.0 windows, window1 and window2. These two windows have certain constant id’s allocated by SDL 2.0. Now, if you have the focus on window1 (meaning the active window is window1) and press a key, the event’s windowID contains the specific id for window1. If the active SDL 2.0 window is window2 and you press a key, the specific id of window2 will be present in windowID. This way you can easily distinguish for which window the program should react to the keyboard event, e.g. a pressed key. Anyway, the capability to handle more than one window has been introduced by SDL 2.0, so you can imagine that for many programs and if you do not have more than one window in your program, you may ignore this field. The windowID field isn’t present (and necessary) for all the events SDL 2.0 provides.
The 8 bit integer state field may be read out to get the state of the key (pressed or release) which are encoded by SDL_PRESSED and SDL_RELEASED. You may be a little bit confused what the difference between the pair SDL_PRESSED and SDL_RELEASED and the pair SDL_KEYDOWN and SDL_KEYUP is. Essentially they have the same meaning for a key of a keyboard. Formally, the difference is that SDL_KEYDOWN and SDL_KEYUP are two different event types whereas SDL_PRESSED and SDL_RELEASED are two different key states. In the case of a pressed key on a keyboard the state is kind of redundant because if you get a SDL_KEYDOWN event you already know that a key was pressed and to read out the state (which will be SDL_PRESSED) is unnecessary. Anyway, the state field seems to be introduced for completeness, since for other event types (e.g. mouse events) there is a huge difference if you move the mouse and have mouse buttons pressed or released.
_repeat let’s you know if the corresponding key is in repeat mode. For most operation systems the repeat mode kicks in after a short delay when you keep a key pressed. You may try out to open up a simple text editor. If you press any key that has a letter (e.g. “a”-key), in the text editor you will see an “a”. If you keep the key pressed after a short delay the repeat mode kicks in and rapidly several further a’s are coming up. If you are in repeat mode for a certain key, repeat_ has a value different from 0 (most likely 1) and otherwise it will be 0. Especially for games, you may want to turn off the initial delay if you keep a key pressed and let the constant repeat mode kick in without delay. In SDL 1.2 I described here how to do it simply by using function called SDL_EnableKeyRepeat, this function is obsolete and does not exist in SDL 2.0 anymore!
A simple solution to the “repeat-delay”-problem: Instead of looking for the actual event being repeatedly triggered by an key event, use a switch which gets turned on if the key down event is occuring and which is turned off if the key up event is occuring. Example: Let’s assume you have a spaceship which should move left on pressing the “a”-key. Instead of changing it’s coordinates only once when the key down event is triggered, you start a switch (e.g. MoveSpaceshipLeft := true). The trigger is treated independently of the events treatment somewhere in the main game loop. As soon as the key up event is triggered for the “a”-key, the switch is turned off (e.g. MoveSpaceshipLeft := false).
I don’t know about meaning of the fields padding2 and padding3. Maybe they are kind of place holders for future developments or used internally.
Last but not least a very important field. The field keysym of SDL_KeySym type contains several information about the identity of the pressed key. In most cases we need to know which exact key has been pressed. Let’s have look into the SDL_KeySym record:
TSDL_Keysym = record scancode: TSDL_ScanCode; // SDL physical key code sym: TSDL_KeyCode; // SDL virtual key code _mod: UInt16; // current key modifiers unicode: UInt32; // (deprecated) end;
As you can see the first two fields contain keycode representations for identitifcation of the key pressed or released. Even though both fields seem to have again special records, namely SDL_ScanCode and SDL_KeyCode, they actually consist of only one field each, DWord (integer type) for SDL_ScanCode and SInt32 (integer type) for SDL_KeyCode.
The difference between scancode and key code
The difference is that the scancode refers to a specific physical location of a key on the keyboard. The scancode is referenced to the typical US keyboard layout (QWERTY layout). The term “QWERTY” just refers to the the first six letters from left to right in the first row with letters on a typical US keyboard. For example: The German keyboard layout (QWERTZ layout) is similar to the US one (for most of the letter keys), though the “Y”-key and the “Z”-key have exactly opposite positions (hence QWERTZ for the German layout in contrast to QWERTY for the US layout). If I press the “Z” key on the German keyboard, the returned scancode will represent the “Y” key since the position of the key (independent of the layout) is equal to the position of the “Y” key on an US keyboard. Scancodes are layout-independent.
The key code refers to the virtual representation of the key according to the keyboard layout. Here you consider the layout, hence key codes are layout-dependent. As discussed before the scancode for the “Z”-key on a German keyboard will return that the “Y”-key has been pressed since the key has the location of the “Y”-key of an US keyboard. But the key code will not return it is the “Y”-key but it will correctly return that the “Z”-key has been pressed. The red marked output in the following image illustrates the result if the”Z” key is pressed on a German keyboard.
You may say, then I should always use keycodes to read out text input, right? – Wrong :-). In fact since SDL 2.0 it depends strongly on what you actually want to do. If you want to get a text or a single character from the user, you should neither use scancodes nor use key codes (anymore). (In former SDL 1.2 key codes or the unicode representation were indeed the preferable choice.) Treatment of real text input is not done this way anymore. We will discuss this case later. Anyway, this quote from the SDL 1.2 to SDL 2.0 migration guide sums it up excellently:
Use SDL_KEYDOWN to treat the keyboard like a 101-button joystick now. Text input comes from somewhere else.
Think of the famous T-shaped WASD key arrangement (arrangement of the four keys “W”, “A”, “S” and “D”) in the US layout, even if you keyboard without any latin letters, you may want to use these four keys to move a game character forward (“W”), left (“A”), backward (“S”) or right (“D”). The labeling of the keys doesn’t matter in that case and the keys are not used to input some text.
Again, keep in mind:
Never use key codes or scancodes to read out text input in SDL 2.0 (anymore)
The remaining _mod field is a 16 bit unsigned integer (corresponds to Pascal’s Word) and represents key modifiers (ctrl, alt, shift, num lock, caps lock, …). If one or more key modifiers are pressed the _mod value has a unique number for each key or the key combination. For example, the left shift key has the decimal value 1, the right shift key has the value 2, the left control (ctrl) key has the value 64, the right ctrl key has the value 128. If the left shift and ctrl key are pressed at the same time the _mod vlue will be 65 (1 + 64). Let’s assume you want to have your application to be quit by the user pressing the ctrl key and “Q”. So you read out the key code for “Q” and check if _mod is 64 or 128. Since there doesn’t seem to exist a table for key modifiers, here the most important ones:
|Modifier key||UInt16 value|
|Right alt||576 (64 + 512?)|
The unicode field is deprecated and will not be discussed here. By the way, also procedure SDL_EnableUnicode is gone, which turned on the unicode mode.
Text input in SDL 2.0
Let’s have a look in the next part of the code and learn about the correct text input in SDL 2.0.
SDL_TEXTINPUT: begin writeln( 'Text input: "', sdlEvent^.text.text, '"' ); text1 := text1 + sdlEvent^.text.text; writeln( 'Full string: ' + text1 ); end;
With SDL 2.0 there is a new event type named SDL_TEXTINPUT. This one has been explicitly introduce to SDL 2.0 to make text input more flexible and easy.
Let’s have a look into the record structure of SDL_TextInputEvent.
TSDL_TextInputEvent = record type_: UInt32; // SDL_TEXTINPUT timestamp: UInt32; windowID: UInt32; // The window with keyboard focus, if any text: array[0..SDL_TEXTINPUTEVENT_TEXT_SIZE] of Char; // The input text end;
In contrast to the event structure SDL_KeyBoardEvent where two event types (SDL_KEYDOWN and KEYUP) were available, at the moment for the event structure SDL_TextInputEvent only one event type, SDL_TEXTINPUT, is possible.
The timestamp and windowID field have been discussed earlier in great detail. They contain some general information about when this event was triggered and which application window had the focus when the event was triggered. You may scroll up to get more information about this.
Unique about this event structure is the field text which is an array of char elements. This array contains from 0 to SDL_TEXTINPUTEVENT_TEXT_SIZE char elements. SDL_TEXTINPUTEVENT_TEXT_SIZE has a size of 32 by default. Attention here, this doesn’t mean you can only have 32 characters in texts or something like this! It means that in the moment this event is triggered not more than 32 characters are submittable to the event. Keep in mind though, if you use a Western language with Latin characters you always only submit one chracter at a time by each key stroke.
So why there are 32 possible characters anyway then? – To understand this, it would be necessary to go deeper into the construction of non-Western languages and how words and sentences are constructed. The massive amount and complexity of the way words and sentences are constructed makes it so that these are constructed beforehand before they are submitted to the text field of SDL_TextInputEvent. These constructs are not simply created by just a single key stroke which could be read out. For these constructs there are 32 chars reserved. (I would be glad to give a more detailed explanation here, please feel free to contact me to improve this paragraph.)
Let’s go back to the code. If a SDL_TEXTINPUT event has been found, its record (SDL_TextInputEvent) can be accessed by text. Its text input information is stored in the text field which we have seen recently. That is why we can use
to access the text input information. In the first line of the code we simply print out the content of the text field. The string variable “text1” is used to store the found character and add to the characters which have been found before. This string is also printed out.
Note how special characters (e.g. currency symbols, French accent symbols, the German “ß” symbol, …) and capital letters are recognized correctly. Try this with key codes or scancodes (it is a pain).
Note also that function keys ( F1, Backspace, …) are not recognized as characters. It is your responsibility for them to work the desired way :-).