Tag Archives: sdl2 tutorial

This tag indicates a tutorial chapter for SDL2 and Free Pascal.

Drawing Primitives

Last updated on November 20th, 2021

Displaying of textures as discussed in previous chapters are sometimes accompanied by drawing operations. For some applications and games drawing operations are even essential, e.g. you need to draw buttons of dynamic dimension in your application. Discussion of a more detailed case is found right after the code example.

Supported Primitives: Points, Lines, Rectangles

SDL2 supports natively the drawing of

  • Points
  • Lines
  • Rectangles (outline only)
  • Filled Rectangles.

Drawing in SDL 2.0

Now lets jump right into the code:

program Chapter5_SDL2;

uses SDL2;

var
  i: Integer;
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlRect1: TSDL_Rect;
  sdlPoints: array[0..499] of TSDL_Point;

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;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then
    halt;

  //render and show cleared window with background color
  SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 255, 255);
  SDL_RenderClear(sdlRenderer);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000);

  //render and show a line
  SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, 255);
  SDL_RenderDrawLine(sdlRenderer, 10, 10, 490, 490);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000);

  //render and draw points diagonally with distance between each other
  SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);
  for i := 0 to 47 do
    SDL_RenderDrawPoint(sdlRenderer, 490-i*10, 10+i*10);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000);

  //prepare, render and draw a rectangle
  sdlRect1.x := 260;
  sdlRect1.y := 10;
  sdlRect1.w := 230;
  sdlRect1.h := 230;
  SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, 255);
  SDL_RenderDrawRect(sdlRenderer, @sdlRect1);

  //relocate, render and draw the rectangle
  sdlRect1.x := 10;
  sdlRect1.y := 260;
  SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BLENDMODE_BLEND);
  SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 255, 128);
  SDL_RenderFillRect(sdlRenderer, @sdlRect1);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000);

  //prepare, render and draw 500 points with random x and y values
  Randomize;
  for i := 0 to 499 do
  begin
    sdlPoints[i].x := Random(500);
    sdlPoints[i].y := Random(500);
  end;
  SDL_SetRenderDrawColor(sdlRenderer, 128, 128, 128, 255);
  SDL_RenderDrawPoints(sdlRenderer, sdlPoints, 500);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(3000);

  //clean memory
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  //shut down SDL2
  SDL_Quit;
end.

Wow, that looks like a big load of new functions, but I promise, drawing in SDL2 is very simple. What the executed program will look like is shown in the following screenshot.

Result screenshot for chapter 5

Now, let’s have a closer look to the first lines of code.

program Chapter5_SDL2;

uses SDL2;

var
  i: Integer;
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlRect1: TSDL_Rect;
  sdlPoints: array[0..499] of TSDL_Point;

The program is called “Chapter5_SDL2”. We will need a counter variable “i” of native Pascal type integer later. We need a window and a renderer and call them “sdlWindow1” and “sdlRenderer” as known from the previous chapters. Next we declare a variable “sdlRect1” which is of type TSDL_Rect. The same is true for “sdlPoints” which is an array of 500 elements of type TSDL_Point.

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;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then
    halt;

Nothing new here, we initilize SDL2, create a window with 500 pixels width and 500 pixels height and associate the renderer with this window.

Colours, alpha value and RGB(A) notation in SDL 2.0

  //render and show cleared window with background color
  SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 255, 255);
  SDL_RenderClear(sdlRenderer);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000);  

Now we have something new here, SDL_SetRenderDrawColor() sets a colour for drawing operations, just as if you choose for a pencil colour to draw something. This function doesn’t draw anything though. It returns 0 on success and the negative error code on failure.

SDL_SetRenderDrawColor(renderer: PSDL_Renderer; r: UInt8; g: UInt8; b: UInt8; a: UInt8): SInt32

First you need to set the renderer for which this drawing colour is meant. After that you have to set the colours red, green, blue and the alpha value. They can range from 0 to 255 (integer values only). Think in terms of 255 being 100% and 0 being 0% of that colour or the alpha value. As a start let’s neglect the alpha value.

If you like to have a red colour, you need to set the value for the red colour high, e.g. 100%, the maximum value is 255, and since you don’t want to mix in green and blue, they get the minimum value of 0 (hence 0%). Setting up red therefore corresponds to 255/0/0 in terms of r/g/b. For comparision, 0/255/0 will lead to green, 255/255/255 is white and 0/0/0 is black, and so on. In the example code we used 0/255/255 which leads to cyan (mixing up green and blue additively). With these three values you can generate every colour possible.

So what is the meaning of the alpha value then? Well, it determines the transparency. 255 means fully opaque and 0 means full transparency. This is very important for blend effects in computer graphics and will be demonstrated later on in the code. By the way, instead of 255 you could use SDL_ALPHA_OPAQUE. If you count the alpha value also as colour variation you have 4.29 billion different possibilities.

Setting up a background in SDL2

The function SDL_RenderClear() is for clearing the screen with the drawing colour. As argument you just need to tell the renderer. It is simple as that :-). Since we set the drawing colour to cyan before, the screen will be cleared with a cyan colour.

SDL_RenderClear(renderer: PSDL_Renderer): SInt32

This function will return 0 on success and a negative error code on failure. The cleared screen will be shown for one second by SDL_RenderPresent() and SDL_Delay(). These two procedures are known from the previous chapter.

Drawing Lines and Points in SDL2

  //render and show a line
  SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, 255);
  SDL_RenderDrawLine(sdlRenderer, 10, 10, 490, 490);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000);  

Now we change the drawing colour by SDL_SetRenderDrawColor() to red and use SDL_RenderDrawLine() to draw a simple line.

SDL_RenderDrawLine(renderer: PSDL_Renderer; x1: SInt32; y1: SInt32; x2: SInt32; y2: SInt32): SInt32

Again you need the renderer, which is “sdlRenderer” in our case. After that you specify the x/y coordinates where the line should begin and then the x/y coordinates where the line should end. Remember that the origin 0/0 is at the top left corner of the window. The coordinates 10/10 mean to start at the point ten pixels to the right and ten pixel to the bottom relative to the origin. Thus, the coordinates 490/490 for the second point will lead to a diagonal line across the window. This diagonal line will be 10 pixels short with respect to the window edge. This function returns 0 on success and a negative error code on failure.

After that again we ask to render this line to the screen by SDL_RenderPresent() and wait one second by SDL_Delay().

  //render and draw points diagonally with distance between each other
  SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);
  for i := 0 to 47 do
    SDL_RenderDrawPoint(sdlRenderer, 490-i*10, 10+i*10);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000); 

Now we change the colour to black and draw some points by the function SDL_RenderDrawPoint(). It is nearly identical to SDL_RenderDrawLine() but instead of four coordinates you need just two coordinates where the point should be drawn.

SDL_RenderDrawPoint(renderer: PSDL_Renderer; x: SInt32; y: SInt32): SInt32

This function returns 0 on success and the negative error code on failure.

I thought it would be nice to have more than just one point to be drawn, so the function is used in a for-loop to draw altogether 48 points. Here we need the counter variable “i”. Maybe you can guess from the code where the points are and how they are arranged, if not, just run the code ;-). Finally the result is rendered to the screen by SDL_RenderPresent() and the program waits one second by SDL_Delay().

Let’s proceed to the next chunk of code now.

  //prepare, render and draw a rectangle
  sdlRect1.x := 260;
  sdlRect1.y := 10;
  sdlRect1.w := 230;
  sdlRect1.h := 230;
  SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, 255);
  SDL_RenderDrawRect(sdlRenderer, @sdlRect1);

The drawing colour is set to green by SDL_SetRenderDrawColor() and the rectangle is drawn by SDL_RenderDrawRect().

SDL_RenderDrawRect(renderer: PSDL_Renderer; rect: PSDL_Rect): SInt32

