Tag Archives: mouse

Event Handling in SDL3


What are Events in Programming?

Event handling is a major concept in game and application programming. It allows for the user to interact dynamically with your program. Whenever you move your mouse cursor, press or release a key on the keyboard, use a touch screen or maximize/minimize the application window, all these interactions (and many, many more) are recognized as so-called events.

Events in SDL3

Whenever an event occurs in SDL3, for instance a key gets pressed on the keyboard, all the information related to that specific event is stored in a TSDL_Event record. Depending on the event type (e.g. mouse motion, key pressed on a keyboard, maximizing the application window) there is a different event field available in the record.

For example for a mouse motion you can read out the x and y position of the cursor (in the motion field). In contrast there is no sense in having x and y values for a pressed key on the keyboard, but to know which specific key has been pressed on the keyboard (in the key field).

Event Types, Event Fields and Event Structures

There are many more types of events than a mouse motion and a key being pressed on a keyboard. Think of using a joystick, using a touchscreen, minimizing/maximizing the application window, and so forth. There are plenty of different events that could occur.

All these event types have certain names, e.g. the event type which indicates a mouse motion is called SDL_EVENT_MOUSE_MOTION. All names of event types start with SDL_EVENT_, then often follows the device it is related to, e.g. MOUSE, KEYBOARD, GAMEPAD, JOYSTICK, PEN, CAMERA, and finally the actual action, e. g. MOTION, BUTTON_DOWN, ADDED, … .

A few selected examples are described below:

  • SDL_EVENT_MOUSE_MOTION
    • created by a mouse motion
    • field name: motion
    • provides x- and y-coordinates of mouse position
    • provides relative distance to last x- and y-coordinate
    • provides state of the pressed buttons (during motion)
  • SDL_EVENT_MOUSE_BUTTON_DOWN / SDL_EVENT_MOUSE_BUTTON_UP
    • created by mouse button press / release
    • field name: button
    • provides the button which has been pressed / released
    • provides click rate (single click, double click, …)
    • provides x- and y-coordinates of mouse position
  • SDL_EVENT_KEY_DOWN, SDL_EVENT_KEY_UP
    • created by a keyboard button press / release
    • field name: key
    • provides key code, scan code and possible key modifiers for the pressed / released button
  • SDL_EVENT_WINDOW_RESIZED, SDL_EVENT_WINDOW_MOVED
    • created when the application window is resized / moved
    • field name: window
  • SDL_EVENT_QUIT
    • created when the application window is quit (e. g. click on “X”)
    • field name: quit

The full list according to the official SDL3 documentation is linked here.

This list is overwhelmingly long but don’t worry, as soon as you get the concept behind events you will easily understand which of these event types will play a role for the applications you like to develop.

Relationship between Event Type, Event Field and Event Structure

The event type determines the available event field, which determines the event structure.

Let’s assume you press a key. This will create an event of type SDL_EVENT_KEY_DOWN. This event provides an event field key which allows access to an event (record) structure.

  TSDL_KeyboardEvent = record
      type_: TSDL_EventType;        {*< SDL_EVENT_KEY_DOWN or SDL_EVENT_KEY_UP  }
      reserved: cuint32;
      timestamp: cuint64;           {*< In nanoseconds, populated using SDL_GetTicksNS()  }
      windowID: TSDL_WindowID;      {*< The window with keyboard focus, if any  }
      
      ...
      
    end; 

The first four fields of the event record structure is the same for all events: their event type, a reserved field, a timestamp when the event was triggered and the window id, hence, in which application window has the event been triggered.

Then follow event specific fields, which are dependent of the specific event as described in the list above. For a keyboard event these may be key codes, for a mouse motion event this may be the mouse position.

Some event types can share the same event field and structure. For example, the event types SDL_EVENT_KEY_DOWN and SDL_EVENT_KEY_UP which are generated by pressing or releasing a key share the same event structure since the information are the same.

Keyboard Event and Mouse Event in Action

Let’s dive into the example code.

program SDL3_EventHandling;

uses SDL3;

var
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlEvent: TSDL_Event;
  sdlPoint: TSDL_FPoint;
  RunLoop: Boolean = True;

