Step-by-Step SDL Basic UI Controls: UITextEdit (1)

sdl-ui-textedit-dev

1. Introduction

In the previous post, we implemented a UIButton and mastered the basics of drawing and interacting with SDL UI controls. In this post, we’ll build a single-line text input box, UITextEdit. This is a fundamental component of many UI systems, used for things like login forms, nickname inputs, and search bars.

Since SDL doesn’t have a built-in text input box, we need to handle the details ourselves, such as the cursor, text selection, IME (Input Method Editor), and focus management.

2. Functional Requirements Analysis

Core Features

  • Keyboard character input
  • Cursor movement (arrow keys, Home/End)
  • Deletion and backspace
  • Copy, cut, and paste (to be implemented in the next chapter)
  • Placeholder display
  • Blinking cursor
  • Password display
  • IME support (for Chinese, Japanese, etc., to be implemented in the next chapter)

Interaction Details

  • Mouse click to gain focus
  • Clicking outside the control to lose focus
  • Highlighting selected text
  • Rendering temporary input text (Composition) (to be implemented in the next chapter)

3. SDL Prerequisites

  • SDL_StartTextInput / SDL_StopTextInput: Enable/disable text input.
  • SDL_TEXTINPUT: Character input event.
  • SDL_TEXTEDITING: IME editing event.
  • SDL_KEYDOWN: Key presses for arrow keys, backspace, delete, etc.
  • SDL_SetClipboardText / SDL_GetClipboardText: Clipboard operations.
  • SDL_ttf: Rendering single-line text.

4. Class Design

We’ll have UITextEdit inherit from UIControl (or your custom base class) to maintain a consistent interface style with UIButton.

UITextEdit.h

C++

#pragma once
#include "UIControl.h"

class UITextEdit : public UIControl
{
public:
	UITextEdit();
	~UITextEdit() = default;

	void Update(uint32_t dt) override;
	void Render() override;
	bool Handle(SDL_Event* e) override;
	void OnTextChanged(const std::string& old) override;
	void OnMouseOver() override;
	void OnMouseOut() override;
	void OnFocusChanged() override;
	std::string GetSelectionText() const;
	bool IsReadOnly() const { return m_isreadonly; }
	inline void SetReadOnly(bool _readonly) { m_isreadonly = _readonly; }

	bool IsPassword() const { return m_ispassword; }
	void SetPassowrd(bool _password);

	inline void SetLength(int len) { m_length = len; }
	inline int GetLength() const { return m_length; }
	void SelectAll();
	void SetNumOnly(bool _numOnly = true) { m_numonly = _numOnly; }
protected:
	void calcTextOffset();
	void calcCharacterWidths();
	SDL_Rect getSelectionRect();
	void setCursorPos(int x);
	void clearSelection();
	SDL_Rect calcCursorRect(int pos);
	//SDL_Rect calcImeListRect();
	//SDL_Rect calcImeListRect(const SDL_Rect& rcCursor);
	void removeCursorCharater();
	void removeSelectedText();
	void replaceSelectionText(const std::string& _utf8);
	std::string getRectText() const;
	std::string getDisplayText() const;
	bool isCursorRight() const;
	bool isAllDigits(const std::string& str);
	bool isAllDigits(const std::wstring& str);
protected:
	struct
	{
		bool visiable{ false };
		int position{ 0 };
	} cursor;

	struct
	{
		int start{ 0 };
		int end{ 0 };
		int pos1{ 0 };
		int pos2{ 2 };
	}selection;
	struct
	{
		std::string text;
		int width;
		int height;
		int index{ -1 };
	}editing;

	bool IsTextSelected() const { return selection.end - selection.start > 0; }

	std::wstring m_wtext;

	std::vector<int> m_chw;
	bool m_isreadonly{ false };
	bool m_ispassword{ false };
	bool m_numonly{ false };
	std::vector<std::wstring> m_ims;
	uint32_t m_ticks{ 0 };
	int m_textwidth{ 0 };
	int m_textheight{ 0 };
	int m_textoffset{ 0 };
	int m_length{ 32 };
	const int IME_WAIT_SIZE = 8;
};

5. Design Highlights

Focus and Placeholder

  • Clicking inside the control gains focus.
  • Losing focus means no longer accepting keyboard input.
  • A gray placeholder is displayed when the text is empty.

Input and Deletion

  • Normal character input is handled by SDL_TEXTINPUT.
  • Backspace / Delete removes characters at the corresponding position.
  • When replacing a selection, first delete the selected text, then insert the new characters.

Cursor and Selection

  • Cursor movement: Use arrow keys, Home, or End.
  • Text selection: Use Shift + arrow keys to update m_selectStart and m_selectEnd.
  • When rendering, first draw the selection background, then the text and cursor.

Paste, Copy, Cut

  • Copy: SDL_SetClipboardText.
  • Paste: SDL_GetClipboardText.
  • Cut: Copy + delete the selected text.

IME Support

  • SDL_TEXTEDITING provides a temporary input string and cursor position.
  • Render this temporary string at the cursor’s position.

Blinking Cursor

  • Toggle the m_cursorVisible state at regular intervals.

6. Usage Example

In the init method of your scene:

C++

auto tb = std::shared_ptr<UITextEdit>(new UITextEdit);
tb->SetPosition({ 190, 200 });
tb->SetSize({120,25});
AddNode(tb);

7. Conclusion

In this post, we designed the header file for a single-line UITextEdit control and analyzed its features and implementation ideas.

In the next part, we will continue to implement:

  • Copy, paste, and cut functionality.
  • IME support.

Sample code for this chapter: https://github.com/84378996/simple_sdl_gui_tutorials/tree/main/tutorials04

Demo video for this chapter:

Leave a Reply