It requires the renderer, which is “sdlRenderer” for us and a PSDL_Rect , thus we use the @ operator for the declared rectangle of TSDL_Rect type to get its pointer value. This function returns 0 on success and the negative error code on failure. Notice, we neither render the result to the screen now nor do we delay here. Anyway, we want a second rectangle! 🙂

  //relocate, render and draw the rectangle
  sdlRect1.x := 10;
  sdlRect1.y := 260;
  SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BLENDMODE_BLEND);
  SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 255, 128);
  SDL_RenderFillRect(sdlRenderer, @sdlRect1);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000); 

We change the rectangles x/y coordinates for the second rectangle but keep its width and height. What we are looking for is a filled rectangle that has some transparency. Until now we always used 255 (opaque) as alpha value. We set the colour to draw the second rectangle to blue by SDL_SetRenderDrawColor(). Notice that the fourth value is 128 (half-transparent) instead of 255 (opaque). So everything behind the blue rectangle, e.g. the cyan background, should therefore shine through. To generate a filled rectangle SDL_RenderFillRect() is used:

SDL_RenderFillRect(renderer: PSDL_Renderer; rect: PSDL_Rect): SInt32

The renderer and the rectangle of type PSDL_Rect are the parameters of this function. So we use “sdlRenderer” and “sdlRect1” (with the @ operator) again to draw the rectangle. This function returns 0 on success and the negative error code on failure.

The Blend Mode in SDL 2.0

But to be honest, even if you change the alpha value it will be opaque. This is because the blend mode is set to SDL_BLENDMODE_NONE by default. We need to change this to be able to use the alpha value as desired. SDL_SetRenderDrawBlendMode() is what we are looking for:

SDL_SetRenderDrawBlendMode(renderer: PSDL_Renderer; blendMode: TSDL_BlendMode): SInt32

First the renderer for which the blend mode has to be set is chosen. In our case it is “sdlRenderer” again. Then there are four blend modes available. Their description is taken from the official SDL2 Wiki.

  1. SDL_BLENDMODE_NONE
    • no blending
    • dstRGBA = srcRGBA
  2. SDL_BLENDMODE_BLEND
    • alpha blending
    • dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA))
    • dstA = srcA + (dstA * (1-srcA))
  3. SDL_BLENDMODE_ADD
    • additive blending
    • dstRGB = (srcRGB * srcA) + dstRGB
    • dstA = dstA
  4. SDL_BLENDMODE_MOD
    • color modulate
    • dstRGB = srcRGB * dstRGB
    • dstA = dstA

We are looking for alpha blending, so we use SDL_BLENDMODE_BLEND as argument for the blend mode. This function returns 0 on success and the negative error code on failure.

After doing so the result is rendered to the screen by SDL_RenderPresent() and shown for one second by SDL_Delay(). Both rectangles, the green one and the half-transparent blue one appear at the same time.

Spread Points Randomly using PSDL_Point

  //prepare, render and draw 500 points with random x and y values
  Randomize;
  for i := 0 to 499 do
  begin
    sdlPoints[i].x := Random(500);
    sdlPoints[i].y := Random(500);
  end; 

Randomize is a Free Pascal procedure (from system unit) to initilize the random number generator. Imagine this as shaking the dice.

Let’s have a look at TSDL_Point. It is just a record of this structure:

PSDL_Point = ^TSDL_Point;
TSDL_Point = record
  x: SInt32;
  y: SInt32;
end;

As expected, the TSDL_Point record has two values, the x/y coordinates of the point. Notice how PSDL_Point is just a pointer to it. Free Pascal’s Random() function generates random numbers between 0 and the number which is used as argument substracted by one. So we generate 500 times a random x and y value between 0 and 499 and save them into the 500 SDL_Point records.

  SDL_SetRenderDrawColor(sdlRenderer, 128, 128, 128, 255);
  SDL_RenderDrawPoints(sdlRenderer, sdlPoints, 500);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(3000);

A grey colour is set by SDL_SetRenderDrawColor() for the points. To draw the points in the array “sdlPoints” we use SDL_RenderDrawPoints().

SDL_RenderDrawPoints(renderer: PSDL_Renderer; points: PSDL_Point; count: SInt32): SInt32

This function returns 0 on success and the negative error code on failure. First you need to set the renderer, then just a pointer to an array of TSDL_Point (returned by simply passing the array’s name) and finally the number of points. The latter should be consistent with the array. So, if your array has 500 elements, the count should be 500 at maximum. Notice how we are not using a loop here to draw all 500 points by calling a certain function 500 times. Instead we just pass an array of points once. This way we save a lot of time at runtime, especially if you think of a real application where even more points have to be drawn. There are similar functions for lines, rectangles and filled rectangles. They are not used in the example but it may be interesting to know, so here they are:

SDL_RenderDrawLines(renderer: PSDL_Renderer; points: PSDL_Point; count: SInt32): SInt32

SDL_RenderDrawRects(renderer: PSDL_Renderer; rects: PSDL_Rect; count: SInt32): SInt32

SDL_RenderFillRects(renderer: PSDL_Renderer; rects: PSDL_Rect; count: SInt32): SInt32

As a hint, try replacing SDL_RenderDrawPoints by SDL_RenderDrawLines, and changing the count to 499. You will be rewarded with a beautiful pattern. 🙂

  //clean memory
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  //shut down SDL2
  SDL_Quit;
end.   

Finally all the memory reserved for points, the rectangle, the renderer and the window is free’d and SDL2 shut down by SDL_Quit.

Congratulations, you just finished this chapter :-).

You may read

  • Trouble: Using Array of PSDL_Point
  • Case: Drawing for Dynamic Colouring
  • Understanding Colours in Computer Graphics

below, or you may directly skip to the next chapter .

← previous Chapter | next Chapter →

Trouble: Using Array of PSDL_Point

To put your array in the heap, you would want to use an array of PSDL_Point instead of an array of TSDL_Point. For some reason it doesn’t seem to work well with the SDL_RenderDrawPoints() function. This means, instead of, let’s say 500 points just a quarter of all points (125 in the example) is shown unless you specifiy the count as being four times greater (2000 in the example). The reason behind this, is not clear to me.

Case: Drawing for Dynamic Colouring

SDL2 drawing and images diagram

As an example when drawing can be very helpful, think of the following situation. You create a game, let’s say a racing game. There are different players. Of course, each player’s car has to have a different colour. There are generally two ways to achieve this:

1) You create several images with differently coloured cars and store them on your harddrive.

This is perfectly okay if you have a small number of cars (and colours) to be chosen. But what if you want the player to choose from a large variety of colours, 100 colours or more, or what if you want to let the player to choose the colour of his car by himself? Notice, in case of 16 bit colouring that means 65536 possibilities after all! Would you want to create that many images? In case of 32 bit colouring you have fantastic 4.29 billion colours!! Amazing, but you will never be able to create so many images in just one human being’s life. Furthermore, this would take up a lot of hard drive memory (4.29 billion times the file size of the car image) for just a simple car racing game. Look at the following image. It contains the discussed method from left to the middle. On the right to the middle is the solution :-).

Instead of having each car coloured as an image file, why not using just one image file of a car without colouring? It is kind of a template. Now you can easily ask the player what colour he prefers (and he may pick from 4.29 billion colours if necessary), and then you simply colour the car on the fly. This is the second way:

2) You create one template image on your harddrive and colour it during runtime of the program.

This is just one example where drawing is very helpful.

Understanding Colours in Computer Graphics