begin

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

  // create window and renderer
  if not SDL_CreateWindowAndRenderer('SDL3 Events', 500, 500, 0, @sdlWindow1, @sdlRenderer) then
    Halt;

  // render and show cleared window with grey background color
  SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE);
  SDL_RenderClear(sdlRenderer);

  // set point coordinates at center of window
  sdlPoint.x := 255;
  sdlPoint.y := 255;

  // run the (game) loop until it should stop (= false); triggered by escape or q key
  while RunLoop do
  begin

    // run the event loop until all events in queue have been treated
    while SDL_PollEvent(@sdlEvent) do
    begin
      case sdlEvent.type_ of

        SDL_EVENT_KEY_DOWN:
          begin
            case sdlEvent.key.key of
              SDLK_W: sdlPoint.y := sdlPoint.y-1;
              SDLK_A: sdlPoint.x := sdlPoint.x-1;
              SDLK_S: sdlPoint.y := sdlPoint.y+1;
              SDLK_D: sdlPoint.x := sdlPoint.x+1;
              SDLK_SPACE:
                begin
                  SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE);
                  SDL_RenderClear(sdlRenderer);
                end;
              SDLK_ESCAPE, SDLK_Q: RunLoop := False;
            end;
            // set yellow drawing color
            SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 0, SDL_ALPHA_OPAQUE);
          end;

        SDL_EVENT_MOUSE_MOTION:
          begin
            sdlPoint.x := sdlEvent.motion.x;
            sdlPoint.y := sdlEvent.motion.y;

            // set red drawing color
            SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
          end;

      end;
    end;

    // render the point
    SDL_RenderPoint(sdlRenderer, sdlPoint.x, sdlPoint.y);
    SDL_RenderPresent(sdlRenderer);
    SDL_Delay(20);
  end;

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

  // quitting SDL3
  SDL_Quit;

end. 

The result depends on your mouse movement in the application window (red dots) and/or the pressed W-, A-, S- or D- keys on the keyboard (yellow dots).

The initial lines of code are:

program SDL3_EventHandling;

uses SDL3;

var
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlEvent: TSDL_Event;
  sdlPoint: TSDL_FPoint;
  RunLoop: Boolean = True;

begin

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

  // create window and renderer
  if not SDL_CreateWindowAndRenderer('SDL3 Events', 500, 500, 0, @sdlWindow1, @sdlRenderer) then
    Halt;

We initialize a SDL3 application with a 500 by 500 pixels window as known.

The TSDL_Event variable “sdlEvent” stores the events generated by the user of the application. We use it later to read out the event information of each individual event that gets created.

We also need a variable “sdlPoint” of known type TSDL_FPoint to describe a point with float point coordinates.

Finally there is a simple Pascal boolean variable “RunLoop”, which is initilized to true.

  // render and show cleared window with grey background color
  SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE);
  SDL_RenderClear(sdlRenderer);

  // set point coordinates at center of window
  sdlPoint.x := 255;
  sdlPoint.y := 255;

Also known from previous chapters, we set up a grey drawing color (RGB: 50, 50, 50) for the background and render it.

Also we set the initial coordinates of the point which is at the center of the window.

  // run the (game) loop until it should stop (= false); triggered by escape or q key
  while RunLoop do
  begin

    // run the event loop until all events in queue have been treated
    while SDL_PollEvent(@sdlEvent) do
    begin                                        

We now enter two while loops. The first, outer while loop keeps the application running as long as the “RunLoop” variable is true. The loop is exited as soon as “RunLoop” equals false.

For every application cycle (outer while loop), we enter a second, inner while loop. It runs as long as the SDL3 function SDL_PollEvent(pointer of TSDL_Event) equals to true. SDL_PollEvent() equals to true as long as there are still events in the event queue.

Every time the SDL_PollEvent() is called, it feeds the event type and the specific event information to the event of the argument and deletes the event from the event queue. In our case, the information is fed to “sdlEvent”. To be precise, the argument does not need the event itself but rather its pointer, so we use the @ operator to get its pointer. That is why we call SDL_PollEvent() by

SDL_PollEvent(@sdlEvent)

as the while loop condition.

The type_ field

Every event provides the type_ field. The event type can be read out from the field type_, hence we check for the type in sdlEvent.type_ by a case-statement.

case sdlEvent.type_ of

        SDL_EVENT_KEY_DOWN:
          begin
            case sdlEvent.key.key of
              SDLK_W: sdlPoint.y := sdlPoint.y-1;
              SDLK_A: sdlPoint.x := sdlPoint.x-1;
              SDLK_S: sdlPoint.y := sdlPoint.y+1;
              SDLK_D: sdlPoint.x := sdlPoint.x+1;
              SDLK_SPACE:
                begin
                  SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE);
                  SDL_RenderClear(sdlRenderer);
                end;
              SDLK_ESCAPE, SDLK_Q: RunLoop := False;
            end;
            // set yellow drawing color
            SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 0, SDL_ALPHA_OPAQUE);
          end;

