Text Input in SDL3


For text input there is a special way and a special event in place in SDL3.

The SDL_EVENT_TEXT_INPUT Event and PAnsiChar?

The SDL_EVENT_TEXT_INPUT event has the follwing structure:

TSDL_TextInputEvent = record
      type_: TSDL_EventType;       {*< SDL_EVENT_TEXT_INPUT  }
      reserved: cuint32;
      timestamp: cuint64;          {*< In nanoseconds, populated using SDL_GetTicksNS()  }
      windowID: TSDL_WindowID;     {*< The window with keyboard focus, if any  }
      text: PAnsiChar;             {*< The input text, UTF-8 encoded  }
    end;

You can see that apart from the first four fields, which all events have in common, it has just one further field, called text. It is of the PAnsiChar type. If you are familar with Pascal and its String type for text handling, you may wonder, why in this record the text field uses this unusual type.

SDL3 is written in C/C++, and in these languages pointers to null-terminated character “strings” are used to store and handle text. For better compatibility and easier translation of SDL3 (and other C/C++ projects) the Pascal compiler provides a similar type, PAnsiChar.

In principle it is no big deal to use PAnsiChar instead of String, even though Pascal programmers prefer the much more convenient String variables. In short: Strings in Pascal are always safe to use, whereas C’s strings may cause memory leaks if handled wrongly.

The best practice is to convert a C string as soon as possible into a Pascal string, especially if string manipulations follow.

The Text Field does not contain what is considered a Text!

It may surprise you to read, that this text field does not contain a whole text but just a single (or a bunch of) character(s), which represent a word.

In Western languages (especially English) with Latin characters you always only submit one character at a time by each key stroke and compose words and sentences this way. But even in some Western languages you sometimes must combine a Latin character with an accent (which means two key strokes, but just one character) to get special characters. In many non-Western languages (think of e. g. Asian languages) words and sentences are constructed by highly individual signs. To construct these signs you need many key strokes before they are submitted to the text field which then generates the correct sign or sometimes several signs. (I would be glad to give a more detailed explanation here, please feel free to contact me to improve this paragraph.)

How is Text Input done in SDL3?

This is the code:

program SDL_TextInput;

uses
  SDL3;

var
  sdlWindow1: PSDL_Window;
  sdlEvent: TSDL_Event;
  RunLoop: Boolean = True;
  Text1: AnsiString = '';

begin

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

  // create a window (necessary to receive events)
  sdlWindow1 := SDL_CreateWindow('SDL3 Text Input', 500, 500, 0);

  while RunLoop do
  begin
    while SDL_PollEvent(@sdlEvent) do
    begin
      case sdlEvent.type_ of

        // quit program
        SDL_EVENT_QUIT: RunLoop := False;

        SDL_EVENT_KEY_DOWN:
          case sdlEvent.key.key of

            // switch text input mode on/off by ENTER/RETURN key
            SDLK_KP_ENTER, SDLK_RETURN:
              begin
                Write('Text Input Mode is ' );
                if SDL_TextInputActive(sdlWindow1) then
                  begin
                    SDL_StopTextInput(sdlWindow1);
                    WriteLn('OFF');
                  end else
                  begin
                    SDL_StartTextInput(sdlWindow1);
                    WriteLn('ON');
                  end;
              end;

            // remove last character
            SDLK_BACKSPACE:
                begin
                  Delete(Text1, Length(Text1), 1);
                  WriteLn( '                --> ' + Text1 );
                end;

          end;

        SDL_EVENT_TEXT_INPUT:
          begin
            Write( 'Text Input: "', sdlEvent.text.text, '"' );
            Text1 := Text1 + StrPas(sdlEvent.text.text);
            WriteLn( ' --> ' + Text1 );
          end;

      end;
    end;
    SDL_Delay(20);
  end;

  // clear memory
  SDL_DestroyWindow(sdlWindow1);

  // quit SDL3
  SDL_Quit;

end.                

The result looks like this:

As you see, in this example we have a blank window and just use the text output to your command line. In the Lazarus IDE you can get the raw output window by Ctrl + Alt + O.

program SDL_TextInput;

uses
  SDL3;

var
  sdlWindow1: PSDL_Window;
  sdlEvent: TSDL_Event;
  RunLoop: Boolean = True;
  Text1: AnsiString = '';

begin

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

  // create a window (necessary to receive events)
  sdlWindow1 := SDL_CreateWindow('SDL3 Text Input', 500, 500, 0);

  while RunLoop do
  begin
    while SDL_PollEvent(@sdlEvent) do
    begin
      case sdlEvent.type_ of 

Most of this first code bit is known to you from previous chapters. We have no renderer variable because we do not need to render something.