The colour is composed of four components, RGBA, that is red, green, blue and the alpha value. The physical screen consists of many small units. Every unit itself consists of three different coloured lights. These colours are red, green and blue. If you mix them up, you can get every other colour. These colours are mixed up additively. For example if you mix red and green you get yellow. For three colours that can be mixed with each other, there are eight combinations possible which lead to different colours (RGB, RG, RB, R, GB, G, B, all lights off). If you mix red, green and blue (all lights on, RGB), you get white, and if all lights are off, you get black. Some may say, white and black are no colours at all. Well, that is right but doesn’t matter here and to keep simpliness I will talk of colours even if I talk of black and white.

Your screen definitvly has more than eight colours, doesn’t it? The reason is, your screen isn’t just able to switch lights on or off. Besides it is able to differ the intensities of the lights. The more intensity levels you have the more colours you can display. The case that you have eight colours as discussed before means that you just have one intensity level, on or off. If your screen is in 8 bit mode every pixel on the screen has the possibility to display 2 power 8 colours. That are 256 different colours. Every of the three lights has therefore a certain amount of different light intensity levels. If you have 16 bit mode you have 2 power 16 and that are 65536 colours. Each light therefore has the appropriate amount of intensity levels. Since we prefer 32 bit mode, we have 4.29 billion different colours!

Event handling Pt. 2, Mouse handling

Last updated on February 17th, 2024

This is part 2 of the chapter about event handling. Mouse and window handling is treated here.

Let’s start with the full example 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
                         27: 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.

Mouse handling in SDL 2.0

If you got the basic concept of event handling, you will find that mouse handling and keyboard handling have a lot in common.

        //mouse events
        SDL_MOUSEMOTION: begin
                           writeln( 'X: ', sdlEvent^.motion.x, '   Y: ', sdlEvent^.motion.y,
                                    '   dX: ', sdlEvent^.motion.xrel, '   dY: ', sdlEvent^.motion.yrel );
                         end;

For mouse motions, mouse buttons and the mouse wheel there are three different mouse event structures: SDL_MouseMotionEvent, SDL_MouseButtonEvent and SDL_MouseWheelEvent.

Mouse motion handling in SDL 2.0

If you are moving the mouse, the SDL_MOUSEMOTION event is triggered. The record structure of the SDL_MouseMotionEvent is shown below:

TSDL_MouseMotionEvent = record
    type_: UInt32;       // SDL_MOUSEMOTION
    timestamp: UInt32;
    windowID: UInt32;    // The window with mouse focus, if any
    which: UInt32;       // The mouse instance id, or SDL_TOUCH_MOUSEID
    state: UInt8;        // The current button state 
    padding1: UInt8;
    padding2: UInt8;
    padding3: UInt8;
    x: SInt32;           // X coordinate, relative to window
    y: SInt32;           // Y coordinate, relative to window
    xrel: SInt32;        // The relative motion in the X direction 
    yrel: SInt32;        // The relative motion in the Y direction
  end;

Again there are the type_, the timestamp and the windowID fields. Nothing new here. The field which contains the mouse id. This is important if you have more than one mouse device attached to your computer. Think for example of a laptop with a touchpad area to move the mouse cursor and at the same time there is an usb mouse attached to the laptop. To distinguish between the two, you may retrieve their id’s.

The state field is known from the SDL_KeyBoardEvent structure. It may be a difference if you have a mouse button pressed and move the mouse or if you don’t have a button pressed. The most famous example is if you want to select a bunch of files on your desktop or in a folder. By the way, the state field encodes a number which is different depending on which buttons you pressed actually. This works similar to the key modifiers, if you keep two mousebuttons pressed while moving, the state is the sum of each individual mouse button state value. As an example for my mouse: No mouse button 0, left mouse button 1, right mouse button 4, middle mouse button 2, thumb button 8. If I keep pressed left and right mouse button 5 (sum 1 + 4).

The x and y fields contain the coordinate of the mouse cursor in pixels. These coordinates are relative to the window of the SDL 2.0 application. Keep in mind, the coordinates (0/0) correspond to the left upper corner. Positive x values are counted from left to right and positive y values are counted from top to bottom.

The fields xrel and yrel are used to determine how fast the mouse has been moved from one point to another. Let’s assume you move the mouse surcor from left to right in your application’s window. The first time you do it slowly, xrel might be 1, means, you just moved pixel from left to right between two mouse motion events. If you move fast, xrel might be 50, meaning that this time you moved by 50 pixels between two mouse motion events. Especially for game programming this can be a extremely important information. E.g., think of first person shooter, if the movement speed of the first person view would be independent of the actual movement of the mouse, this game wouldn’t make much sense.

To access these fields the event’s motion field has to be read out. In the example code the (x/y) coordinates and the relative positions xrel and yrel are read out by

sdlEvent^.motion.x

sdlEvent^.motion.y

sdlEvent^.motion.xrel

sdlEvent^.motion.yrel

and simply printed out to the screen. Let’s go for the next chunk of code.

        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;

Pressing a mouse button in SDL 2.0

As for the SDL_KeyBoardEvent, you would want to know if a mouse button and which one is pressed or released. If a mouse button is pressed, a SDL_MOUSEBUTTONDOWN event is triggered. On releasing a mouse button a SDL_MOUSEBUTTONUP event is triggered. It has SDL_MouseButtonEvent structure. Let’s have a look into the structure:

TSDL_MouseButtonEvent = record
    type_: UInt32;       // SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP 
    timestamp: UInt32;
    windowID: UInt32;    // The window with mouse focus, if any
    which: UInt32;       // The mouse instance id, or SDL_TOUCH_MOUSEID 
    button: UInt8;       // The mouse button index
    state: UInt8;        // SDL_PRESSED or SDL_RELEASED
    padding1: UInt8;
    padding2: UInt8;
    x: SInt32;           // X coordinate, relative to window
    y: SInt32;           // Y coordinate, relative to window 
  end;

If you compare this structure to the structure of the SDL_MouseMotionEvent, you will find, only the two field xrel and yrel are gone and a new field, a crucial one to be clear, is new, which is button of 8 bit unsigned integer type.

I won’t discuss all the fields again we discussed for the SDL_MouseMotionEvent structure. Attention, if you compare the state field of the SDL_MouseButtonEvent, it works another way. It allows just for two values, SDL_PRESSED or SDL_RELEASED, as known from SDL_KeyBoardEvent. As a reminder: For the SDL_MouseMotionEvent, it represented that full state of all the buttons being pressed while mouse motion.

The button field allows to recognize which button has triggered the SDL_MouseButtonEvent. Each button of the mouse has its own index. As an example for my mouse they are as follows: Left mouse button 1, right mouse button 3, middle mouse button 2, thumb mouse button 4. Do not confuse these index numbers with the mouse button state values of the SDL_MouseMotionEvent structure. There can only be one button which triggered this event! Combinations as for the motion event are not possible. In the example code this value is just printed to the screen using

sdlEvent^.button.button.

You see, to access the fields of this record you need to address the event’s button field. This mustn’t be confused with SDL_MouseButtonEvent’s button field discussed above.

The x and y fields contain the (x/y) coordinates when the mouse button (whose index is stored in field button) has been pressed (or released). This is crucial to know. What would an application be worth if you could recognized that a certain button has been pressed but you don’t know where exactly? Not shown in the code but you could access these fields by

sdlEvent^.button.x

sdlEvent^.button.y.

The mouse wheel in SDL 2.0

If the mouse wheel is used a SDL_MOUSEWHEEL event is triggered. Let’s look into the corresponding SDL_MouseWheelEvent structure.

TSDL_MouseWheelEvent = record
    type_: UInt32;        // SDL_MOUSEWHEEL
    timestamp: UInt32;
    windowID: UInt32;    // The window with mouse focus, if any
    which: UInt32;       // The mouse instance id, or SDL_TOUCH_MOUSEID
    x: SInt32;           // The amount scrolled horizontally 
    y: SInt32;           // The amount scrolled vertically
  end;