In our example program we distinguish and treat only two event types by a case statement, namely SDL_EVENT_KEY_DOWN and SDL_EVENT_MOUSE_MOTION. Every other possible event type is ignored by our program.

The SDL_EVENT_KEY_DOWN Event

If _type is a SDL_EVENT_KEY_DOWN event, a begin-end block is entered.

We know from this event type, that the event field which has all the event’s information is called key. The event structure you have there is as follows:

SDL_KeyboardEvent = record
      type_: TSDL_EventType;        {*< SDL_EVENT_KEY_DOWN or SDL_EVENT_KEY_UP  }
      reserved: cuint32;
      timestamp: cuint64;           {*< In nanoseconds, populated using SDL_GetTicksNS()  }
      windowID: TSDL_WindowID;      {*< The window with keyboard focus, if any  }
      which: TSDL_KeyboardID;       {*< The keyboard instance id, or 0 if unknown or virtual  }
      scancode: TSDL_Scancode;      {*< SDL physical key code  }
      key: TSDL_Keycode;            {*< SDL virtual key code  }
      mod_: TSDL_Keymod;            {*< current key modifiers  }
      raw: cuint16;                 {*< The platform dependent scancode for this event  }
      down: cbool;                  {*< true if the key is pressed  }
      repeat_: cbool;               {*< true if this is a key repeat  }
    end;

Additionally to the already seen first four fields, we have

  • the keyboard id,
  • the scan code,
  • the key code,
  • pressed key modifiers (shift, ctrl, …),
  • a raw scan code,
  • if the key is pressed (if not, it means it got released),
  • and if the event is triggered by the repeat mechanism, which kicks in if you keep a key pressed for a longer time.

We are interested in the key code of the pressed key, so we read out the information from the event structure’s key field, hence the information is found in

sdlEvent.key.key

Basically a key code is a constant which corresponds to a certain, unique hexadecimal number. For example the constant for the W-key is SDLK_W, its hexadecimal number is 77 (decimal number 119). All key codes’ names start with SDLK_.

With the case statement we look for specific key codes that may have been pressed. The key code names are self-explanatory. We check for the W-, A-, S-, D-. Space-, Escape- and Q-key by the key codes SDLK_W, SDLK_A, SDLK_S, SDLK_D, SDLK_SPACE, SDLK_ESCAPE and SDLK_Q. A list of all SDL3 key codes and their hexadecimal numbers is found in this link.

Depending on the pressed key, we change the position of the point (W: up, A: left, S: down, D: right), clear the window with the background color (space) or set the “RunLoop” variable to false, which effectively stops the program.

At the end of this event’s block the drawing color is changed to yellow.

The SDL_EVENT_MOUSE_MOTION Event

The second case of the case statement is the mouse motion event.

        SDL_EVENT_MOUSE_MOTION:
          begin
            sdlPoint.x := sdlEvent.motion.x;
            sdlPoint.y := sdlEvent.motion.y;

            // set red drawing color
            SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
          end;

      end;

The event field for a mouse motion is called motion. It’s record structure is shown below:

SDL_MouseMotionEvent = record
      type_: TSDL_EventType;            {*< SDL_EVENT_MOUSE_MOTION  }
      reserved: cuint32;
      timestamp: cuint64;               {*< In nanoseconds, populated using SDL_GetTicksNS()  }
      windowID: TSDL_WindowID;          {*< The window with mouse focus, if any  }
      which: TSDL_MouseID;              {*< The mouse instance id or SDL_TOUCH_MOUSEID  }
      state: TSDL_MouseButtonFlags;     {*< The current button state  }
      x: cfloat;                        {*< X coordinate, relative to window  }
      y: cfloat;                        {*< Y coordinate, relative to window  }
      xrel: cfloat;                     {*< The relative motion in the X direction  }
      yrel: cfloat;                     {*< The relative motion in the Y direction  }
    end;                                                                              

If an SDL_EVENT_MOUSE_MOTION event is found, we change the x- and y-values according to the x- and y-values (mouse coordinates) provided by the event.