Instead we have “Text1” variable of the Pascal AnsiString type. In contrast to the classical ShortString, it has no length limit. If you just use String, it depends on your compiler settings if they are treated as AnsiString or ShortString. We want to be clear about this here!

We create a window “sdlWindow1” without a renderer. You may ask yourself, why do we need a window if we do not render something to it. You are right, in principle we wouldn’t need one. We still need it, because the events are connected to a window, without a window we have no event detection.

As known from previous chapter about event handling, we enter two while loops. The first one for the application to run and the second one to gather all the events each cycle.

        // quit program
        SDL_EVENT_QUIT: RunLoop := False;

        SDL_EVENT_KEY_DOWN:
          case sdlEvent.key.key of

            // switch text input mode on/off by ENTER/RETURN key
            SDLK_KP_ENTER, SDLK_RETURN:
              begin
                Write('Text Input Mode is ' );
                if SDL_TextInputActive(sdlWindow1) then
                  begin
                    SDL_StopTextInput(sdlWindow1);
                    WriteLn('OFF');
                  end else
                  begin
                    SDL_StartTextInput(sdlWindow1);
                    WriteLn('ON');
                  end;
              end;

            // remove last character
            SDLK_BACKSPACE:
                begin
                  Delete(Text1, Length(Text1), 1);
                  WriteLn( '                --> ' + Text1 );
                end;

          end;

        SDL_EVENT_TEXT_INPUT:
          begin
            Write( 'Text Input: "', sdlEvent.text.text, '"' );
            Text1 := Text1 + StrPas(sdlEvent.text.text);
            WriteLn( ' --> ' + Text1 );
          end;

We in the inner “event loop”, we have three events, two of whom are new, SDL_EVENT_QUIT and SDL_EVENT_TEXT_INPUT.

The SDL_EVENT_QUIT

SDL_EVENT_QUIT is triggered when the application gets the signal to quit, e. g. by clicking on the “X” of a typical application window. We didn’t catch this event in the last chapter, you may have noticed that clicking the “X” didn’t do anything.

SDL_EVENT_TEXT_INPUT Functions

Before getting to the SDL_EVENT_TEXT_INPUT, let’s look at the SDL_EVENT_KEY_DOWN part. If you press the enter or return key, the function SDL_TextInputActive(window) checks, if the text input mechanism is started or not. Obviously, its return value is boolean.

If the text input mechanism is started already, it is stopped by the function SDL_StopTextInput(window). If it is not started, it is started by the function SDL_StartTextInput(window). Only if t he mechanism is started, SDL_EVENT_TEXT_INPUT events are created and can be caught.

This is accompanied by a text which is generated reading “Text Input Mode is ON” or “… OFF” accordingly.

Let proceed to the SDL_EVENT_TEXT_INPUT part: If the the text input mechanism has been started by SDL_StartTextInput(window), we get SDL_EVENT_TEXT_INPUT events as soon as keys are pressed. The new character is stored in:

sdlEvent.text.text

The Pascal function Write() prints out PAnsiChar strings (or rather characters in this case) natively and will show us directly what character has been created.

In line

Text1 := Text1 + StrPas(sdlEvent.text.text);

we concatenate the new character to the “Text1” string. Note how the PAnsiChar character is converted into a native Pascal ShortString by Pascal function StrPas(PAnsiChar string) beforehand. The concatenation will convert it to an AnsiString result in “Text1”.

The resulting text in “Text1” is printed out by the Pascal WriteLn statement.

It is remarkable how easy it is to get even complex characters added to the text without any hassle. Try doing this with just SDL_EVENT_KEY_DOWN events and to make accents possible alone would take a lot of effort.

Removing a Character from the Text

The text input mechanism does not provide a way to remove a character natively. Also other features for convenience, like moving a cursor into the text and inserting a character in between are not supported natively.

As “Text1” is a convenient Pascal string now, we can use Pascal’s string routines to implement all the features we need with little effort. You see, if you press the backspace key with key code SDLK_BACKSPACE, we use Pascal’s Delete(string, index, number of characters) and Length(string) functions to pick the last character of the string an delete it.

The resulting text is printed out.

      end;
    end;
    SDL_Delay(20);
  end;

  // clear memory
  SDL_DestroyWindow(sdlWindow1);

  // quit SDL3
  SDL_Quit;

end.                 

The application’s loop speed is reduced again by SDL_Delay().

If the application loop is left, the window and the SDL3 system is destroyed and shut down accordingly.

Great! You learned about the text input now.

Previous Chapter | Next Chapter

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

Leave a Reply

Your email address will not be published. Required fields are marked *