New fields here are the x and y field which do not correspond to the mouse cursor position this time. Instead of that they refer to the direction of the mouse wheel being scrolled. If you scroll the mouse wheel upwards or forward it will return 1, and if you scroll it backwards it will return -1. If you have a mouse wheel which can be scrolled horizontally, it will be similar. By the way scrolling a mouse wheel can be considered as pressing a button very quickly for that direction.

In the code, if a SDL_MOUSEWHEEL event is triggered, the y value is checked to be positive or negative. This decides if an upward or downward scrolling has happened and the corresponding value (1 or -1) will be returned and printed out. Anyway, could you guess what happens if anyone would use a mouse wheel that is scrolling horizontally? – The same block will be executed since a SDL_MOUSEWHEEL event is triggered. Instead of y being 1 or -1, it will be 0 but the x value will be 1 or -1. Nevertheless, the else-block will be executed since y is not greater than 0. So for the example program it will print out wrongly that an backward scroll has happened. Anyway, you get the idea.

Window handling in SDL 2.0

Modern applications are always run in windows. The famous operation system “Windows” by Microsoft even derived it’s name from this. The first task for most of SDL 2.0 applications is the creation of a window. The example code creates a window of width 500 pixels and height 500 pixels. It may be important to know if the user interacts with the application window. Whenever this happens a SDL_WINDOWEVENT is triggered.

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

If the an event of type SDL_WINDOWEVENT is triggered, the text message “Window event: ” is printed out.

To access the window event fields, you need to access the event’s window field. The field event contains the window event’s type information, hence what window event has been triggered.

sdlEvent^.window.event

In contrast to the keyboard and the mouse event we discussed before, the different event types are not distinguished by the type_ field but by an additional field event.

In the example code six different window event types are checked: SDL_WINDOWEVENT_SHOWN, SDL_WINDOWEVENT_MOVED, SDL_WINDOWEVENT_MINIMIZED, SDL_WINDOWEVENT_MAXIMIZED, SDL_WINDOWEVENT_ENTER and SDL_WINDOWEVENT_LEAVE. From the texts printed out you can guess when they get triggered. I think no further explanation is needed here.

By the way, there are more window event types which are shown a little bit later. Sometimes if one of these is triggered, only the text that a window event has been triggered is shown but without any further details since the example code doesn’t covers further treatment. Feel free to extent the code yourself.

Let’s have a look at the event structure of SDL_WindowEvent.

TSDL_WindowEvent = record
    type_: UInt32;       // SDL_WINDOWEVENT
    timestamp: UInt32;
    windowID: UInt32;    // The associated window
    event: UInt8;        // SDL_WindowEventID
    padding1: UInt8;
    padding2: UInt8;
    padding3: UInt8;
    data1: SInt32;       // event dependent data
    data2: SInt32;       // event dependent data 
  end;

The fields type_, timestamp and windowID are known and have the same meaning as discussed before.

The field event stores an identifier (SDL_WindowEventID) to distinguish between different window events. Here they are listed and in brackets you find the window related action which has triggered them:

  1. SDL_WINDOWEVENT_SHOWN (window has been shown)
  2. SDL_WINDOWEVENT_HIDDEN (window has been hidden)
  3. SDL_WINDOWEVENT_EXPOSED (window has been exposed and should be redrawn)
  4. SDL_WINDOWEVENT_MOVED (window has been moved to data1, data2)
  5. SDL_WINDOWEVENT_RESIZED (window has been resized to data1xdata2; this is event is always preceded by SDL_WINDOWEVENT_SIZE_CHANGED)
  6. SDL_WINDOWEVENT_SIZE_CHANGED (window size has changed, either as a result of an API call or through the system or user changing the window size; this event is followed by SDL_WINDOWEVENT_RESIZED if the size was changed by an external event, i.e. the user or the window manager)
  7. SDL_WINDOWEVENT_MINIMIZED (window has been minimized)
  8. SDL_WINDOWEVENT_MAXIMIZED (window has been maximized)
  9. SDL_WINDOWEVENT_RESTORED (window has been restored to normal size and position)
  10. SDL_WINDOWEVENT_ENTER (window has gained mouse focus)
  11. SDL_WINDOWEVENT_LEAVE (window has lost mouse focus)
  12. SDL_WINDOWEVENT_FOCUS_GAINED (window has gained keyboard focus)
  13. SDL_WINDOWEVENT_FOCUS_LOST (window has lost keyboard focus)
  14. SDL_WINDOWEVENT_CLOSE (the window manager requests that the window be closed)

This list is based upon information found at the SDL 2.0 wiki.

If you read through the list carefully you will notice the mention of data1 and data2 which rather explains their occurance in the event structure :-)! They need to be read out for SDL_WINDOWEVENT_MOVED and SDL_WINDOWEVENT_RESIZED to get the new window position or dimensions.

At the moment I’m not sure why for window events the distinction between the individual window events (e.g. SDL_WINDOWEVENT_MOVED, SDL_WINDOWEVENT_RESIZED, and so on) is not done by the type_ field as for keyboard, mouse and other events, but rather by the additional event field.

                         end;
      end;
    end;
    SDL_Delay( 20 );
  end;

  dispose( sdlEvent );
  SDL_DestroyWindow ( sdlWindow1 );

  //shutting down video subsystem
  SDL_Quit;

end.

Not much to learn in the final part. The loop is delayed by 20 milliseconds for better recognizability of the text output.

If the loop is left, the event pointer gets free’d, the SDL 2.0 window gets destroyed and SDL 2.0 quit. That’s it :-)!

Touchscreen events, Joystick events and many more!

This chapter covered keyboard, mouse and window events in some detail. Keep in mind, SDL 2.0 has much more to show! – There are many more events you can use for application development. They basically cover any modern type of interaction you could wish for. This includes touchscreen events (important for smartphone development), joystick events (game console development), and even a dropfile event (drag and drop files) and more.

previous Chapter | next Chapter

Event handling Pt. 1, Keyboard handling

Last updated on February 17th, 2024

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 types, Event structure and SDL_Event field
Event type Event structure SDL_Event field
SDL_AUDIODEVICEADDED
SDL_AUDIODEVICEREMOVED
SDL_AudioDeviceEvent adevice
SDL_CONTROLLERAXISMOTION SDL_ControllerAxisEvent caxis
SDL_CONTROLLERBUTTONDOWN
SDL_CONTROLLERBUTTONUP
SDL_ControllerButtonEvent cbutton
SDL_CONTROLLERDEVICEADDED
SDL_CONTROLLERDEVICEREMOVED
SDL_CONTROLLERDEVICEREMAPPED
SDL_ControllerDeviceEvent cdevice
SDL_DOLLARGESTURE
SDL_DOLLARRECORD
SDL_DollarGestureEvent dgesture
SDL_DROPFILE SDL_DropEvent drop
SDL_FINGERMOTION
SDL_FINGERDOWN
SDL_FINGERUP
SDL_TouchFingerEvent tfinger
SDL_KEYDOWN
SDL_KEYUP
SDL_KeyboardEvent key
SDL_JOYAXISMOTION SDL_JoyAxisEvent jaxis
SDL_JOYBALLMOTION SDL_JoyBallEvent jball
SDL_JOYHATMOTION SDL_JoyHatEvent jhat
SDL_JOYBUTTONDOWN
SDL_JOYBUTTONUP
SDL_JoyButtonEvent jbutton
SDL_JOYDEVICEADDED
SDL_JOYDEVICEREMOVED
SDL_JoyDeviceEvent jdevice
SDL_MOUSEMOTION SDL_MouseMotionEvent motion
SDL_MOUSEBUTTONDOWN
SDL_MOUSEBUTTONUP
SDL_MouseButtonEvent button
SDL_MOUSEWHEEL SDL_MouseWheelEvent wheel
SDL_MULTIGESTURE SDL_MultiGestureEvent mgesture
SDL_QUIT SDL_QuitEvent quit
SDL_SYSWMEVENT SDL_SysWMEvent syswm
SDL_TEXTEDITING SDL_TextEditingEvent edit
SDL_TEXTINPUT SDL_TextInputEvent text
SDL_USEREVENT SDL_UserEvent user
SDL_WINDOWEVENT SDL_WindowEvent window
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).