Finally we change the drawing color to red.

      end;
    end;

    // render the point
    SDL_RenderPoint(sdlRenderer, sdlPoint.x, sdlPoint.y);
    SDL_RenderPresent(sdlRenderer);
    SDL_Delay(20);
  end;                  

The first “end;” closes the case statement for the event types, the second “end;” closes the inner while loop which checks for pending events in the event queue. After that we are back in the outer application-“RunLoop”. Every cycle we render the point once, update the rendered window and delay for 20 milliseconds.

The delay is necessary to prevent the CPU from running as fast as it can, which is kind of short-circuiting the program. You should never do this for your CPU’s sake.

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

  // quitting SDL3
  SDL_Quit;

end. 

Nothing new here, the renderer and window get destroyed and SDL3 is quit.

Congratulations! Event handling in SDL3 is now known to you.

I will will elaborate on some specifics here, which may or may not be interesting to you. Please skip them if you are not interested right now.

The Repeat-Delay Problem (Keyboard)

The _repeat field let’s you know if the corresponding key on a keyboard is in repeat mode. For most operation systems the repeat mode kicks in after a short delay when you keep a key pressed.

You may try out to open up a simple text editor. If you press any key that has a letter (e.g. “a”-key), in the text editor you will see an “a”. And if you keep the key pressed after a short delay the repeat mode kicks in and rapidly several further a’s are coming up. If you are in repeat mode for a certain key, repeat_ has true boolean value, otherwise false.

Especially for games, you may want to turn off the initial delay if you keep a key pressed and let the constant repeat mode kick in without delay. Let’s assume you have a spaceship which should move left on pressing the “a”-key. – You do not want to have this happening with a delay.

A simple solution to the “repeat-delay”-problem: Instead of looking for the actual event being repeatedly triggered by an key event, use a switch which gets turned on if the key down event is occuring and which is turned off if the key up event is occuring.

Example: Let’s assume the spaceship moving left on pressing the “a”-key again. Instead of using the repeat_ field at all, you make a switch (e.g. MoveSpaceshipLeft := True) if the “a”-key is pressed. As soon as the SDL_EVENT_KEY_UP event is triggered for the “a”-key, the switch is turned off (e.g. MoveSpaceshipLeft := False).

The Difference between Key code and Scan code

You may have noticed that we used key codes in the example code, but there is another field in the keyboard event structure, which reads scan code. Scan codes also represent keys on the keyboard. So, why wouldn’t we use these instead?

The scan code refers to a specific physical location of a key on the keyboard. The scan code is referenced to the typical US keyboard layout (QWERTY layout). The term “QWERTY” just refers to the the first six letters from left to right in the first row with letters on a typical US keyboard. For example: The German keyboard layout (QWERTZ layout) is similar to the US one (for most of the letter keys), though the “Y”-key and the “Z”-key have exactly opposite positions (hence QWERTZ for the German layout in contrast to QWERTY for the US layout). If you press the “Z”-key on a German keyboard, the returned scan code will represent the letter “Y”, since the position of the key (independent of the layout) is equal to the position of the “Y” key on an US keyboard. Scan codes are layout-independent.

The key code refers to the virtual representation of the key according to the keyboard layout. Here you consider the layout, hence key codes are layout-dependent. As discussed before the scan code for the “Z”-key on a German keyboard will return a letter “Y”, since the key has the location of the “Y”-key of an US keyboard. In contrast the key code will correctly return that the “Z”-key has been pressed.

Think of the famous WASD key arrangement (arrangement of the four keys “W”, “A”, “S” and “D”) in the US layout, even if you have a keyboard without any latin letters, you may want to use the four keys which are physically at the WASD location to move a game character forward (“W”), left (“A”), backward (“S”) or right (“D”). The labeling of the keys doesn’t matter in that case and the keys are not used to input some text. That is when you use scan codes.

Text Input is done in SDL3 in another way!

The shown way of handling keyboard events could be used to create kind of a text input mechanism. DO NOT do it this way! SDL3 provides a special and much more convenient event for that.

This modified quote from the SDL 1.2 to SDL 2.0 migration guide sums it up excellently:

Use SDL_EVENT_KEY_DOWN to treat the keyboard like a 101-button joystick now. Text input comes from somewhere else.

Key Modifiers: Shift, Ctrl, Alt, …

The _mod field is a 16 bit unsigned integer (corresponds to Pascal’s Word) and represents key modifiers (ctrl, alt, shift, num lock, caps lock, …), which may be pressed while pressing another key. Their names always start with SDL_KMOD_. For example, the left shift key has the name SDL_KMOD_LSHIFT. The full list of possible key modifiers is not very long and found in the link.

