For text input there is a special way and a special event in place in SDL3.
The SDL_EVENT_TEXT_INPUT Event and PAnsiChar?
The SDL_EVENT_TEXT_INPUT event has the follwing structure:
TSDL_TextInputEvent = record type_: TSDL_EventType; {*< SDL_EVENT_TEXT_INPUT } reserved: cuint32; timestamp: cuint64; {*< In nanoseconds, populated using SDL_GetTicksNS() } windowID: TSDL_WindowID; {*< The window with keyboard focus, if any } text: PAnsiChar; {*< The input text, UTF-8 encoded }end;
You can see that apart from the first four fields, which all events have in common, it has just one further field, called text. It is of the PAnsiChar type. If you are familar with Pascal and its String type for text handling, you may wonder, why in this record the text field uses this unusual type.
SDL3 is written in C/C++, and in these languages pointers to null-terminated character “strings” are used to store and handle text. For better compatibility and easier translation of SDL3 (and other C/C++ projects) the Pascal compiler provides a similar type, PAnsiChar.
In principle it is no big deal to use PAnsiChar instead of String, even though Pascal programmers prefer the much more convenient String variables. In short: Strings in Pascal are always safe to use, whereas C’s strings may cause memory leaks if handled wrongly.
The best practice is to convert a C string as soon as possible into a Pascal string, especially if string manipulations follow.
The Text Field does not contain what is considered a Text!
It may surprise you to read, that this text field does not contain a whole text but just a single (or a bunch of) character(s), which represent a word.
In Western languages (especially English) with Latin characters you always only submit one character at a time by each key stroke and compose words and sentences this way. But even in some Western languages you sometimes must combine a Latin character with an accent (which means two key strokes, but just one character) to get special characters. In many non-Western languages (think of e. g. Asian languages) words and sentences are constructed by highly individual signs. To construct these signs you need many key strokes before they are submitted to the text field which then generates the correct sign or sometimes several signs. (I would be glad to give a more detailed explanation here, please feel free to contact me to improve this paragraph.)
How is Text Input done in SDL3?
This is the code:
program SDL_TextInput;uses SDL3;var sdlWindow1: PSDL_Window; sdlEvent: TSDL_Event; RunLoop: Boolean = True; Text1: AnsiString = '';begin// initilization of event subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;// create a window (necessary to receive events) sdlWindow1 := SDL_CreateWindow('SDL3 Text Input', 500, 500, 0);while RunLoop dobeginwhile SDL_PollEvent(@sdlEvent) dobegincase sdlEvent.type_ of// quit program SDL_EVENT_QUIT: RunLoop := False; SDL_EVENT_KEY_DOWN:case sdlEvent.key.key of// switch text input mode on/off by ENTER/RETURN key SDLK_KP_ENTER, SDLK_RETURN:beginWrite('Text Input Mode is ' );if SDL_TextInputActive(sdlWindow1) thenbegin SDL_StopTextInput(sdlWindow1); WriteLn('OFF');endelsebegin SDL_StartTextInput(sdlWindow1); WriteLn('ON');end;end;// remove last character SDLK_BACKSPACE:begin Delete(Text1, Length(Text1), 1); WriteLn( ' --> ' + Text1 );end;end; SDL_EVENT_TEXT_INPUT:beginWrite( 'Text Input: "', sdlEvent.text.text, '"' ); Text1 := Text1 + StrPas(sdlEvent.text.text); WriteLn( ' --> ' + Text1 );end;end;end; SDL_Delay(20);end;// clear memory SDL_DestroyWindow(sdlWindow1);// quit SDL3 SDL_Quit;end.
The result looks like this:
As you see, in this example we have a blank window and just use the text output to your command line. In the Lazarus IDE you can get the raw output window by Ctrl + Alt + O.
program SDL_TextInput;uses SDL3;var sdlWindow1: PSDL_Window; sdlEvent: TSDL_Event; RunLoop: Boolean = True; Text1: AnsiString = '';begin// initilization of event subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;// create a window (necessary to receive events) sdlWindow1 := SDL_CreateWindow('SDL3 Text Input', 500, 500, 0);while RunLoop dobeginwhile SDL_PollEvent(@sdlEvent) dobegincase sdlEvent.type_ of
Most of this first code bit is known to you from previous chapters. We have no renderer variable because we do not need to render something.
Instead we have “Text1” variable of the Pascal AnsiString type. In contrast to the classical ShortString, it has no length limit. If you just use String, it depends on your compiler settings if they are treated as AnsiString or ShortString. We want to be clear about this here!
We create a window “sdlWindow1” without a renderer. You may ask yourself, why do we need a window if we do not render something to it. You are right, in principle we wouldn’t need one. We still need it, because the events are connected to a window, without a window we have no event detection.
As known from previous chapter about event handling, we enter two while loops. The first one for the application to run and the second one to gather all the events each cycle.
We in the inner “event loop”, we have three events, two of whom are new, SDL_EVENT_QUIT and SDL_EVENT_TEXT_INPUT.
The SDL_EVENT_QUIT
SDL_EVENT_QUIT is triggered when the application gets the signal to quit, e. g. by clicking on the “X” of a typical application window. We didn’t catch this event in the last chapter, you may have noticed that clicking the “X” didn’t do anything.
SDL_EVENT_TEXT_INPUT Functions
Before getting to the SDL_EVENT_TEXT_INPUT, let’s look at the SDL_EVENT_KEY_DOWN part. If you press the enter or return key, the function SDL_TextInputActive(window) checks, if the text input mechanism is started or not. Obviously, its return value is boolean.
If the text input mechanism is started already, it is stopped by the function SDL_StopTextInput(window). If it is not started, it is started by the function SDL_StartTextInput(window). Only if t he mechanism is started, SDL_EVENT_TEXT_INPUT events are created and can be caught.
This is accompanied by a text which is generated reading “Text Input Mode is ON” or “… OFF” accordingly.
Let proceed to the SDL_EVENT_TEXT_INPUT part: If the the text input mechanism has been started by SDL_StartTextInput(window), we get SDL_EVENT_TEXT_INPUT events as soon as keys are pressed. The new character is stored in:
sdlEvent.text.text
The Pascal function Write() prints out PAnsiChar strings (or rather characters in this case) natively and will show us directly what character has been created.
In line
Text1 := Text1 + StrPas(sdlEvent.text.text);
we concatenate the new character to the “Text1” string. Note how the PAnsiChar character is converted into a native Pascal ShortString by Pascal function StrPas(PAnsiChar string) beforehand. The concatenation will convert it to an AnsiString result in “Text1”.
The resulting text in “Text1” is printed out by the Pascal WriteLn statement.
It is remarkable how easy it is to get even complex characters added to the text without any hassle. Try doing this with just SDL_EVENT_KEY_DOWN events and to make accents possible alone would take a lot of effort.
Removing a Character from the Text
The text input mechanism does not provide a way to remove a character natively. Also other features for convenience, like moving a cursor into the text and inserting a character in between are not supported natively.
As “Text1” is a convenient Pascal string now, we can use Pascal’s string routines to implement all the features we need with little effort. You see, if you press the backspace key with key code SDLK_BACKSPACE, we use Pascal’s Delete(string, index, number of characters) and Length(string) functions to pick the last character of the string an delete it.
Event handling is a major concept in game and application programming. It allows for the user to interact dynamically with your program. Whenever you move your mouse cursor, press or release a key on the keyboard, use a touch screen or maximize/minimize the application window, all these interactions (and many, many more) are recognized as so-called events.
Events in SDL3
Whenever an event occurs in SDL3, for instance a key gets pressed on the keyboard, all the information related to that specific event is stored in a TSDL_Event record. Depending on the event type (e.g. mouse motion, key pressed on a keyboard, maximizing the application window) there is a different event field available in the record.
For example for a mouse motion you can read out the x and y position of the cursor (in the motion field). In contrast 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 (in the key field).
Event Types, Event Fields and Event Structures
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_EVENT_MOUSE_MOTION. All names of event types start with SDL_EVENT_, then often follows the device it is related to, e.g. MOUSE, KEYBOARD, GAMEPAD, JOYSTICK, PEN, CAMERA, and finally the actual action, e. g. MOTION, BUTTON_DOWN, ADDED, … .
A few selected examples are described below:
SDL_EVENT_MOUSE_MOTION
created by a mouse motion
field name: motion
provides x- and y-coordinates of mouse position
provides relative distance to last x- and y-coordinate
provides state of the pressed buttons (during motion)
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.
Relationship between Event Type, Event Field and Event Structure
The event type determines the available event field, which determines the event structure.
Let’s assume you press a key. This will create an event of type SDL_EVENT_KEY_DOWN. This event provides an event field key which allows access to an event (record) structure.
TSDL_KeyboardEvent = record type_: TSDL_EventType; {*< SDL_EVENT_KEY_DOWN or SDL_EVENT_KEY_UP } reserved: cuint32; timestamp: cuint64; {*< In nanoseconds, populated using SDL_GetTicksNS() } windowID: TSDL_WindowID; {*< The window with keyboard focus, if any } ...end;
The first four fields of the event record structure is the same for all events: their event type, a reserved field, a timestamp when the event was triggered and the window id, hence, in which application window has the event been triggered.
Then follow event specific fields, which are dependent of the specific event as described in the list above. For a keyboard event these may be key codes, for a mouse motion event this may be the mouse position.
Some event types can share the same event field and structure. For example, the event types SDL_EVENT_KEY_DOWN and SDL_EVENT_KEY_UP which are generated by pressing or releasing a key share the same event structure since the information are the same.
Keyboard Event and Mouse Event in Action
Let’s dive into the example code.
program SDL3_EventHandling;uses SDL3;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlEvent: TSDL_Event; sdlPoint: TSDL_FPoint; RunLoop: Boolean = True;begin// initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;// create window and rendererifnot SDL_CreateWindowAndRenderer('SDL3 Events', 500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;// render and show cleared window with grey background color SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer);// set point coordinates at center of window sdlPoint.x := 255; sdlPoint.y := 255;// run the (game) loop until it should stop (= false); triggered by escape or q keywhile RunLoop dobegin// run the event loop until all events in queue have been treatedwhile SDL_PollEvent(@sdlEvent) dobegincase sdlEvent.type_ of SDL_EVENT_KEY_DOWN:begincase sdlEvent.key.key of SDLK_W: sdlPoint.y := sdlPoint.y-1; SDLK_A: sdlPoint.x := sdlPoint.x-1; SDLK_S: sdlPoint.y := sdlPoint.y+1; SDLK_D: sdlPoint.x := sdlPoint.x+1; SDLK_SPACE:begin SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer);end; SDLK_ESCAPE, SDLK_Q: RunLoop := False;end;// set yellow drawing color SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 0, SDL_ALPHA_OPAQUE);end; SDL_EVENT_MOUSE_MOTION:begin sdlPoint.x := sdlEvent.motion.x; sdlPoint.y := sdlEvent.motion.y;// set red drawing color SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);end;end;end;// render the point SDL_RenderPoint(sdlRenderer, sdlPoint.x, sdlPoint.y); SDL_RenderPresent(sdlRenderer); SDL_Delay(20);end;// clear memory SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow (sdlWindow1);// quitting SDL3 SDL_Quit;end.
The result depends on your mouse movement in the application window (red dots) and/or the pressed W-, A-, S- or D- keys on the keyboard (yellow dots).
The initial lines of code are:
program SDL3_EventHandling;uses SDL3;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlEvent: TSDL_Event; sdlPoint: TSDL_FPoint; RunLoop: Boolean = True;begin// initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;// create window and rendererifnot SDL_CreateWindowAndRenderer('SDL3 Events', 500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;
We initialize a SDL3 application with a 500 by 500 pixels window as known.
The TSDL_Event variable “sdlEvent” stores the events generated by the user of the application. We use it later to read out the event information of each individual event that gets created.
We also need a variable “sdlPoint” of known type TSDL_FPoint to describe a point with float point coordinates.
Finally there is a simple Pascal boolean variable “RunLoop”, which is initilized to true.
// render and show cleared window with grey background color SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer);// set point coordinates at center of window sdlPoint.x := 255; sdlPoint.y := 255;
Also known from previous chapters, we set up a grey drawing color (RGB: 50, 50, 50) for the background and render it.
Also we set the initial coordinates of the point which is at the center of the window.
// run the (game) loop until it should stop (= false); triggered by escape or q keywhile RunLoop dobegin// run the event loop until all events in queue have been treatedwhile SDL_PollEvent(@sdlEvent) dobegin
We now enter two while loops. The first, outer while loop keeps the application running as long as the “RunLoop” variable is true. The loop is exited as soon as “RunLoop” equals false.
For every application cycle (outer while loop), we enter a second, inner while loop. It runs as long as the SDL3 functionSDL_PollEvent(pointer of TSDL_Event) equals to true. SDL_PollEvent() equals to true as long as there are still events in the event queue.
Every time the SDL_PollEvent() is called, it feeds the event type and the specific event information to the event of the argument and deletes the event from the event queue. In our case, the information is fed to “sdlEvent”. To be precise, the argument does not need the event itself but rather its pointer, so we use the @ operator to get its pointer. That is why we call SDL_PollEvent() by
SDL_PollEvent(@sdlEvent)
as the while loop condition.
The type_ field
Every event provides 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 SDL_EVENT_KEY_DOWN:begincase sdlEvent.key.key of SDLK_W: sdlPoint.y := sdlPoint.y-1; SDLK_A: sdlPoint.x := sdlPoint.x-1; SDLK_S: sdlPoint.y := sdlPoint.y+1; SDLK_D: sdlPoint.x := sdlPoint.x+1; SDLK_SPACE:begin SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer);end; SDLK_ESCAPE, SDLK_Q: RunLoop := False;end;// set yellow drawing color SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 0, SDL_ALPHA_OPAQUE);end;
In our example program we distinguish and treat only two event types by a case statement, namely SDL_EVENT_KEY_DOWN and SDL_EVENT_MOUSE_MOTION. Every other possible event type is ignored by our program.
The SDL_EVENT_KEY_DOWN Event
If _type is a SDL_EVENT_KEY_DOWN event, a begin-end block is entered.
We know from this event type, that the event field which has all the event’s information is called key. The event structure you have there is as follows:
SDL_KeyboardEvent = record type_: TSDL_EventType; {*< SDL_EVENT_KEY_DOWN or SDL_EVENT_KEY_UP } reserved: cuint32; timestamp: cuint64; {*< In nanoseconds, populated using SDL_GetTicksNS() } windowID: TSDL_WindowID; {*< The window with keyboard focus, if any } which: TSDL_KeyboardID; {*< The keyboard instance id, or 0 if unknown or virtual } scancode: TSDL_Scancode; {*< SDL physical key code } key: TSDL_Keycode; {*< SDL virtual key code } mod_: TSDL_Keymod; {*< current key modifiers } raw: cuint16; {*< The platform dependent scancode for this event } down: cbool; {*< true if the key is pressed } repeat_: cbool; {*< true if this is a key repeat }end;
Additionally to the already seen first four fields, we have
the keyboard id,
the scan code,
the key code,
pressed key modifiers (shift, ctrl, …),
a raw scan code,
if the key is pressed (if not, it means it got released),
and if the event is triggered by the repeat mechanism, which kicks in if you keep a key pressed for a longer time.
We are interested in the key code of the pressed key, so we read out the information from the event structure’s key field, hence the information is found in
sdlEvent.key.key
Basically a key code is a constant which corresponds to a certain, unique hexadecimal number. For example the constant for the W-key is SDLK_W, its hexadecimal number is 77 (decimal number 119). All key codes’ names start with SDLK_.
With the case statement we look for specific key codes that may have been pressed. The key code names are self-explanatory. We check for the W-, A-, S-, D-. Space-, Escape- and Q-key by the key codes SDLK_W, SDLK_A, SDLK_S, SDLK_D, SDLK_SPACE, SDLK_ESCAPE and SDLK_Q. A list of all SDL3 key codes and their hexadecimal numbers is found in this link.
Depending on the pressed key, we change the position of the point (W: up, A: left, S: down, D: right), clear the window with the background color (space) or set the “RunLoop” variable to false, which effectively stops the program.
At the end of this event’s block the drawing color is changed to yellow.
The SDL_EVENT_MOUSE_MOTION Event
The second case of the case statement is the mouse motion event.
SDL_EVENT_MOUSE_MOTION:begin sdlPoint.x := sdlEvent.motion.x; sdlPoint.y := sdlEvent.motion.y;// set red drawing color SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);end;end;
The event field for a mouse motion is called motion. It’s record structure is shown below:
SDL_MouseMotionEvent = record type_: TSDL_EventType; {*< SDL_EVENT_MOUSE_MOTION } reserved: cuint32; timestamp: cuint64; {*< In nanoseconds, populated using SDL_GetTicksNS() } windowID: TSDL_WindowID; {*< The window with mouse focus, if any } which: TSDL_MouseID; {*< The mouse instance id or SDL_TOUCH_MOUSEID } state: TSDL_MouseButtonFlags; {*< The current button state } x: cfloat; {*< X coordinate, relative to window } y: cfloat; {*< Y coordinate, relative to window } xrel: cfloat; {*< The relative motion in the X direction } yrel: cfloat; {*< The relative motion in the Y direction }end;
If an SDL_EVENT_MOUSE_MOTION event is found, we change the x- and y-values according to the x- and y-values (mouse coordinates) provided by the event.
Finally we change the drawing color to red.
end;end;// render the point SDL_RenderPoint(sdlRenderer, sdlPoint.x, sdlPoint.y); SDL_RenderPresent(sdlRenderer); SDL_Delay(20);end;
The first “end;” closes the case statement for the event types, the second “end;” closes the inner while loop which checks for pending events in the event queue. After that we are back in the outer application-“RunLoop”. Every cycle we render the point once, update the rendered window and delay for 20 milliseconds.
The delay is necessary to prevent the CPU from running as fast as it can, which is kind of short-circuiting the program. You should never do this for your CPU’s sake.
Nothing new here, the renderer and window get destroyed and SDL3 is quit.
Congratulations! Event handling in SDL3 is now known to you.
I will will elaborate on some specifics here, which may or may not be interesting to you. Please skip them if you are not interested right now.
The Repeat-Delay Problem (Keyboard)
The _repeat field let’s you know if the corresponding key on a keyboard 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”. And 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 true boolean value, otherwise false.
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. Let’s assume you have a spaceship which should move left on pressing the “a”-key. – You do not want to have this happening with a delay.
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 the spaceship moving left on pressing the “a”-key again. Instead of using the repeat_ field at all, you make a switch (e.g. MoveSpaceshipLeft := True) if the “a”-key is pressed. As soon as the SDL_EVENT_KEY_UP event is triggered for the “a”-key, the switch is turned off (e.g. MoveSpaceshipLeft := False).
The Difference between Key code and Scan code
You may have noticed that we used key codes in the example code, but there is another field in the keyboard event structure, which reads scan code. Scan codes also represent keys on the keyboard. So, why wouldn’t we use these instead?
The scan code refers to a specific physical location of a key on the keyboard. The scan code 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 you press the “Z”-key on a German keyboard, the returned scan code will represent the letter “Y”, since the position of the key (independent of the layout) is equal to the position of the “Y” key on an US keyboard. Scan codes 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 scan code for the “Z”-key on a German keyboard will return a letter “Y”, since the key has the location of the “Y”-key of an US keyboard. In contrast the key code will correctly return that the “Z”-key has been pressed.
Think of the famous WASD key arrangement (arrangement of the four keys “W”, “A”, “S” and “D”) in the US layout, even if you have a keyboard without any latin letters, you may want to use the four keys which are physically at the WASD location 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. That is when you use scan codes.
Text Input is done in SDL3 in another way!
The shown way of handling keyboard events could be used to create kind of a text input mechanism. DO NOT do it this way! SDL3 provides a special and much more convenient event for that.
Use SDL_EVENT_KEY_DOWN to treat the keyboard like a 101-button joystick now. Text input comes from somewhere else.
Key Modifiers: Shift, Ctrl, Alt, …
The _mod field is a 16 bit unsigned integer (corresponds to Pascal’s Word) and represents key modifiers (ctrl, alt, shift, num lock, caps lock, …), which may be pressed while pressing another key. Their names always start with SDL_KMOD_. For example, the left shift key has the name SDL_KMOD_LSHIFT. The full list of possible key modifiers is not very long and found in the link.
If a SDL_EVENT_KEY_DOWN event is caught, you can check the _mod field too see if a key modifiers was pressed at the same time.
Let’s assume you want to have your application to be quit by the user pressing the left or the right ctrl key and “Q”. So you read out the key code for “Q” and check if _mod equals to SDL_KMOD_CTRL.
The major news is the release of SDL3! Many improvements and new features got implemented! We are working on creating an easy to use binding called SDL3 for Pascal in a community effort. It is Free Pascal and Delphi compatible. sechshelme is working on another set of bindings as well.
Fairtris 2: The Ultimate Challenge got released and has got many impressive improvements! You should definitively look into this game. It is open source and you can learn a lot how to use SDL2 in Pascal.
Added a new chapter about the keyboard state and the key states for simple key input handling. Updated the Text and Font chapter.
It seems the author of p_daniel’s (or danpla’s) SDL 2.0 units has deleted his github account (EDIT, 06/10/2019: The user name got changed.), hence I updated the discussion page about which unit to choose. Also the official website of the Bare game project seems to be offline. Thanks for the hints Stéphane.
A new impressive, commercial SDL2 based Pascal project called Savage Vessels has been added to the project list. The project list itself got restructured for better readability and loading times.
In the next chapter we will see how so called events are processed to handle input of any kind (keyboard, mouse, joystick,…) Before that I’d like to introduce a simple, yet powerful way to handle keyboard input via the keyboard state.
Lets have a look at the most important function here. The keyboard state represents the state (pressed = 1 or unpressed = 0) of all the keyboard keys, hence the key states. By
function SDL_GetKeyboardState(numkeys: PInt): PUInt8
we have easy access to this array. This is a highly efficient and fast way of gathering keyboard information without the need to queue any events.
Advantages of using the keyboard state
It is very fast and efficient compared to processing keyboard events. It can (and should) be combined freely with the classical event processing of key-events or other events. You have no hassle with case-statements to care for all the keys you like to consider but rather instant access to all keys on the keyboard. The keyboard state can easily propagated through your program as it is a simple and plain pointer. – Running speed is a crucial property for many games so you should consider using keyboard states if your game relies heavily on keyboard input.
Disadvantages of using the keyboard state
The keyboard states does not consider key modifiers like the shift-key. So you know a key has been pressed but you cannot distinguish between a letter and a capital letter for example. There are no equivalent input states for other devices like mouse, joystick, joypad, and so on. Last but not least, you still need the event processing in the program loop because otherwise the state does not get updated (see below for more information on that). This also means, if the key state of a certain key changes from unpressed to pressed and back to unpressed before the event loop has been processed, the pressed key remains unnoticed.
The red rectangle can be moved by the WASD keys by reading out their key states.
The following code example will draw a red rectangle which can be moved by the WASD keys. Therefore we read out their key states on every cycle of the program loop.
program SDL_KeyboardState;
uses SDL2;
var
sdlWindow1: PSDL_Window;
sdlRenderer: PSDL_Renderer;
sdlRectangle: TSDL_Rect;
sdlKeyboardState: PUInt8;
Running: Boolean = True;
begin
//initilization of video subsystem
if SDL_Init(SDL_INIT_VIDEO) < 0 then Halt;
if SDL_CreateWindowAndRenderer(500, 500, SDL_WINDOW_SHOWN, @sdlWindow1, @sdlRenderer) <> 0
then Halt;
// prepare rectangle
sdlRectangle.x := 250;
sdlRectangle.y := 250;
sdlRectangle.w := 10;
sdlRectangle.h := 10;
// get keyboard state pointer
sdlKeyboardState := SDL_GetKeyboardState(nil);
// program loop
while Running = True do
begin
SDL_PumpEvents;
// ESC pressed
if sdlKeyboardState[SDL_SCANCODE_ESCAPE] = 1 then
Running := False;
// WASD keys pressed
if sdlKeyboardState[SDL_SCANCODE_W] = 1 then
sdlRectangle.y := sdlRectangle.y-1;
if sdlKeyboardState[SDL_SCANCODE_A] = 1 then
sdlRectangle.x := sdlRectangle.x-1;
if sdlKeyboardState[SDL_SCANCODE_S] = 1 then
sdlRectangle.y := sdlRectangle.y+1;
if sdlKeyboardState[SDL_SCANCODE_D] = 1 then
sdlRectangle.x := sdlRectangle.x+1;
// black background
SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderClear(sdlRenderer);
// draw red rectangle
SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderDrawRect(sdlRenderer, @sdlRectangle);
SDL_RenderPresent(sdlRenderer);
SDL_Delay(20);
end;
// clear memory
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow (sdlWindow1);
//closing SDL2
SDL_Quit;
end.
To get the keyboard state, we define a unsigned 8 bit pointer variable sdlKeyboardState in the var clause. It points to the array of key states.
After setting up a SDL2 window and and preparing a SDL2 rectangle, we jump right into allocating the keyboard state pointer to the sdlKeyboardState variable by the function SDL_GetKeyboardState. The argument should be nil. It is sufficient to this once in your program code as you see in the second line:
// get keyboard state pointer
sdlKeyboardState := SDL_GetKeyboardState(nil);
// program loop
while Running = True do
begin
SDL_PumpEvents;
In the program event loop we need to update the event queue by procedure SDL_PumpEvents (or SDL_PollEvent or SDL_WaitEvent; both call SDL_PumpEvent implicitly). Only calling one of these procedures in the program loop updates the keyboard state.
// ESC pressed
if sdlKeyboardState[SDL_SCANCODE_ESCAPE] = 1 then
Running := False;
// WASD keys pressed
if sdlKeyboardState[SDL_SCANCODE_W] = 1 then
sdlRectangle.y := sdlRectangle.y-1;
if sdlKeyboardState[SDL_SCANCODE_A] = 1 then
sdlRectangle.x := sdlRectangle.x-1;
if sdlKeyboardState[SDL_SCANCODE_S] = 1 then
sdlRectangle.y := sdlRectangle.y+1;
if sdlKeyboardState[SDL_SCANCODE_D] = 1 then
sdlRectangle.x := sdlRectangle.x+1;
We now can check for the of any key in the array by sdlKeyboardState[SDL_SCANCODE_…] using its scancode as a handle (e.g. SDL_SCANCODE_ESCAPE for the escape key) and react as desired, e.g. exit the program loop or change the x/y coordinates of the rectangle. The scancode represents the position of the related key state in the array. A detailed description of scancodes in the next chapter. A list of all the scancodes shows all possible scancodes.
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:
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.
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
sdlEvent^.key.keysym.sym.
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:
sdlEvent^.key.keysym.scancode
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
and
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:
Keycodes and Scancodes
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
procedure SDL_StopTextInput
and if it is not active, it gets activated by
procedure SDL_StartTextInput.
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 timestampfield.
The windowIDfield 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 windowIDfield 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:
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 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 decimal values
Modifier key
UInt16 value
Left shift
1
Right shift
2
Left ctrl
64
Right ctrl
128
Left alt
256
Right alt
576 (64 + 512?)
Caps lock
8192
Num lock
4096
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.
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
sdlEvent^.text.text
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 :-).