Chapter 8 - result

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

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
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:

https://wiki.libsdl.org/SDLKeycodeLookup or SDL 2.0 Key code lookup table (Backup hosted here)

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

You may have a look into the SDL 2.0 scancode lookup table: https://wiki.libsdl.org/SDLScancodeLookup or SDL 2.0 Scancode lookup table (Backup)

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.

Difference Key code and Scancode

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.

        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

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 :-).

previous Chapter | next Chapter

SDL2_ttf: Rendering Fonts and Texts

Last updated on November 20th, 2021

Briefly: What’s a font?

A font represents the style how the letters of a word or sentence appear (e.g. Arial, New Times Roman, and so on). These style informations are saved in so-called font files. There are different standards how to save the font into a file, but the most important font file standard is the TrueType Font standard, thus the name SDL2_ttf. SDL2_ttf is capable to work with TrueType fonts. The typical file extension of TrueType font files is “.ttf”. FreeType 2.0 is no font file standard but a software library to render TrueType font files.

Fonts in Applications

In the last chapters you have heard a lot about the creation of textures from image files and how to manipulate and draw to them. But there is nearly no application out there where you go out without some text messages. Of course, you could print some text to an image and load this image to your application to introduce text to it. But what if you like to implement a chat feature to your application where text messages are created dynamically? Or you like the user to insert his name for a highscore list? There are thousands of other circumstances where you need dynamically generated texts.

The SDL2_ttf Unit

Here comes the SDL2_ttf unit into play. The ability to do this is not implemented in the native SDL 2.0 library itself but the SDL project provides an official extension called SDL2_ttf to render TrueType fonts based on the FreeType project and their FreeType 2.0 release.

Installing SDL2_ttf

As for the work with SDL2_image you will need to add some library files to your system to use fonts since SDL2_ttf isn’t part of native SDL 2.0.

  • download the most recent version of the Runtime Binaries of the SDL2_ttf library for your system
  • install the library according to your system (Win32/64, Linux, Mac OS X)

SDL2_ttf Installing Instructions for Windows

Download the corresponding SDL2_ttf package depending on your system (32 bit or 64 bit) and extract the zip file. You will end up with a SDL2_ttf.dll and two more dlls (zlib1.dll, libfreetype-6.dll) which are necessary for support of compression and FreeType routines. Copy all these files to your system folder, e.g. for Windows XP or Windows 8.1 32 bit C:\WINDOWS\system32\. If you are not sure about your system folder, you should copy all these files into the same folder where the source code file (.pas or .pp) of your SDL 2.0 program is.

If you are running on a different platform (e.g. Mac OS X, Linux, …), please check the link given above for further information on SDL2_ttf installation.

The Road to Texts in SDL 2.0

Before jumping right into the code, let’s discuss the two concepts of SDL 2.0 of saving images in memory again. We prefer to work with textures because they have all the advantages we discussed in prior chapters, especially in Chapter – Surfaces and Textures. Anyway, in SDL2_ttf there are several functions to load a text as a surface (details later). Hence we need to convert the surface into a texture as known. Look at the following diagram to understand the way we need to go:

SDL2 text creation diagram
Create a surface with a given text and create a texture from it. This can be rendered as known to the screen.

So according to the diagram the way to go is as follows, if you have a text in mind (left quadrat in diagram), first create a SDL_Surface by using one of the functions SDL2_ttf provides (lower path to quad at the bottom in the diagram). Next you convert this SDL_Surface into a SDL_Texture by the function SDL_CreateTextureFromSurface (go straight from the buttom quad to the upper quad in the diagram). Finally render the text as you like to the screen.

Sidenote, “blitting” and SDL_Flip are not possible in SDL2 anymore for good (performance) reasons.

Let’s jump into the code now .

program SDL_Fonts;

uses SDL2, SDL2_ttf;