If a SDL_EVENT_KEY_DOWN event is caught, you can check the _mod field too see if a key modifiers was pressed at the same time.

Let’s assume you want to have your application to be quit by the user pressing the left or the right ctrl key and “Q”. So you read out the key code for “Q” and check if _mod equals to SDL_KMOD_CTRL.

Previous Chapter | Next Chapter

[Title image created with https://deepimg.ai; public domain]

A Custom Mouse Cursor

Last updated on November 20th, 2021

Any good game has a custom mouse cursor. You may think it would be a good idea to have a SDL2 surface or SDL2 texture and render it as any other sprite right at the mouse position to simulate a mouse cursor. DO NOT do this! The mouse cursor is handled separatly from the other rendering to have it smooth and working in critical situations. 

The following code shows how to set up a custom mouse cursor with SDL2 the correct way.

program SDL_MouseCursor;

uses SDL2, SDL2_image;

var
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlSurface1: PSDL_Surface;
  sdlMouseCursor: PSDL_Cursor;
  sdlEvent: TSDL_Event;
  ExitLoop: Boolean = False;

begin

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

  SDL_CreateWindowAndRenderer(500, 500, SDL_WINDOW_SHOWN, @sdlWindow1, @sdlRenderer);
  if (sdlWindow1 = nil) or (sdlRenderer = nil) then Halt;

  sdlSurface1 := IMG_Load('Cursor.png' );
  if sdlSurface1 = nil then Halt;

  // create and set new mouse cursor
  sdlMouseCursor := SDL_CreateColorCursor(sdlSurface1, 8, 8);
  if sdlMouseCursor = nil then Halt;

  SDL_SetCursor(sdlMouseCursor);

  while ExitLoop = False do
  begin

    // exit loop if mouse button pressed
    while SDL_PollEvent(@sdlEvent) = 1 do
      if sdlEvent.type_ = SDL_MOUSEBUTTONDOWN then
        ExitLoop := True;

    SDL_SetRenderDrawColor(sdlRenderer, 128, 128, 128, SDL_ALPHA_OPAQUE);
    SDL_RenderClear(sdlRenderer);

    SDL_RenderPresent(sdlRenderer);
    SDL_Delay( 20 );
  end;

  SDL_FreeCursor(sdlMouseCursor);

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

  //shutting down video subsystem
  SDL_Quit;

end.

To have a custom mouse cursor we need a variable of type PSDL_Cursor. We call it “sdlMouseCursor” here.

The result looks like this:

Custom Mouse Cursor in SDL2
Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
The blue cross with the yellow outline is the mouse cursor on the grey canvas/window. 
  sdlSurface1 := IMG_Load('Cursor.png' );
  if sdlSurface1 = nil then Halt;

  // create and set new mouse cursor
  sdlMouseCursor := SDL_CreateColorCursor(sdlSurface1, 8, 8);
  if sdlMouseCursor = nil then Halt;

  SDL_SetCursor(sdlMouseCursor);  

This is the interesting part of the code with regard to creating a custom mouse cursor. The cursor’s image is defined by a SDL surface. We create the SDL surface as known from a previous chapter from a png image file to “sdlSurface1” here.

The custom mouse cursor is created by the following function, which returns nil on error.

SDL_CreateColorCursor(surface: PSDL_Surface; hot_x: SInt32; hot_y: SInt32): PSDL_Cursor

It needs the surface to use as cursor image and two coordinates (hot_x/hot_y) as arguments. They determine where the actual hitting point for this cursor is. Since the example cursor image is of dimensions 16×16 px and represents a cross, the “hot” (hitting) coordiates are (8/8), hence the cross’ center is used for hitting a button or something. In contrast you may imagine a typical arrow shaped mouse cursor, where the hitting point has to be adjusted to be right on the tip of the arrow in the arrow’s image.

If the cursor creation has been successful, it is necessary to set it to be the actual cursor. You may have created many different cursors, so tell SDL which one to use by the following procedure.

SDL_SetCursor(cursor: PSDL_Cursor)

The remaining part of the code is just rendering a 500 by 500 pixels window with a grey (128, 128, 128) background that is updated as long as no mouse button has been pressed.

Finally do not forget to free the mouse cursor by SDL_FreeCursor(mouse cursor) as shown.

← previous Chapter | next Chapter →

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