var
  sdlSurface1 : PSDL_Surface;
  ttfFont : PTTF_Font;
  sdlColor1, sdlColor2 : TSDL_Color;
  sdlWindow1 : PSDL_Window;
  sdlRenderer : PSDL_Renderer;
  sdlTexture1 : PSDL_Texture;

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;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then HALT;


  //initialization of TrueType font engine and loading of a font
  if TTF_Init = -1 then HALT;
  ttfFont := TTF_OpenFont('C:\WINDOWS\fonts\Arial.ttf', 40);
  TTF_SetFontStyle(ttfFont, TTF_STYLE_UNDERLINE or TTF_STYLE_ITALIC);
  TTF_SetFontOutline(ttfFont, 1);
  TTF_SetFontHinting(ttfFont, TTF_HINTING_NORMAL);

  //define colors by RGB values
  sdlColor1.r := 255; sdlColor1.g := 0; sdlColor1.b := 0;
  sdlColor2.r := 0; sdlColor2.g := 255; sdlColor2.b := 255;

  //rendering a text to a SDL_Surface
  sdlSurface1 := TTF_RenderText_Shaded(ttfFont, 'Hello World!', sdlColor1, sdlColor2);

  //convert SDL_Surface to SDL_Texture
  sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface1);

  //rendering of the texture
  SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(5000);

  //cleaning procedure
  TTF_CloseFont(ttfFont);
  TTF_Quit;

  SDL_FreeSurface(sdlSurface1);
  SDL_DestroyTexture(sdlTexture1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow(sdlWindow1);

  //shutting down video subsystem
  SDL_Quit;
end.

The final result should behave like this: The text “Hello World!” appears for five seconds in italics and underlined. The text is red and the background is cyan. The following screenshot gives an impression what is to be expected.

Result screenshot for chapter 7

Let’s begin with the initial lines of code.

program SDL_Fonts;

uses SDL2, SDL2_ttf;

The program is called “SDL_Fonts” and we will need unit SDL2_ttf additional to SDL2 to have access to the TrueType font engine.

var
  sdlSurface1 : PSDL_Surface;
  ttfFont : PTTF_Font;
  sdlColor1, sdlColor2 : TSDL_Color;
  sdlWindow1 : PSDL_Window;
  sdlRenderer : PSDL_Renderer;
  sdlTexture1 : PSDL_Texture;

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;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then HALT;

Then we are preparing for some new types of variables. First of all we have a pointer variable called “sdlSurface1” which is of type PSDL_Surface and points at a SDL_Surface. The text message we provide will be rendered into this SDL_Surface first.

Next we find a PTTF_Font variable called “ttfFont” which points at TTF_Font that holds the font’s data. The font’s data itself is usually stored in a font file.

Finally there are two color variables “sdlColor1” and “sdlColor” of pointer type PSDL_Color. These pointers point at records which contain color data as composition of a r, b and g value. The exact definition will be shown when they are used in the code.

Well, the next variables are known from previous chapters. Also the initialization of SDL 2.0 and the creation of the window and the renderer are known already. So we should proceed to the next code chunk now.

//initialization of TrueType font engine and loading of a font
  if TTF_Init = -1 then HALT;
  ttfFont := TTF_OpenFont('C:\WINDOWS\fonts\Arial.ttf', 40);
  TTF_SetFontStyle(ttfFont, TTF_STYLE_UNDERLINE or TTF_STYLE_ITALIC);
  TTF_SetFontOutline(ttfFont, 1);
  TTF_SetFontHinting(ttfFont, TTF_HINTING_NORMAL);

To inialize the TrueType font engine

TTF_Init(): Integer

without any arguments is called which returns 0 on success and -1 on error. There is no specific error code returned.

TTF_OpenFont(_file: PAnsiChar; ptsize: Integer): PTTF_Font

prepares a font. The first parameter “_file” asks for the absolute file path of the font. The parameter “ptsize” asks for an integer value which determines the size of the font. The larger the value, the larger the letters will appear finally (just as known from text editors). Anyway, if you choose too large size the largeste size will be chosen.

Styling the text in SDL 2.0

By procedures

TTF_SetFontStyle(font: PTTF_Font; style: Integer),

TTF_SetFontOutline(font: PTTF_Font; outline: Integer)

and

TTF_SetFontHinting(font: PTTF_Font; hinting: Integer)

further shaping of the appearence of the text can be performed. All the procedures need to know as first argument to which font they should be applied, so “ttfFont” in our case. Then the style can be set by the constants shown in the following table, which are kind of self-explanatory. By OR’ing the style constants you can even combine them as shown in the example code. The text shall be in italics and underlined.

  1. TTF_STYLE_NORMAL
    • No application of a certain style
  2. TTF_STYLE_BOLD
    • Set a bold style
  3. TTF_STYLE_ITALIC
    • Set letters in italics
  4. TTF_STYLE_UNDERLINE
    • Have the text underlined
  5. TTF_STYLE_STRIKETHROUGH
    • Have the text stroken through

The outline is simply set by a number. The larger the number, the larger the outline of the letters will appear. If you don’t need an outline, the argument has to be 0.

The hinting is set similar to the style by pre-defined constants shown in the following table. The hinting setting influences the appearance of the letters and text. In general it should lead to sharper letters, anyway, which setting is best for a certain situation and display device may differ. So if you are unsure what setting to use you should choose TTF_HINTING_NORMAL. If you don’t call this procedure this setting is the default anyway.

  1. TTF_HINTING_NORMAL
    • Normal hinting is applied
  2. TTF_HINTING_LIGHT
    • Light hinting is applied
  3. TTF_HINTING_MONO
    • I guess monospaced characters, so all the characters have the same space between each other
  4. TTF_HINTING_NONE
    • Have the text underlined

All of these three procedure have a counter-function which returns the set style, outline and hinting as an integer number, defined as

TTF_GetFontStyle(font: PTTF_Font): Integer,

TTF_GetFontOutline(font: PTTF_Font): Integer,

TTF_GetFontHinting(font: PTTF_Font): Integer.

Obviously the only parameter to be set is the font whose style, outline or hinting you like to know. These functions aren’t demonstrated in the sample though.

Colouring the Text using TSDL_Color/PSDL_Color

The next lines of code are waiting for us.

//define colors by RGB values
  sdlColor1.r := 255; sdlColor1.g := 0; sdlColor1.b := 0;
  sdlColor2.r := 0; sdlColor2.g := 255; sdlColor2.b := 255;

Here we are starting to consider some coloring of the text. The first color set up in “sdlColor1” of kind TSDL_Color will be the color of the letters. The second color set up in “sdlColor2” will be the background color of the letters. Let’s have a look at the definition of TSDL_Color:

TSDL_Color and RGB triples

  TSDL_Color = record
    r: UInt8;
    g: UInt8;
    b: UInt8;
    a: UInt8;
  end;

The TSDL_Color record has four fields. For additive color mixing you often have a red share, a green share and a blue share (RGB triple). These three fundamental colors are capable of generating any other color by mixing in the respective proportions. E.g. 100% red and 0% green and 0% blue will lead to red. But 100% red, 100% green and 0% blue will lead to yellow. If all three colours are 100% you will get white. If all of them are 0% you will get black. You may notice the variable type UInt8 which means 8bit unsigned integer. From this follows that the values can range between 0 and 255 where 0 equals 0% and 255 equals 100% of the corresponding color. So 2563 = 16,777,216 individual colors can be created.

The a field is for the alpha value, which determines the share of transparency. It is set to opaque (value 255) by default.

Text creation

//rendering a text to a SDL_Surface
  sdlSurface1 := TTF_RenderText_Shaded(ttfFont, 'Hello World!', sdlColor1, sdlColor2);

Finally a text, e.g. the famous “Hello World!” with the chosen colors has to be created. There are several functions to do so and just one of them is chosen for demonstration how the creation generally works. The chosen function is

TTF_RenderText_Shaded(font: PTTF_Font; text: PAnsiChar; fg, bg: TSDL_Color): PSDL_Surface

It requires four arguments and returns a PSDL_Surface (no PSDL_Texture!), a pointer to a SDL_Surface. The first argument is the font you would like to use. Next is the actual text. The text can be directly written as shown in the example but also could be stored in a variable of type PAnsiChar.

PAnsiChar and Pascal Strings

Usually String variables are used in Pascal programming language, so why here we have a PAnsiChar? Well, since SDL is written in C/C++, and there typically null-terminated strings of characters are used to store and handle text, this is kept for better compatibility and easier translation of SDL 2.0 for Pascal compilers. In principle it is no big deal to use PAnsiChar instead of String, even though Pascal programmers prefer String variables.

Last but not least there are the two arguments “fg” and “bg” which translates to foreground and background. Here we insert the colors we defined before. That’s it.

Text Quality and Rendering Speed

Okay, as mentioned before there are more functions to create a SDL_Surface with text. In general there are three groups of functions according to their properties. The general naming scheme is as follows:

TTF_Render[encoding]_[suffix]

Those which have the suffix “Solid” are very fast created but their quality is low. Use them when rendering fast changing text (e.g. the score of a pinball simulation). The second group has the suffix “Shaded”. They are slower rendered and have a background color but have much better quality. The last group of functions have the suffix “Blended”. They are of high quality but slow to be rendered. Use them for more static text which doesn’t change a lot.

For each group of quality and functions you find “Text”, “UTF8”, “UNICODE” and “Glyph” in the encoding part of the functions name right after “TTF_Render”. “Text”, “UTF8” and “UNICODE” are three different types of encoding of the text. “Text” corresponds to Latin1 encoding, “UTF8” to UTF-8 encoding and “UNICODE” to Unicode encoding. Which type to choose depends on what type of characters (e.g. Cyrillic letters, Latin characters, Chinese characters) you are going to use. If you are unsure which of these functions to use, go with the “UNICODE” version.

For rendering a single character by its Unicode code, use the function which contains “Glyph” as suffix to “TTF_Render”.

Finally I’d like to mention a special function which is just available in high quality “Blended” mode. It has the suffix “_Blended_Wrapped”. Additional to the usual parameters there is a parameter wrapLength of type UInt32 (32 bit unsigned integer). Here you can have an integer value which determines the amount of pixels until the text will be word-wrapped. So in our case with a width of the window of 500 pixels the setting of wrapLength to 250 for example would result in a word-wrap when a word would exceed 250 pixels.

Overview: Rendering modes

The following list summarizes all the functions and most important properties for the three differen rendering modes.

  1. Solid
    • transparency by colorkey (0 pixel)
    • very fast but low quality
    • 8-bit palettized RGB surface
    • Functions
      • TTF_RenderText_Solid(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderUTF8_Solid(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderUNICODE_Solid(font: PTTF_Font; text: PUInt16; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderGlyph_Solid(font: PTTF_Font; ch: UInt16; fg: TSDL_Color): PSDL_Surface
  2. Shaded
    • antialiasing
    • slower than solid rendering, but high quality
    • 8-bit palettized RGB surface
    • Functions
      • TTF_RenderText_Shaded(font: PTTF_Font; text: PAnsiChar; fg, bg: TSDL_Color): PSDL_Surface
      • TTF_RenderUTF8_Shaded(font: PTTF_Font; text: PAnsiChar; fg, bg: TSDL_Color): PSDL_Surface
      • TTF_RenderUNICODE_Shaded(font: PTTF_Font; text: PUInt16; fg, bg: TSDL_Color): PSDL_Surface
      • TTF_RenderGlyph_Shaded(font: PTTF_Font; ch: UInt16; fg, bg: TSDL_Color): PSDL_Surface
  3. Blended
    • transparency (alpha channel)
    • antialiasing
    • slow but very high quality
    • 32-bit unpalettized (RGBA) surface
    • Functions
      • TTF_RenderText_Blended(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderUTF8_Blended(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderUNICODE_Blended(font: PTTF_Font; text: UInt16; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderText_Blended_Wrapped(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color; wrapLength: UInt32): PSDL_Surface
      • TTF_RenderGlyph_Blended(font: PTTF_Font; ch: UInt16; fg: TSDL_Color): PSDL_Surface

All the functions will return nil on failure to create a SDL_Surface.

Converting a SDL_Surface into a SDL_Texture

Okay, now let’s proceed to the next lines of code.

//convert SDL_Surface to SDL_Texture
  sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface1);

//rendering of the texture
  SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(5000);

In the previous table you saw all the functions to generate SDL_Surfaces with a certain text. Now we need to transform it into a SDL_Texture. The function to do so by SDL_CreateTextureFromSurface as known.

Then the result will be rendered to the window. For simplicity the third and fourth argument for SDL_RenderCopy() are nil which means that the SDL_Texture with the text will be stretched to the window. The rendered result is shown for 5000 ms (5 seconds).

We can proceed to the cleaning process.

//cleaning procedure
  TTF_CloseFont(ttfFont);
  TTF_Quit;

  SDL_FreeSurface(sdlSurface1);
  SDL_DestroyTexture(sdlTexture1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow(sdlWindow1);

//shutting down video subsystem
  SDL_Quit;

All the allocated memory has to be free’d now. The font is free’d by procedure

TTF_CloseFont(font: PTTF_Font)

and the TrueType engine is quit by procedure

TTF_Quit.

“sdlSurface1” is free’d by procedure

SDL_FreeSurface(surface: PSDL_Surface)

and the texture, renderer and window as known. After that SDL 2.0 is shut down.

Remark: DO NOT create text surfaces on the fly!

As for the loading of bitmap files, while it is possible to directly load the surface to the texture without declaring a surface by combining the surface creation with the texture creation as follows:

sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, TTF_RenderText_Shaded(ttfFont, 'Hello World!', sdlColor1, sdlColor2));

Don’t do this, because you have no handle (the surface pointer) to free the surface memory afterwards.

Have fun working with texts :-).

← previous Chapter | next Chapter →

Logical Resolution

Last updated on November 20th, 2021

Now an awesome feature of SDL 2.0 will be presented which is fixing a big major problem for many programmers. It concerns the resolution of the application or game.

The Logical Resolution in SDL 2.0

If you create a game, often there are a lot of questions you just can solve partly by planning. The main question is: What exact resolution are the users going to use to play the game? Do their monitors support the resolution you decide for? Do the user/gamer use a wide screen monitor? And so on. Depending on the answers to these questions you may choose for sprite/image file resolutions. If you are going for a target resolution of 640 x 480 (VGA), you will choose less detailed source images as if you go for 1024 x 768 (XGA), or even widescreen 1680 x 1050 (WSXGA+) or any other high detail resolution. If every monitors/graphic boards would be capable of displaying all possible resolutions, this wouldn’t be much of a problem. The system would just switch to the necessary resolution and voila the game is running and looking like expected. Unfortunately though, you cannot expect a system whose maximum resolution allows for XGA to display WSXGA+ resolution. Also, even if the system is capable of displaying very high resolutions, there is no guarantee that it will be able to switch down to VGA resolution.

Furthermore, if you optimize a game for VGA resolution, you will choose the sprites/images according to fit into a 640 x 480 pixels screen. Lets say you are doing a racing game. You choose a car image of 64 pixels width and 32 pixels height. This fills a good amount of the screen in VGA resolution. In WSXGA+ resolution the car’s width and height will appear less than half, it will appear to be tiny. SDL 2.0 provides a solution to circumvent many of the troubles related to resolution settings: the logical resolution.

Let’s have a look at the code.

program SDL_LogicalResolution;

uses SDL2;

const
  Border:  TSDL_Rect = (x: 0;   y:   0; w: 640; h: 480);
  Quarter: TSDL_Rect = (x: 320; y: 240; w: 320; h: 240);

var
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;

begin

  // initilization of video subsystem
  if SDL_Init(SDL_INIT_VIDEO) < 0 then
    Halt;

  if SDL_CreateWindowAndRenderer(0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP, @sdlWindow1, @sdlRenderer) <> 0 then
    Halt;

  // set scaling filter
  SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 'linear');  

  // set logical resolution
  if SDL_RenderSetLogicalSize(sdlRenderer, 640, 480) <> 0 then
    Halt;

  // draw red border
  SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
  SDL_RenderDrawRect(sdlRenderer, @Border);

  // draw green quarter
  SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, SDL_ALPHA_OPAQUE);
  SDL_RenderDrawRect(sdlRenderer, @Quarter);

  // render to window for 3 seconds
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(3000);

  // clear memory
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  // closing SDL2
  SDL_Quit;

end.

This program basically draws two rectangles, a red one and a green one. They are defined by these constants.

const
  Border:  TSDL_Rect = (x: 0;   y:   0; w: 640; h: 480);
  Quarter: TSDL_Rect = (x: 320; y: 240; w: 320; h: 240);

The const “Border” is located at (0/0) and has a width of 640 px and a height of 480 px. “Quarter” is located at (320/240) and has a width of 320 px and a height of 240. Let’s assume you have a modern monitor and render these two rectangles fullscreen at a typical resolution of 1680×1050 or even higher. You would expect a result to be something like this:

Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
This is the expected result if your monitor has a resolution of 1680×1050 px, and you render a red rectangle at position (0/0) width dimension 640×480 px, and a green rectangle at position (320/240) with dimension 320×240 px.

But since we introduced a logical resolution in the program, we really get something like this:

Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
This is the result on a 1680×1050 px monitor resolution, if a logical size of 640×480 px is used. Notice the black border (letter boxing) to the left and right of the red rectangle, because of the different aspect ratios, 16:10 (monitor) and 4:3 (640×480 target resolution).

How does SDL2 achieve a Logical Resolution?

Although your monitor resolution may be much larger (e.g. 1680×1050 px) than what your program is expecting (640×480 px), SDL2 will scale everything up or down just to fit your real resolution the best!

The function SDL_RenderSetLogicalSize() is used to achieve a logical resolution and returns 0 on success and the negative error code on failure.

SDL_RenderSetLogicalSize(renderer: PSDL_Renderer; w: SInt32; h: SInt32): SInt32.

It sets a device independent resolution for rendering. It will make use of scaling functions internally to fit the target resolution to the actual screen/device resolution. If you want to display a game in VGA (640 x 480) resolution on a XGA (1024 x 768) resolved screen, SDL_RenderSetLogicalSize() will scale up everything as necessary to fill the screen. Even if the ration of the device is different, for example you are trying to have a 4:3 ratio resolution on a wide screen 16:9 monitor, this function will just put some bars at appropriate places (see screenshot above) and fit the targeted resolution to the device resolution as good as possible. This is demonstrated in the example code. A target resolution of 640 pixels width x 480 pixels height (4:3 ratio) should be achieved in a device (window) of width x height of 1680 x 1050 pixels (16:10 ratio).

Back to the code. Fullscreen rendering at desktop resolution is achieved by the SDL_WINDOW_FULLSCREEN_DESKTOP flag.

  if SDL_CreateWindowAndRenderer(0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP, @sdlWindow1, @sdlRenderer) <> 0 then
    Halt;

And then the logical size is set as discussed by SDL_RenderSetLogicalSize(). By the way, it is strongly recommended to set the scaling filter (SDL_SetHint()) to a better quality filter, because heavy scaling is done behind the scenes by SDL_RenderSetLogicalSize().

  // set scaling filter
  SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 'linear');
  
  // set logical resolution
  if SDL_RenderSetLogicalSize(sdlRenderer, 640, 480) <> 0 then
    Halt;

Actually that’s it :-), everything else, drawing and rendering rectangles and cleaning up memory is known to you.

← previous Chapter | next Chapter →

SDL2_image: Loading Image Files with Different Formats

Last updated on November 20th, 2021

Image files to use in games and applications often are available in other formats than simple bitmap files (e.g. jpg, png, tif and so forth), because often bitmap files need a significant higher amount of disk space memory.

Also it would be desirable to load an image file directly and create a SDL2 texture from it instead of firstly creating a SDL2 surface and then creating a SDL2 texture from the surface (as seen in the chapter about bitmap loading).

The SDL2_image Unit

Here comes the SDL2_image unit into play. It allows:

  • Loading of many common image formats
  • Supported formats: ICO, CUR, BMP, GIF, JPG, LBM, PCX, PNG, PNM, TIF, XCF, XPM, XV, WEBP
  • Creation of SDL2 texture from image files directly

These features are obviously not part of native SDL 2.0. SDL2_image is an official extension of SDL 2.0, which is developed and maintained by the same developers. Therefore, before right jumping into the code we need the corresponding library files.

Installing SDL2_image

  • download the most recent version of the Runtime Binaries of the SDL2_image library for your system
  • install the library according to your system (Win32/64, Linux, Mac OS X)

SDL2_image Installing Instructions for Windows

Download the corresponding SDL2_image package depending on your system (32 bit or 64 bit) and extract the file. You will end up with a SDL2_image.dll and some further dlls which are necessary for support of some image formats. Copy all these files to your system folder, e.g. for Windows XP 32 bit C:\WINDOWS\system32\. If you are not sure about your system folder, you should copy all these files to the same folder where the source code file (.pas or .pp) of your SDL 2.0 program is.

IMG_Load and IMG_LoadTexture

SDL2 Texture and Surface relation diagram
Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
An image file (left) can be loaded as a surface or a texture and then be rendered to the screen.

Have a look at the flow diagram below which is an extended version of the diagram seen in the chapter about loading of bitmap files. You see it is extended by two function with the prefix IMG instead of SDL, namely IMG_LoadTexture() and IMG_Load(). Both of these functions allow to load image files of all the supported file formats mentioned above. Also you see that IMG_LoadTexture() creates a texture directly from the image file, so we skip the step of first creating a SDL2 surface from the image file.

Let’s try the following image files (dimensions: 200 x 200 pixels, formats: bmp, jpg and png) but feel free to use any other image file you like.

Coding example with SDL2_image

And now let’s start with some code. I will show the code as a whole first and after that I will discuss it in smaller pieces from top to bottom until the “end.” 🙂

program Chapter4_SDL2;

uses SDL2, SDL2_image;

var
sdlWindow1 : PSDL_Window;
sdlRenderer : PSDL_Renderer;
sdlTexture1 : PSDL_Texture;

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;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then HALT;

  sdlTexture1 := IMG_LoadTexture(sdlRenderer, 'C:\fpsdl.bmp');
  if sdlTexture1 = nil then HALT;
  SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil);
  SDL_RenderPresent (sdlRenderer);
  SDL_Delay(2000);

  SDL_DestroyTexture(sdlTexture1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  //shutting down video subsystem
  SDL_Quit;

end.

Well, here it is. This code will create a window of dimensions 500 x 500 pixels which is showing the “Freepascal meets SDL” image for two seconds. After that the window closes automatically. The following screenshot shows the result of the example program.

Now let’s get into the code step by step.

program Chapter4_SDL2;

uses SDL2, SDL2_image;

var
sdlWindow1 : PSDL_Window;
sdlRenderer : PSDL_Renderer;
sdlTexture1 : PSDL_Texture;

Now the first eight lines are discussed. Well, the first line starts a Pascal program as usual. In contrast to the previous chapter, the uses clause is extended by SDL2_image. To be clear again, native SDL 2.0 has no support for different image formats, except for BMP image files. Although native SDL 2.0 allows for loading of BMP image files, it just allows for creation of SDL_Surfaces, but we would like to create SDL_Textures.

We need a texture variable to which we can load the image information of an image file. This will be “sdlTexture1” of PSDL_Texture type. All these variable types are pointer types which is indicated by the captial “P” at the beginning of the variable types’ names.

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;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then HALT;

As any Pascal program the main program’s begin-end block is initiated by “begin”. The initilization of SDL 2.0 is started as discussed in detail in the last chapter by SDL_Init().

After successful initialization of SDL 2.0 a window with title “Window1”  and a renderer is created as known from a previous chapter.

Creation of SDL_Texture and Rendering in SDL 2.0

  sdlTexture1 := IMG_LoadTexture(sdlRenderer, 'C:\fpsdl.bmp');
  if sdlTexture1 = nil then HALT;
  SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil);
  SDL_RenderPresent (sdlRenderer);
  SDL_Delay(2000);

Now we load the image file to the SDL_Texture we called “sdlTexture1”. The function to do this is

IMG_LoadTexture(renderer: PSDL_Renderer; _file: PAnsiChar): PSDL_Texture

This function is provided by SDL2_image. Its prefix is IMG instead of SDL for native SDL 2.0 functions. That function is why we needed to insert SDL2_image in the uses clause. The parameters of this function are a renderer, that is “sdlRenderer” for us, and as a second the absolute or relative path to an image file, for us it is “C:\fpsdl.bmp”. Of course you may use any other directory to store/load the image or even use a different image. The function will recognize the image file’s format automatically, so feel free to load any of the allowed formats. If the loading fails, for instance you gave a wrong path as argument, this function will return nil.

Next we would like the successfully loaded image in “sdlTexture1” to be rendererd for which reason we pass it to the renderer by function

SDL_RenderCopy(renderer: PSDL_Renderer; texture: PSDL_Texture; srcrect: PSDL_Rect; dstrect: PSDL_Rect): SInt32.

At first this function asks for a renderer (and indirectly for the related window) to which we would like to copy the texture. In our case this will be “sdlRenderer” again. Next the texture to be copied to the renderer/window is required, this is “sdlTexture1” here. The last two parameters are named “srcrect” and “dstrect” and of type PSDL_Rect. PSDL_Rect is a SDL 2.0 predefined record to define rectangles, hence the name. I will not go into details about this here, but in the next chapter (Chapter 5) we will learn more about PSDL_Rect, although it will be in another context. For simplicity we just use nil as argument here. This makes the function to pass the full texture to the renderer/window and stretching it to the dimensions of the window. So the 200 x 200 pixel image is strechted to 500 x 500 pixels, the latter being the width and height of the window. This function returns 0 on success and the negative error code on failure.

Finally everything gets rendered to the window by the SDL_RenderPresent(Renderer) procedure as known from the previous chapter. To be able to see it, we wait with a 2 seconds delay.

Clean up the memory in SDL 2.0

  SDL_DestroyTexture(sdlTexture1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  //shutting down video subsystem
  SDL_Quit;

end.

We first created a window, then a renderer and finally a texture. So now we go the opposite way, first destroy the texture, then the renderer and finally the window. The procedures  are:

SDL_DestroyTexture(texture: PSDL_Texture)

SDL_DestroyRenderer(renderer: PSDL_Renderer)

and

SDL_DestroyWindow(window: PSDL_Window).

After removing the objects from memory, SDL 2.0 has to be quit as seen in the previous chapter.

Wow, we finally made it. Congratulations, this chapter is finished :-). The next chapter is waiting though.

← previous Chapter | next Chapter →